diff --git a/platforms/android/.github/workflows/bytecode-compilers.yml b/.github/workflows/bytecode-compilers.yml similarity index 70% rename from platforms/android/.github/workflows/bytecode-compilers.yml rename to .github/workflows/bytecode-compilers.yml index 73ef54354..7bd368a97 100644 --- a/platforms/android/.github/workflows/bytecode-compilers.yml +++ b/.github/workflows/bytecode-compilers.yml @@ -6,33 +6,31 @@ name: Build bytecode compilers # # Output: one artifact per (engine, host), named `bytecode-compiler--`, # each containing `//` so the downloads merge straight into -# tools/bytecode-compiler/bin/. +# platforms/android/tools/bytecode-compiler/bin/. # # The final `bundle` job merges them all into a single `bytecode-compiler-bin` -# artifact laid out exactly like tools/bytecode-compiler/bin/ — download it, unzip, -# and drop its contents into tools/bytecode-compiler/bin/ as-is. +# artifact laid out exactly like platforms/android/tools/bytecode-compiler/bin/ — download it, unzip, +# and drop its contents into platforms/android/tools/bytecode-compiler/bin/ as-is. # # NOTE ON COMPATIBILITY: the produced bytecode must be loadable by the engine -# library the runtime ships. Hermes/PrimJS runtimes link prebuilt .so's and -# QuickJS/QuickJS-NG compile from vendored source, so the refs below must be kept -# in step with what the runtime actually bundles. Pin these to exact commits once +# library the runtime ships. Hermes/PrimJS runtimes link prebuilt binaries and +# QuickJS-NG compiles from the vendored source, so the refs below must be kept in +# step with what the runtime actually bundles. Pin these to exact commits once # validated instead of moving branches. on: workflow_dispatch: inputs: hermes_ref: - description: "facebook/hermes ref" + # MUST match UPSTREAM_COMMIT in build-hermes, which is what produced the + # prebuilt both platforms link. See scripts/download_hermes.sh. + description: "facebook/hermes ref (match the prebuilt release)" default: "10a40b5f1de81d007cba6afe567c4ba7a0b45f5d" - quickjs_ref: - # MUST match the quickjs submodule pin (BC_VERSION must agree with the - # runtime). See .gitmodules / scripts/vendor-engines-as-submodules.sh. - description: "bellard/quickjs ref (match the submodule pin)" - default: "04be246001599f5995fa2f2d8c91a0f198d3f34c" quickjs_ng_ref: - # MUST match the quickjs_ng submodule pin (BC_VERSION must agree with the - # runtime, or JS_ReadObject rejects the blob). See .gitmodules / - # scripts/vendor-engines-as-submodules.sh. - description: "quickjs-ng/quickjs ref (match the submodule pin)" + # MUST match the vendored source in vendor/quickjs/source_ng, or + # JS_ReadObject rejects the blob: the compiler writes its own BC_VERSION + # and the runtime accepts only its own. d950d55e == BC_VERSION 27, which + # is what the vendored quickjs.c defines. + description: "quickjs-ng/quickjs ref (match vendor/quickjs/source_ng)" default: "d950d55e950dd7994a96c669c31efa967b8b79f3" primjs_ref: description: "lynx-family/primjs ref" @@ -40,7 +38,6 @@ on: env: HERMES_REPO: https://github.com/facebook/hermes - QUICKJS_REPO: https://github.com/bellard/quickjs QUICKJS_NG_REPO: https://github.com/quickjs-ng/quickjs PRIMJS_REPO: https://github.com/lynx-family/primjs @@ -80,7 +77,7 @@ jobs: # accepts branch/tag names). GitHub allows fetching arbitrary SHAs. run: | set -euxo pipefail - ref="${{ github.event.inputs.hermes_ref || 'master' }}" + ref="${{ github.event.inputs.hermes_ref || '10a40b5f1de81d007cba6afe567c4ba7a0b45f5d' }}" git init hermes git -C hermes remote add origin "$HERMES_REPO" git -C hermes fetch --depth 1 origin "$ref" @@ -112,6 +109,10 @@ jobs: bin="$(find build -type f \( -name hermesc -o -name hermesc.exe \) | head -n1)" test -n "$bin" cp "$bin" "out/hermes/${{ matrix.host }}/" + if [ "${{ runner.os }}" = "macOS" ]; then + codesign --sign - --force "out/hermes/${{ matrix.host }}/$(basename "$bin")" + codesign --verify --verbose "out/hermes/${{ matrix.host }}/$(basename "$bin")" + fi - uses: actions/upload-artifact@v4 with: @@ -121,81 +122,12 @@ jobs: path: out if-no-files-found: error - # --------------------------------------------------------------------------- - # QuickJS (bellard) — build via its own Makefile (libquickjs.a, which injects - # -D_GNU_SOURCE -DCONFIG_VERSION), then link our blob-emitting shim. - # win32-x64 is CROSS-compiled on Linux via bellard's CONFIG_WIN32 (mingw-w64); - # win32-arm64 is not possible (mingw-w64 targets x86 only). - # --------------------------------------------------------------------------- - quickjs: - name: quickjs (${{ matrix.host }}) - runs-on: ${{ matrix.runner }} - strategy: - fail-fast: false - matrix: - include: - - { host: linux-x64, runner: ubuntu-24.04 } - - { host: linux-arm64, runner: ubuntu-24.04-arm } - - { host: darwin-x64, runner: macos-15-intel } - - { host: darwin-arm64, runner: macos-14 } - - { host: win32-x64, runner: ubuntu-24.04 } # mingw cross-compile - steps: - - uses: actions/checkout@v4 - - name: Install toolchain (Linux) - if: runner.os == 'Linux' - run: | - set -euxo pipefail - sudo apt-get update - sudo apt-get install -y build-essential - # mingw-w64 for the win32-x64 cross build. - if [ "${{ matrix.host }}" = "win32-x64" ]; then sudo apt-get install -y mingw-w64; fi - # macOS: make + clang ship with the Xcode command-line tools. - - - name: Clone QuickJS - shell: bash - run: | - set -euxo pipefail - ref="${{ github.event.inputs.quickjs_ref || 'master' }}" - git init quickjs - git -C quickjs remote add origin "$QUICKJS_REPO" - git -C quickjs fetch --depth 1 origin "$ref" - git -C quickjs -c advice.detachedHead=false checkout FETCH_HEAD - - - name: Build lib + shim - shell: bash - env: - SHIM: ${{ github.workspace }}/tools/bytecode-compiler/native/qjs-compile.c - run: | - set -euxo pipefail - cd quickjs - jobs="$(getconf _NPROCESSORS_ONLN)" - out="../out/quickjs/${{ matrix.host }}"; mkdir -p "$out" - if [ "${{ matrix.host }}" = "win32-x64" ]; then - # CONFIG_WIN32=y selects the x86_64-w64-mingw32- cross toolchain. - make -j"$jobs" CONFIG_WIN32=y libquickjs.a - # bellard's win32 LIBS are "-lm -lpthread" (winpthreads provides the - # pthread_* and clock_gettime symbols quickjs.c uses). - x86_64-w64-mingw32-gcc -O2 -DNSBC_MAGIC='"NSBCQJS"' -I. \ - -o "$out/nsbc-quickjs.exe" "$SHIM" libquickjs.a -lm -lpthread - # Cross-built Windows binary — not runnable on the Linux host. - else - # Use bellard's own Makefile so CONFIG_VERSION / _GNU_SOURCE and the - # correct source set come from upstream. - make -j"$jobs" libquickjs.a - cc -O2 -DNSBC_MAGIC='"NSBCQJS"' -I. -o "$out/nsbc-quickjs" "$SHIM" libquickjs.a -lm - "$out/nsbc-quickjs" || true # usage line (exit 2) proves it links & runs - fi - - - uses: actions/upload-artifact@v4 - with: - name: bytecode-compiler-quickjs-${{ matrix.host }} - # Root the artifact at `out/` so it carries the full - # `quickjs//` path and merges straight into bin/. - path: out - if-no-files-found: error - # --------------------------------------------------------------------------- # QuickJS-NG — CMake build of libqjs, then our shim (same JS_* API). + # + # There is no bellard/quickjs job: the runtime dropped that tree, and its + # QUICKJS engine is now an alias for QUICKJS_NG (see + # platforms/android/test-app/runtime/build.gradle). # --------------------------------------------------------------------------- quickjs-ng: name: quickjs-ng (${{ matrix.host }}) @@ -226,7 +158,7 @@ jobs: shell: bash run: | set -euxo pipefail - ref="${{ github.event.inputs.quickjs_ng_ref || 'master' }}" + ref="${{ github.event.inputs.quickjs_ng_ref || 'd950d55e950dd7994a96c669c31efa967b8b79f3' }}" git init quickjs-ng git -C quickjs-ng remote add origin "$QUICKJS_NG_REPO" git -C quickjs-ng fetch --depth 1 origin "$ref" @@ -235,9 +167,11 @@ jobs: - name: Build lib + shim shell: bash env: - SHIM: ${{ github.workspace }}/tools/bytecode-compiler/native/qjs-compile.c + SHIM: ${{ github.workspace }}/platforms/android/tools/bytecode-compiler/native/qjs-compile.c run: | set -euxo pipefail + # Note: the workflow lives at the repository root, so the shim path is + # platforms/android/-prefixed; the tool tree itself is under Android. # Build the shim as a CMake target so it uses the SAME toolchain + CRT # as the engine lib. Linking a separately-compiled (clang) shim against # the MSVC-built qjs.lib fails with __imp_*/CRT-mismatch link errors on @@ -254,6 +188,10 @@ jobs: out="out/quickjs-ng/${{ matrix.host }}"; mkdir -p "$out" bin="$(find quickjs-ng/build -type f \( -name 'nsbc-quickjs-ng' -o -name 'nsbc-quickjs-ng.exe' \) | head -n1)" test -n "$bin"; cp "$bin" "$out/" + if [ "${{ runner.os }}" = "macOS" ]; then + codesign --sign - --force "$out/nsbc-quickjs-ng" + codesign --verify --verbose "$out/nsbc-quickjs-ng" + fi [ "${{ runner.os }}" = "Windows" ] || "$out/nsbc-quickjs-ng" || true - uses: actions/upload-artifact@v4 @@ -295,7 +233,7 @@ jobs: shell: bash run: | set -euxo pipefail - ref="${{ github.event.inputs.primjs_ref || 'develop' }}" + ref="${{ github.event.inputs.primjs_ref || '9c1fe150179b86f0ae5c6fbe670af7d76eca9df6' }}" git init primjs git -C primjs remote add origin "$PRIMJS_REPO" git -C primjs fetch --depth 1 origin "$ref" @@ -304,7 +242,7 @@ jobs: - name: Add shim as a GN target shell: bash env: - SHIM: ${{ github.workspace }}/tools/bytecode-compiler/native/primjs-compile.c + SHIM: ${{ github.workspace }}/platforms/android/tools/bytecode-compiler/native/primjs-compile.c run: | set -euxo pipefail # Build as C++ (.cc); the LEPUS_* API is extern "C" so linkage is fine. @@ -335,6 +273,10 @@ jobs: ninja -C out/Default nsbc-primjs out="../out/primjs/${{ matrix.host }}"; mkdir -p "$out" cp out/Default/nsbc-primjs "$out/nsbc-primjs" + if [ "${{ runner.os }}" = "macOS" ]; then + codesign --sign - --force "$out/nsbc-primjs" + codesign --verify --verbose "$out/nsbc-primjs" + fi "$out/nsbc-primjs" || true # usage line (exit 2) proves it links & runs - uses: actions/upload-artifact@v4 @@ -347,12 +289,12 @@ jobs: # --------------------------------------------------------------------------- # Bundle — merge every per-(engine, host) artifact into a single artifact laid - # out exactly like tools/bytecode-compiler/bin/, ready to download-and-paste. + # out exactly like platforms/android/tools/bytecode-compiler/bin/, ready to download-and-paste. # --------------------------------------------------------------------------- bundle: name: bundle bin/ runs-on: ubuntu-24.04 - needs: [hermes, quickjs, quickjs-ng, primjs] + needs: [hermes, quickjs-ng, primjs] # Run even if some engine/host builds failed, so a partial bin/ is still # published rather than nothing. if: always() @@ -369,6 +311,10 @@ jobs: - name: Show merged layout run: find bin -type f | sort + - name: Restore executable permissions + shell: bash + run: find bin -type f -path '*/darwin-*/*' -exec chmod +x {} \; + - uses: actions/upload-artifact@v4 with: name: bytecode-compiler-bin diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33b34fa13..04aa3704f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,13 @@ jobs: - name: Generate macOS metadata run: npm run metagen macos + - name: Apply QuickJS patches + # vendor/quickjs/source_ng is a pristine submodule checkout at its pinned + # commit; the NativeScript engine changes the Apple build needs (e.g. + # JS_EVAL_FLAG_COMPILE_ONLY_NO_RESOLVE for node:vm) live in + # vendor/quickjs/patches and are applied into the work tree here. + run: npm run patch-quickjs + - name: Build NativeScript run: npm run build @@ -92,6 +99,9 @@ jobs: - name: Generate macOS metadata run: npm run metagen macos + - name: Apply QuickJS patches + run: npm run patch-quickjs + - name: Run macOS test suite env: MACOS_COMMAND_TIMEOUT_MS: "1200000" @@ -148,6 +158,7 @@ jobs: npm run build-libffi METADATA_GENERATOR_ARCHS="$(uname -m)" npm run build-metagen npm run metagen macos + npm run patch-quickjs - name: Build candidate CLI run: | @@ -169,8 +180,12 @@ jobs: rm -rf ./Frameworks/hermes.xcframework ./Frameworks/hermes-headers ;; esac + # A base that still vendors QuickJS-NG as plain files can't be + # checked out over the candidate's submodule work tree. + git submodule deinit --force vendor/quickjs/source_ng git checkout --detach "${BASE_SHA}" git submodule update --init --recursive + if [ -d vendor/quickjs/patches ]; then npm run patch-quickjs; fi npm install npm run build-libffi METADATA_GENERATOR_ARCHS="$(uname -m)" npm run build-metagen diff --git a/.github/workflows/npm_release.yml b/.github/workflows/npm_release.yml index 06f4b0710..ec1d9fd57 100644 --- a/.github/workflows/npm_release.yml +++ b/.github/workflows/npm_release.yml @@ -111,6 +111,12 @@ jobs: echo NPM_VERSION=$NPM_VERSION >> $GITHUB_OUTPUT echo NPM_TAG=$NPM_TAG >> $GITHUB_OUTPUT echo BUILD_ENGINE=$BUILD_ENGINE >> $GITHUB_OUTPUT + - name: Apply QuickJS patches + # vendor/quickjs/source_ng is a pristine submodule checkout at its pinned + # commit; the NativeScript engine changes the Apple build needs (e.g. + # JS_EVAL_FLAG_COMPILE_ONLY_NO_RESOLVE for node:vm) live in + # vendor/quickjs/patches and are applied into the work tree here. + run: npm run patch-quickjs - name: Build run: ./scripts/build_all_ios.sh --${{ steps.npm_version_output.outputs.BUILD_ENGINE }} - name: Upload npm package artifact diff --git a/.github/workflows/npm_trusted_release.yml b/.github/workflows/npm_trusted_release.yml index c9808dbf0..9807e84ea 100644 --- a/.github/workflows/npm_trusted_release.yml +++ b/.github/workflows/npm_trusted_release.yml @@ -1,39 +1,46 @@ name: NPM Trusted Release -# Publishes any combination of the NativeScript runtime npm packages via npm -# trusted publishing (OIDC): +# Publishes 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. +# iOS engine packages: @nativescript/ios-{v8,hermes,jsc,quickjs} +# Android engine packages: @nativescript/android-{v8,hermes,jsc,quickjs-ng,primjs} +# React Native package: @nativescript/react-native # # Each package must be configured on npmjs.com with a trusted publisher that # points at this repository + workflow + environment. +# +# `engines` accepts any comma/space-separated combination (e.g. "v8, quickjs"), +# `all` for every engine a platform supports, or `react-native`. The `ios` and +# `android` checkboxes pick which platforms build the selected engines; primjs +# is Android-only. +# +# The Android runtime lives on a separate branch until it merges to main. +# Dispatch this workflow from main and set `build-ref` to that branch (e.g. +# android-react-native): the run then uses this workflow definition but builds +# and publishes from that branch's sources. on: workflow_dispatch: inputs: - platform: - description: "Package family to release" - required: true - type: choice - default: ios - options: - - 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." + description: "Engines to release, comma/space separated: v8, quickjs, jsc, hermes, primjs — or 'all'. Use 'react-native' for @nativescript/react-native." + required: true + type: string + default: v8 + ios: + description: "Build the selected engines for iOS (@nativescript/ios-)" + required: false + type: boolean + default: true + android: + description: "Build the selected engines for Android (@nativescript/android-)" + required: false + type: boolean + default: false + build-ref: + description: "Git ref to build from (defaults to the ref the workflow runs on). Use android-react-native for Android builds until it merges." required: false type: string - default: all release-type: description: "Version bump (patch/minor/major publish to 'latest'; prerelease uses 'preid' as the dist-tag)" required: false @@ -65,11 +72,13 @@ on: concurrency: # Avoid overlapping publishes on the same ref/package selection. - group: npm-trusted-release-${{ github.ref }}-${{ inputs.platform }}-${{ inputs.engines }} + group: npm-trusted-release-${{ github.ref }}-${{ inputs.engines }}-${{ inputs.ios }}-${{ inputs.android }} cancel-in-progress: false env: XCODE_VERSION: "26.2.0" + # V8 14.9's headers need std::atomic_ref (libc++ from LLVM 19), so NDK r29+. + ANDROID_NDK_VERSION: "29.0.14206865" jobs: matrix: @@ -77,220 +86,166 @@ jobs: runs-on: ubuntu-latest permissions: {} outputs: - targets: ${{ steps.compute.outputs.targets }} - summary: ${{ steps.compute.outputs.summary }} + apple_targets: ${{ steps.compute.outputs.apple_targets }} + android_targets: ${{ steps.compute.outputs.android_targets }} + publish_targets: ${{ steps.compute.outputs.publish_targets }} steps: - name: Compute matrix id: compute env: - PLATFORM: ${{ inputs.platform }} ENGINES: ${{ inputs.engines }} + IOS: ${{ inputs.ios }} + ANDROID: ${{ inputs.android }} run: | set -euo pipefail - 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: '', - }); + raw=$(printf '%s' "$ENGINES" | tr '[:upper:]' '[:lower:]' | tr ',' ' ') - 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, - }; - }; + IOS_ENGINES="v8 hermes jsc quickjs" + ANDROID_ENGINES="v8 hermes jsc quickjs primjs" - 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: '', - }); + apple=() + android=() - const fail = (msg) => { console.error(msg); process.exit(1); }; - const targets = []; + contains() { + local x="$1"; shift + local e + for e in "$@"; do [ "$e" = "$x" ] && return 0; done + return 1 + } + add_apple() { contains "$1" ${apple[@]+"${apple[@]}"} || apple+=("$1"); } + add_android() { contains "$1" ${android[@]+"${android[@]}"} || android+=("$1"); } - 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; - }; + # The Android quickjs package keeps the upstream quickjs-ng name. + android_pkg_for() { + case "$1" in + quickjs) echo quickjs-ng ;; + *) echo "$1" ;; + esac + } - 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; - }; + for token in $raw; do + [ -n "$token" ] || continue + [ "$token" = "quickjs-ng" ] && token=quickjs + case "$token" in + all) + if [ "$IOS" != "true" ] && [ "$ANDROID" != "true" ]; then + echo "::error::'all' requires at least one platform (ios and/or android) to be checked." + exit 1 + fi + if [ "$IOS" = "true" ]; then + for e in $IOS_ENGINES; do add_apple "ios-$e"; done + fi + if [ "$ANDROID" = "true" ]; then + for e in $ANDROID_ENGINES; do add_android "android-$(android_pkg_for "$e")"; done + fi + ;; + react-native) + add_apple react-native + ;; + v8|hermes|jsc|quickjs|primjs) + matched=false + if [ "$IOS" = "true" ] && [ "$token" != "primjs" ]; then + add_apple "ios-$token" + matched=true + fi + if [ "$ANDROID" = "true" ]; then + add_android "android-$(android_pkg_for "$token")" + matched=true + fi + if [ "$matched" != "true" ]; then + if [ "$token" = "primjs" ] && [ "$IOS" = "true" ]; then + echo "::error::primjs is Android-only; check the android box to release @nativescript/android-primjs." + else + echo "::error::Engine '$token' selected but no platform checked. Enable ios and/or android." + fi + exit 1 + fi + if [ "$token" = "primjs" ] && [ "$IOS" = "true" ]; then + echo "::notice::primjs has no iOS package; releasing the Android package only." + fi + ;; + *) + echo "::error::Unknown engine '$token'. Expected v8, quickjs, jsc, hermes, primjs, react-native, or all." + exit 1 + ;; + esac + done - 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 [ ${#apple[@]} -eq 0 ] && [ ${#android[@]} -eq 0 ]; then + echo "::error::No release targets resolved from engines='$ENGINES' (ios=$IOS, android=$ANDROID)." + exit 1 + fi - if (!targets.length) { - fail(`No packages selected for platform="${platform}" engines="${raw}".`); + to_json() { + if [ $# -eq 0 ]; then printf '[]'; return; fi + local out="[" first=true e + for e in "$@"; do + $first || out+="," + out+="\"$e\"" + first=false + done + printf '%s]' "$out" } - 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" + all_targets=(${apple[@]+"${apple[@]}"} ${android[@]+"${android[@]}"}) + { + echo "apple_targets=$(to_json ${apple[@]+"${apple[@]}"})" + echo "android_targets=$(to_json ${android[@]+"${android[@]}"})" + echo "publish_targets=$(to_json ${all_targets[@]+"${all_targets[@]}"})" + } >> "$GITHUB_OUTPUT" + echo "Apple targets: $(to_json ${apple[@]+"${apple[@]}"})" + echo "Android targets: $(to_json ${android[@]+"${android[@]}"})" - build: - name: Build ${{ matrix.key }} + build-apple: + name: Build ${{ matrix.target }} needs: matrix - runs-on: ${{ matrix.runner }} + if: ${{ needs.matrix.outputs.apple_targets != '[]' }} + runs-on: macos-26 permissions: contents: read strategy: fail-fast: false matrix: - include: ${{ fromJson(needs.matrix.outputs.targets) }} + target: ${{ fromJson(needs.matrix.outputs.apple_targets) }} steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 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: + ref: ${{ inputs.build-ref || github.ref }} fetch-depth: 0 - # The Android build fetches (and patches) the engine submodules it needs - # itself, via `npm run setup`. - submodules: ${{ matrix.platform == 'android' && 'false' || 'recursive' }} + submodules: recursive + # react-native also builds the Android tools jars, which compile + # against LFS-stored jars under platforms/android/test-app/**/libs. + lfs: ${{ matrix.target == 'react-native' }} - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version: 24 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: 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' }} + - name: Install Dependencies run: | npm install python3 -m pip install --upgrade pip six if ! command -v ld64.lld >/dev/null; then - brew list lld || brew install lld + brew list lld &>/dev/null || brew install lld fi if ! command -v cmake >/dev/null; then - brew list cmake || brew install cmake + brew list cmake &>/dev/null || brew install cmake fi if [ ! -x /usr/local/bin/cmake ]; then sudo mkdir -p /usr/local/bin 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 @@ -299,20 +254,29 @@ jobs: PACKAGE_VERSION: ${{ inputs.version }} PREID: ${{ inputs.preid }} NPM_TAG_OVERRIDE: ${{ inputs.npm-tag }} - 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 }} + TARGET: ${{ matrix.target }} 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 + engine="${target#ios-}" + pkg_dir="packages/ios-${engine}" + package_name="@nativescript/ios-${engine}" + tarball_basename="nativescript-ios-${engine}" + npm_tag_target="ios-${engine}" + echo "IOS_VARIANT=ios-${engine}" >> "$GITHUB_ENV" + fi - pushd "$PACKAGE_DIR" >/dev/null + pushd "$pkg_dir" >/dev/null if [ -n "$package_version" ]; then npm version "$package_version" --no-git-tag-version >/dev/null elif [ "$release_type" = "prerelease" ]; then @@ -323,7 +287,7 @@ jobs: NPM_VERSION=$(node -e "console.log(require('./package.json').version)") popd >/dev/null - NPM_TAG=$(NPM_VERSION="$NPM_VERSION" node "./$NPM_TAG_SCRIPT" "$NPM_TAG_TARGET") + NPM_TAG=$(NPM_VERSION="$NPM_VERSION" node ./scripts/get-npm-tag.js "$npm_tag_target") if [ -n "$npm_tag_override" ]; then case "$npm_tag_override" in *[[:space:]]*) @@ -341,97 +305,314 @@ 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" + { + echo "NPM_VERSION=$NPM_VERSION" + echo "NPM_TAG=$NPM_TAG" + echo "PACKAGE_DIR=$pkg_dir" + echo "PACKAGE_NAME=$package_name" + echo "TARBALL_BASENAME=$tarball_basename" + } >> "$GITHUB_OUTPUT" + echo "Resolved $package_name@$NPM_VERSION (tag: $NPM_TAG)" + - name: Apply QuickJS patches + # A build-ref that vendors QuickJS needs the NativeScript engine changes + # from vendor/quickjs/patches (e.g. JS_WeakRef_Deref / JS_NewString16 + # used by vendor/quickjs/quickjs-api.c) applied onto the pristine + # submodule checkout before the iOS quickjs build. Refs without the + # vendored engine have nothing to patch. + if: ${{ matrix.target != 'react-native' }} + run: | + if [ -f scripts/apply_quickjs_patches.js ]; then + npm run patch-quickjs + else + echo "No vendored QuickJS in this ref; nothing to patch." fi - - echo "NPM_VERSION=$NPM_VERSION" >> "$GITHUB_OUTPUT" - echo "NPM_TAG=$NPM_TAG" >> "$GITHUB_OUTPUT" - echo "Resolved $PACKAGE_NAME@$NPM_VERSION (tag: $NPM_TAG)" - - name: Build iOS engine (--${{ matrix.engine }}) - if: ${{ matrix.platform == 'ios' }} + - name: Build iOS engine (${{ matrix.target }}) + if: ${{ matrix.target != 'react-native' }} env: - ENGINE: ${{ matrix.engine }} - run: ./scripts/build_all_ios.sh "--${ENGINE}" + TARGET: ${{ matrix.target }} + run: ./scripts/build_all_ios.sh "--${TARGET#ios-}" + # The package ships both halves. The Android half is generated -- both + # android/tools (the build-time jars) and android/src/main/java-runtime + # are gitignored -- so without these steps npm pack produces a tarball + # with an Android directory that has no runtime and no tools in it. + - name: Verify Android runtime sources + if: ${{ matrix.target == 'react-native' }} + run: | + if [ ! -d platforms/android/test-app ]; then + echo "::error::The checked-out ref has no Android runtime, so @nativescript/react-native would publish without its Android half. Set build-ref to a branch that contains it (e.g. android-react-native)." + exit 1 + fi + - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 + if: ${{ matrix.target == 'react-native' }} + with: + distribution: temurin + java-version: 17 - name: Build @nativescript/react-native - if: ${{ matrix.platform == 'react-native' }} + if: ${{ matrix.target == 'react-native' }} run: | ./scripts/build_all_react_native.sh + # Staging only: copies the runtime C++ and com.tns.* Java into the + # package. No SDK or NDK involved -- the consuming app compiles them. + ./scripts/build_react_native_android.sh + # Builds the metadata/dts/static-binding jars through the test-app + # Gradle build, which configures Android modules and so needs the SDK + # the runner image ships. + ./scripts/build_react_native_android_tools.sh + # Stages the iOS half and packs; run last, it is what produces the tarball. ./scripts/build_react_native_turbomodule.sh - - name: 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: - KEY: ${{ matrix.key }} - PLATFORM: ${{ matrix.platform }} - ENGINE: ${{ matrix.engine }} - ARTIFACT_DIR: ${{ matrix.artifact_dir }} - PACKAGE_NAME: ${{ matrix.package_name }} - TARBALL_BASENAME: ${{ matrix.tarball_basename }} + TARGET: ${{ matrix.target }} + PACKAGE_DIR: ${{ steps.bump.outputs.PACKAGE_DIR }} + PACKAGE_NAME: ${{ steps.bump.outputs.PACKAGE_NAME }} NPM_VERSION: ${{ steps.bump.outputs.NPM_VERSION }} NPM_TAG: ${{ steps.bump.outputs.NPM_TAG }} + TARBALL_BASENAME: ${{ steps.bump.outputs.TARBALL_BASENAME }} run: | set -euo pipefail + package_dir="$PACKAGE_DIR" tarball_file="${TARBALL_BASENAME}-${NPM_VERSION}.tgz" - if [ ! -f "${ARTIFACT_DIR}/${tarball_file}" ]; then - echo "Expected tarball ${ARTIFACT_DIR}/${tarball_file} was not produced by the build." >&2 - ls -la "$ARTIFACT_DIR" || true - exit 1 - fi - cat > "${ARTIFACT_DIR}/release-meta.json" < "$package_dir/dist/release-meta.json" </dev/null + fi + - name: Prepare Android build + run: ./scripts/setup_android.sh + - name: Bump version + id: bump + shell: bash + env: + RELEASE_TYPE: ${{ inputs.release-type }} + PACKAGE_VERSION: ${{ inputs.version }} + PREID: ${{ inputs.preid }} + NPM_TAG_OVERRIDE: ${{ inputs.npm-tag }} + TARGET: ${{ matrix.target }} + run: | + set -euo pipefail + release_type="$RELEASE_TYPE" + package_version="$PACKAGE_VERSION" + preid="$PREID" + npm_tag_override="$NPM_TAG_OVERRIDE" + pkg="${TARGET#android-}" + pkg_dir="packages/android-${pkg}" + package_name="@nativescript/android-${pkg}" + tarball_basename="nativescript-android-${pkg}" + npm_tag_target="android-${pkg}" + case "$pkg" in + v8) gradle_engine="V8-13" ;; + quickjs-ng) gradle_engine="QUICKJS_NG" ;; + hermes) gradle_engine="HERMES" ;; + jsc) gradle_engine="JSC" ;; + primjs) gradle_engine="PRIMJS" ;; + *) + echo "::error::Unknown Android engine package '$pkg'." + exit 1 + ;; + esac + if [ ! -f "$pkg_dir/package.json" ]; then + echo "::error::Missing $pkg_dir/package.json on this ref." + exit 1 + fi + + pushd "$pkg_dir" >/dev/null + if [ -n "$package_version" ]; then + npm version "$package_version" --no-git-tag-version >/dev/null + elif [ "$release_type" = "prerelease" ]; then + npm version prerelease --preid "$preid" --no-git-tag-version >/dev/null + else + npm version "$release_type" --no-git-tag-version >/dev/null + fi + NPM_VERSION=$(node -e "console.log(require('./package.json').version)") + popd >/dev/null + + NPM_TAG=$(NPM_VERSION="$NPM_VERSION" node ./scripts/get-npm-tag.js "$npm_tag_target") + if [ -n "$npm_tag_override" ]; then + case "$npm_tag_override" in + *[[:space:]]*) + echo "Invalid npm tag override '$npm_tag_override': dist-tags cannot contain whitespace." >&2 + exit 1 + ;; + esac + if printf '%s\n' "$npm_tag_override" | grep -Eq '^[0-9]+(\.[0-9]+)*$'; then + echo "Invalid npm tag override '$npm_tag_override': dist-tags must not look like semver versions." >&2 + exit 1 + fi + NPM_TAG="$npm_tag_override" + fi + if [ -n "$package_version" ] && [ "$release_type" = "prerelease" ] && [ -z "$npm_tag_override" ] && [ "$NPM_TAG" = "latest" ]; then + echo "Exact prerelease publishes must include a prerelease identifier (for example 9.0.0-preview.0)." >&2 + exit 1 + fi + { + echo "NPM_VERSION=$NPM_VERSION" + echo "NPM_TAG=$NPM_TAG" + echo "PACKAGE_DIR=$pkg_dir" + echo "PACKAGE_NAME=$package_name" + echo "TARBALL_BASENAME=$tarball_basename" + echo "GRADLE_ENGINE=$gradle_engine" + } >> "$GITHUB_OUTPUT" + echo "Resolved $package_name@$NPM_VERSION (tag: $NPM_TAG, gradle engine: $gradle_engine)" + - name: Build Android runtime (${{ steps.bump.outputs.GRADLE_ENGINE }}) + env: + GIT_COMMIT: ${{ github.sha }} + GRADLE_ENGINE: ${{ steps.bump.outputs.GRADLE_ENGINE }} + run: ./scripts/build_all_android.sh "--engine=${GRADLE_ENGINE}" --binding=napi "-PndkVersion=${ANDROID_NDK_VERSION}" + - name: Pack npm package + shell: bash + env: + GRADLE_ENGINE: ${{ steps.bump.outputs.GRADLE_ENGINE }} + PACKAGE_DIR: ${{ steps.bump.outputs.PACKAGE_DIR }} + PACKAGE_NAME: ${{ steps.bump.outputs.PACKAGE_NAME }} + NPM_VERSION: ${{ steps.bump.outputs.NPM_VERSION }} + run: | + set -euo pipefail + # Mirrors the slug the gradle build derives (see platforms/android/build.gradle). + slug=$(printf '%s' "$GRADLE_ENGINE" | tr '[:upper:]-' '[:lower:]_') + [ "$slug" = "quickjs_ng" ] && slug=quickjs + dist_dir="dist/android_${slug}_napi" + if [ ! -f "$dist_dir/package.json" ]; then + echo "::error::Expected staged package at $dist_dir; the gradle build did not produce it." + exit 1 + fi + # The gradle build stages the generic @nativescript/android package; + # stamp the per-engine name and the bumped version before packing. + node -e ' + const fs = require("fs"); + const [, path, name, version] = process.argv; + const pkg = JSON.parse(fs.readFileSync(path, "utf8")); + pkg.name = name; + pkg.version = version; + fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + "\n"); + ' "$dist_dir/package.json" "$PACKAGE_NAME" "$NPM_VERSION" + mkdir -p "$PACKAGE_DIR/dist" + (cd "$dist_dir" && npm pack --pack-destination "$GITHUB_WORKSPACE/$PACKAGE_DIR/dist") + - name: Record metadata + shell: bash + env: + TARGET: ${{ matrix.target }} + PACKAGE_DIR: ${{ steps.bump.outputs.PACKAGE_DIR }} + PACKAGE_NAME: ${{ steps.bump.outputs.PACKAGE_NAME }} + NPM_VERSION: ${{ steps.bump.outputs.NPM_VERSION }} + NPM_TAG: ${{ steps.bump.outputs.NPM_TAG }} + TARBALL_BASENAME: ${{ steps.bump.outputs.TARBALL_BASENAME }} + run: | + set -euo pipefail + package_dir="$PACKAGE_DIR" + tarball_file="${TARBALL_BASENAME}-${NPM_VERSION}.tgz" + if [ ! -f "$package_dir/dist/$tarball_file" ]; then + echo "::error::Expected tarball $package_dir/dist/$tarball_file was not produced." + exit 1 + fi + cat > "$package_dir/dist/release-meta.json" <- + ${{ !cancelled() && + needs.matrix.result == 'success' && + needs.build-apple.result != 'failure' && needs.build-apple.result != 'cancelled' && + needs.build-android.result != 'failure' && needs.build-android.result != 'cancelled' && + (needs.build-apple.result == 'success' || needs.build-android.result == 'success') }} runs-on: ubuntu-latest environment: name: ${{ inputs.dry-run && 'npm-publish-dry-run' || 'npm-publish' }} strategy: fail-fast: false matrix: - include: ${{ fromJson(needs.matrix.outputs.targets) }} + target: ${{ fromJson(needs.matrix.outputs.publish_targets) }} permissions: contents: read id-token: write @@ -446,8 +627,8 @@ jobs: registry-url: "https://registry.npmjs.org" - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: - name: npm-package-${{ matrix.key }} - path: npm-package/${{ matrix.key }} + name: npm-package-${{ matrix.target }} + path: npm-package/${{ matrix.target }} - name: Update npm (required for OIDC trusted publishing) run: | corepack enable npm @@ -458,10 +639,10 @@ jobs: id: meta shell: bash env: - KEY: ${{ matrix.key }} + TARGET: ${{ matrix.target }} run: | set -euo pipefail - meta="npm-package/${KEY}/release-meta.json" + meta="npm-package/${TARGET}/release-meta.json" if [ ! -f "$meta" ]; then echo "Missing release metadata at $meta" >&2 exit 1 @@ -470,10 +651,12 @@ jobs: NPM_TAG=$(node -e "console.log(require('./$meta').tag)") PACKAGE_NAME=$(node -e "console.log(require('./$meta').package_name)") TARBALL=$(node -e "console.log(require('./$meta').tarball)") - echo "NPM_VERSION=$NPM_VERSION" >> "$GITHUB_OUTPUT" - echo "NPM_TAG=$NPM_TAG" >> "$GITHUB_OUTPUT" - echo "PACKAGE_NAME=$PACKAGE_NAME" >> "$GITHUB_OUTPUT" - echo "TARBALL=$TARBALL" >> "$GITHUB_OUTPUT" + { + echo "NPM_VERSION=$NPM_VERSION" + echo "NPM_TAG=$NPM_TAG" + echo "PACKAGE_NAME=$PACKAGE_NAME" + echo "TARBALL=$TARBALL" + } >> "$GITHUB_OUTPUT" - name: Publish package (OIDC trusted publishing) if: ${{ vars.USE_NPM_TOKEN != 'true' }} shell: bash @@ -482,12 +665,12 @@ jobs: NPM_TAG: ${{ steps.meta.outputs.NPM_TAG }} PACKAGE_NAME: ${{ steps.meta.outputs.PACKAGE_NAME }} TARBALL: ${{ steps.meta.outputs.TARBALL }} - KEY: ${{ matrix.key }} + TARGET: ${{ matrix.target }} DRY_RUN: ${{ inputs.dry-run }} NODE_AUTH_TOKEN: "" run: | set -euo pipefail - TARBALL_PATH="npm-package/${KEY}/${TARBALL}" + TARBALL_PATH="npm-package/${TARGET}/${TARBALL}" PUBLISH_ARGS=("$TARBALL_PATH" --tag "$NPM_TAG" --access public --provenance) if [ "$DRY_RUN" = "true" ]; then PUBLISH_ARGS+=(--dry-run) @@ -507,12 +690,12 @@ jobs: NPM_TAG: ${{ steps.meta.outputs.NPM_TAG }} PACKAGE_NAME: ${{ steps.meta.outputs.PACKAGE_NAME }} TARBALL: ${{ steps.meta.outputs.TARBALL }} - KEY: ${{ matrix.key }} + TARGET: ${{ matrix.target }} DRY_RUN: ${{ inputs.dry-run }} NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} run: | set -euo pipefail - TARBALL_PATH="npm-package/${KEY}/${TARBALL}" + TARBALL_PATH="npm-package/${TARGET}/${TARBALL}" PUBLISH_ARGS=("$TARBALL_PATH" --tag "$NPM_TAG" --access public --provenance) if [ "$DRY_RUN" = "true" ]; then PUBLISH_ARGS+=(--dry-run) @@ -525,35 +708,39 @@ jobs: if: always() needs: - matrix - - build + - build-apple + - build-android - publish runs-on: ubuntu-latest permissions: {} steps: - name: Print summary env: - PLATFORM: ${{ inputs.platform }} ENGINES: ${{ inputs.engines }} + IOS: ${{ inputs.ios }} + ANDROID: ${{ inputs.android }} + BUILD_REF: ${{ inputs.build-ref }} RELEASE_TYPE: ${{ inputs.release-type }} PACKAGE_VERSION: ${{ inputs.version }} PREID: ${{ inputs.preid }} NPM_TAG_OVERRIDE: ${{ inputs.npm-tag }} DRY_RUN: ${{ inputs.dry-run }} - PACKAGES: ${{ needs.matrix.outputs.summary }} - BUILD_RESULT: ${{ needs.build.result }} + APPLE_TARGETS: ${{ needs.matrix.outputs.apple_targets }} + ANDROID_TARGETS: ${{ needs.matrix.outputs.android_targets }} + BUILD_APPLE_RESULT: ${{ needs.build-apple.result }} + BUILD_ANDROID_RESULT: ${{ needs.build-android.result }} PUBLISH_RESULT: ${{ needs.publish.result }} run: | - { - echo "| 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" + echo "Engines: $ENGINES" + echo "Platforms: ios=$IOS android=$ANDROID" + echo "Build ref: ${BUILD_REF:-$GITHUB_REF}" + echo "Release type: $RELEASE_TYPE" + echo "Exact version: $PACKAGE_VERSION" + echo "Preid: $PREID" + echo "NPM tag override: $NPM_TAG_OVERRIDE" + echo "Dry run: $DRY_RUN" + echo "Apple targets: $APPLE_TARGETS" + echo "Android targets: $ANDROID_TARGETS" + echo "Apple build result: $BUILD_APPLE_RESULT" + echo "Android build result: $BUILD_ANDROID_RESULT" + echo "Publish result: $PUBLISH_RESULT" diff --git a/.gitignore b/.gitignore index b118017cc..010eb884f 100644 --- a/.gitignore +++ b/.gitignore @@ -101,3 +101,11 @@ metadata-generator/symbol-analyzer/target/ # release as the Apple xcframework. Committing them let the .so files drift out # of step with the headers. /platforms/android/test-app/runtime/src/main/libs/hermes/ + +# @nativescript/react-native Android module build output +packages/react-native/android/.cxx/ +packages/react-native/android/build/ +# Staged by scripts/build_react_native_android_tools.sh at package time +packages/react-native/android/tools/ +# Staged by scripts/build_react_native_android.sh at package time +packages/react-native/android/src/main/java-runtime/ diff --git a/.gitmodules b/.gitmodules index fa45c83c7..8299eb02c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,9 @@ path = libffi url = https://github.com/NativeScript/libffi.git branch = darind/v8-ios +[submodule "vendor/quickjs/source_ng"] + path = vendor/quickjs/source_ng + url = https://github.com/quickjs-ng/quickjs + # The NativeScript patch is applied into the work tree by + # scripts/apply_quickjs_patches.js, so the submodule is always dirty. + ignore = dirty diff --git a/NativeScript/CMakeLists.txt b/NativeScript/CMakeLists.txt index d72159f7a..ff586f6cd 100644 --- a/NativeScript/CMakeLists.txt +++ b/NativeScript/CMakeLists.txt @@ -429,8 +429,6 @@ if(ENABLE_JS_RUNTIME) set(SOURCE_FILES ${SOURCE_FILES} # 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 diff --git a/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.cpp b/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.cpp index 193eab64c..4638f8985 100644 --- a/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.cpp +++ b/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.cpp @@ -19,8 +19,12 @@ #include "ArgConverter.h" #include "JsArgConverter.h" #include "GlobalHelpers.h" +#if !defined(NS_JSI_HOST_RUNTIME) #include "ModuleInternal.h" +#endif +#if !defined(NS_JSI_HOST_RUNTIME) #include "WorkerWrapper.h" +#endif #include #ifdef USE_MIMALLOC @@ -34,6 +38,41 @@ using namespace tns; namespace { +bool EnsureMetadataReturnType(MetadataEntry *entry) { + if (entry == nullptr) { + return false; + } + + auto &returnType = entry->getReturnType(); + if (!returnType.empty()) { + return true; + } + + const auto &signature = entry->getSig(); + const auto closeParen = signature.find(')'); + if (closeParen == std::string::npos || closeParen + 1 >= signature.size()) { + return false; + } + + const auto candidate = signature.substr(closeParen + 1); + switch (candidate[0]) { + case 'V': case 'Z': case 'B': case 'C': case 'S': + case 'I': case 'J': case 'F': case 'D': + if (candidate.size() != 1) return false; + break; + case 'L': + if (candidate.size() < 3 || candidate.back() != ';') return false; + break; + case '[': + if (candidate.size() < 2) return false; + break; + default: + return false; + } + + returnType = candidate; + return true; +} // Converts a NativeScriptException into a JS throw, the way every callback in // this tree has to. The napi tree needed no equivalent: there a native error // was reported with napi_throw and a plain return, so nothing could unwind out @@ -98,7 +137,8 @@ JsValue CallbackHandlers::CallJavaMethod(JsRuntime &rt, const JsValue &caller, c const string &methodName, MetadataEntry *entry, bool isFromInterface, bool isStatic, bool isConstructorCall, const JsValue *argv, size_t argc, - ObjectManager *objectManager) { + ObjectManager *objectManager, + bool metadataSignatureIsUnambiguous) { JEnv jEnv; jclass clazz; @@ -109,7 +149,48 @@ JsValue CallbackHandlers::CallJavaMethod(JsRuntime &rt, const JsValue &caller, c MethodCache::CacheMethodInfo mi; bool isSuper = false; - if ((entry != nullptr) && entry->getIsResolved()) { + if (metadataSignatureIsUnambiguous && entry != nullptr && + !EnsureMetadataReturnType(entry)) { + entry = nullptr; + metadataSignatureIsUnambiguous = false; + } + + if (metadataSignatureIsUnambiguous && entry != nullptr && entry->memberId == nullptr) { + JEnv metadataEnv; + auto metadataClass = metadataEnv.FindClass(className); + if (metadataClass != nullptr) { + jmethodID metadataMethod; + jclass resolvedMetadataClass = metadataClass; + if (isStatic && isFromInterface) { + auto methodAndClassPair = metadataEnv.GetInterfaceStaticMethodIDAndJClass( + className, methodName, entry->getSig()); + metadataMethod = methodAndClassPair.first; + if (methodAndClassPair.second != nullptr) { + resolvedMetadataClass = methodAndClassPair.second; + } + } else { + metadataMethod = isStatic + ? metadataEnv.GetStaticMethodID(metadataClass, methodName, + entry->getSig()) + : metadataEnv.GetMethodID(metadataClass, methodName, + entry->getSig()); + } + if (metadataMethod != nullptr) { + entry->memberId = reinterpret_cast(metadataMethod); + entry->clazz = resolvedMetadataClass; + } else { + metadataEnv.ExceptionClear(); + entry = nullptr; + metadataSignatureIsUnambiguous = false; + } + } else { + metadataEnv.ExceptionClear(); + entry = nullptr; + metadataSignatureIsUnambiguous = false; + } + } + + if ((entry != nullptr) && (entry->getIsResolved() || metadataSignatureIsUnambiguous)) { auto &entrySignature = entry->getSig(); isStatic = entry->isStatic; @@ -614,15 +695,22 @@ JsValue CallbackHandlers::CreateJSWrapper(JsRuntime &rt, jint javaObjectID, return objectManager->CreateJSWrapper(javaObjectID, typeName); } -jobjectArray -CallbackHandlers::GetImplementedInterfaces(JsRuntime &rt, JEnv &jEnv, - const JsValue &implementationObject) { +// The two Collect* helpers below define which names take part in a binding. +// Content-keyed class names hash exactly these sets, so the static binding +// generator has to reproduce them from the bundle byte for byte -- a name that +// is collected here but not there (or the reverse) yields two different class +// names for one extend() site, which fails at runtime as LookedUpClassNotFound. +// Keeping the JNI arrays and the hash on one implementation is what makes that +// contract checkable in a single place. +vector +CallbackHandlers::CollectImplementedInterfaceNames(JsRuntime &rt, + const JsValue &implementationObject) { + vector interfaceNames; + if (!implementationObject.isObject()) { - return CallbackHandlers::GetJavaStringArray(jEnv, 0); + return interfaceNames; } - vector interfacesToImplement; - auto prop = implementationObject.asObjectBorrowed(rt).getProperty(rt, "interfaces"); if (js_util::is_array(rt, prop)) { @@ -637,36 +725,24 @@ CallbackHandlers::GetImplementedInterfaces(JsRuntime &rt, JEnv &jEnv, node = Util::ReplaceAll(node, std::string("/"), std::string(".")); - jstring value = jEnv.NewStringUTF(node.c_str()); - interfacesToImplement.push_back(value); + interfaceNames.push_back(node); } } } - 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; + return interfaceNames; } -jobjectArray -CallbackHandlers::GetMethodOverrides(JsRuntime &rt, JEnv &jEnv, - const JsValue &implementationObject) { +vector +CallbackHandlers::CollectMethodOverrideNames(JsRuntime &rt, + const JsValue &implementationObject, + bool functionsOnly) { + vector methodNames; + if (!implementationObject.isObject()) { - return CallbackHandlers::GetJavaStringArray(jEnv, 0); + return methodNames; } - vector methodNames; - auto implObject = implementationObject.asObjectBorrowed(rt); // Object::getPropertyNames walks the prototype chain, where the napi tree @@ -689,16 +765,103 @@ CallbackHandlers::GetMethodOverrides(JsRuntime &rt, JEnv &jEnv, continue; } + // Runtime bookkeeping stamped onto the implementation object, not + // something the user wrote. getOwnPropertyNames returns non-enumerable + // properties too, so these have to be filtered by name. + if (name == EXTEND_CTOR_CACHE_KEY || name == CLASS_IMPLEMENTATION_OBJECT) { + continue; + } + + // Written onto the prototype by ts_helpers' __extends before it defers + // to extend(), so every TypeScript-transpiled subclass carries them. + // They are invisible to the static binding generator, which reads the + // class body from source -- hashing them put the two sides permanently + // out of step and sent every such class down the on-device dex path. + if (name == PROP_KEY_TS_PARENT || name == PROP_KEY_TS_CHILD) { + continue; + } + + if (!functionsOnly) { + // Hashing, not binding. The generator reads the implementation object + // out of the bundle, where it cannot tell a function from any other + // value -- `{ foo: someIdentifier }` is a method it must assume, and + // a statically-typed guess either way would disagree with what this + // enumeration sees. Taking every name instead makes the two sides + // agree by construction on everything the generator can parse. + // + // The three exclusions below are the names the generator provably + // never puts in the method field: `super` and `constructor` are + // artifacts of how the implementation object is built (ts_helpers + // extends with the class prototype, which always carries a + // constructor), and an array-valued `interfaces` is routed to its own + // field instead. + if (name == "constructor") { + continue; + } + + string lowered = name; + std::transform(lowered.begin(), lowered.end(), lowered.begin(), + [](unsigned char c) { return std::tolower(c); }); + + auto value = implObject.getProperty(rt, element); + bool isInterfacesDeclaration = lowered == "interfaces" && js_util::is_array(rt, value); + if (isInterfacesDeclaration) { + continue; + } + + methodNames.push_back(name); + continue; + } + auto method = implObject.getProperty(rt, element); bool methodFound = method.isObject() && method.asObjectBorrowed(rt).isFunction(rt); if (methodFound) { - jstring value = jEnv.NewStringUTF(name.c_str()); - methodNames.push_back(value); + methodNames.push_back(name); } } + return methodNames; +} + +jobjectArray +CallbackHandlers::GetImplementedInterfaces(JsRuntime &rt, JEnv &jEnv, + const JsValue &implementationObject) { + auto names = CollectImplementedInterfaceNames(rt, implementationObject); + + vector interfacesToImplement; + interfacesToImplement.reserve(names.size()); + for (const auto &name: names) { + interfacesToImplement.push_back(jEnv.NewStringUTF(name.c_str())); + } + + int interfacesCount = interfacesToImplement.size(); + + jobjectArray implementedInterfaces = CallbackHandlers::GetJavaStringArray(jEnv, + interfacesCount); + for (int i = 0; i < interfacesCount; i++) { + jEnv.SetObjectArrayElement(implementedInterfaces, i, interfacesToImplement[i]); + } + + for (int i = 0; i < interfacesCount; i++) { + jEnv.DeleteLocalRef(interfacesToImplement[i]); + } + + return implementedInterfaces; +} + +jobjectArray +CallbackHandlers::GetMethodOverrides(JsRuntime &rt, JEnv &jEnv, + const JsValue &implementationObject) { + auto names = CollectMethodOverrideNames(rt, implementationObject); + + vector methodNames; + methodNames.reserve(names.size()); + for (const auto &name: names) { + methodNames.push_back(jEnv.NewStringUTF(name.c_str())); + } + int methodCount = methodNames.size(); jobjectArray methodOverrides = CallbackHandlers::GetJavaStringArray(jEnv, methodCount); @@ -1235,6 +1398,11 @@ void CallbackHandlers::RemoveEnvEntries(JsRuntime &rt) { } // Worker +// +// A Worker gets its own VM, which a guest cannot build -- the runtime it is +// attached to belongs to the embedder. Excluded from the guest build along with +// WorkerWrapper itself; React Native has its own answer for background work. +#if !defined(NS_JSI_HOST_RUNTIME) JsValue CallbackHandlers::NewThreadCallback(JsRuntime &rt, const JsValue &thisVal, const JsValue *args, size_t argc) { @@ -1604,6 +1772,8 @@ void CallbackHandlers::CallWorkerScopeOnErrorHandle(JsRuntime &rt, const JsError nsEx.ReThrowToJs(rt); } } +#endif + robin_hood::unordered_map CallbackHandlers::cache_; robin_hood::unordered_map CallbackHandlers::jclass_to_runtimeId_cache; diff --git a/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.h b/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.h index 7428cd153..628dd1d28 100644 --- a/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.h +++ b/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.h @@ -63,7 +63,8 @@ namespace tns { CallJavaMethod(JsRuntime &rt, const JsValue &caller, const std::string &className, const std::string &methodName, MetadataEntry *entry, bool isFromInterface, bool isStatic, bool isConstructorCall, const JsValue *argv, size_t argc, - ObjectManager *objectManager = nullptr); + ObjectManager *objectManager = nullptr, + bool metadataSignatureIsUnambiguous = false); static JsValue CallJSMethod(JsRuntime &rt, JNIEnv *jEnv, const JsValue &jsObject, jclass claz, @@ -121,6 +122,21 @@ namespace tns { static jobjectArray GetImplementedInterfaces(JsRuntime &rt, JEnv &jEnv, const JsValue &implementationObject); + /* + * The name sets the two Get* helpers above are built from. Exposed so that + * content-keyed class names can hash the same sets the generated binding is + * built out of, without a second copy of the filtering rules. + */ + // functionsOnly selects what the names are for: true builds the array the + // dex generator overrides against, false the set a content-keyed class + // name hashes. The two differ deliberately -- see the implementation. + static std::vector + CollectMethodOverrideNames(JsRuntime &rt, const JsValue &implementationObject, + bool functionsOnly = true); + + static std::vector + CollectImplementedInterfaceNames(JsRuntime &rt, const JsValue &implementationObject); + static JsValue EnableVerboseLoggingMethodCallback(JsRuntime &rt, const JsValue &thisVal, const JsValue *args, size_t argc); @@ -132,6 +148,8 @@ namespace tns { static JsValue FindClass(JsRuntime &rt, const char *name); +// Worker. Not built for a guest -- see the definitions in CallbackHandlers.cpp. +#if !defined(NS_JSI_HOST_RUNTIME) static JsValue NewThreadCallback(JsRuntime &rt, const JsValue &thisVal, const JsValue *args, size_t argc); @@ -173,6 +191,7 @@ namespace tns { * napi version received) is JSError::value(). */ static void CallWorkerScopeOnErrorHandle(JsRuntime &rt, const JsError &error); +#endif static JsValue PostFrameCallback(JsRuntime &rt, const JsValue &thisVal, const JsValue *args, size_t argc); diff --git a/NativeScript/ffi/jni/jsi/constants/Constants.h b/NativeScript/ffi/jni/jsi/constants/Constants.h index 7f41e29c5..8a0772f0d 100644 --- a/NativeScript/ffi/jni/jsi/constants/Constants.h +++ b/NativeScript/ffi/jni/jsi/constants/Constants.h @@ -10,6 +10,10 @@ #define PROP_KEY_CLASS "class" #define PRIVATE_TYPE_NAME "#typename" #define CLASS_IMPLEMENTATION_OBJECT "t::ClassImplementationObject" +// Identifies an implementation object across repeated extend() calls, so the +// extended constructor can be cached per implementation rather than per +// generated class name -- content-keyed names are shared by design. +#define EXTEND_CTOR_CACHE_KEY "t::ExtendCtorCacheKey" #define PROP_KEY_SUPER "super" #define PROP_KEY_SUPERVALUE "supervalue" #define PRIVATE_JSINFO "#js_info" @@ -17,6 +21,11 @@ #define PRIVATE_IS_NAPI "#is_napi" #define PROP_KEY_TOSTRING "toString" #define PROP_KEY_IS_PROTOTYPE_IMPLEMENTATION_OBJECT "__isPrototypeImplementationObject" +// Stamped onto the implementation object by ts_helpers' __extends, not written +// by the user. They exist only after that helper has run, so the static binding +// generator cannot see them -- see CollectMethodOverrideNames. +#define PROP_KEY_TS_PARENT "__parent" +#define PROP_KEY_TS_CHILD "__child" class Constants { public: diff --git a/NativeScript/ffi/jni/jsi/conversion/ArgConverter.cpp b/NativeScript/ffi/jni/jsi/conversion/ArgConverter.cpp index 13cc59a18..9f1d895a2 100644 --- a/NativeScript/ffi/jni/jsi/conversion/ArgConverter.cpp +++ b/NativeScript/ffi/jni/jsi/conversion/ArgConverter.cpp @@ -231,15 +231,11 @@ int64_t ArgConverter::ConvertToJavaLong(JsRuntime &rt, const JsValue &value) { } ArgConverter::TypeLongOperationsCache *ArgConverter::GetTypeLongCache(JsRuntime &rt) { - TypeLongOperationsCache *cache; - auto itFound = s_type_long_operations_cache.find(rt.identity()); - if (itFound == s_type_long_operations_cache.end()) { - cache = new TypeLongOperationsCache; - s_type_long_operations_cache.emplace(rt.identity(), cache); - } else { - cache = itFound->second; - } - + const void *key = rt.identity(); + auto cache = s_type_long_operations_cache.Get(key); + if (cache) return cache; + cache = new TypeLongOperationsCache; + s_type_long_operations_cache.Insert(key, cache); return cache; } @@ -299,11 +295,10 @@ u16string ArgConverter::ConvertToUtf16String(JsRuntime &rt, const JsValue &s) { } void ArgConverter::onDisposeRuntime(JsRuntime &rt) { - auto itFound = s_type_long_operations_cache.find(rt.identity()); - if (itFound != s_type_long_operations_cache.end()) { - delete itFound->second; - s_type_long_operations_cache.erase(itFound); - } + const void *key = rt.identity(); + auto cache = s_type_long_operations_cache.Get(key); + s_type_long_operations_cache.Remove(key); + delete cache; } -robin_hood::unordered_map ArgConverter::s_type_long_operations_cache; +tns::ConcurrentMap ArgConverter::s_type_long_operations_cache; diff --git a/NativeScript/ffi/jni/jsi/conversion/ArgConverter.h b/NativeScript/ffi/jni/jsi/conversion/ArgConverter.h index 957e04eb3..2c1ddb390 100644 --- a/NativeScript/ffi/jni/jsi/conversion/ArgConverter.h +++ b/NativeScript/ffi/jni/jsi/conversion/ArgConverter.h @@ -131,7 +131,7 @@ namespace tns { * dealing with operations concerning java long -> javascript number. */ // Keyed by JsRuntime::identity(); &rt is not stable across callbacks. - static robin_hood::unordered_map s_type_long_operations_cache; + static tns::ConcurrentMap s_type_long_operations_cache; }; } diff --git a/NativeScript/ffi/jni/jsi/global/GlobalHelpers.cpp b/NativeScript/ffi/jni/jsi/global/GlobalHelpers.cpp index 6e97dcceb..9b87b90f8 100644 --- a/NativeScript/ffi/jni/jsi/global/GlobalHelpers.cpp +++ b/NativeScript/ffi/jni/jsi/global/GlobalHelpers.cpp @@ -167,6 +167,19 @@ std::vector tns::BuildStacktraceFrames(JsRuntime &rt, co const regex bareRegex(R"RE([(\s]([^\s():]+):(\d+):(\d+))RE"); #endif +#if defined(NS_JSI_HOST_RUNTIME) + // A guest's modules come from the embedder's bundler, and its frames carry + // no "file:" scheme -- Metro serves "http://10.0.2.2:8081/index.bundle?..." + // in development and an "index.android.bundle" path in release. Without a + // match there are no frames at all, and MetadataNode::GetExtendLocation then + // has no call site to name an unnamed extend() after, so every one of them + // collapses onto the same generated class. + // + // Anchored the same way as the bytecode form, and the scheme pattern above + // still runs first, so nothing changes for a frame that does carry one. + const regex hostRegex(R"RE([(\s@]([A-Za-z][A-Za-z\d+.-]*://[^\s()]+|[^\s()@]+):(\d+):(\d+))RE"); +#endif + int current = 0; for (auto &frame : stackLines) { smatch match; @@ -178,6 +191,11 @@ std::vector tns::BuildStacktraceFrames(JsRuntime &rt, co else if (regex_search(frame, match, bareRegex)) { filePath = "file://" + Constants::APP_ROOT_FOLDER_PATH + match[1].str(); } +#endif +#if defined(NS_JSI_HOST_RUNTIME) + else if (regex_search(frame, match, hostRegex)) { + filePath = match[1].str(); + } #endif else { continue; diff --git a/NativeScript/ffi/jni/jsi/jni/LRUCache.h b/NativeScript/ffi/jni/jsi/jni/LRUCache.h index 7b829c3dd..afceeb132 100644 --- a/NativeScript/ffi/jni/jsi/jni/LRUCache.h +++ b/NativeScript/ffi/jni/jsi/jni/LRUCache.h @@ -100,6 +100,16 @@ class LRUCache { } } + void clear() { + if (m_evictCallback != nullptr) { + for (auto& entry : m_key_to_value) { + m_evictCallback(entry.second.first, m_state); + } + } + m_key_to_value.clear(); + m_key_tracker.clear(); + } + void update(const key_type& key, const value_type& value) { jweak ref = m_loadCallback(key, m_state); insert(key, ref); diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.cpp b/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.cpp index 7836dfbfd..c3f8badeb 100644 --- a/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.cpp +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.cpp @@ -64,16 +64,20 @@ std::string &MetadataEntry::getReturnType() { } MethodReturnType MetadataEntry::getRetType() { - if (retTypeParsed) return retType; - auto reader = MetadataNode::getMetadataReader(); - - if (type == NodeType::Method && !this->getReturnType().empty()) { - retType = MetadataReader::GetReturnType(this->returnType); + if (retTypeParsed && retType != MethodReturnType::Unknown) { + return retType; } - retTypeParsed = true; + auto &methodReturnType = this->getReturnType(); + if (!methodReturnType.empty()) { + retType = MetadataReader::GetReturnType(methodReturnType); + retTypeParsed = retType != MethodReturnType::Unknown; + return retType; + } - return retType; + retType = MethodReturnType::Unknown; + retTypeParsed = false; + return MethodReturnType::Unknown; } std::string &MetadataEntry::getDeclaringType() { diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.h b/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.h index 6e708526c..1cb79b071 100644 --- a/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.h +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.h @@ -56,6 +56,7 @@ namespace tns { sig = other.sig; returnType = other.returnType; retType = other.retType; + retTypeParsed = other.retTypeParsed; declaringType = other.declaringType; paramCount = other.paramCount; isFinal = other.isFinal; diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataNode.cpp b/NativeScript/ffi/jni/jsi/metadata/MetadataNode.cpp index 950d6c693..357692760 100644 --- a/NativeScript/ffi/jni/jsi/metadata/MetadataNode.cpp +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataNode.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "NativeScriptException.h" #include "MetadataNode.h" #include "CallbackHandlers.h" @@ -16,7 +17,11 @@ #include "FieldCallbackData.h" #include "MetadataBuilder.h" #include "ArgsWrapper.h" +// Only referenced from the module-prologue column adjustment below, which is +// itself standalone-only -- a guest has no module wrapper to undo. +#if !defined(NS_JSI_HOST_RUNTIME) #include "ModuleInternal.h" +#endif #include "Util.h" #include "GlobalHelpers.h" #include "JSONObjectHelper.h" @@ -545,6 +550,109 @@ string MetadataNode::CreateFullClassName(const std::string &className, return fullClassName; } +// The compile-time policy, which is also the floor: a build that hard-wires +// content keying cannot be talked out of it by an app's config. +static bool ContentKeyedBindingsForcedOn() { +#if defined(NS_CONTENT_KEYED_BINDINGS) + return true; +#elif defined(NS_JSI_HOST_RUNTIME) + // A guest runs inside the embedder's bundle, so no location key computed here + // can match one the generator computed from a source tree it never saw. + return true; +#else + return false; +#endif +} + +// Resolved once, before any JS runs. The naming scheme has to match what the +// static binding generator used for this app, so the value comes from the build +// that ran the generator (app/package.json -> AppConfig -> Runtime::Init) rather +// than from anything decided here. +static bool s_contentKeyedBindings = ContentKeyedBindingsForcedOn(); + +void MetadataNode::SetContentKeyedBindings(bool enabled) { + // Only ever widens: an app may turn content keying on, never off under a + // runtime that was built to require it. + s_contentKeyedBindings = s_contentKeyedBindings || enabled; +} + +bool MetadataNode::ContentKeyedBindingsEnabled() { + return s_contentKeyedBindings; +} + +// A class-name token derived from what the generated binding is actually made +// of, rather than from where the extend() call happens to sit. +// +// The static binding generator writes a class out of exactly three inputs: the +// base class, the implemented interfaces, and the overridden method names. Two +// extend() sites that agree on all three produce byte-identical Java, so hashing +// them is enough to name the class -- and two sites that collide are meant to +// share, which is why the generator already treats a duplicate file with equal +// content as a warning rather than an error. +// +// Location keys cannot do this once an embedder owns the bundler: the generator +// reads source coordinates and the runtime reads bundle coordinates. A content +// key needs no shared coordinate system at all. +// +// Sorted, because the runtime's order is JS own-property order and the +// generator's is source order; the binding treats both sets as unordered. +// FNV-1a rather than std::hash, which is not stable across implementations and +// so could not be reproduced by the generator. +// Package separators and nested-type separators both become '.' -- see the +// comment in CreateContentKey. +static string Canonicalize(const string &name) { + string canonical = name; + canonical = Util::ReplaceAll(canonical, std::string("/"), std::string(".")); + return Util::ReplaceAll(canonical, std::string("$"), std::string(".")); +} + +string MetadataNode::CreateContentKey(const string &baseClassName, + vector interfaceNames, + vector methodNames) { + // Canonicalized before sorting, not after: '$' and '.' are different bytes, + // so sorting the binary form and sorting the dotted form can disagree on + // order even once the names themselves match. + for (auto &interfaceName: interfaceNames) { + interfaceName = Canonicalize(interfaceName); + } + + sort(interfaceNames.begin(), interfaceNames.end()); + sort(methodNames.begin(), methodNames.end()); + + // Nested types reach here in their binary form -- `Pack200$Unpacker`, and + // `/` for package separators on the interface side. The generator reads the + // same names out of JS source, where both are written with a dot and there + // is no way to tell a nested type from a package. So the dotted form is the + // canonical one for hashing; the JNI arrays that actually load the classes + // keep the binary form, which is what Class.forName needs. + string canonicalBase = Canonicalize(baseClassName); + + string payload = canonicalBase; + payload += "|"; + for (size_t i = 0; i < interfaceNames.size(); i++) { + if (i > 0) payload += ","; + payload += interfaceNames[i]; + } + payload += "|"; + for (size_t i = 0; i < methodNames.size(); i++) { + if (i > 0) payload += ","; + payload += methodNames[i]; + } + + uint64_t hash = 14695981039346656037ULL; + for (unsigned char c: payload) { + hash ^= static_cast(c); + hash *= 1099511628211ULL; + } + + char buffer[19]; + snprintf(buffer, sizeof(buffer), "h%016llx", static_cast(hash)); + + DEBUG_WRITE("CreateContentKey: %s -> %s", payload.c_str(), buffer); + + return string(buffer); +} + bool MetadataNode::IsValidExtendName(JsRuntime &rt, const JsValue &name) { string extendName = ArgConverter::ConvertToString(rt, name); @@ -568,7 +676,12 @@ MetadataNode::GetExtendLocation(JsRuntime &rt, string &extendLocation, bool isTy auto frames = tns::BuildStacktraceFrames(rt, nullptr, 4); if (frames.empty()) { DEBUG_WRITE("%s", "FRAME IS NULL!"); - return true; + // Reported as a failure, not a success with an empty location. An empty + // location makes every unnamed extend() of the same base class produce + // the same generated class name, and the second one's overrides then + // dispatch to themselves -- a StackOverflowError far from the cause. + // The caller turns this into "No name specified for extend at location". + return false; } tns::JsStacktraceFrame *frame; @@ -590,10 +703,10 @@ MetadataNode::GetExtendLocation(JsRuntime &rt, string &extendLocation, bool isTy 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); + // The app-root-relative substring this used to compute was assigned and + // then immediately overwritten by the full name below, so it was already + // dead -- and it throws for any path that does not start with the app + // root, which is every path a guest sees. fullPathToFile = srcFileName; replace(fullPathToFile.begin(), fullPathToFile.end(), '/', '_'); replace(fullPathToFile.begin(), fullPathToFile.end(), '.', '_'); @@ -619,6 +732,14 @@ MetadataNode::GetExtendLocation(JsRuntime &rt, string &extendLocation, bool isTy return false; } int column = frame->col; + + // Both adjustments below exist to undo the runtime's own module wrapper, so + // that a call site's key matches what the static binding generator computed + // from the unwrapped source. A guest has no such wrapper -- the embedder's + // bundler (Metro) owns module wrapping and the static binding generator + // never saw the source -- so applying them here only corrupts the key, and + // two different extend() sites can collide onto one generated class. +#if !defined(NS_JSI_HOST_RUNTIME) if (frame->line == 1) { column -= ModuleInternal::MODULE_PROLOGUE_LENGTH; } @@ -626,6 +747,7 @@ MetadataNode::GetExtendLocation(JsRuntime &rt, string &extendLocation, bool isTy #ifdef TARGET_ENGINE_HERMES column = column - 6; #endif +#endif // !NS_JSI_HOST_RUNTIME extendLocationStream << fullPathToFile << "_" << frame->line << "_" << column << "_"; extendLocation = extendLocationStream.str(); @@ -766,6 +888,13 @@ void MetadataNode::CreateTopLevelNamespaces(JsRuntime &rt) { if (IsJavascriptKeyword(nameSpace)) { nameSpace = "$" + nameSpace; } + if (global.hasProperty(rt, nameSpace.c_str())) { + string fallback = "$" + nameSpace; + __android_log_print(ANDROID_LOG_WARN, "TNS.Runtime", + "Java package '%s' is exposed as '%s': the global '%s' already exists.", + nameSpace.c_str(), fallback.c_str(), nameSpace.c_str()); + nameSpace = fallback; + } global.setProperty(rt, nameSpace.c_str(), packageObj); } } @@ -1180,6 +1309,12 @@ std::vector MetadataNode::SetClassMembersFro callbackData->candidates.push_back(std::move(entry)); callbackData->objectManager = objectManager; } + for (const auto &extension: collectedExtensionMethods) { + if (std::find(instanceMethodData.begin(), instanceMethodData.end(), extension.second) == + instanceMethodData.end()) { + cache->methodCallbackData.push_back(extension.second); + } + } auto instanceFieldCount = *reinterpret_cast(curPtr); curPtr += sizeof(uint16_t); for (auto i = 0; i < instanceFieldCount; i++) { @@ -1265,6 +1400,7 @@ std::vector MetadataNode::SetClassMembersFro auto &methodName = entry.getName(); if (methodName != lastMethodName) { callbackData = new MethodCallbackData(this); + cache->methodCallbackData.push_back(callbackData); auto method = JsFunction::createFromHostFunction( rt, engine::PropNameID::forAscii(rt, methodName), 0, [callbackData](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, @@ -1397,8 +1533,8 @@ std::vector MetadataNode::SetInstanceMembers entry.paramCount = paramCount; entry.isStatic = false; if (chKind == 'M') { + entry.type = NodeType::Method; if (entry.name != lastMethodName) { - entry.type = NodeType::Method; callbackData = new MethodCallbackData(this); instanceMethodData.push_back(callbackData); instanceMethodsCallbackData.push_back(callbackData); @@ -1831,11 +1967,28 @@ JsValue MetadataNode::ExtendMethodCallback(JsRuntime &rt, const JsValue &thisVal } } + auto useContentKey = false; + if (hasDot) { extendName = JsValue(rt, args[0]); implementationObject = JsValue(rt, args[1]); } else { - bool validExtend = GetExtendLocation(rt, extendLocation, isTypeScriptExtend); + useContentKey = ContentKeyedBindingsEnabled(); + + // Skipped outright when content keying is on: this walks the stack + // purely to build a name, on every class creation. + bool validExtend = + useContentKey || GetExtendLocation(rt, extendLocation, isTypeScriptExtend); + + if (!validExtend) { + // No usable call site -- an anonymous frame, or a bundle the + // generator never indexed. Naming by content still works here, + // where before this was fatal. + useContentKey = true; + validExtend = true; + extendLocation.clear(); + } + extendName = js_util::to_js_string(rt, ""); auto validArgs = ValidateExtendArguments(rt, argc, args, validExtend, extendLocation, @@ -1846,11 +1999,35 @@ JsValue MetadataNode::ExtendMethodCallback(JsRuntime &rt, const JsValue &thisVal } } + string baseClassName = node->m_name; + + // An explicitly named extend keeps its name in front of the hash. The + // generator reads the same name out of the bundle, so the two still + // agree, and it buys back two things the bare hash costs: a class name + // that says what it is in a stack trace, and uniqueness for the classes + // that need it. Anything Java instantiates by name -- an Activity, a + // Fragment, a Worker, anything reached by reflection -- cannot share a + // generated class, because the runtime has only the class name to find + // the JS implementation with. Naming such a class is what makes it + // unique; unnamed extends stay shared, which is the common case and the + // point of the scheme. + string extendNameString = ArgConverter::ConvertToString(rt, extendName); + string extendNameAndLocation; + + if (useContentKey) { + string contentKey = + CreateContentKey(baseClassName, + CallbackHandlers::CollectImplementedInterfaceNames( + rt, implementationObject), + CallbackHandlers::CollectMethodOverrideNames( + rt, implementationObject, /* functionsOnly */ false)); + extendNameAndLocation = + extendNameString.empty() ? contentKey : extendNameString + "_" + contentKey; + } else { + extendNameAndLocation = extendLocation + extendNameString; + } - string extendNameAndLocation = - extendLocation + ArgConverter::ConvertToString(rt, extendName); string fullClassName; - string baseClassName = node->m_name; if (!hasDot) { fullClassName = TNS_PREFIX + CreateFullClassName(baseClassName, extendNameAndLocation); } else { @@ -1863,12 +2040,43 @@ JsValue MetadataNode::ExtendMethodCallback(JsRuntime &rt, const JsValue &thisVal implementationObject, isInterface); auto fullExtendedName = CallbackHandlers::ResolveClassName(rt, clazz); - auto cachedData = GetCachedExtendedClassData(rt, fullExtendedName); + auto implObject = implementationObject.asObjectBorrowed(rt); + + // Which JS constructor to reuse is a different question from which Java + // class to load, and only the second one has to agree with the static + // binding generator. + // + // Two extend() calls with the same base, interfaces and overridden names + // share one generated Java class -- the Java is identical, and sharing it + // is the point. Their JS bodies are not identical, though, so caching the + // constructor under the class name would hand the second call the first + // call's implementation. Under content keying that is not a rare + // collision but the common case, so the constructor is cached per + // implementation object instead. + // The stamp identifies the object, not the class it was used to build: + // combining it with fullExtendedName keeps a second extend() of the same + // object against a *different* base a cache miss, so it still reaches the + // "used to extend another class" check below instead of silently + // returning the first class. + string ctorCacheKey = fullExtendedName; + if (useContentKey) { + auto stamped = implObject.getProperty(rt, EXTEND_CTOR_CACHE_KEY); + string implObjectId; + if (stamped.isString()) { + implObjectId = stamped.asString(rt).utf8(rt); + } else { + static std::atomic s_implObjectCounter{0}; + implObjectId = std::to_string(s_implObjectCounter.fetch_add(1)); + implObject.setProperty(rt, EXTEND_CTOR_CACHE_KEY, + ArgConverter::convertToJsString(rt, implObjectId)); + } + ctorCacheKey = fullExtendedName + "#" + implObjectId; + } + + auto cachedData = GetCachedExtendedClassData(rt, ctorCacheKey); if (!js_util::is_null_or_undefined(cachedData.extendedCtorFunction)) { return JsValue(rt, cachedData.extendedCtorFunction); } - - auto implObject = implementationObject.asObjectBorrowed(rt); auto implementationObjectName = implObject.getProperty(rt, CLASS_IMPLEMENTATION_OBJECT); if (js_util::is_null_or_undefined(implementationObjectName)) { @@ -1920,10 +2128,31 @@ JsValue MetadataNode::ExtendMethodCallback(JsRuntime &rt, const JsValue &thisVal s_name2NodeCache.emplace(fullExtendedName, node); - ExtendedClassCacheData cacheData(JsValue(rt, extendFuncCtorValue), fullExtendedName, - node); auto cache = GetMetadataNodeCache(rt); - cache->ExtendedCtorFuncCache.emplace(fullExtendedName, std::move(cacheData)); + + // Two entries, because there are two ways in. + // + // From JS, extend() asks "have I already built a constructor for this + // implementation object?" -- keyed per object, so two identical-looking + // extends keep their own bodies. + // + // From Java, createJSInstanceNative has only the class name of the + // instance it is wrapping (CreateExtendedJSWrapper), so the class name + // has to resolve too. emplace leaves an existing entry alone, so for a + // shared generated class the first implementation registered is the one + // Java-side instantiation finds. That is unambiguous for a named extend + // -- naming it makes the class unique -- and inherently ambiguous for an + // unnamed one, which is why anything Java constructs by name should be + // named. + ExtendedClassCacheData byImplObject(JsValue(rt, extendFuncCtorValue), fullExtendedName, + node); + cache->ExtendedCtorFuncCache.emplace(ctorCacheKey, std::move(byImplObject)); + + if (ctorCacheKey != fullExtendedName) { + ExtendedClassCacheData byClassName(JsValue(rt, extendFuncCtorValue), fullExtendedName, + node); + cache->ExtendedCtorFuncCache.emplace(fullExtendedName, std::move(byClassName)); + } return extendFuncCtorValue; }); @@ -1974,6 +2203,8 @@ JsValue MetadataNode::MethodCallback(JsRuntime &rt, const JsValue &thisVal, auto callbackData = initialCallbackData; + MethodCallbackData *selectedCallbackData = nullptr; + std::vector arityCandidates; string *className; auto &first = callbackData->candidates.front(); auto &methodName = first.getName(); @@ -1985,6 +2216,8 @@ JsValue MetadataNode::MethodCallback(JsRuntime &rt, const JsValue &thisVal, !first.isExtensionFunction && first.getParamCount() == argc) { className = &callbackData->node->m_name; + selectedCallbackData = callbackData; + arityCandidates.push_back(&first); entry = &first; } @@ -1993,29 +2226,31 @@ JsValue MetadataNode::MethodCallback(JsRuntime &rt, const JsValue &thisVal, className = &callbackData->node->m_name; - // Iterates through all methods and finds the best match based on the number of arguments - auto found = false; + arityCandidates.clear(); 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; + if ((!c.isExtensionFunction && c.getParamCount() == argc) || + (c.isExtensionFunction && c.getParamCount() == argc + 1)) { + arityCandidates.push_back(&c); } } - // Iterates through the parent class's methods to find a good match - if (!found) { + if (!arityCandidates.empty()) { + selectedCallbackData = callbackData; + if (arityCandidates.front()->isExtensionFunction) { + className = &arityCandidates.front()->getDeclaringType(); + } + break; + } else { callbackData = callbackData->parent; } } + if (arityCandidates.size() == 1) { + entry = arityCandidates.front(); + } else if (arityCandidates.size() > 1) { + entry = arityCandidates.front(); + } + if (initialCallbackData->cachedIsValueOf < 0) { initialCallbackData->cachedIsValueOf = @@ -2034,9 +2269,237 @@ JsValue MetadataNode::MethodCallback(JsRuntime &rt, const JsValue &thisVal, initialCallbackData->objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); } + + size_t metadataMatches = 0; + MetadataEntry *metadataMatch = nullptr; + bool metadataSignatureIsUnambiguous = false; + bool forceReflectionFallback = false; + bool preserveGenericObject = false; + bool hasObjectCandidate = false; + bool hasSpecificInterfaceCandidate = false; + bool hasExactStringCandidate = false; + for (auto *candidate : arityCandidates) { + const auto signature = candidate->getSig(); + hasObjectCandidate |= signature.find("Ljava/lang/Object;") != std::string::npos; + hasSpecificInterfaceCandidate |= signature.find("Ljava/io/Serializable;") != std::string::npos || + signature.find("Ljava/lang/CharSequence;") != std::string::npos; + hasExactStringCandidate |= signature.find("(Ljava/lang/String;") == 0; + } + preserveGenericObject = hasObjectCandidate && hasSpecificInterfaceCandidate && !hasExactStringCandidate; + struct MetadataCandidateMatch { + MetadataEntry *entry; + std::vector parameterClasses; + }; + std::vector metadataCandidateMatches; + JEnv metadataEnv; + bool hasNullArgument = false; + for (size_t i = 0; i < argc; i++) { + if (args[i].isNull() || args[i].isUndefined() || + (args[i].isObject() && MetadataNode::GetNullNode(rt, args[i]) != nullptr)) { + hasNullArgument = true; + break; + } + } + constexpr int kMetadataMismatch = -1; + constexpr int kMetadataUnknown = 0; + constexpr int kMetadataMatch = 1; + auto metadataTypesMatch = [&](MetadataEntry &candidate, + std::vector ¶meterClasses) -> int { + const auto signature = candidate.getSig(); + if (signature.empty() || signature[0] != '(') { + return kMetadataMismatch; + } + + std::vector parameterTypes; + size_t index = 1; + while (index < signature.size() && signature[index] != ')') { + const size_t start = index; + if (signature[index] == 'L') { + const auto end = signature.find(';', index); + if (end == std::string::npos) return kMetadataMismatch; + index = end + 1; + } else if (signature[index] == '[') { + do { + index++; + } while (index < signature.size() && signature[index] == '['); + if (index < signature.size() && signature[index] == 'L') { + const auto end = signature.find(';', index); + if (end == std::string::npos) return kMetadataMismatch; + index = end + 1; + } else if (index >= signature.size()) { + return kMetadataMismatch; + } else { + index++; + } + } else { + index++; + } + parameterTypes.emplace_back(signature.substr(start, index - start)); + } + + if (candidate.isExtensionFunction) { + if (parameterTypes.empty()) return kMetadataMismatch; + parameterTypes.erase(parameterTypes.begin()); + } + if (index >= signature.size() || parameterTypes.size() != argc) return kMetadataMismatch; + + parameterClasses.assign(argc, nullptr); + bool hasUnknownArgument = false; + for (size_t i = 0; i < argc; i++) { + const auto ¶meterType = parameterTypes[i]; + if (args[i].isNull() || args[i].isUndefined()) { + hasUnknownArgument = true; + continue; + } + if (parameterType[0] == 'L' || parameterType[0] == '[') { + auto expectedName = parameterType; + if (expectedName[0] == 'L') { + expectedName = expectedName.substr(1, expectedName.size() - 2); + } + auto expectedClass = metadataEnv.FindClass(expectedName); + if (expectedClass == nullptr) { + metadataEnv.ExceptionClear(); + return kMetadataMismatch; + } + parameterClasses[i] = expectedClass; + if (args[i].isString()) { + if (expectedName != "java/lang/String") return kMetadataMismatch; + auto stringClass = metadataEnv.FindClass("java/lang/String"); + if (stringClass == nullptr) { + metadataEnv.ExceptionClear(); + return kMetadataMismatch; + } + if (metadataEnv.IsAssignableFrom(expectedClass, stringClass) != JNI_TRUE) return kMetadataMismatch; + } else { + if (!args[i].isObject()) return kMetadataMismatch; + if (MetadataNode::GetNullNode(rt, args[i]) != nullptr) return kMetadataMismatch; + auto actualObject = initialCallbackData->objectManager->GetJavaObjectByJsObject(args[i]); + if (actualObject.IsNull()) { + // JS array for an array parameter, or a JS value + // without a Java peer: only the lenient + // JsArgConverter can marshal these. + hasUnknownArgument = true; + continue; + } + if (parameterType[0] == '[' && expectedName == "[Ljava/lang/Object;" && + ObjectManager::GetClassName((jobject) actualObject) != expectedName) return kMetadataMismatch; + bool hasSerializableCandidate = false; + for (auto *arityCandidate : arityCandidates) { + if (arityCandidate->getSig().find("Ljava/io/Serializable;") != std::string::npos) { + hasSerializableCandidate = true; + break; + } + } + if (expectedName == "java/lang/Object" && !hasSerializableCandidate) return kMetadataMismatch; + if (metadataEnv.IsInstanceOf(actualObject, expectedClass) != JNI_TRUE) return kMetadataMismatch; + } + } else if (parameterType[0] == 'Z') { + if (!args[i].isBool()) return kMetadataMismatch; + } else if (!args[i].isNumber() || + (parameterType[0] != 'B' && parameterType[0] != 'C' && + parameterType[0] != 'S' && parameterType[0] != 'I' && + parameterType[0] != 'J' && parameterType[0] != 'F' && + parameterType[0] != 'D')) { + return kMetadataMismatch; + } + } + return hasUnknownArgument ? kMetadataUnknown : kMetadataMatch; + }; + if (!hasNullArgument && selectedCallbackData != nullptr && arityCandidates.size() > 1 && !preserveGenericObject) { + for (auto *candidate : arityCandidates) { + if (candidate->isStatic != first.isStatic) continue; + std::vector parameterClasses; + if (metadataTypesMatch(*candidate, parameterClasses) == kMetadataMatch) { + metadataCandidateMatches.push_back({candidate, std::move(parameterClasses)}); + } + } + for (size_t i = 0; i < metadataCandidateMatches.size(); i++) { + bool dominated = false; + for (size_t j = 0; j < metadataCandidateMatches.size(); j++) { + if (i == j) continue; + const auto &candidate = metadataCandidateMatches[i]; + const auto &other = metadataCandidateMatches[j]; + for (size_t parameter = 0; parameter < argc; parameter++) { + auto candidateClass = candidate.parameterClasses[parameter]; + auto otherClass = other.parameterClasses[parameter]; + if (candidateClass == nullptr || otherClass == nullptr) { + if (candidate.entry->getSig() != other.entry->getSig()) break; + continue; + } + if (metadataEnv.IsAssignableFrom(candidateClass, otherClass) == JNI_TRUE && + metadataEnv.IsAssignableFrom(otherClass, candidateClass) != JNI_TRUE) { + dominated = true; + break; + } + } + if (dominated) break; + } + if (!dominated) { + metadataMatches++; + metadataMatch = metadataCandidateMatches[i].entry; + } + } + metadataSignatureIsUnambiguous = metadataMatches == 1 && metadataMatch != nullptr; + for (const auto &candidate : metadataCandidateMatches) { + hasExactStringCandidate |= candidate.entry->getSig().find("(Ljava/lang/String;") == 0; + } + if (metadataCandidateMatches.size() > 1 && !hasExactStringCandidate) { + metadataSignatureIsUnambiguous = false; + metadataMatch = nullptr; + forceReflectionFallback = false; + } + if (!metadataSignatureIsUnambiguous && !metadataCandidateMatches.empty()) { + for (const auto &candidate : metadataCandidateMatches) { + const auto signature = candidate.entry->getSig(); + const auto closeParen = signature.find(')'); + if (closeParen != std::string::npos && closeParen > 1 && signature[1] == '[') { + forceReflectionFallback = true; + break; + } + if (closeParen != std::string::npos && closeParen > 1 && + signature[1] != 'L' && signature[1] != 'Z') { + forceReflectionFallback = true; + break; + } + } + } + } + if (!hasNullArgument && entry != nullptr && !entry->isExtensionFunction && arityCandidates.size() == 1) { + std::vector parameterClasses; + const int compatibility = metadataTypesMatch(*entry, parameterClasses); + if (compatibility == kMetadataMatch) { + metadataMatch = entry; + metadataSignatureIsUnambiguous = true; + } else if (compatibility == kMetadataMismatch) { + // Provable mismatch against the only metadata candidate: the + // metadata may be stale, so let reflection resolve it. + const auto signature = entry->getSig(); + if (entry->isStatic || signature.find("Ljava/lang/Object;") == std::string::npos) { + entry = nullptr; + } + } + // kMetadataUnknown: keep the entry (direct call via JsArgConverter + // when generator-resolved, reflection otherwise). Reflection can't + // marshal JS arrays, so demoting here breaks valid calls. + } + if (!hasNullArgument && entry != nullptr && entry->isExtensionFunction) { + metadataMatch = entry; + metadataSignatureIsUnambiguous = true; + } + if (metadataSignatureIsUnambiguous) { + entry = metadataMatch; + } else if (forceReflectionFallback) { + entry = nullptr; + } + const bool selectedIsStatic = entry != nullptr + ? entry->isStatic + : (arityCandidates.size() == 1 + ? arityCandidates.front()->isStatic + : first.isStatic); return CallbackHandlers::CallJavaMethod(rt, thisVal, *className, methodName, entry, - isFromInterface, first.isStatic, false, - args, argc, initialCallbackData->objectManager); + isFromInterface, selectedIsStatic, false, + args, argc, initialCallbackData->objectManager, + metadataSignatureIsUnambiguous); }); } @@ -2102,6 +2565,7 @@ void MetadataNode::SetMissingBaseMethods( if (callbackData == nullptr) { callbackData = new MethodCallbackData(this); + GetMetadataNodeCache(rt)->methodCallbackData.push_back(callbackData); auto proto = constructor.getPropertyAsObject(rt, "prototype"); auto method = JsFunction::createFromHostFunction( rt, engine::PropNameID::forAscii(rt, methodName), 0, @@ -2128,6 +2592,11 @@ void MetadataNode::SetMissingBaseMethods( } void MetadataNode::BuildMetadata(const std::string &filesPath) { + // Once per process: the static node caches point into this tree, and it + // outlives any one main runtime. + if (s_metadataReader.GetRoot() != nullptr) { + return; + } s_metadataReader = MetadataBuilder::BuildMetadata(filesPath); } @@ -2159,6 +2628,9 @@ void MetadataNode::onDisposeRuntime(JsRuntime &rt) { for (const auto &entry: it->extendedClassCallbackData) { delete entry; } + for (const auto &entry: it->methodCallbackData) { + delete entry; + } } s_metadata_node_cache.Remove(key); delete it; diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataNode.h b/NativeScript/ffi/jni/jsi/metadata/MetadataNode.h index 5b7689f36..265783b4a 100644 --- a/NativeScript/ffi/jni/jsi/metadata/MetadataNode.h +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataNode.h @@ -14,6 +14,12 @@ using namespace tns; class MetadataNode { public: + // Set once from Runtime::Init, out of the app's config: the static binding + // generator picked the naming scheme at build time and the runtime has to + // look names up the same way. Only ever widens -- a runtime compiled to + // require content keys cannot be turned back off by an app. + static void SetContentKeyedBindings(bool enabled); + static void Init(JsRuntime &rt); static void BuildMetadata(const std::string &filesPath); @@ -75,6 +81,16 @@ class MetadataNode { static string CreateFullClassName(const std::string& className, const std::string& extendNameAndLocation); + static string CreateContentKey(const std::string& baseClassName, + std::vector interfaceNames, + std::vector methodNames); + + // Whether an unnamed extend() names its class by content rather than by call + // site. Always available as a fallback when no usable location exists; the + // default is on wherever the bundler is the embedder's, since the generator + // and the runtime cannot agree on coordinates there. + static bool ContentKeyedBindingsEnabled(); + static JsValue CreateArrayObjectConstructor(JsRuntime &rt); static void SetInstanceMetadata(JsRuntime &rt, const JsValue &object, MetadataNode* node); @@ -112,7 +128,6 @@ class MetadataNode { static ExtendedClassCacheData GetCachedExtendedClassData(JsRuntime &rt, const std::string& proxyClassName); static std::string GetJniClassName(const MetadataTreeNode* node); - static void SetClassAccessor(JsRuntime &rt, JsObject constructor); static MetadataEntry GetChildMetadataForPackage(MetadataNode *node, const char *propName); @@ -358,6 +373,9 @@ class MetadataNode { std::vector fieldCallbackData; std::vector propertyCallbackData; std::vector extendedClassCallbackData; + // Method callback data no CtorFuncCache entry owns: static methods, + // extension-only methods and methods copied from a skipped base class. + std::vector methodCallbackData; }; static bool s_profilerEnabled; diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataReader.cpp b/NativeScript/ffi/jni/jsi/metadata/MetadataReader.cpp index 4119d7281..15c0e4cb2 100644 --- a/NativeScript/ffi/jni/jsi/metadata/MetadataReader.cpp +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataReader.cpp @@ -290,6 +290,9 @@ MetadataTreeNode *MetadataReader::GetOrCreateTreeNodeByName(const string &classN child = new MetadataTreeNode; child->name = *it++; child->parent = treeNode; + // Rebuilt by reflection because the metadata files did not + // describe it -- see the field's comment. + child->synthesizedAtRuntime = true; string line; string kind; diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataTreeNode.h b/NativeScript/ffi/jni/jsi/metadata/MetadataTreeNode.h index e30cfb1ea..ed07707ec 100644 --- a/NativeScript/ffi/jni/jsi/metadata/MetadataTreeNode.h +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataTreeNode.h @@ -18,6 +18,13 @@ struct MetadataTreeNode { std::string* metadata; uint8_t type; + // True when this node was not read from the metadata files but rebuilt at + // runtime by MetadataReader::GetOrCreateTreeNodeByName, via reflection. + // That path is the reason a class missing from filtered metadata can still + // be marshalled: it is slower, not broken. Only the usage tracer reads + // this, to tell "recovered" apart from "would have thrown". + bool synthesizedAtRuntime = false; + static const uint8_t PACKAGE = 0; static const uint8_t CLASS = 1 << 0; static const uint8_t INTERFACE = 1 << 1; diff --git a/NativeScript/ffi/jni/jsi/objectmanager/ObjectManager.cpp b/NativeScript/ffi/jni/jsi/objectmanager/ObjectManager.cpp index 394a45446..08e000964 100644 --- a/NativeScript/ffi/jni/jsi/objectmanager/ObjectManager.cpp +++ b/NativeScript/ffi/jni/jsi/objectmanager/ObjectManager.cpp @@ -85,6 +85,10 @@ void ObjectManager::OnDisposeRuntime() { m_idToObject.clear(); m_jsObjectCtor = JsFunction(); + // Its destructor leaves the weak global refs alive, and the process-wide + // weak reference table is finite. + m_cache.clear(); + // The host-object proxies are owned by the *engine*, and each holds an owned // handle to the instance it wraps. Nothing above reaches them: they are only // destroyed when the engine tears its heap down, at which point diff --git a/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.cpp b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.cpp index 5b2fa30f1..f050d6afa 100644 --- a/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.cpp +++ b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.cpp @@ -31,6 +31,57 @@ using namespace std; using namespace tns; +namespace { +bool EnsureMetadataReturnType(MetadataEntry *entry) { + if (entry == nullptr) { + return false; + } + + auto &returnType = entry->getReturnType(); + if (!returnType.empty()) { + return true; + } + + const auto &signature = entry->getSig(); + const auto closeParen = signature.find(')'); + if (closeParen == std::string::npos || closeParen + 1 >= signature.size()) { + return false; + } + + const auto candidate = signature.substr(closeParen + 1); + switch (candidate[0]) { + case 'V': + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + case 'J': + case 'F': + case 'D': + if (candidate.size() != 1) { + return false; + } + break; + case 'L': + if (candidate.size() < 3 || candidate.back() != ';') { + return false; + } + break; + case '[': + if (candidate.size() < 2) { + return false; + } + break; + default: + return false; + } + + returnType = candidate; + return true; +} +} + void CallbackHandlers::Init(napi_env env) { JEnv jEnv; @@ -71,7 +122,8 @@ void CallbackHandlers::Init(napi_env env) { 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) { + ObjectManager *objectManager, + bool metadataSignatureIsUnambiguous) { JEnv jEnv; jclass clazz; @@ -83,10 +135,52 @@ napi_value CallbackHandlers::CallJavaMethod(napi_env env, napi_value caller, con bool isSuper = false; napi_status status; - if ((entry != nullptr) && entry->getIsResolved()) { + if (metadataSignatureIsUnambiguous && entry != nullptr && + !EnsureMetadataReturnType(entry)) { + entry = nullptr; + metadataSignatureIsUnambiguous = false; + } + + if (metadataSignatureIsUnambiguous && entry != nullptr && entry->memberId == nullptr) { + JEnv metadataEnv; + auto metadataClass = metadataEnv.FindClass(className); + if (metadataClass != nullptr) { + jmethodID metadataMethod; + jclass resolvedMetadataClass = metadataClass; + if (isStatic && isFromInterface) { + auto methodAndClassPair = metadataEnv.GetInterfaceStaticMethodIDAndJClass( + className, methodName, entry->getSig()); + metadataMethod = methodAndClassPair.first; + if (methodAndClassPair.second != nullptr) { + resolvedMetadataClass = methodAndClassPair.second; + } + } else { + metadataMethod = isStatic + ? metadataEnv.GetStaticMethodID(metadataClass, methodName, + entry->getSig()) + : metadataEnv.GetMethodID(metadataClass, methodName, + entry->getSig()); + } + if (metadataMethod != nullptr) { + entry->memberId = reinterpret_cast(metadataMethod); + entry->clazz = resolvedMetadataClass; + } else { + metadataEnv.ExceptionClear(); + entry = nullptr; + metadataSignatureIsUnambiguous = false; + } + } else { + metadataEnv.ExceptionClear(); + entry = nullptr; + metadataSignatureIsUnambiguous = false; + } + } + + if ((entry != nullptr) && (entry->getIsResolved() || metadataSignatureIsUnambiguous)) { auto &entrySignature = entry->getSig(); isStatic = entry->isStatic; + if (entry->memberId == nullptr) { clazz = jEnv.FindClass(className); @@ -188,6 +282,7 @@ napi_value CallbackHandlers::CallJavaMethod(napi_env env, napi_value caller, con retType = mi.retType; } + if (!isStatic) { DEBUG_WRITE("CallJavaMethod on instance %s", methodName.c_str()); } else { @@ -206,6 +301,7 @@ napi_value CallbackHandlers::CallJavaMethod(napi_env env, napi_value caller, con : JsArgConverter(env, argv, argc, false, *sig, entry, (JNIEnv *) jEnv, objectManager); + if (!argConverter.IsValid()) { JsArgConverter::Error err = argConverter.GetError(); throw NativeScriptException(err.msg); @@ -215,6 +311,7 @@ napi_value CallbackHandlers::CallJavaMethod(napi_env env, napi_value caller, con jvalue *javaArgs = argConverter.ToArgs(); + if (!isStatic) { int objectId = -1; @@ -236,6 +333,7 @@ napi_value CallbackHandlers::CallJavaMethod(napi_env env, napi_value caller, con } } + napi_value returnValue; switch (retType) { @@ -389,6 +487,7 @@ napi_value CallbackHandlers::CallJavaMethod(napi_env env, napi_value caller, con 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. @@ -516,6 +615,7 @@ bool CallbackHandlers::RegisterInstance(napi_env env, napi_value jsObject, fullClassName.c_str()); } + return success; } @@ -610,15 +710,22 @@ napi_value CallbackHandlers::CreateJSWrapper(napi_env env, jint javaObjectID, return objectManager->CreateJSWrapper(javaObjectID, typeName); } -jobjectArray -CallbackHandlers::GetImplementedInterfaces(napi_env env, JEnv &jEnv, - napi_value implementationObject) { +// The two Collect* helpers below define which names take part in a binding. +// Content-keyed class names hash exactly these sets, so the static binding +// generator has to reproduce them from the bundle byte for byte -- a name that +// is collected here but not there (or the reverse) yields two different class +// names for one extend() site, which fails at runtime as LookedUpClassNotFound. +// Keeping the JNI arrays and the hash on one implementation is what makes that +// contract checkable in a single place. +vector +CallbackHandlers::CollectImplementedInterfaceNames(napi_env env, + napi_value implementationObject) { + vector interfaceNames; + if (implementationObject == nullptr || napi_util::is_undefined(env, implementationObject)) { - return CallbackHandlers::GetJavaStringArray(jEnv, 0); + return interfaceNames; } - vector interfacesToImplement; - napi_status status; napi_value prop; NAPI_GUARD(napi_get_named_property(env, implementationObject, "interfaces", &prop)) {} @@ -629,7 +736,7 @@ CallbackHandlers::GetImplementedInterfaces(napi_env env, JEnv &jEnv, uint32_t length; NAPI_GUARD(napi_get_array_length(env, prop, &length)) {} - for (int j = 0; j < length; j++) { + for (uint32_t j = 0; j < length; j++) { napi_value element; NAPI_GUARD(napi_get_element(env, prop, j, &element)) {} @@ -638,35 +745,23 @@ CallbackHandlers::GetImplementedInterfaces(napi_env env, JEnv &jEnv, node = Util::ReplaceAll(node, std::string("/"), std::string(".")); - jstring value = jEnv.NewStringUTF(node.c_str()); - interfacesToImplement.push_back(value); + interfaceNames.push_back(node); } } } - 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; + return interfaceNames; } -jobjectArray -CallbackHandlers::GetMethodOverrides(napi_env env, JEnv &jEnv, napi_value implementationObject) { +vector +CallbackHandlers::CollectMethodOverrideNames(napi_env env, napi_value implementationObject, + bool functionsOnly) { + vector methodNames; + if (implementationObject == nullptr || napi_util::is_undefined(env, implementationObject)) { - return CallbackHandlers::GetJavaStringArray(jEnv, 0); + return methodNames; } - vector methodNames; - napi_status status; napi_value propNames; @@ -676,7 +771,7 @@ CallbackHandlers::GetMethodOverrides(napi_env env, JEnv &jEnv, napi_value implem uint32_t length; NAPI_GUARD(napi_get_array_length(env, propNames, &length)) {} - for (int i = 0; i < length; i++) { + for (uint32_t i = 0; i < length; i++) { napi_value element; NAPI_GUARD(napi_get_element(env, propNames, i, &element)) {} auto name = ArgConverter::ConvertToString(env, element); @@ -685,16 +780,101 @@ CallbackHandlers::GetMethodOverrides(napi_env env, JEnv &jEnv, napi_value implem continue; } - napi_value method; + // Runtime bookkeeping stamped onto the implementation object, not + // something the user wrote. napi_key_all_properties returns + // non-enumerable properties too, so these have to be filtered by name. + if (name == EXTEND_CTOR_CACHE_KEY || name == CLASS_IMPLEMENTATION_OBJECT) { + continue; + } - NAPI_GUARD(napi_get_property(env, implementationObject, element, &method)) {} + // Written onto the prototype by ts_helpers' __extends before it defers + // to extend(), so every TypeScript-transpiled subclass carries them. + // They are invisible to the static binding generator, which reads the + // class body from source -- hashing them put the two sides permanently + // out of step and sent every such class down the on-device dex path. + if (name == PROP_KEY_TS_PARENT || name == PROP_KEY_TS_CHILD) { + continue; + } + + if (!functionsOnly) { + // Hashing, not binding. The generator reads the implementation object + // out of the bundle, where it cannot tell a function from any other + // value -- `{ foo: someIdentifier }` is a method it must assume, and + // a statically-typed guess either way would disagree with what this + // enumeration sees. Taking every name instead makes the two sides + // agree by construction on everything the generator can parse. + // + // The three exclusions below are the names the generator provably + // never puts in the method field: `super` and `constructor` are + // artifacts of how the implementation object is built (ts_helpers + // extends with the class prototype, which always carries a + // constructor), and an array-valued `interfaces` is routed to its own + // field instead. + if (name == "constructor") { + continue; + } - bool methodFound = napi_util::is_of_type(env, method, napi_function); + string lowered = name; + std::transform(lowered.begin(), lowered.end(), lowered.begin(), + [](unsigned char c) { return std::tolower(c); }); - if (methodFound) { - jstring value = jEnv.NewStringUTF(name.c_str()); - methodNames.push_back(value); + napi_value value; + NAPI_GUARD(napi_get_property(env, implementationObject, element, &value)) {} + bool valueIsArray = false; + NAPI_GUARD(napi_is_array(env, value, &valueIsArray)) {} + if (lowered == "interfaces" && valueIsArray) { + continue; + } + + methodNames.push_back(name); + continue; } + + napi_value method; + NAPI_GUARD(napi_get_property(env, implementationObject, element, &method)) {} + + if (napi_util::is_of_type(env, method, napi_function)) { + methodNames.push_back(name); + } + } + + return methodNames; +} + +jobjectArray +CallbackHandlers::GetImplementedInterfaces(napi_env env, JEnv &jEnv, + napi_value implementationObject) { + auto names = CollectImplementedInterfaceNames(env, implementationObject); + + vector interfacesToImplement; + interfacesToImplement.reserve(names.size()); + for (const auto &name: names) { + interfacesToImplement.push_back(jEnv.NewStringUTF(name.c_str())); + } + + int interfacesCount = interfacesToImplement.size(); + + jobjectArray implementedInterfaces = CallbackHandlers::GetJavaStringArray(jEnv, + interfacesCount); + for (int i = 0; i < interfacesCount; i++) { + jEnv.SetObjectArrayElement(implementedInterfaces, i, interfacesToImplement[i]); + } + + for (int i = 0; i < interfacesCount; i++) { + jEnv.DeleteLocalRef(interfacesToImplement[i]); + } + + return implementedInterfaces; +} + +jobjectArray +CallbackHandlers::GetMethodOverrides(napi_env env, JEnv &jEnv, napi_value implementationObject) { + auto names = CollectMethodOverrideNames(env, implementationObject, /* functionsOnly */ true); + + vector methodNames; + methodNames.reserve(names.size()); + for (const auto &name: names) { + methodNames.push_back(jEnv.NewStringUTF(name.c_str())); } int methodCount = methodNames.size(); diff --git a/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.h b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.h index 39f4f5c01..274bba27e 100644 --- a/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.h +++ b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.h @@ -59,7 +59,8 @@ namespace tns { 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); + ObjectManager *objectManager = nullptr, + bool metadataSignatureIsUnambiguous = false); static napi_value CallJSMethod(napi_env env, JNIEnv *jEnv, napi_value jsObject,jclass claz, @@ -95,6 +96,22 @@ namespace tns { static std::vector GetTypeMetadata(const std::string &name, int index); + /* + * The two Collect* helpers below define which names take part in a binding. + * Content-keyed class names hash exactly these sets, so the static binding + * generator has to reproduce them from the bundle byte for byte -- a name + * that is collected here but not there (or the reverse) yields two + * different class names for one extend() site, which fails at runtime as + * LookedUpClassNotFound. Keeping the JNI arrays and the hash on one + * implementation is what makes that contract checkable in a single place. + */ + static std::vector + CollectImplementedInterfaceNames(napi_env env, napi_value implementationObject); + + static std::vector + CollectMethodOverrideNames(napi_env env, napi_value implementationObject, + bool functionsOnly); + /* * Gets all methods in the implementation object, and packs them in a jobjectArray * to pass them to Java Land, so that their corresponding Java callbacks are written when @@ -374,4 +391,4 @@ namespace tns { }; } -#endif /* CALLBACKHANDLERS_H_ */ \ No newline at end of file +#endif /* CALLBACKHANDLERS_H_ */ diff --git a/NativeScript/ffi/jni/napi/constants/Constants.h b/NativeScript/ffi/jni/napi/constants/Constants.h index 7f41e29c5..15aea1c77 100644 --- a/NativeScript/ffi/jni/napi/constants/Constants.h +++ b/NativeScript/ffi/jni/napi/constants/Constants.h @@ -10,6 +10,7 @@ #define PROP_KEY_CLASS "class" #define PRIVATE_TYPE_NAME "#typename" #define CLASS_IMPLEMENTATION_OBJECT "t::ClassImplementationObject" +#define EXTEND_CTOR_CACHE_KEY "t::ExtendCtorCacheKey" #define PROP_KEY_SUPER "super" #define PROP_KEY_SUPERVALUE "supervalue" #define PRIVATE_JSINFO "#js_info" @@ -17,6 +18,11 @@ #define PRIVATE_IS_NAPI "#is_napi" #define PROP_KEY_TOSTRING "toString" #define PROP_KEY_IS_PROTOTYPE_IMPLEMENTATION_OBJECT "__isPrototypeImplementationObject" +// Stamped onto the implementation object by ts_helpers' __extends, not written +// by the user. They exist only after that helper has run, so the static binding +// generator cannot see them -- see CollectMethodOverrideNames. +#define PROP_KEY_TS_PARENT "__parent" +#define PROP_KEY_TS_CHILD "__child" class Constants { public: diff --git a/NativeScript/ffi/jni/napi/conversion/ArgConverter.cpp b/NativeScript/ffi/jni/napi/conversion/ArgConverter.cpp index 176b7b910..7f3cccf6e 100644 --- a/NativeScript/ffi/jni/napi/conversion/ArgConverter.cpp +++ b/NativeScript/ffi/jni/napi/conversion/ArgConverter.cpp @@ -298,15 +298,10 @@ int64_t ArgConverter::ConvertToJavaLong(napi_env env, napi_value value) { } 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; - } - + auto cache = s_type_long_operations_cache.Get(env); + if (cache) return cache; + cache = new TypeLongOperationsCache; + s_type_long_operations_cache.Insert(env, cache); return cache; } @@ -331,17 +326,17 @@ u16string ArgConverter::ConvertToUtf16String(napi_env env, napi_value s) { 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)) {} + auto cache = s_type_long_operations_cache.Get(env); + if (cache) { + if (cache->LongNumberCtorFunc) { + NAPI_GUARD(napi_delete_reference(env, cache->LongNumberCtorFunc)) {} } - if (itFound->second->NanNumberObject) { - NAPI_GUARD(napi_delete_reference(env, itFound->second->NanNumberObject)) {} + if (cache->NanNumberObject) { + NAPI_GUARD(napi_delete_reference(env, cache->NanNumberObject)) {} } - delete itFound->second; - s_type_long_operations_cache.erase(itFound); + s_type_long_operations_cache.Remove(env); + delete cache; } } -robin_hood::unordered_map ArgConverter::s_type_long_operations_cache; +tns::ConcurrentMap ArgConverter::s_type_long_operations_cache; diff --git a/NativeScript/ffi/jni/napi/conversion/ArgConverter.h b/NativeScript/ffi/jni/napi/conversion/ArgConverter.h index a42a14cf2..ea6eb9e81 100644 --- a/NativeScript/ffi/jni/napi/conversion/ArgConverter.h +++ b/NativeScript/ffi/jni/napi/conversion/ArgConverter.h @@ -130,7 +130,7 @@ namespace tns { * "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; + static tns::ConcurrentMap s_type_long_operations_cache; }; } diff --git a/NativeScript/ffi/jni/napi/jni/LRUCache.h b/NativeScript/ffi/jni/napi/jni/LRUCache.h index 7b829c3dd..afceeb132 100644 --- a/NativeScript/ffi/jni/napi/jni/LRUCache.h +++ b/NativeScript/ffi/jni/napi/jni/LRUCache.h @@ -100,6 +100,16 @@ class LRUCache { } } + void clear() { + if (m_evictCallback != nullptr) { + for (auto& entry : m_key_to_value) { + m_evictCallback(entry.second.first, m_state); + } + } + m_key_to_value.clear(); + m_key_tracker.clear(); + } + void update(const key_type& key, const value_type& value) { jweak ref = m_loadCallback(key, m_state); insert(key, ref); diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataEntry.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.cpp index 7836dfbfd..c3f8badeb 100644 --- a/NativeScript/ffi/jni/napi/metadata/MetadataEntry.cpp +++ b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.cpp @@ -64,16 +64,20 @@ std::string &MetadataEntry::getReturnType() { } MethodReturnType MetadataEntry::getRetType() { - if (retTypeParsed) return retType; - auto reader = MetadataNode::getMetadataReader(); - - if (type == NodeType::Method && !this->getReturnType().empty()) { - retType = MetadataReader::GetReturnType(this->returnType); + if (retTypeParsed && retType != MethodReturnType::Unknown) { + return retType; } - retTypeParsed = true; + auto &methodReturnType = this->getReturnType(); + if (!methodReturnType.empty()) { + retType = MetadataReader::GetReturnType(methodReturnType); + retTypeParsed = retType != MethodReturnType::Unknown; + return retType; + } - return retType; + retType = MethodReturnType::Unknown; + retTypeParsed = false; + return MethodReturnType::Unknown; } std::string &MetadataEntry::getDeclaringType() { diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataEntry.h b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.h index 6e708526c..1cb79b071 100644 --- a/NativeScript/ffi/jni/napi/metadata/MetadataEntry.h +++ b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.h @@ -56,6 +56,7 @@ namespace tns { sig = other.sig; returnType = other.returnType; retType = other.retType; + retTypeParsed = other.retTypeParsed; declaringType = other.declaringType; paramCount = other.paramCount; isFinal = other.isFinal; diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataNode.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataNode.cpp index 2de40366b..df18bb889 100644 --- a/NativeScript/ffi/jni/napi/metadata/MetadataNode.cpp +++ b/NativeScript/ffi/jni/napi/metadata/MetadataNode.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "NativeScriptException.h" #include "MetadataNode.h" #include "CallbackHandlers.h" @@ -800,6 +801,105 @@ string MetadataNode::CreateFullClassName(const std::string &className, return fullClassName; } +// The compile-time policy, which is also the floor: a build that hard-wires +// content keying cannot be talked out of it by an app's config. +static bool ContentKeyedBindingsForcedOn() { +#if defined(NS_CONTENT_KEYED_BINDINGS) + return true; +#else + return false; +#endif +} + +// Resolved once, before any JS runs. The naming scheme has to match what the +// static binding generator used for this app, so the value comes from the build +// that ran the generator (app/package.json -> AppConfig -> Runtime::Init) rather +// than from anything decided here. +static bool s_contentKeyedBindings = ContentKeyedBindingsForcedOn(); + +void MetadataNode::SetContentKeyedBindings(bool enabled) { + // Only ever widens: an app may turn content keying on, never off under a + // runtime that was built to require it. + s_contentKeyedBindings = s_contentKeyedBindings || enabled; +} + +bool MetadataNode::ContentKeyedBindingsEnabled() { + return s_contentKeyedBindings; +} + +// A class-name token derived from what the generated binding is actually made +// of, rather than from where the extend() call happens to sit. +// +// The static binding generator writes a class out of exactly three inputs: the +// base class, the implemented interfaces, and the overridden method names. Two +// extend() sites that agree on all three produce byte-identical Java, so hashing +// them is enough to name the class -- and two sites that collide are meant to +// share, which is why the generator already treats a duplicate file with equal +// content as a warning rather than an error. +// +// Location keys cannot do this once an embedder owns the bundler: the generator +// reads source coordinates and the runtime reads bundle coordinates. A content +// key needs no shared coordinate system at all. +// +// Sorted, because the runtime's order is JS own-property order and the +// generator's is source order; the binding treats both sets as unordered. +// FNV-1a rather than std::hash, which is not stable across implementations and +// so could not be reproduced by the generator. +// Package separators and nested-type separators both become '.' -- see the +// comment in CreateContentKey. +static string Canonicalize(const string &name) { + string canonical = name; + canonical = Util::ReplaceAll(canonical, std::string("/"), std::string(".")); + return Util::ReplaceAll(canonical, std::string("$"), std::string(".")); +} + +string MetadataNode::CreateContentKey(const string &baseClassName, + vector interfaceNames, + vector methodNames) { + // Canonicalized before sorting, not after: '$' and '.' are different bytes, + // so sorting the binary form and sorting the dotted form can disagree on + // order even once the names themselves match. + for (auto &interfaceName: interfaceNames) { + interfaceName = Canonicalize(interfaceName); + } + + sort(interfaceNames.begin(), interfaceNames.end()); + sort(methodNames.begin(), methodNames.end()); + + // Nested types reach here in their binary form -- `Pack200$Unpacker`, and + // `/` for package separators on the interface side. The generator reads the + // same names out of JS source, where both are written with a dot and there + // is no way to tell a nested type from a package. So the dotted form is the + // canonical one for hashing; the JNI arrays that actually load the classes + // keep the binary form, which is what Class.forName needs. + string canonicalBase = Canonicalize(baseClassName); + + string payload = canonicalBase; + payload += "|"; + for (size_t i = 0; i < interfaceNames.size(); i++) { + if (i > 0) payload += ","; + payload += interfaceNames[i]; + } + payload += "|"; + for (size_t i = 0; i < methodNames.size(); i++) { + if (i > 0) payload += ","; + payload += methodNames[i]; + } + + uint64_t hash = 14695981039346656037ULL; + for (unsigned char c: payload) { + hash ^= static_cast(c); + hash *= 1099511628211ULL; + } + + char buffer[19]; + snprintf(buffer, sizeof(buffer), "h%016llx", static_cast(hash)); + + DEBUG_WRITE("CreateContentKey: %s -> %s", payload.c_str(), buffer); + + return string(buffer); +} + bool MetadataNode::IsValidExtendName(napi_env env, napi_value name) { string extendName = ArgConverter::ConvertToString(env, name); @@ -1021,6 +1121,14 @@ void MetadataNode::CreateTopLevelNamespaces(napi_env env) { if (IsJavascriptKeyword(nameSpace)) { nameSpace = "$" + nameSpace; } + bool exists = false; + if (napi_has_named_property(env, global, nameSpace.c_str(), &exists) == napi_ok && exists) { + string fallback = "$" + nameSpace; + __android_log_print(ANDROID_LOG_WARN, "TNS.Runtime", + "Java package '%s' is exposed as '%s': the global '%s' already exists.", + nameSpace.c_str(), fallback.c_str(), nameSpace.c_str()); + nameSpace = fallback; + } NAPI_GUARD(napi_set_named_property(env, global, nameSpace.c_str(), packageObj)) {} } } @@ -1679,8 +1787,8 @@ std::vector MetadataNode::SetInstanceMembers entry.paramCount = paramCount; entry.isStatic = false; if (chKind == 'M') { + entry.type = NodeType::Method; if (entry.name != lastMethodName) { - entry.type = NodeType::Method; callbackData = new MethodCallbackData(this); MetadataNode::GetMetadataNodeCache(env)->methodCallbackData.push_back(callbackData); instanceMethodData.push_back(callbackData); @@ -2226,14 +2334,30 @@ napi_value MetadataNode::ExtendMethodCallback(napi_env env, napi_callback_info i NAPI_GUARD(napi_get_value_bool(env, argv[2], &isTypeScriptExtend)) {} }; } - auto node = reinterpret_cast(data); + auto useContentKey = false; + if (hasDot) { extendName = argv[0]; implementationObject = argv[1]; } else { - bool validExtend = GetExtendLocation(env, extendLocation, isTypeScriptExtend); + useContentKey = ContentKeyedBindingsEnabled(); + + // Skipped outright when content keying is on: this walks the stack + // purely to build a name, on every class creation. + bool validExtend = + useContentKey || GetExtendLocation(env, extendLocation, isTypeScriptExtend); + + if (!validExtend) { + // No usable call site -- an anonymous frame, or a bundle the + // generator never indexed. Naming by content still works here, + // where before this was fatal. + useContentKey = true; + validExtend = true; + extendLocation.clear(); + } + NAPI_GUARD(napi_create_string_utf8(env, "", 0, &extendName)) { return nullptr; } @@ -2245,12 +2369,36 @@ napi_value MetadataNode::ExtendMethodCallback(napi_env env, napi_callback_info i return nullptr; } } + string baseClassName = node->m_name; + // An explicitly named extend keeps its name in front of the hash. The + // generator reads the same name out of the bundle, so the two still + // agree, and it buys back two things the bare hash costs: a class name + // that says what it is in a stack trace, and uniqueness for the classes + // that need it. Anything Java instantiates by name -- an Activity, a + // Fragment, a Worker, anything reached by reflection -- cannot share a + // generated class, because the runtime has only the class name to find + // the JS implementation with. Naming such a class is what makes it + // unique; unnamed extends stay shared, which is the common case and the + // point of the scheme. + string extendNameString = ArgConverter::ConvertToString(env, extendName); + string extendNameAndLocation; + + if (useContentKey) { + auto interfaceNames = CallbackHandlers::CollectImplementedInterfaceNames( + env, implementationObject); + auto methodNames = CallbackHandlers::CollectMethodOverrideNames( + env, implementationObject, /* functionsOnly */ false); + string contentKey = + CreateContentKey(baseClassName, + interfaceNames, methodNames); + extendNameAndLocation = + extendNameString.empty() ? contentKey : extendNameString + "_" + contentKey; + } else { + extendNameAndLocation = extendLocation + extendNameString; + } - string extendNameAndLocation = - extendLocation + ArgConverter::ConvertToString(env, extendName); string fullClassName; - string baseClassName = node->m_name; if (!hasDot) { fullClassName = TNS_PREFIX + CreateFullClassName(baseClassName, extendNameAndLocation); } else { @@ -2263,7 +2411,41 @@ napi_value MetadataNode::ExtendMethodCallback(napi_env env, napi_callback_info i implementationObject, isInterface); auto fullExtendedName = CallbackHandlers::ResolveClassName(env, clazz); - auto cachedData = GetCachedExtendedClassData(env, fullExtendedName); + // Which JS constructor to reuse is a different question from which Java + // class to load, and only the second one has to agree with the static + // binding generator. + // + // Two extend() calls with the same base, interfaces and overridden names + // share one generated Java class -- the Java is identical, and sharing it + // is the point. Their JS bodies are not identical, though, so caching the + // constructor under the class name would hand the second call the first + // call's implementation. Under content keying that is not a rare + // collision but the common case, so the constructor is cached per + // implementation object instead. + // The stamp identifies the object, not the class it was used to build: + // combining it with fullExtendedName keeps a second extend() of the same + // object against a *different* base a cache miss, so it still reaches the + // "used to extend another class" check below instead of silently + // returning the first class. + string ctorCacheKey = fullExtendedName; + if (useContentKey) { + napi_value stamped; + NAPI_GUARD(napi_get_named_property(env, implementationObject, EXTEND_CTOR_CACHE_KEY, + &stamped)) {} + string implObjectId; + if (napi_util::is_of_type(env, stamped, napi_string)) { + implObjectId = ArgConverter::ConvertToString(env, stamped); + } else { + static std::atomic s_implObjectCounter{0}; + implObjectId = std::to_string(s_implObjectCounter.fetch_add(1)); + NAPI_GUARD(napi_set_named_property( + env, implementationObject, EXTEND_CTOR_CACHE_KEY, + ArgConverter::convertToJsString(env, implObjectId))) {} + } + ctorCacheKey = fullExtendedName + "#" + implObjectId; + } + + auto cachedData = GetCachedExtendedClassData(env, ctorCacheKey); if (cachedData.extendedCtorFunction != nullptr) { auto value = napi_util::get_ref_value(env, cachedData.extendedCtorFunction); if (!napi_util::is_null_or_undefined(env, value)) return value; @@ -2321,10 +2503,31 @@ napi_value MetadataNode::ExtendMethodCallback(napi_env env, napi_callback_info i s_name2NodeCache.emplace(fullExtendedName, node); - ExtendedClassCacheData cacheData(napi_util::make_ref(env, extendFuncCtor), fullExtendedName, - node); auto cache = GetMetadataNodeCache(env); - cache->ExtendedCtorFuncCache.emplace(fullExtendedName, cacheData); + + // Two entries, because there are two ways in. + // + // From JS, extend() asks "have I already built a constructor for this + // implementation object?" -- keyed per object, so two identical-looking + // extends keep their own bodies. + // + // From Java, createJSInstanceNative has only the class name of the + // instance it is wrapping (CreateExtendedJSWrapper), so the class name + // has to resolve too. emplace leaves an existing entry alone, so for a + // shared generated class the first implementation registered is the one + // Java-side instantiation finds. That is unambiguous for a named extend + // -- naming it makes the class unique -- and inherently ambiguous for an + // unnamed one, which is why anything Java constructs by name should be + // named. + ExtendedClassCacheData byImplObject(napi_util::make_ref(env, extendFuncCtor), + fullExtendedName, node); + cache->ExtendedCtorFuncCache.emplace(ctorCacheKey, byImplObject); + + if (ctorCacheKey != fullExtendedName) { + ExtendedClassCacheData byClassName(napi_util::make_ref(env, extendFuncCtor), + fullExtendedName, node); + cache->ExtendedCtorFuncCache.emplace(fullExtendedName, byClassName); + } return extendFuncCtor; @@ -2407,6 +2610,8 @@ napi_value MetadataNode::MethodCallback(napi_env env, napi_callback_info info) { auto callbackData = reinterpret_cast(data); auto initialCallbackData = reinterpret_cast(data); + MethodCallbackData *selectedCallbackData = nullptr; + std::vector arityCandidates; string *className; auto &first = callbackData->candidates.front(); @@ -2419,6 +2624,8 @@ napi_value MetadataNode::MethodCallback(napi_env env, napi_callback_info info) { !first.isExtensionFunction && first.getParamCount() == argc) { className = &callbackData->node->m_name; + selectedCallbackData = callbackData; + arityCandidates.push_back(&first); entry = &first; } @@ -2427,29 +2634,31 @@ napi_value MetadataNode::MethodCallback(napi_env env, napi_callback_info info) { className = &callbackData->node->m_name; - // Iterates through all methods and finds the best match based on the number of arguments - auto found = false; + arityCandidates.clear(); 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; + if ((!c.isExtensionFunction && c.getParamCount() == argc) || + (c.isExtensionFunction && c.getParamCount() == argc + 1)) { + arityCandidates.push_back(&c); } } - // Iterates through the parent class's methods to find a good match - if (!found) { + if (!arityCandidates.empty()) { + selectedCallbackData = callbackData; + if (arityCandidates.front()->isExtensionFunction) { + className = &arityCandidates.front()->getDeclaringType(); + } + break; + } else { callbackData = callbackData->parent; } } + if (arityCandidates.size() == 1) { + entry = arityCandidates.front(); + } else if (arityCandidates.size() > 1) { + entry = arityCandidates.front(); + } + if (initialCallbackData->cachedIsValueOf < 0) { initialCallbackData->cachedIsValueOf = @@ -2464,13 +2673,313 @@ napi_value MetadataNode::MethodCallback(napi_env env, napi_callback_info info) { initialCallbackData->node->IsNodeTypeInterface() ? 1 : 0; } bool isFromInterface = initialCallbackData->cachedIsFromInterface == 1; - if (initialCallbackData->objectManager == nullptr) { + if (initialCallbackData->objectManager == nullptr) { initialCallbackData->objectManager = Runtime::GetRuntime(env)->GetObjectManager(); } + + + size_t metadataMatches = 0; + MetadataEntry *metadataMatch = nullptr; + bool metadataSignatureIsUnambiguous = false; + bool forceReflectionFallback = false; + bool preserveGenericObject = false; + bool hasObjectCandidate = false; + bool hasSpecificInterfaceCandidate = false; + bool hasExactStringCandidate = false; + for (auto *candidate : arityCandidates) { + const auto signature = candidate->getSig(); + hasObjectCandidate |= signature.find("Ljava/lang/Object;") != std::string::npos; + hasSpecificInterfaceCandidate |= signature.find("Ljava/io/Serializable;") != std::string::npos || + signature.find("Ljava/lang/CharSequence;") != std::string::npos; + hasExactStringCandidate |= signature.find("(Ljava/lang/String;") == 0; + } + preserveGenericObject = hasObjectCandidate && hasSpecificInterfaceCandidate && !hasExactStringCandidate; + struct MetadataCandidateMatch { + MetadataEntry *entry; + std::vector parameterClasses; + }; + std::vector metadataCandidateMatches; + JEnv metadataEnv; + bool hasNullArgument = false; + for (size_t i = 0; i < argc; i++) { + napi_valuetype valueType; + if (napi_typeof(env, argv[i], &valueType) != napi_ok) { + hasNullArgument = true; + break; + } + if (valueType != napi_object && valueType != napi_function) { + continue; + } + napi_value nullNode = nullptr; + if (napi_get_named_property(env, argv[i], PROP_KEY_NULL_NODE_NAME, &nullNode) == napi_ok && + !napi_util::is_null_or_undefined(env, nullNode)) { + hasNullArgument = true; + break; + } + } + constexpr int kMetadataMismatch = -1; + constexpr int kMetadataUnknown = 0; + constexpr int kMetadataMatch = 1; + auto metadataTypesMatch = [&](MetadataEntry &candidate, + std::vector ¶meterClasses) -> int { + const auto signature = candidate.getSig(); + if (signature.empty() || signature[0] != '(') { + return kMetadataMismatch; + } + + std::vector parameterTypes; + size_t index = 1; + while (index < signature.size() && signature[index] != ')') { + const size_t start = index; + if (signature[index] == 'L') { + const auto end = signature.find(';', index); + if (end == std::string::npos) return kMetadataMismatch; + index = end + 1; + } else if (signature[index] == '[') { + index++; + if (index < signature.size() && signature[index] == 'L') { + const auto end = signature.find(';', index); + if (end == std::string::npos) return kMetadataMismatch; + index = end + 1; + } else { + index++; + } + } else { + index++; + } + parameterTypes.emplace_back(signature.substr(start, index - start)); + } + + if (candidate.isExtensionFunction) { + if (parameterTypes.empty()) return kMetadataMismatch; + parameterTypes.erase(parameterTypes.begin()); + } + if (parameterTypes.size() != argc) return kMetadataMismatch; + + parameterClasses.assign(argc, nullptr); + bool hasUnknownArgument = false; + for (size_t i = 0; i < argc; i++) { + const auto ¶meterType = parameterTypes[i]; + napi_valuetype valueType; + napi_typeof(env, argv[i], &valueType); + if (valueType == napi_null || valueType == napi_undefined) { + // JsArgConverter maps null/undefined for any parameter + // type; nothing to check statically. + hasUnknownArgument = true; + continue; + } + if (parameterType[0] == 'L' || parameterType[0] == '[') { + jclass expectedClass = nullptr; + auto expectedName = parameterType; + if (expectedName[0] == 'L') { + expectedName = expectedName.substr(1, expectedName.size() - 2); + } + expectedClass = metadataEnv.FindClass(expectedName); + if (expectedClass == nullptr) { + metadataEnv.ExceptionClear(); + return kMetadataMismatch; + } + parameterClasses[i] = expectedClass; + + if (valueType == napi_string) { + if (expectedName != "java/lang/String") { + return kMetadataMismatch; + } + auto stringClass = metadataEnv.FindClass("java/lang/String"); + if (stringClass == nullptr) { + metadataEnv.ExceptionClear(); + return kMetadataMismatch; + } + if (metadataEnv.IsAssignableFrom(expectedClass, stringClass) != JNI_TRUE) { + return kMetadataMismatch; + } + } else { + if (valueType != napi_object && valueType != napi_function) return kMetadataMismatch; + napi_value nullNode = nullptr; + if (napi_get_named_property(env, argv[i], PROP_KEY_NULL_NODE_NAME, &nullNode) == napi_ok && + !napi_util::is_null_or_undefined(env, nullNode)) { + return kMetadataMismatch; + } + auto actualObject = initialCallbackData->objectManager->GetJavaObjectByJsObject(argv[i]); + if (actualObject.IsNull()) { + // JS array for an array parameter, or a JS value + // without a Java peer: only the lenient + // JsArgConverter can marshal these. + hasUnknownArgument = true; + continue; + } + if (parameterType[0] == '[' && expectedName == "[Ljava/lang/Object;") { + if (ObjectManager::GetClassName((jobject) actualObject) != expectedName) { + return kMetadataMismatch; + } + } + bool hasSerializableCandidate = false; + for (auto *arityCandidate : arityCandidates) { + if (arityCandidate->getSig().find("Ljava/io/Serializable;") != std::string::npos) { + hasSerializableCandidate = true; + break; + } + } + if (expectedName == "java/lang/Object" && !hasSerializableCandidate) { + return kMetadataMismatch; + } + if (metadataEnv.IsInstanceOf(actualObject, expectedClass) != JNI_TRUE) return kMetadataMismatch; + } + } else if (parameterType[0] == 'Z') { + if (valueType != napi_boolean) return kMetadataMismatch; + } else if (valueType != napi_number || + (parameterType[0] != 'B' && parameterType[0] != 'C' && + parameterType[0] != 'S' && parameterType[0] != 'I' && + parameterType[0] != 'J' && parameterType[0] != 'F' && + parameterType[0] != 'D')) { + return kMetadataMismatch; + } + } + return hasUnknownArgument ? kMetadataUnknown : kMetadataMatch; + }; + if (!hasNullArgument && selectedCallbackData != nullptr && arityCandidates.size() > 1 && !preserveGenericObject) { + for (auto *candidate : arityCandidates) { + if (candidate->isStatic != first.isStatic) { + continue; + } + std::vector parameterClasses; + if (metadataTypesMatch(*candidate, parameterClasses) == kMetadataMatch) { + metadataCandidateMatches.push_back({candidate, std::move(parameterClasses)}); + } + } + + for (size_t i = 0; i < metadataCandidateMatches.size(); i++) { + bool dominated = false; + for (size_t j = 0; j < metadataCandidateMatches.size(); j++) { + if (i == j) continue; + + const auto &candidate = metadataCandidateMatches[i]; + const auto &other = metadataCandidateMatches[j]; + for (size_t parameter = 0; parameter < argc; parameter++) { + auto candidateClass = candidate.parameterClasses[parameter]; + auto otherClass = other.parameterClasses[parameter]; + if (candidateClass == nullptr || otherClass == nullptr) { + if (candidate.entry->getSig() != other.entry->getSig()) { + break; + } + continue; + } + + if (metadataEnv.IsAssignableFrom(candidateClass, otherClass) == JNI_TRUE) { + if (metadataEnv.IsAssignableFrom(otherClass, candidateClass) != JNI_TRUE) { + dominated = true; + break; + } + } else { + break; + } + } + if (dominated) break; + } + if (!dominated) { + metadataMatches++; + metadataMatch = metadataCandidateMatches[i].entry; + } + } + metadataSignatureIsUnambiguous = metadataMatches == 1 && metadataMatch != nullptr; + bool hasExactStringCandidate = false; + for (const auto &candidate : metadataCandidateMatches) { + if (candidate.entry->getSig().find("(Ljava/lang/String;") == 0) { + hasExactStringCandidate = true; + break; + } + } + if (metadataCandidateMatches.size() > 1 && !hasExactStringCandidate) { + metadataSignatureIsUnambiguous = false; + metadataMatch = nullptr; + forceReflectionFallback = false; + } + if (!metadataSignatureIsUnambiguous && !metadataCandidateMatches.empty()) { + for (const auto &candidate : metadataCandidateMatches) { + const auto signature = candidate.entry->getSig(); + const auto closeParen = signature.find(')'); + if (closeParen != std::string::npos && closeParen > 1 && + signature[1] == '[') { + forceReflectionFallback = true; + break; + } + if (closeParen != std::string::npos && closeParen > 1 && + signature[1] != 'L' && signature[1] != 'Z') { + forceReflectionFallback = true; + break; + } + } + } + if (!metadataSignatureIsUnambiguous && metadataCandidateMatches.empty()) { + for (auto *candidate : arityCandidates) { + if (candidate->isExtensionFunction) { + continue; + } + const auto signature = candidate->getSig(); + const auto closeParen = signature.find(')'); + const bool hasObjectParameter = signature.find("Ljava/lang/Object;") != std::string::npos; + const bool hasArrayParameter = signature.find('[') != std::string::npos; + const bool hasPrimitiveParameter = closeParen != std::string::npos && closeParen > 1 && + signature[1] != 'L' && signature[1] != '[' && signature[1] != 'Z'; + if (!hasObjectParameter && !hasArrayParameter && !hasPrimitiveParameter) { + continue; + } + for (size_t i = 0; i < argc; i++) { + napi_valuetype valueType; + napi_typeof(env, argv[i], &valueType); + if (valueType == napi_object || valueType == napi_function || valueType == napi_number) { + forceReflectionFallback = true; + break; + } + } + if (forceReflectionFallback) { + break; + } + } + } + } + if (!hasNullArgument && entry != nullptr && !entry->isExtensionFunction && arityCandidates.size() == 1) { + std::vector parameterClasses; + const int compatibility = metadataTypesMatch(*entry, parameterClasses); + if (compatibility == kMetadataMatch) { + metadataMatch = entry; + metadataSignatureIsUnambiguous = true; + } else if (compatibility == kMetadataMismatch) { + // A provable mismatch against the only metadata candidate + // means the metadata may be stale (the runtime class can + // have overloads the generator never saw). Let reflection + // resolve against the real class. + const auto signature = entry->getSig(); + if (entry->isStatic || signature.find("Ljava/lang/Object;") == std::string::npos) { + entry = nullptr; + } + } + // kMetadataUnknown: keep the entry. A generator-resolved entry is + // invoked directly through the lenient JsArgConverter (the + // pre-metadata-first behavior); unresolved ones still use + // reflection. Reflection can't marshal JS arrays or null for + // primitives, so demoting here would turn valid calls into + // "Failed resolving method" errors. + } + if (!hasNullArgument && entry != nullptr && entry->isExtensionFunction) { + metadataMatch = entry; + metadataSignatureIsUnambiguous = true; + } + if (metadataSignatureIsUnambiguous) { + entry = metadataMatch; + } else if (forceReflectionFallback) { + entry = nullptr; + } + const bool selectedIsStatic = entry != nullptr + ? entry->isStatic + : (arityCandidates.size() == 1 + ? arityCandidates.front()->isStatic + : first.isStatic); napi_value result = CallbackHandlers::CallJavaMethod(env, jsThis, *className, methodName, entry, - isFromInterface, first.isStatic, info, - argc, argv, initialCallbackData->objectManager); + isFromInterface, selectedIsStatic, info, + argc, argv, initialCallbackData->objectManager, + metadataSignatureIsUnambiguous); // napi_value error; // error = Runtime::GetRuntime(env)->getPendingError(); // if (error) { diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataNode.h b/NativeScript/ffi/jni/napi/metadata/MetadataNode.h index f8f6f40e2..3921cec90 100644 --- a/NativeScript/ffi/jni/napi/metadata/MetadataNode.h +++ b/NativeScript/ffi/jni/napi/metadata/MetadataNode.h @@ -14,6 +14,12 @@ using namespace tns; class MetadataNode { public: + // Set once from Runtime::Init, out of the app's config: the static binding + // generator picked the naming scheme at build time and the runtime has to + // look names up the same way. Only ever widens -- a runtime compiled to + // require content keys cannot be turned back off by an app. + static void SetContentKeyedBindings(bool enabled); + static void Init(napi_env env); static void BuildMetadata(const std::string &filesPath); @@ -79,6 +85,14 @@ class MetadataNode { static string CreateFullClassName(const std::string& className, const std::string& extendNameAndLocation); + static string CreateContentKey(const std::string& baseClassName, + std::vector interfaceNames, + std::vector methodNames); + + // Whether an unnamed extend() names its class by content rather than by call + // site. Always available as a fallback when no usable location exists. + static bool ContentKeyedBindingsEnabled(); + static napi_value CreateArrayObjectConstructor(napi_env env); static void SetInstanceMetadata(napi_env env, napi_value object, MetadataNode* node); @@ -116,7 +130,6 @@ class MetadataNode { 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); diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataReader.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataReader.cpp index af2020493..daacbe8a9 100644 --- a/NativeScript/ffi/jni/napi/metadata/MetadataReader.cpp +++ b/NativeScript/ffi/jni/napi/metadata/MetadataReader.cpp @@ -290,6 +290,9 @@ MetadataTreeNode *MetadataReader::GetOrCreateTreeNodeByName(const string &classN child = new MetadataTreeNode; child->name = *it++; child->parent = treeNode; + // Rebuilt by reflection because the metadata files did not + // describe it -- see the field's comment. + child->synthesizedAtRuntime = true; string line; string kind; diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.h b/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.h index e30cfb1ea..ed07707ec 100644 --- a/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.h +++ b/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.h @@ -18,6 +18,13 @@ struct MetadataTreeNode { std::string* metadata; uint8_t type; + // True when this node was not read from the metadata files but rebuilt at + // runtime by MetadataReader::GetOrCreateTreeNodeByName, via reflection. + // That path is the reason a class missing from filtered metadata can still + // be marshalled: it is slower, not broken. Only the usage tracer reads + // this, to tell "recovered" apart from "would have thrown". + bool synthesizedAtRuntime = false; + static const uint8_t PACKAGE = 0; static const uint8_t CLASS = 1 << 0; static const uint8_t INTERFACE = 1 << 1; diff --git a/NativeScript/ffi/jni/napi/metadata/MethodCache.h b/NativeScript/ffi/jni/napi/metadata/MethodCache.h index 58bbbdd8a..a573d247c 100644 --- a/NativeScript/ffi/jni/napi/metadata/MethodCache.h +++ b/NativeScript/ffi/jni/napi/metadata/MethodCache.h @@ -42,7 +42,6 @@ class MethodCache { 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); @@ -150,10 +149,9 @@ class MethodCache { 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)) + napi_value nullNode = nullptr; + if (napi_get_named_property(env, value, PROP_KEY_NULL_NODE_NAME, &nullNode) == napi_ok && + !napi_util::is_null_or_undefined(env, nullNode)) { void *data = nullptr; napi_get_value_external(env, nullNode, &data); @@ -306,11 +304,17 @@ class MethodCache { JniLocalRef jsMethodName(jEnv.NewStringUTF(methodName.c_str())); jobjectArray arrArgs = argConverter.ToJavaArray(); - auto runtime = Runtime::GetRuntime(env); jstring signature = (jstring)jEnv.CallObjectMethod(runtime->GetJavaRuntime(), RESOLVE_METHOD_OVERLOAD_METHOD_ID, (jstring)jsClassName, (jstring)jsMethodName, arrArgs); + if (signature == nullptr || jEnv.ExceptionCheck() == JNI_TRUE) { + if (jEnv.ExceptionCheck() == JNI_TRUE) { + jEnv.ExceptionClear(); + } + return string(); + } + string resolvedSignature; const char *str = jEnv.GetStringUTFChars(signature, nullptr); @@ -366,4 +370,3 @@ class MethodCache { } #endif /* METHODCACHE_H_ */ - diff --git a/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.cpp b/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.cpp index 7f8285df3..e07f68f36 100644 --- a/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.cpp +++ b/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.cpp @@ -92,6 +92,10 @@ void ObjectManager::OnDisposeEnv() { NAPI_GUARD(napi_delete_reference(m_env, entry.second)) {} } m_idToObject.clear(); + + // Its destructor leaves the weak global refs alive, and the process-wide + // weak reference table is finite. + m_cache.clear(); } napi_value ObjectManager::GetOrCreateProxyWeak(jint javaObjectID, napi_value instance) { diff --git a/NativeScript/ffi/objc/hermes/NativeApiJsi.mm b/NativeScript/ffi/objc/hermes/NativeApiJsi.mm index b57fbce55..c7db662cb 100644 --- a/NativeScript/ffi/objc/hermes/NativeApiJsi.mm +++ b/NativeScript/ffi/objc/hermes/NativeApiJsi.mm @@ -32,6 +32,7 @@ #include "Metadata.h" #include "MetadataReader.h" #include "ffi.h" +#include "ffi/objc/shared/EnumMemberAliases.h" #include "NativeApiJsiSignatureDispatch.h" #ifndef NATIVESCRIPT_REACT_NATIVE diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h index 4c595b1c5..4cca8bb61 100644 --- a/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h @@ -24,6 +24,7 @@ #include "Metadata.h" #include "MetadataReader.h" #include "ffi.h" +#include "ffi/objc/shared/EnumMemberAliases.h" #include "jsi/jsc/JSCRuntime.h" diff --git a/NativeScript/ffi/objc/napi/Enum.mm b/NativeScript/ffi/objc/napi/Enum.mm index da6ce6add..c66c80a09 100644 --- a/NativeScript/ffi/objc/napi/Enum.mm +++ b/NativeScript/ffi/objc/napi/Enum.mm @@ -5,6 +5,7 @@ #include #include #include "ObjCBridge.h" +#include "ffi/objc/shared/EnumMemberAliases.h" namespace nativescript { @@ -30,10 +31,6 @@ inline void defineConstantIfMissing(napi_env env, napi_value object, const std:: napi_define_properties(env, object, 1, &prop); } -inline bool startsWith(const std::string& value, const std::string& prefix) { - return value.size() >= prefix.size() && value.compare(0, prefix.size(), prefix) == 0; -} - inline std::string stripEnumSuffix(const std::string& enumName) { static const std::vector suffixes = { "Options", "Option", "Enums", "Enum", "Result", "Direction", "Orientation", @@ -48,14 +45,6 @@ inline bool startsWith(const std::string& value, const std::string& prefix) { return enumName; } - -inline bool isNSComparisonResultOrderingName(const std::string& enumName, - const std::string& member) { - if (enumName != "NSComparisonResult") { - return false; - } - return member == "Ascending" || member == "Same" || member == "Descending"; -} } // namespace void ObjCBridgeState::registerEnumGlobals(napi_env env, napi_value global) { @@ -63,23 +52,17 @@ inline bool isNSComparisonResultOrderingName(const std::string& enumName, while (offset < metadata->signaturesOffset) { MDSectionOffset originalOffset = offset; auto name = metadata->getString(offset); - offset += sizeof(MDSectionOffset); - - bool next = true; - while (next) { - auto nameOffset = metadata->getOffset(offset); - next = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - auto memberName = metadata->resolveString(nameOffset); - offset += sizeof(MDSectionOffset); - int64_t value = metadata->getEnumValue(offset); - offset += sizeof(int64_t); - - napi_value member; - napi_create_int64(env, value, &member); - defineConstantIfMissing(env, global, memberName, member, - (napi_property_attributes)(napi_enumerable | napi_configurable)); - } + + offset = metadata->forEachEnumMember( + originalOffset, [&](const char*, const std::string& cName, int64_t value) { + if (cName.empty()) { + return; + } + napi_value member; + napi_create_int64(env, value, &member); + defineConstantIfMissing(env, global, cName, member, + (napi_property_attributes)(napi_enumerable | napi_configurable)); + }); napi_property_descriptor prop = { .utf8name = name, @@ -112,71 +95,23 @@ inline bool isNSComparisonResultOrderingName(const std::string& enumName, // enum name auto enumName = bridgeState->metadata->getString(offset); std::string enumNameStr = enumName; - offset += sizeof(MDSectionOffset); std::string strippedPrefix = stripEnumSuffix(enumNameStr); - napi_value global; - napi_get_global(env, &global); - - bool next = true; - while (next) { - auto nameOffset = bridgeState->metadata->getOffset(offset); - next = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - auto name = bridgeState->metadata->resolveString(nameOffset); - offset += sizeof(MDSectionOffset); - int64_t value = bridgeState->metadata->getEnumValue(offset); - offset += sizeof(int64_t); - + bridgeState->metadata->forEachEnumMember(originalOffset, [&](const char* name, + const std::string& cName, + int64_t value) { napi_value member; napi_create_int64(env, value, &member); std::string memberName = name; - std::vector aliases; - aliases.push_back(memberName); - - if (!strippedPrefix.empty() && startsWith(memberName, strippedPrefix) && - memberName.size() > strippedPrefix.size()) { - aliases.push_back(memberName.substr(strippedPrefix.size())); - } else if (!strippedPrefix.empty() && !startsWith(memberName, strippedPrefix)) { - aliases.push_back(strippedPrefix + memberName); - } + auto aliases = enum_members::aliasesFor(enumNameStr, strippedPrefix, memberName, cName); - if (startsWith(enumNameStr, "NS") && !startsWith(memberName, "NS")) { - aliases.push_back(std::string("NS") + memberName); - } - - if (enumNameStr == "NSStringCompareOptions" && !memberName.ends_with("Search")) { - aliases.push_back(memberName + "Search"); - aliases.push_back(std::string("NS") + memberName + "Search"); - } - - if (!startsWith(memberName, "k")) { - aliases.push_back(std::string("k") + enumNameStr + memberName); - } - - if (isNSComparisonResultOrderingName(enumNameStr, memberName)) { - aliases.push_back(std::string("Ordered") + memberName); - aliases.push_back(std::string("NSOrdered") + memberName); - } - - std::vector uniqueAliases; - uniqueAliases.reserve(aliases.size()); - std::unordered_set seenAliases; for (const auto& alias : aliases) { - if (seenAliases.insert(alias).second) { - uniqueAliases.push_back(alias); - } + defineConstantIfMissing(env, result, alias.name, member); } - for (const auto& alias : uniqueAliases) { - defineConstantIfMissing(env, result, alias, member); - defineConstantIfMissing(env, global, alias, member, - (napi_property_attributes)(napi_enumerable | napi_configurable)); - } - - std::string reverseCanonical = uniqueAliases.size() > 1 ? uniqueAliases[1] : memberName; + const std::string& reverseCanonical = aliases.size() > 1 ? aliases[1].name : memberName; // reverse mapping enum[value] -> canonical member name char valueKey[32]; @@ -197,7 +132,7 @@ inline bool isNSComparisonResultOrderingName(const std::string& enumName, }; napi_define_properties(env, result, 1, &reverseProp); } - } + }); bridgeState->mdValueCache[originalOffset] = make_ref(env, result); diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h index 9f945f8e0..302546aef 100644 --- a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h @@ -24,6 +24,7 @@ #include "Metadata.h" #include "MetadataReader.h" #include "ffi.h" +#include "ffi/objc/shared/EnumMemberAliases.h" #include "jsi/quickjs/QuickJSRuntime.h" diff --git a/NativeScript/ffi/objc/shared/EnumMemberAliases.h b/NativeScript/ffi/objc/shared/EnumMemberAliases.h new file mode 100644 index 000000000..4040744db --- /dev/null +++ b/NativeScript/ffi/objc/shared/EnumMemberAliases.h @@ -0,0 +1,85 @@ +#ifndef NATIVESCRIPT_FFI_OBJC_SHARED_ENUM_MEMBER_ALIASES_H +#define NATIVESCRIPT_FFI_OBJC_SHARED_ENUM_MEMBER_ALIASES_H + +#include +#include +#include + +namespace nativescript { +namespace enum_members { + +struct Alias { + std::string name; + // Only the C name may become a global; the other aliases are enum-object + // conveniences and can shadow unrelated globals (e.g. Center, Zone). + bool isCName = false; +}; + +inline bool startsWith(const std::string& value, const std::string& prefix) { + return value.size() >= prefix.size() && + value.compare(0, prefix.size(), prefix) == 0; +} + +inline bool endsWith(const std::string& value, const std::string& suffix) { + return value.size() >= suffix.size() && + value.compare(value.size() - suffix.size(), suffix.size(), suffix) == + 0; +} + +// The first alias is always the stored member name. cName is empty when the +// metadata does not record C names; then no alias is tagged as the C name. +inline std::vector aliasesFor(const std::string& enumName, + const std::string& strippedEnumName, + const std::string& storedName, + const std::string& cName) { + std::vector names; + names.push_back(storedName); + + if (!strippedEnumName.empty() && startsWith(storedName, strippedEnumName) && + storedName.size() > strippedEnumName.size()) { + names.push_back(storedName.substr(strippedEnumName.size())); + } else if (!strippedEnumName.empty() && + !startsWith(storedName, strippedEnumName)) { + names.push_back(strippedEnumName + storedName); + } + + if (startsWith(enumName, "NS") && !startsWith(storedName, "NS")) { + names.push_back(std::string("NS") + storedName); + } + + if (enumName == "NSStringCompareOptions" && !endsWith(storedName, "Search")) { + names.push_back(storedName + "Search"); + names.push_back(std::string("NS") + storedName + "Search"); + } + + if (!startsWith(storedName, "k")) { + names.push_back(std::string("k") + enumName + storedName); + } + + if (enumName == "NSComparisonResult" && + (storedName == "Ascending" || storedName == "Same" || + storedName == "Descending")) { + names.push_back(std::string("Ordered") + storedName); + names.push_back(std::string("NSOrdered") + storedName); + } + + if (!cName.empty()) { + names.push_back(cName); + } + + std::vector aliases; + aliases.reserve(names.size()); + std::unordered_set seen; + for (auto& name : names) { + if (!name.empty() && seen.insert(name).second) { + bool isCName = name == cName; + aliases.push_back(Alias{std::move(name), isCName}); + } + } + return aliases; +} + +} // namespace enum_members +} // namespace nativescript + +#endif // NATIVESCRIPT_FFI_OBJC_SHARED_ENUM_MEMBER_ALIASES_H diff --git a/NativeScript/ffi/objc/shared/bridge/HostObject.mm b/NativeScript/ffi/objc/shared/bridge/HostObject.mm index 146824f25..6ab4d0493 100644 --- a/NativeScript/ffi/objc/shared/bridge/HostObject.mm +++ b/NativeScript/ffi/objc/shared/bridge/HostObject.mm @@ -509,6 +509,21 @@ throw JSError(runtime, return enumToObject(runtime, bridge->metadata(), *symbol); }); } + if (property == "getEnumGlobalNames") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getEnumGlobalNames"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string enumName = readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->findEnum(enumName); + if (symbol == nullptr) { + return namesToArray(runtime, {}); + } + return namesToArray(runtime, + enumGlobalNames(bridge->metadata(), *symbol)); + }); + } if (property == "getProtocol") { auto bridge = bridge_; return Function::createFromHostFunction( @@ -626,6 +641,7 @@ throw JSError(runtime, addPropertyName(runtime, names, "getFunction"); addPropertyName(runtime, names, "getConstant"); addPropertyName(runtime, names, "getEnum"); + addPropertyName(runtime, names, "getEnumGlobalNames"); addPropertyName(runtime, names, "getProtocol"); addPropertyName(runtime, names, "getStruct"); addPropertyName(runtime, names, "getUnion"); diff --git a/NativeScript/ffi/objc/shared/bridge/Install.mm b/NativeScript/ffi/objc/shared/bridge/Install.mm index 6fd3e46b7..6623f8491 100644 --- a/NativeScript/ffi/objc/shared/bridge/Install.mm +++ b/NativeScript/ffi/objc/shared/bridge/Install.mm @@ -2062,16 +2062,10 @@ function resolveNativeApiEnum(enumName) { }); 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; - } + api.getEnumGlobalNames(name).forEach(function(memberName) { defineLazyGlobal(memberName, function() { - return enumValue[memberName]; + var enumValue = resolveNativeApiEnum(name); + return enumValue && enumValue[memberName]; }, false, 'enumMember:' + name); }); }); diff --git a/NativeScript/ffi/objc/shared/bridge/Invocation.mm b/NativeScript/ffi/objc/shared/bridge/Invocation.mm index 1925015e1..2bcfac78f 100644 --- a/NativeScript/ffi/objc/shared/bridge/Invocation.mm +++ b/NativeScript/ffi/objc/shared/bridge/Invocation.mm @@ -30,14 +30,6 @@ bool endsWith(const std::string& value, const std::string& suffix) { 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) @@ -101,72 +93,45 @@ Value enumToObject(Runtime& runtime, MDMetadataReader* metadata, return result; } - std::string enumName = symbol.name; + const 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); - } - } + metadata->forEachEnumMember( + symbol.offset, [&](const char* memberName, const std::string& cName, + int64_t value) { + std::string storedName = memberName != nullptr ? memberName : ""; + auto aliases = enum_members::aliasesFor(enumName, strippedPrefix, + storedName, cName); + for (const auto& alias : aliases) { + result.setProperty(runtime, alias.name.c_str(), + static_cast(value)); + } - 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)) { + const std::string& reverseName = + aliases.size() > 1 ? aliases[1].name : storedName; + result.setProperty(runtime, valueKey, + makeString(runtime, reverseName)); + } + }); + return result; +} - 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)); - } +std::vector enumGlobalNames(MDMetadataReader* metadata, + const NativeApiSymbol& symbol) { + std::vector names; + if (metadata == nullptr || symbol.offset == MD_SECTION_OFFSET_NULL) { + return names; } - return result; + metadata->forEachEnumMember( + symbol.offset, [&](const char*, const std::string& cName, int64_t) { + if (!cName.empty()) { + names.push_back(cName); + } + }); + return names; } Value constantToValue(Runtime& runtime, diff --git a/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm b/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm index 051171816..1ae78ad0b 100644 --- a/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm +++ b/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm @@ -933,7 +933,7 @@ Value findRoundTripValue(Runtime& runtime, const void* native, ? validationKey : (nativeIsObject ? nativeObjectClassKey(native) : 0); struct RoundTripCacheEntry { - const NativeApiBridge* bridge = nullptr; + uint64_t bridgeId = 0; uintptr_t key = 0; uint64_t generation = 0; std::weak_ptr value; @@ -947,7 +947,7 @@ Value findRoundTripValue(Runtime& runtime, const void* native, 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 && + if (entry.bridgeId == instanceId_ && entry.key == key && entry.generation == generation) { if (entry.validationKey != expectedValidationKey) { break; @@ -1015,19 +1015,19 @@ Value findRoundTripValue(Runtime& runtime, const void* native, } if (weakValue == nullptr) { cache[firstSlot] = RoundTripCacheEntry{ - this, key, generation, {}, true, false, expectedValidationKey}; + instanceId_, key, generation, {}, true, false, expectedValidationKey}; return Value::undefined(); } #else cache[firstSlot] = RoundTripCacheEntry{ - this, key, generation, {}, true, false, expectedValidationKey}; + instanceId_, key, generation, {}, true, false, expectedValidationKey}; return Value::undefined(); #endif } else { storedValue = entry->value; cachedStringLike = entry->stringLikeNative; cache[firstSlot] = RoundTripCacheEntry{ - this, key, generation, storedValue, false, cachedStringLike, + instanceId_, key, generation, storedValue, false, cachedStringLike, entry->validationKey}; } } @@ -1214,6 +1214,7 @@ void setObjectExpando(Runtime& runtime, const void* native, const uintptr_t runtimeKey = runtimeObjectExpandoKey(runtime); { std::lock_guard lock(objectExpandosMutex_); + purgeRetiredObjectExpandosLocked(key); objectExpandos_[key][property][runtimeKey] = std::make_shared(runtime, value); objectExpandosGeneration_.fetch_add(1, std::memory_order_release); @@ -1224,9 +1225,11 @@ void retainObjectExpandoOwner(const void* native) { if (native == nullptr) { return; } + const uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); std::lock_guard lock(objectExpandosMutex_); - objectExpandoOwnerCounts_[ - normalizeRuntimePointer(reinterpret_cast(native))] += 1; + purgeRetiredObjectExpandosLocked(key); + objectExpandoOwnerCounts_[key] += 1; } // preserveExpandos keeps the stored values around after the last owner @@ -1263,7 +1266,7 @@ Value findObjectExpando(Runtime& runtime, const void* native, return Value::undefined(); } struct ObjectExpandoCacheEntry { - const NativeApiBridge* bridge = nullptr; + uint64_t bridgeId = 0; uintptr_t key = 0; uintptr_t runtimeKey = 0; uint64_t generation = 0; @@ -1280,7 +1283,7 @@ Value findObjectExpando(Runtime& runtime, const void* native, const uint64_t generation = objectExpandosGeneration_.load(std::memory_order_acquire); for (auto& entry : cache) { - if (entry.bridge == this && entry.key == key && + if (entry.bridgeId == instanceId_ && entry.key == key && entry.runtimeKey == runtimeKey && entry.generation == generation && entry.property == property) { if (entry.miss) { @@ -1297,7 +1300,9 @@ Value findObjectExpando(Runtime& runtime, const void* native, const size_t slot = nextSlot++ & 7; { std::lock_guard lock(objectExpandosMutex_); - auto objectIt = objectExpandos_.find(key); + auto objectIt = retiredObjectExpandoKeys_.count(key) != 0 + ? objectExpandos_.end() + : objectExpandos_.find(key); if (objectIt != objectExpandos_.end()) { auto propertyIt = objectIt->second.find(property); if (propertyIt != objectIt->second.end()) { @@ -1310,15 +1315,17 @@ Value findObjectExpando(Runtime& runtime, const void* native, } if (storedValue == nullptr) { cache[slot] = ObjectExpandoCacheEntry{ - this, key, runtimeKey, generation, property, {}, true}; + instanceId_, key, runtimeKey, generation, property, {}, true}; return Value::undefined(); } cache[slot] = ObjectExpandoCacheEntry{ - this, key, runtimeKey, generation, property, storedValue, false}; + instanceId_, key, runtimeKey, generation, property, storedValue, false}; } return Value(runtime, *storedValue); } + uint64_t instanceId() const { return instanceId_; } + // Erases every runtime's stored values for this native (codex semantics): // the native object itself is gone, so no runtime should keep seeing it. void forgetObjectExpandos(const void* native) { @@ -1329,6 +1336,23 @@ void forgetObjectExpandos(const void* native) { { std::lock_guard lock(objectExpandosMutex_); objectExpandos_.erase(key); + retiredObjectExpandoKeys_.erase(key); + objectExpandosGeneration_.fetch_add(1, std::memory_order_release); + } + } + + // For a native that deallocated while its expandos were preserved. Safe on + // any thread: lookups stop seeing them at once, but the stored values are + // only destroyed on a runtime thread, when the address is next wrapped or + // written to. + void retireObjectExpandos(const void* native) { + if (native == nullptr) { + return; + } + auto key = normalizeRuntimePointer(reinterpret_cast(native)); + std::lock_guard lock(objectExpandosMutex_); + if (objectExpandos_.count(key) != 0) { + retiredObjectExpandoKeys_.insert(key); objectExpandosGeneration_.fetch_add(1, std::memory_order_release); } } @@ -1350,7 +1374,7 @@ void forgetObjectExpandos(const void* native) { return nullptr; } struct PropertyGetterCacheEntry { - const NativeApiBridge* bridge = nullptr; + uint64_t bridgeId = 0; Class cls = Nil; uint64_t generation = 0; std::string property; @@ -1363,7 +1387,7 @@ void forgetObjectExpandos(const void* native) { const uint64_t generation = propertyGetterCacheGeneration_.load(std::memory_order_acquire); for (auto& entry : cache) { - if (entry.bridge == this && entry.cls == cls && + if (entry.bridgeId == instanceId_ && entry.cls == cls && entry.generation == generation && entry.property == property) { return entry.miss ? nullptr : entry.getter; } @@ -1373,17 +1397,17 @@ void forgetObjectExpandos(const void* native) { const size_t slot = nextSlot++ & 7; if (classIt == propertyGetterCache_.end()) { cache[slot] = PropertyGetterCacheEntry{ - this, cls, generation, property, nullptr, true}; + instanceId_, 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}; + instanceId_, cls, generation, property, nullptr, true}; return nullptr; } cache[slot] = - PropertyGetterCacheEntry{this, cls, generation, property, + PropertyGetterCacheEntry{instanceId_, cls, generation, property, &propIt->second, false}; return &propIt->second; } @@ -1922,15 +1946,7 @@ void indexEnums() { 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); - } + offset = metadata_->skipEnum(originalOffset); } } @@ -2469,7 +2485,24 @@ static void appendSurfaceMember( std::unordered_map>>> objectExpandos_; std::unordered_map objectExpandoOwnerCounts_; + // Addresses whose expandos belong to a deallocated native; see + // retireObjectExpandos. + std::unordered_set retiredObjectExpandoKeys_; std::atomic objectExpandosGeneration_{1}; + + // Callers hold objectExpandosMutex_ and run on a runtime thread. + void purgeRetiredObjectExpandosLocked(uintptr_t key) { + if (retiredObjectExpandoKeys_.erase(key) != 0) { + objectExpandos_.erase(key); + } + } + // Identifies this bridge in the thread_local lookup caches. Its address + // cannot: a bridge allocated where a destroyed one lived would match that + // one's entries, and the property-getter cache holds raw pointers into it. + const uint64_t instanceId_ = [] { + static std::atomic next{1}; + return next.fetch_add(1, std::memory_order_relaxed); + }(); std::unordered_map> propertyGetterCache_; std::atomic propertyGetterCacheGeneration_{1}; diff --git a/NativeScript/ffi/objc/shared/bridge/host_objects/Object.mm b/NativeScript/ffi/objc/shared/bridge/host_objects/Object.mm index b56675e04..703e83f6b 100644 --- a/NativeScript/ffi/objc/shared/bridge/host_objects/Object.mm +++ b/NativeScript/ffi/objc/shared/bridge/host_objects/Object.mm @@ -523,6 +523,46 @@ Array runtimeMembersArray(Runtime& runtime, Class cls, bool staticMembers) { return result; } +// Preserved expandos (see releaseObjectExpandoOwner) must still die with the +// native object: they are keyed by address, so the next object allocated there +// would be served this one's cached methods. The associated block is released +// while the object deallocates, before its memory can be reused, and that can +// happen on any thread -- hence retiring rather than forgetting them. +void retireObjectExpandosOnDealloc(const std::shared_ptr& bridge, + id object) { + if (bridge == nullptr || object == nil) { + return; + } + + struct Retire { + Retire(std::weak_ptr bridge, const void* native) + : bridge(std::move(bridge)), native(native) {} + ~Retire() { + if (auto alive = bridge.lock()) { + alive->retireObjectExpandos(native); + } + } + std::weak_ptr bridge; + const void* native; + }; + + // Any value unique per bridge works as an association key. + const void* key = reinterpret_cast( + static_cast(bridge->instanceId())); + // Wrappers on two runtime threads can get here for one object at once; a + // second set would replace, and so release, the first block early. + static std::mutex associationMutex; + std::lock_guard lock(associationMutex); + if (objc_getAssociatedObject(object, key) != nil) { + return; + } + // Shared so that copying the block does not run the destructor early. + auto retire = std::make_shared(bridge, (const void*)object); + objc_setAssociatedObject(object, key, ^{ + (void)retire; + }, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + class NativeApiObjectHostObject final : public HostObject, public std::enable_shared_from_this { @@ -548,7 +588,7 @@ Array runtimeMembersArray(Runtime& runtime, Class cls, bool staticMembers) { ~NativeApiObjectHostObject() override { if (bridge_ != nullptr && object_ != nil) { bridge_->forgetRoundTripValue(object_); - bridge_->releaseObjectExpandoOwner( + releaseExpandoOwnership( object_, class_conformsToProtocol(object_getClass(object_), @protocol(NativeApiClassBuilderProtocol))); } @@ -583,7 +623,7 @@ void disownObject(id expected, bool preserveExpandos = false) { if (object_ == expected) { if (bridge_ != nullptr && expected != nil) { bridge_->forgetRoundTripValue(expected); - bridge_->releaseObjectExpandoOwner(expected, preserveExpandos); + releaseExpandoOwnership(expected, preserveExpandos); } ownsObject_ = false; wrapperRetainedObject_ = false; @@ -611,7 +651,7 @@ void detachObjectPreservingBridgeState(id expected) { id object = object_; bool releaseObject = ownsObject_; if (bridge_ != nullptr && expected != nil) { - bridge_->releaseObjectExpandoOwner(expected, /*preserveExpandos=*/true); + releaseExpandoOwnership(expected, /*preserveExpandos=*/true); } ownsObject_ = false; wrapperRetainedObject_ = false; @@ -1641,6 +1681,13 @@ throw JSError( } private: + void releaseExpandoOwnership(id object, bool preserveExpandos) { + if (preserveExpandos) { + retireObjectExpandosOnDealloc(bridge_, object); + } + bridge_->releaseObjectExpandoOwner(object, preserveExpandos); + } + std::shared_ptr bridge_; id object_ = nil; bool ownsObject_ = false; diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h index 67c59063d..bc28ab2a3 100644 --- a/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h +++ b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h @@ -24,6 +24,7 @@ #include "Metadata.h" #include "MetadataReader.h" #include "ffi.h" +#include "ffi/objc/shared/EnumMemberAliases.h" #include "jsi/v8/V8Runtime.h" diff --git a/NativeScript/jsi/hermes/HermesRuntime.h b/NativeScript/jsi/hermes/HermesRuntime.h index b3f42cd65..a01a29ea2 100644 --- a/NativeScript/jsi/hermes/HermesRuntime.h +++ b/NativeScript/jsi/hermes/HermesRuntime.h @@ -1208,6 +1208,57 @@ inline jsi::Value makeConstructReceiver(jsi::Runtime& rt, return jsi::Value(jsi::Object::create(rt, prototype)); } +// A JS function that wraps a host function so the result is constructible. +// +// Whether Hermes lets you `new` a native function is a property of the Hermes +// build, not of jsi: the vendored static-hermes allows it, the Hermes React +// Native ships throws "This function cannot be used as a constructor". Since +// @nativescript/react-native runs on whatever Hermes the app has, the +// constructor cannot be a native function at all. An ordinary JS function is +// constructible everywhere, gets a correct `this` from the language itself, and +// comes with a `prototype` whose `constructor` back-pointer is already right. +// +// Cached on the global because the factory is per-runtime and there is nowhere +// else to hang per-runtime state; the property is non-enumerable so it does not +// show up in application code. +inline jsi::Function hostConstructorFactory(jsi::Runtime& rt) { + static constexpr const char* kFactoryKey = "__nsHostConstructorFactory"; + + jsi::Value cached = rt.global().getProperty(rt, kFactoryKey); + if (cached.isObject() && cached.getObject(rt).isFunction(rt)) { + return cached.getObject(rt).getFunction(rt); + } + + jsi::Value made = rt.evaluateJavaScript( + std::make_shared( + "(function (hostFn, name, length) {" + " function NativeScriptClass() {" + " return hostFn.apply(this, arguments);" + " }" + " Object.defineProperty(NativeScriptClass, 'name'," + " { value: name, configurable: true });" + " Object.defineProperty(NativeScriptClass, 'length'," + " { value: length, configurable: true });" + " return NativeScriptClass;" + "})"), + "nativescript:host-constructor"); + + jsi::Function factory = made.getObject(rt).getFunction(rt); + + jsi::Object global = rt.global(); + jsi::Function defineProperty = global.getPropertyAsObject(rt, "Object") + .getPropertyAsFunction(rt, "defineProperty"); + jsi::Object descriptor(rt); + descriptor.setProperty(rt, "value", jsi::Value(rt, factory)); + descriptor.setProperty(rt, "enumerable", false); + descriptor.setProperty(rt, "configurable", true); + defineProperty.call(rt, jsi::Value(rt, global), + jsi::String::createFromAscii(rt, kFactoryKey), + jsi::Value(rt, descriptor)); + + return factory; +} + inline jsi::Function makeHostConstructor(Runtime& runtime, const PropNameID& name, unsigned int paramCount, @@ -1239,21 +1290,23 @@ inline jsi::Function makeHostConstructor(Runtime& runtime, } }); - weakSelf->emplace(rt, fn); - // A writable own `prototype`: MetadataNode chains class prototypes with a - // plain `ctor.prototype = ...` assignment, and a Hermes host function has no - // prototype at all until one is installed. - // - // It carries a `constructor` back-pointer, as the spec requires of any - // function's prototype. V8's Function::New installs one for us; a bare object - // has none, so `instance.constructor` walked past the class prototype to - // Object.prototype.constructor and every native class reported its name as - // "Object". The QuickJS and JSC backends needed the same property for the - // same reason. - jsi::Object prototype(rt); - prototype.setProperty(rt, "constructor", jsi::Value(rt, fn)); - fn.setProperty(rt, "prototype", prototype); - return fn; + // Wrap it in a real JS function; see hostConstructorFactory above. The + // wrapper brings its own writable `prototype` carrying the `constructor` + // back-pointer the spec requires, which is what stops `instance.constructor` + // walking past the class prototype and reporting every native class as + // "Object". + jsi::Function factory = hostConstructorFactory(rt); + jsi::Value made = factory.call(rt, jsi::Value(rt, fn), + jsi::String::createFromUtf8(rt, text), + jsi::Value(static_cast(paramCount))); + jsi::Function ctor = made.getObject(rt).getFunction(rt); + + // Weak, and pointing at the wrapper rather than the host function: it is the + // wrapper that owns `prototype`, and it is what a direct (non-`new`) call has + // to synthesise a receiver from. Strong would be a cycle through + // prototype.constructor that the GC cannot break. + weakSelf->emplace(rt, ctor); + return ctor; } } // namespace hermesengine diff --git a/NativeScript/runtime/android/jsi/EngineHost.cpp b/NativeScript/runtime/android/jsi/EngineHost.cpp index d384d3ac1..7b54b8d28 100644 --- a/NativeScript/runtime/android/jsi/EngineHost.cpp +++ b/NativeScript/runtime/android/jsi/EngineHost.cpp @@ -11,7 +11,8 @@ #include #endif -#if defined(TARGET_ENGINE_HERMES) +// Only the VM-owning build needs the Hermes VM headers; see EngineHost.h. +#if defined(TARGET_ENGINE_HERMES) && !defined(NS_JSI_HOST_RUNTIME) #include #endif @@ -108,6 +109,21 @@ void EngineHost::SetFlags(const char *flags) { #endif } +#if defined(NS_JSI_HOST_RUNTIME) + +std::shared_ptr EngineHost::Adopt(::facebook::jsi::Runtime &runtime) { + // Not make_shared: the constructor is private. + std::shared_ptr host(new EngineHost()); + // The non-owning engine::Runtime constructor (jsi/hermes/HermesRuntime.h). + // There is no RuntimeConfig to mirror from the Create() path below: the + // embedder already built and configured the VM, and re-deciding any of it + // here would either be ignored or corrupt a runtime other code is using. + host->m_runtime = std::make_unique(runtime); + return host; +} + +#else + std::shared_ptr EngineHost::Create() { // Not make_shared: the constructor is private. std::shared_ptr host(new EngineHost()); @@ -229,10 +245,12 @@ std::shared_ptr EngineHost::Create() { return host; } +#endif // NS_JSI_HOST_RUNTIME + void EngineHost::Lock() { m_mutex.lock(); m_lockDepth++; -#if defined(TARGET_ENGINE_HERMES) +#if defined(TARGET_ENGINE_HERMES) && !defined(NS_JSI_HOST_RUNTIME) // Registers the calling thread with the VM. Hermes records per-thread stack // bounds for its overflow guard, so entering from a Java thread without this // faults rather than raising a RangeError. @@ -242,7 +260,7 @@ void EngineHost::Lock() { void EngineHost::Unlock() { if (m_lockDepth > 0) m_lockDepth--; -#if defined(TARGET_ENGINE_HERMES) +#if defined(TARGET_ENGINE_HERMES) && !defined(NS_JSI_HOST_RUNTIME) if (m_threadSafe != nullptr) m_threadSafe->unlock(); #endif m_mutex.unlock(); @@ -250,7 +268,11 @@ void EngineHost::Unlock() { void EngineHost::ExecutePendingJobs() { if (m_runtime == nullptr) return; +#if defined(NS_JSI_HOST_RUNTIME) + // The embedder owns the queue and decides when it runs; see ~JSScope. +#else m_runtime->drainMicrotasks(); +#endif } engine::Value EngineHost::ExecuteScript(const std::string &source, const std::string &sourceURL) { @@ -395,8 +417,10 @@ void EngineHost::ReleaseEngineState() { } EngineHost::~EngineHost() { + // Under NS_JSI_HOST_RUNTIME this drops the non-owning engine::Runtime + // wrapper only; the jsi::Runtime behind it belongs to the embedder. m_runtime.reset(); -#if defined(TARGET_ENGINE_HERMES) +#if defined(TARGET_ENGINE_HERMES) && !defined(NS_JSI_HOST_RUNTIME) m_threadSafe.reset(); #endif #if defined(TARGET_ENGINE_QUICKJS) diff --git a/NativeScript/runtime/android/jsi/EngineHost.h b/NativeScript/runtime/android/jsi/EngineHost.h index 7357c540d..9a6016cdf 100644 --- a/NativeScript/runtime/android/jsi/EngineHost.h +++ b/NativeScript/runtime/android/jsi/EngineHost.h @@ -11,7 +11,7 @@ // EngineHost is held by shared_ptr and JSScope keeps a strong reference for its // lifetime. That is what makes teardown safe by construction: the VM, and the // recursive mutex the scope holds, cannot be freed while a scope is still -// unwinding over them. See Runtime::DisposeWorkerRuntime. +// unwinding over them. See Runtime::DisposeRuntime. #include #include @@ -33,7 +33,14 @@ #include "v8.h" #endif -#if defined(TARGET_ENGINE_HERMES) +// NS_JSI_HOST_RUNTIME is the guest flavour (@nativescript/react-native): the +// jsi::Runtime belongs to the embedder, which created it, drives its microtask +// queue and guarantees the thread it is entered on. There is no VM to build and +// no ThreadSafeRuntime to register threads with, so the two things that need +// the Hermes VM headers -- makeThreadSafeHermesRuntime and jsi/threadsafe.h -- +// are the only pieces of the engine that this build does not compile. That is +// what lets the binary link nothing but the embedder's libjsi.so. +#if defined(TARGET_ENGINE_HERMES) && !defined(NS_JSI_HOST_RUNTIME) #include #endif @@ -48,7 +55,14 @@ class EngineHost { // `__collect = gc;` on its sixth line, so this is not optional on V8. static void SetFlags(const char *flags); +#if defined(NS_JSI_HOST_RUNTIME) + // Adopts a runtime the embedder owns. The reference must outlive this host: + // React Native tears its runtime down only after the TurboModule that holds + // us has been invalidated, which is where Runtime::DisposeRuntime runs. + static std::shared_ptr Adopt(::facebook::jsi::Runtime &runtime); +#else static std::shared_ptr Create(); +#endif ~EngineHost(); @@ -115,7 +129,7 @@ class EngineHost { std::unique_ptr m_allocator; #endif -#if defined(TARGET_ENGINE_HERMES) +#if defined(TARGET_ENGINE_HERMES) && !defined(NS_JSI_HOST_RUNTIME) // Owns the VM. engine::Runtime only borrows getUnsafeRuntime(). std::unique_ptr m_threadSafe; #endif @@ -154,13 +168,19 @@ class JSScope { } ~JSScope() { -#if defined(TARGET_ENGINE_HERMES) || defined(TARGET_ENGINE_QUICKJS) +#if (defined(TARGET_ENGINE_HERMES) && !defined(NS_JSI_HOST_RUNTIME)) || \ + defined(TARGET_ENGINE_QUICKJS) // Hermes and QuickJS run Promise jobs from an explicit microtask queue and // nothing else on Android drains it; V8 and JSC run theirs themselves when // the host stack unwinds. Draining as the *outermost* scope leaves JS is // what keeps the ordering the specs assert: a microtask queued during a // call runs before the next timer callback, which enters through its own // scope. + // + // NS_JSI_HOST_RUNTIME is excluded because the embedder already drains: + // React Native's RuntimeScheduler runs the queue at the end of each task + // it dispatches. Draining from inside a nested scope of ours would run + // application microtasks at a point RN did not choose. if (m_host->LockDepth() <= 1 && m_host->IsAlive()) { m_host->ExecutePendingJobs(); } diff --git a/NativeScript/runtime/android/jsi/Runtime.cpp b/NativeScript/runtime/android/jsi/Runtime.cpp index 5ee7dfeb1..b5c49f7df 100644 --- a/NativeScript/runtime/android/jsi/Runtime.cpp +++ b/NativeScript/runtime/android/jsi/Runtime.cpp @@ -8,7 +8,9 @@ #include #include #include -#include "zipconf.h" +// PRId64, for the memory-accounting trace below. It used to arrive by accident +// through libzip's zipconf.h, which this file included but never used. +#include #include "NativeScriptException.h" #include #include "File.h" @@ -19,19 +21,27 @@ #include "NativeScriptAssert.h" #include "CallbackHandlers.h" #include "MetadataNode.h" -#include "Console.h" #include "Util.h" -#include "Performance.h" #include "JsArgToArrayConverter.h" #include "ArrayHelper.h" -#include "SimpleProfiler.h" -#include "ManualInstrumentation.h" #include "GlobalHelpers.h" -#include "Timers.h" #include "AndroidRuntimeModules.h" +// The main-thread task queue, not a worker facility: this is how a Java +// callback arriving on another thread gets back onto the JS thread. #include "LooperTasks.h" +// Everything below belongs to the standalone runtime only. Excluded from the +// guest build so it is not compiled into an app that can never reach it -- see +// the source filters in packages/react-native/android/CMakeLists.txt. +#if !defined(NS_JSI_HOST_RUNTIME) +#include "Console.h" +#include "Performance.h" +#include "SimpleProfiler.h" +#include "ManualInstrumentation.h" +#include "Timers.h" +#endif + using namespace tns; using namespace std; @@ -173,6 +183,11 @@ jobject Runtime::GetJavaRuntime() const { return m_runtime; } +// The VM-owning entry points. A guest build (NS_JSI_HOST_RUNTIME) has no +// EngineHost::Create to call and is handed its runtime by the embedder, so this +// is where the two flavours part; they rejoin in InitCommon below. +#if !defined(NS_JSI_HOST_RUNTIME) + void Runtime::Init(JNIEnv *_env, jobject obj, int runtimeId, jstring filesPath, jstring nativeLibsDir, jboolean verboseLoggingEnabled, jboolean isDebuggable, jstring packageName, @@ -206,12 +221,63 @@ void Runtime::Init(JNIEnv *_env, jstring filesPath, jstring nativeLibsDir, JniLocalRef profilerOutputDir(_env->GetObjectArrayElement(args, 2)); + /* Index 14 == AppConfig.KnownKeys.ContentKeyedBindings, which the app's + * build sets when it generated content-keyed static bindings. The generated + * names and the names looked up here are halves of one scheme, so this must + * come from the build that ran the generator -- never from a guess made + * here. A runtime compiled to require content keys ignores a false. */ + JniLocalRef contentKeyedBindings(_env->GetObjectArrayElement(args, 14)); + MetadataNode::SetContentKeyedBindings((bool) contentKeyedBindings); + EngineHost::SetFlags(flags.c_str()); engineHost = EngineHost::Create(); if (engineHost == nullptr) { throw NativeScriptException("Failed to create JS runtime"); } + InitCommon(filesRoot, callingDirStr, maxLogcatObjectSize, forceLog, /*guest*/ false); +} + +#else // NS_JSI_HOST_RUNTIME + +Runtime *Runtime::Attach(JNIEnv *_env, jobject obj, int runtimeId, + ::facebook::jsi::Runtime &hostRuntime, const std::string &filesRoot, + bool verboseLoggingEnabled, int maxLogcatObjectSize, bool forceLog) { + JEnv env(_env); + auto runtime = new Runtime(env, obj, runtimeId); + runtime->Attach(hostRuntime, filesRoot, verboseLoggingEnabled, maxLogcatObjectSize, forceLog); + return runtime; +} + +void Runtime::Attach(::facebook::jsi::Runtime &hostRuntime, const std::string &filesRoot, + bool verboseLoggingEnabled, int maxLogcatObjectSize, bool forceLog) { + LogEnabled = verboseLoggingEnabled; + + // Only the metadata reader and the (unused here) module loader read this. + Constants::APP_ROOT_FOLDER_PATH = filesRoot + "/app/"; + + DEBUG_WRITE("Attaching NativeScript to a host-provided JSI runtime"); + + // No SetFlags: the embedder built the VM long before we were loaded, and on + // every engine the flags this would set are read at VM construction. + engineHost = EngineHost::Adopt(hostRuntime); + + InitCommon(filesRoot, /*callingDir*/ "", maxLogcatObjectSize, forceLog, /*guest*/ true); +} + +#endif // NS_JSI_HOST_RUNTIME + +// Everything both flavours install. +// +// `guest` is true when the runtime belongs to an embedder. A host like React +// Native already provides console, timers, a module loader, performance and +// queueMicrotask, and ours are wired to a message loop and a require() root +// that only exist in a standalone app -- installing them over the host's would +// replace working implementations with broken ones. What a guest still needs is +// the part no host can provide: the metadata tree, the object manager, and the +// JNI interop callbacks. +void Runtime::InitCommon(const std::string &filesRoot, const std::string &callingDir, + int maxLogcatObjectSize, bool forceLog, bool guest) { // The napi runtime opens a process-lifetime handle scope here (global_scope) // so that napi_values created outside any explicit scope have somewhere to // live. There is no such thing to hold open: an owned engine::Value roots @@ -228,9 +294,13 @@ void Runtime::Init(JNIEnv *_env, jstring filesPath, jstring nativeLibsDir, // Newer JSC ships a native `WeakRef` global, so the old polyfill (which was // actually a strong reference and leaked) is no longer needed. - Console::createConsole(rt, maxLogcatObjectSize, forceLog); +#if !defined(NS_JSI_HOST_RUNTIME) + if (!guest) { + Console::createConsole(rt, maxLogcatObjectSize, forceLog); - Timers::InitStatic(rt, global); + Timers::InitStatic(rt, global); + } +#endif // Bound to this (runtime) thread's Looper; drains deferred finalizers posted // via Runtime::PostFinalizer at a safe point off the GC sweep. @@ -294,7 +364,9 @@ void Runtime::Init(JNIEnv *_env, jstring filesPath, jstring nativeLibsDir, return engine::Value::undefined(); }); +#if !defined(NS_JSI_HOST_RUNTIME) SimpleProfiler::Init(rt, global); +#endif CallbackHandlers::CreateGlobalCastFunctions(rt); @@ -306,7 +378,11 @@ void Runtime::Init(JNIEnv *_env, jstring filesPath, jstring nativeLibsDir, m_objectManager->Init(rt); - m_module.Init(rt, ArgConverter::jstringToString(callingDir)); +#if !defined(NS_JSI_HOST_RUNTIME) + if (!guest) { + m_module.Init(rt, callingDir); + } +#endif if (!s_mainThreadInitialized) { m_isMainThread = true; @@ -339,9 +415,14 @@ void Runtime::Init(JNIEnv *_env, jstring filesPath, jstring nativeLibsDir, * Emulate a `WorkerGlobalScope` * Attach 'postMessage', 'close' to the global object of every non-main * (worker) env. + * + * A guest is only ever attached to the embedder's runtime, which is a + * main-thread runtime by construction, so this branch is unreachable + * there and the worker callbacks it needs are not built. */ else { m_isMainThread = false; +#if !defined(NS_JSI_HOST_RUNTIME) engine_util::SetFunction(rt, global, "postMessage", CallbackHandlers::WorkerGlobalPostMessageCallback); engine_util::SetFunction(rt, global, "close", @@ -349,13 +430,17 @@ void Runtime::Init(JNIEnv *_env, jstring filesPath, jstring nativeLibsDir, engine_util::SetFunction(rt, global, "terminate", CallbackHandlers::WorkerGlobalCloseCallback); global.setProperty(rt, "__ns__worker", true); +#endif } /* * Attach the `Worker` object constructor to EVERY env's global object so * that nested workers (a worker spawning its own workers) are supported. */ - { + // A worker gets its own VM, which a guest has no way to build -- and React + // Native ships its own Worker story anyway. +#if !defined(NS_JSI_HOST_RUNTIME) + if (!guest) { engine::Function worker = engine::Function::createFromHostConstructor( rt, engine::PropNameID::forAscii(rt, "Worker"), 0, CallbackHandlers::NewThreadCallback); @@ -366,12 +451,16 @@ void Runtime::Init(JNIEnv *_env, jstring filesPath, jstring nativeLibsDir, CallbackHandlers::WorkerObjectTerminateCallback); global.setProperty(rt, "Worker", worker); } +#endif // The napi runtime installs `global` (and `self`) as accessors returning the // current global object. nativescript::engine has no accessor API, and a // plain self-reference is what globalThis already is, so these are data - // properties here. - global.setProperty(rt, "global", global); + // properties here. A guest leaves it alone: React Native has already defined + // `global`, and it is the same object either way. + if (!guest) { + global.setProperty(rt, "global", global); + } if (!s_mainThreadInitialized) { MetadataNode::BuildMetadata(filesRoot); @@ -384,13 +473,22 @@ void Runtime::Init(JNIEnv *_env, jstring filesPath, jstring nativeLibsDir, ArrayHelper::Init(rt); - Performance::createPerformance(rt, global); +#if !defined(NS_JSI_HOST_RUNTIME) + if (!guest) { + Performance::createPerformance(rt, global); - engine_util::SetFunction(rt, global, "queueMicrotask", QueueMicrotaskCallback, 1); + engine_util::SetFunction(rt, global, "queueMicrotask", QueueMicrotaskCallback, 1); + } +#endif m_arrayBufferHelper.CreateConvertFunctions(rt, global, m_objectManager); - m_loopTimer->Init(engineHost); + // Drives our own timers and GC hints off this thread's looper. A guest's + // timers belong to the host, and the GC hint would be measuring a heap we + // do not own. + if (!guest) { + m_loopTimer->Init(engineHost); + } // Per-runtime task queue bound to this thread's looper. Child workers post // their outbound messages/errors/cleanup onto their parent runtime's queue. @@ -448,7 +546,16 @@ Runtime::~Runtime() { if (m_mainLooper_fd[1] != -1) { close(m_mainLooper_fd[1]); } + + // A guest's main runtime is deleted when its host runtime goes away, and + // the next one attached may run on a different thread. + m_mainLooper = nullptr; + m_mainLooper_fd[0] = m_mainLooper_fd[1] = -1; + s_main_rt = nullptr; + s_mainThreadInitialized = false; } + + JEnv().DeleteGlobalRef(m_runtime); } std::string Runtime::ReadFileText(const std::string &filePath) { @@ -459,6 +566,15 @@ std::string Runtime::ReadFileText(const std::string &filePath) { } void Runtime::DestroyRuntime() { + // Flush still-queued finalizer cleanup before is_destroying is set: with it + // set, the cleanup leaks the engine handles it would release, and a quit + // looper never drains it. Finalizers firing during teardown below run + // inline (Runtime::PostFinalizer's fallback). + if (m_finalizerQueue != nullptr) { + m_finalizerQueue->Destroy(); + delete m_finalizerQueue; + m_finalizerQueue = nullptr; + } is_destroying = true; engine::Runtime &rt = engineHost->GetRuntime(); if (m_looperTasks != nullptr) { @@ -470,22 +586,16 @@ void Runtime::DestroyRuntime() { this->js_method_cache->cleanupCache(); delete this->js_method_cache; this->js_method_cache = nullptr; +#if !defined(NS_JSI_HOST_RUNTIME) this->m_module.DeInit(); Console::onDisposeRuntime(rt); // The napi version tears the timers down from a finalizer on the global // object; there is no equivalent here, so it is driven from the same place // as every other per-runtime teardown. Timers::onDisposeRuntime(rt); +#endif CallbackHandlers::RemoveEnvEntries(rt); this->m_objectManager->OnDisposeRuntime(); - // Release the finalizer handler and flush any still-queued cleanup while the - // runtime is still valid; finalizers firing during teardown below then run - // inline (Runtime::PostFinalizer's fallback). - if (m_finalizerQueue != nullptr) { - m_finalizerQueue->Destroy(); - delete m_finalizerQueue; - m_finalizerQueue = nullptr; - } // Every engine handle this runtime still owns must go while the VM is up. m_gcFunc = engine::Value::undefined(); // Last, because everything above may still call a js_util helper on its way @@ -505,6 +615,19 @@ void Runtime::DestroyRuntime() { // and ~EngineHost does the teardown once nothing is standing on it. } +void Runtime::DisposeRuntime(Runtime *runtime) { + // `engineHost` is referenced by the JSEnterScope macro below. + std::shared_ptr engineHost = runtime->GetEngineHost(); + { + JSEnterScope + runtime->DestroyRuntime(); + } + // Both this scope's reference and the runtime's are gone by the end of the + // next line, and whichever drops last runs ~EngineHost. Nothing frees the VM + // while the scope above is still unwinding over it. + delete runtime; +} + bool Runtime::NotifyGC(JNIEnv *jEnv, jobject obj, jintArray object_ids) { if (this->is_destroying) return true; m_objectManager->OnGarbageCollected(jEnv, object_ids); @@ -548,6 +671,10 @@ bool Runtime::TryCallGC() { return success; } +// Module loading, workers, and the worker->main exception hop are all the +// standalone runtime's. A guest never evaluates a file: the embedder's bundler +// does, and it hands the runtime an already-running VM. +#if !defined(NS_JSI_HOST_RUNTIME) void Runtime::RunModule(JNIEnv *_jEnv, jobject obj, jstring scriptFile) { JEnv jEnv(_jEnv); string filePath = ArgConverter::jstringToString(scriptFile); @@ -569,19 +696,6 @@ void Runtime::RunWorker(const std::string &filePath) { m_module.LoadWorker(engineHost->GetRuntime(), filePath); } -void Runtime::DisposeWorkerRuntime(Runtime *runtime) { - // `engineHost` is referenced by the JSEnterScope macro below. - std::shared_ptr engineHost = runtime->GetEngineHost(); - { - JSEnterScope - runtime->DestroyRuntime(); - } - // Both this scope's reference and the runtime's are gone by the end of the - // next line, and whichever drops last runs ~EngineHost. Nothing frees the VM - // while the scope above is still unwinding over it. - delete runtime; -} - jobject Runtime::RunScript(JNIEnv *_env, jobject obj, jstring scriptFile) { auto filename = ArgConverter::jstringToString(scriptFile); auto sourceUrl = ModuleInternal::EnsureFileProtocol(filename); @@ -602,6 +716,8 @@ jobject Runtime::RunScript(JNIEnv *_env, jobject obj, jstring scriptFile) { return nullptr; } +#endif + engine::Runtime &Runtime::GetJSRuntime() { return engineHost->GetRuntime(); @@ -754,6 +870,7 @@ Runtime::PassExceptionToJsNative(JNIEnv *jEnv, jobject obj, jthrowable exception NativeScriptException::CallJsFuncWithErr(rt, engine::Value(rt, errObj), isDiscarded); } +#if !defined(NS_JSI_HOST_RUNTIME) void Runtime::PassUncaughtExceptionFromWorkerToMainHandler(const engine::Value &message, const engine::Value &stackTrace, @@ -777,13 +894,17 @@ Runtime::PassUncaughtExceptionFromWorkerToMainHandler(const engine::Value &messa jEnv.CallStaticVoidMethod(runtimeClass, mId, (jstring) jMsgLocal, (jstring) jfileNameLocal, (jstring) stTrace, (jint) lineno); } +#endif + +#if !defined(NS_JSI_HOST_RUNTIME) void Runtime::SetManualInstrumentationMode(jstring mode) { auto modeStr = ArgConverter::jstringToString(mode); if (modeStr == "timeline") { tns::instrumentation::Frame::enable(); } } +#endif void Runtime::Lock() { #ifdef APPLICATION_IN_DEBUG diff --git a/NativeScript/runtime/android/jsi/Runtime.h b/NativeScript/runtime/android/jsi/Runtime.h index bc454014d..5088e1dc7 100644 --- a/NativeScript/runtime/android/jsi/Runtime.h +++ b/NativeScript/runtime/android/jsi/Runtime.h @@ -9,7 +9,13 @@ #include "FinalizerQueue.h" #include #include "robin_hood.h" +// The guest has no module loader: the embedder's bundler owns module +// resolution, and require() never reaches this runtime. Excluded from the guest +// build entirely rather than merely left uninitialised, so the code does not +// ship in an app that cannot call it. +#if !defined(NS_JSI_HOST_RUNTIME) #include "ModuleInternal.h" +#endif #include #include "ObjectManager.h" #include "ArrayBufferHelper.h" @@ -74,6 +80,18 @@ namespace tns { bool isDebuggable, jstring packageName, jobjectArray args, jstring callingDir, int maxLogcatObjectSize, bool forceLog); +#if defined(NS_JSI_HOST_RUNTIME) + // The guest counterpart of Init: binds to a jsi::Runtime the embedder + // created and still owns. `filesRoot` is where the metadata .dat files + // were staged; there is no app root to run a module from. + static Runtime *Attach(JNIEnv *_env, jobject obj, int runtimeId, + ::facebook::jsi::Runtime &hostRuntime, const std::string &filesRoot, + bool verboseLoggingEnabled, int maxLogcatObjectSize, bool forceLog); + + void Attach(::facebook::jsi::Runtime &hostRuntime, const std::string &filesRoot, + bool verboseLoggingEnabled, int maxLogcatObjectSize, bool forceLog); +#endif + jobject GetJavaRuntime() const; void DestroyRuntime(); @@ -82,11 +100,14 @@ namespace tns { void RunModule(const char *moduleName); +#if !defined(NS_JSI_HOST_RUNTIME) void RunWorker(const std::string &filePath); +#endif - // Tears down a worker's Runtime (engine scope + engine state release) and - // deletes it. Called from the native worker thread during shutdown. - static void DisposeWorkerRuntime(Runtime *runtime); + // Tears down a Runtime inside its engine scope and deletes it. The engine + // must still be alive with no JS running on it, and nothing may reach + // `runtime` from another thread once this begins. + static void DisposeRuntime(Runtime *runtime); jobject RunScript(JNIEnv *_env, jobject obj, jstring scriptFile); @@ -152,10 +173,12 @@ namespace tns { jstring fullStackTrace, jstring jsStackTrace, jboolean isDiscarded, jboolean isPendingError); +#if !defined(NS_JSI_HOST_RUNTIME) void PassUncaughtExceptionFromWorkerToMainHandler(const engine::Value &message, const engine::Value &stackTrace, const engine::Value &filename, int lineno); +#endif void AdjustAmountOfExternalAllocatedMemory(); @@ -167,6 +190,11 @@ namespace tns { Runtime(JNIEnv *env, jobject runtime, int id); + // The part of startup that does not depend on who owns the runtime. + // See the comment on its definition for what `guest` turns off. + void InitCommon(const std::string &filesRoot, const std::string &callingDir, + int maxLogcatObjectSize, bool forceLog, bool guest); + int m_id; jobject m_runtime; @@ -187,7 +215,9 @@ namespace tns { bool m_isMainThread; +#if !defined(NS_JSI_HOST_RUNTIME) ModuleInternal m_module; +#endif std::shared_ptr m_looperTasks; diff --git a/NativeScript/runtime/android/jsi/com_tns_Runtime.cpp b/NativeScript/runtime/android/jsi/com_tns_Runtime.cpp index 9c250ee7c..20ab03d8f 100644 --- a/NativeScript/runtime/android/jsi/com_tns_Runtime.cpp +++ b/NativeScript/runtime/android/jsi/com_tns_Runtime.cpp @@ -80,7 +80,13 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { // @FastNative ABI: standard JNI signature (registered via RegisterNatives). static void setManualInstrumentationModeFast_impl(JNIEnv* env, jclass clazz, jstring mode) { try { +#if !defined(NS_JSI_HOST_RUNTIME) Runtime::SetManualInstrumentationMode(mode); +#else + // Timeline instrumentation is standalone-runtime tooling; the guest does + // not build it. The symbol stays because RegisterNatives binds it. + (void) mode; +#endif } catch (...) { NativeScriptException nsEx(std::string("Error: c++ exception!")); nsEx.ReThrowToJava(nullptr); @@ -93,6 +99,10 @@ extern "C" JNIEXPORT void Java_com_tns_Runtime_setManualInstrumentationModeLegac setManualInstrumentationModeFast_impl(_env, clazz, mode); } +// Standalone only: a guest is attached by its embedder (see Runtime::Attach), +// never by Java asking the runtime to stand a VM up. Every other native below +// is interop that both flavours need, so only this one is compiled out. +#if !defined(NS_JSI_HOST_RUNTIME) extern "C" JNIEXPORT void Java_com_tns_Runtime_initNativeScript(JNIEnv* _env, jobject obj, jint runtimeId, jstring filesPath, jstring nativeLibDir, jboolean verboseLoggingEnabled, jboolean isDebuggable, jstring packageName, jobjectArray args, jstring callingDir, jint maxLogcatObjectSize, jboolean forceLog) { try { DEBUG_WRITE("NativeScript Initializing!"); @@ -110,6 +120,7 @@ extern "C" JNIEXPORT void Java_com_tns_Runtime_initNativeScript(JNIEnv* _env, jo nsEx.ReThrowToJava(nullptr); } } +#endif // !NS_JSI_HOST_RUNTIME Runtime* TryGetRuntime(int runtimeId) { Runtime* runtime = nullptr; @@ -129,6 +140,10 @@ Runtime* TryGetRuntime(int runtimeId) { return runtime; } +// Bound by name rather than registered, so a guest simply does not export +// it: nothing on the Java side calls runModule or runScript, because the +// embedder's bundler owns module loading. +#if !defined(NS_JSI_HOST_RUNTIME) extern "C" JNIEXPORT void Java_com_tns_Runtime_runModule(JNIEnv* _env, jobject obj, jint runtimeId, jstring scriptFile) { auto runtime = TryGetRuntime(runtimeId); if (runtime == nullptr) { @@ -151,7 +166,10 @@ extern "C" JNIEXPORT void Java_com_tns_Runtime_runModule(JNIEnv* _env, jobject o nsEx.ReThrowToJava(&runtime->GetJSRuntime()); } } +#endif + +#if !defined(NS_JSI_HOST_RUNTIME) extern "C" JNIEXPORT jobject Java_com_tns_Runtime_runScript(JNIEnv* _env, jobject obj, jint runtimeId, jstring scriptFile) { jobject result = nullptr; @@ -176,6 +194,8 @@ extern "C" JNIEXPORT jobject Java_com_tns_Runtime_runScript(JNIEnv* _env, jobjec return result; } +#endif + extern "C" JNIEXPORT jobject Java_com_tns_Runtime_callJSMethodNative(JNIEnv* _env, jobject obj, jint runtimeId, jint javaObjectID, jclass claz, jstring methodName,jint retType, jboolean isConstructor, jobjectArray packagedArgs) { jobject result = nullptr; diff --git a/NativeScript/runtime/android/jsi/workers/WorkerWrapper.cpp b/NativeScript/runtime/android/jsi/workers/WorkerWrapper.cpp index 5bd5b3b7f..86c26c576 100644 --- a/NativeScript/runtime/android/jsi/workers/WorkerWrapper.cpp +++ b/NativeScript/runtime/android/jsi/workers/WorkerWrapper.cpp @@ -422,10 +422,10 @@ void WorkerWrapper::BackgroundLooper(std::shared_ptr self) { workerRt_.store(nullptr); // Enters the engine scope, tears the runtime down and deletes it. - Runtime::DisposeWorkerRuntime(runtime_); + Runtime::DisposeRuntime(runtime_); runtime_ = nullptr; // The last reference this thread holds to the worker VM, and it is - // dropped only after DisposeWorkerRuntime's scope has fully unwound. + // dropped only after DisposeRuntime's scope has fully unwound. workerHost_.reset(); } @@ -529,7 +529,7 @@ void WorkerWrapper::EnsureJniCached() { env.GetStaticMethodID(RUNTIME_CLASS, "initWorkerRuntime", "(ILjava/lang/String;)I"); RUN_WORKER_LOOP_METHOD_ID = env.GetStaticMethodID(RUNTIME_CLASS, "runWorkerLoop", "()V"); DETACH_WORKER_RUNTIME_METHOD_ID = - env.GetStaticMethodID(RUNTIME_CLASS, "detachWorkerRuntime", "(I)V"); + env.GetStaticMethodID(RUNTIME_CLASS, "detachRuntime", "(I)V"); LOOPER_CLASS = env.FindClass("android/os/Looper"); assert(LOOPER_CLASS != nullptr); diff --git a/NativeScript/runtime/android/napi/Runtime.cpp b/NativeScript/runtime/android/napi/Runtime.cpp index 347577d1e..382b5624e 100644 --- a/NativeScript/runtime/android/napi/Runtime.cpp +++ b/NativeScript/runtime/android/napi/Runtime.cpp @@ -232,6 +232,14 @@ void Runtime::Init(JNIEnv *_env, jstring filesPath, jstring nativeLibsDir, JniLocalRef profilerOutputDir(_env->GetObjectArrayElement(args, 2)); + /* Index 14 == AppConfig.KnownKeys.ContentKeyedBindings, which the app's + * build sets when it generated content-keyed static bindings. The generated + * names and the names looked up here are halves of one scheme, so this must + * come from the build that ran the generator -- never from a guess made + * here. A runtime compiled to require content keys ignores a false. */ + JniLocalRef contentKeyedBindings(_env->GetObjectArrayElement(args, 14)); + MetadataNode::SetContentKeyedBindings((bool) contentKeyedBindings); + js_set_runtime_flags(flags.c_str()); auto runtimeStatus = js_create_runtime(&rt); if (runtimeStatus != napi_ok || rt == nullptr) { @@ -605,7 +613,7 @@ void Runtime::RunWorker(const std::string &filePath) { m_module.LoadWorker(env, filePath); } -void Runtime::DisposeWorkerRuntime(Runtime *runtime) { +void Runtime::DisposeRuntime(Runtime *runtime) { // `env` is referenced by the (engine-specific) JSEnterScope macro below. napi_env env = runtime->env; { diff --git a/NativeScript/runtime/android/napi/Runtime.h b/NativeScript/runtime/android/napi/Runtime.h index d9be6a277..e52128e7e 100644 --- a/NativeScript/runtime/android/napi/Runtime.h +++ b/NativeScript/runtime/android/napi/Runtime.h @@ -84,7 +84,7 @@ namespace tns { // Tears down a worker's Runtime (engine scope + env free) and deletes // it. Called from the native worker thread during shutdown. - static void DisposeWorkerRuntime(Runtime *runtime); + static void DisposeRuntime(Runtime *runtime); jobject RunScript(JNIEnv *_env, jobject obj, jstring scriptFile); diff --git a/NativeScript/runtime/android/napi/modules/timers/Timers.cpp b/NativeScript/runtime/android/napi/modules/timers/Timers.cpp index bb3cc9ede..57d6471c5 100644 --- a/NativeScript/runtime/android/napi/modules/timers/Timers.cpp +++ b/NativeScript/runtime/android/napi/modules/timers/Timers.cpp @@ -61,6 +61,10 @@ void Timers::Init(napi_env env, napi_value global) { napi_util::napi_set_function(env, global, "__ns__setInterval", SetIntervalCallback, this); napi_util::napi_set_function(env, global, "__ns__clearTimeout", ClearTimer, this); napi_util::napi_set_function(env, global, "__ns__clearInterval", ClearTimer, this); + napi_util::napi_set_function(env, global, "setTimeout", SetTimeoutCallback, this); + napi_util::napi_set_function(env, global, "setInterval", SetIntervalCallback, this); + napi_util::napi_set_function(env, global, "clearTimeout", ClearTimer, this); + napi_util::napi_set_function(env, global, "clearInterval", ClearTimer, this); napi_status status; // Non-fatal to timer operation if this fails (only affects GC cleanup of diff --git a/NativeScript/runtime/android/napi/workers/WorkerWrapper.cpp b/NativeScript/runtime/android/napi/workers/WorkerWrapper.cpp index 21fa72e02..c16efaa1e 100644 --- a/NativeScript/runtime/android/napi/workers/WorkerWrapper.cpp +++ b/NativeScript/runtime/android/napi/workers/WorkerWrapper.cpp @@ -502,7 +502,7 @@ void WorkerWrapper::BackgroundLooper(std::shared_ptr self) { // Enters the engine scope, tears down the runtime and frees the env, // then deletes the Runtime (which frees the underlying engine runtime). - Runtime::DisposeWorkerRuntime(runtime_); + Runtime::DisposeRuntime(runtime_); runtime_ = nullptr; } @@ -604,7 +604,7 @@ void WorkerWrapper::EnsureJniCached() { env.GetStaticMethodID(RUNTIME_CLASS, "initWorkerRuntime", "(ILjava/lang/String;)I"); RUN_WORKER_LOOP_METHOD_ID = env.GetStaticMethodID(RUNTIME_CLASS, "runWorkerLoop", "()V"); DETACH_WORKER_RUNTIME_METHOD_ID = - env.GetStaticMethodID(RUNTIME_CLASS, "detachWorkerRuntime", "(I)V"); + env.GetStaticMethodID(RUNTIME_CLASS, "detachRuntime", "(I)V"); LOOPER_CLASS = env.FindClass("android/os/Looper"); assert(LOOPER_CLASS != nullptr); diff --git a/README.md b/README.md index b35495a42..665ad411a 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ The core backbone that powers [NativeScript](https://nativescript.org/) apps and - `npm run build-macos`: Builds the macOS runtime platforms. - `npm run build-vision`: Builds the visionOS runtime platforms. - `npm run build-node-api`: Compiles the Node-API (N-API) addon. + - `npm run build-android`: Builds the Android runtime for one JS engine (prompts, or `-- --engine=`). + - `npm run setup-android`: Prepares an Android checkout (QuickJS patches + jsparser deps). - `npm run build-rn-turbomodule`: Compiles the React Native TurboModules compatibility layer. 4. **Core Dependencies**: - `npm run build-metagen`: Builds the Clang metadata generator binary. diff --git a/metadata-generator/include/IR.h b/metadata-generator/include/IR.h index 80ceaa789..d6c0318c8 100644 --- a/metadata-generator/include/IR.h +++ b/metadata-generator/include/IR.h @@ -98,6 +98,7 @@ enum VariableConstEvalKind { struct EnumConstDecl { std::string name; + std::string cName; int64_t value; AvailabilityInfo availability; }; @@ -132,6 +133,10 @@ class EnumDecl { std::string name; std::vector constants; + // Stripped from every member's cName to form its name, except members + // whose name is their cName. + std::string memberPrefix; + std::string memberSuffix; MDSectionOffset mdOffset = MD_SECTION_OFFSET_NULL; }; diff --git a/metadata-generator/include/Metadata.h b/metadata-generator/include/Metadata.h index 8ebc6e9dd..70398f3c1 100644 --- a/metadata-generator/include/Metadata.h +++ b/metadata-generator/include/Metadata.h @@ -18,6 +18,9 @@ typedef uint32_t MDSectionOffset; enum MDSectionOffsetFlag : uint32_t { mdSectionOffsetNext = 1u << 31, + // Enum member names only: the stored name is the full C name rather than + // one shortened by the enum's member prefix/suffix. + mdSectionOffsetEnumMemberCName = 1u << 30, }; // Bits 0-1: flags (2 flags) @@ -113,7 +116,11 @@ enum MDFunctionFlag : uint8_t { #define MD_HEADER_MAGIC_SIZE 4 #define MD_HEADER_MAGIC "NSMD" #define MD_HEADER_VERSION_SIZE 2 -#define MD_HEADER_VERSION 1 +#define MD_HEADER_VERSION 2 +#define MD_HEADER_MIN_VERSION 1 +// From this version, enum entries carry the member prefix/suffix and member +// names carry mdSectionOffsetEnumMemberCName. +#define MD_HEADER_VERSION_ENUM_MEMBER_C_NAMES 2 #define MD_HEADER_SIZE (MD_HEADER_MAGIC_SIZE + MD_HEADER_VERSION_SIZE) #define MD_NUM_SECTIONS 9 diff --git a/metadata-generator/include/MetadataReader.h b/metadata-generator/include/MetadataReader.h index 4a7595322..a1f444582 100644 --- a/metadata-generator/include/MetadataReader.h +++ b/metadata-generator/include/MetadataReader.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "Metadata.h" @@ -14,6 +15,7 @@ class MDMetadataReader { public: void* data; bool ownsData = false; + uint16_t version = 0; MDSectionOffset stringsOffset; MDSectionOffset constantsOffset; @@ -35,9 +37,8 @@ class MDMetadataReader { } ptr_add(&data, MD_HEADER_MAGIC_SIZE); - uint16_t version; memcpy(&version, data, MD_HEADER_VERSION_SIZE); - if (version != MD_HEADER_VERSION) { + if (version < MD_HEADER_MIN_VERSION || version > MD_HEADER_VERSION) { fprintf(stderr, "Invalid metadata header version\n"); exit(1); } @@ -95,6 +96,66 @@ class MDMetadataReader { return *(int64_t*)((char*)data + offset); } + inline bool hasEnumMemberCNames() const { + return version >= MD_HEADER_VERSION_ENUM_MEMBER_C_NAMES; + } + + // Visits fn(name, cName, value) for each member of the enum entry at + // enumOffset and returns the offset just past the entry. cName is empty when + // the metadata predates stored C names; it cannot be derived from name. + template + inline MDSectionOffset forEachEnumMember(MDSectionOffset enumOffset, + Fn&& fn) { + MDSectionOffset offset = enumOffset + sizeof(MDSectionOffset); + const bool hasCNames = hasEnumMemberCNames(); + const char* prefix = ""; + const char* suffix = ""; + if (hasCNames) { + prefix = getString(offset); + offset += sizeof(MDSectionOffset); + suffix = getString(offset); + offset += sizeof(MDSectionOffset); + } + + bool next = true; + while (next) { + MDSectionOffset nameOffset = getOffset(offset); + next = (nameOffset & mdSectionOffsetNext) != 0; + const bool nameIsCName = + hasCNames && (nameOffset & mdSectionOffsetEnumMemberCName) != 0; + if (hasCNames) { + nameOffset &= ~mdSectionOffsetEnumMemberCName; + } + nameOffset &= ~mdSectionOffsetNext; + offset += sizeof(MDSectionOffset); + int64_t value = getEnumValue(offset); + offset += sizeof(int64_t); + + const char* name = resolveString(nameOffset); + std::string cName; + if (nameIsCName) { + cName = name; + } else if (hasCNames) { + cName.append(prefix).append(name).append(suffix); + } + fn(name, cName, value); + } + return offset; + } + + inline MDSectionOffset skipEnum(MDSectionOffset enumOffset) { + MDSectionOffset offset = enumOffset + sizeof(MDSectionOffset); + if (hasEnumMemberCNames()) { + offset += 2 * sizeof(MDSectionOffset); + } + bool next = true; + while (next) { + next = (getOffset(offset) & mdSectionOffsetNext) != 0; + offset += sizeof(MDSectionOffset) + sizeof(int64_t); + } + return offset; + } + inline MDMemberFlag getMemberFlag(MDSectionOffset offset) { return *(MDMemberFlag*)((char*)data + offset); } diff --git a/metadata-generator/include/MetadataWriter.h b/metadata-generator/include/MetadataWriter.h index 711b4d2e8..fe69b860e 100644 --- a/metadata-generator/include/MetadataWriter.h +++ b/metadata-generator/include/MetadataWriter.h @@ -40,11 +40,14 @@ struct MDVariable { struct MDEnumMember { MDSectionOffset name; + bool nameIsCName; int64_t value; }; struct MDEnum { MDSectionOffset name; + MDSectionOffset memberPrefix; + MDSectionOffset memberSuffix; std::vector members; }; diff --git a/metadata-generator/src/IR/Enum.cpp b/metadata-generator/src/IR/Enum.cpp index ba046c583..e82429221 100644 --- a/metadata-generator/src/IR/Enum.cpp +++ b/metadata-generator/src/IR/Enum.cpp @@ -13,7 +13,8 @@ namespace metagen { // - Deferred // Basically, split the name into parts (assuming camel case), and then remove // the repeating parts from the beginning and end of the name. -void transformEnumMemberNames(std::vector& members) { +void transformEnumMemberNames(std::vector& members, + std::string& prefix, std::string& suffix) { std::vector> parts; parts.reserve(members.size()); size_t skip_begin = 0, skip_end = 0; @@ -69,6 +70,13 @@ void transformEnumMemberNames(std::vector& members) { } } + for (size_t j = 0; j < skip_begin; j++) { + prefix += largestPart[j]; + } + for (size_t j = largestPart.size() - skip_end; j < largestPart.size(); j++) { + suffix += largestPart[j]; + } + for (size_t i = 0; i < members.size(); i++) { const auto& part = parts[i]; std::string name; @@ -119,6 +127,7 @@ EnumDecl::EnumDecl(CXCursor cursor) { auto value = clang_getEnumConstantDeclValue(cursor); EnumConstDecl decl; decl.name = nameStr; + decl.cName = nameStr; decl.value = value; if (gCaptureAvailability) { decl.availability = getAvailabilityInfo(cursor); @@ -135,7 +144,8 @@ EnumDecl::EnumDecl(CXCursor cursor) { }, this); - if (!name.empty()) transformEnumMemberNames(constants); + if (!name.empty()) + transformEnumMemberNames(constants, memberPrefix, memberSuffix); } void MetadataFactory::processEnumRefs() { diff --git a/metadata-generator/src/IR/Factory.cpp b/metadata-generator/src/IR/Factory.cpp index 7ddc67996..e8b3a400b 100644 --- a/metadata-generator/src/IR/Factory.cpp +++ b/metadata-generator/src/IR/Factory.cpp @@ -379,6 +379,8 @@ void MetadataFactory::processEnum(CXCursor cursor, bool required) { if (enumIt != enums.end()) { if (enumIt->second.constants.size() < decl.constants.size()) { enumIt->second.constants = std::move(decl.constants); + enumIt->second.memberPrefix = std::move(decl.memberPrefix); + enumIt->second.memberSuffix = std::move(decl.memberSuffix); } return; } diff --git a/metadata-generator/src/MetadataWriter/Enum.cpp b/metadata-generator/src/MetadataWriter/Enum.cpp index a4abe3109..48256137e 100644 --- a/metadata-generator/src/MetadataWriter/Enum.cpp +++ b/metadata-generator/src/MetadataWriter/Enum.cpp @@ -10,11 +10,25 @@ void MDMetadataWriter::write(EnumDecl &decl) { MDEnum *mdEnum = new MDEnum(); mdEnum->name = strings.add(decl.name, decl.name); + mdEnum->memberPrefix = strings.add(decl.memberPrefix, decl.memberPrefix); + mdEnum->memberSuffix = strings.add(decl.memberSuffix, decl.memberSuffix); mdEnum->members.reserve(decl.constants.size()); for (const EnumConstDecl &c : decl.constants) { MDEnumMember member; member.name = strings.add(c.name, c.name); + if ((member.name & mdSectionOffsetEnumMemberCName) != 0) { + std::cerr << "Strings section too large to flag enum member names" + << std::endl; + exit(1); + } + member.nameIsCName = c.name == c.cName; + if (!member.nameIsCName && + decl.memberPrefix + c.name + decl.memberSuffix != c.cName) { + std::cerr << "Enum " << decl.name << ": cannot encode the C name of " + << c.cName << std::endl; + exit(1); + } member.value = c.value; mdEnum->members.push_back(member); } @@ -33,7 +47,11 @@ size_t MDEnumMemberSerde::size(MDEnumMember *value) { void MDEnumMemberSerde::serialize(MDEnumMember *value, void *data) { // Name - binwrite(value->name); + MDSectionOffset name = value->name; + if (value->nameIsCName) { + name |= mdSectionOffsetEnumMemberCName; + } + binwrite(name); // Value binwrite(value->value); } @@ -42,6 +60,8 @@ size_t MDEnumSerde::size(MDEnum *value) { size_t size = 0; // Name addsize(value->name); + addsize(value->memberPrefix); + addsize(value->memberSuffix); // Members MDEnumMemberSerde memberSerde; if (!value->members.empty()) { @@ -53,6 +73,8 @@ size_t MDEnumSerde::size(MDEnum *value) { void MDEnumSerde::serialize(MDEnum *value, void *data) { // Name binwrite(value->name); + binwrite(value->memberPrefix); + binwrite(value->memberSuffix); // Members size_t membersSize = value->members.size(); MDEnumMemberSerde memberSerde; diff --git a/metadata-generator/src/TSEmitter/TypeSpec.cpp b/metadata-generator/src/TSEmitter/TypeSpec.cpp index ad65bbb62..3dcdee653 100644 --- a/metadata-generator/src/TSEmitter/TypeSpec.cpp +++ b/metadata-generator/src/TSEmitter/TypeSpec.cpp @@ -66,7 +66,8 @@ std::string TSFile::typeToString(const TypeSpec& type, bool isStatic, bool isRet result += ") => "; result += typeToString(*type.callbackReturn, false, true); if (type.isNullable) { - result += " | null"; + // `() => T | null` would make the return type nullable instead. + result = "(" + result + ") | null"; } break; } diff --git a/package.json b/package.json index b52771f42..be2ea3d79 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/runtimes" + "url": "git+https://github.com/NativeScript/runtimes.git" }, "author": { "name": "NativeScript Team", @@ -27,6 +27,7 @@ "build-vision": "./scripts/build_all_vision.sh", "build-visionos": "./scripts/build_all_vision.sh", "build-node-api": "./scripts/build_all_react_native.sh", + "build-android": "./scripts/build_all_android.sh", "build-rn-turbomodule": "./scripts/build_react_native_turbomodule.sh", "check:ffi-boundaries": "./scripts/check_ffi_boundaries.sh", "check:jsi-neutral": "./scripts/check_jsi_layer_neutral.sh", @@ -36,6 +37,8 @@ "pack:ios": "./scripts/build_npm_ios.sh", "pack:macos": "./scripts/build_npm_macos.sh", "pack:visionos": "./scripts/build_npm_vision.sh", + "patch-quickjs": "node ./scripts/apply_quickjs_patches.js", + "setup-android": "./scripts/setup_android.sh", "setup-ci": "./scripts/build_metadata_generator.sh", "update-version": "./scripts/update_version.sh ios", "update-version:ios": "./scripts/update_version.sh ios", diff --git a/packages/android-hermes/README.md b/packages/android-hermes/README.md index d8485988c..7cd56d66a 100644 --- a/packages/android-hermes/README.md +++ b/packages/android-hermes/README.md @@ -7,7 +7,9 @@ NativeScript runtime package for Android, built with the Hermes JavaScript engin From the repo root: ```sh -./gradlew -Pengine=HERMES +npm run build-android -- --engine=HERMES ``` -This produces the npm artifact in `dist_hermes/`. +The staged package lands in `dist/android_hermes_napi/` and the tarball in +`build/npm-tarballs/`. Add `--binding=jsi` for the jsi runtime tree +(`dist/android_hermes_jsi/`). diff --git a/packages/android-hermes/package.json b/packages/android-hermes/package.json index 672b3fe06..2bc9be6ae 100644 --- a/packages/android-hermes/package.json +++ b/packages/android-hermes/package.json @@ -10,7 +10,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-android", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/android-hermes" }, "author": { diff --git a/packages/android-jsc/README.md b/packages/android-jsc/README.md index 89a2339a8..699053d91 100644 --- a/packages/android-jsc/README.md +++ b/packages/android-jsc/README.md @@ -7,7 +7,9 @@ NativeScript runtime package for Android, built with the JavaScriptCore engine. From the repo root: ```sh -./gradlew -Pengine=JSC +npm run build-android -- --engine=JSC ``` -This produces the npm artifact in `dist_jsc/`. +The staged package lands in `dist/android_jsc_napi/` and the tarball in +`build/npm-tarballs/`. Add `--binding=jsi` for the jsi runtime tree +(`dist/android_jsc_jsi/`). diff --git a/packages/android-jsc/package.json b/packages/android-jsc/package.json index 535e776ac..3c8800d16 100644 --- a/packages/android-jsc/package.json +++ b/packages/android-jsc/package.json @@ -11,7 +11,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-android", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/android-jsc" }, "author": { diff --git a/packages/android-primjs/README.md b/packages/android-primjs/README.md index 16dc3c78f..edc986cbb 100644 --- a/packages/android-primjs/README.md +++ b/packages/android-primjs/README.md @@ -7,7 +7,9 @@ NativeScript runtime package for Android, built with the PrimJS JavaScript engin From the repo root: ```sh -./gradlew -Pengine=PRIMJS +npm run build-android -- --engine=PRIMJS ``` -This produces the npm artifact in `dist_primjs/`. +The staged package lands in `dist/android_primjs_napi/` and the tarball in +`build/npm-tarballs/`. Add `--binding=jsi` for the jsi runtime tree +(`dist/android_primjs_jsi/`). diff --git a/packages/android-primjs/package.json b/packages/android-primjs/package.json index 2a8991e74..68835c2dd 100644 --- a/packages/android-primjs/package.json +++ b/packages/android-primjs/package.json @@ -10,7 +10,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-android", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/android-primjs" }, "author": { diff --git a/packages/android-quickjs-ng/README.md b/packages/android-quickjs-ng/README.md index c4fdb1292..d27086dd2 100644 --- a/packages/android-quickjs-ng/README.md +++ b/packages/android-quickjs-ng/README.md @@ -7,7 +7,9 @@ NativeScript runtime package for Android, built with the QuickJS-ng JavaScript e From the repo root: ```sh -./gradlew -Pengine=QUICKJS_NG +npm run build-android -- --engine=QUICKJS_NG ``` -This produces the npm artifact in `dist_quickjs_ng/`. +The staged package lands in `dist/android_quickjs_napi/` and the tarball in +`build/npm-tarballs/`. Add `--binding=jsi` for the jsi runtime tree +(`dist/android_quickjs_jsi/`). diff --git a/packages/android-quickjs-ng/package.json b/packages/android-quickjs-ng/package.json index ee1bad6df..fa9bd7af4 100644 --- a/packages/android-quickjs-ng/package.json +++ b/packages/android-quickjs-ng/package.json @@ -11,7 +11,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-android", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/android-quickjs-ng" }, "author": { diff --git a/packages/android-quickjs/LICENSE b/packages/android-quickjs/LICENSE deleted file mode 100755 index 451412229..000000000 --- a/packages/android-quickjs/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (c) 2020, nStudio, LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/android-quickjs/README.md b/packages/android-quickjs/README.md deleted file mode 100644 index 8e1f617af..000000000 --- a/packages/android-quickjs/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# @nativescript/android-quickjs - -NativeScript runtime package for Android, built with the QuickJS JavaScript engine. - -## Build - -From the repo root: - -```sh -./gradlew -Pengine=QUICKJS -``` - -This produces the npm artifact in `dist_quickjs/`. diff --git a/packages/android-quickjs/package.json b/packages/android-quickjs/package.json deleted file mode 100644 index bf7ee8f1f..000000000 --- a/packages/android-quickjs/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "@nativescript/android-quickjs", - "version": "0.0.1", - "description": "NativeScript Runtime for Android (QuickJS engine)", - "keywords": [ - "NativeScript", - "Android", - "runtime", - "quickjs" - ], - "repository": { - "type": "git", - "url": "https://github.com/NativeScript/napi-android", - "directory": "packages/android-quickjs" - }, - "author": { - "name": "NativeScript Team", - "email": "oss@nativescript.org" - }, - "license": "Apache-2.0" -} diff --git a/packages/android-v8/README.md b/packages/android-v8/README.md index 555c7cfc7..ae37c3ef1 100644 --- a/packages/android-v8/README.md +++ b/packages/android-v8/README.md @@ -7,7 +7,9 @@ NativeScript runtime package for Android, built with the V8 JavaScript engine. From the repo root: ```sh -./gradlew -Pengine=V8 +npm run build-android -- --engine=V8-13 ``` -This produces the npm artifact in `dist_v8/`. +The staged package lands in `dist/android_v8_13_napi/` and the tarball in +`build/npm-tarballs/`. Add `--binding=jsi` for the jsi runtime tree +(`dist/android_v8_13_jsi/`). diff --git a/packages/android-v8/package.json b/packages/android-v8/package.json index bdda0ef1c..0850d864c 100644 --- a/packages/android-v8/package.json +++ b/packages/android-v8/package.json @@ -10,7 +10,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-android", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/android-v8" }, "author": { diff --git a/packages/ios-hermes/package.json b/packages/ios-hermes/package.json index 89de4200c..b08a1c868 100644 --- a/packages/ios-hermes/package.json +++ b/packages/ios-hermes/package.json @@ -10,7 +10,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/ios-hermes" }, "author": { diff --git a/packages/ios-jsc/package.json b/packages/ios-jsc/package.json index 48e8809a4..8d2cb1c17 100644 --- a/packages/ios-jsc/package.json +++ b/packages/ios-jsc/package.json @@ -11,7 +11,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/ios-jsc" }, "author": { diff --git a/packages/ios-node-api/package.json b/packages/ios-node-api/package.json index 5984a78d8..89dd746da 100644 --- a/packages/ios-node-api/package.json +++ b/packages/ios-node-api/package.json @@ -4,7 +4,7 @@ "description": "An embeddable, engine-agnostic NativeScript runtime for iOS based on Node-API", "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios.git", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/ios-node-api" }, "typings": "index.d.ts", diff --git a/packages/ios-quickjs/package.json b/packages/ios-quickjs/package.json index ca8c0e449..958eb380b 100644 --- a/packages/ios-quickjs/package.json +++ b/packages/ios-quickjs/package.json @@ -10,7 +10,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/ios-quickjs" }, "author": { diff --git a/packages/ios-v8/package.json b/packages/ios-v8/package.json index 50747030c..ce971ec85 100644 --- a/packages/ios-v8/package.json +++ b/packages/ios-v8/package.json @@ -10,7 +10,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/ios-v8" }, "author": { diff --git a/packages/ios/package.json b/packages/ios/package.json index f96f7163d..a5ba0bc5d 100644 --- a/packages/ios/package.json +++ b/packages/ios/package.json @@ -9,7 +9,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/ios" }, "author": { diff --git a/packages/macos-node-api/package.json b/packages/macos-node-api/package.json index cee35a663..2f8e0e11e 100644 --- a/packages/macos-node-api/package.json +++ b/packages/macos-node-api/package.json @@ -4,7 +4,7 @@ "description": "An embeddable, engine-agnostic NativeScript runtime for macOS based on Node-API", "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios.git", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/macos-node-api" }, "main": "index.js", diff --git a/packages/macos/package.json b/packages/macos/package.json index 1c2a48ad8..331220286 100644 --- a/packages/macos/package.json +++ b/packages/macos/package.json @@ -9,7 +9,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/macos" }, "author": { diff --git a/packages/objc-node-api/index.d.ts b/packages/objc-node-api/index.d.ts index e6eef9d9c..fff83d9b0 100644 --- a/packages/objc-node-api/index.d.ts +++ b/packages/objc-node-api/index.d.ts @@ -9,6 +9,27 @@ declare global { class NativeObject implements interop.PointerObject { readonly __brand__: interop.PointerObject["__brand__"]; + + /** + * Registers an Objective-C subclass of this class. Functions in `methods` + * override the same-named members of this class and of + * `options.protocols`; other names are only callable from Objective-C + * when listed in `options.exposedMethods`. The returned class cannot + * itself be extended. + */ + static extend< + // deno-lint-ignore no-explicit-any + This extends abstract new (...args: any) => any, + // Inferred rather than annotated so that names absent from the class + // (protocol and exposed methods) are not excess properties. `& object` + // keeps literals sharing no name with the class from failing the weak + // type check against the all-optional Partial. + Methods extends Partial> & object, + >( + this: This, + methods: Methods & ThisType & Methods>, + options?: objc.ExtendOptions, + ): This; } namespace objc { @@ -25,6 +46,19 @@ declare global { ptr: interop.Pointer, size: number, ): ArrayBuffer; + + export interface ExposedMethod { + returns?: unknown; + params?: unknown[]; + } + + export interface ExtendOptions { + /** Objective-C class name; a numeric suffix is added if it is taken. */ + name?: string; + protocols?: unknown[]; + /** Keyed by method name in `methods`. */ + exposedMethods?: Record; + } } namespace interop { diff --git a/packages/objc-node-api/package.json b/packages/objc-node-api/package.json index 58bf92fd1..5fb13925a 100644 --- a/packages/objc-node-api/package.json +++ b/packages/objc-node-api/package.json @@ -4,7 +4,7 @@ "description": "Typings for the common Objective-C runtime APIs used by both @nativescript/ios-node-api and @nativescript/macos-node-api", "repository": { "type": "git", - "url": "https://github.com/NativeScript/runtime-node-api.git", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/objc-node-api" }, "main": "index.js", diff --git a/packages/react-native/README.md b/packages/react-native/README.md index ee5f5af5d..da378282d 100644 --- a/packages/react-native/README.md +++ b/packages/react-native/README.md @@ -592,3 +592,162 @@ missing, writes `nativescript.react-native.json`, and warns when the app is not configured for Hermes and the New Architecture. The command is intentionally conservative and does not make destructive native project edits. + +## Android + +The same package runs on Android. Where iOS gives you Objective-C and UIKit, +Android gives you Java and the Android SDK — plus every library your app +depends on, because the metadata is generated from your app's own classpath. + +```ts +import NativeScriptNativeApi from "@nativescript/react-native/src/NativeScriptNativeApi"; + +NativeScriptNativeApi.install(""); + +const now = java.lang.System.currentTimeMillis(); +const sdk = android.os.Build.VERSION.SDK_INT; +``` + +### How it works + +The runtime does not bring a JavaScript engine. It binds to the +`jsi::Runtime` React Native already created, which is why the same build works +whether your app runs Hermes or JSC. The Android module compiles from source in +your app the way every React Native module with C++ does — `jsi::Runtime` is an +abstract C++ class whose layout changes between React Native versions, so a +prebuilt binary could not be safe across them. + +### Setup + +`npx nativescript-rn configure` adds the Gradle script and the typings entries. +To do it by hand, in `android/app/build.gradle`, after the React plugin: + +```groovy +apply from: "../../node_modules/@nativescript/react-native/android/nativescript.gradle" +``` + +That adds two things to your build: + +* **Metadata** — a description of every class on the app's resolved classpath, + generated into the APK's assets on every build. This is what makes + `android.widget.Toast` and any third-party library resolvable from JavaScript. + `whitelist.mdg` / `blacklist.mdg` next to `android/app/build.gradle` filter it, + with the same format the NativeScript CLI uses. +* **TypeScript declarations** — off by default because they take a while: + + ```bash + cd android && ./gradlew generateNativeScriptTypingsDebug -PnsGenerateTypings=true + ``` + + which writes `types/android/` and, with `configure` having run, is already + referenced from `tsconfig.json`. + +### Extending Java classes + +Every class that extends a native class carries `@NativeClass`: + +```ts +import { NativeClass } from "@nativescript/react-native"; + +@NativeClass() +class Ticker extends java.lang.Object { + run() { + console.log("tick"); + } +} +``` + +`@NativeClass` is a plain runtime decorator. It reads nothing at build time and +behaves identically under Babel, SWC or tsc, in either the legacy or the TC39 +decorator dialect. It is not optional: `class X extends java.util.ArrayList {}` +on its own produces a class whose overrides resolve in JavaScript but which Java +still sees as an unmodified `ArrayList`. The decorator is what generates the +real Java subclass, so that Java calling `size()` reaches your implementation. + +A JavaScript class has one base, so several interfaces are declared on the +decorator: + +```ts +@NativeClass({ + interfaces: [java.lang.Runnable, java.util.concurrent.Callable], +}) +class Job extends java.lang.Object { + run() {} + call() { + return new java.lang.String("done"); + } +} +``` + +Decorators are *syntax*, so your bundler has to parse them — that means +`@babel/plugin-proposal-decorators` (or the SWC equivalent) in your Babel +config. That is a standard plugin, not a NativeScript-specific transform. + +#### Naming a class + +`@NativeClass()` on its own generates the Java class on the device. The class is +named after what it contains — its base class, its interfaces, and the names it +overrides — rather than after the source position of the call, because a guest +runs inside React Native's bundle and no source position computed here would +match one a build-time tool computed from your source tree. + +One consequence is worth knowing: two classes that extend the same type, declare +the same interfaces and override the same method names **share one generated +Java class**. That is safe, because the generated Java is identical in both +cases and only forwards into JavaScript — each instance still runs its own +implementation. + +It stops being safe when *Java* constructs the class by name, because then the +class name is all the runtime has to find the JavaScript side with. So: + +> **Name any class that Java instantiates for you** — an `Activity`, a +> `Fragment`, a `Service`, a `Worker`, anything reached by reflection or named +> in `AndroidManifest.xml`. Naming makes it unique. Classes you construct +> yourself with `new` never need it. + +A class that has to exist *before* the app runs — one named in +`AndroidManifest.xml`, for instance — needs a fixed name instead: + +```ts +import { JavaProxy } from "@nativescript/react-native"; + +@JavaProxy("com.example.Ticker") +@NativeClass() +class Ticker extends java.lang.Object {} +``` + +A fully-qualified name is taken verbatim by the runtime, so it is reproducible +at build time. The runtime then *looks the class up* rather than generating it, +which means a named class only resolves once the static binding generator has +emitted it — see the note below. + +> **Release builds only.** The generator reads a bundle, so it needs one on +> disk. On a release build the Gradle script bundles your JavaScript a second +> time -- separately from the one that ships, and with classes preserved -- and +> pre-generates the Java from that. In debug there is no bundle to read: Metro +> serves it over HTTP. Unnamed `@NativeClass()` classes are generated on the +> device and work in both, but `@JavaProxy` names resolve only in release. + +If you would rather not use decorators, the runtime API is always available and +needs nothing from the bundler: + +```ts +const Ticker = java.lang.Object.extend("Ticker", { run() {} }); +const listener = new android.view.View.OnClickListener({ onClick(v) {} }); +``` + +### Threads + +React Native owns the JavaScript thread, and JSI may only be touched there. A +Java callback arriving on another thread is marshalled onto the JS thread for +you. The one thing to avoid is blocking the JS thread while waiting for such a +callback — `thread.join()` immediately after `thread.start()` deadlocks, because +the callback cannot run until the JS thread is free again. + +### What a guest does not install + +React Native already provides `console`, timers, `queueMicrotask`, +`performance` and a module loader, so the runtime leaves all of them alone and +installs only the metadata globals, the object manager and the interop +callbacks. `Worker` is not installed either: a worker needs its own VM, which a +guest has no way to create. diff --git a/packages/react-native/android/.npmignore b/packages/react-native/android/.npmignore new file mode 100644 index 000000000..9b4702f5f --- /dev/null +++ b/packages/react-native/android/.npmignore @@ -0,0 +1,4 @@ +# Gradle/CMake output from building the module in a checkout. The package ships +# sources; the consuming app compiles them. +build/ +.cxx/ diff --git a/packages/react-native/android/CMakeLists.txt b/packages/react-native/android/CMakeLists.txt new file mode 100644 index 000000000..37d178984 --- /dev/null +++ b/packages/react-native/android/CMakeLists.txt @@ -0,0 +1,129 @@ +cmake_minimum_required(VERSION 3.22.1) +project(nativescript_rn) + +# The guest build of the NativeScript Android runtime. See ../build.gradle for +# why this compiles from source rather than shipping a prebuilt .aar. + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(NS_RUNTIME_DIR "${NS_ROOT}/runtime") +set(NS_ANDROID_RUNTIME_DIR "${NS_RUNTIME_DIR}/android/jsi") +set(NS_JNI_DIR "${NS_ROOT}/ffi/jni/jsi") +set(NS_JSI_DIR "${NS_ROOT}/jsi") + +# NS_JSI_HOST_RUNTIME: the jsi::Runtime belongs to React Native. +# TARGET_ENGINE_HERMES selects jsi/hermes, which despite the name is the +# adapter over plain facebook::jsi and holds nothing Hermes-specific -- under a +# host runtime the VM underneath may equally be JSC. +# NS_NO_INSPECTOR: the inspector is written against v8_inspector. +add_compile_definitions( + NS_JSI_HOST_RUNTIME + TARGET_ENGINE_HERMES + NS_NO_INSPECTOR + NATIVESCRIPT_REACT_NATIVE + __HERMES__ +) + +include_directories( + # Rooted includes ("jsi/hermes/HermesRuntime.h") resolve from here. Note + # this shadows an angle-include of a jsi/ header from the engine itself, + # so the shared layer's own includes stay quoted and rooted. + ${NS_ROOT} + ${NS_ANDROID_RUNTIME_DIR} + ${NS_ANDROID_RUNTIME_DIR}/instrumentation + ${NS_ANDROID_RUNTIME_DIR}/messageloop + ${NS_ANDROID_RUNTIME_DIR}/modules + ${NS_ANDROID_RUNTIME_DIR}/modules/console + ${NS_ANDROID_RUNTIME_DIR}/modules/module + ${NS_ANDROID_RUNTIME_DIR}/modules/performance + ${NS_ANDROID_RUNTIME_DIR}/modules/timers + ${NS_ANDROID_RUNTIME_DIR}/profiler + ${NS_ANDROID_RUNTIME_DIR}/sighandler + ${NS_ANDROID_RUNTIME_DIR}/util + ${NS_ANDROID_RUNTIME_DIR}/version + ${NS_ANDROID_RUNTIME_DIR}/workers + ${NS_JNI_DIR} + ${NS_JNI_DIR}/callbackhandlers + ${NS_JNI_DIR}/constants + ${NS_JNI_DIR}/conversion + ${NS_JNI_DIR}/exceptions + ${NS_JNI_DIR}/finalizer + ${NS_JNI_DIR}/global + ${NS_JNI_DIR}/jni + ${NS_JNI_DIR}/jsonhelper + ${NS_JNI_DIR}/metadata + ${NS_JNI_DIR}/objectmanager + ${NS_JNI_DIR}/weakref + # ConcurrentMap.h, robin_hood.h, spinmutex.h. The directory is named for + # Node-API because that is what else lives in it, but these three are + # plain containers with no Node-API in them, and the runtime uses them on + # both binding layers. + ${NS_ROOT}/napi/common + ${NS_METADATA_INCLUDE} + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp +) + +file(GLOB_RECURSE RUNTIME_SOURCES "${NS_ANDROID_RUNTIME_DIR}/*.cpp") +file(GLOB_RECURSE JNI_SOURCES "${NS_JNI_DIR}/*.cpp") +list(FILTER JNI_SOURCES EXCLUDE REGEX "_test\\.cpp$") + +# Parts of the standalone runtime a guest can never reach, dropped rather than +# merely left uninitialised so they are not carried in an app that cannot call +# them. Each has a matching NS_JSI_HOST_RUNTIME guard at its call sites. +# +# assetextractor unpacks a standalone app's app/ directory from the APK with +# libzip. Metro owns the JavaScript here, and the one asset +# needed -- the metadata -- is staged by the Java side. +# Dropping this takes libzip with it. +# WorkerWrapper a Worker gets its own VM, which a guest cannot build. React +# Native has its own answer for background work. Only the +# wrapper goes: LooperTasks and ConcurrentQueue live in the +# same directory but LooperTasks is the main-thread task +# queue -- how a Java callback arriving on another thread gets +# back onto the JS thread, which is core to a guest and not +# worker-specific. ConcurrentQueue has no other user, so it +# goes with the wrapper. +# modules/module require(). The embedder's bundler resolves modules; nothing +# in a guest ever loads a file. +# modules/console React Native installs console before this runtime attaches. +# modules/timers likewise setTimeout and friends. +# modules/performance likewise performance. +# profiler, instrumentation developer tooling for the standalone runtime, +# driven by JNI entry points a guest never calls. +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/assetextractor/") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/workers/(WorkerWrapper|ConcurrentQueue)\\.cpp$") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/modules/module/") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/modules/console/") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/modules/timers/") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/modules/performance/") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/profiler/") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/instrumentation/") + +add_library(nativescript_rn SHARED + ${RUNTIME_SOURCES} + ${JNI_SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/NativeScriptRnInstall.cpp +) + +find_package(ReactAndroid REQUIRED CONFIG) +find_package(fbjni REQUIRED CONFIG) + +find_library(LOG_LIB log) +find_library(ANDROID_LIB android) + +# jsi and nothing else. React Native 0.76+ merges most of its C++ into +# libreactnative.so while keeping jsi separate; both names are tried so the +# module spans the versions where that consolidation happened. +if (TARGET ReactAndroid::reactnative) + set(RN_JSI_TARGETS ReactAndroid::jsi ReactAndroid::reactnative) +else () + set(RN_JSI_TARGETS ReactAndroid::jsi ReactAndroid::turbomodulejsijni ReactAndroid::react_nativemodule_core) +endif () + +target_link_libraries(nativescript_rn + ${RN_JSI_TARGETS} + fbjni::fbjni + ${LOG_LIB} + ${ANDROID_LIB} +) diff --git a/packages/react-native/android/build.gradle b/packages/react-native/android/build.gradle new file mode 100644 index 000000000..59a8f2cab --- /dev/null +++ b/packages/react-native/android/build.gradle @@ -0,0 +1,114 @@ +// @nativescript/react-native -- Android +// +// Built from source in the consuming app, the way every React Native module +// with C++ is. That is not a packaging preference: jsi::Runtime is an abstract +// class whose vtable changes shape between React Native versions, so a binary +// compiled against one version's can call through the wrong slot on +// another. Compiling here means the runtime is always built against exactly the +// jsi the app ships. +// +// Nothing links a JavaScript engine. The runtime speaks nativescript::engine, +// which on this flavour is a header-only adapter over facebook::jsi, so the +// only JS symbols in libnativescript_rn.so come from React Native's own +// libjsi.so -- which is why the same binary works under Hermes and JSC alike. + +apply plugin: "com.android.library" + +def safeExtGet(prop, fallback) { + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback +} + +// The C++ and Java the runtime is made of. Two layouts are supported: this +// repository, where the sources are the originals, and the published npm +// package, which carries a staged copy under native-api/. +// +// The repository wins when both are present. A checkout also contains a staged +// copy (scripts/build_react_native_android.sh leaves one behind), and preferring +// it would mean edits to NativeScript/ silently did not reach the build. +// +// Canonicalise before walking up: in a checkout the app reaches this package +// through a symlink in node_modules, and file("...") normalises ".." lexically +// without ever following it, so the walk has to start from the real path or it +// lands in node_modules instead of at the repository root. +def repoRoot = projectDir.getCanonicalFile().parentFile.parentFile.parentFile +def packagedRoot = file("${projectDir}/../native-api") +def fromRepo = file("${repoRoot}/NativeScript/runtime/android/jsi").exists() +def fromPackage = !fromRepo + +def nsRoot = fromPackage ? packagedRoot : file("${repoRoot}/NativeScript") +// Two trees, exactly as the standalone runtime module composes them: com.tns.* +// proper, plus com.tns.bindings.* (the proxy generator that stands a Java class +// up for a JS subclass), which lives in its own project but is compiled into +// the runtime rather than shipped separately. +def runtimeJava = fromPackage + ? [file("${projectDir}/src/main/java-runtime")] + : [file("${repoRoot}/platforms/android/test-app/runtime/src/main/java"), + file("${repoRoot}/platforms/android/test-app/runtime-binding-generator/src/main/java")] +def metadataInclude = fromPackage + ? file("${packagedRoot}/metadata/include") + : file("${repoRoot}/metadata-generator/include") + +android { + namespace "org.nativescript.reactnative" + compileSdk safeExtGet("compileSdkVersion", 35) + // Follow the app rather than AGP's default, which points at whichever NDK the + // plugin was built against and need not be the one installed. + ndkVersion safeExtGet("ndkVersion", "27.1.12297006") + + defaultConfig { + minSdk safeExtGet("minSdkVersion", 24) + + externalNativeBuild { + cmake { + arguments "-DANDROID_STL=c++_shared", + "-DNS_ROOT=${nsRoot.absolutePath}", + "-DNS_METADATA_INCLUDE=${metadataInclude.absolutePath}" + cppFlags "-std=c++20", "-frtti", "-fexceptions" + } + } + } + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + + buildFeatures { + prefab true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + sourceSets { + main { + // com.tns.* is the runtime's Java half -- the class resolver, the object + // maps, the dex factory. It is plain Java with no engine dependency, so + // the guest ships the same copy the standalone runtime uses. + java.srcDirs += runtimeJava + } + } + + packagingOptions { + // React Native and the NDK both contribute one; either is the NDK's. + pickFirst "**/libc++_shared.so" + // Never ship an engine. If one of these ever turns up here it means a + // dependency pulled a VM in behind our back, which would defeat the whole + // arrangement described at the top of this file. + exclude "**/libhermes.so" + exclude "**/libhermesvm.so" + exclude "**/libjsc.so" + exclude "**/libv8android.so" + } +} + +dependencies { + implementation "com.facebook.react:react-android" +} + +// Generates NativeScriptNativeApiSpec from src/NativeScriptNativeApi.ts, using +// the codegenConfig block in package.json. The same spec drives iOS. +apply plugin: "com.facebook.react" diff --git a/packages/react-native/android/nativescript.gradle b/packages/react-native/android/nativescript.gradle new file mode 100644 index 000000000..7182e75a5 --- /dev/null +++ b/packages/react-native/android/nativescript.gradle @@ -0,0 +1,1125 @@ +// NativeScript build integration for a React Native app. +// +// Apply from android/app/build.gradle: +// +// apply from: "../../node_modules/@nativescript/react-native/android/nativescript.gradle" +// +// It adds two things: +// +// * metadata generation -- the description of every Java/Kotlin class the app +// can see, which is what makes `android.widget.Toast` resolvable from JS. +// Generated from the app's own resolved classpath, so a library the app +// installs is callable without anyone publishing bindings for it. +// +// * TypeScript declarations for the same classpath, on by default, generated +// on the first build and refreshed only when the classpath changes. +// -PnsGenerateTypings=false opts out. +// +// Both are keyed to the variant being built, so debug and release get metadata +// for the dependencies they actually resolve. + +import java.nio.file.Paths + +def nsPackageDir = file("${buildscript.sourceFile.parentFile}") +def toolsDir = file("${nsPackageDir}/tools") + +def metadataGeneratorJar = file("${toolsDir}/android-metadata-generator.jar") +def dtsGeneratorJar = file("${toolsDir}/dts-generator.jar") +def sbgGeneratorJar = file("${toolsDir}/static-binding-generator.jar") + +// Bundler settings, taken from the React extension the app already configures +// so that a project with a custom root, entry file or CLI is bundled the same +// way React Native bundles it. The defaults mirror the plugin's own. +def reactExt = project.extensions.findByName("react") +def reactRoot = reactExt?.root?.getOrNull()?.asFile ?: file("${project.projectDir}/../..") +def reactCliFile = reactExt?.cliFile?.getOrNull()?.asFile?.absolutePath + ?: new File(reactRoot, "node_modules/react-native/cli.js").absolutePath +// Node, as a full command rather than just its head: nodeExecutableAndArgs is a +// list because an app may need flags with it (--max-old-space-size, a wrapper), +// and dropping everything after [0] silently ignores them. +// +// -PnsNodePath wins over everything. A bare "node" is resolved through the +// Gradle daemon's PATH, which is not the PATH of the shell that launched the +// build: version managers (volta, nvm, fnm, asdf) install a shim there that +// fails unless a default version is pinned, and the daemon outlives the +// terminal it was started from. An absolute path sidesteps all of it. +def nsNodeCommand = { + if (project.hasProperty("nsNodePath")) { + return [project.property("nsNodePath").toString()] + } + def fromReact = reactExt?.nodeExecutableAndArgs?.getOrNull() + if (fromReact != null && !fromReact.isEmpty()) { + return fromReact.collect { it.toString() } + } + return ["node"] +}() +def nodeExecutable = nsNodeCommand[0] + +// Shared by every step that shells out to node, so one broken toolchain reads +// the same way wherever it surfaces. +def nsNodeHelp = "Set -PnsNodePath=/absolute/path/to/node (or react { " + + "nodeExecutableAndArgs = [\"/absolute/path/to/node\"] } in " + + "android/app/build.gradle). Version managers such as volta, nvm, fnm " + + "and asdf put a shim on PATH that the Gradle daemon inherits without " + + "the shell state that makes it work." +def nsEntryFile = reactExt?.entryFile?.getOrNull()?.asFile?.absolutePath + ?: (new File(reactRoot, "index.js").exists() + ? new File(reactRoot, "index.js").absolutePath + : new File(reactRoot, "index.android.js").absolutePath) + +// Restrict metadata to what the app's bundle can reach. +// +// On by default, and release-only: a debug build has no bundle on disk to +// analyse, and guessing from a partial view is what breaks apps. Shipping +// metadata for the whole compile classpath means an app that touches 5% of the +// +// The seed is a lower bound that the generator widens (supertypes, nested +// classes, signature types) before dropping anything, because usage alone is +// not enough: android.os.Bundle is never named in JS and never resolved as a +// node, yet removing it makes Activity.onCreate(Bundle) unbuildable and the app +// crashes at launch. +// +// -PnsFilterMetadata=false opts out and ships the whole classpath, which is the +// escape hatch if a filtered build misses something. +def nsFilterMetadata = !project.hasProperty("nsFilterMetadata") || + Boolean.parseBoolean(project.property("nsFilterMetadata").toString()) + +// Classes the app declares it calls from JS, from nativescript.react-native.json +// at the project root -- the same file the iOS side already uses: +// +// { "reactNative": { "metadata": { "android": { +// "include": ["com.ammarahmed.reactnativeworkers.WorkerTurboModules"] } } } } +// +// Entries are added to the metadata whitelist, and a package named here is kept +// out of the autolinked-module exclusion below. Accepts "pkg.Class", "pkg.*" or +// raw .mdg form ("pkg:Class"). +def nsMetadataInclude = { + def configFile = new File(reactRoot, "nativescript.react-native.json") + if (!configFile.exists()) return [] + try { + def parsed = new groovy.json.JsonSlurper().parse(configFile) + def declared = parsed?.reactNative?.metadata?.android?.include + if (declared instanceof List) { + return declared.findAll { it instanceof String && !it.trim().isEmpty() } + .collect { it.toString().trim() } + } + } catch (Exception e) { + // Falling back to a different filter than the author asked for would be + // silent and wrong; a filtered build is not the place to guess. + throw new GradleException("NativeScript: could not read ${configFile}: ${e.message}") + } + return [] +}() + +// "com.foo.Bar" -> "com.foo:Bar", "com.foo.*" -> "com.foo:*", "a:b" unchanged. +def nsToMdgEntry = { String raw -> + def value = raw.trim() + if (value.isEmpty()) return null + if (value.contains(":")) return value + if (value.endsWith(".*")) return value[0..-3] + ":*" + def segments = value.tokenize(".") + for (int i = 0; i < segments.size(); i++) { + if (segments[i] && Character.isUpperCase(segments[i].charAt(0))) { + return segments[0.. + def entry = nsToMdgEntry(raw) + entry == null ? null : entry.substring(0, entry.indexOf(":")) +}.findAll { it != null && !it.isEmpty() } + +// React Native's own native modules are reached from JS through the TurboModule +// bridge, not through the interop, so describing them in metadata buys nothing +// and costs bytes and startup parse time. Their packages are read from the +// PackageList React Native's autolinking generates, so the exclusion tracks +// whatever the app installs without anyone maintaining a list. +// +// This does NOT affect the R8 keep rules: those are written from what metadata +// retains, so a module that is never described is simply never mentioned -- +// and it stays in the app because the Java that registers it references it, +// which is what R8 follows. Excluded from metadata, untouched in the dex. +// +// A package the app named in `include` is left out of the exclusion: the +// generator applies the blacklist *over* the whitelist +// (`isSeed(d) && !isBlacklisted(d)` in MetadataClosure), so blacklisting a +// package the author asked for would silently win over the request. +def nsAutolinkedModulePackages = { variantName -> + def packageList = new File(buildDir, + "generated/autolinking/src/main/java/com/facebook/react/PackageList.java") + if (!packageList.exists()) return [] + + def packages = new LinkedHashSet() + def entry = ~/new\s+([a-zA-Z0-9_.]+)\s*\(/ + packageList.eachLine { line -> + def matcher = entry.matcher(line) + while (matcher.find()) { + def fqcn = matcher.group(1) + def lastDot = fqcn.lastIndexOf('.') + if (lastDot <= 0) return + def pkg = fqcn.substring(0, lastDot) + // The bridge's own packages are described by the runtime keep-list + // and by whatever the JS names; leave that judgement to the seed. + if (pkg.startsWith("com.facebook.react")) return + packages.add(pkg) + } + } + return packages.toList() +} + +// Typings are on by default: without them TypeScript cannot type a single +// native call. The task declares the classpath as an input and the typings +// directory as its output, so an unchanged classpath skips it entirely and +// only a dependency change pays for a run. -PnsGenerateTypings=false opts out. +def nsTypingsEnabled = !project.hasProperty("nsGenerateTypings") + || Boolean.parseBoolean(project.property("nsGenerateTypings").toString()) + +// Where the .dat files land. This is added to the asset source set rather than +// written into src/, so a clean build regenerates and nothing lands in version +// control. NativeScriptNativeApiModule copies them out of the APK on first run. +// +// One directory per variant. A single shared one is not merely untidy: two +// variants' generator tasks then declare the same output, and Gradle refuses to +// merge assets it cannot prove are up to date, failing the build with +// "uses this output of task ... without declaring an explicit dependency". +// Only release surfaces it, because a debug-only build never has a second +// variant's task registered against the same path. +def metadataAssetRootFor = { variantName -> + file("${buildDir}/generated/nativescript/assets/${variantName}") +} +def metadataOutDirFor = { variantName -> + new File(metadataAssetRootFor(variantName), "nativescript-metadata") +} + +// Registered against each variant's own source set rather than main, so that a +// variant only ever picks up the metadata generated for it. +android { + applicationVariants.all { variant -> + def assetRoot = metadataAssetRootFor(variant.name) + def sourceSet = android.sourceSets.findByName(variant.name) + if (sourceSet != null) { + sourceSet.assets.srcDirs += assetRoot + } else { + // Flavoured builds have no source set named after the full variant; + // fall back to main, where the per-variant subdirectory still keeps + // the two generators from colliding. + android.sourceSets.main.assets.srcDirs += assetRoot + } + } +} + +/** + * Every jar the variant resolves, plus android.jar. + * + * AARs are asked for as "android-classes-jar" so the artifact view unpacks + * classes.jar for us; without that an .aar arrives as an archive the generator + * cannot read. + */ +def resolveClasspathJars = { variant -> + def jars = new LinkedHashSet() + + def androidJar = Paths.get(android.sdkDirectory.absolutePath, "platforms", + android.compileSdkVersion, "android.jar").toFile() + if (androidJar.exists()) { + jars.add(androidJar) + } + + // The published name of AGP's "classes.jar unpacked from an aar" artifact. + // Spelled out rather than read off AndroidArtifacts.ArtifactType, which is + // AGP-internal and not resolvable from a script plugin. + def classesJarType = "android-classes-jar" + + try { + def view = variant.runtimeConfiguration.incoming.artifactView { config -> + config.lenient(true) + config.attributes { container -> + container.attribute( + org.gradle.api.attributes.Attribute.of("artifactType", String), + classesJarType) + } + } + view.artifacts.artifactFiles.each { jars.add(it) } + } catch (Exception e) { + logger.warn("NativeScript: could not resolve the ${variant.name} runtime classpath " + + "(${e.message}). Metadata will cover only the Android SDK.") + } + + return jars +} + +/** Directories of .class files this build produced, across every subproject. */ +def resolveCompiledClassDirs = { variant -> + def dirs = new LinkedHashSet() + def variantName = variant.name + + rootProject.subprojects.each { sub -> + ["intermediates/javac/${variantName}/classes", + "intermediates/javac/${variantName}/compile${variantName.capitalize()}JavaWithJavac/classes", + "tmp/kotlin-classes/${variantName}"].each { relative -> + def candidate = new File(sub.buildDir, relative) + if (candidate.isDirectory()) { + dirs.add(candidate) + } + } + } + + return dirs +} + +def collectClassFiles = { Collection roots -> + def files = new ArrayList() + roots.each { root -> + root.eachFileRecurse { f -> + if (f.isFile() && f.name.endsWith(".class")) { + files.add(f) + } + } + } + return files +} + +android.applicationVariants.all { variant -> + def variantName = variant.name.capitalize() + def workDir = file("${buildDir}/nativescript/${variant.name}") + def metadataOutDir = metadataOutDirFor(variant.name) + // R8 rules covering exactly the classes the filtered metadata kept. Only + // written when a filter is in play; without one the generator keeps + // everything and there is nothing to say. + def metadataKeepRules = new File(workDir, "metadata-keep-rules.pro") + + def metadataTask = tasks.register("generateNativeScriptMetadata${variantName}", JavaExec) { + group = "nativescript" + description = "Generates NativeScript metadata for the ${variant.name} classpath." + + // The generator takes its input as files in the working directory + // rather than as arguments -- a classpath can be thousands of entries + // and would overflow the command line. + workingDir workDir + mainClass = "-jar" + + // Everything the emitted metadata is a function of. Without these the + // task is up to date whenever its output directory merely exists, so a + // dependency upgrade -- or turning filtering on -- silently reuses the + // previous tree, which reads exactly like the change having no effect. + // + // Through the artifact view resolveClasspathJars uses; consuming the + // configuration directly leaves artifactType unset and AGP cannot then + // choose between an AAR's variants. Providers keep both this and the + // compiled classes lazy, so they resolve at up-to-date-check time, + // after javac has run. + inputs.files(variant.runtimeConfiguration.incoming.artifactView { config -> + config.lenient(true) + config.attributes { container -> + container.attribute( + org.gradle.api.attributes.Attribute.of("artifactType", String), + "android-classes-jar") + } + }.artifacts.artifactFiles).withPropertyName("nsClasspath") + inputs.files(provider { resolveCompiledClassDirs(variant) }) + .withPropertyName("compiledClasses") + inputs.file(metadataGeneratorJar).withPropertyName("generator") + // The seed is written by another task into this task's working + // directory, so it has to be named or the two never connect. + inputs.files(provider { + def sources = [file("${project.projectDir}/whitelist.mdg"), + file("${project.projectDir}/blacklist.mdg")] + if (nsFilterMetadata) sources.add(new File(workDir, "generated-whitelist.mdg")) + sources.findAll { it.exists() } + }).withPropertyName("filters") + // Toggling the flag off leaves no filter file behind to notice. + inputs.property("filterMetadata", nsFilterMetadata) + // Editing the declaration changes both what is kept and which module + // packages are excluded, and neither is visible in the files above. + inputs.property("metadataInclude", nsMetadataInclude) + + outputs.dir(metadataOutDir) + // Not beside the metadata: that directory's parent is an asset source + // root, and a keep-rules file dropped there ships inside the APK. + outputs.file(metadataKeepRules) + + doFirst { + if (!metadataGeneratorJar.exists()) { + throw new GradleException( + "NativeScript: ${metadataGeneratorJar} is missing. It ships with " + + "@nativescript/react-native; reinstall the package, or run " + + "scripts/build_react_native_android_tools.sh from a checkout.") + } + + workDir.mkdirs() + metadataOutDir.deleteDir() + metadataOutDir.mkdirs() + // A run with no filter writes no rules; the previous run's file + // would otherwise stay behind and keep a set that no longer matches. + metadataKeepRules.delete() + + def jars = resolveClasspathJars(variant) + def classDirs = resolveCompiledClassDirs(variant) + + new File(workDir, "mdg-java-dependencies.txt").withWriter { out -> + jars.each { if (it.name.endsWith(".jar")) out.println it.absolutePath } + classDirs.each { out.println it.absolutePath } + } + + new File(workDir, "mdg-java-out.txt").withWriter { out -> + collectClassFiles(classDirs).each { out.println it.absolutePath } + } + + new File(workDir, "mdg-output-dir.txt").withWriter { out -> + out.println metadataOutDir.absolutePath + } + + // Optional filters, same file names and format the NativeScript CLI + // uses. Absent means "everything on the classpath". + // + // whitelist.mdg has two possible sources: one derived from the + // app's bundle by generateNativeScriptMetadataSeed, and one the app + // author wrote. They are concatenated rather than one winning -- + // both are keep-lists, and the generator takes the union before + // computing its closure. A hand-written entry can therefore only + // ever add, which is the safe direction. + // + // The generated half is gated on the flag rather than on the file + // existing: the seed task leaves its output in the working + // directory, so a later build without -PnsFilterMetadata would + // otherwise go on filtering against it. + def generatedSeed = new File(workDir, "generated-whitelist.mdg") + def useGeneratedSeed = nsFilterMetadata && generatedSeed.exists() + def authoredWhitelist = file("${project.projectDir}/whitelist.mdg") + def whitelistTarget = new File(workDir, "whitelist.mdg") + + // Only in a build that is already filtering. A declaration is meant + // to widen a filtered set, not to become the whole filter: adding it + // to an unfiltered (debug) build would turn a whitelist on for the + // first time and cut that build down to the declaration's closure. + def declaredEntries = useGeneratedSeed + ? nsMetadataInclude.collect { nsToMdgEntry(it) }.findAll { it != null } + : [] + + if (useGeneratedSeed || authoredWhitelist.exists() || !declaredEntries.isEmpty()) { + def parts = [] + if (useGeneratedSeed) parts.add(generatedSeed.text) + if (authoredWhitelist.exists()) parts.add(authoredWhitelist.text) + if (!declaredEntries.isEmpty()) { + parts.add("# from nativescript.react-native.json\n" + + declaredEntries.join("\n") + "\n") + } + whitelistTarget.text = parts.join("\n") + } else { + whitelistTarget.delete() + } + + def blacklistSource = file("${project.projectDir}/blacklist.mdg") + def blacklistTarget = new File(workDir, "blacklist.mdg") + + // Autolinked React Native modules, minus the ones this app actually + // reaches from JS. + // + // Gated with the whitelist because the generator only consults a + // blacklist once a whitelist exists, and an unfiltered build should + // stay unfiltered. + // + // Two things spare a module from the exclusion, and both matter + // because the blacklist beats the whitelist inside the generator + // (`isSeed(d) && !isBlacklisted(d)` in MetadataClosure) -- once a + // package is excluded here, nothing downstream can bring it back: + // + // * the seed names it. A worker calling + // `com.ammarahmed.reactnativeworkers.WorkerTurboModules` is + // using that module as an interop class, whatever else the + // TurboModule bridge is doing with it, so describing it is the + // whole point. + // * the app declared it in nativescript.react-native.json, for + // the case where the analysis cannot see the use. + def seededPackages = [] + if (useGeneratedSeed && whitelistTarget.exists()) { + whitelistTarget.eachLine { line -> + def trimmed = line.trim() + if (trimmed.isEmpty() || trimmed.startsWith("#")) return + def colon = trimmed.indexOf(":") + if (colon > 0) seededPackages.add(trimmed.substring(0, colon)) + } + } + + def keptPackages = (nsIncludedPackages + seededPackages).unique() + def excludedModules = useGeneratedSeed + ? nsAutolinkedModulePackages(variantName) + .findAll { pkg -> !keptPackages.any { it == pkg || it.startsWith(pkg + ".") } } + : [] + + def blacklistParts = [] + if (blacklistSource.exists()) blacklistParts.add(blacklistSource.text) + if (!excludedModules.isEmpty()) { + // Two patterns per module: the package itself, and its + // subpackages. The matcher globs the package name, but "a.b:*" + // matches only "a.b" -- a module that splits itself across + // a.b.core / a.b.views (nitro does) would otherwise leak. + blacklistParts.add( + "# Autolinked React Native modules: reached over the TurboModule\n" + + "# bridge rather than the interop, so they are not described here.\n" + + "# Declare one in nativescript.react-native.json to keep it.\n" + + excludedModules.collect { it + ":*\n" + it + ".*:*" }.join("\n") + "\n") + def keptModules = nsAutolinkedModulePackages(variantName) - excludedModules + logger.lifecycle("NativeScript: excluding ${excludedModules.size()} autolinked " + + "React Native module package(s) from metadata: " + excludedModules.join(", ") + + (keptModules.isEmpty() ? "" : "; kept because the app uses them: " + + keptModules.join(", "))) + } + + if (!blacklistParts.isEmpty()) { + blacklistTarget.text = blacklistParts.join("\n") + } else { + blacklistTarget.delete() + } + + logger.lifecycle("NativeScript: generating metadata from ${jars.size()} jars and " + + "${classDirs.size()} class directories") + + def params = [metadataGeneratorJar.absolutePath] + params.add("proguardOut=" + metadataKeepRules.absolutePath) + // Only a generated seed may be grown into a closure and held to the + // soundness check; an app's own whitelist.mdg is filtered as + // authored. See Builder.applyClosureIfRequested. + if (nsFilterMetadata) { + params.add("enforceClosure=true") + } + if (project.hasProperty("nsVerboseMetadata")) { + params.add("verbose") + } + args params.toArray() + } + } + + // Alongside the app rather than in build/: these are checked in and + // referenced from tsconfig.json, the same way types/ios is on the Apple + // side. Declared out here so it can be the task's output. + def typingsDir = file("${rootProject.projectDir}/../types/android") + + def typingsTask = tasks.register("generateNativeScriptTypings${variantName}", JavaExec) { + group = "nativescript" + description = "Generates TypeScript declarations for the ${variant.name} classpath." + + workingDir workDir + mainClass = "-jar" + onlyIf { nsTypingsEnabled } + + // Content-keyed, so a dependency upgrade that keeps the same path is + // still noticed, and an untouched classpath costs nothing. + // + // Through the same artifact view resolveClasspathJars uses. Consuming + // the configuration directly leaves artifactType unset, and AGP then + // cannot choose between an AAR's variants ("cannot choose between + // android-aar-metadata, android-art-profile, android-assets, ..."). + // artifactFiles stays lazy, so nothing resolves at configuration time. + inputs.files(variant.runtimeConfiguration.incoming.artifactView { config -> + config.lenient(true) + config.attributes { container -> + container.attribute( + org.gradle.api.attributes.Attribute.of("artifactType", String), + "android-classes-jar") + } + }.artifacts.artifactFiles).withPropertyName("nsClasspath") + inputs.file(dtsGeneratorJar).withPropertyName("generator") + outputs.dir(typingsDir).withPropertyName("typings") + + doFirst { + // Created here as well as by the metadata task: the two are + // independent, and typings can be asked for on their own. + workDir.mkdirs() + + if (!dtsGeneratorJar.exists()) { + throw new GradleException( + "NativeScript: ${dtsGeneratorJar} is missing. It ships with " + + "@nativescript/react-native; reinstall the package, or run " + + "scripts/build_react_native_android_tools.sh from a checkout.") + } + + typingsDir.mkdirs() + + def params = [dtsGeneratorJar.absolutePath, "-input"] + resolveClasspathJars(variant).each { params.add(it.absolutePath) } + params.add("-output") + params.add(typingsDir.absolutePath) + + logger.lifecycle("NativeScript: writing TypeScript declarations to ${typingsDir}") + args params.toArray() + } + } + + // Pre-generated Java for named (@JavaProxy) subclasses. + // + // Only for variants that ship a bundle. In debug, Metro serves JS over HTTP, + // there is nothing on disk to read, and unnamed classes are generated on the + // device anyway. + // + // The bundle React Native itself produces is no use here: hermesc compiles + // it and moves the bytecode over the JavaScript inside a single task, so by + // the time any later task runs there is nothing readable left. So Metro is + // run a second time into a scratch directory, purely as generator input. + // That costs a bundle on release builds and nothing on debug. + // + // --dev false is what makes the second bundle usable: it has to be minified + // the same way the shipped one is, or the class names the generator reads + // will not be the names the runtime computes at load. A @JavaProxy name is + // a string literal and survives minification either way; an unnamed class + // that fails to line up simply gets generated on the device, as it does in + // debug, so a mismatch costs startup time rather than correctness. + def sbgEnabled = !variant.buildType.debuggable && + !project.hasProperty("nsSkipStaticBindings") && + sbgGeneratorJar.exists() + + if (sbgEnabled) { + def sbgWorkDir = file("${buildDir}/nativescript/${variant.name}/sbg") + def sbgBundleDir = new File(sbgWorkDir, "bundle") + def sbgJavaDir = new File(sbgWorkDir, "java") + + def bindingsTask = tasks.register("generateNativeScriptBindings${variantName}", JavaExec) { + group = "nativescript" + description = "Pre-generates Java bindings for named NativeScript subclasses (${variant.name})." + + // The generator resolves jsparser and every one of its scratch + // files relative to the working directory, so it gets its own. + workingDir sbgWorkDir + mainClass = "-jar" + + inputs.files(fileTree(reactRoot) { + include "**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx" + exclude "**/node_modules/**", "**/android/**", "**/ios/**", "**/build/**" + }) + // The app's own sources are not the whole program: a dependency's JS + // is bundled into these graphs too, and a library that calls Java is + // as much a part of the analysis as the app is. node_modules is + // excluded above because hashing it on every build is far more + // expensive than the bundling it would guard -- the lockfiles stand + // in for it, since a dependency cannot change without one of them + // changing. Without this, upgrading a library silently leaves the + // bindings and the metadata filter describing the previous one. + // + // Searched up from the app as well as in it: in a workspace the app + // is a package and the lockfile belongs to the repository root, so + // looking only beside package.json finds nothing at all -- which + // fails silently, as an input that never changes. + inputs.files(provider { + def names = ["yarn.lock", "package-lock.json", "pnpm-lock.yaml", "bun.lockb"] + def found = [] + def dir = reactRoot + // A handful of levels: enough for app-in-a-workspace, and never + // so far up that an unrelated lockfile outside the project is + // picked up. + for (int depth = 0; depth < 5 && dir != null; depth++) { + names.each { name -> + def candidate = new File(dir, name) + if (candidate.exists()) found.add(candidate) + } + if (new File(dir, ".git").exists()) break + dir = dir.parentFile + } + def appPackageJson = new File(reactRoot, "package.json") + if (appPackageJson.exists()) found.add(appPackageJson) + found + }).withPropertyName("dependencyLocks").optional() + // Which entry points exist at all. A worker added or repointed + // changes the set of graphs to analyse, and the journal is the only + // place that shows -- adding one usually adds a source file, which + // the tree above catches, but repointing an existing file does not. + inputs.files(provider { + def journal = new File(reactRoot, ".rn-workers/manifest.log") + journal.exists() ? [journal] : [] + }).withPropertyName("workerEntries").optional() + // The generator and its parser are inputs too. Without them a change + // to either leaves the task up to date against stale output, which + // reads exactly like the change having no effect. + inputs.file(sbgGeneratorJar) + inputs.files(fileTree(new File(toolsDir, "jsparser"))) + // The analysis bundler is one as well; it decides what the parser + // and the seed get to see. + inputs.files(provider { + def script = new File(nsPackageDir, "scripts/bundle-for-analysis.js") + script.exists() ? [script] : [] + }).withPropertyName("analysisBundler").optional() + outputs.dir(sbgJavaDir) + // Declared so the bundles survive an up-to-date run: the metadata + // seed reads this directory, and a task that is skipped must still + // leave its inputs in place. + outputs.dir(sbgBundleDir) + + doFirst { + sbgWorkDir.mkdirs() + sbgBundleDir.deleteDir() + sbgBundleDir.mkdirs() + sbgJavaDir.deleteDir() + sbgJavaDir.mkdirs() + + // Plain JDK calls rather than project.copy / project.exec: + // Gradle no longer allows reaching into Project services while a + // task is executing. + def jsparserSource = new File(toolsDir, "jsparser").toPath() + def jsparserTarget = new File(sbgWorkDir, "jsparser").toPath() + java.nio.file.Files.walk(jsparserSource).forEach { source -> + def target = jsparserTarget.resolve(jsparserSource.relativize(source)) + if (java.nio.file.Files.isDirectory(source)) { + java.nio.file.Files.createDirectories(target) + } else { + java.nio.file.Files.createDirectories(target.getParent()) + java.nio.file.Files.copy(source, target, + java.nio.file.StandardCopyOption.REPLACE_EXISTING) + } + } + + // jsparser reads ts_helpers.js from a sibling of the input + // directory to learn the decorator names, so the copy that ships + // in the package is staged where it expects to find it. + def tsHelpersTarget = new File(sbgWorkDir, "internal/ts_helpers.js") + tsHelpersTarget.parentFile.mkdirs() + def tsHelpersSource = new File(nsPackageDir, + "src/main/assets/nativescript/internal/ts_helpers.js") + if (!tsHelpersSource.exists()) { + throw new GradleException("NativeScript: ${tsHelpersSource} is missing from the package.") + } + tsHelpersTarget.text = tsHelpersSource.text + + // Secondary entry points, each of which is its own module graph + // and so appears in no other bundle. A worker's JS is invisible + // to everything here unless it is bundled separately: it neither + // contributes subclasses to the static binding generator nor + // Java usage to the metadata seed, and the result is a release + // build that drops exactly the classes a worker needs. + // + // Read from the journal a worker bundler leaves behind + // (.rn-workers/manifest.log, JSON lines of {id, absPath}) rather + // than from its output: that runs later in the build, and by + // then it has already compiled its bundles to Hermes bytecode, + // which no JS parser can read. + def workerJournal = new File(reactRoot, ".rn-workers/manifest.log") + def entriesById = [:] + if (workerJournal.exists()) { + def slurper = new groovy.json.JsonSlurper() + workerJournal.eachLine { line -> + def trimmed = line.trim() + if (trimmed.isEmpty()) return + try { + def parsed = slurper.parseText(trimmed) + // Later lines win: the journal is appended to on + // every transform, so it holds stale duplicates. + if (parsed?.id && parsed?.absPath) entriesById[parsed.id] = parsed.absPath + } catch (Exception ignored) { + // A malformed line is one worker, not the build. + } + } + } + def workerEntries = entriesById.findAll { id, absPath -> new File(absPath).exists() } + + // With workers to build, one Metro session builds the app entry + // and all of them together -- so the app entry is not bundled + // twice, and the copy that survives is the unminified one the + // analysis reads far better (minified input is what leaves most + // call sites unresolved). + def analysisScript = new File(nsPackageDir, "scripts/bundle-for-analysis.js") + def useAnalysisScript = !workerEntries.isEmpty() && analysisScript.exists() + + logger.lifecycle("NativeScript: bundling ${variant.name} JavaScript for binding generation") + + // --unstable-transform-profile is what keeps `class X extends Y {}` + // in the output. React Native's Babel preset downlevels classes + // into a function plus an anonymous helper call unless this is + // set (preserveClasses = isHermesV1 in the preset), and a + // downlevelled class is not something any static analysis can + // recognise as a subclass. + // + // It only affects this bundle. The one that ships is built by + // React Native's own task and is untouched -- and the runtime + // handles the downlevelled form there perfectly well. + // + // The value is "hermes-stable"; the CLI's own help text still + // says "hermes", which the preset does not match. + def runCliMainBundle = { + def bundleCmd = nsNodeCommand + [reactCliFile, "bundle", + "--platform", "android", + "--dev", "false", + "--unstable-transform-profile", "hermes-stable", + "--entry-file", nsEntryFile, + "--bundle-output", new File(sbgBundleDir, "index.android.bundle").absolutePath, + "--assets-dest", new File(sbgWorkDir, "assets").absolutePath] + + // Kept so a failure can name the node that actually ran: the + // whole class of toolchain problems here is "not the node you + // think", and the exit code alone does not show which one. + def bundleOutput = new StringBuilder() + def bundleProcess = new ProcessBuilder(bundleCmd) + .directory(reactRoot) + .redirectErrorStream(true) + .start() + bundleProcess.inputStream.eachLine { + logger.info(it) + bundleOutput.append(it).append("\n") + } + def bundleExit = bundleProcess.waitFor() + if (bundleExit != 0) { + // 126 is "found but not executable", which is what a version + // manager's shim returns when it has no version to delegate + // to; 127 is "not on PATH at all". Both mean the node lookup, + // not the bundle. + def nodeProblem = bundleExit == 126 || bundleExit == 127 + throw new GradleException( + "NativeScript: bundling for binding generation failed (exit ${bundleExit}) " + + "using node '${nsNodeCommand.join(' ')}'.\n" + + (nodeProblem ? "That exit code means the node command could not be run. " + + nsNodeHelp + "\n" : "") + + (bundleOutput.length() > 0 + ? "Bundler output:\n${bundleOutput.toString().trim()}\n" + : "The bundler produced no output; re-run with --info.\n") + + "Or pass -PnsSkipStaticBindings to build without pre-generated bindings.") + } + } + + // Skipped when the analysis script runs: that builds this same + // entry alongside the workers, in one Metro session. + if (!useAnalysisScript) { + runCliMainBundle() + } + + if (useAnalysisScript) { + { + logger.lifecycle("NativeScript: bundling the app entry and " + + "${workerEntries.size()} worker " + + "entr${workerEntries.size() == 1 ? 'y' : 'ies'} in one Metro session") + + // One session for all of them. A `react-native bundle` + // per entry would pay a fresh Node process, a fresh + // config load and a cold transform cache each time; + // loading the config once and reusing it is what React + // Native's own tooling does. + def analysisCmd = nsNodeCommand + [analysisScript.absolutePath, + "--project-root", reactRoot.absolutePath, + "--out", sbgBundleDir.absolutePath, + "--entry", nsEntryFile, + // Worker graphs swap `react-native` for + // the worker shim; without this the + // analysis would read a graph the + // device never runs. + "--resolver-option", "rnworkers=true"] + workerEntries.each { id, absPath -> analysisCmd.addAll(["--worker", "${id}=${absPath}"]) } + + def analysisOutput = new StringBuilder() + def analysisProcess = new ProcessBuilder(analysisCmd.collect { it.toString() }) + .directory(reactRoot) + .redirectErrorStream(true) + .start() + analysisProcess.inputStream.eachLine { + logger.info(it) + analysisOutput.append(it).append("\n") + } + def analysisExit = analysisProcess.waitFor() + + // Exit 3 is "this React Native has no programmatic + // bundle API"; anything else is a real failure, and + // carrying on would filter metadata against a view of + // the program that is missing these workers -- a launch + // crash in the field rather than an error here. + if (analysisExit == 3) { + logger.lifecycle("NativeScript: falling back to one bundler run per entry") + // The app entry was skipped above on the assumption + // this session would build it. + runCliMainBundle() + workerEntries.each { id, absPath -> + def safeName = id.replaceAll("[^A-Za-z0-9]", "_") + def workerOut = new File(sbgBundleDir, "worker-${safeName}.js") + def workerCmd = nsNodeCommand + [reactCliFile, "bundle", + "--platform", "android", + "--dev", "false", + "--unstable-transform-profile", "hermes-stable", + "--entry-file", absPath, + "--bundle-output", workerOut.absolutePath] + def workerOutput = new StringBuilder() + def workerProcess = new ProcessBuilder(workerCmd.collect { it.toString() }) + .directory(reactRoot) + .redirectErrorStream(true) + .start() + workerProcess.inputStream.eachLine { + logger.info(it) + workerOutput.append(it).append("\n") + } + if (workerProcess.waitFor() != 0) { + throw new GradleException( + "NativeScript: bundling worker '${id}' for analysis failed.\n" + + workerOutput.toString().trim()) + } + } + } else if (analysisExit != 0) { + throw new GradleException( + "NativeScript: bundling the app and its workers for analysis " + + "failed (exit ${analysisExit}).\n" + analysisOutput.toString().trim()) + } + } + } + + def bundles = sbgBundleDir.listFiles()?.findAll { it.isFile() } ?: [] + if (bundles.isEmpty()) { + throw new GradleException("NativeScript: the bundler produced no output in ${sbgBundleDir}") + } + logger.lifecycle("NativeScript: scanning ${bundles.size()} bundle file(s) for named subclasses") + + new File(sbgWorkDir, "sbg-input-file.txt").text = "${sbgBundleDir.absolutePath}\n" + new File(sbgWorkDir, "sbg-output-file.txt").text = "${sbgJavaDir.absolutePath}\n" + + // Only jars that exist and actually open. Two kinds do not: + // + // - sibling library modules whose classes.jar is produced later + // in the build. Registering this task as a source generator + // puts it in front of javac, which is in front of those. + // - artifacts that are not really archives (soloader ships a + // runtime jar with no class entries). + // + // Either one aborts the generator with "Error while reading JAR + // entry" and takes the whole build down, so they are dropped and + // reported. The cost of dropping one is that a class extending + // something declared inside it is not pre-generated -- it still + // works, built on the device as in debug. + def usableJars = [] + def skippedJars = [] + resolveClasspathJars(variant).each { jar -> + if (!jar.name.endsWith(".jar")) return + if (!jar.isFile()) { + skippedJars.add("${jar.name} (not built yet)") + return + } + try { + new java.util.zip.ZipFile(jar).withCloseable { zip -> + if (zip.entries().findAll { it.name.endsWith(".class") }.isEmpty()) { + skippedJars.add("${jar.name} (no classes)") + } else { + usableJars.add(jar) + } + } + } catch (Exception e) { + skippedJars.add("${jar.name} (unreadable)") + } + } + + if (!skippedJars.isEmpty()) { + logger.lifecycle("NativeScript: skipping ${skippedJars.size()} classpath " + + "entr${skippedJars.size() == 1 ? 'y' : 'ies'} for binding generation: " + + skippedJars.join(", ")) + } + + new File(sbgWorkDir, "sbg-java-dependencies.txt").withWriter { out -> + usableJars.each { out.println it.absolutePath } + } + + // jsparser's Babel packages are dependencies of + // @nativescript/react-native, so they live in the app's + // node_modules rather than beside the copied tree. + environment "NODE_PATH", new File(reactRoot, "node_modules").absolutePath + + // The generator spawns `node` for jsparser itself, so the node + // resolved above has to be on the PATH it inherits -- otherwise + // the bundle step uses ours and the parser step silently uses a + // different one (or a version manager's shim, which fails). + def resolvedNode = new File(nodeExecutable) + if (resolvedNode.isAbsolute() && resolvedNode.parentFile != null) { + environment "PATH", resolvedNode.parentFile.absolutePath + + File.pathSeparator + System.getenv("PATH") + } + + // An app that subclasses nothing has no bindings to generate, + // which is an ordinary outcome -- most React Native apps call + // native APIs without ever extending a Java class. The generator + // does not treat it as one: jsparser writes sbg-bindings.txt + // only when it finds something, and the generator then fails + // with "Couldn't find ... bindings input file". Pre-creating the + // file does not help, because the generator deletes it before + // running the parser itself. + // + // So run the parser here first -- the same script, with the node + // and NODE_PATH resolved above -- and skip the generator when it + // comes back empty. The generator would re-run it anyway, so + // this costs one parse of an already-built bundle. + def parserScript = new File(sbgWorkDir, "jsparser/js_parser.js") + def sbgBindings = new File(sbgWorkDir, "sbg-bindings.txt") + if (parserScript.exists()) { + sbgBindings.delete() + def probeBuilder = new ProcessBuilder( + (nsNodeCommand + [parserScript.absolutePath]).collect { it.toString() }) + .directory(sbgWorkDir) + .redirectErrorStream(true) + probeBuilder.environment().put("NODE_PATH", + new File(reactRoot, "node_modules").absolutePath) + def probe = probeBuilder.start() + probe.inputStream.eachLine { logger.info(" [sbg-parser] $it") } + probe.waitFor() + + if (!sbgBindings.exists() || sbgBindings.text.trim().isEmpty()) { + logger.lifecycle("NativeScript: no Java subclasses found in the bundle, " + + "so there are no static bindings to generate. Any subclassing the " + + "app does at runtime is still bound on the device, as in debug.") + throw new org.gradle.api.tasks.StopExecutionException() + } + } + + def params = [sbgGeneratorJar.absolutePath] + // The generated names must be derived the same way the runtime + // derives them; the guest runtime always uses content keys. + params.add("-content-keyed-bindings") + args params.toArray() + } + } + + // Registering the directory is what puts the generated Java in front of + // javac; it also makes javac depend on this task, which is why the + // bundling above has to be free of any dependency on compiled classes. + variant.registerJavaGeneratingTask(bindingsTask.get(), sbgJavaDir) + + // Ship metadata for what the app can actually reach, rather than for + // every class on the compile classpath. + // + // Reuses the bundle the binding generator already produced -- the whole + // reason this is release-only is that debug has no JS on disk to read, + // and a filter built from a partial view of the program is how apps + // break in the field. The seed is only a starting point: the generator + // closes over supertypes, nested classes and signature types, and fails + // the build if that closure turns out unsound. + if (nsFilterMetadata) { + def seedTask = tasks.register("generateNativeScriptMetadataSeed${variantName}") { + group = "nativescript" + description = "Derives the metadata whitelist from the ${variant.name} bundle." + + dependsOn(bindingsTask) + + inputs.dir(sbgBundleDir) + inputs.files(fileTree(new File(toolsDir, "metadata-filter"))) + outputs.file(new File(workDir, "generated-whitelist.mdg")) + + doLast { + workDir.mkdirs() + + def seedScript = new File(toolsDir, "metadata-filter/seed.js") + if (!seedScript.exists()) { + throw new GradleException( + "NativeScript: ${seedScript} is missing. It ships with " + + "@nativescript/react-native; reinstall the package, or run " + + "scripts/build_react_native_android_tools.sh from a checkout.") + } + + def cmd = nsNodeCommand + [seedScript.absolutePath, sbgBundleDir.absolutePath, + "--out", new File(workDir, "generated-whitelist.mdg").absolutePath] + + def manifest = android.sourceSets.main.manifest.srcFile + if (manifest != null && manifest.exists()) { + cmd.addAll(["--manifest", manifest.absolutePath]) + } + + // Custom views, fragments and behaviours are named in + // resource XML and inflated by the framework; the JS never + // mentions them. + android.sourceSets.main.res.srcDirs.each { dir -> + if (dir.exists()) { + cmd.addAll(["--res", dir.absolutePath]) + } + } + + def bindings = new File(sbgWorkDir, "sbg-bindings.txt") + if (bindings.exists()) { + cmd.addAll(["--sbg-bindings", bindings.absolutePath]) + } + + logger.lifecycle("NativeScript: deriving metadata filter from ${sbgBundleDir}") + + // Real Strings: interpolated paths are GStrings, which + // ProcessBuilder's List will not take. + def process = new ProcessBuilder(cmd.collect { it.toString() }) + .redirectErrorStream(true).start() + process.inputStream.eachLine { logger.lifecycle(" [metadata-seed] $it") } + def seedExit = process.waitFor() + if (seedExit != 0) { + throw new GradleException( + "NativeScript: metadata seed generation failed (exit ${seedExit}) " + + "using node '${nsNodeCommand.join(' ')}'." + + ((seedExit == 126 || seedExit == 127) ? "\n" + nsNodeHelp : "")) + } + } + } + + metadataTask.configure { it.dependsOn(seedTask) } + + // Filtered metadata names a subset of the classpath, so R8 is now + // free to remove the rest -- but only if it is told which subset, + // and only if the app has minification on in the first place. This + // adds the rules; it deliberately does not turn minification on, + // which is the app's decision to make. + if (variant.buildType.minifyEnabled) { + variant.buildType.proguardFiles.add(metadataKeepRules) + tasks.matching { + it.name == "minify${variantName}WithR8" || + it.name == "minify${variantName}WithProguard" + }.configureEach { it.dependsOn(metadataTask) } + } else { + logger.lifecycle("NativeScript: metadata filtering is on but ${variant.name} " + + "has minifyEnabled=false, so the keep rules at ${metadataKeepRules} " + + "are written and not applied. The metadata shrinks either way; the " + + "dex does not.") + } + } + } + + // Metadata describes the compiled classes, so it has to run after javac and + // before the assets are merged into the APK. + // Depend on the provider directly. Reaching into javaCompileProvider.configure + // to wire these up nests one task's configuration inside another's, which + // Gradle rejects. + metadataTask.configure { it.dependsOn(variant.javaCompileProvider) } + typingsTask.configure { it.dependsOn(variant.javaCompileProvider) } + + tasks.matching { it.name == "merge${variantName}Assets" }.configureEach { + it.dependsOn(metadataTask) + if (nsTypingsEnabled) { + it.dependsOn(typingsTask) + } + } + + // The typings land next to the app (types/android, checked in and named by + // tsconfig, the way types/ios is on the Apple side), which puts them inside + // the tree React Native's bundler reads. Gradle sees one task writing into + // another's input directory and fails the build rather than risk ordering + // them wrongly -- but only when both are scheduled, which is why a + // single-variant build never hit it and `assembleRelease assembleDebug` + // does: the *debug* typings task and the *release* bundle task have no + // relationship at all. + // + // Ordering rather than a dependency: the bundler does not consume the + // typings, it merely reads a directory that contains them. Making it depend + // on them would serialise a JS bundle behind a full classpath scan for no + // reason. + // + // Only across variants. Within one variant the bundle task runs *before* + // javac (its output feeds the resource pipeline) while typings run after it, + // so ordering that pair asks for a cycle: + // + // createBundleRelease -> typingsRelease -> compileReleaseJavaWithJavac + // -> ... -> mapReleaseSourceSetPaths -> createBundleRelease + // + // and Gradle rejects it. The conflict this fixes is a cross-variant one + // anyway: the release bundle and the *debug* typings task have no + // relationship at all, which is why they only collide when both variants + // are built in a single invocation. + def ownBundleTask = "createBundle${variantName}JsAndAssets" + tasks.matching { + it.name.startsWith("createBundle") && it.name.endsWith("JsAndAssets") && + it.name != ownBundleTask + }.configureEach { + it.mustRunAfter(typingsTask) + } + + // Lint reads the asset source sets, which now include a generated directory. + // Gradle fails the build on an undeclared read of another task's output + // rather than risking a stale result, so the ordering is stated. Matched by + // name because the lint tasks vary between AGP versions and not all of them + // exist in every build. + tasks.matching { + it.name.startsWith("lint") || it.name.startsWith("generate${variantName}Lint") + }.configureEach { + it.mustRunAfter(metadataTask) + it.dependsOn(metadataTask) + } +} diff --git a/packages/react-native/android/scripts/bundle-for-analysis.js b/packages/react-native/android/scripts/bundle-for-analysis.js new file mode 100644 index 000000000..11fae01c9 --- /dev/null +++ b/packages/react-native/android/scripts/bundle-for-analysis.js @@ -0,0 +1,127 @@ +#!/usr/bin/env node +/* + * Builds every JS graph the app ships into one directory, for the static + * binding generator and the metadata seed to read. + * + * Why this exists rather than one `react-native bundle` per entry: an app's + * secondary entry points (workers) are separate module graphs that appear in no + * other bundle, so each has to be built -- but a CLI invocation per entry pays a + * fresh Node process, a fresh Metro config load and a cold transform cache every + * time. Loading the config once and reusing it across entries is what React + * Native's own tooling does, and what a worker bundler does too. + * + * Everything is resolved from the *app's* root, never from this package: the + * Metro, React Native and community-cli-plugin that build the analysis bundles + * must be the ones that build the shipping bundles, or the analysis describes a + * program the device never runs. + * + * Usage: + * bundle-for-analysis.js --project-root --out --entry + * [--worker =]… [--resolver-option k=v]… + */ + +const fs = require("fs"); +const path = require("path"); + +function reqFrom(fromDir, request) { + return require(require.resolve(request, { paths: [fromDir] })); +} + +function parseArgs(argv) { + const args = { workers: [], resolverOptions: [] }; + for (let i = 0; i < argv.length; i++) { + const key = argv[i]; + if (key === "--project-root") args.projectRoot = argv[++i]; + else if (key === "--out") args.out = argv[++i]; + else if (key === "--entry") args.entry = argv[++i]; + else if (key === "--resolver-option") args.resolverOptions.push(argv[++i]); + else if (key === "--worker") { + const value = argv[++i]; + const eq = value.indexOf("="); + if (eq > 0) args.workers.push({ id: value.slice(0, eq), file: value.slice(eq + 1) }); + } + } + return args; +} + +async function loadMetroConfig(projectRoot, platform) { + /* The same path @react-native/community-cli-plugin takes: its own loader when + * present, else Metro's. Both read the app's metro.config.js. */ + try { + const { loadConfigAsync } = reqFrom(projectRoot, "@react-native/community-cli-plugin"); + if (typeof loadConfigAsync === "function") { + return await loadConfigAsync({ root: projectRoot, platform }, { platform }); + } + } catch (_) { + /* fall through to Metro's own loader */ + } + const metro = reqFrom(projectRoot, "metro"); + return await metro.loadConfig({ cwd: projectRoot }); +} + +async function main() { + const args = parseArgs(process.argv.slice(2)); + if (!args.projectRoot || !args.out || !args.entry) { + console.error("bundle-for-analysis: --project-root, --out and --entry are required"); + process.exit(2); + } + + const { unstable_buildBundleWithConfig } = reqFrom( + args.projectRoot, + "@react-native/community-cli-plugin" + ); + if (typeof unstable_buildBundleWithConfig !== "function") { + /* Older React Native. The caller falls back to one CLI run per entry, which + * is slower but produces the same bundles. */ + console.error("bundle-for-analysis: unstable_buildBundleWithConfig is unavailable"); + process.exit(3); + } + + fs.mkdirSync(args.out, { recursive: true }); + const config = await loadMetroConfig(args.projectRoot, "android"); + + const build = async (label, entryFile, outFile, resolverOption) => + unstable_buildBundleWithConfig( + { + platform: "android", + dev: false, + /* Readable output: the analysis reads class and member names out of + * this, and a minified graph hides most of them. */ + minify: false, + entryFile, + bundleOutput: outFile, + assetsDest: path.join(args.out, "assets"), + /* Keeps `class X extends Y {}` intact. React Native's Babel preset + * downlevels classes into a helper call otherwise, and a downlevelled + * class is not something static analysis can recognise as a subclass. */ + unstableTransformProfile: "hermes-stable", + ...(resolverOption ? { resolverOption } : {}), + }, + config + ).catch((e) => { + throw new Error(`bundle-for-analysis: ${label} failed: ${e.stack || e.message}`); + }); + + await build("app entry", args.entry, path.join(args.out, "index.android.bundle"), null); + + for (const worker of args.workers) { + const safe = worker.id.replace(/[^A-Za-z0-9]/g, "_"); + await build( + `worker ${worker.id}`, + worker.file, + path.join(args.out, `worker-${safe}.js`), + /* Worker graphs resolve `react-native` to the worker shim; without the + * flag this would analyse a graph the device never runs. */ + args.resolverOptions.length > 0 ? args.resolverOptions : null + ); + } + + console.error( + `bundle-for-analysis: built ${1 + args.workers.length} bundle(s) in one Metro session` + ); +} + +main().catch((e) => { + console.error(e.message || e); + process.exit(1); +}); diff --git a/packages/react-native/android/src/main/AndroidManifest.xml b/packages/react-native/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..94cbbcfc3 --- /dev/null +++ b/packages/react-native/android/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/packages/react-native/android/src/main/assets/nativescript/internal/ts_helpers.js b/packages/react-native/android/src/main/assets/nativescript/internal/ts_helpers.js new file mode 100644 index 000000000..bc6fe53fc --- /dev/null +++ b/packages/react-native/android/src/main/assets/nativescript/internal/ts_helpers.js @@ -0,0 +1,454 @@ +(function () { + var __decorate = + (this && this.__decorate) || + function (decorators, target, key, desc) { + var c = arguments.length; + var r = + c < 3 + ? target + : desc === null + ? (desc = Object.getOwnPropertyDescriptor(target, key)) + : desc, + d; + + if ( + typeof globalThis.Reflect === "object" && + typeof globalThis.Reflect.decorate === "function" + ) { + r = globalThis.Reflect.decorate(decorators, target, key, desc); + } else { + for (var i = decorators.length - 1; i >= 0; i--) { + if ((d = decorators[i])) { + r = + (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + } + } + } + return c > 3 && r && Object.defineProperty(target, key, r), r; + }; + + // For backward compatibility. + var __native = function (thiz) { + // we are setting the __container__ property to the base class when the super method is called + // if the constructor returns the __native(this) call we will use the old implementation + // copying all the properties to the result + // otherwise if we are using the result from the super() method call we won't need such logic + // as thiz already contains the parent properties + // this way we now support both implementations in typescript generated constructors: + // 1: super(); return __native(this); + // 2: return super() || this; + // if (thiz.__container__) { + // if (__useHostObjects) { + // for (var prop in thiz) { + // if (thiz.hasOwnProperty(prop)) { + // thiz.__proto__[prop] = thiz[prop]; + // delete thiz[prop]; + // } + // } + // } + + return thiz; + // } else { + // return thiz; + // } + }; + + var __extends = function (Child, Parent) { + // Detect a native class by a brand the runtime stamps on its native extend() rather than + // sniffing Parent.extend.toString() for "[native code]": in release/bytecode builds every + // function (JS or native) stringifies to "[native code]", which misdetects a plain JS class + // with a static "extend" method as native. The brand works for source and bytecode alike. + var extendNativeClass = + !!Parent.extend && Parent.extend.__isNativeExtend__ === true; + + if (!extendNativeClass) { + __extends_ts(Child, Parent); + return; + } + if (Parent.__isPrototypeImplementationObject) { + throw new Error("Can not extend an already extended native object."); + } + + function extend(thiz) { + var child = thiz.__proto__.__child; + if (!child.__extended) { + var parent = thiz.__proto__.__parent; + child.__extended = parent.extend(child.name, child.prototype, true); + // This will deal with "i instanceof child" + child[Symbol.hasInstance] = function (instance) { + return instance instanceof this.__extended; + }; + } + return child.__extended; + } + + Parent.__activityExtend = function (parent, name, implementationObject) { + __log("__activityExtend called"); + return parent.extend(name, implementationObject); + }; + + Parent.call = function (thiz) { + var Extended = extend(thiz); + thiz.__container__ = true; + if (arguments.length > 1) { + if (typeof Extended !== "function") { + thiz = Reflect.construct( + Extended, + Array.prototype.slice.call(arguments, 1) + ); + } else { + thiz = new (Function.prototype.bind.apply( + Extended, + [null].concat(Array.prototype.slice.call(arguments, 1)) + ))(); + } + } else { + thiz = new Extended(); + } + return thiz; + }; + + Parent.apply = function (thiz, args) { + var Extended = extend(thiz); + thiz.__container__ = true; + if (args && args.length > 0) { + if (typeof Extended !== "function") { + thiz = Reflect.construct(Extended, [null].concat(args)); + } else { + thiz = new (Function.prototype.bind.apply( + Extended, + [null].concat(args) + ))(); + } + } else { + thiz = new Extended(); + } + return thiz; + }; + __extends_ns(Child, Parent); + Child.__isPrototypeImplementationObject = true; + Child.__proto__ = Parent; + Child.prototype.__parent = Parent; + Child.prototype.__child = Child; + }; + + var __extends_ts = function (child, parent) { + extendStaticFunctions(child, parent); + assignPrototypeFromParentToChild(parent, child); + }; + + var __extends_ns = function (child, parent) { + if (!parent.extend) { + assignPropertiesFromParentToChild(parent, child); + } + + assignPrototypeFromParentToChild(parent, child); + }; + + var extendStaticFunctions = + Object.setPrototypeOf || + (hasInternalProtoProperty() && + function (child, parent) { + child.__proto__ = parent; + }) || + assignPropertiesFromParentToChild; + + function hasInternalProtoProperty() { + return { __proto__: [] } instanceof Array; + } + + function assignPropertiesFromParentToChild(parent, child) { + for (var property in parent) { + if (parent.hasOwnProperty(property)) { + child[property] = parent[property]; + } + } + } + + function assignPrototypeFromParentToChild(parent, child) { + function __() { + this.constructor = child; + } + + if (parent === null) { + child.prototype = Object.create(null); + } else { + __.prototype = parent.prototype; + child.prototype = new __(); + } + } + + function JavaProxy(className) { + return function (target) { + var extended = target.extend(className, target.prototype); + extended.name = className; + return extended; + }; + } + + function Interfaces(interfacesArr) { + return function (target) { + if (interfacesArr instanceof Array) { + // attach interfaces: [] to the object + target.prototype.interfaces = interfacesArr; + } + }; + } + Object.defineProperty(globalThis, "__native", { value: __native }); + Object.defineProperty(globalThis, "__extends", { value: __extends }); + Object.defineProperty(globalThis, "__decorate", { value: __decorate }); + + + + if (!globalThis.__ns__worker) { + globalThis.JavaProxy = JavaProxy; + } + globalThis.Interfaces = Interfaces; + + if (globalThis.WeakRef && !globalThis.WeakRef.prototype.get) { + globalThis.WeakRef.prototype.get = globalThis.WeakRef.prototype.deref; + } + + // Native array access: numeric indexing and the map/forEach/toString/ + // Symbol.iterator helpers are now implemented natively (see MetadataNode's + // array prototype + the host object's indexed accessors), so the old JS + // getNativeArrayProp/setNativeArrayProp helpers are gone. + + function findInPrototypeChain(obj, prop) { + while (obj) { + if (obj.hasOwnProperty(prop)) { + return Object.getOwnPropertyDescriptor(obj, prop); + } + obj = Object.getPrototypeOf(obj); + } + return undefined; + } + + globalThis.__prepareHostObject = function (hostObject, jsThis) { + // const prototype = Object.getPrototypeOf(jsThis); + // Object.setPrototypeOf(hostObject, prototype); + Object.defineProperty(hostObject, "super", { + get: () => jsThis["super"], + }); + }; + + const EXTERNAL_PROP = "[[external]]"; + const REFERENCE_PROP_JSC = "[[jsc_reference_info]]"; + + function __createNativeProxy(object, objectId) { + const proxy = new Proxy(object, { + get: function (target, prop) { + if (prop === EXTERNAL_PROP) return this[EXTERNAL_PROP]; + if (prop === REFERENCE_PROP_JSC) return this[REFERENCE_PROP_JSC]; + // Numeric indices go straight to the native element accessor; the + // map/forEach/toString/Symbol.iterator helpers live on the array + // prototype now, so everything else just forwards to the target. + if (target.__is__javaArray && typeof prop !== "symbol" && !isNaN(prop)) { + return target.getValueAtIndex(parseInt(prop)); + } + return target[prop]; + }, + set: function (target, prop, value) { + if (prop === EXTERNAL_PROP) { + this[EXTERNAL_PROP] = value; + return true; + } + + if (prop === REFERENCE_PROP_JSC) { + this[REFERENCE_PROP_JSC] = value; + } + + if (target.__is__javaArray && !isNaN(prop)) { + target.setValueAtIndex(parseInt(prop), value); + return true; + } + + target[prop] = value; + return true; + }, + }); + return proxy; + } + globalThis.__createNativeProxy = __createNativeProxy; + + globalThis.getErrorStack = (err) => { + if (err) return err.stack; + const stack = new Error("").stack; + const lines = stack.split("\n"); + // Line 2 results in invalid stack if not replaced when doing typescript extend. + lines[2] = " at extend(native)"; + return lines.join("\n"); + }; + + if (globalThis.URL) { + const BLOB_STORE = new Map(); + URL.createObjectURL = function (object, options = null) { + try { + if (object instanceof Blob || object instanceof File) { + const id = java.util.UUID.randomUUID().toString(); + const ret = `blob:nativescript/${id}`; + BLOB_STORE.set(ret, { + blob: object, + type: object ? object.type : undefined, + ext: options ? options.ext : undefined, + }); + return ret; + } + } catch (error) { + return null; + } + return null; + }; + URL.revokeObjectURL = function (url) { + BLOB_STORE.delete(url); + }; + function InternalAccessor() {} + InternalAccessor.getData = function (url) { + return BLOB_STORE.get(url); + }; + URL.InternalAccessor = InternalAccessor; + const searchParamsDescriptor = Object.getOwnPropertyDescriptor( + URL.prototype, + "searchParams" + ); + if (!searchParamsDescriptor || searchParamsDescriptor.configurable) { + Object.defineProperty(URL.prototype, "searchParams", { + get() { + if (this._searchParams == null) { + this._searchParams = new URLSearchParams(this.search); + Object.defineProperty(this._searchParams, "_url", { + enumerable: false, + writable: false, + value: this, + }); + this._searchParams._append = this._searchParams.append; + this._searchParams.append = function (name, value) { + this._append(name, value); + this._url.search = this.toString(); + }; + this._searchParams._delete = this._searchParams.delete; + this._searchParams.delete = function (name) { + this._delete(name); + this._url.search = this.toString(); + }; + this._searchParams._set = this._searchParams.set; + this._searchParams.set = function (name, value) { + this._set(name, value); + this._url.search = this.toString(); + }; + this._searchParams._sort = this._searchParams.sort; + this._searchParams.sort = function () { + this._sort(); + this._url.search = this.toString(); + }; + } + return this._searchParams; + }, + }); + } + } + + const pendingUnhandledRejections = []; + const hasBeenNotifiedProperty = new WeakMap(); + function emitPendingUnhandledRejections() { + while (pendingUnhandledRejections.length > 0) { + var promise = pendingUnhandledRejections.shift(); + var reason = pendingUnhandledRejections.shift(); + if ( + hasBeenNotifiedProperty.get(promise) === false && + globalThis.__onUncaughtError + ) { + globalThis.__onUncaughtError(reason); + } + } + } + + function unhandledPromise(promise, reason) { + pendingUnhandledRejections.push(promise, reason); + __ns__setTimeout(() => { + emitPendingUnhandledRejections(); + }, 1); + } + + function handledPromise(promise) { + const hasBeenNotified = hasBeenNotifiedProperty.get(promise); + if (hasBeenNotified != undefined) { + hasBeenNotifiedProperty.delete(promise); + } + } + + function makeRejectionError(reason) { + const stringValue = Object.prototype.toString.call(reason); + if ( + stringValue === "[object Error]" || + reason instanceof Error || + typeof reason === "object" + ) { + reason.message = `(Unhandled promise rejection): ${reason.message}`; + + if (!reason.stack) { + reason.stack = new Error("").stack; + } + return reason; + } else { + const error = new Error(reason, { + cause: reason, + }); + error.message = `(Unhandled promise rejection): ${error.message}`; + return error; + } + } + + if (globalThis.__engine === "V8") { + // Only report errors for promise rejections that go unhandled. + globalThis.onUnhandledPromiseRejectionTracker = ( + event, + promise, + reason + ) => { + if (event === globalThis.__promiseUnhandledEvent) { + hasBeenNotifiedProperty.set(promise, false); + const error = makeRejectionError(reason); + unhandledPromise(promise, error); + } else { + handledPromise(promise); + } + }; + } else if (globalThis.__engine === "QuickJS") { + globalThis.onUnhandledPromiseRejectionTracker = ( + promise, + reason, + isHandled + ) => { + if (!isHandled) { + hasBeenNotifiedProperty.set(promise, false); + const error = makeRejectionError(reason); + // Preserve original stack trace. + if (!promise.then["[[stack]]"]) { + promise.then["[[stack]]"] = error.stack; + } else { + error.stack = promise.then["[[stack]]"]; + } + unhandledPromise(promise, error); + } else { + handledPromise(promise); + } + }; + } else if (globalThis.__engine === "JSC") { + // JSC's unhandled-rejection callback only fires for rejections that go + // unhandled (there is no "handled later" retraction event), and it is + // invoked with (promise, reason). + globalThis.onUnhandledPromiseRejectionTracker = (promise, reason) => { + hasBeenNotifiedProperty.set(promise, false); + const error = makeRejectionError(reason); + unhandledPromise(promise, error); + }; + } else if (globalThis.__engine === "Hermes") { + HermesInternal.enablePromiseRejectionTracker({ + allRejections: true, + onUnhandled: function (id, error) { + globalThis.__onUncaughtError(makeRejectionError(error)); + }, + }); + } +})(); diff --git a/packages/react-native/android/src/main/cpp/NativeScriptRnInstall.cpp b/packages/react-native/android/src/main/cpp/NativeScriptRnInstall.cpp new file mode 100644 index 000000000..f08e3c5f2 --- /dev/null +++ b/packages/react-native/android/src/main/cpp/NativeScriptRnInstall.cpp @@ -0,0 +1,84 @@ +// The guest entry point: binds the NativeScript runtime to the jsi::Runtime +// React Native owns. +// +// This is the Android counterpart of ffi/objc/hermes/NativeApiJsi.mm, and it is +// deliberately small. Everything that makes NativeScript work -- the metadata +// tree, the object manager, the JNI interop -- is the same code the standalone +// runtime compiles; all that differs is who created the runtime it installs +// into. See NativeScript/runtime/android/jsi/EngineHost.h. + +#include +#include + +#include + +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include "Runtime.h" + +using namespace tns; + +extern "C" JNIEXPORT void JNICALL Java_com_tns_Runtime_attachNativeScript( + JNIEnv* env, jobject obj, jint runtimeId, jstring metadataPath, jlong hostRuntimePtr, + jboolean verboseLoggingEnabled, jint maxLogcatObjectSize, jboolean forceLog, + jstring bootstrapScript) { + try { + if (hostRuntimePtr == 0) { + throw NativeScriptException( + "No JavaScript runtime was provided. NativeScript needs the host's " + "jsi::Runtime, which React Native exposes through its " + "JavaScriptContextHolder."); + } + + auto* hostRuntime = reinterpret_cast(hostRuntimePtr); + std::string metadataDir = ArgConverter::jstringToString(metadataPath); + + Runtime* runtime = Runtime::Attach(env, obj, runtimeId, *hostRuntime, metadataDir, + verboseLoggingEnabled == JNI_TRUE, maxLogcatObjectSize, + forceLog == JNI_TRUE); + + // ts_helpers.js. The standalone runtime runs this as its first module; + // there is no module loader here, so the embedder reads it out of the + // library's assets and hands the source over. Without it there is no + // __extends, no JavaProxy and -- the one that matters most -- no + // __createNativeProxy, which every Java object handed to JS goes through. + if (bootstrapScript != nullptr) { + std::string source = ArgConverter::jstringToString(bootstrapScript); + if (!source.empty()) { + JSScope scope(runtime->GetEngineHost()); + runtime->GetEngineHost()->ExecuteScript(source, "internal/ts_helpers.js"); + } + } + } catch (NativeScriptException& e) { + e.ReThrowToJava(nullptr); + } catch (std::exception& e) { + NativeScriptException nsEx(std::string("Error: c++ exception: ") + e.what()); + nsEx.ReThrowToJava(nullptr); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJava(nullptr); + } +} + +extern "C" JNIEXPORT void JNICALL Java_com_tns_Runtime_detachNativeScript(JNIEnv*, jobject, + jint runtimeId) { + try { + Runtime::DisposeRuntime(Runtime::GetRuntime(runtimeId)); + } catch (NativeScriptException& e) { + e.ReThrowToJava(nullptr); + } catch (std::exception& e) { + NativeScriptException nsEx(std::string("Error: c++ exception: ") + e.what()); + nsEx.ReThrowToJava(nullptr); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJava(nullptr); + } +} + +extern "C" JNIEXPORT jstring JNICALL +Java_org_nativescript_reactnative_NativeScriptNativeApiModule_nativeRuntimeBackend(JNIEnv* env, + jobject) { + // What the *engine layer* is, not which VM sits underneath: a guest build + // talks jsi and cannot tell Hermes from JSC, and does not need to. + return env->NewStringUTF("jsi"); +} diff --git a/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptLogger.java b/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptLogger.java new file mode 100644 index 000000000..4e4e24555 --- /dev/null +++ b/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptLogger.java @@ -0,0 +1,45 @@ +package org.nativescript.reactnative; + +import android.util.Log; + +import com.tns.Logger; + +/** + * Routes the runtime's diagnostics to logcat. + * + *

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

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

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

Note this is the parent: MetadataBuilder appends "/metadata" + * to whatever it is handed, so the .dat files are staged one level below. + */ + @Override + public String defaultMetadataPath() { + return new File(reactContext.getApplicationContext().getFilesDir(), "nativescript") + .getAbsolutePath(); + } + + @Override + public String getRuntimeBackend() { + return nativeRuntimeBackend(); + } + + @Override + public boolean __writeTestMarker(String content) { + return writeMarker("nativescript-test-marker.txt", content); + } + + @Override + public String __readTestMarker() { + return readMarker("nativescript-test-marker.txt"); + } + + // A file of its own, as on iOS: the smoke marker above is rewritten on every + // install, which a reload would do before the new JS reads its phase back. + @Override + public boolean __writeReloadPhaseMarker(String content) { + return writeMarker("nativescript-reload-phase-marker.txt", content); + } + + @Override + public String __readReloadPhaseMarker() { + return readMarker("nativescript-reload-phase-marker.txt"); + } + + private boolean writeMarker(String name, String content) { + try { + File marker = new File(reactContext.getApplicationContext().getFilesDir(), name); + try (OutputStream out = new FileOutputStream(marker)) { + out.write(content.getBytes("UTF-8")); + } + return true; + } catch (IOException e) { + return false; + } + } + + private String readMarker(String name) { + File marker = new File(reactContext.getApplicationContext().getFilesDir(), name); + try { + return marker.exists() ? readAll(marker) : ""; + } catch (IOException e) { + return ""; + } + } + + private static Runtime attachRuntime(Context context, long runtimePtr, String metadataPath, + String bootstrapScript) { + File filesDir = context.getFilesDir(); + File dexDir = new File(filesDir, "nativescript-dex"); + if (!dexDir.exists() && !dexDir.mkdirs()) { + throw new RuntimeException("Could not create " + dexDir.getAbsolutePath()); + } + + Logger logger = new NativeScriptLogger(isDebuggable(context)); + + return Runtime.attachToHostRuntime( + logger, + context.getPackageName(), + context.getApplicationInfo().nativeLibraryDir, + filesDir, + context.getClassLoader(), + dexDir, + /* dexThumb */ buildStamp(context), + isDebuggable(context), + runtimePtr, + metadataPath, + bootstrapScript); + } + + /** + * Reads one of this library's own assets. + * + *

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

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

The reader takes a directory of files, and assets inside an APK are not + * files. Extraction is keyed on the build stamp so a rebuild that regenerates + * metadata does not keep serving the previous build's copy. + */ + private static void stageMetadata(Context context, File root) throws IOException { + File stamp = new File(root, ".version"); + String version = buildStamp(context); + + if (stamp.exists() && version.equals(readAll(stamp))) { + return; + } + + // MetadataBuilder::BuildMetadata appends "/metadata" to the path it is + // given, so the files go one level below the directory we report. + File target = new File(root, "metadata"); + if (!target.exists() && !target.mkdirs()) { + throw new IOException("Could not create " + target.getAbsolutePath()); + } + + for (String name : METADATA_FILES) { + try (InputStream in = context.getAssets().open(ASSET_METADATA_DIR + "/" + name); + OutputStream out = new FileOutputStream(new File(target, name))) { + byte[] buffer = new byte[64 * 1024]; + int read; + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + } + } + + try (OutputStream out = new FileOutputStream(stamp)) { + out.write(version.getBytes("UTF-8")); + } + } + + private static String readAll(File file) throws IOException { + byte[] data = new byte[(int) file.length()]; + try (InputStream in = new java.io.FileInputStream(file)) { + int read = 0; + while (read < data.length) { + int n = in.read(data, read, data.length - read); + if (n < 0) break; + read += n; + } + } + return new String(data, "UTF-8"); + } +} diff --git a/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptNativeApiPackage.java b/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptNativeApiPackage.java new file mode 100644 index 000000000..443c1b570 --- /dev/null +++ b/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptNativeApiPackage.java @@ -0,0 +1,42 @@ +package org.nativescript.reactnative; + +import androidx.annotation.Nullable; + +import com.facebook.react.BaseReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.module.model.ReactModuleInfo; +import com.facebook.react.module.model.ReactModuleInfoProvider; + +import java.util.HashMap; +import java.util.Map; + +/** Autolinking entry point for @nativescript/react-native. */ +public class NativeScriptNativeApiPackage extends BaseReactPackage { + + @Nullable + @Override + public NativeModule getModule(String name, ReactApplicationContext reactContext) { + if (NativeScriptNativeApiModule.NAME.equals(name)) { + return new NativeScriptNativeApiModule(reactContext); + } + return null; + } + + @Override + public ReactModuleInfoProvider getReactModuleInfoProvider() { + return () -> { + Map infos = new HashMap<>(); + infos.put( + NativeScriptNativeApiModule.NAME, + new ReactModuleInfo( + NativeScriptNativeApiModule.NAME, + NativeScriptNativeApiModule.NAME, + /* canOverrideExistingModule */ false, + /* needsEagerInit */ false, + /* isCxxModule */ false, + /* isTurboModule */ true)); + return infos; + }; + } +} diff --git a/packages/react-native/cli/configure-android.js b/packages/react-native/cli/configure-android.js new file mode 100644 index 000000000..477d398f5 --- /dev/null +++ b/packages/react-native/cli/configure-android.js @@ -0,0 +1,91 @@ +const fs = require('fs'); +const path = require('path'); + +const APPLY_LINE = + 'apply from: "../../node_modules/@nativescript/react-native/android/nativescript.gradle"'; + +const TYPINGS = ['types/android/android.d.ts', 'types/android/android-declarations.d.ts']; + +/** + * Adds the NativeScript Gradle script to the app module. + * + * Autolinking brings in the runtime itself, but metadata and typings are + * generated from the *app's* resolved classpath, so they have to be tasks in + * the app project rather than in the library. + */ +function ensureGradleScript(projectRoot) { + const buildGradle = path.join(projectRoot, 'android', 'app', 'build.gradle'); + if (!fs.existsSync(buildGradle)) { + return { changed: false, reason: 'android/app/build.gradle was not found' }; + } + + let source = fs.readFileSync(buildGradle, 'utf8'); + if (source.includes('@nativescript/react-native/android/nativescript.gradle')) { + return { changed: false, reason: 'already applied' }; + } + + // After the React plugin, whose applicationVariants the script hooks into. + const reactPlugin = /apply plugin: ["']com\.facebook\.react["']\s*\n/; + if (reactPlugin.test(source)) { + source = source.replace(reactPlugin, (match) => `${match}${APPLY_LINE}\n`); + } else { + source = `${APPLY_LINE}\n${source}`; + } + + fs.writeFileSync(buildGradle, source); + return { changed: true }; +} + +/** Points tsconfig at the generated declarations, if they are being generated. */ +function ensureTypings(projectRoot) { + const tsconfigPath = path.join(projectRoot, 'tsconfig.json'); + if (!fs.existsSync(tsconfigPath)) { + return { changed: false, reason: 'tsconfig.json was not found' }; + } + + const source = fs.readFileSync(tsconfigPath, 'utf8'); + let tsconfig; + try { + tsconfig = JSON.parse(source); + } catch (e) { + // tsconfig.json permits comments; rewriting it would lose them, and a + // hand-edited config is likelier to be deliberate than stale. + return { changed: false, reason: 'tsconfig.json is not plain JSON; add the types manually' }; + } + + const include = Array.isArray(tsconfig.include) ? tsconfig.include.slice() : []; + const missing = TYPINGS.filter((entry) => !include.includes(entry)); + if (missing.length === 0) { + return { changed: false, reason: 'already referenced' }; + } + + tsconfig.include = include.concat(missing); + fs.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`); + return { changed: true }; +} + +function configureAndroid(projectRoot) { + const gradle = ensureGradleScript(projectRoot); + const typings = ensureTypings(projectRoot); + + if (gradle.changed) { + console.log('Added the NativeScript Gradle script to android/app/build.gradle.'); + } else { + console.log(`Android Gradle script: ${gradle.reason}.`); + } + + if (typings.changed) { + console.log('Added the Android typings to tsconfig.json.'); + } else { + console.log(`Android typings: ${typings.reason}.`); + } + + return { gradle, typings }; +} + +module.exports = { + APPLY_LINE, + configureAndroid, + ensureGradleScript, + ensureTypings, +}; diff --git a/packages/react-native/cli/configure.js b/packages/react-native/cli/configure.js index dcf1be943..dec3586e1 100755 --- a/packages/react-native/cli/configure.js +++ b/packages/react-native/cli/configure.js @@ -8,6 +8,7 @@ const { ensureMetadataConfig, normalizeMetadataOptions, } = require("../plugin/withNativeScriptReactNative"); +const { configureAndroid } = require("./configure-android"); function readJson(filePath) { if (!fs.existsSync(filePath)) { @@ -23,8 +24,12 @@ function projectRoot() { function printNextSteps() { console.log("NativeScript React Native configuration checked."); console.log("Next steps:"); - console.log(" cd ios"); - console.log(" RCT_NEW_ARCH_ENABLED=1 USE_HERMES=1 pod install"); + console.log(" iOS : cd ios && RCT_NEW_ARCH_ENABLED=1 USE_HERMES=1 pod install"); + console.log(" Android : npx react-native run-android"); + console.log(""); + console.log(" Android typings are written to types/android/ on the first build"); + console.log(" (opt out with -PnsGenerateTypings=false). To generate them on their own:"); + console.log(" cd android && ./gradlew generateNativeScriptTypingsDebug"); } function configure(argv = process.argv.slice(2)) { @@ -87,6 +92,8 @@ function configure(argv = process.argv.slice(2)) { } } + configureAndroid(root); + printNextSteps(); } diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 20c7373e7..8d66c400c 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -7,11 +7,13 @@ "React Native", "TurboModule", "JSI", - "iOS" + "iOS", + "Android", + "Java" ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/runtimes", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/react-native" }, "author": { @@ -35,6 +37,7 @@ "plugin", "src", "types", + "android", "ios", "metadata", "native-api", @@ -71,5 +74,11 @@ "NativeScriptNativeApi": "NativeScriptNativeApiModuleProvider" } } + }, + "dependencies": { + "@babel/parser": "^7.17.3", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0", + "split": "^1.0.1" } } diff --git a/packages/react-native/plugin/withNativeScriptReactNative.js b/packages/react-native/plugin/withNativeScriptReactNative.js index 0293a1a4e..ef893b75c 100644 --- a/packages/react-native/plugin/withNativeScriptReactNative.js +++ b/packages/react-native/plugin/withNativeScriptReactNative.js @@ -11,6 +11,9 @@ const DEFAULTS = { const BABEL_PLUGIN = "@nativescript/react-native/babel-plugin"; const WORKLETS_BABEL_PLUGIN = "react-native-worklets/plugin"; +const EXPO_BABEL_PRESET = "babel-preset-expo"; +// babel-preset-expo adds its own worklets plugin, without strictGlobal. +const EXPO_BABEL_PRESET_ENTRY = `[${JSON.stringify(EXPO_BABEL_PRESET)}, { worklets: false }]`; const METADATA_CONFIG_FILE = "nativescript.react-native.json"; const METRO_CONFIG_MARKER = "@nativescript/react-native/metro-config"; @@ -18,18 +21,23 @@ function readBoolean(value, fallback) { return typeof value === "boolean" ? value : fallback; } +function stringList(value) { + return Array.isArray(value) ? value.filter((item) => typeof item === "string") : undefined; +} + function normalizeMetadataOptions(options = {}) { const metadata = options.metadata || {}; - return { - includePods: Array.isArray(metadata.includePods) - ? metadata.includePods.filter((value) => typeof value === "string") - : [], - includeSystemFrameworks: Array.isArray(metadata.includeSystemFrameworks) - ? metadata.includeSystemFrameworks.filter( - (value) => typeof value === "string", - ) - : [], + const normalized = { + includePods: stringList(metadata.includePods) || [], + includeSystemFrameworks: stringList(metadata.includeSystemFrameworks) || [], }; + // Omitted unless given, so ensureMetadataConfig keeps an `android` block + // written by hand into nativescript.react-native.json. + const androidInclude = stringList(metadata.android && metadata.android.include); + if (androidInclude) { + normalized.android = {include: androidInclude}; + } + return normalized; } function normalizeOptions(options = {}) { @@ -67,11 +75,16 @@ function ensureMetadataConfig(projectRoot, metadataOptions) { } } + const existingReactNative = existing.reactNative || {}; + const existingMetadata = + existingReactNative.metadata && typeof existingReactNative.metadata === "object" + ? existingReactNative.metadata + : {}; const merged = { ...existing, reactNative: { - ...(existing.reactNative || {}), - metadata: metadataOptions, + ...existingReactNative, + metadata: {...existingMetadata, ...metadataOptions}, }, }; const nextSource = `${JSON.stringify(merged, null, 2)}\n`; @@ -84,10 +97,27 @@ function ensureMetadataConfig(projectRoot, metadataOptions) { return merged; } +// Without strictGlobal the worklets plugin captures every undeclared name into +// the worklet's closure on the JS thread when the worklet is created, where +// NativeScript's classes (UIView, UILabel, ...) do not exist yet. function workletsBabelEntry(bundleMode) { - return bundleMode - ? `[${JSON.stringify(WORKLETS_BABEL_PLUGIN)}, { bundleMode: true, strictGlobal: true }]` - : JSON.stringify(WORKLETS_BABEL_PLUGIN); + const options = bundleMode + ? "{ bundleMode: true, strictGlobal: true }" + : "{ strictGlobal: true }"; + return `[${JSON.stringify(WORKLETS_BABEL_PLUGIN)}, ${options}]`; +} + +function escapeRegExp(value) { + return value.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&"); +} + +// Replaces `'name'` written as a bare array element with `entry`. Elements +// already given options (`['name', {...}]`) are left alone. +function replaceBareEntry(source, name, entry) { + const bare = new RegExp( + `([\\[,]\\s*)(['"])${escapeRegExp(name)}\\2(?=\\s*(?:,(?!\\s*\\{)|\\]))`, + ); + return source.replace(bare, (match, prefix) => `${prefix}${entry}`); } function ensureBabelPlugin(projectRoot, options = {}) { @@ -97,10 +127,7 @@ function ensureBabelPlugin(projectRoot, options = {}) { options.bundleMode, DEFAULTS.workletsBundleMode, ); - const babelPluginEntries = [ - JSON.stringify(BABEL_PLUGIN), - workletsBabelEntry(bundleMode), - ]; + const workletsEntry = workletsBabelEntry(bundleMode); const candidates = ["babel.config.js", "babel.config.cjs"]; let babelConfigPath = candidates .map((candidate) => path.join(projectRoot, candidate)) @@ -114,8 +141,8 @@ function ensureBabelPlugin(projectRoot, options = {}) { "module.exports = function(api) {", " api.cache(true);", " return {", - " presets: ['babel-preset-expo'],", - ` plugins: [${babelPluginEntries.join(", ")}],`, + ` presets: [${EXPO_BABEL_PRESET_ENTRY}],`, + ` plugins: [${JSON.stringify(BABEL_PLUGIN)}, ${workletsEntry}],`, " };", "};", "", @@ -124,55 +151,67 @@ function ensureBabelPlugin(projectRoot, options = {}) { return; } - let source = fs.readFileSync(babelConfigPath, "utf8"); - if (bundleMode && source.includes(WORKLETS_BABEL_PLUGIN)) { - if (!source.includes("bundleMode")) { - const simplePlugin = /(["'])react-native-worklets\/plugin\1/; - source = source.replace(simplePlugin, workletsBabelEntry(true)); - } - } - + const original = fs.readFileSync(babelConfigPath, "utf8"); + let source = original; const missingPlugins = [BABEL_PLUGIN, WORKLETS_BABEL_PLUGIN].filter( (plugin) => !source.includes(plugin), ); - if (missingPlugins.length === 0) { - fs.writeFileSync(babelConfigPath, source); - return; + if (missingPlugins.length > 0) { + const pluginEntry = + missingPlugins + .map((plugin) => + plugin === WORKLETS_BABEL_PLUGIN + ? workletsEntry + : JSON.stringify(plugin), + ) + .join(", ") + ", "; + if (/plugins\s*:\s*\[/.test(source)) { + source = source.replace( + /plugins\s*:\s*\[/, + (match) => `${match}${pluginEntry}`, + ); + } else if (/return\s*\{/.test(source)) { + source = source.replace( + /return\s*\{/, + (match) => `${match}\n plugins: [${pluginEntry}],`, + ); + } else if (/module\.exports\s*=\s*\{/.test(source)) { + source = source.replace( + /module\.exports\s*=\s*\{/, + (match) => `${match}\n plugins: [${pluginEntry}],`, + ); + } else if (/export\s+default\s+\{/.test(source)) { + source = source.replace( + /export\s+default\s+\{/, + (match) => `${match}\n plugins: [${pluginEntry}],`, + ); + } else { + source += `\n// @nativescript/react-native: add ${missingPlugins.map((plugin) => `'${plugin}'`).join(" and ")} to your Babel plugins.\n`; + } } - const pluginEntry = - missingPlugins - .map((plugin) => - plugin === WORKLETS_BABEL_PLUGIN - ? workletsBabelEntry(bundleMode) - : JSON.stringify(plugin), - ) - .join(", ") + ", "; - if (/plugins\s*:\s*\[/.test(source)) { - source = source.replace( - /plugins\s*:\s*\[/, - (match) => `${match}${pluginEntry}`, - ); - } else if (/return\s*\{/.test(source)) { - source = source.replace( - /return\s*\{/, - (match) => `${match}\n plugins: [${pluginEntry}],`, - ); - } else if (/module\.exports\s*=\s*\{/.test(source)) { - source = source.replace( - /module\.exports\s*=\s*\{/, - (match) => `${match}\n plugins: [${pluginEntry}],`, - ); - } else if (/export\s+default\s+\{/.test(source)) { - source = source.replace( - /export\s+default\s+\{/, - (match) => `${match}\n plugins: [${pluginEntry}],`, - ); - } else { - source += `\n// @nativescript/react-native: add ${missingPlugins.map((plugin) => `'${plugin}'`).join(" and ")} to your Babel plugins.\n`; + source = replaceBareEntry(source, WORKLETS_BABEL_PLUGIN, workletsEntry); + source = replaceBareEntry(source, EXPO_BABEL_PRESET, EXPO_BABEL_PRESET_ENTRY); + + if (source.includes(WORKLETS_BABEL_PLUGIN)) { + const missing = []; + if (!/strictGlobal\s*:\s*true/.test(source)) { + missing.push("strictGlobal: true"); + } + if (bundleMode && !/bundleMode\s*:\s*true/.test(source)) { + missing.push("bundleMode: true"); + } + if (missing.length > 0) { + console.warn( + `@nativescript/react-native: pass { ${missing.join(", ")} } to '${WORKLETS_BABEL_PLUGIN}' ` + + `in ${path.basename(babelConfigPath)}, or worklets that name native classes fail as their module loads.`, + ); + } } - fs.writeFileSync(babelConfigPath, source); + if (source !== original) { + fs.writeFileSync(babelConfigPath, source); + } } function ensureMetroConfig(projectRoot) { @@ -216,16 +255,14 @@ function withNativeScriptTransforms(config, bundleMode) { return config; } - return withDangerousMod(config, [ - "ios", - async (modConfig) => { - ensureBabelPlugin(modConfig.modRequest.projectRoot, { bundleMode }); - if (bundleMode) { - ensureMetroConfig(modConfig.modRequest.projectRoot); - } - return modConfig; - }, - ]); + const mod = async (modConfig) => { + ensureBabelPlugin(modConfig.modRequest.projectRoot, { bundleMode }); + if (bundleMode) { + ensureMetroConfig(modConfig.modRequest.projectRoot); + } + return modConfig; + }; + return withDangerousMod(withDangerousMod(config, ["ios", mod]), ["android", mod]); } function withNativeScriptMetadata(config, metadataOptions) { @@ -236,13 +273,42 @@ function withNativeScriptMetadata(config, metadataOptions) { return config; } - return withDangerousMod(config, [ - "ios", - async (modConfig) => { - ensureMetadataConfig(modConfig.modRequest.projectRoot, metadataOptions); - return modConfig; - }, - ]); + const mod = async (modConfig) => { + ensureMetadataConfig(modConfig.modRequest.projectRoot, metadataOptions); + return modConfig; + }; + return withDangerousMod(withDangerousMod(config, ["ios", mod]), ["android", mod]); +} + +/** + * Applies the NativeScript Gradle script to the app module under prebuild. + * + * Autolinking already brings in the runtime; what this adds are the metadata and + * typings tasks, which read the app's own resolved classpath and so have to live + * in the app project. + */ +function withNativeScriptAndroidGradle(config) { + let withAppBuildGradle; + try { + ({withAppBuildGradle} = require('expo/config-plugins')); + } catch (error) { + return config; + } + + return withAppBuildGradle(config, (modConfig) => { + const {APPLY_LINE} = require('../cli/configure-android'); + let contents = modConfig.modResults.contents; + + if (!contents.includes('@nativescript/react-native/android/nativescript.gradle')) { + const reactPlugin = /apply plugin: ["']com\.facebook\.react["']\s*\n/; + contents = reactPlugin.test(contents) + ? contents.replace(reactPlugin, (match) => `${match}${APPLY_LINE}\n`) + : `${APPLY_LINE}\n${contents}`; + modConfig.modResults.contents = contents; + } + + return modConfig; + }); } function withNativeScriptReactNative(config, options) { @@ -265,6 +331,7 @@ function withNativeScriptReactNative(config, options) { config = withNativeScriptTransforms(config, normalized.workletsBundleMode); } config = withNativeScriptMetadata(config, normalized.metadata); + config = withNativeScriptAndroidGradle(config); return config; } @@ -277,5 +344,6 @@ module.exports.ensureMetroConfig = ensureMetroConfig; module.exports.BABEL_PLUGIN = BABEL_PLUGIN; module.exports.WORKLETS_BABEL_PLUGIN = WORKLETS_BABEL_PLUGIN; module.exports.ensureMetadataConfig = ensureMetadataConfig; +module.exports.withNativeScriptAndroidGradle = withNativeScriptAndroidGradle; module.exports.normalizeMetadataOptions = normalizeMetadataOptions; module.exports.pkg = pkg; diff --git a/packages/react-native/react-native.config.js b/packages/react-native/react-native.config.js index 22b2e13d5..c43d86754 100644 --- a/packages/react-native/react-native.config.js +++ b/packages/react-native/react-native.config.js @@ -1,7 +1,12 @@ module.exports = { dependency: { platforms: { - android: null, + android: { + sourceDir: "android", + packageImportPath: + "import org.nativescript.reactnative.NativeScriptNativeApiPackage;", + packageInstance: "new NativeScriptNativeApiPackage()", + }, }, }, }; diff --git a/packages/react-native/src/NativeClass.d.ts b/packages/react-native/src/NativeClass.d.ts new file mode 100644 index 000000000..2a16bd9de --- /dev/null +++ b/packages/react-native/src/NativeClass.d.ts @@ -0,0 +1,48 @@ +/** + * `@NativeClass` — the marker every class that extends a native class carries. + * + * Implemented in plain JavaScript (NativeClass.js) so that it is identical + * under every bundler and needs no NativeScript-specific transform; these are + * the types for it. + */ + +export type NativeClassOptions = { + /** + * Fully-qualified name for the generated Java class, e.g. + * "com.example.Ticker". Taken verbatim by the runtime, which is what allows + * the static binding generator to emit the class at build time. Without it + * the class is keyed on the call site and generated on the device. + */ + name?: string; + + /** + * Java interfaces the generated class should implement, in addition to + * whatever it extends. A JavaScript class has one base, so this is the only + * way to express "implements Runnable and Callable". + */ + interfaces?: unknown[]; +}; + +type AnyClass = { new (...args: any[]): any; prototype: any }; + +/** Set on every decorated class. */ +export declare const NATIVE_CLASS: "__nsNativeClass"; + +export declare function NativeClass(target: T): T; +export declare function NativeClass( + target: T, + context: unknown, +): T; +export declare function NativeClass( + options?: NativeClassOptions, +): (target: T, context?: unknown) => T; + +/** Equivalent to `@NativeClass({ interfaces })`. */ +export declare function Interfaces( + interfaces: unknown[], +): (target: T, context?: unknown) => T; + +/** `@JavaProxy(name)` — equivalent to `@NativeClass({ name })`. */ +export declare function JavaProxy( + name: string, +): (target: T, context?: unknown) => T; diff --git a/packages/react-native/src/NativeClass.js b/packages/react-native/src/NativeClass.js new file mode 100644 index 000000000..c5a3f61ed --- /dev/null +++ b/packages/react-native/src/NativeClass.js @@ -0,0 +1,186 @@ +/** + * `@NativeClass` — the marker every class that extends a native class carries. + * + * This is a plain runtime decorator. It needs no NativeScript-specific + * transformer, reads nothing at build time, and behaves the same under Babel, + * SWC, tsc or anything else: all it ever receives is the class object the + * language hands a decorator, and all it ever does is annotate it before + * handing it back. + * + * Both decorator dialects are supported, because which one a project gets + * depends on its toolchain rather than on its code: + * + * legacy (TypeScript `experimentalDecorators`, Babel legacy) + * -> called as (target) + * standard (TC39 stage 3, Babel/SWC 2023-05, TypeScript 5) + * -> called as (value, context) + * + * and both the bare and the called form: + * + * @NativeClass + * class Foo extends java.lang.Object {} + * + * @NativeClass() + * class Foo extends java.lang.Object {} + * + * @NativeClass({ interfaces: [java.lang.Runnable, java.util.concurrent.Callable] }) + * class Foo extends java.lang.Object {} + * + * The interfaces list is the part that carries weight: a JavaScript class has + * one base, so implementing several Java interfaces cannot be expressed in the + * `extends` clause. The runtime reads `prototype.interfaces` when it generates + * the proxy class, which is exactly what this sets. + */ + +/** Set on every decorated class, so the runtime and tooling can recognise one. */ +const NATIVE_CLASS = "__nsNativeClass"; + +/** A native base carries the brand the runtime stamps on its own extend(). */ +function nativeExtendOf(base) { + const extend = base && base.extend; + return typeof extend === "function" && extend.__isNativeExtend__ === true + ? extend + : undefined; +} + +function annotate(target, options) { + if (typeof target !== "function") { + throw new TypeError( + "@NativeClass can only be applied to a class declaration.", + ); + } + + Object.defineProperty(target, NATIVE_CLASS, { + value: true, + enumerable: false, + configurable: true, + }); + + const interfaces = options.interfaces; + if (interfaces && interfaces.length > 0) { + // On the prototype: the prototype is the implementation object the runtime + // is handed when it generates the proxy, and it reads `interfaces` off it. + Object.defineProperty(target.prototype, "interfaces", { + value: interfaces, + enumerable: false, + configurable: true, + writable: true, + }); + } + + const explicitName = options.name; + if (explicitName !== undefined && explicitName.indexOf(".") < 0) { + throw new Error( + `@NativeClass name "${explicitName}" must be fully qualified, e.g. ` + + `"com.example.${explicitName}". The name is the generated Java class's ` + + `name, and the static binding generator matches on it.`, + ); + } + + const base = Object.getPrototypeOf(target); + const extend = nativeExtendOf(base); + if (!extend) { + // Not extending a native class. Nothing to wire up, and no reason to + // complain: @NativeClass on a plain class is harmless. + return target; + } + + // Generate the Java class now. + // + // This is the whole reason the decorator exists as more than a marker. A + // downlevelled class calls the runtime's __extends helper, which does this + // lazily; a real `class X extends Native {}` does not call any helper at all, + // so without this the JS side works -- overrides resolve through the + // prototype chain -- while Java sees an unmodified base class and never + // dispatches an override back into JS. + // + // Passing the prototype as the implementation object and `true` for the + // TypeScript-extend form is exactly what ts_helpers does. + // A fully-qualified name is taken verbatim by the runtime, with no call-site + // location mixed in. That is what makes it reproducible at build time: the + // static binding generator can emit a Java class under exactly this name and + // the runtime will find it instead of generating one on the device. + // + // Without a name the generated class is keyed on the call site, which under a + // bundler is a position in the bundle rather than in your source -- so nothing + // ahead of time can predict it, and the class is generated at runtime. + const extended = explicitName + ? extend.call(base, explicitName, target.prototype) + : extend.call(base, target.name, target.prototype, true); + + // Re-point the class at the generated one. `super()` resolves through the + // constructor's own prototype, so this is what makes construction go via the + // runtime -- and because it is still `target` that is being constructed, the + // class's constructor body and field initialisers run on the object the + // runtime returns, exactly as ES semantics require. + // + // Only the constructor. The instance chain is already right: the runtime uses + // the implementation object it was handed as the generated class's prototype, + // so extended.prototype is target.prototype, and re-pointing that at itself is + // a prototype cycle. + Object.setPrototypeOf(target, extended); + + return target; +} + +/** + * True when `args` is a standard-decorator invocation, i.e. (value, context) + * with a class context, rather than a legacy (target) one or an options object. + */ +function isStandardDecoratorCall(args) { + return ( + args.length >= 2 && + typeof args[0] === "function" && + args[1] != null && + typeof args[1] === "object" && + args[1].kind === "class" + ); +} + +function NativeClass(...args) { + // @NativeClass (standard decorators) + if (isStandardDecoratorCall(args)) { + return annotate(args[0], {}); + } + + // @NativeClass (legacy decorators) + if (args.length === 1 && typeof args[0] === "function") { + return annotate(args[0], {}); + } + + // @NativeClass(options) / @NativeClass() -- both dialects land here, and the + // returned decorator has to accept either shape in turn. + const options = args[0] || {}; + return function decorate(target, _context) { + return annotate(target, options); + }; +} + +/** + * Declares the Java interfaces a class implements. + * + * Equivalent to `@NativeClass({ interfaces })`, kept as a separate decorator + * because that is how NativeScript has always spelled it. + */ +function Interfaces(interfaces) { + return function decorate(target, _context) { + return annotate(target, { interfaces }); + }; +} + +/** + * Names the generated Java class. + * + * `@JavaProxy("com.example.Ticker")` is `@NativeClass({ name })`, spelled the way + * NativeScript has always spelled it. Naming a class is what lets it be + * generated at build time rather than on the device, and it is required for a + * class that has to exist before the app runs -- one referenced from + * AndroidManifest.xml, say. + */ +function JavaProxy(name) { + return function decorate(target, _context) { + return annotate(target, { name }); + }; +} + +module.exports = { NativeClass, Interfaces, JavaProxy, NATIVE_CLASS }; diff --git a/packages/react-native/src/NativeScriptNativeApi.ts b/packages/react-native/src/NativeScriptNativeApi.ts index 5168d0cd5..8b2863e68 100644 --- a/packages/react-native/src/NativeScriptNativeApi.ts +++ b/packages/react-native/src/NativeScriptNativeApi.ts @@ -51,4 +51,17 @@ export interface Spec extends TurboModule { ) => boolean; } -export default TurboModuleRegistry.getEnforcing('NativeScriptNativeApi'); +function loadNativeApi(): Spec { + const nativeApi = TurboModuleRegistry.get('NativeScriptNativeApi'); + if (nativeApi == null) { + throw new Error( + "@nativescript/react-native: the native module 'NativeScriptNativeApi' is not in this app binary. " + + 'The package contains native code, so it cannot run in Expo Go: use a development build ' + + '(npx expo run:ios / npx expo run:android, or EAS Build). ' + + 'If the package was added after the app was built, rebuild the app.', + ); + } + return nativeApi; +} + +export default loadNativeApi(); diff --git a/packages/react-native/src/index.d.ts b/packages/react-native/src/index.d.ts index 55a49eaa5..f97ff8833 100644 --- a/packages/react-native/src/index.d.ts +++ b/packages/react-native/src/index.d.ts @@ -36,6 +36,8 @@ export type NativeApiHost = { getClass?: (name: string) => unknown; getProtocol?: (name: string) => unknown; getEnum?: (name: string) => unknown; + /** C constant names of the enum's members, which installGlobals() defines as globals. */ + getEnumGlobalNames?: (name: string) => string[]; getStruct?: (name: string) => unknown; getUnion?: (name: string) => unknown; [name: string]: unknown; diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index 165968e62..fb4548c6c 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -47,6 +47,7 @@ type NativeApiHost = { getClass?: (name: string) => unknown; getProtocol?: (name: string) => unknown; getEnum?: (name: string) => unknown; + getEnumGlobalNames?: (name: string) => string[]; getStruct?: (name: string) => unknown; getUnion?: (name: string) => unknown; [name: string]: unknown; @@ -689,18 +690,13 @@ export function installGlobals(): boolean { api.getEnum?.(enumName) ?? api[enumName]; defineLazyNativeGlobal(name, resolveEnum); - const enumValue = resolveEnum(name); - if (!enumValue || typeof enumValue !== "object") { - continue; - } - for (const memberName of Object.keys(enumValue)) { - if (/^-?\d+$/.test(memberName)) { - continue; - } - defineLazyNativeGlobal( - memberName, - () => (enumValue as Record)[memberName], - ); + for (const memberName of api.getEnumGlobalNames?.(name) ?? []) { + defineLazyNativeGlobal(memberName, () => { + const enumValue = resolveEnum(name) as + | Record + | undefined; + return enumValue?.[memberName]; + }); } } @@ -1344,3 +1340,8 @@ const NativeScript = { }; export default NativeScript; + +// The marker every class extending a native class carries. A runtime decorator, +// so it works the same regardless of bundler or decorator dialect. +export { NativeClass, Interfaces, JavaProxy, NATIVE_CLASS } from "./NativeClass"; +export type { NativeClassOptions } from "./NativeClass"; diff --git a/packages/react-native/test/config-plugin.test.js b/packages/react-native/test/config-plugin.test.js index d8b6906b5..117a98f55 100644 --- a/packages/react-native/test/config-plugin.test.js +++ b/packages/react-native/test/config-plugin.test.js @@ -113,6 +113,85 @@ withTempProject((projectRoot) => { ); }); +function loadBabelConfig(projectRoot) { + const configPath = path.join(projectRoot, 'babel.config.js'); + delete require.cache[require.resolve(configPath)]; + const exported = require(configPath); + return typeof exported === 'function' ? exported({cache() {}}) : exported; +} + +function findEntry(entries, name) { + return (entries || []).find( + (entry) => entry === name || (Array.isArray(entry) && entry[0] === name), + ); +} + +withTempProject((projectRoot) => { + ensureBabelPlugin(projectRoot, {bundleMode: false}); + const config = loadBabelConfig(projectRoot); + assert.deepStrictEqual(findEntry(config.presets, 'babel-preset-expo'), [ + 'babel-preset-expo', + {worklets: false}, + ]); + assert.deepStrictEqual(findEntry(config.plugins, 'react-native-worklets/plugin'), [ + 'react-native-worklets/plugin', + {strictGlobal: true}, + ]); +}); + +withTempProject((projectRoot) => { + fs.writeFileSync( + path.join(projectRoot, 'babel.config.js'), + [ + 'module.exports = function(api) {', + ' api.cache(true);', + ' return {', + " presets: ['babel-preset-expo'],", + " plugins: ['@nativescript/react-native/babel-plugin', 'react-native-worklets/plugin'],", + ' };', + '};', + '', + ].join('\n'), + ); + ensureBabelPlugin(projectRoot, {bundleMode: false}); + ensureBabelPlugin(projectRoot, {bundleMode: false}); + const config = loadBabelConfig(projectRoot); + assert.deepStrictEqual(findEntry(config.presets, 'babel-preset-expo'), [ + 'babel-preset-expo', + {worklets: false}, + ]); + assert.deepStrictEqual(findEntry(config.plugins, 'react-native-worklets/plugin'), [ + 'react-native-worklets/plugin', + {strictGlobal: true}, + ]); + assert.strictEqual(config.plugins.length, 2); +}); + +withTempProject((projectRoot) => { + const source = [ + 'module.exports = {', + " plugins: [['react-native-worklets/plugin', { processNestedWorklets: true }]],", + '};', + '', + ].join('\n'); + fs.writeFileSync(path.join(projectRoot, 'babel.config.js'), source); + const warnings = []; + const warn = console.warn; + console.warn = (message) => warnings.push(message); + try { + ensureBabelPlugin(projectRoot); + } finally { + console.warn = warn; + } + const config = loadBabelConfig(projectRoot); + assert.deepStrictEqual(findEntry(config.plugins, 'react-native-worklets/plugin'), [ + 'react-native-worklets/plugin', + {processNestedWorklets: true}, + ]); + assert.strictEqual(warnings.length, 1); + assert.match(warnings[0], /strictGlobal/); +}); + withTempProject((projectRoot) => { const metadata = normalizeMetadataOptions({ metadata: { @@ -134,4 +213,44 @@ withTempProject((projectRoot) => { }); }); +withTempProject((projectRoot) => { + const configPath = path.join(projectRoot, "nativescript.react-native.json"); + fs.writeFileSync( + configPath, + JSON.stringify({ + reactNative: { + metadata: { + includePods: ["StalePod"], + android: {include: ["com.example.Worker"]}, + }, + }, + }), + ); + ensureMetadataConfig(projectRoot, normalizeMetadataOptions({})); + const config = JSON.parse(fs.readFileSync(configPath, "utf8")); + assert.deepStrictEqual(config.reactNative.metadata, { + includePods: [], + includeSystemFrameworks: [], + android: {include: ["com.example.Worker"]}, + }); +}); + +withTempProject((projectRoot) => { + const configPath = path.join(projectRoot, "nativescript.react-native.json"); + fs.writeFileSync( + configPath, + JSON.stringify({reactNative: {metadata: {android: {include: ["com.example.Old"]}}}}), + ); + ensureMetadataConfig( + projectRoot, + normalizeMetadataOptions({ + metadata: {android: {include: ["com.example.Worker", 7, "com.example.views.*"]}}, + }), + ); + const config = JSON.parse(fs.readFileSync(configPath, "utf8")); + assert.deepStrictEqual(config.reactNative.metadata.android, { + include: ["com.example.Worker", "com.example.views.*"], + }); +}); + console.log("config plugin tests passed"); diff --git a/packages/react-native/test/configure-android.test.js b/packages/react-native/test/configure-android.test.js new file mode 100644 index 000000000..033927f6a --- /dev/null +++ b/packages/react-native/test/configure-android.test.js @@ -0,0 +1,92 @@ +const assert = require('assert'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const { + APPLY_LINE, + ensureGradleScript, + ensureTypings, +} = require('../cli/configure-android'); + +function scratchProject() { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ns-rn-android-')); + fs.mkdirSync(path.join(root, 'android', 'app'), { recursive: true }); + return root; +} + +function writeBuildGradle(root, contents) { + fs.writeFileSync(path.join(root, 'android', 'app', 'build.gradle'), contents); +} + +function readBuildGradle(root) { + return fs.readFileSync(path.join(root, 'android', 'app', 'build.gradle'), 'utf8'); +} + +// The script hooks applicationVariants, so it has to come after the plugin that +// creates them. +{ + const root = scratchProject(); + writeBuildGradle( + root, + 'apply plugin: "com.android.application"\napply plugin: "com.facebook.react"\n\nandroid {\n}\n', + ); + + const result = ensureGradleScript(root); + assert.strictEqual(result.changed, true); + + const lines = readBuildGradle(root).split('\n'); + const reactIndex = lines.findIndex((line) => line.includes('com.facebook.react')); + const applyIndex = lines.findIndex((line) => line.includes('nativescript.gradle')); + assert.ok(applyIndex > reactIndex, 'apply line must follow the React plugin'); +} + +// Running configure twice must not add it twice. +{ + const root = scratchProject(); + writeBuildGradle(root, `apply plugin: "com.facebook.react"\n${APPLY_LINE}\n`); + + const result = ensureGradleScript(root); + assert.strictEqual(result.changed, false); + const occurrences = readBuildGradle(root).split('nativescript.gradle').length - 1; + assert.strictEqual(occurrences, 1); +} + +// A project that does not apply the React plugin still gets the script. +{ + const root = scratchProject(); + writeBuildGradle(root, 'apply plugin: "com.android.application"\n'); + + assert.strictEqual(ensureGradleScript(root).changed, true); + assert.ok(readBuildGradle(root).includes('nativescript.gradle')); +} + +// Typings are added to tsconfig, once. +{ + const root = scratchProject(); + const tsconfigPath = path.join(root, 'tsconfig.json'); + fs.writeFileSync(tsconfigPath, JSON.stringify({ include: ['**/*.ts'] }, null, 2)); + + assert.strictEqual(ensureTypings(root).changed, true); + const include = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8')).include; + assert.ok(include.includes('types/android/android.d.ts')); + assert.ok(include.includes('types/android/android-declarations.d.ts')); + + assert.strictEqual(ensureTypings(root).changed, false); + const after = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8')).include; + assert.strictEqual(after.length, include.length); +} + +// A tsconfig with comments is left alone rather than rewritten and stripped. +{ + const root = scratchProject(); + const tsconfigPath = path.join(root, 'tsconfig.json'); + const original = '{\n // keep me\n "include": ["**/*.ts"]\n}\n'; + fs.writeFileSync(tsconfigPath, original); + + const result = ensureTypings(root); + assert.strictEqual(result.changed, false); + assert.strictEqual(fs.readFileSync(tsconfigPath, 'utf8'), original); +} + +console.log('configure-android tests passed'); diff --git a/packages/react-native/test/native-class.test.js b/packages/react-native/test/native-class.test.js new file mode 100644 index 000000000..52f7dfcd5 --- /dev/null +++ b/packages/react-native/test/native-class.test.js @@ -0,0 +1,158 @@ +const assert = require('assert'); + +// Plain JavaScript on purpose: the decorator has to behave identically under +// every bundler, so it is shipped untranspiled and tested as shipped. +const { + NativeClass, + Interfaces, + JavaProxy, + NATIVE_CLASS, +} = require('../src/NativeClass'); + +/** Stands in for a metadata-backed native class. */ +function makeNativeBase() { + const calls = []; + function Base() {} + Base.prototype.baseMethod = function () { + return 'base'; + }; + + function extend(name, implementationObject, isTypeScriptExtend) { + calls.push({ name, implementationObject, isTypeScriptExtend }); + function Extended() {} + // The runtime uses the implementation object it is handed as the generated + // class's prototype; the decorator relies on that. + Extended.prototype = implementationObject; + return Extended; + } + extend.__isNativeExtend__ = true; + Base.extend = extend; + + return { Base, calls }; +} + +// A plain class is annotated and returned untouched. +{ + class Plain {} + const result = NativeClass(Plain); + assert.strictEqual(result, Plain); + assert.strictEqual(Plain[NATIVE_CLASS], true); +} + +// Called form, no options. +{ + class Plain {} + const result = NativeClass()(Plain); + assert.strictEqual(result, Plain); + assert.strictEqual(Plain[NATIVE_CLASS], true); +} + +// Standard (TC39) decorators pass (value, context). +{ + class Plain {} + const result = NativeClass(Plain, { kind: 'class', name: 'Plain' }); + assert.strictEqual(result, Plain); + assert.strictEqual(Plain[NATIVE_CLASS], true); +} + +// Extending a native base generates the Java class and re-points the +// constructor, so that super() goes through the runtime. +{ + const { Base, calls } = makeNativeBase(); + class Child extends Base { + size() { + return 5; + } + } + + const result = NativeClass(Child); + + assert.strictEqual(result, Child); + assert.strictEqual(calls.length, 1, 'extend() must be called exactly once'); + assert.strictEqual(calls[0].name, 'Child'); + assert.strictEqual(calls[0].implementationObject, Child.prototype); + assert.strictEqual(calls[0].isTypeScriptExtend, true); + assert.notStrictEqual( + Object.getPrototypeOf(Child), + Base, + 'the constructor must be re-pointed at the generated class', + ); + // The instance chain has to stay intact -- re-pointing it too is a cycle. + assert.strictEqual(Child.prototype.size.call(null), 5); +} + +// Interfaces land on the prototype, which is what the runtime reads. +{ + const { Base, calls } = makeNativeBase(); + const IRunnable = {}; + const ICallable = {}; + + class Child extends Base {} + NativeClass({ interfaces: [IRunnable, ICallable] })(Child); + + assert.deepStrictEqual(Child.prototype.interfaces, [IRunnable, ICallable]); + // Set before extend() runs, or the generated class would not declare them. + assert.deepStrictEqual(calls[0].implementationObject.interfaces, [ + IRunnable, + ICallable, + ]); +} + +// The separate Interfaces decorator does the same thing. +{ + const { Base } = makeNativeBase(); + const IRunnable = {}; + class Child extends Base {} + Interfaces([IRunnable])(Child); + assert.deepStrictEqual(Child.prototype.interfaces, [IRunnable]); +} + +// A base with a plain (non-native) static extend must not be mistaken for one. +{ + class NotNative { + static extend() { + throw new Error('must not be called'); + } + } + class Child extends NotNative {} + assert.doesNotThrow(() => NativeClass(Child)); +} + +// Applying it to something that is not a class is a mistake worth reporting. +{ + assert.throws(() => NativeClass()(42), /class declaration/); +} + +// A fully-qualified name goes to the runtime verbatim, with no +// isTypeScriptExtend flag -- that is the form the runtime takes as-is, and the +// only one the static binding generator can predict. +{ + const { Base, calls } = makeNativeBase(); + class Ticker extends Base {} + JavaProxy('com.example.Ticker')(Ticker); + + assert.strictEqual(calls.length, 1); + assert.strictEqual(calls[0].name, 'com.example.Ticker'); + assert.strictEqual(calls[0].isTypeScriptExtend, undefined); +} + +// @NativeClass({ name }) is the same thing. +{ + const { Base, calls } = makeNativeBase(); + class Ticker extends Base {} + NativeClass({ name: 'com.example.Ticker' })(Ticker); + assert.strictEqual(calls[0].name, 'com.example.Ticker'); +} + +// An unqualified name would collide across packages and cannot be generated +// ahead of time; say so rather than producing a class nobody can find. +{ + const { Base } = makeNativeBase(); + class Ticker extends Base {} + assert.throws( + () => JavaProxy('Ticker')(Ticker), + /fully qualified/, + ); +} + +console.log('native-class tests passed'); diff --git a/packages/visionos/package.json b/packages/visionos/package.json index 4c0fc9ae9..8279ffe39 100644 --- a/packages/visionos/package.json +++ b/packages/visionos/package.json @@ -9,7 +9,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/visionos" }, "author": { diff --git a/platforms/.gitignore b/platforms/.gitignore new file mode 100644 index 000000000..46eaaaf1d --- /dev/null +++ b/platforms/.gitignore @@ -0,0 +1,4 @@ + +# Copied out of build-tools/typings by copyTypings. +android.d.ts +android-declarations.d.ts diff --git a/platforms/android/.github/workflows/build_hermes.yml b/platforms/android/.github/workflows/build_hermes.yml deleted file mode 100644 index 75605e37c..000000000 --- a/platforms/android/.github/workflows/build_hermes.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Build with Hermes - -on: workflow_dispatch - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - - name: Install dependencies - run: | - cd test-app/build-tools/jsparser - npm install - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Build with HERMES - run: ./gradlew -Pengine=HERMES - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: release - path: | - ./dist_hermes - - diff --git a/platforms/android/.github/workflows/build_jsc.yml b/platforms/android/.github/workflows/build_jsc.yml deleted file mode 100644 index 6b5626228..000000000 --- a/platforms/android/.github/workflows/build_jsc.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Build with JSC - -on: workflow_dispatch - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - - name: Install dependencies - run: | - cd test-app/build-tools/jsparser - npm install - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Build with JSC - run: ./gradlew -Pengine=JSC - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: release - path: | - ./dist_jsc - - diff --git a/platforms/android/.github/workflows/build_quickjs.yml b/platforms/android/.github/workflows/build_quickjs.yml deleted file mode 100644 index 231820e3b..000000000 --- a/platforms/android/.github/workflows/build_quickjs.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Build with QuickJS - -on: workflow_dispatch - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - - name: Install dependencies - run: | - cd test-app/build-tools/jsparser - npm install - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Build with QUICKJS - run: ./gradlew -Pengine=QUICKJS - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: release - path: | - ./dist_quickjs - - diff --git a/platforms/android/.github/workflows/build_v8.yml b/platforms/android/.github/workflows/build_v8.yml deleted file mode 100644 index d864ba712..000000000 --- a/platforms/android/.github/workflows/build_v8.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Build with V8 - -on: workflow_dispatch - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - - name: Install dependencies - run: | - cd test-app/build-tools/jsparser - npm install - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Build with V8 - run: ./gradlew -Pengine=V8 - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: release - path: | - ./dist_v8 - - diff --git a/platforms/android/.github/workflows/release.yml b/platforms/android/.github/workflows/release.yml deleted file mode 100644 index fe36daed1..000000000 --- a/platforms/android/.github/workflows/release.yml +++ /dev/null @@ -1,219 +0,0 @@ -on: - workflow_dispatch: - inputs: - publish: - description: 'If true, run publish after build (default: false)' - required: false - default: 'false' - run_tests: - description: 'If true, run gradle runtestsAndVerifyResults before builds (default: false)' - required: false - default: 'false' - custom_version: - description: 'Optional custom NPM version (overrides computed version).' - required: false - default: '' - enable_HERMES: - description: 'Enable HERMES in the matrix' - required: false - default: 'true' - enable_V8: - description: 'Enable V8 in the matrix' - required: false - default: 'true' - enable_QUICKS: - description: 'Enable QUICKS in the matrix' - required: false - default: 'true' - enable_JSC: - description: 'Enable JSC in the matrix' - required: false - default: 'true' - -jobs: - set-matrix: - name: Build matrix from inputs - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.emit.outputs.matrix }} - steps: - - name: Emit matrix JSON - id: emit - run: | - set -e - # Build the engine list based on workflow dispatch inputs. - engines=() - if [ "${{ github.event.inputs.enable_HERMES }}" != "false" ]; then engines+=("HERMES"); fi - if [ "${{ github.event.inputs.enable_V8 }}" != "false" ]; then engines+=("V8"); fi - if [ "${{ github.event.inputs.enable_QUICKS }}" != "false" ]; then engines+=("QUICKS"); fi - if [ "${{ github.event.inputs.enable_JSC }}" != "false" ]; then engines+=("JSC"); fi - - # If user disabled everything, fall back to a safe default (all engines). - if [ ${#engines[@]} -eq 0 ]; then - echo "All engines were disabled; defaulting to all engines." - engines=(HERMES V8 QUICKS JSC) - fi - - # Build a JSON array string like ["HERMES","V8"] - json="[" - for e in "${engines[@]}"; do - json="${json}\"${e}\"," - done - json="${json%,}" # remove trailing comma - json="${json}]" - - echo "Computed matrix JSON: $json" - echo "matrix=$json" >> $GITHUB_OUTPUT - - engine: - name: Test · Build · Publish (engine=${{ matrix.engine }}) - needs: set-matrix - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - # fromJson converts the JSON string emitted by set-matrix into the matrix array - engine: ${{ fromJson(needs.set-matrix.outputs.matrix) }} - env: - JS_PARSER_DIR: test-app/build-tools/jsparser - ENGINE: ${{ matrix.engine }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - persist-credentials: true - - - name: Setup Node.js 18 - uses: actions/setup-node@v4 - with: - node-version: 18 - - - name: set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - - name: Restore npm cache for jsparser - uses: actions/cache@v4 - with: - path: | - ~/.npm - ${{ github.workspace }}/${{ env.JS_PARSER_DIR }}/node_modules - key: ${{ runner.os }}-node-${{ hashFiles('test-app/build-tools/jsparser/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - name: Install dependencies (jsparser) - working-directory: ${{ env.JS_PARSER_DIR }} - run: npm ci - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Run runSbgTests (no emulator) for ${{ matrix.engine }} - if: ${{ github.event.inputs.run_tests == 'true' }} - run: | - echo "Running runSbgTests with ENGINE=${ENGINE}" - ./gradlew -Pengine="${ENGINE}" runSbgTests - env: - ENGINE: ${{ matrix.engine }} - - - name: Run runtestsAndVerifyResults inside emulator for ${{ matrix.engine }} - if: ${{ github.event.inputs.run_tests == 'true' }} - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 35 - arch: x86_64 - target: google_apis - emulator-options: -no-window - script: | - echo "Running runtestsAndVerifyResults with ENGINE=${ENGINE}" - ./gradlew -Pengine="${ENGINE}" runtestsAndVerifyResults - env: - ENGINE: ${{ matrix.engine }} - - - name: Build with engine ${{ matrix.engine }} - run: | - echo "Building with ENGINE=${ENGINE}" - ./gradlew -Pengine="${ENGINE}" - env: - ENGINE: ${{ matrix.engine }} - - - name: Publish tarball (or add dist-tag if version already exists) - id: npm_publish - run: | - set -e - eng_lc="${ENG_LC}" - TARBALL="${TARBALL:-dist-${eng_lc}-${NPM_VERSION}.tgz}" - TAG="${eng_lc}-${NPM_VERSION}" - echo "Publishing tarball $TARBALL with npm tag $TAG (package version: $NPM_VERSION)..." - echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > .npmrc - - if npm publish "$TARBALL" --tag "$TAG" --provenance --access public; then - echo "npm publish succeeded for $TARBALL" - echo "PUBLISHED=true" >> $GITHUB_ENV - else - echo "npm publish failed; attempting to add dist-tag pointing to existing version" - npm dist-tag add @nativescript/android@"${NPM_VERSION}" "${TAG}" - echo "DIST_TAG_ADDED=true" >> $GITHUB_ENV - fi - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} - NPM_VERSION: ${{ env.NPM_VERSION }} - ENG_LC: ${{ env.ENG_LC }} - TARBALL: ${{ env.TARBALL }} - - - name: Create and push git tag for engine/version - if: always() - id: tag_release - run: | - set -e - eng_lc="${ENG_LC}" - TAG_NAME="${eng_lc}-v${NPM_VERSION}" - echo "Creating tag $TAG_NAME" - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - if git rev-parse --verify "$TAG_NAME" >/dev/null 2>&1; then - git tag -d "$TAG_NAME" || true - fi - git tag -a "$TAG_NAME" -m "Release ${TAG_NAME}" - git push origin "refs/tags/${TAG_NAME}" || true - echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV - - - name: Update CHANGELOG - id: changelog - uses: requarks/changelog-action@v1 - with: - useGitmojis: false - excludeTypes: build,docs,other,style,chore,perf,doc - token: ${{ secrets.GITHUB_TOKEN }} - tag: v${{ env.TAG_NAME }} - writeToFile: false - - - name: Create GitHub release for engine - uses: actions/create-release@v1 - if: always() - with: - tag_name: ${{ env.TAG_NAME }} - release_name: ${{ env.ENGINE }} ${{ env.NPM_VERSION }} - body: ${{ steps.changelog.outputs.changes }} - draft: false - prerelease: ${{ contains(env.NPM_VERSION, '-dev') }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ENGINE: ${{ matrix.engine }} - NPM_VERSION: ${{ env.NPM_VERSION }} - - - name: Final status print - run: | - echo "Engine: ${ENGINE}" - echo "NPM_VERSION: ${NPM_VERSION}" - echo "Published: ${PUBLISHED:-false}" - echo "Dist-tag added: ${DIST_TAG_ADDED:-false}" - echo "Tag created: ${TAG_NAME:-none}" - env: - PUBLISHED: ${{ env.PUBLISHED }} - DIST_TAG_ADDED: ${{ env.DIST_TAG_ADDED }} - TAG_NAME: ${{ env.TAG_NAME }} \ No newline at end of file diff --git a/platforms/android/README.md b/platforms/android/README.md index 2ab0b21d6..8d8cfc901 100644 --- a/platforms/android/README.md +++ b/platforms/android/README.md @@ -74,20 +74,36 @@ Set the following environment variables: - `ANDROID_HOME` pointing to where you have installed the Android SDK - `ANDROID_NDK_HOME` pointing to the version of the Android NDK needed for this version of NativeScript -Run the following commands to build the runtime: +Run the following commands **from the repository root** -- the Android build +entry points live in `scripts/` alongside the Apple ones: ``` -npm run setup +git submodule update --init --force vendor/quickjs/source_ng +npm run setup-android ``` +The first command fetches the vendored QuickJS-NG source at its pinned commit -- +a deliberate manual step, so nothing moves you off the pin behind your back (see +`vendor/quickjs/patches/README.md`). `npm run setup-android` then applies the +NativeScript patches to it and installs the jsparser build-tool dependencies. + ``` -npm run build +npm run build-android ``` -The command will let you interactively choose the JS engine you want to build with. i.e V8, QuickJS, Hermes or JSC, PrimJS, Static Hermes. +The command will let you interactively choose the JS engine you want to build +with, i.e. V8, QuickJS-NG, Hermes, JSC, PrimJS or Static Hermes. Pass the engine +directly to skip the prompt: + +``` +npm run build-android -- --engine=QUICKJS_NG +``` - The build process includes building of the runtime package (both optimized and with unstripped v8 symbol table), as well as all supplementary tools used for the android builds: metadata-generator, binding-generator, metadata-generator, static-binding-generator -- The result of the build will be in the dist\_[engine] folder. For example if you are building with V8, the result will be in the dist_v8 folder. +- Output goes to the repo-root `dist/` and `build/` folders, next to the Apple build output: + - staged package: `dist/android__/` -- e.g. `dist/android_quickjs_napi/`, `dist/android_v8_13_jsi/` + - tarball: `build/npm-tarballs/nativescript-android__-.tgz` +- `` is the runtime tree the AAR was compiled from, `napi` (default) or `jsi` (`--binding=jsi`). They are two complete parallel runtimes, so each variant gets its own folder. ## How to Run Tests diff --git a/platforms/android/build.gradle b/platforms/android/build.gradle index 074885e4e..d2eab1953 100644 --- a/platforms/android/build.gradle +++ b/platforms/android/build.gradle @@ -20,8 +20,6 @@ def hasNdkVersion = project.hasProperty("ndkVersion") def hasHostObjects = project.hasProperty("useHostObjects") -def asNapiModule = project.hasProperty("asNapiModule") - if (hasNdkVersion) { println "Using NDK version " + ndkVersion } @@ -35,7 +33,24 @@ def generateRegularRuntimePackage = !project.hasProperty("skipUnoptimized") def jsEngine = engine ?: 'V8-10' -def DIST_PATH = "$rootDir/dist_${jsEngine.toLowerCase()}" +// Which runtime tree the AAR was compiled from -- see test-app/runtime/build.gradle. +// It is part of the output name because the two are different runtimes, not two +// flavours of one, and a build of each has to be able to sit on disk at once. +def bindingLayer = project.findProperty("bindingLayer") ?: "napi" + +// QUICKJS_NG is the only quickjs lane left (QUICKJS is an alias for it), so it +// carries the plain name; everything else is just lowercased with '-' -> '_'. +def engineSlug = jsEngine.toLowerCase().replace('-', '_') +if (engineSlug == "quickjs_ng") { + engineSlug = "quickjs" +} + +// Build outputs live in the repo-root dist/ and build/ folders, next to the +// Apple ones, rather than in a platforms/android/dist_ of their own. +def REPO_ROOT = file("$rootDir/../..").canonicalPath +def PACKAGE_VARIANT = "android_${engineSlug}_${bindingLayer}" +def DIST_PATH = "$REPO_ROOT/dist/$PACKAGE_VARIANT" +def TARBALL_PATH = "$REPO_ROOT/build/npm-tarballs" def TEST_APP_PATH = "$rootDir/test-app" def BUILD_TOOLS_PATH = "$TEST_APP_PATH/build-tools" def DIST_FRAMEWORK_PATH = "$DIST_PATH/framework" @@ -225,10 +240,6 @@ def getAssembleReleaseBuildArguments = { -> arguments.add("-PuseHostObjects") } - if (asNapiModule) { - arguments.add("-PasNapiModule") - } - arguments += ["--warning-mode", "all"] return arguments @@ -304,6 +315,15 @@ task copyFilesToProjectTemeplate { from "$BUILD_TOOLS_PATH/jsparser/build/js_parser.js" into "$DIST_FRAMEWORK_PATH/build-tools/jsparser" } + // Metadata filtering runs from the generated project template. Ship + // the scripts with the template, but not their development/runtime + // dependencies: seed.js invokes the sibling harvest.js and resolves + // Babel from the consuming app's node_modules. + copy { + from "$BUILD_TOOLS_PATH/metadata-filter" + include "*.js" + into "$DIST_FRAMEWORK_PATH/build-tools/metadata-filter" + } copy { from "$BUILD_TOOLS_PATH/android-metadata-generator/build/libs/android-metadata-generator.jar" into "$DIST_FRAMEWORK_PATH/build-tools" @@ -417,12 +437,30 @@ task copyReadme(type: Copy) { task createNpmPackage(type: Exec) { doFirst { workingDir "$DIST_PATH" + new File(TARBALL_PATH).mkdirs() if (isWinOs) { - commandLine "cmd", "/c", "npm", "pack" + commandLine "cmd", "/c", "npm", "pack", "--pack-destination", TARBALL_PATH } else { - commandLine "npm", "pack" + commandLine "npm", "pack", "--pack-destination", TARBALL_PATH + } + } + + // npm names the tarball after the package (nativescript-android-), + // which is identical for every engine and runtime tree. Rename it to the + // variant so parallel builds don't overwrite each other in the shared + // tarball folder. + doLast { + def version = new JsonSlurper().parseText(new File("$DIST_PATH/package.json").text).version + def packed = new File("$TARBALL_PATH/nativescript-android-${version}.tgz") + def named = new File("$TARBALL_PATH/nativescript-${PACKAGE_VARIANT}-${version}.tgz") + if (packed.exists()) { + named.delete() + if (!packed.renameTo(named)) { + throw new GradleException("Failed to rename ${packed} to ${named}") + } } + println "Tarball: ${named}" } } diff --git a/platforms/android/docs/bytecode.md b/platforms/android/docs/bytecode.md index 6f3db0eb8..cf11ecb93 100644 --- a/platforms/android/docs/bytecode.md +++ b/platforms/android/docs/bytecode.md @@ -12,16 +12,22 @@ that engine's compiler binary, which is encapsulated in a small adapter under | Engine | Bytecode | Compiler CLI | Status | | ------- | -------- | ------------ | ------ | | Hermes | ✅ HBC | `hermesc` (upstream) | wired end-to-end | -| QuickJS | ✅ | `nsbc-quickjs` shim (`native/qjs-compile.c`) | CLI built by CI; runtime read-support pending | -| QuickJS-NG | ✅ | `nsbc-quickjs-ng` shim | CLI built by CI; runtime read-support pending | -| PrimJS | ✅ | `nsbc-primjs` shim (`native/primjs-compile.c`) | CLI built by CI; runtime read-support pending | +| QuickJS-NG | ✅ | `nsbc-quickjs-ng` shim (`native/qjs-compile.c`) | wired end-to-end; also serves the `QUICKJS` alias | +| PrimJS | ✅ | `nsbc-primjs` shim (`native/primjs-compile.c`) | wired end-to-end | | V8 | ⚠️ | runtime code-cache only (no AOT format) | see [v8-code-cache.md](./v8-code-cache.md) | -| JSC | ❌ never | — | n/a | +| JSC | ⚠️ | runtime code-cache only (no AOT format) | `js_run_bytecode_file` falls back to source | -Only Hermes is enabled today. The compiler CLIs for the QuickJS family are built -by the [bytecode-compilers workflow](../.github/workflows/bytecode-compilers.yml), -but those engines stay gated off (`ready: false`) until their runtime execution -path (`js_run_bytecode_file`) exists — see "Adding an engine" below. +All three AOT engines are enabled (`ready: true`) and have a runtime read side. +Their compiler CLIs are built by the +[bytecode-compilers workflow](../../../.github/workflows/bytecode-compilers.yml); +an engine with no binary for the build host simply ships source. V8 and JSC have +no compile-time format at all — both cache code at runtime instead +(`js_cache_script` / `js_run_cached_script`), so their `js_run_bytecode_file` +returns `napi_cannot_run_js` by design, not because it is unimplemented. + +There is no bellard QuickJS lane: that tree was dropped, and the runtime's +`QUICKJS` is an alias for `QUICKJS_NG`, which the `quickjs-ng` adapter answers to +under both names. ### Bytecode container (non-Hermes engines) @@ -33,9 +39,10 @@ a loadable blob, so the QuickJS-family shims (`native/`) wrap the engine's [8-byte magic][4-byte format version, little-endian][JS_WriteObject payload] ``` -Magic is per engine (`NSBCQJS\0`, `NSBCNGS\0`, `NSBCPJS\0`) and must match the -adapter's `magic` and the runtime detection. The runtime read side strips the -12-byte header and hands the payload to `JS_ReadObject` + `JS_EvalFunction`. +Magic is per engine (`NSBCNGS\0`, `NSBCPJS\0`) and must match the adapter's +`magic` and the runtime detection. The runtime read side strips the 12-byte +header and hands the payload to `JS_ReadObject` + `JS_EvalFunction` (PrimJS: the +`LEPUS_*` equivalents). ## How it works @@ -52,16 +59,21 @@ adapter's `magic` and the runtime detection. The runtime read side strips the sync. The C++ loader calls a single engine hook for both the `require` path -([ModuleInternal.cpp](../test-app/runtime/src/main/cpp/runtime/module/ModuleInternal.cpp)) +([ModuleInternal.cpp](../../../NativeScript/runtime/android/napi/modules/module/ModuleInternal.cpp)) and the raw-script path -([Runtime::RunScript](../test-app/runtime/src/main/cpp/runtime/Runtime.cpp)): +([Runtime::RunScript](../../../NativeScript/runtime/android/napi/Runtime.cpp)): ```c // napi/common/jsr_common.h -napi_status js_run_bytecode_file(napi_env env, const char *file, - const char *source_url, napi_value *result); +napi_status js_run_bytecode_file(napi_env env, const char *file, napi_value *result); ``` +The runtime has two trees — `runtime/android/napi` (the default) and +`runtime/android/jsi` — and each carries its own loader. The napi side goes +through the hook above; the jsi side detects the container in +[EngineHost::ExecuteBytecodeFile](../../../NativeScript/runtime/android/jsi/EngineHost.cpp). +A change to one is not a change to the other. + ### Module wrapping The runtime wraps every `require`d module in a function before executing it: @@ -81,12 +93,14 @@ Files the runtime runs **unwrapped** as raw scripts (via `Runtime::RunScript`, e.g. `internal/ts_helpers.js`) are compiled as-is. The build's `--raw` list keeps these in sync (default: `internal/ts_helpers.js`). -The prologue/epilogue live in two places that must stay byte-for-byte identical: +The prologue/epilogue live in several places that must stay byte-for-byte +identical (`MODULE_PROLOGUE` / `MODULE_EPILOGUE` in each): -- `runtime/src/main/cpp/runtime/module/ModuleInternal.cpp` - (`MODULE_PROLOGUE` / `MODULE_EPILOGUE`) +- `NativeScript/runtime/android/napi/modules/module/ModuleInternal.cpp` +- `NativeScript/runtime/android/jsi/modules/module/ModuleInternal.cpp` - `tools/bytecode-compiler/compile-bytecode.js` - (`MODULE_PROLOGUE` / `MODULE_EPILOGUE`) + +(The Apple runtime carries its own copy under `runtime/apple/`.) ## The compiler tooling @@ -98,9 +112,10 @@ template doesn't carry `hermesc`. Compiler binaries are host-specific, laid out as `bin///` where `` is `-` -(`darwin-arm64`, `darwin-x64`, `linux-x64`, `linux-arm64`, `win32-x64`). Host -slots without a real binary contain a placeholder; the driver detects the current -host automatically and, if there's no compiler for it, leaves the app as source. +(`darwin-arm64`, `darwin-x64`, `linux-x64`, `linux-arm64`, `win32-x64`). A host +with no binary is simply absent — PrimJS, for instance, ships only the three Unix +hosts its GN toolchain can build. The driver detects the current host +automatically and, if there is no compiler for it, leaves the app as source. ## Enabling / disabling @@ -134,10 +149,22 @@ column offset on line 1). ## Version compatibility -A compiler and the runtime's engine library must share the same bytecode version -(for Hermes, `BYTECODE_VERSION`, currently 99). They should be built from the same -engine checkout. On a mismatch the runtime fails loudly at load rather than -silently misbehaving. +A compiler and the runtime's engine library must share the same bytecode version, +so both must come from the same engine checkout. The workflow inputs are pinned to +exact commits for this reason — see the header of +[bytecode-compilers.yml](../../../.github/workflows/bytecode-compilers.yml). + +- **Hermes** — `BYTECODE_VERSION`, currently 99. The pinned `hermes_ref` must match + `UPSTREAM_COMMIT` in build-hermes, which produced the prebuilt both platforms link. +- **QuickJS-NG** — `BC_VERSION`, currently 21 (`vendor/quickjs/source_ng/quickjs.c`, + upstream v0.11.0). The compiler stamps its own value and `JS_ReadObject` accepts + only its own, so a compiler built from a different ref produces blobs that load + nowhere. + +A mismatch is caught at load: the magic/version check fails and the file is +rejected rather than misinterpreted. Note the failure mode is a **fallback**, not a +crash — the runtime falls back to source, so the app still works and the only +visible symptom is that the TTI win quietly disappears. ## Adding an engine @@ -145,9 +172,11 @@ silently misbehaving. (documented in `lib/index.js`) and register it in `lib/index.js`. 2. Drop the host compiler binaries under `tools/bytecode-compiler/bin///`. 3. Implement runtime execution in `napi//jsr.cpp` — `js_run_bytecode_file` - currently returns `napi_cannot_run_js` (source fallback) for every engine - except Hermes. It must detect that engine's bytecode magic and run it, setting - `*result` to the completion value (the module wrapper function for modules). + must detect that engine's bytecode magic and run it, setting `*result` to the + completion value (the module wrapper function for modules). Returning + `napi_cannot_run_js` means "fall back to source", which is what V8 and JSC do. + If the engine is also wired into the jsi tree, add the matching detection to + `runtime/android/jsi/EngineHost.cpp`. 4. Flip the adapter's `ready` flag to `true` **only** once both the compiler and the runtime support exist — otherwise the build would ship bytecode the runtime can't execute. diff --git a/platforms/android/package.json b/platforms/android/package.json index aa8cc6953..3fadb47d8 100644 --- a/platforms/android/package.json +++ b/platforms/android/package.json @@ -4,7 +4,7 @@ "version": "9.0.0", "repository": { "type": "git", - "url": "https://github.com/NativeScript/runtimes.git", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "platforms/android" }, "files": [ @@ -27,9 +27,7 @@ }, "scripts": { "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", - "version": "npm run changelog && git add CHANGELOG.md", - "build": "node ./scripts/build.js", - "setup": "node ./scripts/setup.js" + "version": "npm run changelog && git add CHANGELOG.md" }, "devDependencies": { "conventional-changelog-cli": "^2.1.1", diff --git a/platforms/android/scripts/build.js b/platforms/android/scripts/build.js deleted file mode 100644 index 4ecd560d8..000000000 --- a/platforms/android/scripts/build.js +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env node -// Simple build helper for NativeScript Android runtime. -// Supports interactive prompts or direct CLI flags. -// See README in the repo for Gradle usage. - -const { spawn } = require('child_process'); -const path = require('path'); -const readline = require('readline'); - -// SHERMES is kept as an input alias for the unified Static Hermes backend. -const VALID_ENGINES = ['V8-10', 'V8-11', 'V8-13', 'QUICKJS', 'QUICKJS_NG', 'HERMES', 'SHERMES', 'JSC', 'PRIMJS']; -// Hermes and JSC gained host-object support in the engine updates, so every -// engine now supports them; SHERMES rides along as an alias for HERMES. -const HOST_OBJECTS_SUPPORTED = new Set(['V8-10', 'V8-11', 'V8-13', 'QUICKJS', 'QUICKJS_NG', 'HERMES', 'SHERMES', 'JSC', 'PRIMJS']); - -// Host objects are enabled by default whenever the selected engine supports -// them. They can be force-disabled with --disable-host-objects (or by -// answering "no" to the interactive prompt). -function hostObjectsEnabled(opts) { - if (!HOST_OBJECTS_SUPPORTED.has(opts.engine)) return false; - if (opts['disable-host-objects']) return false; - return true; -} - -function parseArgs(argv) { - const opts = {}; - argv.forEach(arg => { - if (!arg.startsWith('--')) return; - const eq = arg.indexOf('='); - if (eq !== -1) { - const k = arg.slice(2, eq); - const v = arg.slice(eq + 1); - opts[k] = v; - } else { - const k = arg.slice(2); - // flag -> boolean true - opts[k] = true; - } - }); - return opts; -} - -async function prompt(question, rl, defaultVal) { - return new Promise(resolve => { - const q = defaultVal ? `${question} (${defaultVal}) ` : `${question} `; - rl.question(q, ans => { - if (!ans && typeof defaultVal !== 'undefined') return resolve(defaultVal); - resolve(ans); - }); - }); -} - -async function interactiveFill(opts) { - const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); - try { - // If skip-all-args is given, only ask for engine and host-objects (when supported). - if (opts['skip-all-args']) { - if (!opts.engine) { - console.log('Select JS engine:'); - VALID_ENGINES.forEach((e, i) => console.log(` ${i + 1}) ${e}`)); - const ans = await prompt('Choose number or name', rl, 'V8-10'); - const pick = /^\d+$/.test(ans) ? VALID_ENGINES[Number(ans) - 1] : ans; - opts.engine = VALID_ENGINES.includes(pick) ? pick : 'V8-10'; - } - - return opts; - } - - // original interactive flow (unchanged) when not skipping all args - if (!opts.engine) { - console.log('Select JS engine:'); - VALID_ENGINES.forEach((e, i) => console.log(` ${i + 1}) ${e}`)); - const ans = await prompt('Choose number or name', rl, 'V8-10'); - const pick = /^\d+$/.test(ans) ? VALID_ENGINES[Number(ans) - 1] : ans; - opts.engine = VALID_ENGINES.includes(pick) ? pick : 'V8-10'; - } - - } finally { - rl.close(); - } - - return opts; -} - -function buildGradleArgs(opts) { - const props = []; - if (opts.engine) props.push(`-Pengine=${opts.engine}`); - if (hostObjectsEnabled(opts)) props.push('-PuseHostObjects'); - if (opts['as-napi-module']) props.push('-PasNapiModule'); - - return props; -} - -async function main() { - const initial = parseArgs(process.argv.slice(2)); - const opts = await interactiveFill(initial); - - const gradleProps = buildGradleArgs(opts); - const gradlew = process.platform === 'win32' ? 'gradlew' : './gradlew'; - const gradleCmd = [gradlew].concat(gradleProps, []); - - console.log('\nGradle command:'); - console.log(gradleCmd.join(' '), '\n'); - - if (opts['dry-run']) { - console.log('Dry run requested. Exiting without executing gradle.'); - return; - } - - const proc = spawn(gradleCmd[0], gradleCmd.slice(1), { stdio: 'inherit', cwd: process.cwd(), shell: false }); - - proc.on('exit', code => { - if (code === 0) { - console.log('\nBuild finished successfully.'); - } else { - console.error(`\nBuild failed with exit code ${code}.`); - } - process.exit(code); - }); - - proc.on("message", (message) => { - console.log(message); - }) - - proc.on('error', err => { - console.error('Failed to start gradle:', err.message || err); - process.exit(1); - }); -} - -main().catch(err => { - console.error('Error:', err && err.message ? err.message : err); - process.exit(1); -}); diff --git a/platforms/android/scripts/get-next-version.js b/platforms/android/scripts/get-next-version.js deleted file mode 100644 index 832380d28..000000000 --- a/platforms/android/scripts/get-next-version.js +++ /dev/null @@ -1,89 +0,0 @@ -const semver = require("semver"); -const child_process = require("child_process"); -const dayjs = require("dayjs"); -const fs = require("fs"); - -function resolveCurrentVersion() { - if (process.env.NPM_VERSION) { - return process.env.NPM_VERSION; - } - const target = process.argv[2] || "android"; - const packageJsonByTarget = { - android: "../package.json", - "android-v8": "../packages/android-v8/package.json", - "android-hermes": "../packages/android-hermes/package.json", - "android-jsc": "../packages/android-jsc/package.json", - "android-quickjs": "../packages/android-quickjs/package.json", - "android-quickjs-ng": "../packages/android-quickjs-ng/package.json", - "android-primjs": "../packages/android-primjs/package.json", - }; - - if (!packageJsonByTarget[target]) { - throw new Error( - `Unknown target "${target}". Expected one of ${Object.keys(packageJsonByTarget) - .map((name) => `"${name}"`) - .join(", ")}.`, - ); - } - - return require(packageJsonByTarget[target]).version; -} - -const currentVersion = resolveCurrentVersion(); - -if (!currentVersion) { - throw new Error("Invalid current version"); -} -const currentTag = process.env.NPM_TAG || "next"; -const runID = process.env.GITHUB_RUN_ID || 0; - -let prPrerelease = ""; - -if (currentTag === "pr" && process.env.GITHUB_EVENT_PATH) { - try { - const ev = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8")); - const prNum = ev.pull_request.number; - // add extra PR number to version-pr.PRNUM-.... - prPrerelease = `${prNum}-`; - } catch (e) { - // don't add pr prerelease - } -} - -const preRelease = `${currentTag}.${prPrerelease}${dayjs().format("YYYY-MM-DD")}-${runID}`; - -let lastTagVersion = ( - process.env.LAST_TAGGED_VERSION || - child_process - .spawnSync("git", ["describe", "--tags", "--abbrev=0", "--match=v*"]) - .stdout.toString() -) - .trim() - .substring(1); -if (!semver.parse(lastTagVersion)) { - throw new Error("Invalid last tag version"); -} - -function setPreRelease(version) { - const parsed = semver.parse(version); - return semver.parse( - `${parsed.major}.${parsed.minor}.${parsed.patch}-${preRelease}` - ); -} - -let nextVersion = setPreRelease(currentVersion); - -if (!nextVersion) { - throw new Error("Invalid next version"); -} - -if (semver.compare(currentVersion, lastTagVersion) <= 0) { - // next version is older than current version - nextVersion = setPreRelease(semver.parse(lastTagVersion).inc("patch")); -} - -if (!nextVersion) { - throw new Error("Invalid next version"); -} - -console.log(nextVersion.format()); diff --git a/platforms/android/scripts/get-npm-tag.js b/platforms/android/scripts/get-npm-tag.js deleted file mode 100644 index d56157224..000000000 --- a/platforms/android/scripts/get-npm-tag.js +++ /dev/null @@ -1,46 +0,0 @@ -const process = require("node:process"); -const semver = require("semver"); - -let currentVersion = process.env.NPM_VERSION; -if (!currentVersion) { - const cmdArgs = process.argv.slice(2); - const target = cmdArgs[0] || "android"; - const packageJsonByTarget = { - android: "../package.json", - "android-v8": "../packages/android-v8/package.json", - "android-hermes": "../packages/android-hermes/package.json", - "android-jsc": "../packages/android-jsc/package.json", - "android-quickjs": "../packages/android-quickjs/package.json", - "android-quickjs-ng": "../packages/android-quickjs-ng/package.json", - "android-primjs": "../packages/android-primjs/package.json", - }; - - if (!packageJsonByTarget[target]) { - throw new Error( - `Unknown target "${target}". Expected one of ${Object.keys(packageJsonByTarget) - .map((name) => `"${name}"`) - .join(", ")}.`, - ); - } - - currentVersion = require(packageJsonByTarget[target]).version; -} - -function validateNpmTag(version) { - const parsed = semver.parse(version); - return ( - parsed.prerelease.length === 0 || - (typeof parsed.prerelease[0] === "string" && - /^[a-zA-Z][0-9A-Za-z-]*$/.test(parsed.prerelease[0])) - ); -} - -function getNpmTag(version) { - if (!validateNpmTag(version)) { - throw new Error(`Invalid npm tag "${version}"`); - } - const parsed = semver.parse(version); - return parsed.prerelease[0] || "latest"; -} - -console.log(getNpmTag(currentVersion)); diff --git a/platforms/android/scripts/setup.js b/platforms/android/scripts/setup.js deleted file mode 100644 index 4c953ec04..000000000 --- a/platforms/android/scripts/setup.js +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env node -// -// One-shot developer/CI setup for the android-runtime repo: -// 1. Fetch the engine source submodules (QuickJS / QuickJS-NG) and reset them -// to their pinned commit. -// 2. Apply the NativeScript local patches onto the pristine submodules. -// 3. Install the jsparser build-tool dependencies (the previous `npm run setup`). -// -// Idempotent: re-running resets the submodules to the pinned commit (discarding -// an already-applied patch) and re-applies, so it is safe to run repeatedly. -// -// The submodule + patch config mirrors scripts/vendor-engines-as-submodules.sh. - -'use strict'; - -const { execFileSync } = require('child_process'); -const fs = require('fs'); -const path = require('path'); - -// This script lives in platforms/android/scripts, but the engine submodules are -// registered against the napi-ios repository root, so the two are tracked -// separately: git commands run from REPO_ROOT, the android build tools from -// ANDROID_ROOT. -const ANDROID_ROOT = path.resolve(__dirname, '..'); -const REPO_ROOT = path.resolve(__dirname, '../../..'); -const NPM = process.platform === 'win32' ? 'npm.cmd' : 'npm'; - -const NAPI_QJS = 'vendor/quickjs'; -const PATCHES = path.join(ANDROID_ROOT, 'tools/patches'); -const ENGINES = [ - { - sub: `${NAPI_QJS}/source`, - patch: path.join(PATCHES, 'quickjs/0001-nativescript-local-changes.patch'), - }, - { - sub: `${NAPI_QJS}/source_ng`, - patch: path.join(PATCHES, 'quickjs_ng/0001-nativescript-local-changes.patch'), - }, -]; - -function run(cmd, args, opts = {}) { - console.log(`==> ${cmd} ${args.join(' ')}`); - execFileSync(cmd, args, { stdio: 'inherit', cwd: REPO_ROOT, ...opts }); -} - -function insideGitWorkTree() { - try { - return execFileSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: REPO_ROOT }) - .toString().trim() === 'true'; - } catch (_) { - return false; - } -} - -function setupEngines() { - if (!insideGitWorkTree()) { - console.log('==> Not a git work tree; skipping submodule + patch setup.'); - return; - } - - // 1. Fetch + reset the submodules to their pinned commit. --force discards any - // previously-applied patch so step 2 always starts from a clean checkout. - run('git', ['submodule', 'update', '--init', '--force', ...ENGINES.map((e) => e.sub)]); - - // 2. Hard-reset each submodule's working tree. `submodule update --force` - // above only forces a checkout when the recorded SHA differs; if the - // submodule is already at the pinned commit it leaves local modifications - // alone, so a re-run would try to apply the patch onto an already-patched - // tree and fail. Resetting here is what actually makes this idempotent. - for (const { sub } of ENGINES) { - run('git', ['-C', sub, 'reset', '--hard', '--quiet']); - run('git', ['-C', sub, 'clean', '-qfd']); - } - - // 3. Apply the local patches onto the pristine submodules. - for (const { sub, patch } of ENGINES) { - if (!fs.existsSync(patch)) { - console.warn(` ! patch missing, skipping: ${patch}`); - continue; - } - run('git', ['-C', sub, 'apply', '--whitespace=nowarn', patch]); - } -} - -function setupJsParser() { - const dir = path.join(ANDROID_ROOT, 'test-app/build-tools/jsparser'); - const hasLock = fs.existsSync(path.join(dir, 'package-lock.json')); - run(NPM, [hasLock ? 'ci' : 'install'], { cwd: dir }); -} - -setupEngines(); -setupJsParser(); -console.log('==> Setup complete'); diff --git a/platforms/android/scripts/vendor-engines-as-submodules.sh b/platforms/android/scripts/vendor-engines-as-submodules.sh deleted file mode 100644 index bee70e93b..000000000 --- a/platforms/android/scripts/vendor-engines-as-submodules.sh +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env bash -# -# Convert the vendored QuickJS / QuickJS-NG engine sources into pinned git -# submodules, preserving our hand-written local changes as patch files. -# -# What it does (default / full run): -# 1. Resolves the target upstream commit for each engine. -# 2. Generates a patch capturing the local edits in the current vendored tree -# relative to that upstream commit -> tools/patches//. -# 3. Replaces the vendored directory with a submodule pinned to that commit. -# 4. Re-applies the patch so the working tree stays buildable (the submodule is -# left "dirty" with the local changes, which is expected for this pattern). -# -# Engines: -# quickjs -> https://github.com/bellard/quickjs @ latest (default HEAD) -# quickjs_ng -> https://github.com/quickjs-ng/quickjs @ 3c8f3d68953955950074c41c6e4d999562ae82a7 -# -# Modes: -# (no args) full conversion described above -# --patches-only only (re)generate the patch files; do NOT touch submodules -# (safe, non-destructive preview) -# --apply-only re-apply existing patches onto already-converted submodules -# (use on a fresh checkout / in CI after `submodule update`) -# -# The script never commits; it prints the commands to review and commit at the end. - -set -euo pipefail - -# ---- engine table (bash 3.2-friendly parallel arrays) ---------------------- -ENGINE_NAMES=(quickjs quickjs_ng) -ENGINE_SUBDIRS=(source source_ng) -ENGINE_REPOS=(https://github.com/bellard/quickjs https://github.com/quickjs-ng/quickjs) -# Use HEAD to mean "latest default-branch commit"; otherwise pin to the given sha. -# quickjs : bellard master @ 04be246 (vendored base ~= this) -> auto-diff a clean patch. -# quickjs_ng: pinned to master @ d950d55 (BC_VERSION 27). The vendored source was -# v0.15.1 (BC_VERSION 26), so a diff can't isolate our edits; the patch -# is hand-migrated (see tools/patches/quickjs_ng) and used as-is. -ENGINE_REFS=(04be246001599f5995fa2f2d8c91a0f198d3f34c d950d55e950dd7994a96c669c31efa967b8b79f3) - -# Per-engine patch handling: -# auto -> regenerate the patch by diffing the vendored files vs upstream -# manual -> use the committed patch as-is (do NOT regenerate) -ENGINE_PATCH_MODE=(auto manual) - -# Only these files carry hand-written NativeScript changes, so the (auto) patch is -# scoped to them. Everything else comes verbatim from the pinned upstream commit. -PATCH_FILES=(quickjs.c quickjs.h) - -PATCH_NAME="0001-nativescript-local-changes.patch" - -# ---- setup ----------------------------------------------------------------- -REPO_ROOT="$(git rev-parse --show-toplevel)" -cd "$REPO_ROOT" - -# Paths are relative to the napi-ios repository root: the engine submodules are -# registered there, while the patch files live under platforms/android. -NAPI_QJS_REL="vendor/quickjs" -PATCHES_REL="platforms/android/tools/patches" - -command -v rsync >/dev/null || { echo "error: rsync is required" >&2; exit 1; } - -log() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } -warn() { printf '\033[1;33m warning:\033[0m %s\n' "$*" >&2; } - -resolve_commit() { - local repo="$1" ref="$2" - if [ "$ref" = "HEAD" ]; then - git ls-remote "$repo" HEAD | awk '{print $1}' - else - echo "$ref" - fi -} - -# Generate a patch of the local edits in $vendored relative to $repo@$commit, -# scoped to the files in PATCH_FILES (the ones we hand-edit). Overlaying only -# those files onto a pristine upstream checkout means the diff is purely our -# changes, with none of the upstream drift in the surrounding files. -gen_patch() { - local vendored="$1" repo="$2" commit="$3" patch_out="$4" - local tmp f - tmp="$(mktemp -d)" - git -C "$tmp" init -q - git -C "$tmp" config core.autocrlf false - git -C "$tmp" remote add origin "$repo" - git -C "$tmp" fetch -q --depth 1 origin "$commit" - git -C "$tmp" checkout -q FETCH_HEAD - # Overlay only the hand-edited files onto the pristine upstream checkout. - for f in "${PATCH_FILES[@]}"; do - if [ -f "$vendored/$f" ]; then - cp "$vendored/$f" "$tmp/$f" - else - warn "$(basename "$vendored"): patch file '$f' missing in vendored tree" - fi - done - git -C "$tmp" add -- "${PATCH_FILES[@]}" - mkdir -p "$(dirname "$patch_out")" - git -C "$tmp" diff --cached --binary -- "${PATCH_FILES[@]}" >"$patch_out" - rm -rf "$tmp" -} - -apply_patch() { - local subdir="$1" patch_abs="$2" - if [ ! -s "$patch_abs" ]; then - log " no local changes to apply ($patch_abs is empty)" - return 0 - fi - git -C "$subdir" apply --whitespace=nowarn "$patch_abs" -} - -to_submodule() { - local subdir_rel="$1" repo="$2" commit="$3" - # Drop the vendored copy from index + working tree, then clear any leftovers. - git rm -r -q --ignore-unmatch "$subdir_rel" || true - rm -rf "$subdir_rel" - # Add the submodule (non-recursive: upstream's own test submodules stay off). - git submodule add --force "$repo" "$subdir_rel" - git -C "$subdir_rel" checkout -q "$commit" - git add .gitmodules "$subdir_rel" -} - -MODE="full" -case "${1:-}" in - --patches-only) MODE="patches" ;; - --apply-only) MODE="apply" ;; - "") MODE="full" ;; - *) echo "usage: $0 [--patches-only|--apply-only]" >&2; exit 2 ;; -esac - -# ---- guard: don't clobber an existing submodule on a full run -------------- -if [ "$MODE" = "full" ] && [ -f .gitmodules ]; then - for i in "${!ENGINE_NAMES[@]}"; do - if git config -f .gitmodules --get-regexp path 2>/dev/null \ - | awk '{print $2}' | grep -qx "$NAPI_QJS_REL/${ENGINE_SUBDIRS[$i]}"; then - echo "error: '$NAPI_QJS_REL/${ENGINE_SUBDIRS[$i]}' is already a submodule." >&2 - echo " Use --apply-only to re-apply patches, or remove it first." >&2 - exit 1 - fi - done -fi - -# ---- run ------------------------------------------------------------------- -declare -a PINNED -for i in "${!ENGINE_NAMES[@]}"; do - name="${ENGINE_NAMES[$i]}" - subdir_rel="$NAPI_QJS_REL/${ENGINE_SUBDIRS[$i]}" - repo="${ENGINE_REPOS[$i]}" - patch_abs="$REPO_ROOT/$PATCHES_REL/$name/$PATCH_NAME" - - if [ "$MODE" = "apply" ]; then - log "$name: re-applying $PATCHES_REL/$name/$PATCH_NAME" - apply_patch "$subdir_rel" "$patch_abs" - continue - fi - - log "$name: resolving ${ENGINE_REFS[$i]} on $repo" - commit="$(resolve_commit "$repo" "${ENGINE_REFS[$i]}")" - PINNED+=("$name -> $commit") - log "$name: target commit $commit" - - if [ "${ENGINE_PATCH_MODE[$i]}" = "manual" ]; then - if [ ! -s "$patch_abs" ]; then - warn "$name: manual patch '$PATCHES_REL/$name/$PATCH_NAME' is missing/empty." - else - log "$name: using hand-migrated patch $PATCHES_REL/$name/$PATCH_NAME (not regenerated)" - fi - elif [ -d "$subdir_rel" ]; then - log "$name: generating patch -> $PATCHES_REL/$name/$PATCH_NAME" - gen_patch "$subdir_rel" "$repo" "$commit" "$patch_abs" - lines="$(wc -l <"$patch_abs" | tr -d ' ')" - log "$name: patch is $lines line(s)" - [ "$lines" -eq 0 ] && warn "$name: patch is empty (no local changes vs $commit)" - elif [ "$MODE" = "patches" ]; then - warn "$name: vendored dir '$subdir_rel' missing; cannot diff. Skipping patch." - continue - fi - - if [ "$MODE" = "full" ]; then - log "$name: converting '$subdir_rel' to submodule @ $commit" - to_submodule "$subdir_rel" "$repo" "$commit" - log "$name: applying local patch onto the submodule" - apply_patch "$subdir_rel" "$patch_abs" - fi -done - -# ---- summary --------------------------------------------------------------- -echo -log "Done (mode: $MODE)." -if [ "${#PINNED[@]:-0}" -gt 0 ]; then - echo "Pinned commits:" - printf ' %s\n' "${PINNED[@]}" -fi -if [ "$MODE" = "full" ]; then - cat < + if (f.isFile() && f.name.endsWith(".java") + && !f.absolutePath.contains("${File.separator}com${File.separator}tns${File.separator}gen${File.separator}")) { + sources.add(f.absolutePath) + } + } + + if (sources.isEmpty()) { + return + } + + outDir.deleteDir() + outDir.mkdirs() + + // The classpath collectAllJars just wrote, plus whatever the runtime + // module has already produced -- com.tns.* is what the app's own + // classes extend. + def cp = [] + def depsFile = file("$BUILD_TOOLS_PATH/$SBG_JAVA_DEPENDENCIES") + if (depsFile.exists()) { + depsFile.eachLine { line -> if (line.trim()) cp.add(line.trim()) } + } + def runtimeProject = findProject(':runtime') + if (runtimeProject != null) { + runtimeProject.fileTree(runtimeProject.buildDir) { + include "intermediates/javac/**/classes" + include "tmp/kotlin-classes/**" + }.visit { d -> if (d.directory) cp.add(d.file.absolutePath) } + } + + def argsFile = new File(outDir.parentFile, "sbg-javac-args.txt") + argsFile.text = sources.collect { "\"${it.replace('\\', '/')}\"" }.join("\n") + + def cmd = ["javac", "-proc:none", "-nowarn", "-g", + "-source", "17", "-target", "17", + "-d", outDir.absolutePath] + if (!cp.isEmpty()) { + cmd.addAll(["-cp", cp.join(File.pathSeparator)]) + } + cmd.add("@${argsFile.absolutePath}".toString()) + + def process = new ProcessBuilder(cmd.collect { it.toString() }) + .redirectErrorStream(true).start() + def errors = 0 + process.inputStream.eachLine { line -> if (line.contains("error:")) errors++ } + process.waitFor() + + def produced = 0 + if (outDir.exists()) { + outDir.eachFileRecurse { f -> if (f.isFile() && f.name.endsWith(".class")) produced++ } + } + outLogger.withStyle(Style.Info).println( + "\t ~ [sbg] compiled ${produced} app class file(s) for binding generation" + + (errors > 0 ? " (${errors} source(s) skipped -- see above)" : "")) + } +} + +// Name generated bindings after what they contain rather than after the extend() +// call site. Off by default; an app opts in with -PuseContentBindings, or with +// +// project.ext.useContentBindings = true +// +// in App_Resources/Android/app.gradle or before-plugins.gradle. The value drives +// both halves of the scheme from one place: the static binding generator (which +// writes the names) and the runtime (which looks them up, via the app config +// written by writeRuntimeAppConfig). -Pcontent-keyed-bindings is the older +// spelling, kept because it is what a runtime build passes to :runtime. +def nsContentKeyedBindings = ["useContentBindings", "content-keyed-bindings"].any { name -> + project.hasProperty(name) && + (project.property(name).toString().isEmpty() + || Boolean.parseBoolean(project.property(name).toString())) +} + task runSbg(type: BuildToolTask) { dependsOn "collectAllJars" + dependsOn "compileAppClassesForSbg" def rootPath = "" if (!findProject(':static-binding-generator').is(null)) { rootPath = Paths.get(project(':static-binding-generator').projectDir.path, "build/libs").toString() @@ -541,6 +655,14 @@ task runSbg(type: BuildToolTask) { paramz.add("-line-column-primjs") } + // The generated names and the names the runtime looks up are two halves of + // one scheme, and half of it is a silent LookedUpClassNotFound. The other + // half is no longer a runtime rebuild: writeRuntimeAppConfig hands the same + // value to the runtime through the app's package.json. + if (nsContentKeyedBindings) { + paramz.add("-content-keyed-bindings") + } + setOutputs outLogger args paramz @@ -716,6 +838,9 @@ task 'collectAllJars' { new File("$BUILD_TOOLS_PATH/$SBG_JAVA_DEPENDENCIES").withWriter { out -> allJarPaths.each { out.println it } + // The app's own classes, compiled by compileAppClassesForSbg. A + // directory rather than a jar; the generator reads both. + out.println layout.buildDirectory.dir("nativescript/sbg-app-classes").get().asFile.absolutePath } new File("$BUILD_TOOLS_PATH/$MDG_JAVA_DEPENDENCIES").withWriter { out -> allJarPaths.each { @@ -736,12 +861,159 @@ task 'collectAllJars' { } } +// Derives whitelist.mdg from the app's own JS, so metadata carries only what +// the app can reach. Opt-in: without -PnsFilterMetadata the seed is removed and +// the generator emits everything, which is the behaviour every existing build +// already has. +// +// The seed is only a starting point -- the generator closes over supertypes, +// nested classes and signature types before dropping anything, and fails the +// build if that closure turns out to be unsound. +def nsFilterMetadata = project.hasProperty("nsFilterMetadata") + +// Build-owned, and deliberately NOT $rootDir/whitelist.mdg: that path belongs to +// the app. An app may ship its own hand-written whitelist.mdg / blacklist.mdg +// and filter without -PnsFilterMetadata at all, exactly as it could before the +// generated seed existed -- writing the seed there overwrote that file, and +// turning filtering off deleted it. +def nsGeneratedSeed = file("$projectDir/build/nativescript/generated-whitelist.mdg") + +task generateMetadataSeed { + def seedScript = "$BUILD_TOOLS_PATH/metadata-filter/seed.js" + def generatedSeed = nsGeneratedSeed + + inputs.property("enabled", nsFilterMetadata) + inputs.dir(APP_DIR) + inputs.files(fileTree("$projectDir/src/main/res")) + inputs.files(fileTree("$BUILD_TOOLS_PATH/metadata-filter") { include "*.js" }) + outputs.file(generatedSeed) + + doLast { + if (!nsFilterMetadata) { + // Leaving a stale seed behind would keep filtering on for a build + // that did not ask for it -- the confusing direction to fail. Only + // the generated file is removed; an app's own whitelist.mdg is + // untouched and still filters. + if (generatedSeed.exists()) { + generatedSeed.delete() + outLogger.withStyle(Style.Info).println "\t ~ metadata filtering off; removed the generated seed" + } + return + } + + generatedSeed.parentFile.mkdirs() + + def cmd = ["node", seedScript, APP_DIR, + "--manifest", "$projectDir/src/main/AndroidManifest.xml", + "--out", generatedSeed.absolutePath] + + // Custom views, fragments and behaviours are named in resource XML and + // instantiated by the framework; nothing in the JS mentions them. + def resDir = file("$projectDir/src/main/res") + if (resDir.exists()) { + cmd.addAll(["--res", resDir.absolutePath]) + } + + def bindings = file("$BUILD_TOOLS_PATH/$SBG_BINDINGS_NAME") + if (bindings.exists()) { + cmd.addAll(["--sbg-bindings", bindings.absolutePath]) + } + + // This app ships files that are invalid on purpose, to test how the + // loader reports them. Anywhere else an unparseable file disables + // filtering for the whole build. + cmd.addAll(["--allow-unparseable", "shared/Require/SyntaxErrorInModule", + "--allow-unparseable", "shared/Workers/WorkerInvalidSyntax"]) + + // ProcessBuilder needs real Strings; interpolated paths are GStrings. + def process = new ProcessBuilder(cmd.collect { it.toString() }) + .redirectErrorStream(true).start() + process.inputStream.eachLine { outLogger.withStyle(Style.Info).println "\t ~ [metadata-seed] $it" } + if (process.waitFor() != 0) { + throw new GradleException("NativeScript: metadata seed generation failed.") + } + } +} + +// Device-free guard against the one part of the seed that is written by hand. +// Wired into `check` so it runs in CI: the runtime naming a new class from +// native code is otherwise invisible until an app built with filtering on hits +// the code path that needs it. +task checkMetadataRuntimeKeepList { + group = "verification" + description = "Verifies RUNTIME_KEEP covers every class the runtime resolves by name." + + def runtimeSources = ["$rootDir/../../../NativeScript/ffi/jni/jsi", + "$rootDir/../../../NativeScript/runtime/android/jsi"] + def script = "$BUILD_TOOLS_PATH/metadata-filter/check-runtime-keeplist.js" + + inputs.files(fileTree("$BUILD_TOOLS_PATH/metadata-filter") { include "*.js" }) + runtimeSources.each { dir -> + if (file(dir).exists()) { + inputs.files(fileTree(dir) { include "**/*.cpp", "**/*.h" }) + } + } + outputs.upToDateWhen { false } + + doLast { + def cmd = ["node", script] + runtimeSources.findAll { file(it).exists() } + def process = new ProcessBuilder(cmd.collect { it.toString() }) + .redirectErrorStream(true).start() + process.inputStream.eachLine { outLogger.withStyle(Style.Info).println "\t ~ [keeplist] $it" } + if (process.waitFor() != 0) { + throw new GradleException( + "NativeScript: the metadata runtime keep-list is out of date. " + + "Add the classes listed above to RUNTIME_KEEP in " + + "build-tools/metadata-filter/seed.js.") + } + } +} + +tasks.matching { it.name == "check" }.configureEach { it.dependsOn(checkMetadataRuntimeKeepList) } + task copyMetadataFilters { + dependsOn generateMetadataSeed + + // Declared so the task re-runs when a filter file appears or disappears. + // Without inputs it has none to compare, stays up to date on the strength + // of its outputs alone, and leaves the previous build's filter in place -- + // which silently keeps filtering on for a build that turned it off. + inputs.files("$rootDir/whitelist.mdg", "$rootDir/blacklist.mdg", nsGeneratedSeed) outputs.files("$BUILD_TOOLS_PATH/whitelist.mdg", "$BUILD_TOOLS_PATH/blacklist.mdg") - // use an explicit copy task here because the copy task itselfs marks the whole built-tools as an output! - copy { - from file("$rootDir/whitelist.mdg"), file("$rootDir/blacklist.mdg") - into "$BUILD_TOOLS_PATH" + + // Copies at execution time, not configuration time: the seed does not exist + // until generateMetadataSeed has run. Files.copy rather than project.copy, + // which Gradle 9 no longer allows during execution. + doLast { + // The whitelist MDG reads is the app's own file, the generated seed, or + // both concatenated. Either alone is a complete filter: an app with a + // hand-written whitelist and no -PnsFilterMetadata filters by its list + // (and MDG leaves it alone -- see enforceClosure in buildMetadata), + // while a build with the flag and no app file filters by the seed. + def whitelistParts = [] + def appWhitelist = file("$rootDir/whitelist.mdg") + if (appWhitelist.exists()) { + whitelistParts.add(appWhitelist.text) + } + if (nsFilterMetadata && nsGeneratedSeed.exists()) { + whitelistParts.add(nsGeneratedSeed.text) + } + + def whitelistTarget = file("$BUILD_TOOLS_PATH/whitelist.mdg") + if (whitelistParts.isEmpty()) { + whitelistTarget.delete() + } else { + whitelistTarget.text = whitelistParts.join("\n") + } + + def blacklistSource = file("$rootDir/blacklist.mdg") + def blacklistTarget = file("$BUILD_TOOLS_PATH/blacklist.mdg") + if (blacklistSource.exists()) { + Files.copy(blacklistSource.toPath(), blacklistTarget.toPath(), + StandardCopyOption.REPLACE_EXISTING) + } else { + blacklistTarget.delete() + } } } @@ -957,6 +1229,16 @@ task buildMetadata(type: BuildToolTask) { // use explicit inputs as the above makes the whole build-tools directory an input! inputs.files("$BUILD_TOOLS_PATH/whitelist.mdg", "$BUILD_TOOLS_PATH/blacklist.mdg") + // The generator itself decides what the metadata contains, so a change to + // it has to invalidate the output. Without this the task stays up-to-date + // across a generator rebuild and silently ships the previous run's tree. + inputs.files(Paths.get(rootPath, "android-metadata-generator.jar").toString()) + + // R8 keep rules derived from the same retained set as the metadata. Build + // output, not an asset -- shipping it would put 100KB+ of build input in + // the APK. + outputs.file(METADATA_KEEP_RULES) + def classesDir = layout.buildDirectory.dir("intermediates/javac").get().asFile if (classesDir.exists()) { inputs.dir(classesDir) @@ -1052,13 +1334,31 @@ task buildMetadata(type: BuildToolTask) { paramz.add("verbose") } + paramz.add("proguardOut=$METADATA_KEEP_RULES") + + // Grow the whitelist into a closure, and fail the build if that closure + // is unsound, only when the seed is the one we generated. An app's own + // whitelist.mdg is filtered as authored -- see the comment on + // generateMetadataSeed and Builder.applyClosureIfRequested. + if (nsFilterMetadata) { + paramz.add("enforceClosure=true") + } + args paramz.toArray() } } task generateTypescriptDefinitions(type: BuildToolTask) { + // allJarLibraries is filled by collectAllJars at execution time; without + // this the generator was invoked with an empty -input and failed. The task + // has been off by default, so nothing exercised it. + dependsOn "collectAllJars" + + def dtsGeneratorJar = "dts-generator.jar" if (!findProject(':dts-generator').is(null)) { dependsOn ':dts-generator:jar' + dtsGeneratorJar = Paths.get(project(':dts-generator').projectDir.path, + "build/libs/dts-generator.jar").toString() } def paramz = new ArrayList() @@ -1067,10 +1367,28 @@ task generateTypescriptDefinitions(type: BuildToolTask) { workingDir "$BUILD_TOOLS_PATH" mainClass = "-jar" + // Declared so Gradle can skip the task outright when no jar has changed, + // which is what makes generating typings by default affordable: the common + // build does no work at all, and a classpath change costs one full run. + // + // Keyed on the jars' *contents*, not the list of paths -- a dependency + // upgrade keeps the same path and would otherwise go unnoticed. + inputs.dir(extractedDependenciesDir).withPropertyName("dependencyJars") + inputs.file(android.sdkDirectory.getAbsolutePath() + "/platforms/" + + android.compileSdkVersion + "/android.jar").withPropertyName("androidJar") + if (!findProject(':dts-generator').is(null)) { + // By path, not by task: the other project is not configured yet, so + // tasks.named("jar") would fail during evaluation. + inputs.files(fileTree("${project(':dts-generator').projectDir}/build/libs") { + include "*.jar" + }).withPropertyName("generator") + } + outputs.dir(TYPINGS_PATH).withPropertyName("typings") + doFirst { delete "$TYPINGS_PATH" - paramz.add("dts-generator.jar") + paramz.add(dtsGeneratorJar) paramz.add("-input") for (String jarPath : allJarLibraries) { @@ -1094,8 +1412,14 @@ task generateTypescriptDefinitions(type: BuildToolTask) { } } +// Off by default: the dts-generator walks the whole compile classpath, which is +// slow, and most builds never look at the output. TypeScript users who need the +// typings to call anything native opt in per build with -PgenerateTypings +// (bare, or =true); -PgenerateTypings=false is still an explicit no. generateTypescriptDefinitions.onlyIf { - (project.hasProperty("generateTypings") && Boolean.parseBoolean(project.generateTypings)) || PASSED_TYPINGS_PATH != null + project.hasProperty("generateTypings") && + (project.generateTypings.toString().isEmpty() + || Boolean.parseBoolean(project.generateTypings.toString())) } collectAllJars.finalizedBy(generateTypescriptDefinitions) @@ -1213,7 +1537,8 @@ task compileBytecode { // merged assets are refreshed. onlyIf { project.ext.bytecodeEnabled } doLast { - def appDir = getMergedAssetsOutputPath() + "/app" + def assetsDir = getMergedAssetsOutputPath() + def appDir = assetsDir + "/app" if (!new File(appDir).exists()) { outLogger.withStyle(Style.Info).println "\t ~ [bytecode] no merged app assets at ${appDir}, skipping" return @@ -1226,10 +1551,17 @@ task compileBytecode { // (the runtime loads source directly) and the build continues. Opt into // fail-the-build behaviour with -PnsBytecodeStrict. def strict = project.hasProperty("nsBytecodeStrict") + // QuickJS bytecode drops each function's source text, so + // Function.prototype.toString() returns "[native code]" for every + // function. Code that sniffs that string to tell a native class from a + // JS one then misreads every class as native -- @nativescript/core's + // Style constructor does exactly this. -PnsBytecodeKeepSource keeps the + // text, at roughly 4x the blob size. + def keepSource = project.hasProperty("nsBytecodeKeepSource") outLogger.withStyle(Style.SuccessHeader).println "\t + [bytecode] compiling app JS → ${ns_engine} bytecode" outLogger.withStyle(Style.Info).println "\t ~ engine: ${ns_engine}" - outLogger.withStyle(Style.Info).println "\t ~ app assets: ${appDir}" + outLogger.withStyle(Style.Info).println "\t ~ app assets: ${assetsDir} (app/, internal/)" outLogger.withStyle(Style.Info).println "\t ~ driver: ${script}" if (project.hasProperty("bytecodeCompilerBinary")) { outLogger.withStyle(Style.Info).println "\t ~ compiler: ${bytecodeCompilerBinary}" @@ -1237,10 +1569,13 @@ task compileBytecode { outLogger.withStyle(Style.Info).println "\t ~ source maps: ${sourceMaps ? "on" : "off"}" outLogger.withStyle(Style.Info).println "\t ~ on error: ${strict ? "fail the build (strict)" : "skip file, keep source"}" - def cmd = [node, script, "--app", appDir, "--engine", ns_engine as String] + def cmd = [node, script, "--app", assetsDir, "--engine", ns_engine as String] if (project.hasProperty("bytecodeCompilerBinary")) { cmd += ["--compiler", bytecodeCompilerBinary as String] } + if (keepSource) { + cmd += ["--keep-source"] + } if (sourceMaps) { cmd += ["--source-maps"] } @@ -1308,6 +1643,33 @@ tasks.configureEach({ DefaultTask currentTask -> if (currentTask =~ /merge.*Assets/) { currentTask.dependsOn(buildMetadata) + // Hand the runtime what it cannot infer, by patching the app's + // package.json where the assets were merged -- the app's own source file + // is never touched, and AppConfig already reads this one at startup. + // + // Driven off this task's own outputs rather than + // getMergedAssetsOutputPath(), which follows the pinned + // project.ext.selectedBuildType and so names the release assets even in + // a debug build. + currentTask.doLast { + if (!nsContentKeyedBindings) return + currentTask.outputs.files.files.each { File out -> + if (out.path.contains("${File.separator}incremental${File.separator}")) return + def packageJson = new File(out, "app/package.json") + if (!packageJson.exists()) return + + def json = new JsonSlurper().parseText(packageJson.text) + def androidConfig = json.android ?: [:] + // The static binding generator wrote the class names during this + // same build; the runtime has to look them up the same way. + androidConfig.contentKeyedBindings = true + json.android = androidConfig + packageJson.text = JsonOutput.prettyPrint(JsonOutput.toJson(json)) + + outLogger.withStyle(Style.SuccessHeader).println \ + "\t + [bindings] content-keyed bindings on (generator + runtime)" + } + } // Compile the freshly-merged app JS to engine bytecode right after the // assets are merged (the onlyIf guard makes this a no-op unless it's a // release build with bytecode enabled). Packaging is made to wait for it below. @@ -1340,9 +1702,11 @@ tasks.configureEach({ DefaultTask currentTask -> currentTask.dependsOn(compileBytecode) } -// // ensure buildMetadata is done before R8 to allow custom proguard from metadata + // R8 consumes metadata-keep-rules.pro, which buildMetadata writes. Without + // this ordering R8 either reads the previous build's rules or fails on a + // missing file, and either way strips classes the app reaches from JS. if (currentTask =~ /minify.*WithR8/) { - // buildMetadata.finalizedBy(currentTask) + currentTask.dependsOn(buildMetadata) } if (currentTask =~ /assemble.*Debug/ || currentTask =~ /assemble.*Release/) { currentTask.finalizedBy("validateAppIdMatch") diff --git a/platforms/android/test-app/app/src/debug/java/com/tns/tests/OverloadResolutionFixture.java b/platforms/android/test-app/app/src/debug/java/com/tns/tests/OverloadResolutionFixture.java new file mode 100644 index 000000000..8e8dfbb96 --- /dev/null +++ b/platforms/android/test-app/app/src/debug/java/com/tns/tests/OverloadResolutionFixture.java @@ -0,0 +1,81 @@ +package com.tns.tests; + +public final class OverloadResolutionFixture { + private OverloadResolutionFixture() { + } + + public static final class HttpUrl { + } + + public interface Marker { + } + + public static final class MarkerImpl implements Marker { + } + + public static final class Builder { + public String url(HttpUrl value) { + return "http-url"; + } + + public String url(java.net.URL value) { + return "java-url"; + } + + public String url(String value) { + return "string"; + } + } + + public static String pick(Object value) { + return "object"; + } + + public static String pick(HttpUrl value) { + return "http-url"; + } + + public static String pick(String value) { + return "string"; + } + + public static String text(Object value) { + return "object"; + } + + public static String text(CharSequence value) { + return "char-sequence"; + } + + public static String text(String value) { + return "string"; + } + + public static String marker(Object value) { + return "object"; + } + + public static String marker(Marker value) { + return "marker"; + } + + public static String numeric(int value) { + return "int"; + } + + public static String numeric(long value) { + return "long"; + } + + public static String numeric(double value) { + return "double"; + } + + public static String array(Object[] value) { + return "object-array"; + } + + public static String array(String[] value) { + return "string-array"; + } +} diff --git a/platforms/android/test-app/app/src/main/assets/app/mainpage.js b/platforms/android/test-app/app/src/main/assets/app/mainpage.js index d799dff29..0dac6a823 100644 --- a/platforms/android/test-app/app/src/main/assets/app/mainpage.js +++ b/platforms/android/test-app/app/src/main/assets/app/mainpage.js @@ -30,6 +30,9 @@ require("./tests/testAsserts"); require("./tests/testWeakRef"); require("./tests/tests"); require("./tests/testMethodResolution"); +require("./tests/testOverloadResolutionRegression"); +require("./tests/testReturnTypeResolution"); +require("./tests/testSingleOverloadResolution"); require("./tests/testMethodResolutionWithNulls"); require("./tests/testArrays"); require("./tests/testsForRuntimeBindingGenerator"); @@ -78,5 +81,3 @@ require('./tests/testQueueMicrotask.js'); require('./tests/testConcurrentAccess.js'); require('./tests/testClassForNameDiscovery.js'); require('./tests/testNativeTimers.js'); - - diff --git a/platforms/android/test-app/app/src/main/assets/app/shared/Workers/index.js b/platforms/android/test-app/app/src/main/assets/app/shared/Workers/index.js index 7eaaa1cb8..1578259c8 100644 --- a/platforms/android/test-app/app/src/main/assets/app/shared/Workers/index.js +++ b/platforms/android/test-app/app/src/main/assets/app/shared/Workers/index.js @@ -219,6 +219,15 @@ describe("TNS Workers", () => { worker.postMessage({ eval: "close();" }); }); + if (global.android) { + it("Should not crash on close() after a native wrapper is collected in the same message", (done) => { + var worker = new Worker("./EvalWorker.js"); + + worker.onmessage = () => setTimeout(done, 500); + worker.postMessage({ eval: "new java.lang.Object(); gc(); postMessage('closing'); close();" }); + }); + } + it("Should not throw error if post message is called with native object", () => { var worker = new Worker("./EvalWorker.js"); diff --git a/platforms/android/test-app/app/src/main/assets/app/tests/testMethodResolution.js b/platforms/android/test-app/app/src/main/assets/app/tests/testMethodResolution.js index a5bf51139..2c485d0af 100644 --- a/platforms/android/test-app/app/src/main/assets/app/tests/testMethodResolution.js +++ b/platforms/android/test-app/app/src/main/assets/app/tests/testMethodResolution.js @@ -1,5 +1,15 @@ describe("Test Method Resolution", function () { + it("resolves both TimeZone.getOffset overloads and preserves the int return", function () { + var zone = java.util.TimeZone.getTimeZone("Europe/Paris"); + var epoch = Date.UTC(2025, 0, 15); + + for (var i = 0; i < 100; i++) { + expect(zone.getOffset(epoch)).toBe(3600000); + expect(zone.getOffset(1, 2025, 0, 15, 4, 0)).toBe(3600000); + } + }); + it("When_call_overloaded_base_method_it_should_call_base_method1", function () { __log("TEST: When_call_overloaded_base_method_it_should_call_base_method1"); @@ -80,4 +90,4 @@ describe("Test Method Resolution", function () { expect(res1).toBe(1); expect(res2).toBe(2); }); -}); \ No newline at end of file +}); diff --git a/platforms/android/test-app/app/src/main/assets/app/tests/testOverloadResolutionRegression.js b/platforms/android/test-app/app/src/main/assets/app/tests/testOverloadResolutionRegression.js new file mode 100644 index 000000000..1a52b1a04 --- /dev/null +++ b/platforms/android/test-app/app/src/main/assets/app/tests/testOverloadResolutionRegression.js @@ -0,0 +1,52 @@ +describe("Tests overload resolution with same-arity reference overloads", function () { + it("selects the String overload when metadata also contains unrelated reference overloads", function () { + var builder = new com.tns.tests.OverloadResolutionFixture.Builder(); + var result; + var error; + + try { + result = builder.url("https://example.com"); + } catch (e) { + error = e; + } + + expect(error).toBeUndefined(); + expect(result).toBe("string"); + }); + + it("selects the most specific Java-object overload from factual runtime types", function () { + var httpUrl = new com.tns.tests.OverloadResolutionFixture.HttpUrl(); + var javaUrl = new java.net.URL("https://example.com"); + + expect(com.tns.tests.OverloadResolutionFixture.pick("value")).toBe("string"); + expect(com.tns.tests.OverloadResolutionFixture.pick(httpUrl)).toBe("http-url"); + + var builder = new com.tns.tests.OverloadResolutionFixture.Builder(); + expect(builder.url(httpUrl)).toBe("http-url"); + expect(builder.url(javaUrl)).toBe("java-url"); + }); + + it("resolves String, CharSequence, and Object overloads by runtime facts", function () { + expect(com.tns.tests.OverloadResolutionFixture.text("value")).toBe("string"); + expect(com.tns.tests.OverloadResolutionFixture.text(new java.lang.String("value"))).toBe("string"); + }); + + it("prefers an implemented interface over Object for a Java proxy", function () { + var marker = new com.tns.tests.OverloadResolutionFixture.MarkerImpl(); + expect(com.tns.tests.OverloadResolutionFixture.marker(marker)).toBe("marker"); + }); + + it("resolves numeric overloads without choosing an incompatible signature", function () { + expect(com.tns.tests.OverloadResolutionFixture.numeric(java.lang.Integer.valueOf(7))).toBe("int"); + expect(com.tns.tests.OverloadResolutionFixture.numeric(long(7))).toBe("long"); + expect(com.tns.tests.OverloadResolutionFixture.numeric(double(7))).toBe("double"); + }); + + it("resolves array overloads from the Java array runtime type", function () { + var strings = Array.create("java.lang.String", 1); + var objects = Array.create(java.lang.Object, 1); + + expect(com.tns.tests.OverloadResolutionFixture.array(strings)).toBe("string-array"); + expect(com.tns.tests.OverloadResolutionFixture.array(objects)).toBe("object-array"); + }); +}); diff --git a/platforms/android/test-app/app/src/main/assets/app/tests/testReturnTypeResolution.js b/platforms/android/test-app/app/src/main/assets/app/tests/testReturnTypeResolution.js new file mode 100644 index 000000000..ecf309188 --- /dev/null +++ b/platforms/android/test-app/app/src/main/assets/app/tests/testReturnTypeResolution.js @@ -0,0 +1,42 @@ +describe("Tests metadata-first return type resolution", function () { + it("returns strings and objects without producing undefined", function () { + __log("RETURN TEST before constructor"); + console.log("RETURN TEST before constructor"); + var instance = new com.tns.tests.ReturnTypeResolutionTest(); + __log("RETURN TEST after constructor"); + console.log("RETURN TEST after constructor"); + console.log("RETURN TEST before string"); + expect(instance.stringValue()).toBe("string-result"); + console.log("RETURN TEST after string"); + console.log("RETURN TEST before object"); + expect(instance.objectValue().toString()).toBe("object-result"); + console.log("RETURN TEST after object"); + }); + + it("returns every primitive category through the metadata fast path", function () { + var instance = new com.tns.tests.ReturnTypeResolutionTest(); + expect(instance.intValue()).toBe(42); + expect(instance.longValue()).toBe(42000000000); + expect(instance.doubleValue()).toBe(4.25); + expect(instance.booleanValue()).toBe(true); + expect(instance.byteValue()).toBe(7); + expect(instance.shortValue()).toBe(8); + expect(instance.charValue()).toBe("A"); + expect(instance.floatValue()).toBe(2.5); + }); + + it("returns arrays and handles void methods", function () { + var instance = new com.tns.tests.ReturnTypeResolutionTest(); + var values = instance.intArrayValue(); + expect(values.length).toBe(3); + expect(values[0]).toBe(1); + expect(values[2]).toBe(3); + expect(instance.voidValue()).toBe(undefined); + }); + + it("keeps return types correct when overload resolution selects candidates", function () { + var instance = new com.tns.tests.ReturnTypeResolutionTest(); + expect(instance.overloaded(1)).toBe("int-overload"); + expect(instance.overloaded("value")).toBe(7); + }); +}); diff --git a/platforms/android/test-app/app/src/main/assets/app/tests/testSingleOverloadResolution.js b/platforms/android/test-app/app/src/main/assets/app/tests/testSingleOverloadResolution.js new file mode 100644 index 000000000..5b9d2d356 --- /dev/null +++ b/platforms/android/test-app/app/src/main/assets/app/tests/testSingleOverloadResolution.js @@ -0,0 +1,60 @@ +describe("Tests metadata-resolved single overload invocation", function () { + var F = com.tns.tests.SingleOverloadResolutionFixture; + + it("calls a static method with null/undefined arguments, including null for primitives", function () { + var ctx = new android.content.ContextWrapper(null); + expect(F.htmlLike(ctx, "fonts", null, "x", false, -16776961)) + .toBe("true|fonts|null|x|false|-16776961"); + expect(F.htmlLike(ctx, "fonts", undefined, "x", true, undefined)) + .toBe("true|fonts|null|x|true|0"); + expect(F.htmlLike(null, null, null, null, false, 0)) + .toBe("false|null|null|null|false|0"); + }); + + it("calls an instance method taking String[] with a plain JS array", function () { + var f = new F(); + expect(f.requestLike(["a", "b"], 7)).toBe("a,b#7"); + expect(f.requestLike([], 1)).toBe("#1"); + expect(f.requestLike(null, 2)).toBe("null#2"); + }); + + it("calls a static method taking int[] with a plain JS array", function () { + expect(F.intArray([1, 2, 3])).toBe("sum=6"); + }); + + it("passes JS values to Object, boxed and CharSequence parameters", function () { + expect(F.objectParam(null)).toBe("null"); + expect(F.objectParam("s")).toBe("java.lang.String"); + expect(F.boxedInt(java.lang.Integer.valueOf(5))).toBe("boxed=5"); + expect(F.boxedInt(null)).toBe("boxed=null"); + expect(F.boxedBool(java.lang.Boolean.valueOf(true))).toBe("bool=true"); + expect(F.charSeq("abc")).toBe("cs=abc"); + expect(F.twoStrings(null, "b")).toBe("null+b"); + }); + + it("passes a JS interface implementation", function () { + var ran = false; + var r = new java.lang.Runnable({ run: function () { ran = true; } }); + expect(F.runIt(r)).toBe("ran"); + expect(ran).toBe(true); + }); + + it("lets non-null arguments discriminate same-arity overloads when another is null", function () { + expect(F.mixed(null, 5)).toBe("string-int"); + expect(F.mixed(null, "x")).toBe("string-string"); + expect(F.mixed("a", 5)).toBe("string-int"); + }); + + it("throws instead of returning undefined when no overload matches", function () { + var builder = new com.tns.tests.OverloadResolutionFixture.Builder(); + var error; + var result; + try { + result = builder.url(123); + } catch (e) { + error = e; + } + expect(result).toBeUndefined(); + expect(error).toBeDefined(); + }); +}); diff --git a/platforms/android/test-app/app/src/main/java/com/tns/tests/ReturnTypeResolutionTest.java b/platforms/android/test-app/app/src/main/java/com/tns/tests/ReturnTypeResolutionTest.java new file mode 100644 index 000000000..0676e4204 --- /dev/null +++ b/platforms/android/test-app/app/src/main/java/com/tns/tests/ReturnTypeResolutionTest.java @@ -0,0 +1,58 @@ +package com.tns.tests; + +public class ReturnTypeResolutionTest { + public String stringValue() { + return "string-result"; + } + + public Object objectValue() { + return new String("object-result"); + } + + public int intValue() { + return 42; + } + + public long longValue() { + return 42000000000L; + } + + public double doubleValue() { + return 4.25d; + } + + public boolean booleanValue() { + return true; + } + + public byte byteValue() { + return 7; + } + + public short shortValue() { + return 8; + } + + public char charValue() { + return 'A'; + } + + public float floatValue() { + return 2.5f; + } + + public int[] intArrayValue() { + return new int[] { 1, 2, 3 }; + } + + public void voidValue() { + } + + public String overloaded(int value) { + return "int-overload"; + } + + public int overloaded(String value) { + return 7; + } +} diff --git a/platforms/android/test-app/app/src/main/java/com/tns/tests/SingleOverloadResolutionFixture.java b/platforms/android/test-app/app/src/main/java/com/tns/tests/SingleOverloadResolutionFixture.java new file mode 100644 index 000000000..6df90b026 --- /dev/null +++ b/platforms/android/test-app/app/src/main/java/com/tns/tests/SingleOverloadResolutionFixture.java @@ -0,0 +1,74 @@ +package com.tns.tests; + +import android.content.Context; + +/** + * Methods with exactly one overload per name/arity. These are "resolved" by the + * metadata generator and must be invoked directly with their metadata signature, + * whatever JS values are passed (null/undefined, JS arrays, JS interface + * implementations, numbers for boxed/Object parameters). + */ +public class SingleOverloadResolutionFixture { + + // Mirrors com.nativescript.text.Font.stringBuilderFromHtmlString + public static String htmlLike(Context context, String fontFolder, String parentFontFamily, + String htmlString, final boolean disableLinkUnderline, final int linkColor) { + return (context != null) + "|" + fontFolder + "|" + parentFontFamily + "|" + htmlString + "|" + + disableLinkUnderline + "|" + linkColor; + } + + // Mirrors Activity.requestPermissions(String[], int) + public String requestLike(String[] permissions, int requestCode) { + StringBuilder sb = new StringBuilder(); + if (permissions == null) { + sb.append("null"); + } else { + for (int i = 0; i < permissions.length; i++) { + if (i > 0) sb.append(","); + sb.append(permissions[i]); + } + } + return sb.append("#").append(requestCode).toString(); + } + + public static String intArray(int[] values) { + int sum = 0; + for (int v : values) sum += v; + return "sum=" + sum; + } + + public static String objectParam(Object value) { + return value == null ? "null" : value.getClass().getName(); + } + + public static String boxedInt(Integer value) { + return "boxed=" + value; + } + + public static String boxedBool(Boolean value) { + return "bool=" + value; + } + + public static String charSeq(CharSequence value) { + return "cs=" + value; + } + + public static String runIt(Runnable r) { + r.run(); + return "ran"; + } + + public static String twoStrings(String a, String b) { + return a + "+" + b; + } + + // Multi-overload, same arity: null in the first position must not prevent + // the second argument from discriminating. + public static String mixed(String a, int b) { + return "string-int"; + } + + public static String mixed(String a, String b) { + return "string-string"; + } +} diff --git a/platforms/android/test-app/app/src/release/java/com/tns/tests/OverloadResolutionFixture.java b/platforms/android/test-app/app/src/release/java/com/tns/tests/OverloadResolutionFixture.java new file mode 100644 index 000000000..a67fadf2b --- /dev/null +++ b/platforms/android/test-app/app/src/release/java/com/tns/tests/OverloadResolutionFixture.java @@ -0,0 +1,45 @@ +package com.tns.tests; + +public final class OverloadResolutionFixture { + private OverloadResolutionFixture() { + } + + public static final class HttpUrl { + } + + public interface Marker { + } + + public static final class MarkerImpl implements Marker { + } + + public static final class Builder { + public String url(HttpUrl value) { + return "http-url"; + } + } + + public static String pick(Object value) { + return "object"; + } + + public static String pick(HttpUrl value) { + return "http-url"; + } + + public static String text(Object value) { + return "object"; + } + + public static String marker(Object value) { + return "object"; + } + + public static String numeric(double value) { + return "double"; + } + + public static String array(Object[] value) { + return "object-array"; + } +} diff --git a/platforms/android/test-app/build-tools/android-dts-generator/dts-generator/src/main/java/com/telerik/dts/ClassRepo.java b/platforms/android/test-app/build-tools/android-dts-generator/dts-generator/src/main/java/com/telerik/dts/ClassRepo.java index b8e1b75d3..7c4a78763 100644 --- a/platforms/android/test-app/build-tools/android-dts-generator/dts-generator/src/main/java/com/telerik/dts/ClassRepo.java +++ b/platforms/android/test-app/build-tools/android-dts-generator/dts-generator/src/main/java/com/telerik/dts/ClassRepo.java @@ -57,6 +57,11 @@ public static void sortCachedProviders() { }); } + /** Every class name to be emitted, in emission order. Empty before sortCachedProviders. */ + public static List getAllClassNames() { + return sortedClasses == null ? Collections.emptyList() : sortedClasses; + } + public static JavaClass findClass(String className) { JavaClass clazz = null; for (ClassMapProvider classMapProvider : cachedProviders) { diff --git a/platforms/android/test-app/build-tools/android-dts-generator/dts-generator/src/main/java/com/telerik/dts/DtsApi.java b/platforms/android/test-app/build-tools/android-dts-generator/dts-generator/src/main/java/com/telerik/dts/DtsApi.java index 831aada44..be129d23f 100644 --- a/platforms/android/test-app/build-tools/android-dts-generator/dts-generator/src/main/java/com/telerik/dts/DtsApi.java +++ b/platforms/android/test-app/build-tools/android-dts-generator/dts-generator/src/main/java/com/telerik/dts/DtsApi.java @@ -29,6 +29,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -95,6 +96,14 @@ public class DtsApi { "volatile", "while", "with", "yield" ); + /** + * Package prefixes skipped because a segment is a reserved word, discovered + * while walking the classpath. Feeds getIgnoredNamespaces() so references to + * their types collapse to {@code any} like any other unemitted namespace. + */ + private final Set reservedWordNamespaces = new LinkedHashSet<>(); + private boolean scannedForReservedWords = false; + public DtsApi(boolean allGenericImplements, InputParameters inputParameters) { this.allGenericImplements = allGenericImplements; this.ignoreObfuscatedNameLength = inputParameters.getIgnoreObfuscatedNameLength(); @@ -124,7 +133,7 @@ public String generateDtsContent(List javaClasses) { String simpleClassName = getSimpleClassname(currClass); - if (isObfuscated(simpleClassName)) { + if (isObfuscated(simpleClassName) || !isEmittableClassName(currentFileClassname)) { continue; } Signature signature = this.getSignature(currClass); @@ -139,7 +148,13 @@ public String generateDtsContent(List javaClasses) { currentFileClassname.contains(".debugger.") || currentFileClassname.endsWith("package-info") || currentFileClassname.endsWith("module-info") || - currentFileClassname.endsWith("Kt")) { + currentFileClassname.endsWith("Kt") || + // ...and classes nested inside one. A Kotlin file facade + // (FooKt) is already skipped above; its synthetic members + // (FooKt$WhenMappings) are just as unreachable from JS, and + // emitting them opens a module for a parent that was never + // written. + currentFileClassname.split("\\$")[0].endsWith("Kt")) { continue; } @@ -232,6 +247,18 @@ public String generateDtsContent(List javaClasses) { this.prevClass = currClass; } closePackage(prevClass, null); + + // closePackage derives how much to close from the last emitted class + // name, which does not always match how many modules were actually + // opened -- and one unclosed module makes the whole .d.ts unparseable + // rather than degrading the declarations it belongs to. Close whatever + // is still open. + String emitted = sbContent.toString(); + int unclosed = emitted.length() - emitted.replace("{", "").length() + - (emitted.length() - emitted.replace("}", "").length()); + while (unclosed > 0) { + sbContent.appendln(getTabs(--unclosed) + "}"); + } // process class scope end String[] refs = references.toArray(new String[references.size()]); @@ -243,20 +270,157 @@ public String generateDtsContent(List javaClasses) { return content; } + /** + * Replaces every type from a namespace we do not emit with {@code any}. + * + *

Scanned rather than matched with a regular expression. Generic arguments + * nest -- {@code androidNative.Array>} -- and a pattern + * either stops before the arguments (leaving {@code any}) or runs past + * the closing bracket (leaving {@code Array ignored = new ArrayList<>(); + for (String namespace : this.getIgnoredNamespaces()) { + ignored.add(namespace); + String aliased = getGlobalAliasedClassName(namespace); + if (!aliased.equals(namespace)) { + ignored.add(aliased); + } + } + + // Bucketed by first character. Every candidate position used to be + // tested against all ~48 entries with startsWith; almost all of those + // comparisons fail on the first character, and this made + // replaceIgnoredNamespaces the hottest thing in the generator once the + // generics pass was fixed (34% of samples). Indexing by that character + // leaves one or two real comparisons per position. + List> ignoredByFirstChar = bucketByFirstChar(ignored); + + // Copied in runs rather than a character at a time. Matches are rare, + // so the old loop spent most of its life doing single-char appends and + // bounds checks across the whole document. + StringBuilder out = new StringBuilder(content.length()); + int contentLength = content.length(); + int i = 0; + int runStart = 0; + + while (i < contentLength) { + int length = ignoredTypeLengthAt(content, i, ignoredByFirstChar); + if (length > 0) { + if (i > runStart) { + out.append(content, runStart, i); + } + out.append("any"); + i += length; + runStart = i; + } else { + i++; + } + } + + if (runStart < contentLength) { + out.append(content, runStart, contentLength); } // replace "extends any" with "extends java.lang.Object" - content = content.replace(" extends any ", String.format(" extends %s ", DtsApi.JavaLangObject)); + return out.toString().replace(" extends any ", String.format(" extends %s ", DtsApi.JavaLangObject)); + } - return content; + private static boolean isTypeNameChar(char c) { + return Character.isLetterOrDigit(c) || c == '_' || c == '$'; + } + + /** + * Length of the ignored-namespace type starting at {@code start}, or 0. + * Includes the qualified name and its balanced generic arguments. + */ + private static List> bucketByFirstChar(List ignored) { + List> buckets = new ArrayList<>(ASCII_BUCKETS); + for (int i = 0; i < ASCII_BUCKETS; i++) { + buckets.add(null); + } + + for (String namespace : ignored) { + if (namespace.isEmpty()) { + continue; + } + char first = namespace.charAt(0); + if (first >= ASCII_BUCKETS) { + continue; + } + if (buckets.get(first) == null) { + buckets.set(first, new ArrayList<>()); + } + buckets.get(first).add(namespace); + } + return buckets; + } + + private static final int ASCII_BUCKETS = 128; + + private static int ignoredTypeLengthAt(String content, int start, List> ignoredByFirstChar) { + // Must begin a name, not land in the middle of one. + if (start > 0) { + char previous = content.charAt(start - 1); + if (isTypeNameChar(previous) || previous == '.') { + return 0; + } + } + + char firstChar = content.charAt(start); + List ignored = firstChar < ASCII_BUCKETS ? ignoredByFirstChar.get(firstChar) : null; + if (ignored == null) { + return 0; + } + + for (int n = 0; n < ignored.size(); n++) { + String namespace = ignored.get(n); + if (!content.startsWith(namespace, start)) { + continue; + } + int at = start + namespace.length(); + // "kotlin" must not match "kotlinx". + if (at < content.length() && isTypeNameChar(content.charAt(at))) { + continue; + } + // Trailing name segments: kotlin -> kotlin.jvm.functions.Function3 + while (at + 1 < content.length() && content.charAt(at) == '.' + && isTypeNameChar(content.charAt(at + 1))) { + at++; + while (at < content.length() && isTypeNameChar(content.charAt(at))) { + at++; + } + } + // Generic arguments, to the matching bracket. + if (at < content.length() && content.charAt(at) == '<') { + int depth = 0; + int scan = at; + while (scan < content.length()) { + char c = content.charAt(scan); + if (c == '<') { + depth++; + } else if (c == '>') { + depth--; + if (depth == 0) { + scan++; + break; + } + } else if (c == '\n' || c == ';') { + // Unbalanced; leave the arguments alone. + depth = -1; + break; + } + scan++; + } + if (depth == 0) { + at = scan; + } + } + return at - start; + } + return 0; } public static String serializeGenerics() { @@ -299,42 +463,114 @@ public static void loadGenerics(File inputFile) throws Exception { } // Adds javalangObject types to all generics which are used without types + /** + * Appends `` to every use of a generic type that was written + * without type arguments. + * + * Done in one pass over the document. It used to be one pass *per generic + * type*: each compiled its own pattern, scanned the whole file to see + * whether it occurred, and on a hit rebuilt the entire multi-megabyte + * string with replaceAll. With android.jar that was the single most + * expensive thing the generator did -- 52% of samples inside the regex + * engine and another 24% copying strings -- and it grew with the product of + * the generic count and the output size, so a larger classpath made it + * disproportionately worse. + * + * One alternation of every name, matched longest-first so a nested class + * wins over its outer, replaces N scans and N rebuilds with one of each. + */ public static String replaceGenericsInText(String content) { String any = "any"; - String result = content; - List> allGenerics = Stream.concat(generics.stream(), externalGenerics.stream()).collect(Collectors.toList()); + List> allGenerics = + Stream.concat(generics.stream(), externalGenerics.stream()).collect(Collectors.toList()); + if (allGenerics.isEmpty()) { + return content; + } + + Map suffixByName = new HashMap<>(); + for (Tuple generic : allGenerics) { + String suffix = buildGenericArgumentSuffix(generic.y, any); + suffixByName.putIfAbsent(generic.x, suffix); - for(Tuple generic: allGenerics) { - result = replaceNonGenericUsage(result, generic.x, generic.y, any); String globalAliasedClassName = getGlobalAliasedClassName(generic.x); - if(!generic.x.equals(globalAliasedClassName)) { - result = replaceNonGenericUsage(result, globalAliasedClassName, generic.y, any); + if (!generic.x.equals(globalAliasedClassName)) { + suffixByName.putIfAbsent(globalAliasedClassName, suffix); } } - return result; - } + // Scanned in one linear pass rather than with a regex. + // + // The obvious rewrite -- one alternation of every name -- is three + // times *slower* than the original (11.2s vs 3.8s on android.jar): a + // large alternation defeats the Boyer-Moore fast path and drops the + // engine into generic matching at every position. Matching is really + // "is this maximal dotted token a known generic", which is a hash + // lookup, so that is what this does. + StringBuilder result = new StringBuilder(content.length() + 8192); + Set reported = new HashSet<>(); + boolean replaced = false; + + int i = 0; + int length = content.length(); + + while (i < length) { + char c = content.charAt(i); + if (!isGenericTypeNameChar(c)) { + result.append(c); + i++; + continue; + } - private static String replaceNonGenericUsage(String content, String className, Integer occurencies, String javalangObject) { - String result = content; - Pattern usedAsNonGenericPattern = Pattern.compile(className.replace(".", "\\.") + "(?[^a-zA-Z\\d^\\.^\\$^\\<])"); - Matcher matcher = usedAsNonGenericPattern.matcher(result); - - if (!matcher.find()) + int end = i; + while (end < length && isGenericTypeNameChar(content.charAt(end))) { + end++; + } + + String token = content.substring(i, end); + String classSuffix = suffixByName.get(token); + + // A use already followed by '<' carries its type arguments; one at + // the very end of the file has no following character, which the + // original pattern also required. + if (classSuffix != null && end < length && content.charAt(end) != '<') { + if (reported.add(token)) { + System.out.println(String.format( + "Appending %s to occurrences of class %s without passed generic types", + classSuffix, token)); + } + result.append(token).append(classSuffix); + replaced = true; + } else { + result.append(token); + } + + i = end; + } + + if (!replaced) { return content; + } + return result.toString(); + } + + /** + * The characters that continue a type name. Deliberately excludes '_', + * matching the original pattern, whose trailing class `[^a-zA-Z\\d.$<]` + * treated an underscore as a terminator. + */ + private static boolean isGenericTypeNameChar(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') || c == '.' || c == '$'; + } + + private static String buildGenericArgumentSuffix(int occurencies, String javalangObject) { List arguments = new ArrayList<>(); for (int i = 0; i < occurencies; i++) { arguments.add(javalangObject); } - String classSuffix = "<" + String.join(",", arguments) + ">"; - - System.out.println(String.format("Appending %s to occurrences of class %s without passed generic types", classSuffix, className)); - - String replaceString = String.format("%s%s$1", className, classSuffix); - result = matcher.replaceAll(replaceString); - return result; + return "<" + String.join(",", arguments) + ">"; } private String getExtendsLine(JavaClass currClass, TypeDefinition typeDefinition) { @@ -790,7 +1026,12 @@ private Type[] getArgumentTypes(Method m) { Type[] types = new Type[referenceTypes.size()]; types = referenceTypes.toArray(types); return types; - } catch (ClassCastException classCast) { + } catch (ClassCastException | IllegalArgumentException parseFailure) { + // A generic signature the parser does not model -- an + // intersection bound () + // is the common one. The erased descriptor is always valid and + // always available, so fall back to it rather than failing the + // whole run over one method. return m.getArgumentTypes(); } } @@ -826,7 +1067,7 @@ private Type getFieldType(Field f) { } try { return GenericUtilities.getType(typeSignature); - } catch (ClassCastException classCast) { + } catch (ClassCastException | IllegalArgumentException parseFailure) { return f.getType(); } } @@ -843,7 +1084,11 @@ private Type getReturnType(Method m) { if(isVoid.matcher(returnSignature).matches()){ return m.getReturnType(); // returning void } - return GenericUtilities.getType(returnSignature); + try { + return GenericUtilities.getType(returnSignature); + } catch (ClassCastException | IllegalArgumentException parseFailure) { + return m.getReturnType(); + } } } return m.getReturnType(); @@ -939,6 +1184,52 @@ private String getMethodName(Method m) { return name; } + /** + * Whether a class can be written out at all. + * + *

Every segment of the qualified name becomes either a namespace or a + * class identifier, so all of them have to be identifiers. Kotlin breaks this + * in both directions: it mangles some internal classes with a leading dash + * ({@code -Base64}), and synthesises names with empty segments, which come + * out as {@code export module {}. Neither is reachable from JavaScript, so + * they are skipped rather than emitted as unparseable declarations. + */ + private static boolean isEmittableClassName(String qualifiedName) { + if (qualifiedName == null || qualifiedName.isEmpty()) { + return false; + } + for (String segment : qualifiedName.replace('$', '.').split("\\.", -1)) { + if (!isValidJsIdentifier(segment)) { + return false; + } + } + return true; + } + + /** + * Whether a name from the bytecode can be used as a TypeScript parameter name. + * + *

Kotlin emits synthetic parameter names that are not identifiers at all -- + * a property setter's parameter is literally {@code } -- and writing + * one out produces a .d.ts the compiler cannot parse. Such parameters fall + * back to paramN, exactly like the interface case below. + */ + private static boolean isValidJsIdentifier(String name) { + if (name == null || name.isEmpty()) { + return false; + } + if (!Character.isJavaIdentifierStart(name.charAt(0)) && name.charAt(0) != '$') { + return false; + } + for (int i = 1; i < name.length(); i++) { + char c = name.charAt(i); + if (!Character.isJavaIdentifierPart(c) && c != '$') { + return false; + } + } + return true; + } + private String getMethodParamSignature(JavaClass clazz, TypeDefinition typeDefinition, Method m) { LocalVariableTable table = m.getLocalVariableTable(); LocalVariable[] variables = table != null ? table.getLocalVariableTable() : null; @@ -956,7 +1247,7 @@ private String getMethodParamSignature(JavaClass clazz, TypeDefinition typeDefin ? variables[localVarIndex] : null; - if (localVariable != null) { + if (localVariable != null && isValidJsIdentifier(localVariable.getName())) { String name = localVariable.getName(); if(reservedJsKeywords.contains(name)){ System.out.println(String.format("Appending _ to reserved JS keyword %s", name)); @@ -1143,13 +1434,19 @@ private void convertToTypeScriptType(Type type, TypeDefinition typeDefinition, S typeName = this.typeOverrides.get(typeName); } + int emittedBaseAt = tsType.length(); if (!typeBelongsInCurrentTopLevelNamespace(typeName) && !typeName.startsWith("java.util.function.") && !isPrivateGoogleApiClass(typeName)) { tsType.append(getAliasedClassName(typeName)); } else { tsType.append(typeName); } - if(type instanceof GenericObjectType) { + // A base that collapsed to `any` takes no type arguments: `any` + // is not parseable TypeScript. Kotlin's synthetic inline-function + // parameters are the usual way to end up here. + boolean baseIsAny = "any".contentEquals(tsType.subSequence(emittedBaseAt, tsType.length())); + + if(!baseIsAny && type instanceof GenericObjectType) { GenericObjectType genericType = (GenericObjectType) type; if (genericType.getNumParameters() > 0) { tsType.append("<"); @@ -1353,45 +1650,125 @@ private void setGlobalAliases() { globalAliases.put("android", "globalAndroid"); } - private List getIgnoredNamespaces(){ - // for some reason these namespaces are references but not existing, so we are replacing all types from these namespaces with "any" - List result = new ArrayList<>(); - - result.add("kotlin"); - result.add("org.jetbrains"); - result.add("org.intellij"); - - result.add("android.app.job"); - result.add("android.app.SharedElementCallback"); - result.add("android.arch"); - result.add("android.content.pm.ShortcutInfo"); - result.add("android.graphics.drawable.Icon"); - result.add("android.graphics.Outline"); - result.add("android.view.SearchEvent"); - result.add("android.view.KeyboardShortcutGroup"); - result.add("android.view.ViewStructure"); - result.add("android.view.textclassifier"); - result.add("android.telephony.mbms"); - result.add("android.text.PrecomputedText"); - result.add("android.media.browse"); - result.add("android.media.session"); - result.add("android.media.AudioAttributes"); - result.add("android.media.MediaMetadata"); - result.add("android.media.Rating"); - result.add("android.service.media"); - result.add("android.print"); - result.add("java.util.function"); - - result.add("com.tom_roush.pdfbox.pdmodel.common.function"); // com.tom_roush:pdfbox-android:1.8.10.0 - - return result; + /** + * Namespaces referenced by the SDK but not shipped in it; every type from + * them becomes `any`. + * + * Built once. It used to be rebuilt -- a fresh ArrayList and 24 adds -- on + * every call, and it is called once per class plus once per pass over the + * document. On android.jar that was tens of thousands of throwaway lists + * and, with the concatenation in isIgnoredNamespace below, roughly 170,000 + * throwaway strings. + */ + private static final List IGNORED_NAMESPACES = List.of( + "kotlin", + "org.jetbrains", + "org.intellij", + "android.app.job", + "android.app.SharedElementCallback", + "android.arch", + "android.content.pm.ShortcutInfo", + "android.graphics.drawable.Icon", + "android.graphics.Outline", + "android.view.SearchEvent", + "android.view.KeyboardShortcutGroup", + "android.view.ViewStructure", + "android.view.textclassifier", + "android.telephony.mbms", + "android.text.PrecomputedText", + "android.media.browse", + "android.media.session", + "android.media.AudioAttributes", + "android.media.MediaMetadata", + "android.media.Rating", + "android.service.media", + "android.print", + "java.util.function", + "com.tom_roush.pdfbox.pdmodel.common.function"); + + /** `namespace + "."`, precomputed; the concatenation used to happen inside the loop. */ + private static final List IGNORED_NAMESPACE_PREFIXES = + IGNORED_NAMESPACES.stream().map(n -> n + ".").collect(Collectors.toList()); + + /** + * Collects the reserved-word packages across the whole classpath, once. + * + *

Discovering them as classes are emitted is not enough: content is + * generated and replaced one class group at a time, so a group processed + * before the offending package is reached keeps raw references to types we + * never declare -- which tsc rejects just as loudly as the bad namespace + * did. Scanning every class name up front makes the ignore list complete + * from the first group on. + */ + private void ensureReservedWordNamespaces() { + if (scannedForReservedWords) { + return; + } + scannedForReservedWords = true; + for (String className : ClassRepo.getAllClassNames()) { + String[] parts = className.split("\\."); + // parts[length-1] is the class name; only package segments matter. + for (int i = 0; i < parts.length - 1; i++) { + if (reservedJsKeywords.contains(parts[i])) { + String prefix = String.join(".", Arrays.copyOf(parts, i + 1)); + if (reservedWordNamespaces.add(prefix)) { + System.out.println(String.format( + "Skipping package %s: \"%s\" is a reserved word and cannot be a TypeScript namespace. " + + "Types under it are typed as any.", + prefix, parts[i])); + } + break; + } + } + } } + private List getIgnoredNamespaces(){ + // for some reason these namespaces are references but not existing, so we are replacing all types from these namespaces with "any" + ensureReservedWordNamespaces(); + if (reservedWordNamespaces.isEmpty()) { + return IGNORED_NAMESPACES; + } + List all = new ArrayList<>(IGNORED_NAMESPACES); + all.addAll(reservedWordNamespaces); + return all; + } + + /** + * A namespace is skipped when it is listed in IGNORED_NAMESPACES, or when + * any of its segments is a word TypeScript will not accept as a namespace + * name. + * + *

A package such as org.apache.commons.lang3.function is emitted as + * "export module function {", which does not parse -- and because the brace + * can never be closed, it takes the rest of the .d.ts down with + * it rather than just its own declarations. Java forbids its own keywords in + * package names, so the collisions that actually reach here are the words + * only JavaScript reserves ({@code function}, {@code in}, {@code with}, + * {@code let}, {@code yield}, ...). Two of them, java.util.function and + * com.tom_roush...common.function, were previously listed by hand above; + * this covers every such package on any classpath, including ones we have + * never seen. + * + *

The types are dropped rather than escaped: an escaped segment no longer + * matches the package path the runtime resolves from metadata, so a name + * that type-checks would fail at runtime. Dropping them yields `any`, which + * is honest about what we know. + */ private boolean isIgnoredNamespace() { String[] namespaceOnlyParts = Arrays.copyOf(namespaceParts, namespaceParts.length-1); String namespace = String.join(".", namespaceOnlyParts); - for (String ignoredNamespace : getIgnoredNamespaces()) { - if(ignoredNamespace.equals(namespace) || namespace.startsWith(ignoredNamespace + ".")) { + for (int i = 0; i < IGNORED_NAMESPACES.size(); i++) { + if (namespace.equals(IGNORED_NAMESPACES.get(i)) + || namespace.startsWith(IGNORED_NAMESPACE_PREFIXES.get(i))) { + return true; + } + } + + for (int i = 0; i < namespaceOnlyParts.length; i++) { + if (reservedJsKeywords.contains(namespaceOnlyParts[i])) { + // Everything from the reserved segment down is unnameable, so + // record that prefix -- not the individual class -- as ignored. return true; } } diff --git a/platforms/android/test-app/build-tools/android-dts-generator/dts-generator/src/main/java/com/telerik/dts/TypeDefinition.java b/platforms/android/test-app/build-tools/android-dts-generator/dts-generator/src/main/java/com/telerik/dts/TypeDefinition.java index 05f1aba02..dace0c5b4 100644 --- a/platforms/android/test-app/build-tools/android-dts-generator/dts-generator/src/main/java/com/telerik/dts/TypeDefinition.java +++ b/platforms/android/test-app/build-tools/android-dts-generator/dts-generator/src/main/java/com/telerik/dts/TypeDefinition.java @@ -98,7 +98,26 @@ private void setTypeGenerics(String typeGenericsSignature) { this.genericDefinitions = new ArrayList<>(); while (matcher.find()) { String label = matcher.group(1); - Type type = GenericUtilities.getType(matcher.group(2) + ";"); + String bound = matcher.group(2); + + // A type parameter may carry an intersection bound -- React Native's + // ReactScrollViewHelper has -- + // which arrives here as the bounds joined by ";:". A TypeScript type + // parameter takes a single constraint, and the first bound is the one + // the erased signature uses, so the rest are dropped. + int intersection = bound.indexOf(";:"); + if (intersection >= 0) { + bound = bound.substring(0, intersection); + } + + Type type; + try { + type = GenericUtilities.getType(bound + ";"); + } catch (IllegalArgumentException unparsed) { + // Some other shape the parser does not model. One unusable + // constraint is not worth failing the whole run over. + continue; + } this.genericDefinitions.add(new GenericDefinition(label, type)); } } diff --git a/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Builder.java b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Builder.java index 6c46b9cb5..3638a4ab3 100644 --- a/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Builder.java +++ b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Builder.java @@ -19,6 +19,14 @@ import com.telerik.metadata.parsing.kotlin.metadata.bytecode.BytecodeClassMetadataParser; import com.telerik.metadata.security.classes.SecuredClassRepository; import com.telerik.metadata.security.classes.SecuredNativeClassDescriptor; +import com.telerik.metadata.security.filtering.blacklisting.MetadataBlacklist; +import com.telerik.metadata.security.filtering.closure.ClosureVerifier; +import com.telerik.metadata.security.filtering.closure.MetadataClosure; +import com.telerik.metadata.security.filtering.closure.ProguardRulesWriter; +import com.telerik.metadata.security.filtering.input.PatternEntry; +import com.telerik.metadata.security.filtering.input.user.UserPatternsCollection; +import com.telerik.metadata.security.filtering.matching.impl.PatternMatcherImpl; +import com.telerik.metadata.security.filtering.whitelisting.MetadataWhitelist; import com.telerik.metadata.storage.functions.FunctionsStorage; import com.telerik.metadata.storage.functions.extensions.ExtensionFunctionsStorage; import com.telerik.metadata.security.MetadataSecurityViolationException; @@ -83,11 +91,16 @@ static TreeNode build(List paths, List classes) throws Exception String[] classNames = SecuredClassRepository.INSTANCE.getClassNames(); + // Before the closure, and deliberately unfiltered: a Kotlin extension + // function is declared on a file class the app never names, so the + // closure can only retain that class if it already knows the function + // exists. Entries for classes that go on to be dropped are inert -- + // the storage is only ever consulted for a class being generated. for (String className : classNames) { try { - SecuredNativeClassDescriptor clazz = SecuredClassRepository.INSTANCE.findClass(className); - if (clazz.isUsageAllowed()) { - tryCollectKotlinExtensionFunctions(clazz.getNativeDescriptor()); + NativeClassDescriptor clazz = SecuredClassRepository.INSTANCE.findClassUnfiltered(className); + if (clazz != null) { + tryCollectKotlinExtensionFunctions(clazz); } } catch (Throwable e) { System.out.println("Skip " + className); @@ -96,6 +109,11 @@ static TreeNode build(List paths, List classes) throws Exception } } + // Grow the whitelist into something the tree can be built from before + // anything is generated. Skipped entirely when no whitelist was given, + // which is what keeps the default behaviour "emit everything". + applyClosureIfRequested(); + for (String className : classNames) { try { // possible exceptions here are: @@ -108,6 +126,7 @@ static TreeNode build(List paths, List classes) throws Exception SecuredNativeClassDescriptor clazz = SecuredClassRepository.INSTANCE.findClass(className); if (clazz.isUsageAllowed()) { + ClosureVerifier.INSTANCE.setCurrentContext(className); generate(clazz.getNativeDescriptor(), root); } } catch (Throwable e) { @@ -117,6 +136,10 @@ static TreeNode build(List paths, List classes) throws Exception } } + ensureSeedPackageSpine(root); + + assertClosureIsSound(); + System.out.println("Added Properties "+TreeNode.addedProperties); System.out.println("Ignored Properties "+TreeNode.skippedProperties+" duplicates."); System.out.println("Added Methods "+TreeNode.addedMethods); @@ -129,6 +152,179 @@ static TreeNode build(List paths, List classes) throws Exception return root; } + // Set from Generator before build() runs. + private static int signatureClosureDepth = Integer.MAX_VALUE; + private static String proguardRulesPath = null; + private static boolean enforceClosure = false; + + public static void configureClosure(int depth, String proguardOut, boolean enforce) { + signatureClosureDepth = depth; + proguardRulesPath = proguardOut; + enforceClosure = enforce; + } + + /** + * Turns the whitelist into the concrete set of classes to emit, and writes + * the matching R8 keep rules. + * + * A no-op unless the build asked for it *and* a whitelist was supplied. The + * flag matters as much as the file: only a seed the build generated may be + * grown and then held to the soundness check. An app's own hand-written + * whitelist.mdg is filtered exactly as authored -- widening included -- + * which is how it behaved before the closure existed, and what an app that + * ships one is relying on. + */ + private static void applyClosureIfRequested() { + if (!enforceClosure || !UserPatternsCollection.INSTANCE.getWhitelistProvided()) { + return; + } + + PatternMatcherImpl patternMatcher = new PatternMatcherImpl(); + MetadataWhitelist seedFilter = new MetadataWhitelist( + UserPatternsCollection.INSTANCE.getWhitelistEntries(), patternMatcher); + MetadataBlacklist blacklistFilter = new MetadataBlacklist( + UserPatternsCollection.INSTANCE.getBlacklistEntries(), patternMatcher); + + MetadataClosure closure = new MetadataClosure( + SecuredClassRepository.INSTANCE.getCachedProviders(), + blacklistFilter, + signatureClosureDepth); + + Set retained = closure.compute(descriptor -> { + String simplified = descriptor.getClassName() + .substring(descriptor.getPackageName().length()) + .replace('$', '.'); + while (simplified.startsWith(".")) { + simplified = simplified.substring(1); + } + return seedFilter.isAllowed(descriptor.getPackageName(), simplified).isAllowed(); + }); + + SecuredClassRepository.INSTANCE.applyClosure(retained); + ClosureVerifier.INSTANCE.setEnabled(true); + + System.out.println(String.format( + "Metadata filter: seed %d classes -> retained %d after closure (depth %s).", + closure.getSeedSize(), retained.size(), + signatureClosureDepth == Integer.MAX_VALUE + ? "unbounded" : String.valueOf(signatureClosureDepth))); + + if (proguardRulesPath != null) { + ProguardRulesWriter.INSTANCE.write(new File(proguardRulesPath), retained); + System.out.println("Metadata filter: wrote keep rules to " + proguardRulesPath); + } + } + + /** + * Fails the build if the filter changed the meaning of any signature. + * + * A violation is a latent NoSuchMethodError -- the metadata is well-formed + * and describes a method that does not exist. It would surface only when + * the app happens to call that method, which is exactly the kind of defect + * that ships. The closure is meant to make this set empty; that it is + * checked separately is the point, since a closure bug would otherwise be + * invisible until runtime. + */ + private static void assertClosureIsSound() throws MetadataSecurityViolationException { + Collection violations = ClosureVerifier.INSTANCE.violations(); + if (violations.isEmpty()) { + return; + } + + StringBuilder message = new StringBuilder(); + message.append(String.format( + "Metadata filter is unsound: %d signature type(s) were replaced by a " + + "supertype because the filter dropped them. Each is a " + + "NoSuchMethodError waiting to happen at runtime.%n", + violations.size())); + + int shown = 0; + for (ClosureVerifier.Widening violation : violations) { + if (shown++ == 25) { + message.append(String.format(" … and %d more%n", violations.size() - 25)); + break; + } + message.append(String.format(" %s -> %s (needed by %s)%n", + violation.getRequested(), violation.getSubstituted(), violation.getContext())); + } + + message.append("This is a bug in the closure, not in the app: add the missing edge " + + "to MetadataClosure, or name the type in whitelist.mdg to unblock."); + + throw new MetadataSecurityViolationException(message.toString()); + } + + /** + * Creates an empty package node for every package the seed named outright, + * whether or not any class under it survived. + * + * JS feature-detects with `if (a.b.C)`. Unfiltered, `a.b` is an object and + * a missing `C` reads as undefined -- falsy, and the guard does its job. + * Filter every class out of `a.b` and the package node disappears with + * them, so the same guard throws: + * + * TypeError: Cannot read property 'design' of undefined + * + * An empty package costs a name and a node. Turning a working guard into a + * crash costs the app. + * + * Restricted to packages that exist on the classpath. A package the filter + * emptied must stay visible; a package that was never there must stay + * absent, or the same guard breaks the other way -- it passes, and the + * constructor behind it is undefined. + */ + private static void ensureSeedPackageSpine(TreeNode root) { + if (!UserPatternsCollection.INSTANCE.getWhitelistProvided()) { + return; + } + + Set existingPackages = new HashSet<>(); + for (ClassMapProvider provider : SecuredClassRepository.INSTANCE.getCachedProviders()) { + for (NativeClassDescriptor descriptor : provider.getClassMap().values()) { + String packageName = descriptor.getPackageName(); + while (!packageName.isEmpty() && existingPackages.add(packageName)) { + int idx = packageName.lastIndexOf('.'); + packageName = idx < 0 ? "" : packageName.substring(0, idx); + } + } + } + + for (PatternEntry entry : UserPatternsCollection.INSTANCE.getWhitelistEntries()) { + String packagePattern = entry.getPackagePattern(); + + // Only literal package names; a wildcard does not name a package to create. + if (packagePattern.isEmpty() + || packagePattern.contains("*") + || packagePattern.contains("?")) { + continue; + } + + // Walk the prefixes and stop at the first that does not exist on + // the classpath. `android.support.design` is named by the app but + // was never there; its parent `android.support` was, via another + // library, and is what the guard actually dereferences. + TreeNode node = root; + StringBuilder soFar = new StringBuilder(); + for (String part : packagePattern.split("\\.")) { + if (soFar.length() > 0) { + soFar.append('.'); + } + soFar.append(part); + + if (!existingPackages.contains(soFar.toString())) { + break; + } + + TreeNode child = node.getChild(part); + if (child == null) { + child = node.createChild(part); + child.nodeType = TreeNode.Package; + } + node = child; + } + } + } + private static void tryCollectKotlinExtensionFunctions(NativeClassDescriptor classDescriptor) { if (classDescriptor instanceof KotlinClassDescriptor) { ExtensionFunctionsCollector extensionFunctionsCollector = new BytecodeExtensionFunctionsCollector(new BytecodeClassMetadataParser()); @@ -258,7 +454,7 @@ private static void setMethodsInfo(TreeNode root, TreeNode node, NativeClassDesc } } } - mi.isResolved = countUnique == 1; + mi.isResolved = countUnique == 1 || clazz instanceof KotlinClassDescriptor; NativeTypeDescriptor[] params = ownMethod.getArgumentTypes(); @@ -409,6 +605,15 @@ private static TreeNode getOrCreateNode(TreeNode root, NativeTypeDescriptor type return null; } + // A substitution here rewrites the signature being emitted. Only a + // type that exists on the classpath indicts the filter -- one that + // was never there is widened with or without filtering. + String resolved = clazz.getNativeDescriptor().getClassName(); + if (!resolved.equals(name) + && SecuredClassRepository.INSTANCE.findClassUnfiltered(name) != null) { + ClosureVerifier.INSTANCE.record(name, resolved); + } + node = getOrCreateNode(root, clazz.getNativeDescriptor(), null); } @@ -585,6 +790,14 @@ private static TreeNode createArrayNode(TreeNode root, String className) SecuredNativeClassDescriptor securedNativeClassDescriptor = SecuredClassRepository.INSTANCE.findNearestAllowedClass(name); if (securedNativeClassDescriptor.isUsageAllowed()) { NativeClassDescriptor nativeClassDescriptor = securedNativeClassDescriptor.getNativeDescriptor(); + + // Array element types reach signatures the same way; see + // the sibling check in getOrCreateNode(root, type). + if (!nativeClassDescriptor.getClassName().equals(name) + && SecuredClassRepository.INSTANCE.findClassUnfiltered(name) != null) { + ClosureVerifier.INSTANCE.record(name, nativeClassDescriptor.getClassName()); + } + child.nodeType = nativeClassDescriptor.isInterface() ? TreeNode.Interface : TreeNode.Class; if (nativeClassDescriptor.isStatic()) { diff --git a/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Generator.java b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Generator.java index 6a53cea27..f99b4e799 100644 --- a/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Generator.java +++ b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Generator.java @@ -26,9 +26,35 @@ public class Generator { private static final String MDG_WHITELIST = "whitelist.mdg"; private static final String MDG_BLACKLIST = "blacklist.mdg"; private static final String METADATA_JAVA_OUT = "mdg-java-out.txt"; + private static final String PROGUARD_RULES_OUT = "metadata-keep-rules.pro"; + private static final String CLOSURE_DEPTH_ARGUMENT_BEGINNING = "signatureClosureDepth="; + private static final String ENFORCE_CLOSURE_ARGUMENT_BEGINNING = "enforceClosure="; + private static final String PROGUARD_OUT_ARGUMENT_BEGINNING = "proguardOut="; private static boolean verbose_mode = false; + /* How far signature types are chased when growing the whitelist. Unbounded + * by default: a bounded closure leaves the last level's signatures liable + * to be widened, and a widened signature is a NoSuchMethodError at the call + * site rather than a build error. Overridable only for measurement. */ + private static int signatureClosureDepth = Integer.MAX_VALUE; + + /* Where to write the R8 keep rules. Set by the build; defaults to beside + * the metadata output rather than inside it. */ + private static String proguardRulesPath = null; + + /* Whether to grow the whitelist into a closure and then fail the build if + * that closure is unsound. + * + * Off unless the build says otherwise, because it may only be applied to a + * seed the build generated for it. A hand-written whitelist.mdg is an app + * author's own list, filtered as authored: signature types it does not name + * are widened, which is how a user whitelist has always behaved. Growing it + * would change what the app ships, and holding it to the closure's + * soundness bar would fail a build that was fine before the closure + * existed. */ + private static boolean enforceClosure = false; + /** * @param args arguments */ @@ -60,6 +86,15 @@ public static void main(String[] args) { throw new InvalidParameterException(String.format("You need to pass a file containing a list of jar/class paths, so metadata can be generated for them! %s\n", e.getMessage())); } + // Deliberately not under metadataOutputDir: that directory is the + // app's assets, and the keep rules are build input for R8, not + // something to ship inside the APK. + String proguardOut = proguardRulesPath != null + ? proguardRulesPath + : new File(metadataOutputDir, "../" + PROGUARD_RULES_OUT).getCanonicalPath(); + + Builder.configureClosure(signatureClosureDepth, proguardOut, enforceClosure); + TreeNode root = Builder.build(params, classes); FileOutputStream ovs = new FileOutputStream(new File(metadataOutputDir, "treeValueStream.dat")); @@ -90,6 +125,14 @@ private static void enableFlaggedFeatures(String[] args) { if (arg.startsWith(ANALYTICS_ARGUMENT_BEGINNING)) { String filePath = arg.replace(ANALYTICS_ARGUMENT_BEGINNING, ""); AnalyticsConfiguration.enableAnalytics(filePath); + } else if (arg.startsWith(CLOSURE_DEPTH_ARGUMENT_BEGINNING)) { + signatureClosureDepth = Integer.parseInt( + arg.substring(CLOSURE_DEPTH_ARGUMENT_BEGINNING.length())); + } else if (arg.startsWith(ENFORCE_CLOSURE_ARGUMENT_BEGINNING)) { + enforceClosure = Boolean.parseBoolean( + arg.substring(ENFORCE_CLOSURE_ARGUMENT_BEGINNING.length())); + } else if (arg.startsWith(PROGUARD_OUT_ARGUMENT_BEGINNING)) { + proguardRulesPath = arg.substring(PROGUARD_OUT_ARGUMENT_BEGINNING.length()); } else if (VERBOSE_FLAG_NAME.equals(arg)) { verbose_mode = true; MetadataFilterConsoleLogger.INSTANCE.setEnabled(true); diff --git a/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/classes/SecuredClassRepository.kt b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/classes/SecuredClassRepository.kt index 5df72c25d..b88af5e25 100644 --- a/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/classes/SecuredClassRepository.kt +++ b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/classes/SecuredClassRepository.kt @@ -55,7 +55,40 @@ object SecuredClassRepository { return SecuredNativeClassDescriptor(false, NativeClassDescriptor.Missing) } + /** + * When a closure has been computed, it -- not the raw whitelist -- decides + * what is emitted. The whitelist is only the seed it grew from; a class the + * closure added is required for the tree to be internally consistent, so + * consulting the patterns again here would drop it right back out. + * Null means no filtering was requested and everything is allowed. + */ + private var retainedClasses: Set? = null + + fun applyClosure(retained: Set) { + retainedClasses = retained + } + + fun getRetainedClasses(): Set? = retainedClasses + + fun getCachedProviders(): List = cachedProviders + + /** Bypasses filtering; for the passes that must run before the filter exists. */ + fun findClassUnfiltered(className: String): NativeClassDescriptor? = + findClassFromProviders(className) + private fun isClassUsageAllowed(packageName: String, className: String): Boolean { + val retained = retainedClasses + if (retained != null) { + /* className arrives simplified ($ -> .); the closure is keyed by the + * binary name, which is what the class map uses. */ + val binaryName = if (packageName.isEmpty()) { + className.replace('.', '$') + } else { + packageName + "." + className.replace('.', '$') + } + return binaryName in retained + } + val whitelistFilterResult = whitelist.isAllowed(packageName, className) val blacklistFilterResult = blacklist.isAllowed(packageName, className) diff --git a/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/filtering/closure/ClosureVerifier.kt b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/filtering/closure/ClosureVerifier.kt new file mode 100644 index 000000000..fefd3f9d9 --- /dev/null +++ b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/filtering/closure/ClosureVerifier.kt @@ -0,0 +1,49 @@ +package com.telerik.metadata.security.filtering.closure + +/** + * Independent check that the filter did not quietly change the meaning of any + * signature it emitted. + * + * The closure is supposed to retain every type a retained signature mentions. + * If it misses one, nothing fails at build time: Builder.getOrCreateNode calls + * findNearestAllowedClass, which substitutes the nearest retained *ancestor*. + * `onCreate(Bundle)` becomes `onCreate(Object)`, the metadata is written + * happily, and the app dies at the call site: + * + * java.lang.NoSuchMethodError: no non-static method + * "Landroid/app/Activity;.onCreate(Ljava/lang/Object;)V" + * + * So the substitution *is* the defect, and this records every one. The check + * does not re-derive the closure -- it watches what generation actually did, + * which is why it catches a closure that is wrong as well as one that is + * incomplete. + * + * Only substitutions of types that exist on the classpath count. A type that is + * genuinely absent is widened unfiltered too; that is pre-existing behaviour and + * an app can compile against an SDK it does not ship. + */ +object ClosureVerifier { + + data class Widening(val requested: String, val substituted: String, val context: String) + + private val widenings = LinkedHashMap() + + var enabled: Boolean = false + + /** Set while a class's members are generated, to attribute the violation. */ + var currentContext: String = "" + + fun record(requested: String, substituted: String) { + if (!enabled) return + if (requested == substituted) return + + widenings.putIfAbsent(requested, Widening(requested, substituted, currentContext)) + } + + fun violations(): Collection = widenings.values + + fun reset() { + widenings.clear() + currentContext = "" + } +} diff --git a/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/filtering/closure/MetadataClosure.kt b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/filtering/closure/MetadataClosure.kt new file mode 100644 index 000000000..bab9e556c --- /dev/null +++ b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/filtering/closure/MetadataClosure.kt @@ -0,0 +1,172 @@ +package com.telerik.metadata.security.filtering.closure + +import com.telerik.metadata.ClassMapProvider +import com.telerik.metadata.parsing.NativeClassDescriptor +import com.telerik.metadata.security.filtering.MetadataFilter +import com.telerik.metadata.storage.functions.extensions.ExtensionFunctionsStorage + +/** + * Expands a seed set of class names into one the metadata tree can actually be + * built from. + * + * A whitelist naming exactly the classes an app was observed to use does not + * work: it produces metadata that cannot start the app. Two reasons, both + * measured. + * + * - A retained class's *supertypes* carry most of its members. + * - A retained method's *signature types* must be present **exactly**. When one + * is missing, Builder.getOrCreateNode does not fail; it substitutes the + * nearest retained ancestor via findNearestAllowedClass. That silently + * rewrites `onCreate(Bundle)` to `onCreate(Object)`, which then fails to + * resolve against the real method at runtime: + * + * java.lang.NoSuchMethodError: no non-static method + * "Landroid/app/Activity;.onCreate(Ljava/lang/Object;)V" + * + * So the closure's job is to make widening never happen for anything reachable. + * + * [signatureDepth] bounds how far signature types are chased. Depth 1 keeps the + * signatures of the seed exact; the types pulled in at the last level may still + * have their own signatures widened, which only matters if the app calls into + * them. Unbounded is the safe end and the default -- it converged well short of + * the whole classpath on the test-app, so the size win survives it. + */ +class MetadataClosure( + private val providers: List, + private val blacklist: MetadataFilter, + private val signatureDepth: Int = Int.MAX_VALUE +) { + + /** Reported so the build can print what the closure cost. */ + var seedSize: Int = 0 + private set + + private val byName = HashMap() + + /** outer class name -> its directly nested classes */ + private val nestedByOuter = HashMap>() + + private fun index() { + if (byName.isNotEmpty()) return + + for (provider in providers) { + for ((name, descriptor) in provider.classMap) { + byName.putIfAbsent(name, descriptor) + + val idx = name.lastIndexOf('$') + if (idx > 0) { + nestedByOuter.getOrPut(name.substring(0, idx)) { ArrayList() }.add(name) + } + } + } + } + + /** + * The element type of an arbitrarily nested array signature, or null when + * the signature bottoms out in a primitive. + */ + private fun elementClassName(signature: String): String? { + var s = signature + while (s.startsWith("[")) { + s = s.substring(1) + } + if (!s.startsWith("L") || !s.endsWith(";")) { + return null + } + return s.substring(1, s.length - 1).replace('/', '.') + } + + private fun isBlacklisted(descriptor: NativeClassDescriptor): Boolean { + val simple = descriptor.className + .substring(descriptor.packageName.length) + .replace('$', '.') + .trimStart('.') + return !blacklist.isAllowed(descriptor.packageName, simple).isAllowed + } + + /** + * @param isSeed decides membership of the starting set, by (package, class) + * exactly as the whitelist does. + * @return every class name whose metadata must be emitted. + */ + fun compute(isSeed: (NativeClassDescriptor) -> Boolean): Set { + index() + + val retained = HashSet() + /* Worklist entries carry the remaining signature budget, so a class + * reached at depth 0 still contributes its supertypes -- those are + * unconditional -- but stops pulling in new signature types. */ + val pending = ArrayDeque>() + + fun enqueue(name: String, budget: Int) { + val descriptor = byName[name] ?: return + if (isBlacklisted(descriptor)) return + if (!retained.add(name)) return + pending.addLast(name to budget) + } + + for ((name, descriptor) in byName) { + if (isSeed(descriptor) && !isBlacklisted(descriptor)) { + if (retained.add(name)) { + pending.addLast(name to signatureDepth) + } + } + } + seedSize = retained.size + + while (pending.isNotEmpty()) { + val (name, budget) = pending.removeFirst() + val descriptor = byName[name] ?: continue + + /* Supertypes and interfaces: unconditional. A class without them + * loses the members it inherits, and findNearestAllowedClass would + * silently rebase it on something further up. */ + descriptor.superclassName + .takeIf { it.isNotEmpty() } + ?.let { enqueue(it, budget) } + + for (interfaceName in descriptor.interfaceNames) { + enqueue(interfaceName, budget) + } + + /* The enclosing class, so a nested class is reachable at all, and + * the nested classes, which JS names through their outer. */ + val idx = name.lastIndexOf('$') + if (idx > 0) { + enqueue(name.substring(0, idx), budget) + } + for (nested in nestedByOuter[name].orEmpty()) { + enqueue(nested, budget) + } + + /* Kotlin extension functions appear on the receiver but are + * declared in a file class the app never names -- there is no + * reference anywhere to pull it in, so it has to be looked up from + * the receiver side. Unconditional: dropping it silently removes + * methods from a class that is being kept. */ + for (extension in ExtensionFunctionsStorage.getInstance().retrieveFunctions(name)) { + extension.declaringClass.className + .takeIf { it.isNotEmpty() } + ?.let { enqueue(it, budget) } + } + + if (budget <= 0) continue + val next = if (budget == Int.MAX_VALUE) budget else budget - 1 + + /* Signature types: the step whose absence crashes the app at + * launch rather than at the call site. */ + for (method in descriptor.methods) { + elementClassName(method.returnType.signature)?.let { enqueue(it, next) } + for (argument in method.argumentTypes) { + elementClassName(argument.signature)?.let { enqueue(it, next) } + } + } + + for (field in descriptor.fields) { + elementClassName(field.type.signature)?.let { enqueue(it, next) } + } + } + + return retained + } +} diff --git a/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/filtering/closure/ProguardRulesWriter.kt b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/filtering/closure/ProguardRulesWriter.kt new file mode 100644 index 000000000..3333f2337 --- /dev/null +++ b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/filtering/closure/ProguardRulesWriter.kt @@ -0,0 +1,89 @@ +package com.telerik.metadata.security.filtering.closure + +import java.io.File + +/** + * Writes the retained set out as R8/ProGuard keep rules. + * + * Metadata and dex have to agree. Metadata says which classes JS can name; R8 + * decides which classes survive into the dex. Neither can see the other's + * input -- R8 cannot read JS, and the metadata generator does not shrink code -- + * so the only way they stay consistent is to drive both from the same set. + * + * The rules keep, and nothing else: whatever the closure did not retain is left + * for R8 to remove. So this file is what makes shrinking safe to turn on, not + * an extra restriction on top of it. + * + * Members are kept wholesale for a retained class. Per-member rules would need + * the JS side to have resolved every call exactly, and it cannot -- a computed + * or reflective call names no member at all. Keeping all members of a class the + * app can reach is the conservative half of that trade; the class-level strip + * is where the size comes from. + */ +object ProguardRulesWriter { + + fun write(target: File, retainedClasses: Set) { + target.parentFile?.mkdirs() + + target.bufferedWriter().use { out -> + out.write( + """ + # GENERATED by the NativeScript metadata generator. Do not edit. + # + # Keeps every class reachable from JS, as computed by + # MetadataClosure from the app's own bundle. Classes absent + # here are absent from the metadata too, so JS cannot name + # them and R8 is free to remove them. + # + # Regenerate by rebuilding. + + """.trimIndent() + ) + out.newLine() + + /* The runtime reaches these by name from native code, so no static + * analysis of either side can see the reference. */ + out.write( + """ + -keep class com.tns.** { *; } + -keep class **.R$* { *; } + -keepattributes Signature,InnerClasses,EnclosingMethod,*Annotation* + + # Shrink, but do not rename. Every lookup this runtime makes + # is by name: metadata names classes, and JS names them + # through it. A -keep rule preserves the names of classes it + # matches, but R8 still renames the synthetic classes it + # generates itself -- desugaring moved a static interface + # method into StaticProducer${'$'}-CC and R8 emitted it as B.a, + # which the runtime then could not find. Renaming buys some + # string space; removing unreachable classes is where the + # size comes from, and that still happens. + -dontobfuscate + + # Below minSdk 24 a static interface method cannot live on + # the interface, so desugaring moves the body into a + # synthetic companion -- ${'$'}-CC for D8/R8, ${'$'}${'$'}CC for Bazel. The + # runtime tries the interface and both companion names in + # turn (JEnv::GetInterfaceStaticMethodIDAndJClass), so all + # three have to survive. Nothing in Java calls them, so + # without this R8 removes every one: measured, 18 companion + # classes went to 0 and three specs failed with + # "Could not call static interface method". + -keep class **${'$'}-CC { *; } + -keep class **${'$'}${'$'}CC { *; } + -keepclassmembers interface * { public static ; } + + """.trimIndent() + ) + out.newLine() + + for (name in retainedClasses.sorted()) { + /* $ separates nested classes in the binary name and is also + * ProGuard's own wildcard-free separator -- it needs no + * escaping, but the name must not be split on it either. */ + out.write("-keep class $name { *; }") + out.newLine() + } + } + } +} diff --git a/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/filtering/input/user/UserPatternsCollection.kt b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/filtering/input/user/UserPatternsCollection.kt index a1e4e04c8..279a3fcdd 100644 --- a/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/filtering/input/user/UserPatternsCollection.kt +++ b/platforms/android/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/security/filtering/input/user/UserPatternsCollection.kt @@ -15,6 +15,15 @@ object UserPatternsCollection : PatternsCollection { private set + /** + * Whether a whitelist file was actually supplied. Distinguishes "the app + * asked for everything" from "the app asked for nothing in particular", + * which the default `*:*` entry alone cannot express -- and which decides + * whether the closure runs at all. + */ + var whitelistProvided: Boolean = false + private set + override fun populateWhitelistEntriesFromFile(filePath: String) { val path = Paths.get(filePath) @@ -22,6 +31,7 @@ object UserPatternsCollection : PatternsCollection { return } + whitelistProvided = true whitelistEntries = parseFile(path) + PatternEntry("java.lang", "Object") + PatternEntry("com.tns.gen*", "*") diff --git a/platforms/android/test-app/build-tools/jsparser/js_parser.js b/platforms/android/test-app/build-tools/jsparser/js_parser.js index abada5747..6da2fce85 100644 --- a/platforms/android/test-app/build-tools/jsparser/js_parser.js +++ b/platforms/android/test-app/build-tools/jsparser/js_parser.js @@ -147,6 +147,13 @@ function getFileAst(tsHelpersFilePath) { const ast = babelParser.parse(fileContent, { minify: false, plugins: [ + // "decorators-legacy" is the @babel/parser plugin. The + // "@babel/plugin-proposal-decorators" entry below names a Babel + // *transform*, which the parser does not recognise, so on its own + // decorator syntax fails to parse outright -- which is why only + // downlevelled __decorate([...]) output was ever handled. Kept + // alongside it so nothing that reads this list changes meaning. + "decorators-legacy", [ "@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }, @@ -249,6 +256,10 @@ const astFromFileContent = function (path, data, err) { const parserOptions = { minify: false, plugins: [ + // See the note on the same list in getFileAst: without + // "decorators-legacy" the parser rejects `@NativeClass class X {}` + // outright, so a bundle that keeps its decorators cannot be read. + "decorators-legacy", ["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }], "objectRestSpread", ], @@ -263,7 +274,25 @@ const astFromFileContent = function (path, data, err) { logger.info(`Parsing CommonJS file: ${path}`); } - const ast = babelParser.parse(data.data, parserOptions); + let ast; + try { + ast = babelParser.parse(data.data, parserOptions); + } catch (scriptError) { + // The extension is only a hint, and for bundler output it is often not + // even a JavaScript one -- Metro emits index.android.bundle, and a RAM + // bundle scatters further files beside it. Anything that fails to parse + // as a script is retried as a module before being treated as an error, + // so which name the bundler happened to choose does not decide whether + // the file can be read. + if (isESModule) { + throw scriptError; + } + + logger.info(`Re-parsing as an ES module: ${path}`); + parserOptions.sourceType = "module"; + ast = babelParser.parse(data.data, parserOptions); + } + data.ast = ast; return resolve(data); }); diff --git a/platforms/android/test-app/build-tools/jsparser/tests/native-class-visitor.test.js b/platforms/android/test-app/build-tools/jsparser/tests/native-class-visitor.test.js new file mode 100644 index 000000000..abd34b063 --- /dev/null +++ b/platforms/android/test-app/build-tools/jsparser/tests/native-class-visitor.test.js @@ -0,0 +1,274 @@ +// Covers the native-class visitor: `class X extends android.view.View {}` with +// its decorators still in the source. +// +// Plain node rather than a jasmine spec in specs/, because the specs there +// shell out to gradle and need `npm install` in this directory first. This +// drives the visitor directly against jsparser's own dependencies, so it runs +// with nothing installed beyond what the parser already needs: +// +// node tests/native-class-visitor.test.js +const assert = require("assert"); +const path = require("path"); + +const root = path.resolve(__dirname, ".."); +const babelParser = require(path.join(root, "node_modules/@babel/parser")); +const traverse = require(path.join(root, "node_modules/@babel/traverse")).default; +const es5 = require(path.join(root, "visitors/es5-visitors")); + +function parse(source) { + const ast = babelParser.parse(source, { + minify: false, + plugins: ["decorators-legacy", "objectRestSpread"], + }); + + traverse(ast, { + enter(nodePath) { + es5.es5Visitor(nodePath, { + filePath: "app/subject.js", + fullPathName: "app_subject", + extendDecoratorName: "JavaProxy", + interfacesDecoratorName: "Interfaces", + logger: null, + }); + }, + }); + + return { + named: es5.es5Visitor.getProxyExtendInfo(), + unnamed: es5.es5Visitor.getCommonExtendInfo(), + }; +} + +function fields(row) { + return row.split("*"); +} + +// The override set has to match CallbackHandlers::CollectMethodOverrideNames in +// the runtime, because a content-keyed class name hashes it. Accessors are on +// the prototype and count; the constructor, statics and instance fields are not +// overrides. +{ + const { unnamed, named } = parse(` + @NativeClass + class MyView extends android.view.View { + constructor() { super(); } + onClick(v) { return 1; } + get title() { return "t"; } + set title(v) {} + static helper() {} + handler = () => {}; + onTouch(e) { return false; } + } + `); + + assert.strictEqual(named.length, 0); + assert.strictEqual(unnamed.length, 1); + + const f = fields(unnamed[0]); + assert.strictEqual(f[0], "android.view.View"); + assert.strictEqual(f[4], "MyView", "declared name must match the runtime's extendName"); + assert.deepStrictEqual(f[5].split(","), ["onClick", "title", "onTouch"]); + assert.strictEqual(f[6], "", "an unnamed class must not claim a JavaProxy name"); +} + +// @JavaProxy puts the fully qualified name in the filename field, which is what +// makes hasSpecifiedName true on the generator side. +{ + const { named, unnamed } = parse(` + @JavaProxy("com.example.MyActivity") + @Interfaces([android.view.View.OnClickListener, java.lang.Runnable]) + class MyActivity extends android.app.Activity { + onCreate(b) {} + run() {} + } + `); + + assert.strictEqual(unnamed.length, 0); + assert.strictEqual(named.length, 1); + + const f = fields(named[0]); + assert.strictEqual(f[0], "android.app.Activity"); + assert.strictEqual(f[5], "onCreate,run"); + assert.strictEqual(f[6], "com.example.MyActivity"); + assert.strictEqual(f[8], "android.view.View.OnClickListener,java.lang.Runnable"); +} + +// @NativeClass({ name }) is the same request as @JavaProxy. +{ + const { named } = parse(` + @NativeClass({ name: "com.example.Ticker" }) + class Ticker extends java.lang.Object { + toString() { return "tick"; } + } + `); + + assert.strictEqual(named.length, 1); + assert.strictEqual(fields(named[0])[6], "com.example.Ticker"); +} + +// An undecorated class is not a binding, however native its superclass looks. +// Bundles are full of `class X extends Foo.Bar {}` that has nothing to do with +// Java, which is why the decorator is required rather than inferred. +{ + const { named, unnamed } = parse(` + class NotNative extends Something.Else { + whatever() {} + } + @NativeClass + class AlsoNotNative extends PlainIdentifier { + whatever() {} + } + `); + + assert.strictEqual(named.length, 0); + assert.strictEqual(unnamed.length, 0); +} + +console.log("native-class visitor tests passed"); + +// Java types are reached through `global` in a React Native project, and the +// minifier renames that binding, so the prefix has to be dropped by shape +// rather than by matching a literal "global". +{ + const { unnamed } = parse(` + @NativeClass + class G extends global.java.util.ArrayList { size() { return 0; } } + `); + assert.strictEqual(fields(unnamed[0])[0], "java.util.ArrayList"); +} + +{ + const { unnamed } = parse(` + @NativeClass + class M extends g.java.util.ArrayList { size() { return 0; } } + `); + assert.strictEqual(fields(unnamed[0])[0], "java.util.ArrayList", + "a minified global binding must not end up in the class name"); +} + +// A name that already starts at its root is untouched. +{ + const { unnamed } = parse(` + @NativeClass + class C extends com.example.Widget { go() {} } + `); + assert.strictEqual(fields(unnamed[0])[0], "com.example.Widget"); +} + +console.log("global-prefix tests passed"); + +// The call form. A project without the Babel decorator plugin writes this, and +// it must produce exactly what the decorator form produces. +{ + const { named } = parse(` + const Ticker = JavaProxy('com.example.nsrn.Ticker')( + class Ticker extends global.java.lang.Object { + toString() { return 'ticked'; } + } + ); + `); + assert.strictEqual(named.length, 1, "JavaProxy(...)(class) must emit a binding"); + const f = fields(named[0]); + assert.strictEqual(f[0], "java.lang.Object"); + assert.strictEqual(f[5], "toString"); + assert.strictEqual(f[6], "com.example.nsrn.Ticker"); +} + +// Bare call form, no arguments. +{ + const { unnamed } = parse(` + const L = NativeClass(class MyList extends global.java.util.ArrayList { + size() { return 0; } + }); + `); + assert.strictEqual(unnamed.length, 1); + assert.strictEqual(fields(unnamed[0])[0], "java.util.ArrayList"); + assert.strictEqual(fields(unnamed[0])[4], "MyList"); +} + +// Nested call form: both wrappers have to be seen. +{ + const { named } = parse(` + const B = JavaProxy('com.example.Both')( + Interfaces([java.lang.Runnable, java.util.concurrent.Callable])( + class Both extends global.java.lang.Object { run() {} call() {} } + ) + ); + `); + assert.strictEqual(named.length, 1); + const f = fields(named[0]); + assert.strictEqual(f[6], "com.example.Both"); + assert.strictEqual(f[8], "java.lang.Runnable,java.util.concurrent.Callable"); +} + +// An ordinary call taking a class must not be mistaken for a decorator. +{ + const { named, unnamed } = parse(` + const x = describe(class Plain extends global.java.lang.Object { run() {} }); + `); + assert.strictEqual(named.length, 0); + assert.strictEqual(unnamed.length, 0); +} + +console.log("call-form decorator tests passed"); + +// The shape a bundler actually emits for an imported decorator. This is the +// form that reaches the generator in a real Metro build. +{ + const { named } = parse(` + var Ticker = (0, _$$_REQUIRE(_dependencyMap[5]).JavaProxy)('com.example.nsrn.Ticker')( + class Ticker extends global.java.lang.Object { + toString() { return 'ticked'; } + } + ); + `); + assert.strictEqual(named.length, 1, "a bundled JavaProxy import must be recognised"); + const f = fields(named[0]); + assert.strictEqual(f[0], "java.lang.Object"); + assert.strictEqual(f[5], "toString"); + assert.strictEqual(f[6], "com.example.nsrn.Ticker"); +} + +// Namespace-imported form. +{ + const { unnamed } = parse(` + var L = ns.NativeClass(class MyList extends global.java.util.ArrayList { size() {} }); + `); + assert.strictEqual(unnamed.length, 1); + assert.strictEqual(fields(unnamed[0])[4], "MyList"); +} + +console.log("bundled-import decorator tests passed"); + +// A quoted key is the same override as a bare one. The runtime enumerates own +// property names and cannot tell them apart, so this side must not either -- +// skipping the quoted form gave the two sides different method lists, hence +// different content keys, hence a class rebuilt on device every launch. +{ + const { unnamed } = parse(` + var O = java.lang.Object.extend({ + "toString": function () { return "x"; }, + hashCode: function () { return 1; } + }); + `); + assert.strictEqual(unnamed.length, 1); + assert.deepStrictEqual(fields(unnamed[0])[5].split(","), ["toString", "hashCode"]); +} + +// Keys that genuinely cannot name a Java method stay skipped, and must not +// abort the traversal: bundled vendor code is full of them. +{ + const { unnamed } = parse(` + var O = java.lang.Object.extend({ + ...base, + ["computed" + n]: function () {}, + "not-an-identifier": function () {}, + 3: function () {}, + hashCode: function () { return 1; } + }); + `); + assert.strictEqual(unnamed.length, 1); + assert.deepStrictEqual(fields(unnamed[0])[5].split(","), ["hashCode"]); +} + +console.log("implementation-object key tests passed"); diff --git a/platforms/android/test-app/build-tools/jsparser/visitors/es5-visitors.js b/platforms/android/test-app/build-tools/jsparser/visitors/es5-visitors.js index 44604c550..1fdcfb6d4 100644 --- a/platforms/android/test-app/build-tools/jsparser/visitors/es5-visitors.js +++ b/platforms/android/test-app/build-tools/jsparser/visitors/es5-visitors.js @@ -1,6 +1,10 @@ var es5_visitors = (function () { var types = require("@babel/types"), defaultExtendDecoratorName = "JavaProxy", + // Marks a class that extends a native type. Required on every such class, + // so that this parser never has to guess whether a dotted superclass is a + // Java type or an ordinary JS one. + NATIVE_CLASS_DECORATOR_NAME = "NativeClass", columnOffset = 1, ASTERISK_SEPARATOR = "*", customExtendsArr = [], @@ -41,6 +45,15 @@ var es5_visitors = (function () { traverseInterface(path, config); } + // Native `class X extends android.view.View {}`, decorators intact. + // Everything below this point anchors on transpiler output (__extends, + // swc_inherit_polyfill, .extend). A bundler that emits classes and + // decorators as written -- Metro with Hermes, esbuild targeting esnext -- + // produces none of those, so the class itself has to be an anchor too. + if (types.isClassDeclaration(path) || types.isClassExpression(path)) { + traverseNativeClassDeclaration(path, config); + } + // // Parsed Typescript to ES5 Syntax (normal extend pattern + custom extend pattern) // // anchor is __extends if (types.isIdentifier(path) && path.node.name === "__extends") { @@ -418,7 +431,7 @@ var es5_visitors = (function () { foundInterface = false, interfaceNames = config.interfaceNames; - var currentInterface = interfaceArr.reverse().join("."); + var currentInterface = _stripNonJavaPrefix(interfaceArr.reverse().join(".")); for (var i in interfaceNames) { var interfaceName = interfaceNames[i].trim(); if (interfaceName === currentInterface) { @@ -476,6 +489,307 @@ var es5_visitors = (function () { * Finds the java proxy name from custom class decorator. * Write results in "customExtendsArr" */ + /* + * Handles a native class declaration that still has its decorators: + * + * @NativeClass + * class MyView extends android.view.View { onClick() {} } + * + * @JavaProxy("com.example.MyActivity") + * @Interfaces([android.view.View.OnClickListener]) + * class MyActivity extends android.app.Activity { onCreate() {} } + * + * A class is only reported when it carries one of those two decorators. + * That is the documented rule -- every class extending a native type is + * decorated -- and it keeps this visitor off the many ordinary classes in a + * bundle that merely extend something with a dotted name. + */ + function traverseNativeClassDeclaration(path, config) { + var node = path.node; + + var appliedDecorators = _collectAppliedDecorators(path); + if (appliedDecorators.length === 0) { + return; + } + + if (!node.superClass || !types.isMemberExpression(node.superClass)) { + return; + } + + var extendClass = _stripNonJavaPrefix( + _getWholeName(node.superClass).reverse().join(".") + ); + if (!extendClass || extendClass.indexOf(".") === -1) { + return; + } + + var extendDecoratorName = + config.extendDecoratorName === undefined + ? defaultExtendDecoratorName + : config.extendDecoratorName; + + var javaProxyName = null; + var isNativeClass = false; + var implementedInterfaces = []; + + for (var i = 0; i < appliedDecorators.length; i++) { + var expression = appliedDecorators[i]; + var isCall = types.isCallExpression(expression); + var name = _decoratorName(isCall ? expression.callee : expression); + + if (!name) { + continue; + } + + if (name === extendDecoratorName) { + // @JavaProxy("a.b.C") -- reuse the shared validation so the error text + // is the same one the transpiled path produces. + javaProxyName = _getDecoratorArgument( + { parent: expression }, + config, + extendDecoratorName + ); + } else if (name === config.interfacesDecoratorName) { + if (isCall && expression.arguments.length > 0) { + var interfacesNode = expression.arguments[0]; + if (types.isArrayExpression(interfacesNode)) { + for (var j = 0; j < interfacesNode.elements.length; j++) { + implementedInterfaces.push( + _getWholeInterfaceNameFromInterfacesNode( + interfacesNode.elements[j] + ) + ); + } + } + } + } else if (name === NATIVE_CLASS_DECORATOR_NAME) { + isNativeClass = true; + // @NativeClass({ name: "a.b.C" }) is the same request as @JavaProxy. + if (isCall && expression.arguments.length > 0) { + var options = expression.arguments[0]; + if (types.isObjectExpression(options)) { + for (var k = 0; k < options.properties.length; k++) { + var property = options.properties[k]; + if ( + _getIdentifierKeyName(property) === "name" && + types.isStringLiteral(property.value) + ) { + javaProxyName = property.value.value; + } + } + } + } + } + } + + if (!isNativeClass && javaProxyName === null) { + return; + } + + var overriddenMethodNames = _getOverriddenMethodsFromClassBody(node); + + if (javaProxyName !== null) { + traverseJavaProxyExtend( + javaProxyName, + config, + extendDecoratorName, + extendClass, + overriddenMethodNames, + implementedInterfaces.join(",") + ); + return; + } + + var location = node.loc && node.loc.start; + var lineToWrite = _generateLineToWrite( + "", + extendClass, + overriddenMethodNames, + { + file: config.fullPathName, + line: location ? location.line : "", + column: location ? location.column + columnOffset : "", + className: node.id ? node.id.name : "", + }, + config.fullPathName, + implementedInterfaces.join(",") + ); + + if (config.logger) { + config.logger.info(lineToWrite); + } + + normalExtendsArr.push(lineToWrite); + } + + /* + * The name of the decorator being applied, through whatever the module + * system wrapped it in. + * + * In source it is a bare `JavaProxy`. Once bundled, the same import reads as + * `(0, _$$_REQUIRE(_dependencyMap[5]).JavaProxy)` -- a sequence whose last + * element is a member expression -- and other bundlers produce + * `ns.JavaProxy` or a further call. Only the trailing property name is + * stable across all of them, and it is the one thing minification leaves + * alone, since renaming a property would break the import. + */ + function _decoratorName(node) { + if (!node) { + return undefined; + } + + if (types.isIdentifier(node)) { + return node.name; + } + + if (types.isMemberExpression(node)) { + return node.property && node.property.name; + } + + if (types.isSequenceExpression(node)) { + // `(0, x)` -- the comma operator's value is its last element. + return _decoratorName(node.expressions[node.expressions.length - 1]); + } + + if (types.isCallExpression(node)) { + return _decoratorName(node.callee); + } + + return undefined; + } + + /* + * Every decorator applied to a class, however it was spelled. + * + * `@NativeClass class X {}` keeps its decorators on the class node, but a + * decorator is only syntax: a project without the Babel plugin writes the + * same thing as a call, `NativeClass(class X {})`, and so does any bundler + * that downlevels decorators it cannot emit. Both forms reach the runtime + * identically, so both have to be recognised here or a class is silently + * left without a binding. + * + * The call form nests, `JavaProxy("a.b.C")(Interfaces([...])(class X {}))`, + * so the chain is walked while the class is still the first argument. + * + * What gets collected is the callee, which lands on the same shapes the + * decorator form produces: an Identifier for a bare decorator, a + * CallExpression for one with arguments. + */ + function _collectAppliedDecorators(path) { + var expressions = []; + var node = path.node; + + if (node.decorators) { + for (var i = 0; i < node.decorators.length; i++) { + expressions.push(node.decorators[i].expression); + } + } + + var current = path; + var parent = current.parentPath; + while ( + parent && + types.isCallExpression(parent.node) && + parent.node.arguments.length > 0 && + parent.node.arguments[0] === current.node + ) { + expressions.push(parent.node.callee); + current = parent; + parent = current.parentPath; + } + + return expressions; + } + + /* + * Drops whatever the code used to reach the Java namespace. + * + * Java types are usually written `global.java.util.ArrayList` outside of + * NativeScript's own tooling, because `java` is not a declared global in a + * React Native project. The runtime resolves that to java/util/ArrayList, + * and so must this: left alone, the base class reads as + * `global.java.util.ArrayList`, and after minification as `g.java.util. + * ArrayList`, neither of which names a class the generator can load. + * + * The first segment that looks like a Java root wins. A name already + * starting at its root is returned untouched. + */ + var JAVA_ROOT_PACKAGES = [ + "java", + "javax", + "android", + "androidx", + "dalvik", + "kotlin", + "com", + "org", + "net", + ]; + + function _stripNonJavaPrefix(dottedName) { + if (!dottedName) { + return dottedName; + } + + var segments = dottedName.split("."); + for (var i = 0; i < segments.length; i++) { + if (JAVA_ROOT_PACKAGES.indexOf(segments[i]) !== -1) { + return segments.slice(i).join("."); + } + } + + return dottedName; + } + + /* + * The names a class body puts on the prototype, which is the object the + * runtime is handed as the implementation. + * + * Must agree with CallbackHandlers::CollectMethodOverrideNames in the + * runtime, because a content-keyed class name hashes this set: a name + * collected on one side and not the other produces two different class + * names for one class, which surfaces only as LookedUpClassNotFound. + * + * - "constructor" is skipped; it is on the prototype but is never a Java + * override, and the runtime skips it too. + * - static members live on the constructor, not the prototype. + * - class fields are per-instance, so they are not on the prototype either. + * - getters and setters are, so they count. + * - computed keys cannot be read statically; skipping them costs only the + * pre-generated class, since the runtime still generates one at load. + */ + function _getOverriddenMethodsFromClassBody(node) { + var overriddenMethodNames = []; + + if (!node.body || !node.body.body) { + return overriddenMethodNames; + } + + var members = node.body.body; + for (var i = 0; i < members.length; i++) { + var member = members[i]; + + if (!types.isClassMethod(member) || member.static || member.computed) { + continue; + } + + if (member.kind === "constructor") { + continue; + } + + var keyName = _getIdentifierKeyName(member); + if (keyName === null) { + continue; + } + + if (overriddenMethodNames.indexOf(keyName) === -1) { + overriddenMethodNames.push(keyName); + } + } + + return overriddenMethodNames; + } + function traverseJavaProxyExtend( path, config, @@ -601,7 +915,7 @@ var es5_visitors = (function () { var classNameFromDecorator = isCorrectExtendClassName ? className : ""; lineToWrite = _generateLineToWrite( classNameFromDecorator, - extendClass.reverse().join("."), + _stripNonJavaPrefix(extendClass.reverse().join(".")), overriddenMethodNames, "", config.fullPathName, @@ -632,7 +946,7 @@ var es5_visitors = (function () { }; lineToWrite = _generateLineToWrite( isCorrectExtendClassName ? className : "", - extendClass.reverse().join("."), + _stripNonJavaPrefix(extendClass.reverse().join(".")), overriddenMethodNames, extendInfo, "", @@ -657,18 +971,33 @@ var es5_visitors = (function () { * HELPER METHODS */ - // Returns the Identifier name of a property's key, or null when the property - // is a SpreadElement, has a computed key, or uses a non-Identifier key - // (e.g. StringLiteral, NumericLiteral). Such properties cannot be mapped to - // a Java method binding, so callers should skip them. + // A method name a Java binding could carry, or null when the property cannot + // be mapped to one: a SpreadElement, a computed key, or a literal that is not + // a legal identifier. + // + // A quoted key counts. `{ "toString": function () {} }` is the same override + // as `{ toString: function () {} }` -- the runtime enumerates the object's + // own property names and cannot tell the two apart, so skipping the quoted + // form put this side's method list (and therefore the content key derived + // from it) permanently out of step with the runtime's for any app that writes + // its overrides that way. Minifiers and code generators emit it routinely. + var JAVA_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/; + function _getIdentifierKeyName(property) { if (!property || property.computed) { return null; } - if (!property.key || !types.isIdentifier(property.key)) { + if (!property.key) { return null; } - return property.key.name; + if (types.isIdentifier(property.key)) { + return property.key.name; + } + if (types.isStringLiteral(property.key) && + JAVA_IDENTIFIER.test(property.key.value)) { + return property.key.value; + } + return null; } function getTypeScriptExtendSuperCallLocation(extendPath, config) { @@ -1022,7 +1351,7 @@ var es5_visitors = (function () { extClassArr = _getWholeName(o); - return extClassArr.reverse().join("."); + return _stripNonJavaPrefix(extClassArr.reverse().join(".")); } function _getDecoratorArgument(path, config, customDecoratorName) { diff --git a/platforms/android/test-app/build-tools/metadata-filter/.gitignore b/platforms/android/test-app/build-tools/metadata-filter/.gitignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/platforms/android/test-app/build-tools/metadata-filter/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/platforms/android/test-app/build-tools/metadata-filter/README.md b/platforms/android/test-app/build-tools/metadata-filter/README.md new file mode 100644 index 000000000..875e7e91b --- /dev/null +++ b/platforms/android/test-app/build-tools/metadata-filter/README.md @@ -0,0 +1,88 @@ +# NativeScript Android metadata filter + +This directory contains the build-time metadata filtering tools used by the +Android test application. The filter derives a conservative class seed from +the JavaScript bundle and lets the metadata generator expand that seed into a +safe metadata closure. + +## Purpose + +The complete Android classpath contains substantially more metadata than an +application normally needs. Filtering reduces the generated metadata while +preserving classes required indirectly through: + +- superclass and interface relationships; +- nested and enclosing classes; +- method and field signatures; +- constructors and generated bindings; +- NativeScript runtime-owned requirements; +- unresolved or computed JavaScript access, which widens the retained scope. + +The filter is conservative. If an access cannot be resolved safely, it retains +the narrowest provably safe package or class scope. If no safe scope exists, +the complete relevant member set is retained rather than risking a runtime +metadata miss. + +## Tools + +`harvest.js` scans a JavaScript bundle and emits the names that can be +resolved statically. It records both exact resolved classes and broader roots +for computed or uncertain access. + +`seed.js` converts the harvested bundle, Android manifest, resources, and SBG +bindings into a deterministic seed file. The metadata generator then expands +the seed through its superclass, interface, nested-class, and signature-type +closure. + +`check-runtime-keeplist.js` checks that classes referenced directly by native +runtime source are represented in the runtime keep list. + +## Typical usage + +From the Android test-app directory: + +```bash +node build-tools/metadata-filter/harvest.js \ + app/src/main/assets/app \ + --json build-tools/metadata-filter/harvest.json + +node build-tools/metadata-filter/seed.js \ + app/src/main/assets/app \ + --manifest platforms/android/app/src/main/AndroidManifest.xml \ + --out build-tools/whitelist.mdg + +node build-tools/metadata-filter/check-runtime-keeplist.js \ + ../../../../NativeScript/ffi/jni/jsi \ + ../../../../NativeScript/runtime/android/jsi +``` + +`seed.js` runs the harvester itself, so invoking `harvest.js` separately is +only useful when inspecting or debugging the intermediate JSON. Normal Gradle +builds invoke the seed and closure steps through the metadata-generation task. +The generated JSON, seed, and whitelist are build outputs and should not be +edited manually. + +## Build controls + +Release builds enable filtering by default when the bundle is available. Use +`-PnsFilterMetadata=false` to disable filtering and generate metadata for the +complete classpath. This is useful for compatibility comparisons and +diagnosing a suspected filtering miss. + +Filtering is intentionally disabled when the required bundle analysis input +is unavailable. A partial analysis must not silently produce an incomplete +metadata set. + +## Invariants + +The filter must remain: + +1. deterministic for identical inputs; +2. conservative for computed property access; +3. closed over all referenced types and supertypes; +4. compatible with generated bindings and runtime-owned requirements; and +5. fail-fast when its input or generated closure is inconsistent. + +After changing the filter, rebuild the Android test app and exercise the +metadata-backed Java and Kotlin runtime tests. A successful JavaScript bundle +build alone does not prove that the generated metadata is complete. diff --git a/platforms/android/test-app/build-tools/metadata-filter/check-runtime-keeplist.js b/platforms/android/test-app/build-tools/metadata-filter/check-runtime-keeplist.js new file mode 100644 index 000000000..133437a39 --- /dev/null +++ b/platforms/android/test-app/build-tools/metadata-filter/check-runtime-keeplist.js @@ -0,0 +1,121 @@ +/* + * Checks that RUNTIME_KEEP in seed.js still covers every class the runtime + * resolves by name from native code. + * + * That list was written by reading string literals out of the C++. Nothing kept + * the two in step, so a class added to the runtime later would simply go + * missing from filtered metadata -- and the failure would surface as a + * TypeError in an app, not as anything the build could see. This turns that + * into a test. + * + * usage: node check-runtime-keeplist.js … + */ + +const fs = require("fs"); +const path = require("path"); + +/* Mirrors PatternMatcherImpl in the metadata generator: '*' spans anything, + * '?' one character. Iterative, because the generator's own matcher is a + * recursive one and this runs over a lot of candidates. */ +function wildcardMatch(pattern, input) { + let p = 0, i = 0, star = -1, mark = 0; + while (i < input.length) { + if (p < pattern.length && (pattern[p] === "?" || pattern[p] === input[i])) { + p++; i++; + } else if (p < pattern.length && pattern[p] === "*") { + star = p++; mark = i; + } else if (star !== -1) { + p = star + 1; i = ++mark; + } else { + return false; + } + } + while (p < pattern.length && pattern[p] === "*") p++; + return p === pattern.length; +} + +function loadKeepList(seedPath) { + const src = fs.readFileSync(seedPath, "utf8"); + const start = src.indexOf("const RUNTIME_KEEP = ["); + if (start === -1) throw new Error("RUNTIME_KEEP not found in " + seedPath); + const end = src.indexOf("];", start); + const body = src.slice(src.indexOf("[", start), end + 1); + + return eval(body).map((entry) => { + const idx = entry.indexOf(":"); + return { pkg: entry.slice(0, idx), cls: entry.slice(idx + 1) }; + }); +} + +function covered(keep, fqn) { + const idx = fqn.lastIndexOf("."); + if (idx < 0) return true; + const pkg = fqn.slice(0, idx); + const cls = fqn.slice(idx + 1); + return keep.some((k) => wildcardMatch(k.pkg, pkg) && wildcardMatch(k.cls, cls)); +} + +function collectSources(dir, out) { + for (const entry of fs.readdirSync(dir)) { + const full = path.join(dir, entry); + const st = fs.statSync(full); + if (st.isDirectory()) collectSources(full, out); + else if (/\.(cpp|h|hpp|cc)$/.test(entry)) out.push(full); + } + return out; +} + +function main() { + const dirs = process.argv.slice(2); + if (dirs.length === 0) { + console.error("usage: check-runtime-keeplist.js …"); + process.exit(2); + } + + const seedPath = path.join(__dirname, "seed.js"); + const keep = loadKeepList(seedPath); + + /* Java class names as the runtime writes them: "java/lang/Object" for JNI + * FindClass, or dotted for the reflective paths. */ + const literal = /"((?:java|javax|android|androidx|dalvik|kotlin|org|com|net)[./][A-Za-z0-9_./$]*)"/g; + + const missing = new Map(); + const files = []; + for (const dir of dirs) { + if (fs.existsSync(dir)) collectSources(dir, files); + } + + for (const file of files) { + const src = fs.readFileSync(file, "utf8"); + let m; + while ((m = literal.exec(src)) !== null) { + const fqn = m[1].replace(/\//g, "."); + + /* Package prefixes and signature fragments are not class names. */ + if (fqn.endsWith(".")) continue; + const last = fqn.slice(fqn.lastIndexOf(".") + 1); + if (!last || last[0] < "A" || last[0] > "Z") continue; + + if (!covered(keep, fqn)) { + if (!missing.has(fqn)) missing.set(fqn, file); + } + } + } + + console.error(`checked ${files.length} runtime source files against ${keep.length} keep patterns`); + + if (missing.size > 0) { + console.error( + `\nRUNTIME_KEEP in ${seedPath} does not cover ${missing.size} class(es) the runtime ` + + `names directly. Filtered metadata would omit them:\n` + ); + for (const [fqn, file] of missing) { + console.error(` ${fqn}\n ${file}`); + } + process.exit(1); + } + + console.error("RUNTIME_KEEP covers every class name the runtime resolves."); +} + +main(); diff --git a/platforms/android/test-app/build-tools/metadata-filter/harvest.js b/platforms/android/test-app/build-tools/metadata-filter/harvest.js new file mode 100644 index 000000000..8614a9b94 --- /dev/null +++ b/platforms/android/test-app/build-tools/metadata-filter/harvest.js @@ -0,0 +1,397 @@ +/* + * Harvests the Java types a JS program can be seen to use, so the build can + * generate a metadata whitelist from them. + * + * The output is deliberately two-sided. `resolved` is what the analysis could + * name exactly. `roots` is what it could only place -- a package it is sure the + * program reaches into but whose class it cannot name, because the access was + * computed. A filter built from `resolved` alone would be wrong. Every + * construct this file cannot resolve must widen `roots`, never fail silently; + * that asymmetry is the whole safety argument. + */ + +const fs = require("fs"); +const path = require("path"); +const parser = require("@babel/parser"); +const traverse = require("@babel/traverse").default; + +/* + * There is deliberately no list of "the packages a Java name can start with". + * + * Such a list is a guess about a classpath it cannot see, and it rots: a + * dependency under me.*, de.*, ru.* or dev.* is invisible to it, and its + * classes then go missing from the metadata with no warning. The test-app + * already contained one casualty -- `in.tns.tests.JavascriptKeywordClass`, + * whose root is a JS keyword. + * + * Instead any dotted chain shaped like a Java name is harvested, and the + * decision about what is real is left to the generator, which holds the actual + * class graph. A pattern matching no class costs a line in a file; a class + * missing from the seed costs a TypeError in the app. Only the second is a bug, + * so this errs entirely in the first direction. + */ + +// Kept only to recognise the JS globals whose members are never Java, so the +// seed does not fill up with `Math:floor`-shaped noise. Not a correctness +// filter: anything wrongly admitted simply matches nothing. +const JS_GLOBAL_OBJECTS = [ + "Math", "JSON", "Object", "Array", "String", "Number", "Boolean", "Date", + "RegExp", "Promise", "Symbol", "Reflect", "Proxy", "Map", "Set", "WeakMap", + "WeakSet", "Error", "TypeError", "console", "process", "globalThis", +]; + +/* + * Names for the global object itself. A chain rooted at one of these is + * unwrapped rather than rejected: the segment after it is the real root. + */ +const HOST_GLOBAL_ALIASES = ["global", "globalThis", "window", "self"]; + +/* + * Property names that appear in every JS program and in no Java package path. + * Used only to keep the *package alias* heuristic from turning `a.length` into + * a whitelist entry. Purely cosmetic: an alias wrongly emitted matches no + * package and retains nothing, but the seed is easier to review without a + * thousand of them, and the generator's wildcard matcher is recursive. + */ +const JS_MEMBER_NOISE = [ + "length", "call", "apply", "bind", "prototype", "constructor", "message", + "source", "global", "name", "value", "type", "state", "props", "target", + "current", "default", "exports", "children", "style", "data", "id", "key", + "index", "parent", "node", "next", "prev", "push", "pop", "slice", "split", + "join", "concat", "filter", "map", "reduce", "forEach", "then", "catch", +]; + +/* A chain that could plausibly be a package path: several conventional, + * lowercase, non-noise segments. */ +function looksLikePackagePath(segments) { + if (segments.length < 2) return false; + return segments.every( + (s) => /^[a-z][a-z0-9_]*$/.test(s) && s.length > 1 && JS_MEMBER_NOISE.indexOf(s) === -1 + ); +} + +const PARSER_PLUGINS = [ + "jsx", + "flow", + "decorators-legacy", + "classProperties", + "classPrivateProperties", + "classPrivateMethods", + "objectRestSpread", + "asyncGenerators", + "dynamicImport", + "optionalChaining", + "nullishCoalescingOperator", + "numericSeparator", + "logicalAssignment", + "topLevelAwait", + "bigInt", +]; + +/* A dotted name is a class name if any segment starts with an uppercase + * letter; the segments before it are the package. Java's own convention is the + * only signal available, and it is the same one the metadata tree encodes. */ +function splitPackageAndClass(segments) { + for (let i = 0; i < segments.length; i++) { + const s = segments[i]; + if (s && s[0] >= "A" && s[0] <= "Z") { + return { + packageName: segments.slice(0, i).join("."), + className: segments.slice(i).join("."), + classIndex: i, + }; + } + } + return null; +} + +/* + * A chain is Java-shaped when at least one all-lowercase package segment + * precedes an initial-capital class segment. Requiring the package segment is + * what keeps `Math.floor` and `JSON.stringify` out -- and it matters beyond + * noise: an entry with an empty package pattern matches *every* package in the + * generator's matcher, so `:Foo` would quietly retain `anything.Foo`. + */ +function isJavaShaped(segments) { + if (segments.length < 2) return false; + if (JS_GLOBAL_OBJECTS.indexOf(segments[0]) !== -1) return false; + + const split = splitPackageAndClass(segments); + return split != null && split.classIndex >= 1; +} + +/* + * A root package whose name is a JS keyword cannot be a global identifier, so + * MetadataNode::CreateTopLevelNamespaces exposes it prefixed with '$' -- + * `in.tns.foo.Bar` is reached from JS as `$in.tns.foo.Bar`. The prefix is + * therefore a positive signal that a chain is Java, and the only way to see + * such a package at all. + */ +function unmangleRoot(name) { + return name.length > 1 && name[0] === "$" ? name.slice(1) : name; +} + +class Harvest { + constructor() { + /* fully-qualified name -> Set of reasons, kept for the report only */ + this.resolved = new Map(); + /* package prefix -> Set of reasons; the easing set */ + this.roots = new Map(); + /* things worth a human's attention, with file/line */ + this.unresolved = []; + } + + addResolved(fqn, reason) { + if (!this.resolved.has(fqn)) this.resolved.set(fqn, new Set()); + this.resolved.get(fqn).add(reason); + } + + addRoot(pkg, reason) { + if (!pkg) return; + if (!this.roots.has(pkg)) this.roots.set(pkg, new Set()); + this.roots.get(pkg).add(reason); + } + + addUnresolved(kind, detail, loc) { + this.unresolved.push({ kind, detail, ...loc }); + } +} + +/* + * Walks a MemberExpression outward from its innermost object, returning the + * static prefix and whether the chain was cut short by a computed access. + * `java.lang.System.out` yields ["java","lang","System","out"]; `java[pkg].Foo` + * yields ["java"] with truncated=true, which is the case that produces a root + * hint instead of a name. + */ +function staticChain(node) { + const segments = []; + let truncated = false; + let cur = node; + + /* Descend to the root identifier first. */ + const stack = []; + while (cur && cur.type === "MemberExpression") { + stack.push(cur); + cur = cur.object; + } + + if (!cur || cur.type !== "Identifier") { + return null; + } + + /* `global.java.lang.System` is the same chain as `java.lang.System`: the + * runtime installs the package roots on the global object, and in a module + * (where `java` is not a declared binding) reaching them through `global` is + * the form TypeScript accepts. Left in place the root becomes a segment, and + * the entry is `global.java.lang:System` -- which matches no package, so the + * class is silently dropped from the seed. */ + let rootIndex = stack.length - 1; + if (HOST_GLOBAL_ALIASES.indexOf(cur.name) !== -1 && rootIndex >= 0) { + const outermost = stack[rootIndex]; + const promoted = outermost.computed + ? (outermost.property.type === "StringLiteral" ? outermost.property.value : null) + : (outermost.property.type === "Identifier" ? outermost.property.name : null); + if (promoted == null) { + /* global[expr] tells us nothing at all. */ + return null; + } + cur = { name: promoted }; + rootIndex--; + } + + /* A '$'-prefixed root is a JS-keyword package; recover the real name. */ + segments.push(unmangleRoot(cur.name)); + const keywordPrefixed = cur.name !== segments[0]; + + for (let i = rootIndex; i >= 0; i--) { + const m = stack[i]; + if (m.computed) { + /* obj["Literal"] is still static; obj[expr] is not. */ + if (m.property.type === "StringLiteral") { + segments.push(m.property.value); + continue; + } + truncated = true; + break; + } + if (m.property.type !== "Identifier") { + truncated = true; + break; + } + segments.push(m.property.name); + } + + return { segments, truncated, keywordPrefixed }; +} + +function harvestFile(filePath, source, harvest) { + let ast; + const attempt = (sourceType) => + parser.parse(source, { + sourceType, + allowReturnOutsideFunction: true, + allowAwaitOutsideFunction: true, + allowSuperOutsideMethod: true, + errorRecovery: true, + plugins: PARSER_PLUGINS, + }); + + try { + ast = attempt("unambiguous"); + } catch (e) { + try { + ast = attempt("script"); + } catch (e2) { + /* A file that cannot be parsed is a file whose Java usage is entirely + * invisible. That must widen the filter to everything, not narrow it. */ + harvest.addUnresolved("parse-error", `${e2.message}`, { file: filePath }); + return false; + } + } + + const rel = filePath; + + traverse(ast, { + MemberExpression(p) { + /* Only consider the outermost member expression of a chain. */ + if (p.parent && p.parent.type === "MemberExpression" && p.parent.object === p.node) { + return; + } + + const chain = staticChain(p.node); + if (!chain) return; + + const split = splitPackageAndClass(chain.segments); + + if (chain.truncated) { + /* We know the program reaches into some package but not which class. + * Record the all-lowercase prefix as a root hint, so the package is + * kept whole. Restricted to chains that got at least two segments in, + * which is what separates `java.lang[name]` from `foo[bar]`. */ + const pkgSegments = []; + for (const s of chain.segments) { + if (s && s[0] >= "A" && s[0] <= "Z") break; + pkgSegments.push(s); + } + if (!chain.keywordPrefixed && !looksLikePackagePath(pkgSegments)) return; + harvest.addRoot(pkgSegments.join("."), `computed access in ${rel}`); + harvest.addUnresolved("computed-member", chain.segments.join(".") + "[…]", { + file: rel, + line: p.node.loc && p.node.loc.start.line, + }); + return; + } + + if (!split) { + /* All-lowercase chain: a package object being passed around, e.g. + * `var l = java.lang`. Everything under it is reachable. */ + if (!looksLikePackagePath(chain.segments)) return; + harvest.addRoot(chain.segments.join("."), `package alias in ${rel}`); + harvest.addUnresolved("package-alias", chain.segments.join("."), { + file: rel, + line: p.node.loc && p.node.loc.start.line, + }); + return; + } + + if (!isJavaShaped(chain.segments)) return; + + /* Trailing segments after the class name are members/nested classes. + * Keep the class and, for a nested name, its outer class too. */ + const classSegments = split.className.split("."); + const outer = split.packageName + ? split.packageName + "." + classSegments[0] + : classSegments[0]; + harvest.addResolved(outer, `member access in ${rel}`); + + /* android.R.id.foo and friends: the second uppercase segment is a nested + * class, not a field. Emitting both is harmless -- the filter is a + * union -- and missing one is not. */ + if (classSegments.length > 1 && classSegments[1][0] >= "A" && classSegments[1][0] <= "Z") { + harvest.addResolved(outer + "." + classSegments[1], `nested access in ${rel}`); + } + }, + + StringLiteral(p) { + /* Class.forName("…"), loadClass("…"), and every other name-by-string + * path. Cheap to include and impossible to see any other way. */ + const v = p.node.value; + if (v.length < 3 || v.indexOf(".") === -1 || /\s/.test(v)) return; + const segments = v.split("."); + if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segments[0])) return; + if (!isJavaShaped(segments)) return; + harvest.addResolved(v, `string literal in ${rel}`); + }, + }); + + return true; +} + +function collectFiles(target, out) { + const stat = fs.statSync(target); + if (stat.isFile()) { + out.push(target); + return out; + } + for (const entry of fs.readdirSync(target)) { + if (entry === "node_modules" || entry.startsWith(".")) continue; + const full = path.join(target, entry); + const st = fs.statSync(full); + if (st.isDirectory()) collectFiles(full, out); + else if (/\.(js|mjs|cjs|jsx|bundle|jsbundle)$/.test(entry)) out.push(full); + } + return out; +} + +function main() { + const args = process.argv.slice(2); + if (args.length < 1) { + console.error("usage: harvest.js [more…] [--json out.json]"); + process.exit(2); + } + + let jsonOut = null; + const targets = []; + for (let i = 0; i < args.length; i++) { + if (args[i] === "--json") { + jsonOut = args[++i]; + } else { + targets.push(args[i]); + } + } + + const harvest = new Harvest(); + const files = []; + for (const t of targets) collectFiles(t, files); + + let parsed = 0; + for (const f of files) { + const source = fs.readFileSync(f, "utf8"); + if (harvestFile(f, source, harvest)) parsed++; + } + + const result = { + filesSeen: files.length, + filesParsed: parsed, + resolved: Array.from(harvest.resolved.keys()).sort(), + roots: Array.from(harvest.roots.keys()).sort(), + unresolved: harvest.unresolved, + }; + + if (jsonOut) { + fs.writeFileSync(jsonOut, JSON.stringify(result, null, 2)); + } + + console.error( + `harvest: ${parsed}/${files.length} files parsed, ` + + `${result.resolved.length} resolved names, ${result.roots.length} root hints, ` + + `${result.unresolved.length} unresolved sites` + ); + + if (!jsonOut) { + for (const n of result.resolved) console.log(n); + } +} + +main(); diff --git a/platforms/android/test-app/build-tools/metadata-filter/seed.js b/platforms/android/test-app/build-tools/metadata-filter/seed.js new file mode 100644 index 000000000..ee28616e9 --- /dev/null +++ b/platforms/android/test-app/build-tools/metadata-filter/seed.js @@ -0,0 +1,284 @@ +/* + * Turns a harvest of the app's JS into the seed whitelist the metadata + * generator grows its closure from. + * + * The seed is deliberately generous. Everything here is a *lower* bound on what + * the app needs -- the generator widens it further (supertypes, nested classes, + * signature types) before anything is dropped. The rules below are the ones + * that cannot be derived from the class graph and so have to be decided here: + * + * - what the JS names, exactly -> resolved harvest + * - what the JS reaches but does not name -> root hints, as pkg:* + * - what Android instantiates by name -> the manifest + * - what the runtime itself resolves -> the keep-list below + * + * When in doubt this file keeps more. Over-inclusion costs bytes; the + * under-inclusion failures are launch crashes. + */ + +const fs = require("fs"); +const path = require("path"); +const { execFileSync } = require("child_process"); + +/* + * Classes the runtime resolves from native code or by reflection. No analysis + * of either the JS or the class graph can see these references, so they are + * listed by hand -- and they live here, next to the runtime they belong to, + * rather than in any individual app's config. + * + * Derived from the string literals in NativeScript/ffi/jni/jsi and + * NativeScript/runtime/android/jsi; keep in step when that changes. The + * Throwable and reflect entries are there because exceptions and members + * arrive at JS as objects whose type the app never mentions. + */ +const RUNTIME_KEEP = [ + "com.tns:*", + "java.lang:Object", + "java.lang:Class", + "java.lang:String", + "java.lang:CharSequence", + "java.lang:Number", + "java.lang:Boolean", + "java.lang:Byte", + "java.lang:Character", + "java.lang:Short", + "java.lang:Integer", + "java.lang:Long", + "java.lang:Float", + "java.lang:Double", + "java.lang:Void", + "java.lang:Enum", + "java.lang:Iterable", + "java.lang:Runnable", + "java.lang:Thread", + "java.lang:ClassLoader", + "java.lang:StackTraceElement", + /* every Throwable: they reach JS as thrown objects, named nowhere */ + "java.lang:Throwable", + "java.lang:Exception", + "java.lang:RuntimeException", + "java.lang:Error", + "java.lang:*Exception", + "java.lang:*Error", + "java.lang.reflect:*", + "java.lang.annotation:*", + "java.io:*Exception", + "java.util:*Exception", + "java.nio:ByteBuffer", + "java.nio:ByteOrder", + "java.nio:Buffer", + "dalvik.system:*", + "android.os:Bundle", + "android.os:Looper", + "android.os:Handler", + "android.os:Process", + "android.app:Activity", + "android.app:Application", + "android.content:Context", + "org.json:*", +]; + +function parseManifest(manifestPath) { + /* android:name on any component -- Android instantiates these by name, so + * they must exist however the JS is written. Regex rather than an XML + * parser: the attribute is unambiguous and this avoids a dependency. */ + const names = new Set(); + if (!fs.existsSync(manifestPath)) return names; + + const xml = fs.readFileSync(manifestPath, "utf8"); + const re = /android:name\s*=\s*"([^"]+)"/g; + let m; + while ((m = re.exec(xml)) !== null) { + const v = m[1]; + /* Permissions and features share the attribute; only dotted names that + * look like classes are of interest. */ + if (v.indexOf(".") > 0 && !v.startsWith("android.permission") && !v.startsWith("android.hardware")) { + names.add(v.startsWith(".") ? v.slice(1) : v); + } + } + return names; +} + +/* + * Class names reachable from resource XML. The framework instantiates these by + * name and nothing in the JS or the class graph refers to them: + * + * custom view, the tag *is* the class + * fragment + * the generic form of a custom view + * app:layout_behavior="…" CoordinatorLayout behaviours + * the usual place a library hides a class name + * + * Scanned with a regex rather than an XML parser: every one of these is a + * dotted token, and a malformed or exotic file should contribute nothing + * rather than abort the build. + */ +function parseResourceXml(resDir) { + const names = new Set(); + if (!resDir || !fs.existsSync(resDir)) return names; + + const files = []; + const walk = (dir) => { + for (const entry of fs.readdirSync(dir)) { + const full = path.join(dir, entry); + const st = fs.statSync(full); + if (st.isDirectory()) walk(full); + else if (entry.endsWith(".xml")) files.push(full); + } + }; + walk(resDir); + + /* A dotted, Java-shaped token: lowercase package segments then a class. */ + const fqn = /\b([a-z][a-zA-Z0-9_]*(?:\.[a-z][a-zA-Z0-9_]*)*\.[A-Z][a-zA-Z0-9_$]*)\b/g; + + for (const file of files) { + const xml = fs.readFileSync(file, "utf8"); + + /* Custom view tags: */ + const tag = /<\s*([a-z][a-zA-Z0-9_]*(?:\.[a-z][a-zA-Z0-9_]*)*\.[A-Z][a-zA-Z0-9_$]*)[\s/>]/g; + let m; + while ((m = tag.exec(xml)) !== null) names.add(m[1]); + + /* Attribute values that name a class. */ + const attr = /(?:android:name|class|app:layout_behavior|android:value|android:targetClass)\s*=\s*"([^"]+)"/g; + while ((m = attr.exec(xml)) !== null) { + let v = m[1]; + if (v.indexOf(".") <= 0) continue; + fqn.lastIndex = 0; + const hit = fqn.exec(v); + if (hit && hit[1] === v) names.add(v); + } + } + + return names; +} + +function toEntry(fqn) { + const segments = fqn.split("."); + for (let i = 0; i < segments.length; i++) { + if (segments[i] && segments[i][0] >= "A" && segments[i][0] <= "Z") { + return segments.slice(0, i).join(".") + ":" + segments.slice(i).join("."); + } + } + return null; +} + +function main() { + const args = process.argv.slice(2); + let out = null; + let manifest = null; + let bindings = null; + const targets = []; + /* Paths whose parse failure is expected -- the runtime test-app ships files + * that are invalid on purpose, to test how the loader reports them. Nothing + * an ordinary app should ever need. */ + const allowUnparseable = []; + /* Resource directories to scan for class names the framework inflates. */ + const resDirs = []; + + for (let i = 0; i < args.length; i++) { + if (args[i] === "--out") out = args[++i]; + else if (args[i] === "--manifest") manifest = args[++i]; + else if (args[i] === "--sbg-bindings") bindings = args[++i]; + else if (args[i] === "--allow-unparseable") allowUnparseable.push(args[++i]); + else if (args[i] === "--res") resDirs.push(args[++i]); + else targets.push(args[i]); + } + + if (!out || targets.length === 0) { + console.error( + "usage: seed.js … --out whitelist.mdg [--manifest AndroidManifest.xml] " + + "[--res res-dir]… [--sbg-bindings sbg-bindings.txt] [--allow-unparseable path]…" + ); + process.exit(2); + } + + /* Run the harvester in-process so the two cannot disagree about grammar. */ + const harvestJson = path.join(path.dirname(out), ".metadata-harvest.json"); + execFileSync( + process.execPath, + [path.join(__dirname, "harvest.js"), ...targets, "--json", harvestJson], + { stdio: ["ignore", "ignore", "inherit"] } + ); + const harvest = JSON.parse(fs.readFileSync(harvestJson, "utf8")); + + /* A file we could not parse is a file whose Java usage is entirely unknown. + * Filtering on a partial view of the program is how apps break in the field, + * so refuse to emit a seed at all -- the generator then ships everything. */ + const parseErrors = harvest.unresolved + .filter((u) => u.kind === "parse-error") + .filter((u) => !allowUnparseable.some((p) => u.file.indexOf(p) !== -1)); + if (parseErrors.length > 0) { + console.error( + `seed: ${parseErrors.length} file(s) could not be parsed; metadata filtering disabled for this build:` + ); + for (const e of parseErrors.slice(0, 10)) console.error(` ${e.file}: ${e.detail}`); + if (fs.existsSync(out)) fs.unlinkSync(out); + process.exit(0); + } + + const entries = new Set(RUNTIME_KEEP); + + for (const fqn of harvest.resolved) { + const e = toEntry(fqn); + if (e) entries.add(e); + } + + /* The easing rule. A computed access or a package alias tells us which + * package the program reaches into but not which class; keep the package + * whole rather than guess. This is also what preserves feature-detection + * guards like `if (android.support.design && …)`, which throw when an + * intermediate package node disappears rather than merely being empty. */ + for (const root of harvest.roots) { + entries.add(root + ":*"); + } + + for (const name of parseManifest(manifest)) { + const e = toEntry(name); + if (e) entries.add(e); + } + + let resourceNames = 0; + for (const resDir of resDirs) { + for (const name of parseResourceXml(resDir)) { + const e = toEntry(name); + if (e) { + entries.add(e); + resourceNames++; + } + } + } + + /* SBG bindings: the base class and interfaces of everything the app extends. + * Field 0 is the base, field 8 the interface list. */ + if (bindings && fs.existsSync(bindings)) { + for (const line of fs.readFileSync(bindings, "utf8").split("\n")) { + if (!line.trim()) continue; + const f = line.split("*"); + for (const candidate of [f[0], ...(f[8] ? f[8].split(",") : [])]) { + const name = (candidate || "").trim().replace(/\//g, "."); + if (!name) continue; + const e = toEntry(name); + if (e) entries.add(e); + } + } + } + + const sorted = Array.from(entries).sort(); + fs.writeFileSync( + out, + "# GENERATED from the app's JS bundle. Do not edit.\n" + + "# Seed only -- the metadata generator computes the closure over\n" + + "# supertypes, nested classes and signature types before filtering.\n" + + sorted.join("\n") + + "\n" + ); + + console.error( + `seed: ${sorted.length} entries ` + + `(${harvest.resolved.length} named, ${harvest.roots.length} eased to whole packages, ` + + `${resourceNames} from resources)` + ); +} + +main(); diff --git a/platforms/android/test-app/build-tools/metadata-filter/verify-coverage.js b/platforms/android/test-app/build-tools/metadata-filter/verify-coverage.js new file mode 100644 index 000000000..c4e1d8c50 Binary files /dev/null and b/platforms/android/test-app/build-tools/metadata-filter/verify-coverage.js differ diff --git a/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/ContentKey.java b/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/ContentKey.java new file mode 100644 index 000000000..f03c38b72 --- /dev/null +++ b/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/ContentKey.java @@ -0,0 +1,105 @@ +package org.nativescript.staticbindinggenerator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * The build-time half of content-keyed binding names. + * + * This must agree byte for byte with MetadataNode::CreateContentKey in the + * runtime -- the whole scheme rests on both sides deriving the same name from + * the same class, with no shared coordinate system to fall back on. A change + * here without the matching change there does not fail the build; it fails on + * device as LookedUpClassNotFound. ContentKeyTest pins the pairing. + * + * Sorted because the runtime reads JS own-property order and this side reads + * source order, and the generated binding treats both as unordered sets. + * FNV-1a because it is trivially reproducible in C++; String.hashCode would not + * survive the trip and java.util.zip checksums are not defined over this input. + */ +public final class ContentKey { + + private static final long FNV_OFFSET_BASIS = 0xcbf29ce484222325L; + private static final long FNV_PRIME = 0x100000001b3L; + + private ContentKey() { + } + + public static String of(String baseClassName, String[] interfaceNames, String[] methodNames) { + String payload = payload(baseClassName, interfaceNames, methodNames); + + long hash = FNV_OFFSET_BASIS; + for (byte b : payload.getBytes(java.nio.charset.StandardCharsets.UTF_8)) { + hash ^= (b & 0xffL); + hash *= FNV_PRIME; + } + + return String.format("h%016x", hash); + } + + static String payload(String baseClassName, String[] interfaceNames, String[] methodNames) { + StringBuilder builder = new StringBuilder(); + builder.append(canonicalize(baseClassName)); + builder.append('|'); + builder.append(String.join(",", sortedNonEmpty(interfaceNames))); + builder.append('|'); + builder.append(String.join(",", sortedNonEmpty(methodNames))); + return builder.toString(); + } + + /** + * Package and nested-type separators both collapse to '.'. + * + * This side reads names out of JS source, where a nested type is written + * with a dot and is indistinguishable from a package; the runtime holds them + * in binary form, with '/' between packages and '$' before a nested type. + * The dotted form is therefore the only one both sides can produce. Leaving + * '$' alone cost every class implementing a nested interface a content key + * that disagreed with the runtime's, so none of them resolved. + */ + private static String canonicalize(String name) { + return name == null ? "" : name.replace('/', '.').replace('$', '.'); + } + + /** + * An absent field in sbg-bindings.txt splits to a single empty string rather + * than to nothing, so the empties are dropped here; the runtime never has + * them to begin with. + */ + private static List sortedNonEmpty(String[] values) { + List result = new ArrayList(); + if (values != null) { + for (String value : values) { + if (value != null && !value.isEmpty()) { + // Canonicalized before sorting, not after: '$' sorts after + // '.', so the two sides would otherwise order a nested + // interface differently even once the names matched. + result.add(canonicalize(value)); + } + } + } + // Ordered by UTF-8 bytes, not by String.compareTo: the runtime sorts + // std::string, which compares bytes, and UTF-16 order diverges from + // UTF-8 order above the BMP. Identical for ASCII identifiers, which is + // every realistic name -- but the two sides have no way to discover a + // disagreement short of a missing class at runtime. + Collections.sort(result, new java.util.Comparator() { + @Override + public int compare(String left, String right) { + byte[] a = left.getBytes(java.nio.charset.StandardCharsets.UTF_8); + byte[] b = right.getBytes(java.nio.charset.StandardCharsets.UTF_8); + int shared = Math.min(a.length, b.length); + for (int i = 0; i < shared; i++) { + int diff = (a[i] & 0xff) - (b[i] & 0xff); + if (diff != 0) { + return diff; + } + } + return a.length - b.length; + } + }); + return result; + } +} diff --git a/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/Generator.java b/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/Generator.java index 801527d9f..acdaf6f19 100644 --- a/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/Generator.java +++ b/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/Generator.java @@ -96,22 +96,26 @@ public Generator(File outputDir, List libs, boolean suppressCallJSMetho public void writeBindings(String filename) throws IOException, ClassNotFoundException { fileSystemHelper.cleanPreviouslyAutoGeneratedSbgFiles(this.outputDir); Binding[] bindings = generateBindings(filename); - Set writtenFiles = new HashSet(); + // Keyed by what was generated, not by what is on disk: the file carries + // AUTO_GENERATED_FILE_PROLOGUE and getContent() does not, so reading it + // back to compare never matched and every duplicate name threw -- + // including duplicates that were byte-identical and safe to share. + // + // Sharing is the normal case under content-keyed naming, where two + // extend() sites with the same base, interfaces and overrides are meant + // to resolve to one generated class. + Map writtenFiles = new HashMap(); for (Binding b : bindings) { - if (writtenFiles.add(b.getFile())) { + String alreadyWritten = writtenFiles.get(b.getFile()); + + if (alreadyWritten == null) { try (PrintStream ps = new PrintStream(b.getFile())) { ps.append(AUTO_GENERATED_FILE_PROLOGUE); ps.append(b.getContent()); } - // A file with that name has already been written - } else { - // Compare text contents for equality - String content = new String(Files.readAllBytes(Paths.get(b.getFile().toString()))); - if (content.equals(b.getContent())) { - System.out.println("Warning: File already exists. This could mean the same code has been parsed more than once from two or more different files. \nFile:" + b.getFile()); - } else { - throw new IOException("File already exists. This may lead to undesired behavior.\nPlease change the name of one of the extended classes.\nFile:" + b.getFile() + " Class: " + b.getClassname()); - } + writtenFiles.put(b.getFile(), b.getContent()); + } else if (!alreadyWritten.equals(b.getContent())) { + throw new IOException("File already exists. This may lead to undesired behavior.\nPlease change the name of one of the extended classes.\nFile:" + b.getFile() + " Class: " + b.getClassname()); } } } @@ -153,9 +157,21 @@ public Binding generateBinding(DataRow dataRow, HashSet interfaceNames) name = getSimpleClassname(clazz.getClassName()); } else { name = getSimpleClassname(clazz.getClassName().replace("$", "_")) + "_"; - // name of the class: last portion of the full file name + line + column + variable name - String[] lastFilePathPart = dataRow.getFile().split("_"); - name += lastFilePathPart[lastFilePathPart.length - 1] + "_" + dataRow.getLine() + "_" + dataRow.getColumn() + "_" + dataRow.getNewClassName(); + if (InputParameters.getCurrent().getContentKeyedBindings()) { + // Named after what the binding is made of, with the extend + // name kept in front when there is one. Must match the + // runtime's MetadataNode::ExtendMethodCallback exactly -- + // including the case where the name is absent. + String extendName = dataRow.getNewClassName(); + if (extendName != null && !extendName.isEmpty()) { + name += extendName + "_"; + } + name += ContentKey.of(dataRow.getBaseClassname(), dataRow.getInterfaces(), dataRow.getMethods()); + } else { + // name of the class: last portion of the full file name + line + column + variable name + String[] lastFilePathPart = dataRow.getFile().split("_"); + name += lastFilePathPart[lastFilePathPart.length - 1] + "_" + dataRow.getLine() + "_" + dataRow.getColumn() + "_" + dataRow.getNewClassName(); + } } } diff --git a/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/GetInterfaceNames.java b/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/GetInterfaceNames.java index dbf0250b5..4ab049b23 100644 --- a/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/GetInterfaceNames.java +++ b/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/GetInterfaceNames.java @@ -34,6 +34,17 @@ public static void generateInterfaceFile(List rows) String pathToDependency = dr.getRow(); if (pathToDependency.endsWith(".jar")) { generateInterfaceNames(pathToDependency, interfacesList); + } else { + // The app's own compiled classes arrive as a directory, not a + // jar. Skipping those meant an interface the app declares was + // never in this list, so the parser did not recognise + // `new com.tns.tests.InterfaceOne({...})` as implementing one + // and no binding was pre-generated -- the class was built on + // device instead, which works but costs a dex at startup. + File dir = new File(pathToDependency); + if (dir.isDirectory()) { + generateInterfaceNamesFromDirectory(dir, interfacesList); + } } }; @@ -46,6 +57,34 @@ public static void generateInterfaceFile(List rows) out.close(); } + private static void generateInterfaceNamesFromDirectory(File dir, List interfacesList) { + File[] entries = dir.listFiles(); + if (entries == null) { + return; + } + + for (File entry : entries) { + if (entry.isDirectory()) { + generateInterfaceNamesFromDirectory(entry, interfacesList); + continue; + } + if (!entry.getName().endsWith(CLASS_EXT)) { + continue; + } + // Unreadable or half-written class files are skipped rather than + // fatal: compileAppClassesForSbg is best-effort by design, so a + // source it could not compile must not take the build down here. + try (FileInputStream in = new FileInputStream(entry)) { + JavaClass clazz = new ClassParser(in, entry.getName()).parse(); + if (clazz.isInterface()) { + interfacesList.add(clazz.getClassName().replace('$', '.')); + } + } catch (IOException | RuntimeException e) { + // ignored -- see above + } + } + } + private static void generateInterfaceNames(String pathToJar, List interfacesList) { if (pathToJar == null) { return; diff --git a/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/InputParameters.java b/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/InputParameters.java index 1408df4b5..86ad6d48e 100644 --- a/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/InputParameters.java +++ b/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/InputParameters.java @@ -7,17 +7,22 @@ public class InputParameters { // if the flag is passed the generation will exit on error private static final String THROW_ON_ERROR = "-throw-on-error"; private static final String LINE_COL_PRIMJS = "-line-column-primjs"; + // name generated classes after their content instead of their call site; + // must match NS_CONTENT_KEYED_BINDINGS in the runtime + private static final String CONTENT_KEYED_BINDINGS = "-content-keyed-bindings"; private static InputParameters current = new InputParameters(); private boolean showDeprecationWarnings; private boolean throwOnError; private boolean lineColumnPrimjs; + private boolean contentKeyedBindings; public InputParameters() { this.showDeprecationWarnings = false; this.throwOnError = false; this.lineColumnPrimjs = false; + this.contentKeyedBindings = false; } public void setShowDeprecationWarnings(boolean value) { @@ -44,6 +49,14 @@ public boolean getLineColumnPrimjs() { return lineColumnPrimjs; } + public void setContentKeyedBindings(boolean value) { + this.contentKeyedBindings = value; + } + + public boolean getContentKeyedBindings() { + return contentKeyedBindings; + } + public static void parseCommand(String[] args) { InputParameters inputParameters = new InputParameters(); @@ -62,6 +75,10 @@ public static void parseCommand(String[] args) { if (commandArg.equals(LINE_COL_PRIMJS)) { inputParameters.setLineColumnPrimjs(true); } + + if (commandArg.equals(CONTENT_KEYED_BINDINGS)) { + inputParameters.setContentKeyedBindings(true); + } } } diff --git a/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/Main.java b/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/Main.java index c4b96d920..bcf63ff3c 100644 --- a/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/Main.java +++ b/platforms/android/test-app/build-tools/static-binding-generator/src/main/java/org/nativescript/staticbindinggenerator/Main.java @@ -230,7 +230,26 @@ private static boolean isWorkerScript(String currFile) { return false; } + /* + * Bundler output is not always named ".js". Metro writes + * index.android.bundle, and a RAM bundle scatters further files beside it; + * a webpack build writes plain .js. All of them are JavaScript and all of + * them can declare a class that needs a binding, so the extension is + * treated as a hint rather than a gate. The parser confirms what each file + * actually is. + * + * The old form also indexed blindly three characters from the end, which + * threw on any name shorter than that. + */ + private static final String[] JS_FILE_EXTENSIONS = + new String[]{".js", ".mjs", ".cjs", ".bundle", ".jsbundle"}; + private static boolean isJsFile(String fileName) { - return fileName.substring(fileName.length() - 3).equals(".js"); + for (String extension : JS_FILE_EXTENSIONS) { + if (fileName.endsWith(extension)) { + return true; + } + } + return false; } } \ No newline at end of file diff --git a/platforms/android/test-app/build-tools/static-binding-generator/src/test/java/org/nativescript/staticbindinggenerator/test/ContentKeyTest.java b/platforms/android/test-app/build-tools/static-binding-generator/src/test/java/org/nativescript/staticbindinggenerator/test/ContentKeyTest.java new file mode 100644 index 000000000..6a10cc8ed --- /dev/null +++ b/platforms/android/test-app/build-tools/static-binding-generator/src/test/java/org/nativescript/staticbindinggenerator/test/ContentKeyTest.java @@ -0,0 +1,115 @@ +package org.nativescript.staticbindinggenerator.test; + +import org.junit.Test; +import org.nativescript.staticbindinggenerator.ContentKey; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +/** + * Pins the generator's half of the content key against values produced by the + * runtime's MetadataNode::CreateContentKey. + * + * The two implementations are in different languages and cannot call each + * other, so the literals below are the contract between them. If one side is + * changed without the other, this test fails here rather than as a missing + * class on device -- which is the only other place the disagreement shows up. + */ +public class ContentKeyTest { + + @Test + public void matchesRuntimeForOverridesOnly() { + assertEquals("h4b3d4e7b5dcd7eac", + ContentKey.of("com.tns.tests.Button1", new String[]{""}, new String[]{"toString", "echo"})); + } + + @Test + public void matchesRuntimeForInterfaces() { + assertEquals("h43486683deee2d72", + ContentKey.of("java.lang.Object", + new String[]{"java.lang.Runnable", "java.io.Closeable"}, + new String[]{"run", "close"})); + } + + @Test + public void matchesRuntimeForSingleOverride() { + assertEquals("he2246413e8de25b9", + ContentKey.of("android.view.View", new String[]{""}, new String[]{"onClick"})); + } + + /** The runtime's base class name is slash-separated; both must canonicalize. */ + @Test + public void treatsSlashAndDotBasesAlike() { + assertEquals(ContentKey.of("com.tns.tests.Button1", new String[]{""}, new String[]{""}), + ContentKey.of("com/tns/tests/Button1", new String[]{""}, new String[]{""})); + assertEquals("h55d3a3a72d6a9f05", + ContentKey.of("com/tns/tests/Button1", new String[]{""}, new String[]{""})); + } + + /** + * A nested interface is `Pack200$Unpacker` to the runtime and + * `Pack200.Unpacker` in JS source. Both sides collapse '$' to '.', so the + * two spellings have to hash alike -- before this held, every class + * implementing a nested interface was built on device instead. + * + * The literal is the real key for the test-app's + * MyTSTranspiledObjectThatExtendsInterfaces. + */ + @Test + public void treatsNestedAndDottedInterfacesAlike() { + String[] dotted = {"java.util.jar.Pack200.Unpacker", "java.util.Formattable", + "java.util.Observer", "java.util.jar.Pack200.Packer"}; + String[] binary = {"java/util/jar/Pack200$Unpacker", "java.util.Formattable", + "java.util.Observer", "java/util/jar/Pack200$Packer"}; + String[] methods = {"greet", "unpack", "properties", "toString", "formatTo", "update", + "addPropertyChangeListener", "pack", "removePropertyChangeListener"}; + + assertEquals(ContentKey.of("java.lang.Object", dotted, methods), + ContentKey.of("java.lang.Object", binary, methods)); + assertEquals("haa054d88a5def37b", + ContentKey.of("java.lang.Object", dotted, methods)); + } + + /** The same collapse applies to a nested base class. */ + @Test + public void treatsNestedAndDottedBasesAlike() { + assertEquals( + ContentKey.of("com.tns.tests.DummyClass.DummyDerivedClass", new String[]{""}, + new String[]{"toString"}), + ContentKey.of("com/tns/tests/DummyClass$DummyDerivedClass", new String[]{""}, + new String[]{"toString"})); + } + + /** + * An absent field in sbg-bindings.txt splits to one empty string. The runtime + * has no such element, so it has to be dropped rather than hashed as "". + */ + @Test + public void ignoresTheEmptyFieldPlaceholder() { + assertEquals(ContentKey.of("android.view.View", new String[0], new String[]{"onClick"}), + ContentKey.of("android.view.View", new String[]{""}, new String[]{"onClick"})); + } + + /** Order is the one thing the two sides provably disagree on, so it must not count. */ + @Test + public void isOrderIndependent() { + assertEquals(ContentKey.of("android.view.View", new String[]{"a.B", "c.D"}, new String[]{"x", "y"}), + ContentKey.of("android.view.View", new String[]{"c.D", "a.B"}, new String[]{"y", "x"})); + } + + /** Everything that reaches the generated Java must still separate classes. */ + @Test + public void separatesDifferentContent() { + String base = ContentKey.of("android.view.View", new String[0], new String[]{"onClick"}); + assertNotEquals(base, ContentKey.of("android.view.ViewGroup", new String[0], new String[]{"onClick"})); + assertNotEquals(base, ContentKey.of("android.view.View", new String[0], new String[]{"onTouch"})); + assertNotEquals(base, ContentKey.of("android.view.View", new String[]{"a.B"}, new String[]{"onClick"})); + } + + /** The delimiters must not be forgeable from a name that contains them. */ + @Test + public void doesNotConfuseFieldBoundaries() { + assertNotEquals(ContentKey.of("android.view.View", new String[]{"a.B"}, new String[0]), + ContentKey.of("android.view.View", new String[0], new String[]{"a.B"})); + } +} diff --git a/platforms/android/test-app/gradle.properties b/platforms/android/test-app/gradle.properties index 907eb44ba..3bfd6f3fe 100644 --- a/platforms/android/test-app/gradle.properties +++ b/platforms/android/test-app/gradle.properties @@ -1,4 +1,4 @@ -#Thu, 09 Jul 2026 00:31:20 +0500 +#Tue, 18 Aug 2026 23:49:46 +0500 # Project-wide Gradle settings. @@ -26,24 +26,24 @@ NS_DEFAULT_COMPILE_SDK_VERSION=35 NS_DEFAULT_MIN_SDK_VERSION=21 NS_DEFAULT_ANDROID_BUILD_TOOLS_VERSION=8.12.1 -ns_default_androidx_appcompat_version = 1.7.0 -ns_default_androidx_exifinterface_version = 1.3.7 -ns_default_androidx_fragment_version = 1.8.5 -ns_default_androidx_material_version = 1.8.0 -ns_default_androidx_multidex_version = 2.0.1 -ns_default_androidx_transition_version = 1.5.1 -ns_default_androidx_viewpager_version = 1.0.0 -ns_default_asm_util_version = 9.7 -ns_default_asm_version = 9.7 -ns_default_bcel_version = 6.8.2 -ns_default_commons_io_version = 2.6 -ns_default_google_java_format_version = 1.6 -ns_default_gson_version = 2.10.1 -ns_default_json_version = 20180813 -ns_default_junit_version = 4.13.2 -ns_default_kotlin_version = 2.2.20 -ns_default_kotlinx_metadata_jvm_version = 2.2.20 -ns_default_mockito_core_version = 3.0.0 -ns_default_spotbugs_version = 3.1.12 +ns_default_androidx_appcompat_version=1.7.0 +ns_default_androidx_exifinterface_version=1.3.7 +ns_default_androidx_fragment_version=1.8.5 +ns_default_androidx_material_version=1.8.0 +ns_default_androidx_multidex_version=2.0.1 +ns_default_androidx_transition_version=1.5.1 +ns_default_androidx_viewpager_version=1.0.0 +ns_default_asm_util_version=9.7 +ns_default_asm_version=9.7 +ns_default_bcel_version=6.8.2 +ns_default_commons_io_version=2.6 +ns_default_google_java_format_version=1.6 +ns_default_gson_version=2.10.1 +ns_default_json_version=20180813 +ns_default_junit_version=4.13.2 +ns_default_kotlin_version=2.2.20 +ns_default_kotlinx_metadata_jvm_version=2.2.20 +ns_default_mockito_core_version=3.0.0 +ns_default_spotbugs_version=3.1.12 ns_engine=V8-13 diff --git a/platforms/android/test-app/runtime/CMakeLists.txt b/platforms/android/test-app/runtime/CMakeLists.txt index 7e3be5e0b..d12df14e3 100644 --- a/platforms/android/test-app/runtime/CMakeLists.txt +++ b/platforms/android/test-app/runtime/CMakeLists.txt @@ -201,17 +201,20 @@ if (NS_BINDING STREQUAL "jsi") endif () if (QUICKJS OR QUICKJS_NG) - # quickjs-ng, NativeScript-patched, vendored at the repo root and shared with - # the Apple build. The patches (JS_WeakRef_Deref, JS_NewString16, - # JS_ClearWeakRefKeepAlives) are required by quickjs-api.c and exist in no - # upstream release, which is why this is a vendored tree rather than a - # submodule. + # quickjs-ng, NativeScript-patched, shared with the Apple build. The tree is + # a git submodule pinned to an upstream commit; our patches (JS_WeakRef_Deref, + # JS_NewString16, JS_ClearWeakRefKeepAlives -- required by quickjs-api.c and + # in no upstream release) are applied into its work tree by + # scripts/apply_quickjs_patches.js. The pin also fixes BC_VERSION, so the + # bytecode compiler must be built from the same commit; see + # vendor/quickjs/patches/README.md. set(QJS_SOURCE_DIR ${VENDOR_DIR}/quickjs/source_ng) add_compile_definitions(__QUICKJS_NG__) set(SOURCES ${SOURCES} # quickjs - ${QJS_SOURCE_DIR}/cutils.c + # NOTE: no cutils.c -- upstream folded it away after v0.11.0; the + # pinned commit no longer ships it. ${QJS_SOURCE_DIR}/libregexp.c ${QJS_SOURCE_DIR}/libunicode.c ${QJS_SOURCE_DIR}/quickjs.c @@ -457,10 +460,6 @@ if (USE_HOST_OBJECTS) target_compile_definitions(NativeScript PRIVATE USE_HOST_OBJECT) endif () -if (IS_NAPI_MODULE) - target_compile_definitions(NativeScript PRIVATE IS_NAPI_MODULE) -endif () - # Bytecode builds embed app-relative source names (no "file://" scheme). This # lets the JS stack-trace parser (BuildStacktraceFrames) accept those frames and # rebuild the full runtime URL from the app root. Wired from runtime/build.gradle. @@ -468,6 +467,14 @@ if (NS_BYTECODE) target_compile_definitions(NativeScript PRIVATE NS_BYTECODE_ENABLED) endif () +# Name generated bindings after their content rather than their call site. Must +# be passed together with -content-keyed-bindings to the static binding +# generator; the two names are halves of one scheme. Wired from +# runtime/build.gradle. +if (NS_CONTENT_KEYED_BINDINGS) + target_compile_definitions(NativeScript PRIVATE NS_CONTENT_KEYED_BINDINGS) +endif () + # if("${ANDROID_ABI}" MATCHES "armeabi-v7a$" OR "${ANDROID_ABI}" MATCHES "x86$") # # On API Level 19 and lower we need to link with android_support # # because it contains some implementation of functions such as "strtoll" and "strtoul" diff --git a/platforms/android/test-app/runtime/build.gradle b/platforms/android/test-app/runtime/build.gradle index 6422cf287..a360b2ece 100644 --- a/platforms/android/test-app/runtime/build.gradle +++ b/platforms/android/test-app/runtime/build.gradle @@ -10,7 +10,6 @@ if (hasEngine) { jsEngine = engine } def hasHostObjects = true //project.hasProperty("useHostObjects") -def isNapiModule = project.hasProperty("asNapiModule"); // Which runtime to compile. These are two complete parallel implementations, // not a runtime plus a shim: @@ -23,6 +22,13 @@ if (!["napi", "jsi"].contains(bindingLayer)) { throw new GradleException("bindingLayer must be 'napi' or 'jsi', got '${bindingLayer}'") } +// Name generated bindings after what they contain rather than after the call +// site that created them. Off here so the standalone runtime keeps its existing +// names; on by default only where the bundler belongs to an embedder. The static +// binding generator has to be told the same thing -- see -Pcontent-keyed-bindings +// in app/build.gradle. +def contentKeyedBindings = project.hasProperty("content-keyed-bindings") + printf("Compiling NativeScript with %s (runtime: %s).\n", jsEngine, bindingLayer) // Version.h moved under the per-binding runtime tree when the jsi runtime was @@ -190,12 +196,12 @@ android { arguments.add("-DUSE_HOST_OBJECTS=true") } - if (isNapiModule) { - arguments.add("-DIS_NAPI_MODULE=true") - } - arguments.add("-DNS_BINDING=${bindingLayer}") + if (contentKeyedBindings) { + arguments.add("-DNS_CONTENT_KEYED_BINDINGS=1") + } + if (bytecodeStacktraces) { arguments.add("-DNS_BYTECODE=1") } @@ -470,6 +476,13 @@ tasks.register('downloadHermesLibs', Exec) { } } } + doLast { + // Any CMake config generated before this fetch recorded no runtimeFiles + // for the prebuilts, and AGP would keep reusing it -- building green and + // shipping an APK that dlopen-fails at launch. Having had to download at + // all means every existing config predates the libraries. + delete new File(projectDir, ".cxx") + } } task 'setPackageVersion' { diff --git a/platforms/android/test-app/runtime/src/main/java/com/tns/AppConfig.java b/platforms/android/test-app/runtime/src/main/java/com/tns/AppConfig.java index d989f93ca..7193520d2 100644 --- a/platforms/android/test-app/runtime/src/main/java/com/tns/AppConfig.java +++ b/platforms/android/test-app/runtime/src/main/java/com/tns/AppConfig.java @@ -20,7 +20,12 @@ protected enum KnownKeys { ForceLog("forceLog", false), DiscardUncaughtJsExceptions("discardUncaughtJsExceptions", false), EnableLineBreakpoins("enableLineBreakpoints", false), - EnableMultithreadedJavascript("enableMultithreadedJavascript", false); + EnableMultithreadedJavascript("enableMultithreadedJavascript", false), + // Written by the build that ran the static binding generator, not by + // hand: the generated class names and the names the runtime looks up + // are halves of one scheme. Keep this last -- Runtime.cpp reads this + // array by ordinal. + ContentKeyedBindings("contentKeyedBindings", false); private final String name; private final Object defaultValue; @@ -107,6 +112,9 @@ public AppConfig(File appDir) { if (androidObject.has(KnownKeys.EnableMultithreadedJavascript.getName())) { values[KnownKeys.EnableMultithreadedJavascript.ordinal()] = androidObject.getBoolean(KnownKeys.EnableMultithreadedJavascript.getName()); } + if (androidObject.has(KnownKeys.ContentKeyedBindings.getName())) { + values[KnownKeys.ContentKeyedBindings.ordinal()] = androidObject.getBoolean(KnownKeys.ContentKeyedBindings.getName()); + } } } } catch (Exception e) { diff --git a/platforms/android/test-app/runtime/src/main/java/com/tns/DexFactory.java b/platforms/android/test-app/runtime/src/main/java/com/tns/DexFactory.java index 539390b1e..c4a367232 100644 --- a/platforms/android/test-app/runtime/src/main/java/com/tns/DexFactory.java +++ b/platforms/android/test-app/runtime/src/main/java/com/tns/DexFactory.java @@ -124,6 +124,27 @@ public Class resolveClass(String baseClassName, String name, String className // just the interface name without the extra file, line, column information if (!baseClassName.isEmpty() && isInterface) { fullClassName = COM_TNS_GEN_PREFIX + classToProxy; + + // The pre-generated lookup above ran against the name the caller + // asked for, which for an interface still carries the content key + // or extend name. Only here is the canonical one known -- and it is + // the name the static binding generator emits. Without this retry a + // pre-generated interface binding is never found, and every + // interface implementation is dexed on the device even though the + // class is sitting in the APK. + if (!fullClassName.equals(originalFullClassName)) { + try { + Class pregeneratedInterface = classLoader.loadClass(fullClassName.replace("-", "_")); + if (logger.isEnabled()) { + logger.write("Pre-generated interface class found: " + fullClassName); + } + return pregeneratedInterface; + } catch (Exception e) { + if (logger.isEnabled()) { + logger.write("Pre-generated interface class not found: " + fullClassName); + } + } + } } File dexFile = this.getDexFile(desiredDexClassName); diff --git a/platforms/android/test-app/runtime/src/main/java/com/tns/GcListener.java b/platforms/android/test-app/runtime/src/main/java/com/tns/GcListener.java index 0d5a69892..8dc8e7c14 100644 --- a/platforms/android/test-app/runtime/src/main/java/com/tns/GcListener.java +++ b/platforms/android/test-app/runtime/src/main/java/com/tns/GcListener.java @@ -5,11 +5,13 @@ import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; import java.util.ArrayList; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; class GCSubscriber { ConcurrentHashMap, Integer> referencesMap = new ConcurrentHashMap<>(); public final ReferenceQueue referenceQueue = new ReferenceQueue<>(); + boolean unsubscribed; // guarded by this } public class GcListener { @@ -60,7 +62,11 @@ public void run() { for (int i =0; i < objIds.length; i++) { objIds[i] = collectedObjectIds.get(i); } - runtime.notifyGc(objIds); + synchronized (subscriber) { + if (!subscriber.unsubscribed) { + runtime.notifyGc(objIds); + } + } if (runtime.getLogger().isEnabled()) { runtime.getLogger().write("GC", "GC triggered for " + String.valueOf(objIds.length) + " objects"); runtime.getLogger().write("NS.GCListener","Objects referenced after GC: " + subscriber.referencesMap.size()); @@ -152,9 +158,17 @@ public static void subscribe(Runtime runtime) { } } + /** + * Once this returns, no GC thread is inside the runtime's notifyGc and none + * will enter it. Must not be called from inside that runtime's JS scope: + * delivery holds the subscriber's lock while it waits to enter the runtime. + */ public static void unsubscribe(Runtime runtime) { - synchronized (instance) { - instance.subscribers.remove(runtime); + GCSubscriber subscriber = instance.subscribers.remove(runtime); + if (subscriber != null) { + synchronized (subscriber) { + subscriber.unsubscribed = true; + } } } @@ -168,15 +182,18 @@ private void start() { } private void notifyGc() { - synchronized (instance) { -// ManualInstrumentation.Frame frame = ManualInstrumentation.start("GcListener.notifyGc"); - try { - for (Runtime runtime : instance.subscribers.keySet()) { - runtime.notifyGc(); +// ManualInstrumentation.Frame frame = ManualInstrumentation.start("GcListener.notifyGc"); + try { + for (Map.Entry entry : subscribers.entrySet()) { + GCSubscriber subscriber = entry.getValue(); + synchronized (subscriber) { + if (!subscriber.unsubscribed) { + entry.getKey().notifyGc(); + } } - } finally { -// frame.close(); } + } finally { +// frame.close(); } } } diff --git a/platforms/android/test-app/runtime/src/main/java/com/tns/MethodResolver.java b/platforms/android/test-app/runtime/src/main/java/com/tns/MethodResolver.java index 1b456d535..4bebd398e 100644 --- a/platforms/android/test-app/runtime/src/main/java/com/tns/MethodResolver.java +++ b/platforms/android/test-app/runtime/src/main/java/com/tns/MethodResolver.java @@ -359,11 +359,22 @@ private static Tuple isAssignableFrom(Class assignTo, Class success = assignTo.isAssignableFrom(assignFrom); if (success) { - // TODO: consider interfaces as well - Class currClass = assignFrom; - while (!assignTo.equals(currClass) && (currClass != null)) { - dist += 10 * 1000; - currClass = currClass.getSuperclass(); + if (assignTo.isArray() && assignFrom.isArray()) { + Class assignToComponent = assignTo.getComponentType(); + Class assignFromComponent = assignFrom.getComponentType(); + if (!assignToComponent.equals(assignFromComponent)) { + dist += 10 * 1000; + } + } else if (assignTo.isInterface()) { + String interfaceName = assignTo.getName(); + dist += ("java.io.Serializable".equals(interfaceName) || + "java.lang.CharSequence".equals(interfaceName)) ? 30000 : 1000; + } else { + Class currClass = assignFrom; + while (!assignTo.equals(currClass) && (currClass != null)) { + dist += 10 * 1000; + currClass = currClass.getSuperclass(); + } } } } @@ -590,4 +601,4 @@ public Method getMatchingMethodWithArguments(String methodName, Object[] args) { } } } -} \ No newline at end of file +} diff --git a/platforms/android/test-app/runtime/src/main/java/com/tns/Runtime.java b/platforms/android/test-app/runtime/src/main/java/com/tns/Runtime.java index 961a8541a..f08f4d87d 100644 --- a/platforms/android/test-app/runtime/src/main/java/com/tns/Runtime.java +++ b/platforms/android/test-app/runtime/src/main/java/com/tns/Runtime.java @@ -44,6 +44,18 @@ public class Runtime { private native void initNativeScript(int runtimeId, String filesPath, String nativeLibDir, boolean verboseLoggingEnabled, boolean isDebuggable, String packageName, Object[] v8Options, String callingDir, int maxLogcatObjectSize, boolean forceLog); + // The guest counterpart of initNativeScript, implemented by + // @nativescript/react-native rather than by the standalone runtime: instead + // of standing a VM up it binds to `hostRuntimePtr`, a jsi::Runtime the + // embedder created and still owns. Unresolved (and never called) in a + // standalone build, which binds natives lazily on first invocation. + private native void attachNativeScript(int runtimeId, String metadataPath, long hostRuntimePtr, + boolean verboseLoggingEnabled, int maxLogcatObjectSize, boolean forceLog, + String bootstrapScript); + + // Implemented alongside attachNativeScript. + private native void detachNativeScript(int runtimeId); + private native void runModule(int runtimeId, String filePath) throws NativeScriptException; private native Object runScript(int runtimeId, String filePath) throws NativeScriptException; @@ -435,6 +447,91 @@ public static Runtime initializeRuntimeWithConfiguration(StaticConfiguration con return runtime; } + /* + Binds the runtime to a JavaScript runtime an embedder owns, for + @nativescript/react-native. Must be called on the embedder's JS thread: + the native side installs the metadata globals into that runtime straight + away, and jsi may only be touched from the thread the host designates. + + The differences from initializeRuntimeWithConfiguration are all + consequences of being a guest. There is no VM to create. There is no + app/ directory to require from, so Module is not initialized and + ts_helpers.js is not run -- the host's bundler owns module loading. + `metadataPath` is the directory the .dat files were staged into, which + is the only part of the standalone file layout a guest still needs. + */ + public static Runtime attachToHostRuntime(Logger logger, String appName, String nativeLibDir, + File filesDir, ClassLoader classLoader, File dexDir, + String dexThumb, boolean isDebuggable, + long hostRuntimePtr, String metadataPath, + String bootstrapScript) { + // Built here rather than by the caller so that AppConfig, which is + // package-private and reads a package.json a guest does not have, stays + // an implementation detail. Its defaults are what a guest wants anyway. + StaticConfiguration config = new StaticConfiguration(logger, appName, nativeLibDir, + filesDir, filesDir, classLoader, dexDir, dexThumb, new AppConfig(filesDir), + isDebuggable); + return attachToHostRuntime(config, hostRuntimePtr, metadataPath, bootstrapScript); + } + + static Runtime attachToHostRuntime(StaticConfiguration config, long hostRuntimePtr, String metadataPath, + String bootstrapScript) { + staticConfiguration = config; + WorkThreadScheduler scheduler = new WorkThreadScheduler(new Handler(Looper.myLooper())); + DynamicConfiguration dynamicConfiguration = new DynamicConfiguration(0, scheduler, null); + Runtime runtime = new Runtime(config, dynamicConfiguration); + try { + runtime.attach(hostRuntimePtr, metadataPath, bootstrapScript); + } catch (Throwable t) { + // Same rollback as initRuntime: the constructor already registered + // the instance, so a failed bootstrap must not leave a stale one + // reachable through the caches. + runtimeCache.remove(runtime.getRuntimeId()); + currentRuntime.remove(); + GcListener.unsubscribe(runtime); + throw t; + } + return runtime; + } + + private void attach(long hostRuntimePtr, String metadataPath, String bootstrapScript) { + if (initialized) { + throw new RuntimeException("NativeScript is already attached to a runtime"); + } + + this.logger = config.logger; + + AppConfig appConfig = config.appConfig; + if (appConfig != null) { + this.cachedDiscardUncaughtJsExceptions = appConfig.getDiscardUncaughtJsExceptions(); + this.cachedEnableMultithreadedJavascript = appConfig.getEnableMultithreadedJavascript(); + } + + // Generated proxies still need somewhere to be defined and injected: + // extending a Java class from JS works the same way under a host runtime. + this.dexFactory = new DexFactory(logger, config.classLoader, config.dexDir, config.dexThumb, classStorageService, true); + + boolean forceConsoleLog = appConfig != null + && (appConfig.getForceLog() || "timeline".equalsIgnoreCase(appConfig.getProfilingMode())); + int maxLogcatObjectSize = appConfig != null ? appConfig.getMaxLogcatObjectSize() : 1024; + + attachNativeScript(getRuntimeId(), metadataPath, hostRuntimePtr, logger.isEnabled(), maxLogcatObjectSize, forceConsoleLog, + bootstrapScript); + + GcListener.subscribe(this); + + initialized = true; + } + + /* + Unbinds from the embedder's runtime and frees what NativeScript holds in + it. Must run while that runtime is still alive and not running JS. + */ + public void detach() { + detachRuntime(runtimeId); + detachNativeScript(runtimeId); + } + /* Called via JNI from a native-spawned worker thread (WorkerWrapper), after the thread has attached to the JVM and before the worker script runs. @@ -477,11 +574,11 @@ public static void runWorkerLoop() { } /* - Called via JNI from the native worker thread during shutdown, before the - worker's runtime is disposed. + Called before a runtime is disposed: via JNI from the native worker + thread during shutdown, and by detach(). */ @RuntimeCallable - public static void detachWorkerRuntime(int runtimeId) { + public static void detachRuntime(int runtimeId) { Runtime runtime = runtimeCache.remove(runtimeId); if (runtime != null) { GcListener.unsubscribe(runtime); diff --git a/platforms/android/test/react-native/App.tsx b/platforms/android/test/react-native/App.tsx new file mode 100644 index 000000000..cc549cf70 --- /dev/null +++ b/platforms/android/test/react-native/App.tsx @@ -0,0 +1,247 @@ +/** + * The Android conformance fixture for @nativescript/react-native. + * + * Driven by scripts/test_react_native_android.sh, which installs this as an RN + * app's App.tsx, launches it and reads the results off logcat. Every assertion + * exercises something the guest runtime has to get right: resolving the Android + * SDK and the app's own dependencies, constructing and calling Java objects, + * generating real Java subclasses, and marshalling a callback that arrives on + * another thread. + */ +import React from 'react'; +import {SafeAreaView, Text, ScrollView} from 'react-native'; +import NativeScriptNativeApi from '@nativescript/react-native/src/NativeScriptNativeApi'; +import {NativeClass, JavaProxy} from '@nativescript/react-native/src/NativeClass'; + +declare const global: any; + +const MARK = 'NS_ANDROID_SMOKE'; +const lines: string[] = []; + +function record(label: string, fn: () => unknown) { + try { + const value = fn(); + const text = String(value); + // A check that catches its own error and returns a description of it is + // still a failed check. Reporting on "did it throw" alone marked a + // "FAIL ...LookedUpClassNotFound" result as PASS, which is the one thing a + // test harness must never do. + const failed = text.startsWith('FAIL'); + lines.push(`${label} = ${text}`); + console.log(`${MARK} ${failed ? 'FAIL' : 'PASS'} ${label} = ${text}`); + } catch (e: any) { + lines.push(`${label} THREW ${e && e.message}`); + console.log(`${MARK} FAIL ${label} :: ${e && e.message}`); + } +} + +try { + const installed = NativeScriptNativeApi.install(''); + console.log(`${MARK} install -> ${installed}`); + console.log(`${MARK} backend -> ${NativeScriptNativeApi.getRuntimeBackend()}`); + + record('typeof java', () => typeof global.java); + record('typeof android', () => typeof global.android); + record('System.currentTimeMillis', () => + global.java.lang.System.currentTimeMillis(), + ); + record('new java.lang.StringBuilder', () => + new global.java.lang.StringBuilder('ns').append('-ok').toString(), + ); + record('Integer.parseInt', () => global.java.lang.Integer.parseInt('41') + 1); + record('Build.VERSION.SDK_INT', () => global.android.os.Build.VERSION.SDK_INT); + // From a third-party library the app installs, not the Android SDK. + record('androidx.core.view.ViewCompat', () => + typeof global.androidx.core.view.ViewCompat, + ); + + // --- subclassing Java from JS --- + record('ts_helpers.__extends', () => typeof global.__extends); + record('ts_helpers.__createNativeProxy', () => + typeof global.__createNativeProxy, + ); + + record('implement Runnable', () => { + let ran = false; + const r = new global.java.lang.Runnable({ + run() { + ran = true; + }, + }); + r.run(); + return ran; + }); + + record('extend a Java class', () => { + const MyList = global.java.util.ArrayList.extend('NsRnMyList', { + size() { + return 42; + }, + }); + const instance = new MyList(); + return instance.size(); + }); + + // No explicit class name: the runtime keys the generated class on the + // extend() call site (file/line/column), which is engine-sensitive. + record('unnamed extend', () => { + const Anon = global.java.util.ArrayList.extend({ + size() { + return 7; + }, + }); + return new Anon().size(); + }); + + // ES class syntax over a native base -- what a React Native developer would + // reach for first, and the form that on the NativeScript CLI needs + // @NativeClass() plus a TypeScript transformer. + record('class syntax extends', () => { + const MyEsList = NativeClass( + class MyEsList extends global.java.util.ArrayList { + size() { + return 5; + } + }, + ); + const instance = new MyEsList(); + // Java's own view of it: Collections.unmodifiableList calls size() from + // Java, so this only agrees if the generated class really overrides it. + const viaJava = global.java.util.Collections.unmodifiableList(instance).size(); + return `${instance.size()}/${viaJava}`; + }); + + // @NativeClass with several Java interfaces -- a JS class has one base, so + // this is the only way to say "implements Runnable and Callable". + record('@NativeClass multiple interfaces', () => { + // Applied as a function rather than with @-syntax: decorator syntax needs + // @babel/plugin-proposal-decorators, and this is exactly what the legacy + // dialect desugars the decorator to. + const Both = NativeClass({ + interfaces: [ + global.java.lang.Runnable, + global.java.util.concurrent.Callable, + ], + })( + class Both extends global.java.lang.Object { + ran = false; + run() { + this.ran = true; + } + call() { + return new global.java.lang.String('called'); + } + }, + ); + + const instance = new Both(); + + // Ask the generated Java class what it implements. submit() would be + // ambiguous here -- ExecutorService overloads it for both Runnable and + // Callable, and this object is both. + const declared = instance.getClass().getInterfaces(); + const names: string[] = []; + for (let i = 0; i < declared.length; i++) { + names.push(String(declared[i].getName())); + } + const wanted = ['java.lang.Runnable', 'java.util.concurrent.Callable']; + return wanted.every((n) => names.indexOf(n) >= 0) + ? 'both' + : `only ${names.join(',')}`; + }); + + // A fully-qualified name makes the runtime *look up* a pre-generated class + // instead of building one on the device, so this only passes in a release + // build, where the static binding generator has run. In debug it is expected + // to report LookedUpClassNotFound, which is why the check is explicit about + // which build it is looking at rather than simply failing. + record('@JavaProxy resolves a pre-generated class', () => { + try { + const Ticker = JavaProxy('com.example.nsrn.Ticker')( + class Ticker extends global.java.lang.Object { + toString() { + return 'ticked'; + } + }, + ); + + const instance = new Ticker(); + const name = String(instance.getClass().getName()); + if (name !== 'com.example.nsrn.Ticker') { + return `wrong class ${name}`; + } + + // Round-trip through Java so this cannot pass on the JS override alone. + return String(new global.java.lang.StringBuilder().append(instance).toString()); + } catch (e: any) { + const message = String(e && e.message ? e.message : e); + if (message.indexOf('LookedUpClassNotFound') >= 0) { + return __DEV__ ? 'skipped (debug: no build-time generator)' : `FAIL ${message}`; + } + return `FAIL ${message}`; + } + }); + + record('Comparator drives a real Java sort', () => { + const list = new global.java.util.ArrayList(); + list.add(new global.java.lang.Integer(3)); + list.add(new global.java.lang.Integer(1)); + list.add(new global.java.lang.Integer(2)); + const cmp = new global.java.util.Comparator({ + compare(a: any, b: any) { + // The runtime may hand back a JS number rather than an Integer proxy, + // depending on how the boxed value was marshalled. + const av = a && a.intValue ? a.intValue() : Number(a); + const bv = b && b.intValue ? b.intValue() : Number(b); + return av - bv; + }, + }); + global.java.util.Collections.sort(list, cmp); + const at = (i: number) => { + const v = list.get(i); + return v && v.intValue ? v.intValue() : Number(v); + }; + return `${at(0)}${at(1)}${at(2)}`; + }); +} catch (e: any) { + console.log(`${MARK} FATAL ${e && e.message}`); + lines.push(`FATAL ${e && e.message}`); +} +// Cross-thread callback, checked asynchronously. +// +// Deliberately NOT joined: a Java thread calling into JS is marshalled onto the +// JS thread through the runtime's scheduler, so blocking the JS thread waiting +// for that thread would deadlock -- the callback can only run once the JS thread +// is free again. +let threadFlag = ''; +try { + const r = new global.java.lang.Runnable({ + run() { + threadFlag = 'called'; + }, + }); + new global.java.lang.Thread(r).start(); +} catch (e: any) { + threadFlag = `THREW ${e && e.message}`; +} + +setTimeout(() => { + const ok = threadFlag === 'called'; + lines.push(`Runnable on a Java thread = ${threadFlag}`); + console.log( + `${MARK} ${ok ? 'PASS' : 'FAIL'} Runnable on a Java thread = ${threadFlag}`, + ); + console.log(`${MARK}_DONE`); +}, 1500); + +export default function App() { + return ( + + + {lines.map((l, i) => ( + {l} + ))} + + + ); +} diff --git a/platforms/android/tools/bytecode-compiler/README.md b/platforms/android/tools/bytecode-compiler/README.md index db86ff65f..8c059fdf9 100644 --- a/platforms/android/tools/bytecode-compiler/README.md +++ b/platforms/android/tools/bytecode-compiler/README.md @@ -13,8 +13,7 @@ bytecode-compiler/ lib/ index.js engine registry + host resolution hermes.js Hermes adapter (wired end-to-end today) - quickjs.js wired; gated off (ready:false) until runtime read-support lands - quickjs-ng.js + quickjs-ng.js serves both QUICKJS_NG and QUICKJS (the bellard tree was dropped) primjs.js native/ qjs-compile.c blob-emitting shim for QuickJS + QuickJS-NG (JS_* API) @@ -25,9 +24,9 @@ bytecode-compiler/ ## Building the compiler binaries -The host binaries are built by [`.github/workflows/bytecode-compilers.yml`](../../.github/workflows/bytecode-compilers.yml) +The host binaries are built by [`.github/workflows/bytecode-compilers.yml`](../../../../.github/workflows/bytecode-compilers.yml) (manual `workflow_dispatch`). It clones each engine's **upstream** repo -(facebook/hermes, bellard/quickjs, quickjs-ng/quickjs, lynx-family/primjs), builds +(facebook/hermes, quickjs-ng/quickjs, lynx-family/primjs), builds the CLI for every host, and uploads one artifact per `(engine, host)` named `bytecode-compiler--`. Download and drop them into `bin///`. @@ -40,12 +39,14 @@ that links the engine and emits a **NativeScript bytecode container**: [8-byte magic][4-byte format version, little-endian][engine JS_WriteObject payload] ``` -The magic (`NSBCQJS\0`, `NSBCNGS\0`, `NSBCPJS\0`) must match the adapter's `magic` -and the runtime's detection. Hermes uses its native HBC magic instead (no container). +The magic (`NSBCNGS\0`, `NSBCPJS\0`) must match the adapter's `magic` and the +runtime's detection. Hermes uses its native HBC magic instead (no container). > Compatibility: the CLI must be built from the engine ref that matches what the -> runtime bundles (prebuilt `.so` for Hermes/PrimJS, vendored source for -> QuickJS/QuickJS-NG). Pin the workflow inputs to exact commits once validated. +> runtime bundles (prebuilt binaries for Hermes/PrimJS, `vendor/quickjs/source_ng` +> for QuickJS-NG). QuickJS-NG is the strict case: the compiler stamps its own +> `BC_VERSION` and `JS_ReadObject` accepts only the runtime's, so a mismatched ref +> produces blobs that load nowhere. Pin the workflow inputs to exact commits. Only the compiler binary and its command line are engine-specific; that lives in each adapter. Everything else (module wrapping, the raw-file list, idempotency, diff --git a/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/darwin-arm64/nsbc-quickjs-ng b/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/darwin-arm64/nsbc-quickjs-ng old mode 100644 new mode 100755 index cc19d916c..db2ec24e7 Binary files a/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/darwin-arm64/nsbc-quickjs-ng and b/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/darwin-arm64/nsbc-quickjs-ng differ diff --git a/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/darwin-x64/nsbc-quickjs-ng b/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/darwin-x64/nsbc-quickjs-ng old mode 100644 new mode 100755 index 5345c4eca..e9d329ad0 Binary files a/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/darwin-x64/nsbc-quickjs-ng and b/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/darwin-x64/nsbc-quickjs-ng differ diff --git a/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/linux-arm64/nsbc-quickjs-ng b/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/linux-arm64/nsbc-quickjs-ng index 10738b91a..e6a7b0dbf 100644 Binary files a/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/linux-arm64/nsbc-quickjs-ng and b/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/linux-arm64/nsbc-quickjs-ng differ diff --git a/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/linux-x64/nsbc-quickjs-ng b/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/linux-x64/nsbc-quickjs-ng index 8a6aa6e3c..0f829b9f3 100644 Binary files a/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/linux-x64/nsbc-quickjs-ng and b/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/linux-x64/nsbc-quickjs-ng differ diff --git a/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/win32-x64/nsbc-quickjs-ng.exe b/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/win32-x64/nsbc-quickjs-ng.exe index eba600ee5..5eb5a6cb7 100644 --- a/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/win32-x64/nsbc-quickjs-ng.exe +++ b/platforms/android/tools/bytecode-compiler/bin/quickjs-ng/win32-x64/nsbc-quickjs-ng.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:834d7f03640141f65697454ad65bd84012fd68c7583684087c9137001834519e -size 966656 +oid sha256:90ad8876759a850297e98dbc49622b8eda1345d9aa14a14e439ac12e23acd284 +size 967168 diff --git a/platforms/android/tools/bytecode-compiler/bin/quickjs/darwin-arm64/nsbc-quickjs b/platforms/android/tools/bytecode-compiler/bin/quickjs/darwin-arm64/nsbc-quickjs deleted file mode 100644 index c51f953f4..000000000 Binary files a/platforms/android/tools/bytecode-compiler/bin/quickjs/darwin-arm64/nsbc-quickjs and /dev/null differ diff --git a/platforms/android/tools/bytecode-compiler/bin/quickjs/darwin-x64/nsbc-quickjs b/platforms/android/tools/bytecode-compiler/bin/quickjs/darwin-x64/nsbc-quickjs deleted file mode 100644 index c31009163..000000000 Binary files a/platforms/android/tools/bytecode-compiler/bin/quickjs/darwin-x64/nsbc-quickjs and /dev/null differ diff --git a/platforms/android/tools/bytecode-compiler/bin/quickjs/linux-arm64/nsbc-quickjs b/platforms/android/tools/bytecode-compiler/bin/quickjs/linux-arm64/nsbc-quickjs deleted file mode 100644 index ec89bd4f7..000000000 Binary files a/platforms/android/tools/bytecode-compiler/bin/quickjs/linux-arm64/nsbc-quickjs and /dev/null differ diff --git a/platforms/android/tools/bytecode-compiler/bin/quickjs/linux-x64/nsbc-quickjs b/platforms/android/tools/bytecode-compiler/bin/quickjs/linux-x64/nsbc-quickjs deleted file mode 100644 index f50ae842a..000000000 Binary files a/platforms/android/tools/bytecode-compiler/bin/quickjs/linux-x64/nsbc-quickjs and /dev/null differ diff --git a/platforms/android/tools/bytecode-compiler/bin/quickjs/win32-x64/nsbc-quickjs.exe b/platforms/android/tools/bytecode-compiler/bin/quickjs/win32-x64/nsbc-quickjs.exe deleted file mode 100644 index 0f8270237..000000000 --- a/platforms/android/tools/bytecode-compiler/bin/quickjs/win32-x64/nsbc-quickjs.exe +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0e5bd0606548f699b6554c67c603f0f7f928a96fa011b4d6e716ef393f916b0b -size 5207690 diff --git a/platforms/android/tools/bytecode-compiler/compile-bytecode.js b/platforms/android/tools/bytecode-compiler/compile-bytecode.js index f404f6477..56227eb2f 100644 --- a/platforms/android/tools/bytecode-compiler/compile-bytecode.js +++ b/platforms/android/tools/bytecode-compiler/compile-bytecode.js @@ -29,7 +29,15 @@ * node compile-bytecode.js --app --engine [options] * * Options: - * --app Directory to process recursively (required). + * --app Merged assets directory to process (required). Only the + * subtrees named by --roots are touched. + * --roots Comma-separated subdirectories of --app that hold runtime + * JS (default: "app,internal"). Everything else under + * assets/ -- a WebView bundle, a plugin's own payload -- is + * left alone: it is not loaded through the module loader, + * so bytecode there is unreadable to whoever does load it. + * Pass "." to process the directory itself, which is what a + * --app pointing straight at assets/app already implies. * --engine ns_engine value, e.g. HERMES, QUICKJS, QUICKJS_NG, PRIMJS, * V8-13, JSC (required). Engines without a bytecode compiler * are a no-op. @@ -39,6 +47,10 @@ * --compiler Explicit compiler binary path (overrides --bin-dir lookup). * --raw Comma-separated app-relative paths compiled unwrapped * (default: "internal/ts_helpers.js"). + * --keep-source Keep each function's source text in the blob. QuickJS + * strips it by default: it is ~4x the bytecode on a bundle, + * and nothing but Function.prototype.toString() reads it. + * Needed only by code that sniffs toString() output. * --source-maps Emit source maps next to each file (.map) when the * engine's compiler supports them. * --optimize Override the engine's default optimization flag. @@ -49,26 +61,30 @@ * --verbose Log every file processed. */ -'use strict'; +"use strict"; -const fs = require('fs'); -const path = require('path'); -const os = require('os'); -const { spawnSync } = require('child_process'); +const fs = require("fs"); +const path = require("path"); +const os = require("os"); +const { spawnSync } = require("child_process"); -const { adapterForEngine, hostKey } = require('./lib'); +const { adapterForEngine, hostKey } = require("./lib"); // MUST stay identical to ModuleInternal::MODULE_PROLOGUE / MODULE_EPILOGUE. -const MODULE_PROLOGUE = '(function(module, exports, require, __filename, __dirname){ '; -const MODULE_EPILOGUE = '\n})'; +const MODULE_PROLOGUE = + "(function(module, exports, require, __filename, __dirname){ "; +const MODULE_EPILOGUE = "\n})"; function parseArgs(argv) { const opts = { app: null, engine: null, - binDir: path.join(__dirname, 'bin'), + binDir: path.join(__dirname, "bin"), compiler: null, - raw: ['internal/ts_helpers.js'], + raw: ["internal/ts_helpers.js"], + // The runtime loads JS from these two only; see listJsFiles. + roots: ["app", "internal"], + keepSource: false, sourceMaps: false, optimize: undefined, strict: false, @@ -78,39 +94,85 @@ function parseArgs(argv) { for (let i = 2; i < argv.length; i++) { const a = argv[i]; switch (a) { - case '--app': opts.app = argv[++i]; break; - case '--engine': opts.engine = argv[++i]; break; - case '--bin-dir': opts.binDir = argv[++i]; break; - case '--compiler': opts.compiler = argv[++i]; break; - case '--raw': opts.raw = argv[++i].split(',').map((s) => s.trim()).filter(Boolean); break; - case '--source-maps': opts.sourceMaps = true; break; - case '--optimize': opts.optimize = argv[++i]; break; - case '--strict': opts.strict = true; break; - case '--keep-going': opts.keepGoing = true; break; - case '--verbose': opts.verbose = true; break; - default: throw new Error(`Unknown argument: ${a}`); + case "--app": + opts.app = argv[++i]; + break; + case "--roots": + opts.roots = argv[++i] + .split(",") + .map((r) => r.trim()) + .filter(Boolean); + break; + case "--engine": + opts.engine = argv[++i]; + break; + case "--bin-dir": + opts.binDir = argv[++i]; + break; + case "--compiler": + opts.compiler = argv[++i]; + break; + case "--raw": + opts.raw = argv[++i] + .split(",") + .map((s) => s.trim()) + .filter(Boolean); + break; + case "--keep-source": + opts.keepSource = true; + break; + case "--source-maps": + opts.sourceMaps = true; + break; + case "--optimize": + opts.optimize = argv[++i]; + break; + case "--strict": + opts.strict = true; + break; + case "--keep-going": + opts.keepGoing = true; + break; + case "--verbose": + opts.verbose = true; + break; + default: + throw new Error(`Unknown argument: ${a}`); } } - if (!opts.app) throw new Error('--app is required'); - if (!opts.engine) throw new Error('--engine is required'); + if (!opts.app) throw new Error("--app is required"); + if (!opts.engine) throw new Error("--engine is required"); return opts; } -function listJsFiles(dir) { +function listJsFiles(dir, roots) { const out = []; + // Android WebView and other packaged payloads can live under app/assets, + // but they are not NativeScript modules and must remain source files. + // Support both --app= and the historical --app=assets/app + // shape, which falls back to walking ".". + const excludedDirs = new Set([ + path.resolve(dir, "app", "assets"), + path.resolve(dir, "assets"), + ]); const walk = (d) => { for (const entry of fs.readdirSync(d, { withFileTypes: true })) { const full = path.join(d, entry.name); - if (entry.isDirectory()) walk(full); - else if (entry.isFile() && entry.name.endsWith('.js')) out.push(full); + if (entry.isDirectory()) { + if (!excludedDirs.has(path.resolve(full))) walk(full); + } else if (entry.isFile() && entry.name.endsWith(".js")) out.push(full); } }; - walk(dir); + for (const root of roots) { + const start = root === "." ? dir : path.join(dir, root); + if (!fs.existsSync(start)) continue; + walk(start); + } return out; } function readHead(file, n) { - const fd = fs.openSync(file, 'r'); + const fd = fs.openSync(file, "r"); try { const head = Buffer.alloc(n); const read = fs.readSync(fd, head, 0, n, 0); @@ -122,7 +184,7 @@ function readHead(file, n) { /** Normalize to a forward-slashed app-relative path for raw-file matching. */ function relKey(appDir, file) { - return path.relative(appDir, file).split(path.sep).join('/'); + return path.relative(appDir, file).split(path.sep).join("/"); } function resolveCompiler(opts, adapter, host) { @@ -140,8 +202,14 @@ function isPlaceholder(file) { } } -function compileFile(opts, adapter, compiler, file, isRaw, appDir) { - const source = fs.readFileSync(file, 'utf8'); +// `nameBase` is what the embedded source name is relative to, and it is NOT the +// directory being walked: the runtime resolves a relative require() against the +// requiring module's name, and its module root is assets/app. A module named +// "app/shared/Workers/EvalWorker.js" makes "./EvalWorker" resolve under +// app/app/shared/Workers/ and the load fails. Files outside app/ (internal/) are +// run by absolute path, so their name only shows up in stack traces. +function compileFile(opts, adapter, compiler, file, isRaw, nameBase) { + const source = fs.readFileSync(file, "utf8"); const wrapped = isRaw ? source : MODULE_PROLOGUE + source + MODULE_EPILOGUE; // Embed the app-relative path as the module's source name so runtime stack @@ -157,16 +225,17 @@ function compileFile(opts, adapter, compiler, file, isRaw, appDir) { // used here: hermesc must be given a real, openable host file (no URL scheme / // device path), and that prefix is device-specific anyway. The app-relative // path is the identity all engines can embed consistently. - const rel = relKey(appDir, file); - const relParts = rel.split('/'); - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nsbc-')); + const rel = relKey(nameBase, file); + const relParts = rel.split("/"); + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nsbc-")); const tmpSrc = path.join(tmpDir, ...relParts); - const tmpOut = path.join(tmpDir, path.basename(file) + '.bc'); + const tmpOut = path.join(tmpDir, path.basename(file) + ".bc"); const wantMap = opts.sourceMaps && adapter.supportsSourceMaps; try { fs.mkdirSync(path.dirname(tmpSrc), { recursive: true }); fs.writeFileSync(tmpSrc, wrapped); - const optimize = opts.optimize !== undefined ? opts.optimize : adapter.defaultOptimize; + const optimize = + opts.optimize !== undefined ? opts.optimize : adapter.defaultOptimize; const args = adapter.buildArgs({ // Pass the app-relative path (forward-slashed); with cwd = tmpDir the // compiler opens tmpDir/ and embeds "" as the source name. @@ -175,20 +244,41 @@ function compileFile(opts, adapter, compiler, file, isRaw, appDir) { sourceMap: wantMap ? adapter.sourceMapOutput(tmpOut) : null, optimize, }); - const res = spawnSync(compiler, args, { cwd: tmpDir, encoding: 'utf8' }); + // The quickjs shim reads this: strip the per-function source text unless the + // build asked to keep it. Harmless for compilers that ignore it. + const env = { ...process.env }; + if (opts.keepSource) env.NSBC_KEEP_SOURCE = "1"; + else delete env.NSBC_KEEP_SOURCE; + const res = spawnSync(compiler, args, { + cwd: tmpDir, + encoding: "utf8", + env, + }); if (res.status !== 0) { - const detail = (res.stderr || res.stdout || (res.error && res.error.message) || '').toString().trim(); - throw new Error(`compiler failed for ${file} (exit ${res.status}):\n${detail}`); + const detail = ( + res.stderr || + res.stdout || + (res.error && res.error.message) || + "" + ) + .toString() + .trim(); + throw new Error( + `compiler failed for ${file} (exit ${res.status}):\n${detail}`, + ); } const bytecode = fs.readFileSync(tmpOut); - if (adapter.magic && !adapter.isBytecode(bytecode.subarray(0, adapter.magic.length))) { + if ( + adapter.magic && + !adapter.isBytecode(bytecode.subarray(0, adapter.magic.length)) + ) { throw new Error(`compiler produced non-bytecode output for ${file}`); } // Overwrite in place: the file keeps its .js name; the runtime detects the magic. fs.writeFileSync(file, bytecode); if (wantMap) { const mapSrc = adapter.sourceMapOutput(tmpOut); - if (fs.existsSync(mapSrc)) fs.copyFileSync(mapSrc, file + '.map'); + if (fs.existsSync(mapSrc)) fs.copyFileSync(mapSrc, file + ".map"); } return bytecode.length; } finally { @@ -203,11 +293,15 @@ function main() { const adapter = adapterForEngine(opts.engine); if (!adapter) { - console.log(`[bytecode] engine ${opts.engine} has no bytecode compiler — leaving app as source.`); + console.log( + `[bytecode] engine ${opts.engine} has no bytecode compiler — leaving app as source.`, + ); return; } if (!adapter.ready) { - console.log(`[bytecode] ${adapter.key} bytecode is not enabled yet — leaving app as source.`); + console.log( + `[bytecode] ${adapter.key} bytecode is not enabled yet — leaving app as source.`, + ); return; } if (!fs.existsSync(appDir)) { @@ -218,7 +312,7 @@ function main() { const compiler = resolveCompiler(opts, adapter, host); if (!fs.existsSync(compiler) || (!opts.compiler && isPlaceholder(compiler))) { const msg = `[bytecode] no ${adapter.key} compiler for host ${host} at ${compiler}`; - if (opts.strict) throw new Error(msg + ' (--strict)'); + if (opts.strict) throw new Error(msg + " (--strict)"); console.warn(`${msg} — leaving app as source.`); return; } @@ -226,7 +320,7 @@ function main() { // Ensure the compiler is executable. Binaries shipped via CI artifact zips, // npm packages, or restored without the exec bit lose it, which surfaces as a // spawn EACCES. (No-op on Windows.) - if (process.platform !== 'win32') { + if (process.platform !== "win32") { try { fs.chmodSync(compiler, 0o755); } catch (err) { @@ -236,7 +330,14 @@ function main() { const rawSet = new Set(opts.raw); const magicLen = adapter.magic ? adapter.magic.length : 0; - const files = listJsFiles(appDir); + // Called with --app pointing at assets/app itself (the historical shape): the + // roots are relative to assets/, so fall back to walking the directory given. + const roots = opts.roots.some( + (r) => r === "." || fs.existsSync(path.join(appDir, r)), + ) + ? opts.roots + : ["."]; + const files = listJsFiles(appDir, roots); let compiled = 0; let skipped = 0; let rawCount = 0; @@ -249,12 +350,18 @@ function main() { continue; } const isRaw = rawSet.has(relKey(appDir, file)); + // Modules under app/ are named relative to app/, which is the runtime's + // module root; anything else keeps its assets-relative name. + const appRoot = path.join(appDir, "app"); + const nameBase = file.startsWith(appRoot + path.sep) ? appRoot : appDir; try { - compileFile(opts, adapter, compiler, file, isRaw, appDir); + compileFile(opts, adapter, compiler, file, isRaw, nameBase); compiled++; if (isRaw) rawCount++; if (opts.verbose) { - console.log(`[bytecode] compiled${isRaw ? ' (raw)' : ''}: ${relKey(appDir, file)}`); + console.log( + `[bytecode] compiled${isRaw ? " (raw)" : ""}: ${relKey(appDir, file)}`, + ); } } catch (err) { if (opts.keepGoing) { @@ -268,7 +375,7 @@ function main() { const ms = Date.now() - start; console.log( `[bytecode] ${adapter.key}/${host}: compiled ${compiled} file(s) ` + - `(${rawCount} raw), skipped ${skipped} already-compiled, in ${ms}ms.` + `(${rawCount} raw), skipped ${skipped} already-compiled, in ${ms}ms.`, ); } diff --git a/platforms/android/tools/bytecode-compiler/lib/index.js b/platforms/android/tools/bytecode-compiler/lib/index.js index ae917f11e..f6fdea3e7 100644 --- a/platforms/android/tools/bytecode-compiler/lib/index.js +++ b/platforms/android/tools/bytecode-compiler/lib/index.js @@ -34,7 +34,6 @@ function hostKey() { const adapters = [ require('./hermes'), - require('./quickjs'), require('./quickjs-ng'), require('./primjs'), ]; diff --git a/platforms/android/tools/bytecode-compiler/lib/quickjs-ng.js b/platforms/android/tools/bytecode-compiler/lib/quickjs-ng.js index 2bb6123f6..64ca90906 100644 --- a/platforms/android/tools/bytecode-compiler/lib/quickjs-ng.js +++ b/platforms/android/tools/bytecode-compiler/lib/quickjs-ng.js @@ -1,14 +1,18 @@ 'use strict'; -// QuickJS-NG adapter. Same JS_* API and shim as QuickJS (native/qjs-compile.c), -// but a distinct engine whose bytecode is NOT interchangeable — hence its own -// magic and bin/quickjs-ng/ directory. The runtime reads this container in -// napi/quickjs js_execute_script (the __QUICKJS_NG__ build); see quickjs.js. +// QuickJS-NG adapter, built on the shared shim (native/qjs-compile.c). The +// runtime reads this container in napi/quickjs js_execute_script (the +// __QUICKJS_NG__ build). +// +// This is the only QuickJS lane left: the bellard tree was dropped and the +// runtime's QUICKJS is an alias for QUICKJS_NG (see test-app/runtime/ +// build.gradle), so both names must resolve here. Were QUICKJS left +// unregistered, the driver would silently skip compilation and ship plain JS. const MAGIC = Buffer.from('NSBCNGS\0', 'latin1'); // 8 bytes, must match the shim + runtime module.exports = { key: 'quickjs-ng', - engineKeys: ['QUICKJS_NG'], + engineKeys: ['QUICKJS_NG', 'QUICKJS'], ready: true, magic: MAGIC, supportsSourceMaps: false, diff --git a/platforms/android/tools/bytecode-compiler/lib/quickjs.js b/platforms/android/tools/bytecode-compiler/lib/quickjs.js deleted file mode 100644 index 91d73bc00..000000000 --- a/platforms/android/tools/bytecode-compiler/lib/quickjs.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -// QuickJS (bellard) adapter. -// -// The compiler is our blob-emitting shim (native/qjs-compile.c), built per host -// by .github/workflows/bytecode-compilers.yml. It writes a NativeScript bytecode -// container: 8-byte magic + 4-byte LE format version + JS_WriteObject payload. -// -// The runtime side reads this container in napi/quickjs js_execute_script (strips -// the 12-byte header, JS_ReadObject(JS_READ_OBJ_BYTECODE) + JS_EvalFunction). -// Keep the CLI's engine ref in step with the runtime's vendored QuickJS. -const MAGIC = Buffer.from('NSBCQJS\0', 'latin1'); // 8 bytes, must match the shim + runtime - -module.exports = { - key: 'quickjs', - engineKeys: ['QUICKJS'], - ready: true, - magic: MAGIC, - supportsSourceMaps: false, - defaultOptimize: null, - - binName(hostKey) { - return hostKey.startsWith('win32') ? 'nsbc-quickjs.exe' : 'nsbc-quickjs'; - }, - - isBytecode(head) { - return head.length >= MAGIC.length && head.subarray(0, MAGIC.length).equals(MAGIC); - }, - - // Our shim takes: - buildArgs({ input, output }) { - return [input, output]; - }, - - sourceMapOutput(output) { - return output + '.map'; - }, -}; diff --git a/platforms/android/tools/bytecode-compiler/native/qjs-compile.c b/platforms/android/tools/bytecode-compiler/native/qjs-compile.c index de2242590..e1830aa99 100644 --- a/platforms/android/tools/bytecode-compiler/native/qjs-compile.c +++ b/platforms/android/tools/bytecode-compiler/native/qjs-compile.c @@ -3,7 +3,8 @@ * * The stock `qjsc` emits a C source array, not a loadable blob, so it can't feed * our runtime. This tiny tool instead reads a JS file, compiles it with - * JS_EVAL_FLAG_COMPILE_ONLY, serializes it via JS_WriteObject(JS_WRITE_OBJ_BYTECODE), + * JS_EVAL_FLAG_COMPILE_ONLY, serializes it via JS_WriteObject (bytecode with the + * per-function source text stripped -- see the flags below), * and writes a small NativeScript bytecode *container*: * * [8 bytes magic][4 bytes format version, little-endian][JS_WriteObject payload] @@ -81,9 +82,15 @@ int main(int argc, char **argv) { JS_FreeValue(ctx, exc); return 1; } + + int write_flags = JS_WRITE_OBJ_BYTECODE; + const char *keep_source = getenv("NSBC_KEEP_SOURCE"); + if (!(keep_source && keep_source[0] && strcmp(keep_source, "0") != 0)) { + write_flags |= JS_WRITE_OBJ_STRIP_SOURCE; + } size_t bc_len = 0; - uint8_t *bc = JS_WriteObject(ctx, &bc_len, obj, JS_WRITE_OBJ_BYTECODE); + uint8_t *bc = JS_WriteObject(ctx, &bc_len, obj, write_flags); JS_FreeValue(ctx, obj); if (!bc) { fprintf(stderr, "JS_WriteObject failed for %s\n", in_path); return 1; } diff --git a/platforms/android/tools/patches/README.md b/platforms/android/tools/patches/README.md deleted file mode 100644 index 4f4aada16..000000000 --- a/platforms/android/tools/patches/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Engine source patches - -The QuickJS / QuickJS-NG engine sources under -`test-app/runtime/src/main/cpp/napi/quickjs/{source,source_ng}` are git submodules -pinned to an upstream commit. Our hand-written NativeScript changes are kept here -as patches applied on top of that pristine checkout, rather than baked into a -vendored copy. - -``` -tools/patches/ - quickjs/ 0001-nativescript-local-changes.patch (bellard/quickjs) - quickjs_ng/ 0001-nativescript-local-changes.patch (quickjs-ng/quickjs) -``` - -Both patches touch only `quickjs.c` / `quickjs.h`, which is where all our edits -live: the `USE_HOST_OBJECT` host-object hooks in `JS_GetPropertyInternal` (host -objects act as a non-masking prototype fallback) and the functions after the -`/* CUSTOM */` marker at the bottom of each file. - -## Managing them - -`scripts/vendor-engines-as-submodules.sh` owns this setup: - -- **quickjs** patch is *auto-generated* by diffing the submodule against upstream - (the vendored base tracks bellard master closely, so the diff is exactly our - edits). -- **quickjs_ng** patch is *hand-migrated* and used as-is (`ENGINE_PATCH_MODE=manual`). - The old vendored source was v0.15.1 (BC_VERSION 26) while the submodule is pinned - to master `d950d55` (BC_VERSION 27), so a diff can't isolate our edits — the - patch was ported onto master by hand and verified to compile. - -On a fresh checkout / in CI: - -``` -git submodule update --init \ - test-app/runtime/src/main/cpp/napi/quickjs/source \ - test-app/runtime/src/main/cpp/napi/quickjs/source_ng -scripts/vendor-engines-as-submodules.sh --apply-only -``` - -## Compatibility - -The submodule commit sets the engine's `BC_VERSION`. The compile-time bytecode -CLI (see `.github/workflows/bytecode-compilers.yml`) MUST be built from the same -commit, or `JS_ReadObject` will reject the produced bytecode at runtime. Keep the -workflow refs and the submodule pins in lock-step. diff --git a/platforms/android/tools/patches/quickjs/0001-nativescript-local-changes.patch b/platforms/android/tools/patches/quickjs/0001-nativescript-local-changes.patch deleted file mode 100644 index 2079b8a89..000000000 --- a/platforms/android/tools/patches/quickjs/0001-nativescript-local-changes.patch +++ /dev/null @@ -1,304 +0,0 @@ -diff --git a/VERSION b/VERSION -deleted file mode 100644 -index ba2d8bc..0000000 ---- a/VERSION -+++ /dev/null -@@ -1 +0,0 @@ --2026-06-04 -diff --git a/quickjs.c b/quickjs.c -index e3716a4..f017812 100644 ---- a/quickjs.c -+++ b/quickjs.c -@@ -341,6 +341,7 @@ struct JSRuntime { - JSGCPhaseEnum gc_phase : 8; - size_t malloc_gc_threshold; - struct list_head weakref_list; /* list of JSWeakRefHeader.link */ -+ struct list_head weakref_keepalive_list; /* strong refs kept until the current host job completes */ - #ifdef DUMP_LEAKS - struct list_head string_list; /* list of JSString.link */ - #endif -@@ -419,6 +420,11 @@ typedef struct JSStackFrame { - JSValue *cur_sp; - } JSStackFrame; - -+typedef struct JSWeakRefKeepAliveEntry { -+ struct list_head link; -+ JSValue value; -+} JSWeakRefKeepAliveEntry; -+ - typedef enum { - JS_GC_OBJ_TYPE_JS_OBJECT, - JS_GC_OBJ_TYPE_FUNCTION_BYTECODE, -@@ -2087,6 +2093,7 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) - init_list_head(&rt->gc_zero_ref_count_list); - rt->gc_phase = JS_GC_PHASE_NONE; - init_list_head(&rt->weakref_list); -+ init_list_head(&rt->weakref_keepalive_list); - - #ifdef DUMP_LEAKS - init_list_head(&rt->string_list); -@@ -2417,6 +2424,7 @@ void JS_FreeRuntime(JSRuntime *rt) - js_free_rt(rt, e); - } - init_list_head(&rt->job_list); -+ JS_ClearWeakRefKeepAlives(rt); - - /* don't remove the weak objects to avoid create new jobs with - FinalizationRegistry */ -@@ -7223,7 +7231,7 @@ void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) - - void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) - { -- fprintf(fp, "QuickJS memory usage -- " CONFIG_VERSION " version, %d-bit, malloc limit: %"PRId64"\n\n", -+ fprintf(fp, "QuickJS memory usage -- version, %d-bit, malloc limit: %"PRId64"\n\n", - (int)sizeof(void *) * 8, s->malloc_limit); - #if 1 - if (rt) { -@@ -7632,7 +7640,9 @@ static BOOL is_backtrace_needed(JSContext *ctx, JSValueConst obj) - - JSValue JS_NewError(JSContext *ctx) - { -- return JS_NewObjectClass(ctx, JS_CLASS_ERROR); -+ JSValue error = JS_NewObjectClass(ctx, JS_CLASS_ERROR); -+ build_backtrace(ctx, error, NULL, 0, 0, 0); -+ return error; - } - - static JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num, -@@ -8207,6 +8217,13 @@ static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, - return 0; - } - -+#ifdef USE_HOST_OBJECT -+/* NativeScript host-object exotic methods (defined in quickjs-api.c). Compared -+ by address below so the get_property handler can be treated as a non-masking -+ fallback for host objects only, leaving Proxy (js_proxy_get) authoritative. */ -+extern JSClassExoticMethods NapiHostObjectExoticMethods; -+#endif -+ - JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, - JSAtom prop, JSValueConst this_obj, - BOOL throw_ref_error) -@@ -8215,6 +8232,11 @@ JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, - JSProperty *pr; - JSShapeProperty *prs; - uint32_t tag; -+#ifdef USE_HOST_OBJECT -+ /* A host object whose get_property we deferred: the prototype chain is -+ consulted first, and this is called only if nothing was found. */ -+ JSObject *deferred_host = NULL; -+#endif - - tag = JS_VALUE_GET_TAG(obj); - if (unlikely(tag != JS_TAG_OBJECT)) { -@@ -8319,14 +8341,27 @@ JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em) { - if (em->get_property) { -- JSValue obj1, retval; -- /* XXX: should pass throw_ref_error */ -- /* Note: if 'p' is a prototype, it can be -- freed in the called function */ -- obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); -- retval = em->get_property(ctx, obj1, prop, this_obj); -- JS_FreeValue(ctx, obj1); -- return retval; -+#ifdef USE_HOST_OBJECT -+ /* NativeScript host objects use get_property as a -+ NON-masking fallback: defer it so the prototype chain -+ (native methods / field accessors) is consulted first, -+ and call it only if nothing is found (see after loop). -+ Proxy and other exotics stay authoritative. */ -+ if (em == &NapiHostObjectExoticMethods) { -+ if (!deferred_host) -+ deferred_host = p; -+ } else -+#endif -+ { -+ JSValue obj1, retval; -+ /* XXX: should pass throw_ref_error */ -+ /* Note: if 'p' is a prototype, it can be -+ freed in the called function */ -+ obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); -+ retval = em->get_property(ctx, obj1, prop, this_obj); -+ JS_FreeValue(ctx, obj1); -+ return retval; -+ } - } - if (em->get_own_property) { - JSPropertyDescriptor desc; -@@ -8356,6 +8391,17 @@ JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, - if (!p) - break; - } -+#ifdef USE_HOST_OBJECT -+ /* Nothing found on the prototype chain: now consult the deferred host -+ object's get_property (expandos, numeric indices, dynamic members). */ -+ if (deferred_host) { -+ JSValue obj1, retval; -+ obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, deferred_host)); -+ retval = NapiHostObjectExoticMethods.get_property(ctx, obj1, prop, this_obj); -+ JS_FreeValue(ctx, obj1); -+ return retval; -+ } -+#endif - if (unlikely(throw_ref_error)) { - return JS_ThrowReferenceErrorNotDefined(ctx, prop); - } else { -@@ -61179,6 +61225,7 @@ static JSValue js_weakref_constructor(JSContext *ctx, JSValueConst new_target, - wrd->target = js_weakref_new(ctx, arg); - wrd->weakref_header.weakref_type = JS_WEAKREF_TYPE_WEAKREF; - list_add_tail(&wrd->weakref_header.link, &ctx->rt->weakref_list); -+ JS_KeepWeakRefTargetAlive(ctx, arg); - JS_SetOpaque(obj, wrd); - return obj; - } -@@ -61422,3 +61469,112 @@ int JS_AddIntrinsicWeakRef(JSContext *ctx) - JS_FreeValue(ctx, obj); - return 0; - } -+ -+/* CUSTOM */ -+ -+int JS_IsArrayBuffer2(JSContext *ctx, JSValueConst val) -+{ -+ JSObject *p; -+ if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { -+ p = JS_VALUE_GET_OBJ(val); -+ return p->class_id == JS_CLASS_ARRAY_BUFFER || p->class_id == JS_CLASS_SHARED_ARRAY_BUFFER; -+ } else { -+ return false; -+ } -+} -+ -+JSValue JS_NewString16(JSContext *ctx, const uint16_t *buf, int len) -+{ -+ return js_new_string16_len(ctx, buf, len); -+} -+ -+JSValue JS_GetPropertyInt64_2(JSContext *ctx, JSValueConst obj, int64_t idx) { -+ return JS_GetPropertyInt64(ctx, obj, idx); -+} -+ -+JSValue JS_WeakRef_Deref(JSContext *ctx, JSValueConst this_val) { -+ return js_weakref_deref(ctx, this_val, 0, NULL); -+} -+ -+void JS_KeepWeakRefTargetAlive(JSContext *ctx, JSValueConst value) -+{ -+ JSRuntime *rt; -+ JSWeakRefKeepAliveEntry *entry; -+ -+ if (!js_weakref_is_target(value)) -+ return; -+ -+ rt = JS_GetRuntime(ctx); -+ entry = js_malloc(ctx, sizeof(*entry)); -+ if (!entry) -+ return; -+ -+ entry->value = JS_DupValue(ctx, value); -+ list_add_tail(&entry->link, &rt->weakref_keepalive_list); -+} -+ -+void JS_ClearWeakRefKeepAlives(JSRuntime *rt) -+{ -+ struct list_head *el, *el1; -+ -+ list_for_each_safe(el, el1, &rt->weakref_keepalive_list) { -+ JSWeakRefKeepAliveEntry *entry = list_entry(el, JSWeakRefKeepAliveEntry, link); -+ list_del(&entry->link); -+ JS_FreeValueRT(rt, entry->value); -+ js_free_rt(rt, entry); -+ } -+} -+ -+int JS_GetLength(JSContext *ctx, JSValueConst obj, int64_t *pres) { -+ return js_get_length64(ctx, pres, obj); -+} -+ -+ -+static bool is_typed_array(JSClassID class_id) -+{ -+ return class_id >= JS_CLASS_UINT8C_ARRAY && class_id <= JS_CLASS_FLOAT64_ARRAY; -+} -+ -+int JS_GetTypedArrayType(JSValueConst obj) -+{ -+ JSClassID class_id = JS_GetClassID(obj); -+ if (is_typed_array(class_id)) -+ return class_id - JS_CLASS_UINT8C_ARRAY; -+ else -+ return -1; -+} -+ -+JS_BOOL JS_IsDataView(JSValueConst val) -+{ -+ return JS_CLASS_DATAVIEW == JS_GetClassID(val); -+} -+ -+int JS_ToBigUint64(JSContext *ctx, uint64_t *pres, JSValueConst val) -+{ -+ return JS_ToBigInt64Free(ctx, (int64_t *)pres, JS_DupValue(ctx, val)); -+} -+ -+JS_BOOL JS_IsStrictEqual(JSContext *ctx, JSValueConst op1, JSValueConst op2) -+{ -+ return js_strict_eq2(ctx, JS_DupValue(ctx, op1), JS_DupValue(ctx,op2), JS_EQ_STRICT); -+} -+ -+int JS_SealObject(JSContext *ctx, JSValueConst obj) -+{ -+ JSValue value = js_object_seal(ctx, JS_UNDEFINED, 1, &obj, 0); -+ int result = JS_IsException(value) ? -1 : true; -+ JS_FreeValue(ctx, value); -+ return result; -+} -+ -+int JS_FreezeObject(JSContext *ctx, JSValueConst obj) -+{ -+ JSValue value = js_object_seal(ctx, JS_UNDEFINED, 1, &obj, 1); -+ int result = JS_IsException(value) ? -1 : true; -+ JS_FreeValue(ctx, value); -+ return result; -+} -+ -+size_t JS_GetGCThreshold(JSRuntime *rt) { -+ return rt->malloc_gc_threshold; -+} -diff --git a/quickjs.h b/quickjs.h -index 476d735..05bc181 100644 ---- a/quickjs.h -+++ b/quickjs.h -@@ -409,6 +409,8 @@ int JS_AddIntrinsicMapSet(JSContext *ctx); - int JS_AddIntrinsicTypedArrays(JSContext *ctx); - int JS_AddIntrinsicPromise(JSContext *ctx); - int JS_AddIntrinsicWeakRef(JSContext *ctx); -+void JS_KeepWeakRefTargetAlive(JSContext *ctx, JSValueConst value); -+void JS_ClearWeakRefKeepAlives(JSRuntime *rt); - - JSValue js_string_codePointRange(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv); -@@ -1170,6 +1172,22 @@ void JS_PrintValueRT(JSRuntime *rt, JSPrintValueWrite *write_func, void *write_o - void JS_PrintValue(JSContext *ctx, JSPrintValueWrite *write_func, void *write_opaque, - JSValueConst val, const JSPrintValueOptions *options); - -+/* CUSTOM */ -+ -+int JS_IsArrayBuffer2(JSContext *ctx, JSValueConst val); -+JSValue JS_NewString16(JSContext *ctx, const uint16_t *buf, int len); -+JSValue JS_GetPropertyInt64_2(JSContext *ctx, JSValueConst obj, int64_t idx); -+JSValue JS_WeakRef_Deref(JSContext *ctx, JSValueConst this_val); -+int JS_GetLength(JSContext *ctx, JSValueConst obj, int64_t *pres); -+int JS_GetTypedArrayType(JSValueConst obj); -+JS_BOOL JS_IsDataView(JSValueConst val); -+int JS_ToBigUint64(JSContext *ctx, uint64_t *pres, JSValueConst val); -+JS_BOOL JS_IsStrictEqual(JSContext *ctx, JSValueConst op1, JSValueConst op2); -+int JS_SealObject(JSContext *ctx, JSValueConst obj); -+int JS_FreezeObject(JSContext *ctx, JSValueConst obj); -+size_t JS_GetGCThreshold(JSRuntime *rt); -+ -+ - #undef js_unlikely - #undef js_force_inline - diff --git a/platforms/apple/benchmarks/objc-dispatch/objc-dispatch-benchmarks.js b/platforms/apple/benchmarks/objc-dispatch/objc-dispatch-benchmarks.js index 547728a03..94258eb82 100644 --- a/platforms/apple/benchmarks/objc-dispatch/objc-dispatch-benchmarks.js +++ b/platforms/apple/benchmarks/objc-dispatch/objc-dispatch-benchmarks.js @@ -58,6 +58,12 @@ var iterations = Math.max(1, Math.floor(baseIterations * factor)); var warmup = Math.min(warmupIterations, iterations); runLoop(warmup, fn); + // Collect the garbage earlier cases left behind, so a collection it would + // trigger isn't timed against this case. A fast case runs for about a + // millisecond, less than one such pause. + if (typeof gc === "function") { + gc(); + } var started = nowMs(); runLoop(iterations, fn); diff --git a/platforms/apple/test/runtime/runner/app/tests/Marshalling/EnumTests.js b/platforms/apple/test/runtime/runner/app/tests/Marshalling/EnumTests.js index 2dd33c858..b9786514b 100644 --- a/platforms/apple/test/runtime/runner/app/tests/Marshalling/EnumTests.js +++ b/platforms/apple/test/runtime/runner/app/tests/Marshalling/EnumTests.js @@ -43,6 +43,30 @@ describe(module.id, function () { expect(NSOrderedAscending).toBe(-1); expect(NSOrderedSame).toBe(0); expect(NSOrderedDescending).toBe(1); + expect(NSTextAlignmentCenter).toBe(NSTextAlignment.Center); + expect(kCLAuthorizationStatusRestricted).toBe(1); + expect(NSCaseInsensitiveSearch).toBe(1); + }); + + it("EnumConstantsWhoseNameDiffersFromTheEnumName", function () { + expect(MKMapTypeStandard).toBe(0); + expect(MKMapType.MKMapTypeStandard).toBe(0); + expect(CKErrorZoneBusy).toBe(23); + expect(CKErrorCode.CKErrorZoneBusy).toBe(23); + expect(CKRecordZoneCapabilityAtomic).toBe(2); + expect(CKRecordZoneEncryptionScopePerZone).toBe(1); + }); + + it("EnumMemberAliasesAreNotGlobalVariables", function () { + expect(NSTextAlignment.Center).toBe(1); + expect(CKRecordZoneEncryptionScope.Zone).toBe(1); + expect(CKErrorCode.ZoneBusy).toBe(23); + + ["Center", "Zone", "Ascending", "Restricted", "ZoneBusy", + "NSComparisonAscending", "kNSTextAlignmentCenter"].forEach(function (name) { + expect(name + " in globalThis: " + (name in globalThis)) + .toBe(name + " in globalThis: false"); + }); }); it("EnumWithOneValue", function () { diff --git a/platforms/apple/test/runtime/runner/app/tests/Marshalling/ObjCTypesTests.js b/platforms/apple/test/runtime/runner/app/tests/Marshalling/ObjCTypesTests.js index f1d7abd16..eafc0bc49 100644 --- a/platforms/apple/test/runtime/runner/app/tests/Marshalling/ObjCTypesTests.js +++ b/platforms/apple/test/runtime/runner/app/tests/Marshalling/ObjCTypesTests.js @@ -3,6 +3,18 @@ describe(module.id, function () { global.process && global.process.versions && global.process.versions.engine === "hermes"; + const isJSC = + global.process && + global.process.versions && + global.process.versions.engine === "jsc"; + + // JSC scans the stack conservatively, so a stale slot can keep a block's + // function alive for a while; give its poll 10s, past Jasmine's 5s default. + let originalTimeout; + beforeEach(function () { + originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; + }); function expectWeakRefCollected(ref, done, timeoutMs) { if (isHermes) { @@ -10,7 +22,7 @@ describe(module.id, function () { return; } - const deadline = Date.now() + (timeoutMs || 2000); + const deadline = Date.now() + (timeoutMs || (isJSC ? 10000 : 2000)); function poll() { gc(); if (!ref.deref()) { @@ -32,6 +44,7 @@ describe(module.id, function () { afterEach(function () { TNSClearOutput(); + jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; }); it("FunctionWithCFTypeRefArgument", function () { diff --git a/platforms/apple/test/runtime/runner/app/tests/Timers.js b/platforms/apple/test/runtime/runner/app/tests/Timers.js index 8df89d7db..eb5792581 100644 --- a/platforms/apple/test/runtime/runner/app/tests/Timers.js +++ b/platforms/apple/test/runtime/runner/app/tests/Timers.js @@ -27,6 +27,17 @@ describe("native timer", () => { /** @type {global.queueMicrotask} */ let queueMicrotask = global.__ns__queueMicrotask || global.queueMicrotask; + // expectWeakRefCleared polls for up to 10s on JSC, past Jasmine's 5s + // default, which would otherwise time the spec out before the poll gives up. + let originalTimeout; + beforeEach(() => { + originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; + }); + afterEach(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; + }); + function expectWeakRefCleared(ref, done, timeoutMs) { const deadline = Date.now() + (timeoutMs || 1000); diff --git a/scripts/apply_quickjs_patches.js b/scripts/apply_quickjs_patches.js new file mode 100644 index 000000000..27bad2ea1 --- /dev/null +++ b/scripts/apply_quickjs_patches.js @@ -0,0 +1,110 @@ +#!/usr/bin/env node +// +// Applies the NativeScript local patches onto the vendored QuickJS-NG source. +// +// The engine source under vendor/ is shared by the Apple and Android builds, so +// this lives at the repo root rather than under platforms/. +// +// This script does NOT touch submodule registration or the pin -- fetching the +// engine source is a manual step, so nothing here can silently move you off the +// pinned commit: +// +// git submodule update --init --force vendor/quickjs/source_ng +// +// (never --remote: that moves the submodule to the tip of its branch, silently +// changing the engine and its BC_VERSION -- see vendor/quickjs/patches/README.md) +// +// Idempotent: the submodule work tree is hard-reset to the pinned commit before +// the patch is applied, so re-running is safe. Pass --no-reset to apply on top +// of the current work tree instead (it will fail if the patch is already in). +// +// Flags: +// --no-reset don't hard-reset/clean the submodule work tree first +// --check verify the patch still applies to the pinned commit, change +// nothing (runs in a throwaway worktree, so an already-patched +// checkout doesn't turn into a false failure) + +'use strict'; + +const { execFileSync } = require('child_process'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const REPO_ROOT = path.resolve(__dirname, '..'); + +// The pinned commit fixes the engine's BC_VERSION, which the ahead-of-time +// bytecode compiler must be built from as well -- see +// vendor/quickjs/patches/README.md. +const ENGINES = [ + { + sub: 'vendor/quickjs/source_ng', + patch: 'vendor/quickjs/patches/source_ng/0001-nativescript-local-changes.patch', + }, +]; + +const flags = new Set(process.argv.slice(2)); +const reset = !flags.has('--no-reset'); +const checkOnly = flags.has('--check'); + +function run(cmd, args, opts = {}) { + console.log(`==> ${cmd} ${args.join(' ')}`); + try { + execFileSync(cmd, args, { stdio: 'inherit', cwd: REPO_ROOT, ...opts }); + } catch (err) { + // The command already printed its own diagnostics; a node stack trace on top + // of them just buries the useful part. + process.exit(typeof err.status === 'number' ? err.status : 1); + } +} + +function fail(msg) { + console.error(`\nerror: ${msg}`); + process.exit(1); +} + +// Verify the patch against the pinned commit itself rather than the (usually +// already-patched) checkout, in a temporary worktree that is thrown away after. +function checkAgainstPin(sub, patchFile) { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'qjs-patch-check-')); + const work = path.join(tmp, 'src'); + try { + run('git', ['-C', sub, 'worktree', 'add', '--detach', '--quiet', work, 'HEAD']); + run('git', ['-C', work, 'apply', '--check', '--whitespace=nowarn', patchFile]); + } finally { + try { + execFileSync('git', ['-C', sub, 'worktree', 'remove', '--force', work], + { cwd: REPO_ROOT, stdio: 'ignore' }); + } catch (_) { /* best effort */ } + fs.rmSync(tmp, { recursive: true, force: true }); + } +} + +for (const { sub, patch } of ENGINES) { + const subDir = path.join(REPO_ROOT, sub); + const patchFile = path.join(REPO_ROOT, patch); + + // An unfetched submodule is an empty directory. Say so plainly rather than + // failing later inside git with something cryptic. + if (!fs.existsSync(path.join(subDir, '.git'))) { + fail( + `${sub} is not checked out. Fetch it first:\n\n` + + ` git submodule update --init --force ${sub}\n` + ); + } + if (!fs.existsSync(patchFile)) fail(`patch missing: ${patch}`); + + if (checkOnly) { + checkAgainstPin(sub, patchFile); + console.log(`==> ${patch} applies cleanly to the pinned commit`); + continue; + } + + if (reset) { + run('git', ['-C', sub, 'reset', '--hard', '--quiet']); + run('git', ['-C', sub, 'clean', '-qfd']); + } + run('git', ['-C', sub, 'apply', '--whitespace=nowarn', patchFile]); +} + +console.log(checkOnly ? '==> Check complete' : '==> Patches applied'); diff --git a/scripts/build_all_android.sh b/scripts/build_all_android.sh new file mode 100755 index 000000000..b945af762 --- /dev/null +++ b/scripts/build_all_android.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# +# Builds the NativeScript Android runtime for one JS engine. +# +# The gradle build itself lives in platforms/android; this script only picks the +# engine, maps it to the gradle properties, and runs it from the repo root so +# every build entry point is in scripts/. +# +# Usage: +# ./scripts/build_all_android.sh # prompts for the engine +# ./scripts/build_all_android.sh --engine=QUICKJS_NG +# ./scripts/build_all_android.sh --engine=HERMES --disable-host-objects +# ENGINE=V8-13 ./scripts/build_all_android.sh # non-interactive +# +# Flags: +# --engine= V8-10 | V8-11 | V8-13 | QUICKJS_NG | HERMES | +# SHERMES | JSC | PRIMJS +# --binding=napi|jsi which runtime tree to compile (default napi) +# --disable-host-objects build without host-object support (on by default; +# every engine supports it) +# --dry-run print the gradle command and exit +# anything else passed through to gradle verbatim +# +# The staged package lands in dist/android__/ and the tarball in +# build/npm-tarballs/, alongside the Apple build output. +# +set -e +source "$(dirname "$0")/build_utils.sh" + +ANDROID_ROOT="$REPO_ROOT/platforms/android" + +# SHERMES is kept as an input alias for the unified Static Hermes backend. +VALID_ENGINES=(V8-10 V8-11 V8-13 QUICKJS_NG HERMES SHERMES JSC PRIMJS) +DEFAULT_ENGINE=V8-13 + +ENGINE=${ENGINE:-} +BINDING=${BINDING:-napi} +DISABLE_HOST_OBJECTS=false +DRY_RUN=false +PASSTHROUGH=() + +for arg in "$@"; do + case $arg in + --engine=*) ENGINE="${arg#*=}" ;; + --binding=*) BINDING="${arg#*=}" ;; + --disable-host-objects) DISABLE_HOST_OBJECTS=true ;; + --dry-run) DRY_RUN=true ;; + *) PASSTHROUGH+=("$arg") ;; + esac +done + +is_valid_engine() { + local candidate="$1" + for e in "${VALID_ENGINES[@]}"; do + [ "$e" = "$candidate" ] && return 0 + done + return 1 +} + +# Ask only when nothing was passed and someone is there to answer; a CI run with +# no --engine gets the default rather than hanging on a prompt. +if [ -z "$ENGINE" ]; then + if [ -t 0 ]; then + echo "Select JS engine:" + for i in "${!VALID_ENGINES[@]}"; do + echo " $((i + 1))) ${VALID_ENGINES[$i]}" + done + read -r -p "Choose number or name ($DEFAULT_ENGINE) " answer + if [ -z "$answer" ]; then + ENGINE=$DEFAULT_ENGINE + elif [[ "$answer" =~ ^[0-9]+$ ]]; then + ENGINE=${VALID_ENGINES[$((answer - 1))]:-$DEFAULT_ENGINE} + else + ENGINE=$answer + fi + else + ENGINE=$DEFAULT_ENGINE + fi +fi + +if ! is_valid_engine "$ENGINE"; then + echo "error: unknown engine '$ENGINE'. Expected one of: ${VALID_ENGINES[*]}" >&2 + exit 1 +fi + +case "$BINDING" in + napi|jsi) ;; + *) echo "error: --binding must be 'napi' or 'jsi', got '$BINDING'" >&2; exit 1 ;; +esac + +GRADLE_ARGS=("-Pengine=$ENGINE" "-PbindingLayer=$BINDING") +# Host objects are on by default for every engine; opting out is explicit. +$DISABLE_HOST_OBJECTS || GRADLE_ARGS+=("-PuseHostObjects") +[ ${#PASSTHROUGH[@]} -gt 0 ] && GRADLE_ARGS+=("${PASSTHROUGH[@]}") + +checkpoint "Building the Android runtime with $ENGINE ($BINDING runtime)" +echo "gradle command: ./gradlew ${GRADLE_ARGS[*]}" + +if $DRY_RUN; then + echo "Dry run requested. Exiting without executing gradle." + exit 0 +fi + +cd "$ANDROID_ROOT" +./gradlew "${GRADLE_ARGS[@]}" + +# Mirrors the slug the gradle build derives (see platforms/android/build.gradle). +ENGINE_SLUG=$(echo "$ENGINE" | tr '[:upper:]-' '[:lower:]_') +[ "$ENGINE_SLUG" = "quickjs_ng" ] && ENGINE_SLUG=quickjs +checkpoint "Done. Package staged in dist/android_${ENGINE_SLUG}_${BINDING}/, tarball in build/npm-tarballs/" diff --git a/scripts/build_react_native_android.sh b/scripts/build_react_native_android.sh new file mode 100755 index 000000000..5708bc5e6 --- /dev/null +++ b/scripts/build_react_native_android.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# Stages the Android half of @nativescript/react-native into the package. +# +# The module compiles from source in the consuming app (see +# packages/react-native/android/build.gradle for why), so what ships is source: +# the runtime's C++ and its com.tns.* Java, plus the two build-time tools. In a +# checkout the Gradle build reads these straight out of the repository instead -- +# that fallback is what lets the demo app build with no staging step. +set -euo pipefail +source "$(dirname "$0")/build_utils.sh" + +PACKAGE_DIR="$REPO_ROOT/packages/react-native" +ANDROID_DIR="$PACKAGE_DIR/android" +NATIVE_API="$PACKAGE_DIR/native-api" +RUNTIME_JAVA_SRC="$REPO_ROOT/platforms/android/test-app/runtime/src/main/java" +BINDINGS_JAVA_SRC="$REPO_ROOT/platforms/android/test-app/runtime-binding-generator/src/main/java" + +checkpoint "Staging the NativeScript Android runtime sources..." + +# Only the Android trees; the iOS staging step owns the rest of native-api/. +rm -rf \ + "$NATIVE_API/runtime/android" \ + "$NATIVE_API/ffi/jni" \ + "$NATIVE_API/jsi" \ + "$NATIVE_API/napi/common" \ + "$ANDROID_DIR/src/main/java-runtime" + +mkdir -p \ + "$NATIVE_API/runtime/android" \ + "$NATIVE_API/ffi/jni" \ + "$NATIVE_API/jsi" \ + "$NATIVE_API/napi/common" \ + "$NATIVE_API/metadata/include" \ + "$ANDROID_DIR/src/main/java-runtime" + +# The jsi runtime and its JNI interop. The napi lane is not shipped: a guest has +# no Node-API anywhere in it. +cp -R "$REPO_ROOT/NativeScript/runtime/android/jsi" "$NATIVE_API/runtime/android/" +cp -R "$REPO_ROOT/NativeScript/ffi/jni/jsi" "$NATIVE_API/ffi/jni/" + +# Parts of the standalone runtime the guest build excludes (see the source +# filters in packages/react-native/android/CMakeLists.txt). They are dropped +# here too so the package does not carry sources no consumer can compile. +# +# workers/ keeps LooperTasks: despite living beside the Worker implementation it +# is the main-thread task queue every guest needs. ConcurrentQueue has no user +# other than the Worker wrapper, so it goes with it. +rm -rf \ + "$NATIVE_API/runtime/android/jsi/assetextractor" \ + "$NATIVE_API/runtime/android/jsi/modules/module" \ + "$NATIVE_API/runtime/android/jsi/modules/console" \ + "$NATIVE_API/runtime/android/jsi/modules/timers" \ + "$NATIVE_API/runtime/android/jsi/modules/performance" \ + "$NATIVE_API/runtime/android/jsi/profiler" \ + "$NATIVE_API/runtime/android/jsi/instrumentation" +rm -f \ + "$NATIVE_API/runtime/android/jsi/workers/WorkerWrapper.cpp" \ + "$NATIVE_API/runtime/android/jsi/workers/WorkerWrapper.h" \ + "$NATIVE_API/runtime/android/jsi/workers/WorkerMessage.h" \ + "$NATIVE_API/runtime/android/jsi/workers/ConcurrentQueue.cpp" \ + "$NATIVE_API/runtime/android/jsi/workers/ConcurrentQueue.h" + +# The engine layer. Only the Hermes adapter is reachable -- it is the one written +# against plain facebook::jsi -- but the tree is copied whole so that a rooted +# include of a sibling resolves the same way it does in the repository. +cp -R "$REPO_ROOT/NativeScript/jsi/." "$NATIVE_API/jsi/" + +# ConcurrentMap.h, robin_hood.h, spinmutex.h: plain containers the runtime uses +# on both binding layers, which happen to live under napi/. +cp "$REPO_ROOT/NativeScript/napi/common/ConcurrentMap.h" \ + "$REPO_ROOT/NativeScript/napi/common/robin_hood.h" \ + "$REPO_ROOT/NativeScript/napi/common/spinmutex.h" \ + "$NATIVE_API/napi/common/" + +cp "$REPO_ROOT/metadata-generator/include/Metadata.h" \ + "$REPO_ROOT/metadata-generator/include/MetadataReader.h" \ + "$NATIVE_API/metadata/include/" 2>/dev/null || true + +# com.tns.* and com.tns.bindings.*, exactly as the standalone runtime composes them. +cp -R "$RUNTIME_JAVA_SRC/." "$ANDROID_DIR/src/main/java-runtime/" +cp -R "$BINDINGS_JAVA_SRC/." "$ANDROID_DIR/src/main/java-runtime/" + +# ts_helpers.js -- evaluated at attach; there is no module loader to require it. +mkdir -p "$ANDROID_DIR/src/main/assets/nativescript/internal" +cp "$REPO_ROOT/platforms/android/test-app/app/src/main/assets/internal/ts_helpers.js" \ + "$ANDROID_DIR/src/main/assets/nativescript/internal/ts_helpers.js" + +"$SCRIPT_DIR/build_react_native_android_tools.sh" + +checkpoint "Staged the Android runtime:" +echo " C++ sources : $(find "$NATIVE_API/runtime/android" "$NATIVE_API/ffi/jni" "$NATIVE_API/jsi" -name '*.cpp' -o -name '*.h' | wc -l | tr -d ' ') files" +echo " Java sources: $(find "$ANDROID_DIR/src/main/java-runtime" -name '*.java' | wc -l | tr -d ' ') files" +echo " Build tools : $(ls "$ANDROID_DIR/tools" | tr '\n' ' ')" diff --git a/scripts/build_react_native_android_tools.sh b/scripts/build_react_native_android_tools.sh new file mode 100755 index 000000000..2cd750f03 --- /dev/null +++ b/scripts/build_react_native_android_tools.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# Builds the Java build-time tools @nativescript/react-native ships and stages +# them into the package. +# +# android-metadata-generator.jar -- describes the app's classpath so JS can +# resolve android.* and java.* +# dts-generator.jar -- TypeScript declarations for the same +# static-binding-generator.jar -- pre-generates the Java classes that named +# (@JavaProxy) subclasses resolve to +# +# All are plain JVM tools with no NDK involvement, so unlike the runtime itself +# they can be prebuilt and shipped: there is no jsi ABI to track. +# +# The binding generator additionally shells out to jsparser, which it looks for +# under its own working directory, so that tree is staged beside the jars. Its +# Babel dependencies are declared in the package's package.json rather than +# vendored, so they resolve from the app's node_modules. +set -euo pipefail +source "$(dirname "$0")/build_utils.sh" + +TEST_APP_DIR="$REPO_ROOT/platforms/android/test-app" +TOOLS_DIR="$REPO_ROOT/packages/react-native/android/tools" + +checkpoint "Building the NativeScript Android build tools..." + +( + cd "$TEST_APP_DIR" + ./gradlew --quiet :android-metadata-generator:jar :dts-generator:jar \ + :static-binding-generator:jar +) + +mkdir -p "$TOOLS_DIR" + +MDG_JAR=$(find "$TEST_APP_DIR/build-tools/android-metadata-generator/build/libs" \ + -name "*.jar" ! -name "*-sources.jar" | head -1) +DTS_JAR=$(find "$TEST_APP_DIR/build-tools/android-dts-generator/dts-generator/build/libs" \ + -name "*.jar" ! -name "*-sources.jar" | head -1) +SBG_JAR=$(find "$TEST_APP_DIR/build-tools/static-binding-generator/build/libs" \ + -name "*.jar" ! -name "*-sources.jar" | head -1) + +if [ -z "$MDG_JAR" ] || [ -z "$DTS_JAR" ] || [ -z "$SBG_JAR" ]; then + echo "Could not find the built tool jars." >&2 + exit 1 +fi + +cp "$MDG_JAR" "$TOOLS_DIR/android-metadata-generator.jar" +cp "$DTS_JAR" "$TOOLS_DIR/dts-generator.jar" +cp "$SBG_JAR" "$TOOLS_DIR/static-binding-generator.jar" + +# The binding generator resolves jsparser relative to its working directory, so +# the tree ships with it. node_modules is deliberately excluded -- the Babel +# packages are dependencies of @nativescript/react-native and resolve from the +# app's own node_modules at run time. +rm -rf "$TOOLS_DIR/jsparser" +mkdir -p "$TOOLS_DIR/jsparser" +( + cd "$TEST_APP_DIR/build-tools/jsparser" + tar cf - --exclude node_modules --exclude tests --exclude test-results \ + --exclude package-lock.json . +) | (cd "$TOOLS_DIR/jsparser" && tar xf -) + +# Derives the metadata whitelist from the app's own bundle. Plain node scripts +# that reuse the Babel packages jsparser already depends on. +rm -rf "$TOOLS_DIR/metadata-filter" +mkdir -p "$TOOLS_DIR/metadata-filter" +cp "$TEST_APP_DIR/build-tools/metadata-filter/harvest.js" \ + "$TEST_APP_DIR/build-tools/metadata-filter/seed.js" \ + "$TEST_APP_DIR/build-tools/metadata-filter/verify-coverage.js" \ + "$TEST_APP_DIR/build-tools/metadata-filter/check-runtime-keeplist.js" \ + "$TOOLS_DIR/metadata-filter/" + +checkpoint "Staged:" +ls -la "$TOOLS_DIR" diff --git a/scripts/build_react_native_turbomodule.sh b/scripts/build_react_native_turbomodule.sh index c6a566677..5e604df80 100755 --- a/scripts/build_react_native_turbomodule.sh +++ b/scripts/build_react_native_turbomodule.sh @@ -59,8 +59,14 @@ checkpoint "Generating Hermes signature dispatch bindings for the TurboModule... mkdir -p "$(dirname "$GENERATED_SIGNATURE_DISPATCH")" NS_SIGNATURE_BINDINGS_CPP_PATH="$GENERATED_SIGNATURE_DISPATCH" npm run metagen ios-sim +# Only the trees this script stages. It used to wipe native-api/ whole, which +# also deleted what build_react_native_android.sh had put there +# (native-api/runtime/android, native-api/ffi/jni, native-api/napi/common) -- +# so whichever of the two ran last was the only half the tarball carried. rm -rf \ - "$PACKAGE_DIR/native-api" \ + "$PACKAGE_DIR/native-api/ffi/objc" \ + "$PACKAGE_DIR/native-api/jsi" \ + "$PACKAGE_DIR/native-api/metadata" \ "$PACKAGE_DIR/metadata" \ "$PACKAGE_DIR/ios/vendor" \ "$PACKAGE_DIR/types" @@ -71,6 +77,7 @@ mkdir -p \ "$PACKAGE_DIR/native-api/ffi/objc/shared/bridge/host_objects" \ "$PACKAGE_DIR/native-api/jsi/hermes" \ "$PACKAGE_DIR/native-api/jsi/shared" \ + "$PACKAGE_DIR/native-api/jsi" \ "$PACKAGE_DIR/native-api/metadata/include" \ "$PACKAGE_DIR/metadata" \ "$PACKAGE_DIR/ios/vendor/libffi/include" \ @@ -78,18 +85,27 @@ mkdir -p \ "$PACKAGE_DIR/types/objc-node-api" \ "$PACK_DESTINATION" -cp NativeScript/ffi/objc/hermes/NativeApiJsi*.mm "$PACKAGE_DIR/native-api/ffi/objc/hermes/" -cp NativeScript/ffi/objc/hermes/NativeApiJsi*.h "$PACKAGE_DIR/native-api/ffi/objc/hermes/" -cp NativeScript/ffi/objc/hermes/NativeApiJsiReactNative.h "$PACKAGE_DIR/native-api/ffi/objc/hermes/" -cp NativeScript/ffi/objc/shared/bridge/*.mm "$PACKAGE_DIR/native-api/ffi/objc/shared/bridge/" -cp NativeScript/ffi/objc/shared/bridge/*.h "$PACKAGE_DIR/native-api/ffi/objc/shared/bridge/" -cp NativeScript/ffi/objc/shared/bridge/host_objects/*.mm \ - "$PACKAGE_DIR/native-api/ffi/objc/shared/bridge/host_objects/" -cp NativeScript/ffi/objc/shared/NativeApiBackendConfig.h "$PACKAGE_DIR/native-api/ffi/objc/shared/" -cp NativeScript/ffi/objc/shared/SignatureDispatchCore.h "$PACKAGE_DIR/native-api/ffi/objc/shared/" -cp NativeScript/ffi/objc/shared/PreparedSignatureDispatch.h "$PACKAGE_DIR/native-api/ffi/objc/shared/" -cp NativeScript/jsi/hermes/*.h "$PACKAGE_DIR/native-api/jsi/hermes/" -cp NativeScript/jsi/shared/*.h "$PACKAGE_DIR/native-api/jsi/shared/" +# The platform-neutral engine layer. The Apple headers under ffi/objc are thin +# forwarders onto it -- NativeApiJsi.h is a one-line #include of +# "jsi/hermes/NativeApiJsi.h" -- so the pod does not compile without this tree, +# even though it compiles nothing out of it directly. Header-only as far as the +# TurboModule is concerned; it is on the pod's header search path via +# native-api. Staged here as well as by the Android step: either half of the +# package must be able to build from a tarball the other never contributed to. +cp -R "$REPO_ROOT/NativeScript/jsi/." "$PACKAGE_DIR/native-api/jsi/" + +# The Apple side of the bridge, staged whole rather than file by file. +# +# Only NativeApiJsi.mm is compiled: everything else arrives through #include, +# either as a header or as a .mm folded into that translation unit (see +# HostObjects.mm, which assembles host_objects/*.mm). An enumerated list has to +# name every one of those transitively, so it silently goes stale whenever the +# sources grow a file or a directory -- which is what happened here. Copying the +# trees keeps the staged set equal to the source set by construction. +cp -R "$REPO_ROOT/NativeScript/ffi/objc/hermes/." "$PACKAGE_DIR/native-api/ffi/objc/hermes/" +cp -R "$REPO_ROOT/NativeScript/ffi/objc/shared/." "$PACKAGE_DIR/native-api/ffi/objc/shared/" +# Build leftovers, not sources. +find "$PACKAGE_DIR/native-api/ffi/objc" -name '*.stamp' -delete cp "$GENERATED_SIGNATURE_DISPATCH" "$PACKAGE_DIR/native-api/ffi/objc/shared/GeneratedSignatureDispatch.inc" GENERATED_GSD_SIGNATURE_DISPATCH="$(dirname "$GENERATED_SIGNATURE_DISPATCH")/GeneratedGsdSignatureDispatch.inc" if [ -f "$GENERATED_GSD_SIGNATURE_DISPATCH" ]; then @@ -233,12 +249,53 @@ fi rm -rf "$OUTPUT_DIR" mkdir -p "$OUTPUT_DIR" +# The package ships an iOS half and an Android half, and this script only stages +# the iOS one. The Android half is generated by build_react_native_android.sh and +# build_react_native_android_tools.sh, and both of its trees are gitignored -- so +# a checkout that never ran them packs an android/ directory containing gradle +# files and nothing to build. That publishes cleanly and fails in every consuming +# app, which is why it is a hard error here rather than a warning. +missing=() +[ -d "$PACKAGE_DIR/android/src/main/java-runtime" ] || missing+=("android/src/main/java-runtime (scripts/build_react_native_android.sh)") +[ -d "$PACKAGE_DIR/native-api/runtime/android" ] || missing+=("native-api/runtime/android (scripts/build_react_native_android.sh)") +for jar in android-metadata-generator dts-generator static-binding-generator; do + [ -f "$PACKAGE_DIR/android/tools/$jar.jar" ] || missing+=("android/tools/$jar.jar (scripts/build_react_native_android_tools.sh)") +done +[ -d "$PACKAGE_DIR/android/tools/jsparser" ] || missing+=("android/tools/jsparser (scripts/build_react_native_android_tools.sh)") +[ -f "$PACKAGE_DIR/android/tools/metadata-filter/seed.js" ] || missing+=("android/tools/metadata-filter/seed.js (scripts/build_react_native_android_tools.sh)") +[ -f "$PACKAGE_DIR/android/tools/metadata-filter/harvest.js" ] || missing+=("android/tools/metadata-filter/harvest.js (scripts/build_react_native_android_tools.sh)") + +if [ ${#missing[@]} -gt 0 ]; then + echo "error: the Android half of @nativescript/react-native is not staged; refusing to pack:" >&2 + printf ' - %s\n' "${missing[@]}" >&2 + echo "Run the staging scripts named above before packing, or pass --no-pack to stage only." >&2 + exit 1 +fi + checkpoint "Packing @nativescript/react-native..." ( cd "$PACKAGE_DIR" npm pack --pack-destination "$REPO_ROOT/$OUTPUT_DIR" ) +# npm honours .npmignore and the files list; check the artifact itself rather +# than trusting that what was staged is what shipped. +tarball=$(find "$OUTPUT_DIR" -name '*.tgz' | head -1) +for required in package/android/tools/static-binding-generator.jar \ + package/android/src/main/java-runtime \ + package/android/nativescript.gradle \ + package/android/tools/metadata-filter/seed.js \ + package/android/tools/metadata-filter/harvest.js \ + package/native-api/runtime/android \ + package/native-api/ffi/jni \ + package/native-api/ffi/objc \ + package/ios; do + if ! tar tzf "$tarball" | grep -q "^${required}"; then + echo "error: ${tarball##*/} is missing ${required#package/}." >&2 + exit 1 + fi +done + cp "$OUTPUT_DIR"/*.tgz "$PACK_DESTINATION/" checkpoint "@nativescript/react-native npm package created." diff --git a/scripts/get-next-version.js b/scripts/get-next-version.js index 9bdcc2e4c..b48de49d2 100644 --- a/scripts/get-next-version.js +++ b/scripts/get-next-version.js @@ -20,6 +20,12 @@ if (!currentVersion) { "ios-node-api": "../packages/ios-node-api/package.json", "macos-node-api": "../packages/macos-node-api/package.json", "objc-node-api": "../packages/objc-node-api/package.json", + android: "../platforms/android/package.json", + "android-v8": "../packages/android-v8/package.json", + "android-hermes": "../packages/android-hermes/package.json", + "android-jsc": "../packages/android-jsc/package.json", + "android-quickjs-ng": "../packages/android-quickjs-ng/package.json", + "android-primjs": "../packages/android-primjs/package.json", }; if (!packageJsonByTarget[target]) { diff --git a/scripts/get-npm-tag.js b/scripts/get-npm-tag.js index 046c37ee9..cf065a150 100644 --- a/scripts/get-npm-tag.js +++ b/scripts/get-npm-tag.js @@ -17,6 +17,12 @@ if (!currentVersion) { "macos-node-api": "../packages/macos-node-api/package.json", "objc-node-api": "../packages/objc-node-api/package.json", "react-native": "../packages/react-native/package.json", + android: "../platforms/android/package.json", + "android-v8": "../packages/android-v8/package.json", + "android-hermes": "../packages/android-hermes/package.json", + "android-jsc": "../packages/android-jsc/package.json", + "android-quickjs-ng": "../packages/android-quickjs-ng/package.json", + "android-primjs": "../packages/android-primjs/package.json", }; if (!packageJsonByTarget[target]) { diff --git a/scripts/setup_android.sh b/scripts/setup_android.sh new file mode 100755 index 000000000..085148c59 --- /dev/null +++ b/scripts/setup_android.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# +# Prepares a checkout for building the Android runtime: +# 1. Applies the NativeScript patches onto the vendored QuickJS-NG source +# (shared with the Apple build -- see vendor/quickjs/patches/README.md). +# 2. Installs the jsparser build-tool dependencies. +# +# Fetching the engine source is a deliberate manual step, so nothing here can +# move you off the pinned commit: +# +# git submodule update --init --force vendor/quickjs/source_ng +# +# Idempotent: re-running resets the submodule work tree to the pin and re-applies. +# +set -e +source "$(dirname "$0")/build_utils.sh" + +checkpoint "Applying QuickJS patches" +node "$SCRIPT_DIR/apply_quickjs_patches.js" + +checkpoint "Installing jsparser build-tool dependencies" +JSPARSER_DIR="$REPO_ROOT/platforms/android/test-app/build-tools/jsparser" +if [ -f "$JSPARSER_DIR/package-lock.json" ]; then + npm --prefix "$JSPARSER_DIR" ci +else + npm --prefix "$JSPARSER_DIR" install +fi + +checkpoint "Android setup complete" diff --git a/scripts/test_react_native_android.sh b/scripts/test_react_native_android.sh new file mode 100755 index 000000000..41517ffd0 --- /dev/null +++ b/scripts/test_react_native_android.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# Runs the Android conformance fixture in a React Native app on a device. +# +# scripts/test_react_native_android.sh +# +# The app must already have @nativescript/react-native installed and +# android/nativescript.gradle applied (npx nativescript-rn configure does both). +# Metro has to be reachable: the script starts one if nothing answers on 8081. +set -euo pipefail +source "$(dirname "$0")/build_utils.sh" + +APP_DIR=${1:-} +if [ -z "$APP_DIR" ] || [ ! -d "$APP_DIR/android" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +ADB="${ANDROID_HOME:?ANDROID_HOME is not set}/platform-tools/adb" +FIXTURE="$REPO_ROOT/platforms/android/test/react-native/App.tsx" +MARKER="NS_ANDROID_SMOKE" +LOG=$(mktemp) + +checkpoint "Installing the conformance fixture..." +cp "$FIXTURE" "$APP_DIR/App.tsx" + +if ! curl -s -o /dev/null "http://localhost:8081/status"; then + checkpoint "Starting Metro..." + (cd "$APP_DIR" && nohup npx react-native start >/dev/null 2>&1 &) + for _ in $(seq 1 30); do + curl -s -o /dev/null "http://localhost:8081/status" && break + sleep 2 + done +fi + +PKG=$(sed -n 's/.*applicationId "\(.*\)".*/\1/p' "$APP_DIR/android/app/build.gradle" | head -1) +if [ -z "$PKG" ]; then + echo "Could not read applicationId from android/app/build.gradle" >&2 + exit 1 +fi + +checkpoint "Building and installing $PKG..." +(cd "$APP_DIR/android" && ./gradlew --quiet :app:assembleDebug) +"$ADB" reverse tcp:8081 tcp:8081 >/dev/null +"$ADB" install -r -t "$APP_DIR/android/app/build/outputs/apk/debug/app-debug.apk" >/dev/null + +checkpoint "Running..." +"$ADB" shell am force-stop "$PKG" >/dev/null 2>&1 || true +"$ADB" logcat -c +"$ADB" logcat > "$LOG" 2>&1 & +LOGPID=$! +sleep 1 +"$ADB" shell am start -n "$PKG/.MainActivity" >/dev/null + +for _ in $(seq 1 60); do + grep -q "${MARKER}_DONE\|has died\|SyntaxError" "$LOG" 2>/dev/null && break + sleep 2 +done +sleep 2 +kill $LOGPID 2>/dev/null || true + +grep -oE "$MARKER (PASS|FAIL)[^\"\\\\]*" "$LOG" || true +FAILURES=$(grep -c "$MARKER FAIL" "$LOG" || true) +TOTAL=$(grep -cE "$MARKER (PASS|FAIL)" "$LOG" || true) + +echo +if [ "$TOTAL" -eq 0 ]; then + echo "No results. Full log: $LOG" >&2 + exit 1 +fi +echo "$((TOTAL - FAILURES))/$TOTAL passed" +[ "$FAILURES" -eq 0 ] diff --git a/scripts/test_react_native_turbomodule.sh b/scripts/test_react_native_turbomodule.sh index 72ffbf910..99e84d0fb 100755 --- a/scripts/test_react_native_turbomodule.sh +++ b/scripts/test_react_native_turbomodule.sh @@ -78,7 +78,8 @@ const marker = 'NATIVESCRIPT_RN_TURBO_SMOKE_PASS'; async function runSmoke(): Promise { try { - const installed = NativeScript.init(); + // Metadata globals on the React runtime are opt-in. + const installed = NativeScript.init('', {globals: true}); const api = (globalThis as any).__nativeScriptNativeApi; if (!installed || !api) { throw new Error('NativeScript Native API JSI host object was not installed'); diff --git a/vendor/jsc/jsc-api.cpp b/vendor/jsc/jsc-api.cpp index 34c23c654..f2839d2a1 100644 --- a/vendor/jsc/jsc-api.cpp +++ b/vendor/jsc/jsc-api.cpp @@ -566,7 +566,9 @@ class FunctionInfo : public NativeInfo { info->_env->last_exception = nullptr; } - return ToJSValue(result); + // Node-API: a callback returning NULL means undefined. JSC would read a + // NULL JSValueRef as null. + return result != nullptr ? ToJSValue(result) : JSValueMakeUndefined(ctx); } // JSObjectFinalizeCallback diff --git a/vendor/quickjs/patches/README.md b/vendor/quickjs/patches/README.md new file mode 100644 index 000000000..e57346763 --- /dev/null +++ b/vendor/quickjs/patches/README.md @@ -0,0 +1,74 @@ +# Engine source patches + +The QuickJS-NG engine source under `vendor/quickjs/source_ng` is a git submodule +pinned to an upstream commit. Our hand-written NativeScript changes are kept here +as a patch applied on top of that pristine checkout, rather than baked into a +vendored copy. The engine source is shared by the Apple and Android builds, which +is why both the source and its patches live under `vendor/` rather than under +`platforms//`. + +``` +vendor/quickjs/ + source_ng/ (submodule, pristine upstream) + patches/source_ng/0001-nativescript-local-changes.patch +``` + +QuickJS-NG is the only QuickJS lane left: the bellard tree was dropped, and +`QUICKJS` is an alias for `QUICKJS_NG` (see `test-app/runtime/build.gradle`). + +The patch touches `quickjs.c` / `quickjs.h` only: the `USE_HOST_OBJECT` +host-object hooks in `JS_GetPropertyInternal` (host objects act as a non-masking +prototype fallback), the weak-ref keep-alive pair +(`JS_KeepWeakRefTargetAlive` / `JS_ClearWeakRefKeepAlives`, required by +`vendor/quickjs/quickjs-api.c`), `JS_EVAL_FLAG_COMPILE_ONLY_NO_RESOLVE` (required +by the Apple `node:vm` module), and the functions after the `/* CUSTOM */` +marker. + +## Managing them + +The patch is generated by diffing the working tree against the pinned commit: + +``` +git -C vendor/quickjs/source_ng diff > vendor/quickjs/patches/source_ng/0001-nativescript-local-changes.patch +``` + +Because the submodule work tree is patched in place it is always dirty, which is +expected -- `.gitmodules` sets `ignore = dirty` so `git status` stays quiet. Edit +the engine by editing that work tree, then regenerate the patch with the command +above. + +On a fresh checkout / in CI, fetch the submodule first, then patch it: + +``` +git submodule update --init --force vendor/quickjs/source_ng +npm run patch-quickjs # = node scripts/apply_quickjs_patches.js +``` + +Fetching is a deliberate manual step, so no script can move you off the pin +behind your back. The patch script hard-resets the submodule work tree to the +pinned commit and re-applies the patch, so it is idempotent; `--check` verifies +the patch still applies (in a throwaway worktree) without touching anything. + +Never pass `--remote` to `git submodule update`: that moves the submodule to the +tip of its branch and off the pin, silently changing the engine and its +`BC_VERSION`. + +## Compatibility + +The submodule commit sets the engine's `BC_VERSION`. The compile-time bytecode +CLI (see `.github/workflows/bytecode-compilers.yml`) MUST be built from the same +commit, or `JS_ReadObject` rejects the produced bytecode at runtime. The app then +dies on launch with no JS error at all -- on Android it surfaces as +`Failed to create JavaScript extend wrapper for class 'com/tns/NativeScriptActivity'`, +because no module ever loaded. Keep the workflow ref and the submodule pin in +lock-step. + +To check what a built APK actually shipped, the byte right after the 12-byte +container header is the engine's `BC_VERSION`: + +``` +unzip -p app-release.apk assets/app/bundle.js | head -c 16 | xxd +grep -m1 'define BC_VERSION' vendor/quickjs/source_ng/quickjs.c +``` + +Current pin: `d950d55e950dd7994a96c669c31efa967b8b79f3` (`BC_VERSION 27`). diff --git a/platforms/android/tools/patches/quickjs_ng/0001-nativescript-local-changes.patch b/vendor/quickjs/patches/source_ng/0001-nativescript-local-changes.patch similarity index 64% rename from platforms/android/tools/patches/quickjs_ng/0001-nativescript-local-changes.patch rename to vendor/quickjs/patches/source_ng/0001-nativescript-local-changes.patch index bd79a1661..ab6e0cc54 100644 --- a/platforms/android/tools/patches/quickjs_ng/0001-nativescript-local-changes.patch +++ b/vendor/quickjs/patches/source_ng/0001-nativescript-local-changes.patch @@ -1,5 +1,5 @@ diff --git a/quickjs.c b/quickjs.c -index 9053c26..83233d1 100644 +index 9053c26..d377977 100644 --- a/quickjs.c +++ b/quickjs.c @@ -379,6 +379,7 @@ struct JSRuntime { @@ -52,7 +52,7 @@ index 9053c26..83233d1 100644 static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, JSAtom prop, JSValueConst this_obj, bool throw_ref_error) -@@ -9070,6 +9085,11 @@ static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, +@@ -9070,14 +9085,19 @@ static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, JSProperty *pr; JSShapeProperty *prs; uint32_t tag; @@ -64,6 +64,16 @@ index 9053c26..83233d1 100644 tag = JS_VALUE_GET_TAG(obj); if (unlikely(tag != JS_TAG_OBJECT)) { + switch(tag) { + case JS_TAG_NULL: +- return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", prop); ++ return JS_ThrowTypeErrorAtom(ctx, "Cannot read properties of null (reading '%s')", prop); + case JS_TAG_UNDEFINED: +- return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of undefined", prop); ++ return JS_ThrowTypeErrorAtom(ctx, "Cannot read properties of undefined (reading '%s')", prop); + case JS_TAG_EXCEPTION: + return JS_EXCEPTION; + case JS_TAG_STRING: @@ -9174,14 +9194,27 @@ static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; if (em) { @@ -118,7 +128,61 @@ index 9053c26..83233d1 100644 if (unlikely(throw_ref_error)) { return JS_ThrowReferenceErrorNotDefined(ctx, prop); } else { -@@ -62187,6 +62231,7 @@ static JSValue js_weakref_constructor(JSContext *ctx, JSValueConst new_target, +@@ -10001,9 +10045,9 @@ static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj, + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + if (tag == JS_TAG_NULL) { +- JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", atom); ++ JS_ThrowTypeErrorAtom(ctx, "Cannot read properties of null (reading '%s')", atom); + } else { +- JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of undefined", atom); ++ JS_ThrowTypeErrorAtom(ctx, "Cannot read properties of undefined (reading '%s')", atom); + } + JS_FreeAtom(ctx, atom); + return JS_EXCEPTION; +@@ -37877,7 +37921,8 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, + /* Could add a flag to avoid resolution if necessary */ + if (m) { + m->func_obj = fun_obj; +- if (js_resolve_module(ctx, m) < 0) ++ if (!(flags & JS_EVAL_FLAG_COMPILE_ONLY_NO_RESOLVE) && ++ js_resolve_module(ctx, m) < 0) + goto fail1; + fun_obj = JS_NewModuleValue(ctx, m); + } +@@ -42267,11 +42312,21 @@ static JSValue js_function_toString(JSContext *ctx, JSValueConst this_val, + return JS_EXCEPTION; + + p = JS_VALUE_GET_OBJ(this_val); ++ /* NativeScript: a JS function whose source was stripped at build time must ++ not stringify like a C function. Release builds compile the app to ++ bytecode with JS_WRITE_OBJ_STRIP_SOURCE, and library code routinely asks ++ "is this native?" by looking for "[native code]" in toString() output -- ++ @nativescript/core's Style does it to tell a WeakRef from a view. Without ++ this, every class in the app answers yes and that check inverts. ++ js_class_has_bytecode() is the real distinction: true only for functions ++ backed by JS bytecode, never for a C function. */ ++ bool js_source_stripped = false; + if (js_class_has_bytecode(p->class_id)) { + JSFunctionBytecode *b = p->u.func.function_bytecode; + /* `b->source` must be pure ASCII or UTF-8 encoded */ + if (b->source) + return JS_NewStringLen(ctx, b->source, b->source_len); ++ js_source_stripped = true; + } + { + JSValue name; +@@ -42292,7 +42347,8 @@ static JSValue js_function_toString(JSContext *ctx, JSValueConst this_val, + pref = "async function *"; + break; + } +- suff = "() {\n [native code]\n}"; ++ suff = js_source_stripped ? "() {\n [stripped code]\n}" ++ : "() {\n [native code]\n}"; + name = JS_GetProperty(ctx, this_val, JS_ATOM_name); + if (JS_IsUndefined(name)) + name = js_empty_string(ctx->rt); +@@ -62187,6 +62243,7 @@ static JSValue js_weakref_constructor(JSContext *ctx, JSValueConst new_target, wr->kind = JS_WEAK_REF_KIND_WEAK_REF; wr->u.weak_ref_data = wrd; insert_weakref_record(arg, wr); @@ -126,7 +190,7 @@ index 9053c26..83233d1 100644 JS_SetOpaqueInternal(obj, wrd); return obj; -@@ -62549,6 +62594,35 @@ static void insert_weakref_record(JSValueConst target, +@@ -62549,6 +62606,35 @@ static void insert_weakref_record(JSValueConst target, *pwr = wr; } @@ -162,7 +226,7 @@ index 9053c26..83233d1 100644 /* CallSite */ static void js_callsite_finalizer(JSRuntime *rt, JSValueConst val) -@@ -63992,6 +64066,32 @@ uintptr_t js_std_cmd(int cmd, ...) { +@@ -63992,6 +64078,32 @@ uintptr_t js_std_cmd(int cmd, ...) { return rv; } @@ -196,10 +260,20 @@ index 9053c26..83233d1 100644 #undef free #undef realloc diff --git a/quickjs.h b/quickjs.h -index 6763f83..36c1f82 100644 +index 6763f83..9fb1860 100644 --- a/quickjs.h +++ b/quickjs.h -@@ -557,6 +557,8 @@ JS_EXTERN int JS_AddIntrinsicTypedArrays(JSContext *ctx); +@@ -456,6 +456,9 @@ static inline bool JS_VALUE_IS_NAN(JSValue v) + JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE tag. It can be executed + with JS_EvalFunction(). */ + #define JS_EVAL_FLAG_COMPILE_ONLY (1 << 5) ++/* NativeScript: compile a module without resolving its imports yet. Reuses the ++ unused bit; consumed in JS_EvalThis. Needed by the Apple node:vm module. */ ++#define JS_EVAL_FLAG_COMPILE_ONLY_NO_RESOLVE JS_EVAL_FLAG_UNUSED + /* don't include the stack frames before this eval in the Error() backtraces */ + #define JS_EVAL_FLAG_BACKTRACE_BARRIER (1 << 6) + /* allow top-level await in normal script. JS_Eval() returns a +@@ -557,6 +560,8 @@ JS_EXTERN int JS_AddIntrinsicTypedArrays(JSContext *ctx); JS_EXTERN int JS_AddIntrinsicPromise(JSContext *ctx); JS_EXTERN int JS_AddIntrinsicBigInt(JSContext *ctx); JS_EXTERN int JS_AddIntrinsicWeakRef(JSContext *ctx); @@ -208,7 +282,7 @@ index 6763f83..36c1f82 100644 JS_EXTERN int JS_AddPerformance(JSContext *ctx); JS_EXTERN int JS_AddIntrinsicDOMException(JSContext *ctx); JS_EXTERN int JS_AddIntrinsicAToB(JSContext *ctx); -@@ -1428,6 +1430,13 @@ JS_EXTERN int JS_SetModuleExport(JSContext *ctx, JSModuleDef *m, const char *exp +@@ -1428,6 +1433,13 @@ JS_EXTERN int JS_SetModuleExport(JSContext *ctx, JSModuleDef *m, const char *exp JS_EXTERN int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m, const JSCFunctionListEntry *tab, int len); diff --git a/vendor/quickjs/quickjs-api.c b/vendor/quickjs/quickjs-api.c index 219e08228..3ac8b0627 100644 --- a/vendor/quickjs/quickjs-api.c +++ b/vendor/quickjs/quickjs-api.c @@ -2565,28 +2565,44 @@ napi_status napi_get_all_property_names(napi_env env, napi_value object, JSValue array = JS_NewArray(env->context); JSValue proto = JS_DupValue(env->context, jsValue); + uint32_t array_index = 0; while (!JS_IsNull(proto)) { JSPropertyEnum* tab = NULL; uint32_t len = 0; - JS_GetOwnPropertyNames(env->context, &tab, &len, proto, get_filter); + if (JS_GetOwnPropertyNames(env->context, &tab, &len, proto, get_filter)) { + JS_FreeValue(env->context, proto); + JS_FreeValue(env->context, array); + return napi_pending_exception; + } for (uint32_t i = 0; i < len; i++) { JSValue name = JS_AtomToValue(env->context, tab[i].atom); - JS_SetPropertyInt64(env->context, array, i, name); + if (JS_SetPropertyInt64(env->context, array, array_index++, name) < 0) { + JS_FreePropertyEnum(env->context, tab, len); + JS_FreeValue(env->context, proto); + JS_FreeValue(env->context, array); + return napi_pending_exception; + } } JS_FreePropertyEnum(env->context, tab, len); - // Free the prototype. - JS_FreeValue(env->context, proto); - + JSValue current_proto = proto; if (key_mode == napi_key_include_prototypes) { - proto = JS_GetPrototype(env->context, proto); + proto = JS_GetPrototype(env->context, current_proto); + if (JS_IsException(proto)) { + JS_FreeValue(env->context, current_proto); + JS_FreeValue(env->context, proto); + JS_FreeValue(env->context, array); + return napi_pending_exception; + } } else { - proto = JS_NULL; + proto = JS_NULL; } + + JS_FreeValue(env->context, current_proto); } return CreateScopedResult(env, array, result); diff --git a/vendor/quickjs/source_ng b/vendor/quickjs/source_ng new file mode 160000 index 000000000..d950d55e9 --- /dev/null +++ b/vendor/quickjs/source_ng @@ -0,0 +1 @@ +Subproject commit d950d55e950dd7994a96c669c31efa967b8b79f3 diff --git a/vendor/quickjs/source_ng/CMakeLists.txt b/vendor/quickjs/source_ng/CMakeLists.txt deleted file mode 100755 index f2ada27e9..000000000 --- a/vendor/quickjs/source_ng/CMakeLists.txt +++ /dev/null @@ -1,461 +0,0 @@ -cmake_minimum_required(VERSION 3.10) - -project(quickjs LANGUAGES C) - -include(CheckCCompilerFlag) -include(GNUInstallDirs) - -set(CMAKE_C_VISIBILITY_PRESET hidden) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS ON) -set(CMAKE_C_STANDARD 11) - -# MINGW doesn't exist in older cmake versions, newer versions don't know -# about CMAKE_COMPILER_IS_MINGW, and there is no unique CMAKE_C_COMPILER_ID -# for mingw-based compilers... -if(MINGW) - # do nothing -elseif(CMAKE_C_COMPILER MATCHES "mingw") - set(MINGW TRUE) -else() - set(MINGW FALSE) -endif() - -if(CMAKE_SYSTEM_NAME STREQUAL "tvOS") - set(TVOS TRUE) -else() - set(TVOS FALSE) -endif() -if(CMAKE_SYSTEM_NAME STREQUAL "watchOS") - set(WATCHOS TRUE) -else() - set(WATCHOS FALSE) -endif() - -if(NOT CMAKE_BUILD_TYPE) - message(STATUS "No build type selected, default to Release") - set(CMAKE_BUILD_TYPE "Release") -endif() - -message(STATUS "Building in ${CMAKE_BUILD_TYPE} mode") -message(STATUS "Building with ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION} on ${CMAKE_SYSTEM}") - -macro(xcheck_add_c_compiler_flag FLAG) - string(REPLACE "-" "" FLAG_NO_HYPHEN ${FLAG}) - check_c_compiler_flag(${FLAG} COMPILER_SUPPORTS_${FLAG_NO_HYPHEN}) - if(COMPILER_SUPPORTS_${FLAG_NO_HYPHEN}) - add_compile_options(${FLAG}) - endif() -endmacro() - -xcheck_add_c_compiler_flag(-Wall) -if(NOT MSVC AND NOT IOS AND NOT TVOS AND NOT WATCHOS) - xcheck_add_c_compiler_flag(-Werror) - xcheck_add_c_compiler_flag(-Wextra) -endif() -xcheck_add_c_compiler_flag(-Wformat=2) -xcheck_add_c_compiler_flag(-Wno-implicit-fallthrough) -xcheck_add_c_compiler_flag(-Wno-sign-compare) -xcheck_add_c_compiler_flag(-Wno-missing-field-initializers) -xcheck_add_c_compiler_flag(-Wno-unused-parameter) -xcheck_add_c_compiler_flag(-Wno-unused-but-set-variable) -xcheck_add_c_compiler_flag(-Wno-unused-result) -xcheck_add_c_compiler_flag(-Wno-stringop-truncation) -xcheck_add_c_compiler_flag(-Wno-array-bounds) -xcheck_add_c_compiler_flag(-funsigned-char) - -# ClangCL is command line compatible with MSVC, so 'MSVC' is set. -if(MSVC) - xcheck_add_c_compiler_flag(-Wno-unsafe-buffer-usage) - xcheck_add_c_compiler_flag(-Wno-sign-conversion) - xcheck_add_c_compiler_flag(-Wno-nonportable-system-include-path) - xcheck_add_c_compiler_flag(-Wno-implicit-int-conversion) - xcheck_add_c_compiler_flag(-Wno-shorten-64-to-32) - xcheck_add_c_compiler_flag(-Wno-reserved-macro-identifier) - xcheck_add_c_compiler_flag(-Wno-reserved-identifier) - xcheck_add_c_compiler_flag(-Wdeprecated-declarations) - xcheck_add_c_compiler_flag(/experimental:c11atomics) - xcheck_add_c_compiler_flag(/wd4018) # -Wno-sign-conversion - xcheck_add_c_compiler_flag(/wd4061) # -Wno-implicit-fallthrough - xcheck_add_c_compiler_flag(/wd4100) # -Wno-unused-parameter - xcheck_add_c_compiler_flag(/wd4200) # -Wno-zero-length-array - xcheck_add_c_compiler_flag(/wd4242) # -Wno-shorten-64-to-32 - xcheck_add_c_compiler_flag(/wd4244) # -Wno-shorten-64-to-32 - xcheck_add_c_compiler_flag(/wd4245) # -Wno-sign-compare - xcheck_add_c_compiler_flag(/wd4267) # -Wno-shorten-64-to-32 - xcheck_add_c_compiler_flag(/wd4388) # -Wno-sign-compare - xcheck_add_c_compiler_flag(/wd4389) # -Wno-sign-compare - xcheck_add_c_compiler_flag(/wd4456) # Hides previous local declaration - xcheck_add_c_compiler_flag(/wd4457) # Hides function parameter - xcheck_add_c_compiler_flag(/wd4710) # Function not inlined - xcheck_add_c_compiler_flag(/wd4711) # Function was inlined - xcheck_add_c_compiler_flag(/wd4820) # Padding added after construct - xcheck_add_c_compiler_flag(/wd4996) # -Wdeprecated-declarations - xcheck_add_c_compiler_flag(/wd5045) # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified -endif() - -# Set a 8MB default stack size on Windows. -# It defaults to 1MB on MSVC, which is the same as our current JS stack size, -# so it will overflow and crash otherwise. -# On MinGW it defaults to 2MB. -if(WIN32) - if(MSVC) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:8388608") - else() - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack,8388608") - endif() -endif() - -# MacOS and GCC 11 or later need -Wno-maybe-uninitialized -# https://github.com/quickjs-ng/quickjs/issues/453 -if(APPLE AND CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 11) - xcheck_add_c_compiler_flag(-Wno-maybe-uninitialized) -endif() - -if(CMAKE_SYSTEM_NAME STREQUAL "WASI") - add_compile_definitions( - _WASI_EMULATED_PROCESS_CLOCKS - _WASI_EMULATED_SIGNAL - ) - add_link_options( - -lwasi-emulated-process-clocks - -lwasi-emulated-signal - ) -endif() - -if(CMAKE_BUILD_TYPE MATCHES "Debug") - xcheck_add_c_compiler_flag(/Od) - xcheck_add_c_compiler_flag(-O0) - xcheck_add_c_compiler_flag(-ggdb) - xcheck_add_c_compiler_flag(-fno-omit-frame-pointer) -endif() - -macro(xoption OPTION_NAME OPTION_TEXT OPTION_DEFAULT) - option(${OPTION_NAME} ${OPTION_TEXT} ${OPTION_DEFAULT}) - if(DEFINED ENV{${OPTION_NAME}}) - # Allow setting the option through an environment variable. - set(${OPTION_NAME} $ENV{${OPTION_NAME}}) - endif() - if(${OPTION_NAME}) - add_definitions(-D${OPTION_NAME}) - endif() - message(STATUS " ${OPTION_NAME}: ${${OPTION_NAME}}") -endmacro() - -xoption(BUILD_SHARED_LIBS "Build a shared library" OFF) -if(BUILD_SHARED_LIBS) - message(STATUS "Building a shared library") -endif() - -# note: QJS_ENABLE_TSAN is currently incompatible with the other sanitizers but we -# don't explicitly check for that because who knows what the future will bring? -# QJS_ENABLE_MSAN only works with clang at the time of writing; also not checked -# for the same reason -xoption(QJS_BUILD_EXAMPLES "Build examples" OFF) -xoption(QJS_BUILD_CLI_STATIC "Build a static qjs executable" OFF) -xoption(QJS_BUILD_CLI_WITH_MIMALLOC "Build the qjs executable with mimalloc" OFF) -xoption(QJS_BUILD_CLI_WITH_STATIC_MIMALLOC "Build the qjs executable with mimalloc (statically linked)" OFF) -xoption(QJS_DISABLE_PARSER "Disable JS source code parser" OFF) -xoption(QJS_ENABLE_ASAN "Enable AddressSanitizer (ASan)" OFF) -xoption(QJS_ENABLE_MSAN "Enable MemorySanitizer (MSan)" OFF) -xoption(QJS_ENABLE_TSAN "Enable ThreadSanitizer (TSan)" OFF) -xoption(QJS_ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer (UBSan)" OFF) - -if(QJS_ENABLE_ASAN) -message(STATUS "Building with ASan") -add_compile_options( - -fsanitize=address - -fno-sanitize-recover=all - -fno-omit-frame-pointer -) -add_link_options( - -fsanitize=address - -fno-sanitize-recover=all - -fno-omit-frame-pointer -) -endif() - -if(QJS_ENABLE_MSAN) -message(STATUS "Building with MSan") -add_compile_options( - -fsanitize=memory - -fno-sanitize-recover=all - -fno-omit-frame-pointer -) -add_link_options( - -fsanitize=memory - -fno-sanitize-recover=all - -fno-omit-frame-pointer -) -endif() - -if(QJS_ENABLE_TSAN) -message(STATUS "Building with TSan") -add_compile_options( - -fsanitize=thread - -fno-sanitize-recover=all - -fno-omit-frame-pointer -) -add_link_options( - -fsanitize=thread - -fno-sanitize-recover=all - -fno-omit-frame-pointer -) -endif() - -if(QJS_ENABLE_UBSAN) -message(STATUS "Building with UBSan") -add_compile_options( - -fsanitize=undefined - -fno-sanitize-recover=all - -fno-omit-frame-pointer -) -add_link_options( - -fsanitize=undefined - -fno-sanitize-recover=all - -fno-omit-frame-pointer -) -endif() - - -# QuickJS library -# - -xoption(QJS_BUILD_LIBC "Build standard library modules as part of the library" OFF) -macro(add_qjs_libc_if_needed target) - if(NOT QJS_BUILD_LIBC) - target_sources(${target} PRIVATE quickjs-libc.c) - endif() -endmacro() -macro(add_static_if_needed target) - if(QJS_BUILD_CLI_STATIC OR MINGW) - target_link_options(${target} PRIVATE -static) - if(MINGW) - target_link_options(${target} PRIVATE -static-libgcc) - endif() - endif() -endmacro() - -set(qjs_sources - cutils.c - dtoa.c - libregexp.c - libunicode.c - quickjs.c -) - -if(QJS_BUILD_LIBC) - list(APPEND qjs_sources quickjs-libc.c) -endif() -list(APPEND qjs_defines _GNU_SOURCE) -if(WIN32) - # NB: Windows 7 is EOL and we are only supporting in so far as it doesn't interfere with progress. - list(APPEND qjs_defines WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0601) -endif() -if(TVOS) - list(APPEND qjs_defines _TVOS) -endif() -if(WATCHOS) - list(APPEND qjs_defines _WATCHOS) -endif() -list(APPEND qjs_libs ${CMAKE_DL_LIBS}) -find_package(Threads) -if(NOT CMAKE_SYSTEM_NAME STREQUAL "WASI") - list(APPEND qjs_libs ${CMAKE_THREAD_LIBS_INIT}) -endif() - -# try to find libm -find_library(M_LIBRARIES m) -if(M_LIBRARIES OR CMAKE_C_COMPILER_ID STREQUAL "TinyCC") - list(APPEND qjs_libs m) -endif() - -add_library(qjs ${qjs_sources}) -target_compile_definitions(qjs PRIVATE ${qjs_defines}) -target_include_directories(qjs PUBLIC - $ - $ -) -target_link_libraries(qjs PUBLIC ${qjs_libs}) - -if(EMSCRIPTEN) - add_executable(qjs_wasm ${qjs_sources}) - target_link_options(qjs_wasm PRIVATE - # in emscripten 3.x, this will be set to 16k which is too small for quickjs. #write sth. to force github rebuild - -sSTACK_SIZE=2097152 # let it be 2m = 2 * 1024 * 1024 = 2097152, otherwise, stack overflow may be occured at bootstrap - -sNO_INVOKE_RUN - -sNO_EXIT_RUNTIME - -sMODULARIZE # do not mess the global - -sEXPORT_ES6 # export js file to morden es module - -sEXPORT_NAME=getQuickJs # give a name - -sTEXTDECODER=1 # it will be 2 if we use -Oz, and that will cause js -> c string convertion fail - -sNO_DEFAULT_TO_CXX # this project is pure c project, no need for c plus plus handle - -sEXPORTED_RUNTIME_METHODS=ccall,cwrap - ) - target_compile_definitions(qjs_wasm PRIVATE ${qjs_defines}) - target_link_libraries(qjs_wasm m) -endif() - - -# QuickJS bytecode compiler -# - -add_executable(qjsc - qjsc.c -) -add_qjs_libc_if_needed(qjsc) -add_static_if_needed(qjsc) -target_compile_definitions(qjsc PRIVATE ${qjs_defines}) -target_link_libraries(qjsc qjs) - - -# QuickJS CLI -# - -add_executable(qjs_exe - gen/repl.c - gen/standalone.c - qjs.c -) -add_qjs_libc_if_needed(qjs_exe) -add_static_if_needed(qjs_exe) -set_target_properties(qjs_exe PROPERTIES - OUTPUT_NAME "qjs" -) -target_compile_definitions(qjs_exe PRIVATE ${qjs_defines}) -target_link_libraries(qjs_exe qjs) -if(NOT WIN32) - set_target_properties(qjs_exe PROPERTIES ENABLE_EXPORTS TRUE) -endif() -if(QJS_BUILD_CLI_WITH_MIMALLOC OR QJS_BUILD_CLI_WITH_STATIC_MIMALLOC) - find_package(mimalloc REQUIRED) - # Upstream mimalloc doesn't provide a way to know if both libraries are supported. - if(QJS_BUILD_CLI_WITH_STATIC_MIMALLOC) - target_link_libraries(qjs_exe mimalloc-static) - else() - target_link_libraries(qjs_exe mimalloc) - endif() -endif() - -# Test262 runner -# - -if(NOT EMSCRIPTEN) - add_executable(run-test262 - run-test262.c - ) - add_qjs_libc_if_needed(run-test262) - target_compile_definitions(run-test262 PRIVATE ${qjs_defines}) - target_link_libraries(run-test262 qjs) -endif() - -# Interrupt test -# - -add_executable(api-test - api-test.c -) -target_compile_definitions(api-test PRIVATE ${qjs_defines}) -target_link_libraries(api-test qjs) - -# Unicode generator -# - -add_executable(unicode_gen EXCLUDE_FROM_ALL - cutils.c - libunicode.c - unicode_gen.c -) -target_compile_definitions(unicode_gen PRIVATE ${qjs_defines}) - -add_executable(function_source - gen/function_source.c -) -add_qjs_libc_if_needed(function_source) -target_include_directories(function_source PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_compile_definitions(function_source PRIVATE ${qjs_defines}) -target_link_libraries(function_source qjs) - -# Examples -# - -if(QJS_BUILD_EXAMPLES) - add_executable(hello - gen/hello.c - ) - add_qjs_libc_if_needed(hello) - target_include_directories(hello PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_compile_definitions(hello PRIVATE ${qjs_defines}) - target_link_libraries(hello qjs) - - add_executable(hello_module - gen/hello_module.c - ) - add_qjs_libc_if_needed(hello_module) - target_include_directories(hello_module PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_compile_definitions(hello_module PRIVATE ${qjs_defines}) - target_link_libraries(hello_module qjs) - - add_library(fib MODULE examples/fib.c) - set_target_properties(fib PROPERTIES - PREFIX "" - C_VISIBILITY_PRESET default - ) - target_compile_definitions(fib PRIVATE JS_SHARED_LIBRARY) - if(WIN32) - target_link_libraries(fib qjs) - elseif(APPLE) - target_link_options(fib PRIVATE -undefined dynamic_lookup) - endif() - - add_library(point MODULE examples/point.c) - set_target_properties(point PROPERTIES - PREFIX "" - C_VISIBILITY_PRESET default - ) - target_compile_definitions(point PRIVATE JS_SHARED_LIBRARY) - if(WIN32) - target_link_libraries(point qjs) - elseif(APPLE) - target_link_options(point PRIVATE -undefined dynamic_lookup) - endif() - - add_executable(test_fib - examples/fib.c - gen/test_fib.c - ) - add_qjs_libc_if_needed(test_fib) - target_include_directories(test_fib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_compile_definitions(test_fib PRIVATE ${qjs_defines}) - target_link_libraries(test_fib qjs) -endif() - -# Install target -# - -file(STRINGS quickjs.h quickjs_h REGEX QJS_VERSION) -string(REGEX MATCH "QJS_VERSION_MAJOR ([0-9]*)" _ "${quickjs_h}") -set(QJS_VERSION_MAJOR ${CMAKE_MATCH_1}) -string(REGEX MATCH "QJS_VERSION_MINOR ([0-9]*)" _ "${quickjs_h}") -set(QJS_VERSION_MINOR ${CMAKE_MATCH_1}) -string(REGEX MATCH "QJS_VERSION_PATCH ([0-9]*)" _ "${quickjs_h}") -set(QJS_VERSION_PATCH ${CMAKE_MATCH_1}) -set_target_properties(qjs PROPERTIES - VERSION ${QJS_VERSION_MAJOR}.${QJS_VERSION_MINOR}.${QJS_VERSION_PATCH} - SOVERSION ${QJS_VERSION_MAJOR} -) -install(FILES quickjs.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -if(QJS_BUILD_LIBC) - install(FILES quickjs-libc.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -endif() -if(NOT IOS AND NOT TVOS AND NOT WATCHOS) - install(TARGETS qjs_exe RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) - install(TARGETS qjsc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -endif() -install(TARGETS qjs EXPORT qjsConfig - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) -install(EXPORT qjsConfig DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/quickjs) -install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) -install(DIRECTORY examples DESTINATION ${CMAKE_INSTALL_DOCDIR}) diff --git a/vendor/quickjs/source_ng/LICENSE b/vendor/quickjs/source_ng/LICENSE deleted file mode 100755 index 3c78b6929..000000000 --- a/vendor/quickjs/source_ng/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017-2024 Fabrice Bellard -Copyright (c) 2017-2024 Charlie Gordon -Copyright (c) 2023-2025 Ben Noordhuis -Copyright (c) 2023-2025 Saúl Ibarra Corretgé - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/quickjs/source_ng/Makefile b/vendor/quickjs/source_ng/Makefile deleted file mode 100755 index 4ff8ca17e..000000000 --- a/vendor/quickjs/source_ng/Makefile +++ /dev/null @@ -1,147 +0,0 @@ -# -# QuickJS Javascript Engine -# -# Copyright (c) 2017-2024 Fabrice Bellard -# Copyright (c) 2017-2024 Charlie Gordon -# Copyright (c) 2023-2025 Ben Noordhuis -# Copyright (c) 2023-2025 Saúl Ibarra Corretgé -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -BUILD_DIR=build -BUILD_TYPE?=Release -INSTALL_PREFIX?=/usr/local - -QJS=$(BUILD_DIR)/qjs -QJSC=$(BUILD_DIR)/qjsc -RUN262=$(BUILD_DIR)/run-test262 - -JOBS?=$(shell getconf _NPROCESSORS_ONLN) -ifeq ($(JOBS),) -JOBS := $(shell sysctl -n hw.ncpu) -endif -ifeq ($(JOBS),) -JOBS := $(shell nproc) -endif -ifeq ($(JOBS),) -JOBS := 4 -endif - -all: $(QJS) - -amalgam: TEMP := $(shell mktemp -d) -amalgam: $(QJS) - $(QJS) amalgam.js $(TEMP)/quickjs-amalgam.c - cp quickjs.h quickjs-libc.h $(TEMP) - cd $(TEMP) && zip -9 quickjs-amalgam.zip quickjs-amalgam.c quickjs.h quickjs-libc.h - cp $(TEMP)/quickjs-amalgam.zip $(BUILD_DIR) - cd $(TEMP) && $(RM) quickjs-amalgam.zip quickjs-amalgam.c quickjs.h quickjs-libc.h - $(RM) -d $(TEMP) - -fuzz: - clang -g -O1 -fsanitize=address,undefined,fuzzer -o fuzz fuzz.c - ./fuzz - -$(BUILD_DIR): - cmake -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -DCMAKE_INSTALL_PREFIX=$(INSTALL_PREFIX) - -$(QJS): $(BUILD_DIR) - cmake --build $(BUILD_DIR) -j $(JOBS) - -$(QJSC): $(BUILD_DIR) - cmake --build $(BUILD_DIR) --target qjsc -j $(JOBS) - -install: $(QJS) $(QJSC) - cmake --build $(BUILD_DIR) --target install - -clean: - cmake --build $(BUILD_DIR) --target clean - -codegen: $(QJSC) - $(QJSC) -ss -o gen/repl.c -m repl.js - $(QJSC) -ss -o gen/standalone.c -m standalone.js - $(QJSC) -e -o gen/function_source.c tests/function_source.js - $(QJSC) -e -o gen/hello.c examples/hello.js - $(QJSC) -e -o gen/hello_module.c -m examples/hello_module.js - $(QJSC) -e -o gen/test_fib.c -m examples/test_fib.js - $(QJSC) -C -ss -o builtin-array-fromasync.h builtin-array-fromasync.js - -debug: - BUILD_TYPE=Debug $(MAKE) - -distclean: - @rm -rf $(BUILD_DIR) - -stats: $(QJS) - $(QJS) -qd - -jscheck: CFLAGS=-I. -D_GNU_SOURCE -DJS_CHECK_JSVALUE -Wall -Werror -fsyntax-only -c -o /dev/null -jscheck: - $(CC) $(CFLAGS) api-test.c - $(CC) $(CFLAGS) ctest.c - $(CC) $(CFLAGS) fuzz.c - $(CC) $(CFLAGS) gen/function_source.c - $(CC) $(CFLAGS) gen/hello.c - $(CC) $(CFLAGS) gen/hello_module.c - $(CC) $(CFLAGS) gen/repl.c - $(CC) $(CFLAGS) gen/standalone.c - $(CC) $(CFLAGS) gen/test_fib.c - $(CC) $(CFLAGS) qjs.c - $(CC) $(CFLAGS) qjsc.c - $(CC) $(CFLAGS) quickjs-libc.c - $(CC) $(CFLAGS) quickjs.c - $(CC) $(CFLAGS) run-test262.c - -# effectively .PHONY because it doesn't generate output -ctest: CFLAGS=-std=c11 -fsyntax-only -Wall -Wextra -Werror -pedantic -ctest: ctest.c quickjs.h - $(CC) $(CFLAGS) -DJS_NAN_BOXING=0 $< - $(CC) $(CFLAGS) -DJS_NAN_BOXING=1 $< - -# effectively .PHONY because it doesn't generate output -cxxtest: CXXFLAGS=-std=c++11 -fsyntax-only -Wall -Wextra -Werror -pedantic -cxxtest: cxxtest.cc quickjs.h - $(CXX) $(CXXFLAGS) -DJS_NAN_BOXING=0 $< - $(CXX) $(CXXFLAGS) -DJS_NAN_BOXING=1 $< - -test: $(QJS) - $(RUN262) -c tests.conf - -test262: $(QJS) - $(RUN262) -m -c test262.conf -a - -test262-fast: $(QJS) - $(RUN262) -m -c test262.conf -c test262-fast.conf -a - -test262-update: $(QJS) - $(RUN262) -u -c test262.conf -a -t 1 - -test262-check: $(QJS) - $(RUN262) -m -c test262.conf -E -a - -microbench: $(QJS) - $(QJS) tests/microbench.js - -unicode_gen: $(BUILD_DIR) - cmake --build $(BUILD_DIR) --target unicode_gen - -libunicode-table.h: unicode_gen - $(BUILD_DIR)/unicode_gen unicode $@ - -.PHONY: all amalgam ctest cxxtest debug fuzz jscheck install clean codegen distclean stats test test262 test262-update test262-check microbench unicode_gen $(QJS) $(QJSC) diff --git a/vendor/quickjs/source_ng/README.md b/vendor/quickjs/source_ng/README.md deleted file mode 100755 index e4b0e34cd..000000000 --- a/vendor/quickjs/source_ng/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# ⚡️ QuickJS - A mighty JavaScript engine - -## Overview - -QuickJS is a small and embeddable JavaScript engine. It aims to support the latest -[ECMAScript] specification. - -This project is a _fork_ of the [original QuickJS project] by Fabrice Bellard and Charlie Gordon, after it went dormant, with the intent of reigniting its development. - -## Getting started - -Head over to the [project website] for instructions on how to get started and more -documentation. - -## Authors - -[@bnoordhuis], [@saghul], and many more [contributors]. - -[ECMAScript]: https://tc39.es/ecma262/ -[original QuickJS project]: https://bellard.org/quickjs -[@bnoordhuis]: https://github.com/bnoordhuis -[@saghul]: https://github.com/saghul -[contributors]: https://github.com/quickjs-ng/quickjs/graphs/contributors -[project website]: https://quickjs-ng.github.io/quickjs/ diff --git a/vendor/quickjs/source_ng/amalgam.js b/vendor/quickjs/source_ng/amalgam.js deleted file mode 100755 index 028d77985..000000000 --- a/vendor/quickjs/source_ng/amalgam.js +++ /dev/null @@ -1,49 +0,0 @@ -import {loadFile, writeFile} from "qjs:std" - -const cutils_c = loadFile("cutils.c") -const cutils_h = loadFile("cutils.h") -const dtoa_c = loadFile("dtoa.c") -const dtoa_h = loadFile("dtoa.h") -const libregexp_c = loadFile("libregexp.c") -const libregexp_h = loadFile("libregexp.h") -const libregexp_opcode_h = loadFile("libregexp-opcode.h") -const libunicode_c = loadFile("libunicode.c") -const libunicode_h = loadFile("libunicode.h") -const libunicode_table_h = loadFile("libunicode-table.h") -const list_h = loadFile("list.h") -const quickjs_atom_h = loadFile("quickjs-atom.h") -const quickjs_c = loadFile("quickjs.c") -const quickjs_c_atomics_h = loadFile("quickjs-c-atomics.h") -const quickjs_h = loadFile("quickjs.h") -const quickjs_libc_c = loadFile("quickjs-libc.c") -const quickjs_libc_h = loadFile("quickjs-libc.h") -const quickjs_opcode_h = loadFile("quickjs-opcode.h") -const gen_builtin_array_fromasync_h = loadFile("builtin-array-fromasync.h") - -let source = "#if defined(QJS_BUILD_LIBC) && defined(__linux__) && !defined(_GNU_SOURCE)\n" - + "#define _GNU_SOURCE\n" - + "#endif\n" - + quickjs_c_atomics_h - + cutils_h - + dtoa_h - + list_h - + libunicode_h // exports lre_is_id_start, used by libregexp.h - + libregexp_h - + libunicode_table_h - + quickjs_h - + quickjs_c - + cutils_c - + dtoa_c - + libregexp_c - + libunicode_c - + "#ifdef QJS_BUILD_LIBC\n" - + quickjs_libc_h - + quickjs_libc_c - + "#endif // QJS_BUILD_LIBC\n" -source = source.replace(/#include "quickjs-atom.h"/g, quickjs_atom_h) -source = source.replace(/#include "quickjs-opcode.h"/g, quickjs_opcode_h) -source = source.replace(/#include "libregexp-opcode.h"/g, libregexp_opcode_h) -source = source.replace(/#include "builtin-array-fromasync.h"/g, - gen_builtin_array_fromasync_h) -source = source.replace(/#include "[^"]+"/g, "") -writeFile(execArgv[2] ?? "quickjs-amalgam.c", source) diff --git a/vendor/quickjs/source_ng/api-test.c b/vendor/quickjs/source_ng/api-test.c deleted file mode 100755 index ef28a4f5f..000000000 --- a/vendor/quickjs/source_ng/api-test.c +++ /dev/null @@ -1,763 +0,0 @@ -#ifdef NDEBUG -#undef NDEBUG -#endif -#include -#include -#include -#include "quickjs.h" -#include "cutils.h" - -static JSValue eval(JSContext *ctx, const char *code) -{ - return JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_GLOBAL); -} - -static JSValue cfunc_callback(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_ThrowTypeError(ctx, "from cfunc"); -} - -static JSValue cfuncdata_callback(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, - int magic, JSValueConst *func_data) -{ - return JS_ThrowTypeError(ctx, "from cfuncdata"); -} - -static void cfunctions(void) -{ - uint32_t length; - const char *s; - JSValue ret, stack; - - JSRuntime *rt = JS_NewRuntime(); - JSContext *ctx = JS_NewContext(rt); - JSValue cfunc = JS_NewCFunction(ctx, cfunc_callback, "cfunc", 42); - JSValue cfuncdata = - JS_NewCFunctionData2(ctx, cfuncdata_callback, "cfuncdata", - /*length*/1337, /*magic*/0, /*data_len*/0, NULL); - JSValue global = JS_GetGlobalObject(ctx); - JS_SetPropertyStr(ctx, global, "cfunc", cfunc); - JS_SetPropertyStr(ctx, global, "cfuncdata", cfuncdata); - JS_FreeValue(ctx, global); - - ret = eval(ctx, "cfunc.name"); - assert(!JS_IsException(ret)); - assert(JS_IsString(ret)); - s = JS_ToCString(ctx, ret); - JS_FreeValue(ctx, ret); - assert(s); - assert(!strcmp(s, "cfunc")); - JS_FreeCString(ctx, s); - ret = eval(ctx, "cfunc.length"); - assert(!JS_IsException(ret)); - assert(JS_IsNumber(ret)); - assert(0 == JS_ToUint32(ctx, &length, ret)); - assert(length == 42); - - ret = eval(ctx, "cfuncdata.name"); - assert(!JS_IsException(ret)); - assert(JS_IsString(ret)); - s = JS_ToCString(ctx, ret); - JS_FreeValue(ctx, ret); - assert(s); - assert(!strcmp(s, "cfuncdata")); - JS_FreeCString(ctx, s); - ret = eval(ctx, "cfuncdata.length"); - assert(!JS_IsException(ret)); - assert(JS_IsNumber(ret)); - assert(0 == JS_ToUint32(ctx, &length, ret)); - assert(length == 1337); - - ret = eval(ctx, "cfunc()"); - assert(JS_IsException(ret)); - ret = JS_GetException(ctx); - assert(JS_IsError(ret)); - stack = JS_GetPropertyStr(ctx, ret, "stack"); - assert(JS_IsString(stack)); - s = JS_ToCString(ctx, stack); - JS_FreeValue(ctx, stack); - assert(s); - assert(!strcmp(s, " at cfunc (native)\n at (:1:1)\n")); - JS_FreeCString(ctx, s); - s = JS_ToCString(ctx, ret); - JS_FreeValue(ctx, ret); - assert(s); - assert(!strcmp(s, "TypeError: from cfunc")); - JS_FreeCString(ctx, s); - - ret = eval(ctx, "cfuncdata()"); - assert(JS_IsException(ret)); - ret = JS_GetException(ctx); - assert(JS_IsError(ret)); - stack = JS_GetPropertyStr(ctx, ret, "stack"); - assert(JS_IsString(stack)); - s = JS_ToCString(ctx, stack); - JS_FreeValue(ctx, stack); - assert(s); - assert(!strcmp(s, " at cfuncdata (native)\n at (:1:1)\n")); - JS_FreeCString(ctx, s); - s = JS_ToCString(ctx, ret); - JS_FreeValue(ctx, ret); - assert(s); - assert(!strcmp(s, "TypeError: from cfuncdata")); - JS_FreeCString(ctx, s); - - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -#define MAX_TIME 10 - -static int timeout_interrupt_handler(JSRuntime *rt, void *opaque) -{ - int *time = (int *)opaque; - if (*time <= MAX_TIME) - *time += 1; - return *time > MAX_TIME; -} - -static void sync_call(void) -{ - static const char code[] = -"(function() { \ - try { \ - while (true) {} \ - } catch (e) {} \ -})();"; - - JSRuntime *rt = JS_NewRuntime(); - JSContext *ctx = JS_NewContext(rt); - int time = 0; - JS_SetInterruptHandler(rt, timeout_interrupt_handler, &time); - JSValue ret = eval(ctx, code); - assert(time > MAX_TIME); - assert(JS_IsException(ret)); - JS_FreeValue(ctx, ret); - assert(JS_HasException(ctx)); - JSValue e = JS_GetException(ctx); - assert(JS_IsUncatchableError(e)); - JS_FreeValue(ctx, e); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -static void async_call(void) -{ - static const char code[] = -"(async function() { \ - const loop = async () => { \ - await Promise.resolve(); \ - while (true) {} \ - }; \ - await loop().catch(() => {}); \ -})();"; - - JSRuntime *rt = JS_NewRuntime(); - JSContext *ctx = JS_NewContext(rt); - int time = 0; - JS_SetInterruptHandler(rt, timeout_interrupt_handler, &time); - JSValue ret = eval(ctx, code); - assert(!JS_IsException(ret)); - JS_FreeValue(ctx, ret); - assert(JS_IsJobPending(rt)); - int r = 0; - while (JS_IsJobPending(rt)) { - r = JS_ExecutePendingJob(rt, &ctx); - } - assert(time > MAX_TIME); - assert(r == -1); - assert(JS_HasException(ctx)); - JSValue e = JS_GetException(ctx); - assert(JS_IsUncatchableError(e)); - JS_FreeValue(ctx, e); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -static JSValue save_value(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - assert(argc == 1); - JSValue *p = (JSValue *)JS_GetContextOpaque(ctx); - *p = JS_DupValue(ctx, argv[0]); - return JS_UNDEFINED; -} - -static void async_call_stack_overflow(void) -{ - static const char code[] = -"(async function() { \ - const f = () => f(); \ - try { \ - await Promise.resolve(); \ - f(); \ - } catch (e) { \ - save_value(e); \ - } \ -})();"; - - JSRuntime *rt = JS_NewRuntime(); - JSContext *ctx = JS_NewContext(rt); - JSValue value = JS_UNDEFINED; - JS_SetContextOpaque(ctx, &value); - JSValue global = JS_GetGlobalObject(ctx); - JS_SetPropertyStr(ctx, global, "save_value", JS_NewCFunction(ctx, save_value, "save_value", 1)); - JS_FreeValue(ctx, global); - JSValue ret = eval(ctx, code); - assert(!JS_IsException(ret)); - JS_FreeValue(ctx, ret); - assert(JS_IsJobPending(rt)); - int r = 0; - while (JS_IsJobPending(rt)) { - r = JS_ExecutePendingJob(rt, &ctx); - } - assert(r == 1); - assert(!JS_HasException(ctx)); - assert(JS_IsError(value)); // stack overflow should be caught - JS_FreeValue(ctx, value); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -// https://github.com/quickjs-ng/quickjs/issues/914 -static void raw_context_global_var(void) -{ - JSRuntime *rt = JS_NewRuntime(); - JSContext *ctx = JS_NewContextRaw(rt); - JS_AddIntrinsicEval(ctx); - { - JSValue ret = eval(ctx, "globalThis"); - assert(JS_IsException(ret)); - JS_FreeValue(ctx, ret); - } - { - JSValue ret = eval(ctx, "var x = 42"); - assert(JS_IsUndefined(ret)); - JS_FreeValue(ctx, ret); - } - { - JSValue ret = eval(ctx, "function f() {}"); - assert(JS_IsUndefined(ret)); - JS_FreeValue(ctx, ret); - } - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -static void is_array(void) -{ - JSRuntime *rt = JS_NewRuntime(); - JSContext *ctx = JS_NewContext(rt); - { - JSValue ret = eval(ctx, "[]"); - assert(!JS_IsException(ret)); - assert(JS_IsArray(ret)); - JS_FreeValue(ctx, ret); - } - { - JSValue ret = eval(ctx, "new Proxy([], {})"); - assert(!JS_IsException(ret)); - assert(!JS_IsArray(ret)); - assert(JS_IsProxy(ret)); - JSValue handler = JS_GetProxyHandler(ctx, ret); - JSValue target = JS_GetProxyTarget(ctx, ret); - assert(!JS_IsException(handler)); - assert(!JS_IsException(target)); - assert(!JS_IsProxy(handler)); - assert(!JS_IsProxy(target)); - assert(JS_IsObject(handler)); - assert(JS_IsArray(target)); - JS_FreeValue(ctx, handler); - JS_FreeValue(ctx, target); - JS_FreeValue(ctx, ret); - } - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -static int loader_calls; - -static JSModuleDef *loader(JSContext *ctx, const char *name, void *opaque) -{ - loader_calls++; - assert(!strcmp(name, "b")); - static const char code[] = "export function f(x){}"; - JSValue ret = JS_Eval(ctx, code, strlen(code), "b", - JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY); - assert(!JS_IsException(ret)); - JSModuleDef *m = JS_VALUE_GET_PTR(ret); - assert(m); - JS_FreeValue(ctx, ret); - return m; -} - -static void module_serde(void) -{ - JSRuntime *rt = JS_NewRuntime(); - //JS_SetDumpFlags(rt, JS_DUMP_MODULE_RESOLVE); - JS_SetModuleLoaderFunc(rt, NULL, loader, NULL); - JSContext *ctx = JS_NewContext(rt); - static const char code[] = "import {f} from 'b'; f()"; - assert(loader_calls == 0); - JSValue mod = JS_Eval(ctx, code, strlen(code), "a", - JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY); - assert(loader_calls == 1); - assert(!JS_IsException(mod)); - assert(JS_IsModule(mod)); - size_t len = 0; - uint8_t *buf = JS_WriteObject(ctx, &len, mod, - JS_WRITE_OBJ_BYTECODE|JS_WRITE_OBJ_REFERENCE); - assert(buf); - assert(len > 0); - JS_FreeValue(ctx, mod); - assert(loader_calls == 1); - mod = JS_ReadObject(ctx, buf, len, JS_READ_OBJ_BYTECODE); - js_free(ctx, buf); - assert(loader_calls == 1); // 'b' is returned from cache - assert(!JS_IsException(mod)); - JSValue ret = JS_EvalFunction(ctx, mod); - assert(!JS_IsException(ret)); - assert(JS_IsPromise(ret)); - JSValue result = JS_PromiseResult(ctx, ret); - assert(!JS_IsException(result)); - assert(JS_IsUndefined(result)); - JS_FreeValue(ctx, result); - JS_FreeValue(ctx, ret); - JS_FreeValue(ctx, mod); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -static void two_byte_string(void) -{ - JSRuntime *rt = JS_NewRuntime(); - JSContext *ctx = JS_NewContext(rt); - { - JSValue v = JS_NewTwoByteString(ctx, NULL, 0); - assert(!JS_IsException(v)); - const char *s = JS_ToCString(ctx, v); - assert(s); - assert(!strcmp(s, "")); - JS_FreeCString(ctx, s); - JS_FreeValue(ctx, v); - } - { - JSValue v = JS_NewTwoByteString(ctx, (uint16_t[]){'o','k'}, 2); - assert(!JS_IsException(v)); - const char *s = JS_ToCString(ctx, v); - assert(s); - assert(!strcmp(s, "ok")); - JS_FreeCString(ctx, s); - JS_FreeValue(ctx, v); - } - { - JSValue v = JS_NewTwoByteString(ctx, (uint16_t[]){0xD800}, 1); - assert(!JS_IsException(v)); - const char *s = JS_ToCString(ctx, v); - assert(s); - // questionable but surrogates don't map to UTF-8 without WTF-8 - assert(!strcmp(s, "\xED\xA0\x80")); - JS_FreeCString(ctx, s); - JS_FreeValue(ctx, v); - } - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -static void weak_map_gc_check(void) -{ - static const char init_code[] = -"const map = new WeakMap(); \ -function addItem() { \ - const k = { \ - text: 'a', \ - }; \ - map.set(k, {k}); \ -}"; - static const char test_code[] = "addItem()"; - - JSRuntime *rt = JS_NewRuntime(); - JSContext *ctx = JS_NewContext(rt); - - JSValue ret = eval(ctx, init_code); - assert(!JS_IsException(ret)); - - JSValue ret_test = eval(ctx, test_code); - assert(!JS_IsException(ret_test)); - JS_RunGC(rt); - JSMemoryUsage memory_usage; - JS_ComputeMemoryUsage(rt, &memory_usage); - - for (int i = 0; i < 3; i++) { - JSValue ret_test2 = eval(ctx, test_code); - assert(!JS_IsException(ret_test2)); - JS_RunGC(rt); - JSMemoryUsage memory_usage2; - JS_ComputeMemoryUsage(rt, &memory_usage2); - - assert(memory_usage.memory_used_count == memory_usage2.memory_used_count); - assert(memory_usage.memory_used_size == memory_usage2.memory_used_size); - JS_FreeValue(ctx, ret_test2); - } - - JS_FreeValue(ctx, ret); - JS_FreeValue(ctx, ret_test); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -struct { - int hook_type_call_count[4]; -} promise_hook_state; - -static void promise_hook_cb(JSContext *ctx, JSPromiseHookType type, - JSValueConst promise, JSValueConst parent_promise, - void *opaque) -{ - assert(type == JS_PROMISE_HOOK_INIT || - type == JS_PROMISE_HOOK_BEFORE || - type == JS_PROMISE_HOOK_AFTER || - type == JS_PROMISE_HOOK_RESOLVE); - promise_hook_state.hook_type_call_count[type]++; - assert(opaque == (void *)&promise_hook_state); - if (!JS_IsUndefined(parent_promise)) { - JSValue global_object = JS_GetGlobalObject(ctx); - JS_SetPropertyStr(ctx, global_object, "actual", - JS_DupValue(ctx, parent_promise)); - JS_FreeValue(ctx, global_object); - } -} - -static void promise_hook(void) -{ - int *cc = promise_hook_state.hook_type_call_count; - JSContext *unused; - JSRuntime *rt = JS_NewRuntime(); - //JS_SetDumpFlags(rt, JS_DUMP_PROMISE); - JS_SetPromiseHook(rt, promise_hook_cb, &promise_hook_state); - JSContext *ctx = JS_NewContext(rt); - JSValue global_object = JS_GetGlobalObject(ctx); - { - // empty module; creates an outer and inner module promise; - // JS_Eval returns the outer promise - JSValue ret = JS_Eval(ctx, "", 0, "", JS_EVAL_TYPE_MODULE); - assert(!JS_IsException(ret)); - assert(JS_IsPromise(ret)); - assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); - JS_FreeValue(ctx, ret); - assert(2 == cc[JS_PROMISE_HOOK_INIT]); - assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); - assert(0 == cc[JS_PROMISE_HOOK_AFTER]); - assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]); - assert(!JS_IsJobPending(rt)); - } - memset(&promise_hook_state, 0, sizeof(promise_hook_state)); - { - // module with unresolved promise; the outer and inner module promises - // are resolved but not the user's promise - static const char code[] = "new Promise(() => {})"; - JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); - assert(!JS_IsException(ret)); - assert(JS_IsPromise(ret)); - assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise - JS_FreeValue(ctx, ret); - assert(3 == cc[JS_PROMISE_HOOK_INIT]); - assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); - assert(0 == cc[JS_PROMISE_HOOK_AFTER]); - assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]); // outer and inner module promise - assert(!JS_IsJobPending(rt)); - } - memset(&promise_hook_state, 0, sizeof(promise_hook_state)); - { - // module with resolved promise - static const char code[] = "new Promise((resolve,reject) => resolve())"; - JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); - assert(!JS_IsException(ret)); - assert(JS_IsPromise(ret)); - assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise - JS_FreeValue(ctx, ret); - assert(3 == cc[JS_PROMISE_HOOK_INIT]); - assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); - assert(0 == cc[JS_PROMISE_HOOK_AFTER]); - assert(3 == cc[JS_PROMISE_HOOK_RESOLVE]); - assert(!JS_IsJobPending(rt)); - } - memset(&promise_hook_state, 0, sizeof(promise_hook_state)); - { - // module with rejected promise - static const char code[] = "new Promise((resolve,reject) => reject())"; - JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); - assert(!JS_IsException(ret)); - assert(JS_IsPromise(ret)); - assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise - JS_FreeValue(ctx, ret); - assert(3 == cc[JS_PROMISE_HOOK_INIT]); - assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); - assert(0 == cc[JS_PROMISE_HOOK_AFTER]); - assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]); - assert(!JS_IsJobPending(rt)); - } - memset(&promise_hook_state, 0, sizeof(promise_hook_state)); - { - // module with promise chain - static const char code[] = - "globalThis.count = 0;" - "globalThis.actual = undefined;" // set by promise_hook_cb - "globalThis.expected = new Promise(resolve => resolve());" - "expected.then(_ => count++)"; - JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); - assert(!JS_IsException(ret)); - assert(JS_IsPromise(ret)); - assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise - JS_FreeValue(ctx, ret); - assert(4 == cc[JS_PROMISE_HOOK_INIT]); - assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); - assert(0 == cc[JS_PROMISE_HOOK_AFTER]); - assert(3 == cc[JS_PROMISE_HOOK_RESOLVE]); - JSValue v = JS_GetPropertyStr(ctx, global_object, "count"); - assert(!JS_IsException(v)); - int32_t count; - assert(0 == JS_ToInt32(ctx, &count, v)); - assert(0 == count); - JS_FreeValue(ctx, v); - assert(JS_IsJobPending(rt)); - assert(1 == JS_ExecutePendingJob(rt, &unused)); - assert(!JS_HasException(ctx)); - assert(4 == cc[JS_PROMISE_HOOK_INIT]); - assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); - assert(0 == cc[JS_PROMISE_HOOK_AFTER]); - assert(4 == cc[JS_PROMISE_HOOK_RESOLVE]); - assert(!JS_IsJobPending(rt)); - v = JS_GetPropertyStr(ctx, global_object, "count"); - assert(!JS_IsException(v)); - assert(0 == JS_ToInt32(ctx, &count, v)); - assert(1 == count); - JS_FreeValue(ctx, v); - JSValue actual = JS_GetPropertyStr(ctx, global_object, "actual"); - JSValue expected = JS_GetPropertyStr(ctx, global_object, "expected"); - assert(!JS_IsException(actual)); - assert(!JS_IsException(expected)); - assert(JS_IsSameValue(ctx, actual, expected)); - JS_FreeValue(ctx, actual); - JS_FreeValue(ctx, expected); - } - memset(&promise_hook_state, 0, sizeof(promise_hook_state)); - { - // module with thenable; fires before and after hooks - static const char code[] = - "new Promise(resolve => resolve({then(resolve){ resolve() }}))"; - JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); - assert(!JS_IsException(ret)); - assert(JS_IsPromise(ret)); - assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise - JS_FreeValue(ctx, ret); - assert(3 == cc[JS_PROMISE_HOOK_INIT]); - assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); - assert(0 == cc[JS_PROMISE_HOOK_AFTER]); - assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]); - assert(JS_IsJobPending(rt)); - assert(1 == JS_ExecutePendingJob(rt, &unused)); - assert(!JS_HasException(ctx)); - assert(3 == cc[JS_PROMISE_HOOK_INIT]); - assert(1 == cc[JS_PROMISE_HOOK_BEFORE]); - assert(1 == cc[JS_PROMISE_HOOK_AFTER]); - assert(3 == cc[JS_PROMISE_HOOK_RESOLVE]); - assert(!JS_IsJobPending(rt)); - } - JS_FreeValue(ctx, global_object); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -static void dump_memory_usage(void) -{ - JSMemoryUsage stats; - - JSRuntime *rt = NULL; - JSContext *ctx = NULL; - - rt = JS_NewRuntime(); - ctx = JS_NewContext(rt); - - //JS_SetDumpFlags(rt, JS_DUMP_PROMISE); - - static const char code[] = - "globalThis.count = 0;" - "globalThis.actual = undefined;" // set by promise_hook_cb - "globalThis.expected = new Promise(resolve => resolve());" - "expected.then(_ => count++)"; - - JSValue evalVal = JS_Eval(ctx, code, strlen(code), "", 0); - JS_FreeValue(ctx, evalVal); - - FILE *temp = tmpfile(); - assert(temp != NULL); - JS_ComputeMemoryUsage(rt, &stats); - JS_DumpMemoryUsage(temp, &stats, rt); - // JS_DumpMemoryUsage(stdout, &stats, rt); - fclose(temp); - - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -static void new_errors(void) -{ - typedef struct { - const char name[16]; - JSValue (*func)(JSContext *, const char *, ...); - } Entry; - static const Entry entries[] = { - {"Error", JS_NewPlainError}, - {"InternalError", JS_NewInternalError}, - {"RangeError", JS_NewRangeError}, - {"ReferenceError", JS_NewReferenceError}, - {"SyntaxError", JS_NewSyntaxError}, - {"TypeError", JS_NewTypeError}, - }; - const Entry *e; - - JSRuntime *rt = JS_NewRuntime(); - JSContext *ctx = JS_NewContext(rt); - for (e = entries; e < endof(entries); e++) { - JSValue obj = (*e->func)(ctx, "the %s", "needle"); - assert(!JS_IsException(obj)); - assert(JS_IsObject(obj)); - assert(JS_IsError(obj)); - const char *haystack = JS_ToCString(ctx, obj); - char needle[256]; - snprintf(needle, sizeof(needle), "%s: the needle", e->name); - assert(strstr(haystack, needle)); - JS_FreeCString(ctx, haystack); - JSValue stack = JS_GetPropertyStr(ctx, obj, "stack"); - assert(!JS_IsException(stack)); - assert(JS_IsString(stack)); - JS_FreeValue(ctx, stack); - JS_FreeValue(ctx, obj); - } - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -static int gop_get_own_property(JSContext *ctx, JSPropertyDescriptor *desc, - JSValueConst obj, JSAtom prop) -{ - const char *name; - int found; - - found = 0; - name = JS_AtomToCString(ctx, prop); - if (!name) - return -1; - if (!strcmp(name, "answer")) { - found = 1; - *desc = (JSPropertyDescriptor){ - .value = JS_NewInt32(ctx, 42), - .flags = JS_PROP_C_W_E | JS_PROP_HAS_VALUE, - }; - } - JS_FreeCString(ctx, name); - return found; -} - -static void global_object_prototype(void) -{ - static const char code[] = "answer"; - JSValue global_object, proto, ret; - JSClassID class_id; - JSRuntime *rt; - JSContext *ctx; - int32_t answer; - int res; - - { - rt = JS_NewRuntime(); - ctx = JS_NewContext(rt); - proto = JS_NewObject(ctx); - assert(JS_IsObject(proto)); - JSCFunctionListEntry prop = JS_PROP_INT32_DEF("answer", 42, JS_PROP_C_W_E); - JS_SetPropertyFunctionList(ctx, proto, &prop, 1); - global_object = JS_GetGlobalObject(ctx); - res = JS_SetPrototype(ctx, global_object, proto); - assert(res == true); - JS_FreeValue(ctx, global_object); - JS_FreeValue(ctx, proto); - ret = eval(ctx, code); - assert(!JS_IsException(ret)); - assert(JS_IsNumber(ret)); - res = JS_ToInt32(ctx, &answer, ret); - assert(res == 0); - assert(answer == 42); - JS_FreeValue(ctx, ret); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); - } - { - JSClassExoticMethods exotic = (JSClassExoticMethods){ - .get_own_property = gop_get_own_property, - }; - JSClassDef def = (JSClassDef){ - .class_name = "Global Object", - .exotic = &exotic, - }; - rt = JS_NewRuntime(); - class_id = 0; - JS_NewClassID(rt, &class_id); - res = JS_NewClass(rt, class_id, &def); - assert(res == 0); - ctx = JS_NewContext(rt); - proto = JS_NewObjectClass(ctx, class_id); - assert(JS_IsObject(proto)); - global_object = JS_GetGlobalObject(ctx); - res = JS_SetPrototype(ctx, global_object, proto); - assert(res == true); - JS_FreeValue(ctx, global_object); - JS_FreeValue(ctx, proto); - ret = eval(ctx, code); - assert(!JS_IsException(ret)); - assert(JS_IsNumber(ret)); - res = JS_ToInt32(ctx, &answer, ret); - assert(res == 0); - assert(answer == 42); - JS_FreeValue(ctx, ret); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); - } -} - -// https://github.com/quickjs-ng/quickjs/issues/1178 -static void slice_string_tocstring(void) -{ - JSRuntime *rt = JS_NewRuntime(); - JSContext *ctx = JS_NewContext(rt); - JSValue ret = eval(ctx, "'.'.repeat(16384).slice(1, -1)"); - assert(!JS_IsException(ret)); - assert(JS_IsString(ret)); - const char *str = JS_ToCString(ctx, ret); - assert(strlen(str) == 16382); - JS_FreeCString(ctx, str); - JS_FreeValue(ctx, ret); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -int main(void) -{ - cfunctions(); - sync_call(); - async_call(); - async_call_stack_overflow(); - raw_context_global_var(); - is_array(); - module_serde(); - two_byte_string(); - weak_map_gc_check(); - promise_hook(); - dump_memory_usage(); - new_errors(); - global_object_prototype(); - slice_string_tocstring(); - return 0; -} diff --git a/vendor/quickjs/source_ng/builtin-array-fromasync.h b/vendor/quickjs/source_ng/builtin-array-fromasync.h deleted file mode 100755 index 19f1f0774..000000000 --- a/vendor/quickjs/source_ng/builtin-array-fromasync.h +++ /dev/null @@ -1,113 +0,0 @@ -/* File generated automatically by the QuickJS-ng compiler. */ - -#include - -const uint32_t qjsc_builtin_array_fromasync_size = 826; - -const uint8_t qjsc_builtin_array_fromasync[826] = { - 0x15, 0x0d, 0x01, 0x1a, 0x61, 0x73, 0x79, 0x6e, - 0x63, 0x49, 0x74, 0x65, 0x72, 0x61, 0x74, 0x6f, - 0x72, 0x01, 0x10, 0x69, 0x74, 0x65, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x01, 0x12, 0x61, 0x72, 0x72, - 0x61, 0x79, 0x4c, 0x69, 0x6b, 0x65, 0x01, 0x0a, - 0x6d, 0x61, 0x70, 0x46, 0x6e, 0x01, 0x0e, 0x74, - 0x68, 0x69, 0x73, 0x41, 0x72, 0x67, 0x01, 0x0c, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x01, 0x02, - 0x69, 0x01, 0x1a, 0x69, 0x73, 0x43, 0x6f, 0x6e, - 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, 0x72, - 0x01, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x01, 0x0c, - 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x01, 0x08, - 0x69, 0x74, 0x65, 0x72, 0x01, 0x1c, 0x6e, 0x6f, - 0x74, 0x20, 0x61, 0x20, 0x66, 0x75, 0x6e, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x01, 0x08, 0x63, 0x61, - 0x6c, 0x6c, 0x0c, 0x00, 0x02, 0x00, 0xa2, 0x01, - 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x04, 0x01, - 0xa4, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x43, 0x02, - 0x01, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x01, - 0x03, 0x05, 0xaa, 0x02, 0x00, 0x01, 0x40, 0xa0, - 0x03, 0x00, 0x01, 0x40, 0xc6, 0x03, 0x00, 0x01, - 0x40, 0xcc, 0x01, 0x00, 0x01, 0x40, 0xc8, 0x03, - 0x00, 0x01, 0x40, 0x0c, 0x60, 0x02, 0x01, 0xf8, - 0x01, 0x03, 0x0e, 0x01, 0x06, 0x05, 0x00, 0x86, - 0x04, 0x11, 0xca, 0x03, 0x00, 0x01, 0x00, 0xcc, - 0x03, 0x00, 0x01, 0x00, 0xce, 0x03, 0x00, 0x01, - 0x00, 0xca, 0x03, 0x01, 0xff, 0xff, 0xff, 0xff, - 0x0f, 0x20, 0xcc, 0x03, 0x01, 0x01, 0x20, 0xce, - 0x03, 0x01, 0x02, 0x20, 0xd0, 0x03, 0x02, 0x00, - 0x20, 0xd2, 0x03, 0x02, 0x04, 0x20, 0xd4, 0x03, - 0x02, 0x05, 0x20, 0xd6, 0x03, 0x02, 0x06, 0x20, - 0xd8, 0x03, 0x02, 0x07, 0x20, 0x64, 0x06, 0x08, - 0x20, 0x82, 0x01, 0x07, 0x09, 0x20, 0xda, 0x03, - 0x0a, 0x08, 0x30, 0x82, 0x01, 0x0d, 0x0b, 0x20, - 0xd4, 0x01, 0x0d, 0x0c, 0x20, 0x10, 0x00, 0x01, - 0x00, 0xa0, 0x03, 0x01, 0x03, 0xc6, 0x03, 0x02, - 0x03, 0xc8, 0x03, 0x04, 0x03, 0xaa, 0x02, 0x00, - 0x03, 0xcc, 0x01, 0x03, 0x03, 0x08, 0xc4, 0x0d, - 0x62, 0x02, 0x00, 0x62, 0x01, 0x00, 0x62, 0x00, - 0x00, 0xd3, 0xcb, 0xd4, 0x11, 0xf4, 0xec, 0x08, - 0x0e, 0x39, 0x46, 0x00, 0x00, 0x00, 0xdc, 0xcc, - 0xd5, 0x11, 0xf4, 0xec, 0x08, 0x0e, 0x39, 0x46, - 0x00, 0x00, 0x00, 0xdd, 0xcd, 0x62, 0x07, 0x00, - 0x62, 0x06, 0x00, 0x62, 0x05, 0x00, 0x62, 0x04, - 0x00, 0x62, 0x03, 0x00, 0xd4, 0x39, 0x46, 0x00, - 0x00, 0x00, 0xb0, 0xec, 0x16, 0xd4, 0x98, 0x04, - 0x1b, 0x00, 0x00, 0x00, 0xb0, 0xec, 0x0c, 0xdf, - 0x11, 0x04, 0xee, 0x00, 0x00, 0x00, 0x21, 0x01, - 0x00, 0x30, 0x06, 0xce, 0xb6, 0xc4, 0x04, 0xc3, - 0x0d, 0xf7, 0xc4, 0x05, 0x09, 0xc4, 0x06, 0xd3, - 0xe0, 0x48, 0xc4, 0x07, 0x63, 0x07, 0x00, 0x07, - 0xad, 0xec, 0x0f, 0x0a, 0x11, 0x64, 0x06, 0x00, - 0x0e, 0xd3, 0xe1, 0x48, 0x11, 0x64, 0x07, 0x00, - 0x0e, 0x63, 0x07, 0x00, 0x07, 0xad, 0x6a, 0xa6, - 0x00, 0x00, 0x00, 0x62, 0x08, 0x00, 0x06, 0x11, - 0xf4, 0xed, 0x0c, 0x71, 0x43, 0x32, 0x00, 0x00, - 0x00, 0xc4, 0x08, 0x0e, 0xee, 0x05, 0x0e, 0xd3, - 0xee, 0xf2, 0x63, 0x08, 0x00, 0x8e, 0x11, 0xed, - 0x03, 0x0e, 0xb6, 0x11, 0x64, 0x08, 0x00, 0x0e, - 0x63, 0x05, 0x00, 0xec, 0x0c, 0xc3, 0x0d, 0x11, - 0x63, 0x08, 0x00, 0x21, 0x01, 0x00, 0xee, 0x06, - 0xe2, 0x63, 0x08, 0x00, 0xf1, 0x11, 0x64, 0x03, - 0x00, 0x0e, 0x63, 0x04, 0x00, 0x63, 0x08, 0x00, - 0xa7, 0x6a, 0x2a, 0x01, 0x00, 0x00, 0x62, 0x09, - 0x00, 0xd3, 0x63, 0x04, 0x00, 0x48, 0xc4, 0x09, - 0x63, 0x06, 0x00, 0xec, 0x0a, 0x63, 0x09, 0x00, - 0x8c, 0x11, 0x64, 0x09, 0x00, 0x0e, 0xd4, 0xec, - 0x17, 0xd4, 0x43, 0xef, 0x00, 0x00, 0x00, 0xd5, - 0x63, 0x09, 0x00, 0x63, 0x04, 0x00, 0x24, 0x03, - 0x00, 0x8c, 0x11, 0x64, 0x09, 0x00, 0x0e, 0x5f, - 0x04, 0x00, 0x63, 0x03, 0x00, 0x63, 0x04, 0x00, - 0x92, 0x64, 0x04, 0x00, 0x0b, 0x63, 0x09, 0x00, - 0x4d, 0x41, 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3e, - 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3f, 0x00, 0x00, - 0x00, 0xf3, 0x0e, 0xee, 0x9e, 0x62, 0x0a, 0x00, - 0x63, 0x07, 0x00, 0x43, 0xef, 0x00, 0x00, 0x00, - 0xd3, 0x24, 0x01, 0x00, 0xc4, 0x0a, 0x63, 0x05, - 0x00, 0xec, 0x09, 0xc3, 0x0d, 0x11, 0x21, 0x00, - 0x00, 0xee, 0x03, 0xe2, 0xf0, 0x11, 0x64, 0x03, - 0x00, 0x0e, 0x6d, 0x8c, 0x00, 0x00, 0x00, 0x62, - 0x0c, 0x00, 0x62, 0x0b, 0x00, 0x06, 0x11, 0xf4, - 0xed, 0x13, 0x71, 0x43, 0x41, 0x00, 0x00, 0x00, - 0xc4, 0x0b, 0x43, 0x6a, 0x00, 0x00, 0x00, 0xc4, - 0x0c, 0x0e, 0xee, 0x10, 0x0e, 0x63, 0x0a, 0x00, - 0x43, 0x6b, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, - 0x8c, 0xee, 0xe0, 0x63, 0x0c, 0x00, 0xed, 0x4e, - 0x63, 0x06, 0x00, 0xec, 0x0a, 0x63, 0x0b, 0x00, - 0x8c, 0x11, 0x64, 0x0b, 0x00, 0x0e, 0xd4, 0xec, - 0x17, 0xd4, 0x43, 0xef, 0x00, 0x00, 0x00, 0xd5, - 0x63, 0x0b, 0x00, 0x63, 0x04, 0x00, 0x24, 0x03, - 0x00, 0x8c, 0x11, 0x64, 0x0b, 0x00, 0x0e, 0x5f, - 0x04, 0x00, 0x63, 0x03, 0x00, 0x63, 0x04, 0x00, - 0x92, 0x64, 0x04, 0x00, 0x0b, 0x63, 0x0b, 0x00, - 0x4d, 0x41, 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3e, - 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3f, 0x00, 0x00, - 0x00, 0xf3, 0x0e, 0xee, 0x83, 0x0e, 0x06, 0x6e, - 0x0d, 0x00, 0x00, 0x00, 0x0e, 0xee, 0x1e, 0x6e, - 0x05, 0x00, 0x00, 0x00, 0x30, 0x63, 0x0a, 0x00, - 0x42, 0x06, 0x00, 0x00, 0x00, 0xec, 0x0d, 0x63, - 0x0a, 0x00, 0x43, 0x06, 0x00, 0x00, 0x00, 0x24, - 0x00, 0x00, 0x0e, 0x6f, 0x63, 0x03, 0x00, 0x63, - 0x04, 0x00, 0x44, 0x32, 0x00, 0x00, 0x00, 0x63, - 0x03, 0x00, 0x2f, 0xc1, 0x00, 0x28, 0xc1, 0x00, - 0xcf, 0x28, -}; - diff --git a/vendor/quickjs/source_ng/builtin-array-fromasync.js b/vendor/quickjs/source_ng/builtin-array-fromasync.js deleted file mode 100755 index a07e40eff..000000000 --- a/vendor/quickjs/source_ng/builtin-array-fromasync.js +++ /dev/null @@ -1,36 +0,0 @@ -;(function(Array, TypeError, asyncIterator, defineProperty, iterator) { - "use strict" // result.length=i should throw if .length is not writable - return async function fromAsync(arrayLike, mapFn=undefined, thisArg=undefined) { - if (mapFn !== undefined && typeof mapFn !== "function") throw new TypeError("not a function") - let result, i = 0, isConstructor = typeof this === "function" - let sync = false, method = arrayLike[asyncIterator] - if (method == null) sync = true, method = arrayLike[iterator] - if (method == null) { - let {length} = arrayLike - length = +length || 0 - result = isConstructor ? new this(length) : Array(length) - while (i < length) { - let value = arrayLike[i] - if (sync) value = await value - if (mapFn) value = await mapFn.call(thisArg, value, i) - defineProperty(result, i++, {value, configurable: true, writable: true}) - } - } else { - const iter = method.call(arrayLike) - result = isConstructor ? new this() : Array() - try { - for (;;) { - let {value, done} = await iter.next() - if (done) break - if (sync) value = await value - if (mapFn) value = await mapFn.call(thisArg, value, i) - defineProperty(result, i++, {value, configurable: true, writable: true}) - } - } finally { - if (iter.return) iter.return() - } - } - result.length = i - return result - } -}) diff --git a/vendor/quickjs/source_ng/ctest.c b/vendor/quickjs/source_ng/ctest.c deleted file mode 100755 index eeb03e869..000000000 --- a/vendor/quickjs/source_ng/ctest.c +++ /dev/null @@ -1,17 +0,0 @@ -// note: file is not actually compiled, only checked for C syntax errors -#include "quickjs.h" - -int main(void) -{ - JSRuntime *rt = JS_NewRuntime(); - JSContext *ctx = JS_NewContext(rt); - JS_FreeValue(ctx, JS_NAN); - JS_FreeValue(ctx, JS_UNDEFINED); - JS_FreeValue(ctx, JS_NewFloat64(ctx, 42)); - // not a legal way of using JS_MKPTR but this is here - // to have the compiler syntax-check its definition - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_UNINITIALIZED, 0)); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); - return 0; -} diff --git a/vendor/quickjs/source_ng/cutils.c b/vendor/quickjs/source_ng/cutils.c deleted file mode 100755 index 7d36ddfdb..000000000 --- a/vendor/quickjs/source_ng/cutils.c +++ /dev/null @@ -1,1342 +0,0 @@ -/* - * C utilities - * - * Copyright (c) 2017 Fabrice Bellard - * Copyright (c) 2018 Charlie Gordon - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include -#include -#include -#include -#include -#include -#if !defined(_MSC_VER) -#include -#endif -#if defined(_WIN32) -#include -#include // _beginthread -#endif -#if defined(__APPLE__) -#include -#endif - -#include "cutils.h" - -#undef NANOSEC -#define NANOSEC ((uint64_t) 1e9) - -#ifdef __GNUC__ -#pragma GCC visibility push(default) -#endif - -void js__pstrcpy(char *buf, int buf_size, const char *str) -{ - int c; - char *q = buf; - - if (buf_size <= 0) - return; - - for(;;) { - c = *str++; - if (c == 0 || q >= buf + buf_size - 1) - break; - *q++ = c; - } - *q = '\0'; -} - -/* strcat and truncate. */ -char *js__pstrcat(char *buf, int buf_size, const char *s) -{ - int len; - len = strlen(buf); - if (len < buf_size) - js__pstrcpy(buf + len, buf_size - len, s); - return buf; -} - -int js__strstart(const char *str, const char *val, const char **ptr) -{ - const char *p, *q; - p = str; - q = val; - while (*q != '\0') { - if (*p != *q) - return 0; - p++; - q++; - } - if (ptr) - *ptr = p; - return 1; -} - -int js__has_suffix(const char *str, const char *suffix) -{ - size_t len = strlen(str); - size_t slen = strlen(suffix); - return (len >= slen && !memcmp(str + len - slen, suffix, slen)); -} - -/* Dynamic buffer package */ - -static void *dbuf_default_realloc(void *opaque, void *ptr, size_t size) -{ - if (unlikely(size == 0)) { - free(ptr); - return NULL; - } - return realloc(ptr, size); -} - -void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func) -{ - memset(s, 0, sizeof(*s)); - if (!realloc_func) - realloc_func = dbuf_default_realloc; - s->opaque = opaque; - s->realloc_func = realloc_func; -} - -void dbuf_init(DynBuf *s) -{ - dbuf_init2(s, NULL, NULL); -} - -/* return < 0 if error */ -int dbuf_realloc(DynBuf *s, size_t new_size) -{ - size_t size; - uint8_t *new_buf; - if (new_size > s->allocated_size) { - if (s->error) - return -1; - size = s->allocated_size * 3 / 2; - if (size > new_size) - new_size = size; - new_buf = s->realloc_func(s->opaque, s->buf, new_size); - if (!new_buf) { - s->error = true; - return -1; - } - s->buf = new_buf; - s->allocated_size = new_size; - } - return 0; -} - -int dbuf_write(DynBuf *s, size_t offset, const void *data, size_t len) -{ - size_t end; - end = offset + len; - if (dbuf_realloc(s, end)) - return -1; - memcpy(s->buf + offset, data, len); - if (end > s->size) - s->size = end; - return 0; -} - -int dbuf_put(DynBuf *s, const void *data, size_t len) -{ - if (unlikely((s->size + len) > s->allocated_size)) { - if (dbuf_realloc(s, s->size + len)) - return -1; - } - if (len > 0) { - memcpy(s->buf + s->size, data, len); - s->size += len; - } - return 0; -} - -int dbuf_put_self(DynBuf *s, size_t offset, size_t len) -{ - if (unlikely((s->size + len) > s->allocated_size)) { - if (dbuf_realloc(s, s->size + len)) - return -1; - } - memcpy(s->buf + s->size, s->buf + offset, len); - s->size += len; - return 0; -} - -int dbuf_putc(DynBuf *s, uint8_t c) -{ - return dbuf_put(s, &c, 1); -} - -int dbuf_putstr(DynBuf *s, const char *str) -{ - return dbuf_put(s, (const uint8_t *)str, strlen(str)); -} - -int JS_PRINTF_FORMAT_ATTR(2, 3) dbuf_printf(DynBuf *s, JS_PRINTF_FORMAT const char *fmt, ...) -{ - va_list ap; - char buf[128]; - int len; - - va_start(ap, fmt); - len = vsnprintf(buf, sizeof(buf), fmt, ap); - va_end(ap); - if (len < (int)sizeof(buf)) { - /* fast case */ - return dbuf_put(s, (uint8_t *)buf, len); - } else { - if (dbuf_realloc(s, s->size + len + 1)) - return -1; - va_start(ap, fmt); - vsnprintf((char *)(s->buf + s->size), s->allocated_size - s->size, - fmt, ap); - va_end(ap); - s->size += len; - } - return 0; -} - -void dbuf_free(DynBuf *s) -{ - /* we test s->buf as a fail safe to avoid crashing if dbuf_free() - is called twice */ - if (s->buf) { - s->realloc_func(s->opaque, s->buf, 0); - } - memset(s, 0, sizeof(*s)); -} - -/*--- UTF-8 utility functions --*/ - -/* Note: only encode valid codepoints (0x0000..0x10FFFF). - At most UTF8_CHAR_LEN_MAX bytes are output. */ - -/* Compute the number of bytes of the UTF-8 encoding for a codepoint - `c` is a code-point. - Returns the number of bytes. If a codepoint is beyond 0x10FFFF the - return value is 3 as the codepoint would be encoded as 0xFFFD. - */ -size_t utf8_encode_len(uint32_t c) -{ - if (c < 0x80) - return 1; - if (c < 0x800) - return 2; - if (c < 0x10000) - return 3; - if (c < 0x110000) - return 4; - return 3; -} - -/* Encode a codepoint in UTF-8 - `buf` points to an array of at least `UTF8_CHAR_LEN_MAX` bytes - `c` is a code-point. - Returns the number of bytes. If a codepoint is beyond 0x10FFFF the - return value is 3 and the codepoint is encoded as 0xFFFD. - No null byte is stored after the encoded bytes. - Return value is in range 1..4 - */ -size_t utf8_encode(uint8_t buf[minimum_length(UTF8_CHAR_LEN_MAX)], uint32_t c) -{ - if (c < 0x80) { - buf[0] = c; - return 1; - } - if (c < 0x800) { - buf[0] = (c >> 6) | 0xC0; - buf[1] = (c & 0x3F) | 0x80; - return 2; - } - if (c < 0x10000) { - buf[0] = (c >> 12) | 0xE0; - buf[1] = ((c >> 6) & 0x3F) | 0x80; - buf[2] = (c & 0x3F) | 0x80; - return 3; - } - if (c < 0x110000) { - buf[0] = (c >> 18) | 0xF0; - buf[1] = ((c >> 12) & 0x3F) | 0x80; - buf[2] = ((c >> 6) & 0x3F) | 0x80; - buf[3] = (c & 0x3F) | 0x80; - return 4; - } - buf[0] = (0xFFFD >> 12) | 0xE0; - buf[1] = ((0xFFFD >> 6) & 0x3F) | 0x80; - buf[2] = (0xFFFD & 0x3F) | 0x80; - return 3; -} - -/* Decode a single code point from a UTF-8 encoded array of bytes - `p` is a valid pointer to an array of bytes - `pp` is a valid pointer to a `const uint8_t *` to store a pointer - to the byte following the current sequence. - Return the code point at `p`, in the range `0..0x10FFFF` - Return 0xFFFD on error. Only a single byte is consumed in this case - The maximum length for a UTF-8 byte sequence is 4 bytes. - This implements the algorithm specified in whatwg.org, except it accepts - UTF-8 encoded surrogates as JavaScript allows them in strings. - The source string is assumed to have at least UTF8_CHAR_LEN_MAX bytes - or be null terminated. - If `p[0]` is '\0', the return value is `0` and the byte is consumed. - cf: https://encoding.spec.whatwg.org/#utf-8-encoder - */ -uint32_t utf8_decode(const uint8_t *p, const uint8_t **pp) -{ - uint32_t c; - uint8_t lower, upper; - - c = *p++; - if (c < 0x80) { - *pp = p; - return c; - } - switch(c) { - case 0xC2: case 0xC3: - case 0xC4: case 0xC5: case 0xC6: case 0xC7: - case 0xC8: case 0xC9: case 0xCA: case 0xCB: - case 0xCC: case 0xCD: case 0xCE: case 0xCF: - case 0xD0: case 0xD1: case 0xD2: case 0xD3: - case 0xD4: case 0xD5: case 0xD6: case 0xD7: - case 0xD8: case 0xD9: case 0xDA: case 0xDB: - case 0xDC: case 0xDD: case 0xDE: case 0xDF: - if (*p >= 0x80 && *p <= 0xBF) { - *pp = p + 1; - return ((c - 0xC0) << 6) + (*p - 0x80); - } - // otherwise encoding error - break; - case 0xE0: - lower = 0xA0; /* reject invalid encoding */ - goto need2; - case 0xE1: case 0xE2: case 0xE3: - case 0xE4: case 0xE5: case 0xE6: case 0xE7: - case 0xE8: case 0xE9: case 0xEA: case 0xEB: - case 0xEC: case 0xED: case 0xEE: case 0xEF: - lower = 0x80; - need2: - if (*p >= lower && *p <= 0xBF && p[1] >= 0x80 && p[1] <= 0xBF) { - *pp = p + 2; - return ((c - 0xE0) << 12) + ((*p - 0x80) << 6) + (p[1] - 0x80); - } - // otherwise encoding error - break; - case 0xF0: - lower = 0x90; /* reject invalid encoding */ - upper = 0xBF; - goto need3; - case 0xF4: - lower = 0x80; - upper = 0x8F; /* reject values above 0x10FFFF */ - goto need3; - case 0xF1: case 0xF2: case 0xF3: - lower = 0x80; - upper = 0xBF; - need3: - if (*p >= lower && *p <= upper && p[1] >= 0x80 && p[1] <= 0xBF - && p[2] >= 0x80 && p[2] <= 0xBF) { - *pp = p + 3; - return ((c - 0xF0) << 18) + ((*p - 0x80) << 12) + - ((p[1] - 0x80) << 6) + (p[2] - 0x80); - } - // otherwise encoding error - break; - default: - // invalid lead byte - break; - } - *pp = p; - return 0xFFFD; -} - -uint32_t utf8_decode_len(const uint8_t *p, size_t max_len, const uint8_t **pp) { - switch (max_len) { - case 0: - *pp = p; - return 0xFFFD; - case 1: - if (*p < 0x80) - goto good; - break; - case 2: - if (*p < 0xE0) - goto good; - break; - case 3: - if (*p < 0xF0) - goto good; - break; - default: - good: - return utf8_decode(p, pp); - } - *pp = p + 1; - return 0xFFFD; -} - -/* Scan a UTF-8 encoded buffer for content type - `buf` is a valid pointer to a UTF-8 encoded string - `len` is the number of bytes to scan - `plen` points to a `size_t` variable to receive the number of units - Return value is a mask of bits. - - `UTF8_PLAIN_ASCII`: return value for 7-bit ASCII plain text - - `UTF8_NON_ASCII`: bit for non ASCII code points (8-bit or more) - - `UTF8_HAS_16BIT`: bit for 16-bit code points - - `UTF8_HAS_NON_BMP1`: bit for non-BMP1 code points, needs UTF-16 surrogate pairs - - `UTF8_HAS_ERRORS`: bit for encoding errors - */ -int utf8_scan(const char *buf, size_t buf_len, size_t *plen) -{ - const uint8_t *p, *p_end, *p_next; - size_t i, len; - int kind; - uint8_t cbits; - - kind = UTF8_PLAIN_ASCII; - cbits = 0; - len = buf_len; - // TODO: handle more than 1 byte at a time - for (i = 0; i < buf_len; i++) - cbits |= buf[i]; - if (cbits >= 0x80) { - p = (const uint8_t *)buf; - p_end = p + buf_len; - kind = UTF8_NON_ASCII; - len = 0; - while (p < p_end) { - len++; - if (*p++ >= 0x80) { - /* parse UTF-8 sequence, check for encoding error */ - uint32_t c = utf8_decode_len(p - 1, p_end - (p - 1), &p_next); - if (p_next == p) - kind |= UTF8_HAS_ERRORS; - p = p_next; - if (c > 0xFF) { - kind |= UTF8_HAS_16BIT; - if (c > 0xFFFF) { - len++; - kind |= UTF8_HAS_NON_BMP1; - } - } - } - } - } - *plen = len; - return kind; -} - -/* Decode a string encoded in UTF-8 into an array of bytes - `src` points to the source string. It is assumed to be correctly encoded - and only contains code points below 0x800 - `src_len` is the length of the source string - `dest` points to the destination array, it can be null if `dest_len` is `0` - `dest_len` is the length of the destination array. A null - terminator is stored at the end of the array unless `dest_len` is `0`. - */ -size_t utf8_decode_buf8(uint8_t *dest, size_t dest_len, const char *src, size_t src_len) -{ - const uint8_t *p, *p_end; - size_t i; - - p = (const uint8_t *)src; - p_end = p + src_len; - for (i = 0; p < p_end; i++) { - uint32_t c = *p++; - if (c >= 0xC0) - c = (c << 6) + *p++ - ((0xC0 << 6) + 0x80); - if (i < dest_len) - dest[i] = c; - } - if (i < dest_len) - dest[i] = '\0'; - else if (dest_len > 0) - dest[dest_len - 1] = '\0'; - return i; -} - -/* Decode a string encoded in UTF-8 into an array of 16-bit words - `src` points to the source string. It is assumed to be correctly encoded. - `src_len` is the length of the source string - `dest` points to the destination array, it can be null if `dest_len` is `0` - `dest_len` is the length of the destination array. No null terminator is - stored at the end of the array. - */ -size_t utf8_decode_buf16(uint16_t *dest, size_t dest_len, const char *src, size_t src_len) -{ - const uint8_t *p, *p_end; - size_t i; - - p = (const uint8_t *)src; - p_end = p + src_len; - for (i = 0; p < p_end; i++) { - uint32_t c = *p++; - if (c >= 0x80) { - /* parse utf-8 sequence */ - c = utf8_decode_len(p - 1, p_end - (p - 1), &p); - /* encoding errors are converted as 0xFFFD and use a single byte */ - if (c > 0xFFFF) { - if (i < dest_len) - dest[i] = get_hi_surrogate(c); - i++; - c = get_lo_surrogate(c); - } - } - if (i < dest_len) - dest[i] = c; - } - return i; -} - -/* Encode a buffer of 8-bit bytes as a UTF-8 encoded string - `src` points to the source buffer. - `src_len` is the length of the source buffer - `dest` points to the destination array, it can be null if `dest_len` is `0` - `dest_len` is the length in bytes of the destination array. A null - terminator is stored at the end of the array unless `dest_len` is `0`. - */ -size_t utf8_encode_buf8(char *dest, size_t dest_len, const uint8_t *src, size_t src_len) -{ - size_t i, j; - uint32_t c; - - for (i = j = 0; i < src_len; i++) { - c = src[i]; - if (c < 0x80) { - if (j + 1 >= dest_len) - goto overflow; - dest[j++] = c; - } else { - if (j + 2 >= dest_len) - goto overflow; - dest[j++] = (c >> 6) | 0xC0; - dest[j++] = (c & 0x3F) | 0x80; - } - } - if (j < dest_len) - dest[j] = '\0'; - return j; - -overflow: - if (j < dest_len) - dest[j] = '\0'; - while (i < src_len) - j += 1 + (src[i++] >= 0x80); - return j; -} - -/* Encode a buffer of 16-bit code points as a UTF-8 encoded string - `src` points to the source buffer. - `src_len` is the length of the source buffer - `dest` points to the destination array, it can be null if `dest_len` is `0` - `dest_len` is the length in bytes of the destination array. A null - terminator is stored at the end of the array unless `dest_len` is `0`. - */ -size_t utf8_encode_buf16(char *dest, size_t dest_len, const uint16_t *src, size_t src_len) -{ - size_t i, j; - uint32_t c; - - for (i = j = 0; i < src_len;) { - c = src[i++]; - if (c < 0x80) { - if (j + 1 >= dest_len) - goto overflow; - dest[j++] = c; - } else { - if (is_hi_surrogate(c) && i < src_len && is_lo_surrogate(src[i])) - c = from_surrogate(c, src[i++]); - if (j + utf8_encode_len(c) >= dest_len) - goto overflow; - j += utf8_encode((uint8_t *)dest + j, c); - } - } - if (j < dest_len) - dest[j] = '\0'; - return j; - -overflow: - i -= 1 + (c > 0xFFFF); - if (j < dest_len) - dest[j] = '\0'; - while (i < src_len) { - c = src[i++]; - if (c < 0x80) { - j++; - } else { - if (is_hi_surrogate(c) && i < src_len && is_lo_surrogate(src[i])) - c = from_surrogate(c, src[i++]); - j += utf8_encode_len(c); - } - } - return j; -} - -/*---- sorting with opaque argument ----*/ - -typedef void (*exchange_f)(void *a, void *b, size_t size); -typedef int (*cmp_f)(const void *, const void *, void *opaque); - -static void exchange_bytes(void *a, void *b, size_t size) { - uint8_t *ap = (uint8_t *)a; - uint8_t *bp = (uint8_t *)b; - - while (size-- != 0) { - uint8_t t = *ap; - *ap++ = *bp; - *bp++ = t; - } -} - -static void exchange_one_byte(void *a, void *b, size_t size) { - uint8_t *ap = (uint8_t *)a; - uint8_t *bp = (uint8_t *)b; - uint8_t t = *ap; - *ap = *bp; - *bp = t; -} - -static void exchange_int16s(void *a, void *b, size_t size) { - uint16_t *ap = (uint16_t *)a; - uint16_t *bp = (uint16_t *)b; - - for (size /= sizeof(uint16_t); size-- != 0;) { - uint16_t t = *ap; - *ap++ = *bp; - *bp++ = t; - } -} - -static void exchange_one_int16(void *a, void *b, size_t size) { - uint16_t *ap = (uint16_t *)a; - uint16_t *bp = (uint16_t *)b; - uint16_t t = *ap; - *ap = *bp; - *bp = t; -} - -static void exchange_int32s(void *a, void *b, size_t size) { - uint32_t *ap = (uint32_t *)a; - uint32_t *bp = (uint32_t *)b; - - for (size /= sizeof(uint32_t); size-- != 0;) { - uint32_t t = *ap; - *ap++ = *bp; - *bp++ = t; - } -} - -static void exchange_one_int32(void *a, void *b, size_t size) { - uint32_t *ap = (uint32_t *)a; - uint32_t *bp = (uint32_t *)b; - uint32_t t = *ap; - *ap = *bp; - *bp = t; -} - -static void exchange_int64s(void *a, void *b, size_t size) { - uint64_t *ap = (uint64_t *)a; - uint64_t *bp = (uint64_t *)b; - - for (size /= sizeof(uint64_t); size-- != 0;) { - uint64_t t = *ap; - *ap++ = *bp; - *bp++ = t; - } -} - -static void exchange_one_int64(void *a, void *b, size_t size) { - uint64_t *ap = (uint64_t *)a; - uint64_t *bp = (uint64_t *)b; - uint64_t t = *ap; - *ap = *bp; - *bp = t; -} - -static void exchange_int128s(void *a, void *b, size_t size) { - uint64_t *ap = (uint64_t *)a; - uint64_t *bp = (uint64_t *)b; - - for (size /= sizeof(uint64_t) * 2; size-- != 0; ap += 2, bp += 2) { - uint64_t t = ap[0]; - uint64_t u = ap[1]; - ap[0] = bp[0]; - ap[1] = bp[1]; - bp[0] = t; - bp[1] = u; - } -} - -static void exchange_one_int128(void *a, void *b, size_t size) { - uint64_t *ap = (uint64_t *)a; - uint64_t *bp = (uint64_t *)b; - uint64_t t = ap[0]; - uint64_t u = ap[1]; - ap[0] = bp[0]; - ap[1] = bp[1]; - bp[0] = t; - bp[1] = u; -} - -static inline exchange_f exchange_func(const void *base, size_t size) { - switch (((uintptr_t)base | (uintptr_t)size) & 15) { - case 0: - if (size == sizeof(uint64_t) * 2) - return exchange_one_int128; - else - return exchange_int128s; - case 8: - if (size == sizeof(uint64_t)) - return exchange_one_int64; - else - return exchange_int64s; - case 4: - case 12: - if (size == sizeof(uint32_t)) - return exchange_one_int32; - else - return exchange_int32s; - case 2: - case 6: - case 10: - case 14: - if (size == sizeof(uint16_t)) - return exchange_one_int16; - else - return exchange_int16s; - default: - if (size == 1) - return exchange_one_byte; - else - return exchange_bytes; - } -} - -static void heapsortx(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) -{ - uint8_t *basep = (uint8_t *)base; - size_t i, n, c, r; - exchange_f swap = exchange_func(base, size); - - if (nmemb > 1) { - i = (nmemb / 2) * size; - n = nmemb * size; - - while (i > 0) { - i -= size; - for (r = i; (c = r * 2 + size) < n; r = c) { - if (c < n - size && cmp(basep + c, basep + c + size, opaque) <= 0) - c += size; - if (cmp(basep + r, basep + c, opaque) > 0) - break; - swap(basep + r, basep + c, size); - } - } - for (i = n - size; i > 0; i -= size) { - swap(basep, basep + i, size); - - for (r = 0; (c = r * 2 + size) < i; r = c) { - if (c < i - size && cmp(basep + c, basep + c + size, opaque) <= 0) - c += size; - if (cmp(basep + r, basep + c, opaque) > 0) - break; - swap(basep + r, basep + c, size); - } - } - } -} - -static inline void *med3(void *a, void *b, void *c, cmp_f cmp, void *opaque) -{ - return cmp(a, b, opaque) < 0 ? - (cmp(b, c, opaque) < 0 ? b : (cmp(a, c, opaque) < 0 ? c : a )) : - (cmp(b, c, opaque) > 0 ? b : (cmp(a, c, opaque) < 0 ? a : c )); -} - -/* pointer based version with local stack and insertion sort threshhold */ -void rqsort(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) -{ - struct { uint8_t *base; size_t count; int depth; } stack[50], *sp = stack; - uint8_t *ptr, *pi, *pj, *plt, *pgt, *top, *m; - size_t m4, i, lt, gt, span, span2; - int c, depth; - exchange_f swap = exchange_func(base, size); - exchange_f swap_block = exchange_func(base, size | 128); - - if (nmemb < 2 || size <= 0) - return; - - sp->base = (uint8_t *)base; - sp->count = nmemb; - sp->depth = 0; - sp++; - - while (sp > stack) { - sp--; - ptr = sp->base; - nmemb = sp->count; - depth = sp->depth; - - while (nmemb > 6) { - if (++depth > 50) { - /* depth check to ensure worst case logarithmic time */ - heapsortx(ptr, nmemb, size, cmp, opaque); - nmemb = 0; - break; - } - /* select median of 3 from 1/4, 1/2, 3/4 positions */ - /* should use median of 5 or 9? */ - m4 = (nmemb >> 2) * size; - m = med3(ptr + m4, ptr + 2 * m4, ptr + 3 * m4, cmp, opaque); - swap(ptr, m, size); /* move the pivot to the start or the array */ - i = lt = 1; - pi = plt = ptr + size; - gt = nmemb; - pj = pgt = top = ptr + nmemb * size; - for (;;) { - while (pi < pj && (c = cmp(ptr, pi, opaque)) >= 0) { - if (c == 0) { - swap(plt, pi, size); - lt++; - plt += size; - } - i++; - pi += size; - } - while (pi < (pj -= size) && (c = cmp(ptr, pj, opaque)) <= 0) { - if (c == 0) { - gt--; - pgt -= size; - swap(pgt, pj, size); - } - } - if (pi >= pj) - break; - swap(pi, pj, size); - i++; - pi += size; - } - /* array has 4 parts: - * from 0 to lt excluded: elements identical to pivot - * from lt to pi excluded: elements smaller than pivot - * from pi to gt excluded: elements greater than pivot - * from gt to n excluded: elements identical to pivot - */ - /* move elements identical to pivot in the middle of the array: */ - /* swap values in ranges [0..lt[ and [i-lt..i[ - swapping the smallest span between lt and i-lt is sufficient - */ - span = plt - ptr; - span2 = pi - plt; - lt = i - lt; - if (span > span2) - span = span2; - swap_block(ptr, pi - span, span); - /* swap values in ranges [gt..top[ and [i..top-(top-gt)[ - swapping the smallest span between top-gt and gt-i is sufficient - */ - span = top - pgt; - span2 = pgt - pi; - pgt = top - span2; - gt = nmemb - (gt - i); - if (span > span2) - span = span2; - swap_block(pi, top - span, span); - - /* now array has 3 parts: - * from 0 to lt excluded: elements smaller than pivot - * from lt to gt excluded: elements identical to pivot - * from gt to n excluded: elements greater than pivot - */ - /* stack the larger segment and keep processing the smaller one - to minimize stack use for pathological distributions */ - if (lt > nmemb - gt) { - sp->base = ptr; - sp->count = lt; - sp->depth = depth; - sp++; - ptr = pgt; - nmemb -= gt; - } else { - sp->base = pgt; - sp->count = nmemb - gt; - sp->depth = depth; - sp++; - nmemb = lt; - } - } - /* Use insertion sort for small fragments */ - for (pi = ptr + size, top = ptr + nmemb * size; pi < top; pi += size) { - for (pj = pi; pj > ptr && cmp(pj - size, pj, opaque) > 0; pj -= size) - swap(pj, pj - size, size); - } - } -} - -/*---- Portable time functions ----*/ - -#ifdef _WIN32 - // From: https://stackoverflow.com/a/26085827 -static int gettimeofday_msvc(struct timeval *tp) -{ - static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); - - SYSTEMTIME system_time; - FILETIME file_time; - uint64_t time; - - GetSystemTime(&system_time); - SystemTimeToFileTime(&system_time, &file_time); - time = ((uint64_t)file_time.dwLowDateTime); - time += ((uint64_t)file_time.dwHighDateTime) << 32; - - tp->tv_sec = (long)((time - EPOCH) / 10000000L); - tp->tv_usec = (long)(system_time.wMilliseconds * 1000); - - return 0; -} - -uint64_t js__hrtime_ns(void) { - LARGE_INTEGER counter, frequency; - double scaled_freq; - double result; - - if (!QueryPerformanceFrequency(&frequency)) - abort(); - assert(frequency.QuadPart != 0); - - if (!QueryPerformanceCounter(&counter)) - abort(); - assert(counter.QuadPart != 0); - - /* Because we have no guarantee about the order of magnitude of the - * performance counter interval, integer math could cause this computation - * to overflow. Therefore we resort to floating point math. - */ - scaled_freq = (double) frequency.QuadPart / NANOSEC; - result = (double) counter.QuadPart / scaled_freq; - return (uint64_t) result; -} -#else -uint64_t js__hrtime_ns(void) { - struct timespec t; - - if (clock_gettime(CLOCK_MONOTONIC, &t)) - abort(); - - return t.tv_sec * NANOSEC + t.tv_nsec; -} -#endif - -int64_t js__gettimeofday_us(void) { - struct timeval tv; -#ifdef _WIN32 - gettimeofday_msvc(&tv); -#else - gettimeofday(&tv, NULL); -#endif - return ((int64_t)tv.tv_sec * 1000000) + tv.tv_usec; -} - -#if defined(_WIN32) -int js_exepath(char *buffer, size_t *size_ptr) { - int utf8_len, utf16_buffer_len, utf16_len; - WCHAR* utf16_buffer; - - if (buffer == NULL || size_ptr == NULL || *size_ptr == 0) - return -1; - - if (*size_ptr > 32768) { - /* Windows paths can never be longer than this. */ - utf16_buffer_len = 32768; - } else { - utf16_buffer_len = (int)*size_ptr; - } - - utf16_buffer = malloc(sizeof(WCHAR) * utf16_buffer_len); - if (!utf16_buffer) - return -1; - - /* Get the path as UTF-16. */ - utf16_len = GetModuleFileNameW(NULL, utf16_buffer, utf16_buffer_len); - if (utf16_len <= 0) - goto error; - - /* Convert to UTF-8 */ - utf8_len = WideCharToMultiByte(CP_UTF8, - 0, - utf16_buffer, - -1, - buffer, - (int)*size_ptr, - NULL, - NULL); - if (utf8_len == 0) - goto error; - - free(utf16_buffer); - - /* utf8_len *does* include the terminating null at this point, but the - * returned size shouldn't. */ - *size_ptr = utf8_len - 1; - return 0; - -error: - free(utf16_buffer); - return -1; -} -#elif defined(__APPLE__) -int js_exepath(char *buffer, size_t *size) { - /* realpath(exepath) may be > PATH_MAX so double it to be on the safe side. */ - char abspath[PATH_MAX * 2 + 1]; - char exepath[PATH_MAX + 1]; - uint32_t exepath_size; - size_t abspath_size; - - if (buffer == NULL || size == NULL || *size == 0) - return -1; - - exepath_size = sizeof(exepath); - if (_NSGetExecutablePath(exepath, &exepath_size)) - return -1; - - if (realpath(exepath, abspath) != abspath) - return -1; - - abspath_size = strlen(abspath); - if (abspath_size == 0) - return -1; - - *size -= 1; - if (*size > abspath_size) - *size = abspath_size; - - memcpy(buffer, abspath, *size); - buffer[*size] = '\0'; - - return 0; -} -#elif defined(__linux__) || defined(__GNU__) -int js_exepath(char *buffer, size_t *size) { - ssize_t n; - - if (buffer == NULL || size == NULL || *size == 0) - return -1; - - n = *size - 1; - if (n > 0) - n = readlink("/proc/self/exe", buffer, n); - - if (n == -1) - return n; - - buffer[n] = '\0'; - *size = n; - - return 0; -} -#else -int js_exepath(char* buffer, size_t* size_ptr) { - return -1; -} -#endif - -/*--- Cross-platform threading APIs. ----*/ - -#if JS_HAVE_THREADS -#if defined(_WIN32) -typedef void (*js__once_cb)(void); - -typedef struct { - js__once_cb callback; -} js__once_data_t; - -static int WINAPI js__once_inner(INIT_ONCE *once, void *param, void **context) { - js__once_data_t *data = param; - - data->callback(); - - return 1; -} - -void js_once(js_once_t *guard, js__once_cb callback) { - js__once_data_t data = { .callback = callback }; - InitOnceExecuteOnce(guard, js__once_inner, (void*) &data, NULL); -} - -void js_mutex_init(js_mutex_t *mutex) { - InitializeCriticalSection(mutex); -} - -void js_mutex_destroy(js_mutex_t *mutex) { - DeleteCriticalSection(mutex); -} - -void js_mutex_lock(js_mutex_t *mutex) { - EnterCriticalSection(mutex); -} - -void js_mutex_unlock(js_mutex_t *mutex) { - LeaveCriticalSection(mutex); -} - -void js_cond_init(js_cond_t *cond) { - InitializeConditionVariable(cond); -} - -void js_cond_destroy(js_cond_t *cond) { - /* nothing to do */ - (void) cond; -} - -void js_cond_signal(js_cond_t *cond) { - WakeConditionVariable(cond); -} - -void js_cond_broadcast(js_cond_t *cond) { - WakeAllConditionVariable(cond); -} - -void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex) { - if (!SleepConditionVariableCS(cond, mutex, INFINITE)) - abort(); -} - -int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout) { - if (SleepConditionVariableCS(cond, mutex, (DWORD)(timeout / 1e6))) - return 0; - if (GetLastError() != ERROR_TIMEOUT) - abort(); - return -1; -} - -int js_thread_create(js_thread_t *thrd, void (*start)(void *), void *arg, - int flags) -{ - HANDLE h, cp; - - *thrd = INVALID_HANDLE_VALUE; - if (flags & ~JS_THREAD_CREATE_DETACHED) - return -1; - h = (HANDLE)_beginthread(start, /*stacksize*/2<<20, arg); - if (!h) - return -1; - if (flags & JS_THREAD_CREATE_DETACHED) - return 0; - // _endthread() automatically closes the handle but we want to wait on - // it so make a copy. Race-y for very short-lived threads. Can be solved - // by switching to _beginthreadex(CREATE_SUSPENDED) but means changing - // |start| from __cdecl to __stdcall. - cp = GetCurrentProcess(); - if (DuplicateHandle(cp, h, cp, thrd, 0, FALSE, DUPLICATE_SAME_ACCESS)) - return 0; - return -1; -} - -int js_thread_join(js_thread_t thrd) -{ - if (WaitForSingleObject(thrd, INFINITE)) - return -1; - CloseHandle(thrd); - return 0; -} - -#else /* !defined(_WIN32) */ - -void js_once(js_once_t *guard, void (*callback)(void)) { - if (pthread_once(guard, callback)) - abort(); -} - -void js_mutex_init(js_mutex_t *mutex) { - if (pthread_mutex_init(mutex, NULL)) - abort(); -} - -void js_mutex_destroy(js_mutex_t *mutex) { - if (pthread_mutex_destroy(mutex)) - abort(); -} - -void js_mutex_lock(js_mutex_t *mutex) { - if (pthread_mutex_lock(mutex)) - abort(); -} - -void js_mutex_unlock(js_mutex_t *mutex) { - if (pthread_mutex_unlock(mutex)) - abort(); -} - -void js_cond_init(js_cond_t *cond) { -#if defined(__APPLE__) && defined(__MACH__) - if (pthread_cond_init(cond, NULL)) - abort(); -#else - pthread_condattr_t attr; - - if (pthread_condattr_init(&attr)) - abort(); - - if (pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) - abort(); - - if (pthread_cond_init(cond, &attr)) - abort(); - - if (pthread_condattr_destroy(&attr)) - abort(); -#endif -} - -void js_cond_destroy(js_cond_t *cond) { -#if defined(__APPLE__) && defined(__MACH__) - /* It has been reported that destroying condition variables that have been - * signalled but not waited on can sometimes result in application crashes. - * See https://codereview.chromium.org/1323293005. - */ - pthread_mutex_t mutex; - struct timespec ts; - int err; - - if (pthread_mutex_init(&mutex, NULL)) - abort(); - - if (pthread_mutex_lock(&mutex)) - abort(); - - ts.tv_sec = 0; - ts.tv_nsec = 1; - - err = pthread_cond_timedwait_relative_np(cond, &mutex, &ts); - if (err != 0 && err != ETIMEDOUT) - abort(); - - if (pthread_mutex_unlock(&mutex)) - abort(); - - if (pthread_mutex_destroy(&mutex)) - abort(); -#endif /* defined(__APPLE__) && defined(__MACH__) */ - - if (pthread_cond_destroy(cond)) - abort(); -} - -void js_cond_signal(js_cond_t *cond) { - if (pthread_cond_signal(cond)) - abort(); -} - -void js_cond_broadcast(js_cond_t *cond) { - if (pthread_cond_broadcast(cond)) - abort(); -} - -void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex) { -#if defined(__APPLE__) && defined(__MACH__) - int r; - - errno = 0; - r = pthread_cond_wait(cond, mutex); - - /* Workaround for a bug in OS X at least up to 13.6 - * See https://github.com/libuv/libuv/issues/4165 - */ - if (r == EINVAL && errno == EBUSY) - return; - if (r) - abort(); -#else - if (pthread_cond_wait(cond, mutex)) - abort(); -#endif -} - -int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout) { - int r; - struct timespec ts; - -#if !defined(__APPLE__) - timeout += js__hrtime_ns(); -#endif - - ts.tv_sec = timeout / NANOSEC; - ts.tv_nsec = timeout % NANOSEC; -#if defined(__APPLE__) && defined(__MACH__) - r = pthread_cond_timedwait_relative_np(cond, mutex, &ts); -#else - r = pthread_cond_timedwait(cond, mutex, &ts); -#endif - - if (r == 0) - return 0; - - if (r == ETIMEDOUT) - return -1; - - abort(); - - /* Pacify some compilers. */ - return -1; -} - -int js_thread_create(js_thread_t *thrd, void (*start)(void *), void *arg, - int flags) -{ - union { - void (*x)(void *); - void *(*f)(void *); - } u = {start}; - pthread_attr_t attr; - int ret; - - if (flags & ~JS_THREAD_CREATE_DETACHED) - return -1; - if (pthread_attr_init(&attr)) - return -1; - ret = -1; - if (pthread_attr_setstacksize(&attr, 2<<20)) - goto fail; - if (flags & JS_THREAD_CREATE_DETACHED) - if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) - goto fail; - if (pthread_create(thrd, &attr, u.f, arg)) - goto fail; - ret = 0; -fail: - pthread_attr_destroy(&attr); - return ret; -} - -int js_thread_join(js_thread_t thrd) -{ - if (pthread_join(thrd, NULL)) - return -1; - return 0; -} - -#endif /* !defined(_WIN32) */ -#endif /* JS_HAVE_THREADS */ - -#ifdef __GNUC__ -#pragma GCC visibility pop -#endif diff --git a/vendor/quickjs/source_ng/cutils.h b/vendor/quickjs/source_ng/cutils.h deleted file mode 100755 index f5d2a8bce..000000000 --- a/vendor/quickjs/source_ng/cutils.h +++ /dev/null @@ -1,660 +0,0 @@ -/* - * C utilities - * - * Copyright (c) 2017 Fabrice Bellard - * Copyright (c) 2018 Charlie Gordon - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef CUTILS_H -#define CUTILS_H - -#include -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(_MSC_VER) -#include -#include -#define alloca _alloca -#define ssize_t ptrdiff_t -#endif -#if defined(__APPLE__) -#include -#elif defined(__linux__) || defined(__ANDROID__) || defined(__CYGWIN__) || defined(__GLIBC__) -#include -#elif defined(__FreeBSD__) -#include -#elif defined(_WIN32) -#include -#endif -#if !defined(_WIN32) && !defined(EMSCRIPTEN) && !defined(__wasi__) -#include -#include -#endif -#if !defined(_WIN32) -#include -#include -#endif - -#if defined(_MSC_VER) && !defined(__clang__) -# define likely(x) (x) -# define unlikely(x) (x) -# define force_inline __forceinline -# define no_inline __declspec(noinline) -# define __maybe_unused -# define __attribute__(x) -# define __attribute(x) -#else -# define likely(x) __builtin_expect(!!(x), 1) -# define unlikely(x) __builtin_expect(!!(x), 0) -# define force_inline inline __attribute__((always_inline)) -# define no_inline __attribute__((noinline)) -# define __maybe_unused __attribute__((unused)) -#endif - -#if defined(_MSC_VER) && !defined(__clang__) -#include -#define INF INFINITY -#define NEG_INF -INFINITY -#else -#define INF (1.0/0.0) -#define NEG_INF (-1.0/0.0) -#endif - -#ifndef offsetof -#define offsetof(type, field) ((size_t) &((type *)0)->field) -#endif -#ifndef countof -#define countof(x) (sizeof(x) / sizeof((x)[0])) -#ifndef endof -#define endof(x) ((x) + countof(x)) -#endif -#endif -#ifndef container_of -/* return the pointer of type 'type *' containing 'ptr' as field 'member' */ -#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member))) -#endif - -#if defined(_MSC_VER) || defined(__cplusplus) -#define minimum_length(n) n -#else -#define minimum_length(n) static n -#endif - -/* Borrowed from Folly */ -#ifndef JS_PRINTF_FORMAT -#ifdef _MSC_VER -#include -#define JS_PRINTF_FORMAT _Printf_format_string_ -#define JS_PRINTF_FORMAT_ATTR(format_param, dots_param) -#else -#define JS_PRINTF_FORMAT -#if !defined(__clang__) && defined(__GNUC__) -#define JS_PRINTF_FORMAT_ATTR(format_param, dots_param) \ - __attribute__((format(gnu_printf, format_param, dots_param))) -#else -#define JS_PRINTF_FORMAT_ATTR(format_param, dots_param) \ - __attribute__((format(printf, format_param, dots_param))) -#endif -#endif -#endif - -#if defined(PATH_MAX) -# define JS__PATH_MAX PATH_MAX -#elif defined(_WIN32) -# define JS__PATH_MAX 32767 -#else -# define JS__PATH_MAX 8192 -#endif - -void js__pstrcpy(char *buf, int buf_size, const char *str); -char *js__pstrcat(char *buf, int buf_size, const char *s); -int js__strstart(const char *str, const char *val, const char **ptr); -int js__has_suffix(const char *str, const char *suffix); - -static inline uint8_t is_be(void) { - union { - uint16_t a; - uint8_t b; - } u = { 0x100 }; - return u.b; -} - -static inline int max_int(int a, int b) -{ - if (a > b) - return a; - else - return b; -} - -static inline int min_int(int a, int b) -{ - if (a < b) - return a; - else - return b; -} - -static inline uint32_t max_uint32(uint32_t a, uint32_t b) -{ - if (a > b) - return a; - else - return b; -} - -static inline uint32_t min_uint32(uint32_t a, uint32_t b) -{ - if (a < b) - return a; - else - return b; -} - -static inline int64_t max_int64(int64_t a, int64_t b) -{ - if (a > b) - return a; - else - return b; -} - -static inline int64_t min_int64(int64_t a, int64_t b) -{ - if (a < b) - return a; - else - return b; -} - -/* WARNING: undefined if a = 0 */ -static inline int clz32(unsigned int a) -{ -#if defined(_MSC_VER) && !defined(__clang__) - unsigned long index; - _BitScanReverse(&index, a); - return 31 - index; -#else - return __builtin_clz(a); -#endif -} - -/* WARNING: undefined if a = 0 */ -static inline int clz64(uint64_t a) -{ -#if defined(_MSC_VER) && !defined(__clang__) -#if INTPTR_MAX == INT64_MAX - unsigned long index; - _BitScanReverse64(&index, a); - return 63 - index; -#else - if (a >> 32) - return clz32((unsigned)(a >> 32)); - else - return clz32((unsigned)a) + 32; -#endif -#else - return __builtin_clzll(a); -#endif -} - -/* WARNING: undefined if a = 0 */ -static inline int ctz32(unsigned int a) -{ -#if defined(_MSC_VER) && !defined(__clang__) - unsigned long index; - _BitScanForward(&index, a); - return index; -#else - return __builtin_ctz(a); -#endif -} - -/* WARNING: undefined if a = 0 */ -static inline int ctz64(uint64_t a) -{ -#if defined(_MSC_VER) && !defined(__clang__) - unsigned long index; - _BitScanForward64(&index, a); - return index; -#else - return __builtin_ctzll(a); -#endif -} - -static inline uint64_t get_u64(const uint8_t *tab) -{ - uint64_t v; - memcpy(&v, tab, sizeof(v)); - return v; -} - -static inline int64_t get_i64(const uint8_t *tab) -{ - int64_t v; - memcpy(&v, tab, sizeof(v)); - return v; -} - -static inline void put_u64(uint8_t *tab, uint64_t val) -{ - memcpy(tab, &val, sizeof(val)); -} - -static inline uint32_t get_u32(const uint8_t *tab) -{ - uint32_t v; - memcpy(&v, tab, sizeof(v)); - return v; -} - -static inline int32_t get_i32(const uint8_t *tab) -{ - int32_t v; - memcpy(&v, tab, sizeof(v)); - return v; -} - -static inline void put_u32(uint8_t *tab, uint32_t val) -{ - memcpy(tab, &val, sizeof(val)); -} - -static inline uint32_t get_u16(const uint8_t *tab) -{ - uint16_t v; - memcpy(&v, tab, sizeof(v)); - return v; -} - -static inline int32_t get_i16(const uint8_t *tab) -{ - int16_t v; - memcpy(&v, tab, sizeof(v)); - return v; -} - -static inline void put_u16(uint8_t *tab, uint16_t val) -{ - memcpy(tab, &val, sizeof(val)); -} - -static inline uint32_t get_u8(const uint8_t *tab) -{ - return *tab; -} - -static inline int32_t get_i8(const uint8_t *tab) -{ - return (int8_t)*tab; -} - -static inline void put_u8(uint8_t *tab, uint8_t val) -{ - *tab = val; -} - -#ifndef bswap16 -static inline uint16_t bswap16(uint16_t x) -{ - return (x >> 8) | (x << 8); -} -#endif - -#ifndef bswap32 -static inline uint32_t bswap32(uint32_t v) -{ - return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) | - ((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24); -} -#endif - -#ifndef bswap64 -static inline uint64_t bswap64(uint64_t v) -{ - return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) | - ((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) | - ((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) | - ((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) | - ((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) | - ((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) | - ((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) | - ((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8)); -} -#endif - -static inline void inplace_bswap16(uint8_t *tab) { - put_u16(tab, bswap16(get_u16(tab))); -} - -static inline void inplace_bswap32(uint8_t *tab) { - put_u32(tab, bswap32(get_u32(tab))); -} - -static inline double fromfp16(uint16_t v) { - double d, s; - int e; - if ((v & 0x7C00) == 0x7C00) { - d = (v & 0x3FF) ? NAN : INFINITY; - } else { - d = (v & 0x3FF) / 1024.; - e = (v & 0x7C00) >> 10; - if (e == 0) { - e = -14; - } else { - d += 1; - e -= 15; - } - d = scalbn(d, e); - } - s = (v & 0x8000) ? -1.0 : 1.0; - return d * s; -} - -static inline uint16_t tofp16(double d) { - uint16_t f, s; - double t; - int e; - s = 0; - if (copysign(1, d) < 0) { // preserve sign when |d| is negative zero - d = -d; - s = 0x8000; - } - if (isinf(d)) - return s | 0x7C00; - if (isnan(d)) - return s | 0x7C01; - if (d == 0) - return s | 0; - d = 2 * frexp(d, &e); - e--; - if (e > 15) - return s | 0x7C00; // out of range, return +/-infinity - if (e < -25) { - d = 0; - e = 0; - } else if (e < -14) { - d = scalbn(d, e + 14); - e = 0; - } else { - d -= 1; - e += 15; - } - d *= 1024.; - f = (uint16_t)d; - t = d - f; - if (t < 0.5) - goto done; - if (t == 0.5) - if ((f & 1) == 0) - goto done; - // adjust for rounding - if (++f == 1024) { - f = 0; - if (++e == 31) - return s | 0x7C00; // out of range, return +/-infinity - } -done: - return s | (e << 10) | f; -} - -static inline int isfp16nan(uint16_t v) { - return (v & 0x7FFF) > 0x7C00; -} - -static inline int isfp16zero(uint16_t v) { - return (v & 0x7FFF) == 0; -} - -/* XXX: should take an extra argument to pass slack information to the caller */ -typedef void *DynBufReallocFunc(void *opaque, void *ptr, size_t size); - -typedef struct DynBuf { - uint8_t *buf; - size_t size; - size_t allocated_size; - bool error; /* true if a memory allocation error occurred */ - DynBufReallocFunc *realloc_func; - void *opaque; /* for realloc_func */ -} DynBuf; - -void dbuf_init(DynBuf *s); -void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func); -int dbuf_realloc(DynBuf *s, size_t new_size); -int dbuf_write(DynBuf *s, size_t offset, const void *data, size_t len); -int dbuf_put(DynBuf *s, const void *data, size_t len); -int dbuf_put_self(DynBuf *s, size_t offset, size_t len); -int dbuf_putc(DynBuf *s, uint8_t c); -int dbuf_putstr(DynBuf *s, const char *str); -static inline int dbuf_put_u16(DynBuf *s, uint16_t val) -{ - return dbuf_put(s, (uint8_t *)&val, 2); -} -static inline int dbuf_put_u32(DynBuf *s, uint32_t val) -{ - return dbuf_put(s, (uint8_t *)&val, 4); -} -static inline int dbuf_put_u64(DynBuf *s, uint64_t val) -{ - return dbuf_put(s, (uint8_t *)&val, 8); -} -int JS_PRINTF_FORMAT_ATTR(2, 3) dbuf_printf(DynBuf *s, JS_PRINTF_FORMAT const char *fmt, ...); -void dbuf_free(DynBuf *s); -static inline bool dbuf_error(DynBuf *s) { - return s->error; -} -static inline void dbuf_set_error(DynBuf *s) -{ - s->error = true; -} - -/*---- UTF-8 and UTF-16 handling ----*/ - -#define UTF8_CHAR_LEN_MAX 4 - -enum { - UTF8_PLAIN_ASCII = 0, // 7-bit ASCII plain text - UTF8_NON_ASCII = 1, // has non ASCII code points (8-bit or more) - UTF8_HAS_16BIT = 2, // has 16-bit code points - UTF8_HAS_NON_BMP1 = 4, // has non-BMP1 code points, needs UTF-16 surrogate pairs - UTF8_HAS_ERRORS = 8, // has encoding errors -}; -int utf8_scan(const char *buf, size_t len, size_t *plen); -size_t utf8_encode_len(uint32_t c); -size_t utf8_encode(uint8_t buf[minimum_length(UTF8_CHAR_LEN_MAX)], uint32_t c); -uint32_t utf8_decode_len(const uint8_t *p, size_t max_len, const uint8_t **pp); -uint32_t utf8_decode(const uint8_t *p, const uint8_t **pp); -size_t utf8_decode_buf8(uint8_t *dest, size_t dest_len, const char *src, size_t src_len); -size_t utf8_decode_buf16(uint16_t *dest, size_t dest_len, const char *src, size_t src_len); -size_t utf8_encode_buf8(char *dest, size_t dest_len, const uint8_t *src, size_t src_len); -size_t utf8_encode_buf16(char *dest, size_t dest_len, const uint16_t *src, size_t src_len); - -static inline bool is_surrogate(uint32_t c) -{ - return (c >> 11) == (0xD800 >> 11); // 0xD800-0xDFFF -} - -static inline bool is_hi_surrogate(uint32_t c) -{ - return (c >> 10) == (0xD800 >> 10); // 0xD800-0xDBFF -} - -static inline bool is_lo_surrogate(uint32_t c) -{ - return (c >> 10) == (0xDC00 >> 10); // 0xDC00-0xDFFF -} - -static inline uint32_t get_hi_surrogate(uint32_t c) -{ - return (c >> 10) - (0x10000 >> 10) + 0xD800; -} - -static inline uint32_t get_lo_surrogate(uint32_t c) -{ - return (c & 0x3FF) | 0xDC00; -} - -static inline uint32_t from_surrogate(uint32_t hi, uint32_t lo) -{ - return 65536 + 1024 * (hi & 1023) + (lo & 1023); -} - -static inline int from_hex(int c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - else if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - else - return -1; -} - -static inline uint8_t is_upper_ascii(uint8_t c) { - return c >= 'A' && c <= 'Z'; -} - -static inline uint8_t to_upper_ascii(uint8_t c) { - return c >= 'a' && c <= 'z' ? c - 'a' + 'A' : c; -} - -void rqsort(void *base, size_t nmemb, size_t size, - int (*cmp)(const void *, const void *, void *), - void *arg); - -static inline uint64_t float64_as_uint64(double d) -{ - union { - double d; - uint64_t u64; - } u; - u.d = d; - return u.u64; -} - -static inline double uint64_as_float64(uint64_t u64) -{ - union { - double d; - uint64_t u64; - } u; - u.u64 = u64; - return u.d; -} - -int64_t js__gettimeofday_us(void); -uint64_t js__hrtime_ns(void); - -static inline size_t js__malloc_usable_size(const void *ptr) -{ -#if defined(__APPLE__) - return malloc_size(ptr); -#elif defined(_WIN32) - return _msize((void *)ptr); -#elif defined(__linux__) || defined(__ANDROID__) || defined(__CYGWIN__) || defined(__FreeBSD__) || defined(__GLIBC__) - return malloc_usable_size((void *)ptr); -#else - return 0; -#endif -} - -int js_exepath(char* buffer, size_t* size); - -/* Cross-platform threading APIs. */ - -#if defined(EMSCRIPTEN) || defined(__wasi__) - -#define JS_HAVE_THREADS 0 - -#else - -#define JS_HAVE_THREADS 1 - -#if defined(_WIN32) -#define JS_ONCE_INIT INIT_ONCE_STATIC_INIT -typedef INIT_ONCE js_once_t; -typedef CRITICAL_SECTION js_mutex_t; -typedef CONDITION_VARIABLE js_cond_t; -typedef HANDLE js_thread_t; -#else -#define JS_ONCE_INIT PTHREAD_ONCE_INIT -typedef pthread_once_t js_once_t; -typedef pthread_mutex_t js_mutex_t; -typedef pthread_cond_t js_cond_t; -typedef pthread_t js_thread_t; -#endif - -void js_once(js_once_t *guard, void (*callback)(void)); - -void js_mutex_init(js_mutex_t *mutex); -void js_mutex_destroy(js_mutex_t *mutex); -void js_mutex_lock(js_mutex_t *mutex); -void js_mutex_unlock(js_mutex_t *mutex); - -void js_cond_init(js_cond_t *cond); -void js_cond_destroy(js_cond_t *cond); -void js_cond_signal(js_cond_t *cond); -void js_cond_broadcast(js_cond_t *cond); -void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex); -int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout); - -enum { - JS_THREAD_CREATE_DETACHED = 1, -}; - -// creates threads with 2 MB stacks (glibc default) -int js_thread_create(js_thread_t *thrd, void (*start)(void *), void *arg, - int flags); -int js_thread_join(js_thread_t thrd); - -#endif /* !defined(EMSCRIPTEN) && !defined(__wasi__) */ - -// JS requires strict rounding behavior. Turn on 64-bits double precision -// and disable x87 80-bits extended precision for intermediate floating-point -// results. 0x300 is extended precision, 0x200 is double precision. -// Note that `*&cw` in the asm constraints looks redundant but isn't. -#if defined(__i386__) && !defined(_MSC_VER) -#define JS_X87_FPCW_SAVE_AND_ADJUST(cw) \ - unsigned short cw; \ - __asm__ __volatile__("fnstcw %0" : "=m"(*&cw)); \ - do { \ - unsigned short t = 0x200 | (cw & ~0x300); \ - __asm__ __volatile__("fldcw %0" : /*empty*/ : "m"(*&t)); \ - } while (0) -#define JS_X87_FPCW_RESTORE(cw) \ - __asm__ __volatile__("fldcw %0" : /*empty*/ : "m"(*&cw)) -#else -#define JS_X87_FPCW_SAVE_AND_ADJUST(cw) -#define JS_X87_FPCW_RESTORE(cw) -#endif - -#ifdef __cplusplus -} /* extern "C" { */ -#endif - -#endif /* CUTILS_H */ diff --git a/vendor/quickjs/source_ng/cxxtest.cc b/vendor/quickjs/source_ng/cxxtest.cc deleted file mode 100755 index b6151009a..000000000 --- a/vendor/quickjs/source_ng/cxxtest.cc +++ /dev/null @@ -1,2 +0,0 @@ -// note: file is not actually compiled, only checked for C++ syntax errors -#include "ctest.c" diff --git a/vendor/quickjs/source_ng/dtoa.c b/vendor/quickjs/source_ng/dtoa.c deleted file mode 100755 index a89e824f9..000000000 --- a/vendor/quickjs/source_ng/dtoa.c +++ /dev/null @@ -1,1619 +0,0 @@ -/* - * Tiny float64 printing and parsing library - * - * Copyright (c) 2024 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include -#include -#include -#include -#include -#include -#include -// #include -#include -// #include - -#include "cutils.h" -#include "dtoa.h" - -/* - TODO: - - simplify subnormal handling - - reduce max memory usage - - free format: could add shortcut if exact result - - use 64 bit limb_t when possible - - use another algorithm for free format dtoa in base 10 (ryu ?) -*/ - -#define USE_POW5_TABLE -/* use fast path to print small integers in free format */ -#define USE_FAST_INT - -#define LIMB_LOG2_BITS 5 - -#define LIMB_BITS (1 << LIMB_LOG2_BITS) - -typedef int32_t slimb_t; -typedef uint32_t limb_t; -typedef uint64_t dlimb_t; - -#define LIMB_DIGITS 9 - -#define JS_RADIX_MAX 36 - -#define DBIGNUM_LEN_MAX 52 /* ~ 2^(1072+53)*36^100 (dtoa) */ -#define MANT_LEN_MAX 18 /* < 36^100 */ - -typedef intptr_t mp_size_t; - -/* the represented number is sum(i, tab[i]*2^(LIMB_BITS * i)) */ -typedef struct { - int len; /* >= 1 */ - limb_t tab[]; -} mpb_t; - -static limb_t mp_add_ui(limb_t *tab, limb_t b, size_t n) -{ - size_t i; - limb_t k, a; - - k=b; - for(i=0;i> LIMB_BITS; - } - return l; -} - -/* WARNING: d must be >= 2^(LIMB_BITS-1) */ -static inline limb_t udiv1norm_init(limb_t d) -{ - limb_t a0, a1; - a1 = -d - 1; - a0 = -1; - return (((dlimb_t)a1 << LIMB_BITS) | a0) / d; -} - -/* return the quotient and the remainder in '*pr'of 'a1*2^LIMB_BITS+a0 - / d' with 0 <= a1 < d. */ -static inline limb_t udiv1norm(limb_t *pr, limb_t a1, limb_t a0, - limb_t d, limb_t d_inv) -{ - limb_t n1m, n_adj, q, r, ah; - dlimb_t a; - n1m = ((slimb_t)a0 >> (LIMB_BITS - 1)); - n_adj = a0 + (n1m & d); - a = (dlimb_t)d_inv * (a1 - n1m) + n_adj; - q = (a >> LIMB_BITS) + a1; - /* compute a - q * r and update q so that the remainder is between - 0 and d - 1 */ - a = ((dlimb_t)a1 << LIMB_BITS) | a0; - a = a - (dlimb_t)q * d - d; - ah = a >> LIMB_BITS; - q += 1 + ah; - r = (limb_t)a + (ah & d); - *pr = r; - return q; -} - -static limb_t mp_div1(limb_t *tabr, const limb_t *taba, limb_t n, - limb_t b, limb_t r) -{ - slimb_t i; - dlimb_t a1; - for(i = n - 1; i >= 0; i--) { - a1 = ((dlimb_t)r << LIMB_BITS) | taba[i]; - tabr[i] = a1 / b; - r = a1 % b; - } - return r; -} - -/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). - 1 <= shift <= LIMB_BITS - 1 */ -static limb_t mp_shr(limb_t *tab_r, const limb_t *tab, mp_size_t n, - int shift, limb_t high) -{ - mp_size_t i; - limb_t l, a; - - assert(shift >= 1 && shift < LIMB_BITS); - l = high; - for(i = n - 1; i >= 0; i--) { - a = tab[i]; - tab_r[i] = (a >> shift) | (l << (LIMB_BITS - shift)); - l = a; - } - return l & (((limb_t)1 << shift) - 1); -} - -/* r = (a << shift) + low. 1 <= shift <= LIMB_BITS - 1, 0 <= low < - 2^shift. */ -static limb_t mp_shl(limb_t *tab_r, const limb_t *tab, mp_size_t n, - int shift, limb_t low) -{ - mp_size_t i; - limb_t l, a; - - assert(shift >= 1 && shift < LIMB_BITS); - l = low; - for(i = 0; i < n; i++) { - a = tab[i]; - tab_r[i] = (a << shift) | l; - l = (a >> (LIMB_BITS - shift)); - } - return l; -} - -static no_inline limb_t mp_div1norm(limb_t *tabr, const limb_t *taba, limb_t n, - limb_t b, limb_t r, limb_t b_inv, int shift) -{ - slimb_t i; - - if (shift != 0) { - r = (r << shift) | mp_shl(tabr, taba, n, shift, 0); - } - for(i = n - 1; i >= 0; i--) { - tabr[i] = udiv1norm(&r, r, taba[i], b, b_inv); - } - r >>= shift; - return r; -} - -static __maybe_unused void mpb_dump(const char *str, const mpb_t *a) -{ - int i; - - printf("%s= 0x", str); - for(i = a->len - 1; i >= 0; i--) { - printf("%08x", a->tab[i]); - if (i != 0) - printf("_"); - } - printf("\n"); -} - -static void mpb_renorm(mpb_t *r) -{ - while (r->len > 1 && r->tab[r->len - 1] == 0) - r->len--; -} - -#ifdef USE_POW5_TABLE -static const uint32_t pow5_table[17] = { - 0x00000005, 0x00000019, 0x0000007d, 0x00000271, - 0x00000c35, 0x00003d09, 0x0001312d, 0x0005f5e1, - 0x001dcd65, 0x009502f9, 0x02e90edd, 0x0e8d4a51, - 0x48c27395, 0x6bcc41e9, 0x1afd498d, 0x86f26fc1, - 0xa2bc2ec5, -}; - -static const uint8_t pow5h_table[4] = { - 0x00000001, 0x00000007, 0x00000023, 0x000000b1, -}; - -static const uint32_t pow5_inv_table[13] = { - 0x99999999, 0x47ae147a, 0x0624dd2f, 0xa36e2eb1, - 0x4f8b588e, 0x0c6f7a0b, 0xad7f29ab, 0x5798ee23, - 0x12e0be82, 0xb7cdfd9d, 0x5fd7fe17, 0x19799812, - 0xc25c2684, -}; -#endif - -/* return a^b */ -static uint64_t pow_ui(uint32_t a, uint32_t b) -{ - int i, n_bits; - uint64_t r; - if (b == 0) - return 1; - if (b == 1) - return a; -#ifdef USE_POW5_TABLE - if ((a == 5 || a == 10) && b <= 17) { - r = pow5_table[b - 1]; - if (b >= 14) { - r |= (uint64_t)pow5h_table[b - 14] << 32; - } - if (a == 10) - r <<= b; - return r; - } -#endif - r = a; - n_bits = 32 - clz32(b); - for(i = n_bits - 2; i >= 0; i--) { - r *= r; - if ((b >> i) & 1) - r *= a; - } - return r; -} - -static uint32_t pow_ui_inv(uint32_t *pr_inv, int *pshift, uint32_t a, uint32_t b) -{ - uint32_t r_inv, r; - int shift; -#ifdef USE_POW5_TABLE - if (a == 5 && b >= 1 && b <= 13) { - r = pow5_table[b - 1]; - shift = clz32(r); - r <<= shift; - r_inv = pow5_inv_table[b - 1]; - } else -#endif - { - r = pow_ui(a, b); - shift = clz32(r); - r <<= shift; - r_inv = udiv1norm_init(r); - } - *pshift = shift; - *pr_inv = r_inv; - return r; -} - -enum { - JS_RNDN, /* round to nearest, ties to even */ - JS_RNDNA, /* round to nearest, ties away from zero */ - JS_RNDZ, -}; - -static int mpb_get_bit(const mpb_t *r, int k) -{ - int l; - - l = (unsigned)k / LIMB_BITS; - k = k & (LIMB_BITS - 1); - if (l >= r->len) - return 0; - else - return (r->tab[l] >> k) & 1; -} - -/* compute round(r / 2^shift). 'shift' can be negative */ -static void mpb_shr_round(mpb_t *r, int shift, int rnd_mode) -{ - int l, i; - - if (shift == 0) - return; - if (shift < 0) { - shift = -shift; - l = (unsigned)shift / LIMB_BITS; - shift = shift & (LIMB_BITS - 1); - if (shift != 0) { - r->tab[r->len] = mp_shl(r->tab, r->tab, r->len, shift, 0); - r->len++; - mpb_renorm(r); - } - if (l > 0) { - for(i = r->len - 1; i >= 0; i--) - r->tab[i + l] = r->tab[i]; - for(i = 0; i < l; i++) - r->tab[i] = 0; - r->len += l; - } - } else { - limb_t bit1, bit2; - int k, add_one; - - switch(rnd_mode) { - default: - case JS_RNDZ: - add_one = 0; - break; - case JS_RNDN: - case JS_RNDNA: - bit1 = mpb_get_bit(r, shift - 1); - if (bit1) { - if (rnd_mode == JS_RNDNA) { - bit2 = 1; - } else { - /* bit2 = oring of all the bits after bit1 */ - bit2 = 0; - if (shift >= 2) { - k = shift - 1; - l = (unsigned)k / LIMB_BITS; - k = k & (LIMB_BITS - 1); - for(i = 0; i < min_int(l, r->len); i++) - bit2 |= r->tab[i]; - if (l < r->len) - bit2 |= r->tab[l] & (((limb_t)1 << k) - 1); - } - } - if (bit2) { - add_one = 1; - } else { - /* round to even */ - add_one = mpb_get_bit(r, shift); - } - } else { - add_one = 0; - } - break; - } - - l = (unsigned)shift / LIMB_BITS; - shift = shift & (LIMB_BITS - 1); - if (l >= r->len) { - r->len = 1; - r->tab[0] = add_one; - } else { - if (l > 0) { - r->len -= l; - for(i = 0; i < r->len; i++) - r->tab[i] = r->tab[i + l]; - } - if (shift != 0) { - mp_shr(r->tab, r->tab, r->len, shift, 0); - mpb_renorm(r); - } - if (add_one) { - limb_t a; - a = mp_add_ui(r->tab, 1, r->len); - if (a) - r->tab[r->len++] = a; - } - } - } -} - -/* return -1, 0 or 1 */ -static int mpb_cmp(const mpb_t *a, const mpb_t *b) -{ - mp_size_t i; - if (a->len < b->len) - return -1; - else if (a->len > b->len) - return 1; - for(i = a->len - 1; i >= 0; i--) { - if (a->tab[i] != b->tab[i]) { - if (a->tab[i] < b->tab[i]) - return -1; - else - return 1; - } - } - return 0; -} - -static void mpb_set_u64(mpb_t *r, uint64_t m) -{ -#if LIMB_BITS == 64 - r->tab[0] = m; - r->len = 1; -#else - r->tab[0] = m; - r->tab[1] = m >> LIMB_BITS; - if (r->tab[1] == 0) - r->len = 1; - else - r->len = 2; -#endif -} - -static uint64_t mpb_get_u64(mpb_t *r) -{ -#if LIMB_BITS == 64 - return r->tab[0]; -#else - if (r->len == 1) { - return r->tab[0]; - } else { - return r->tab[0] | ((uint64_t)r->tab[1] << LIMB_BITS); - } -#endif -} - -/* floor_log2() = position of the first non zero bit or -1 if zero. */ -static int mpb_floor_log2(mpb_t *a) -{ - limb_t v; - v = a->tab[a->len - 1]; - if (v == 0) - return -1; - else - return a->len * LIMB_BITS - 1 - clz32(v); -} - -#define MUL_LOG2_RADIX_BASE_LOG2 24 - -/* round((1 << MUL_LOG2_RADIX_BASE_LOG2)/log2(i + 2)) */ -static const uint32_t mul_log2_radix_table[JS_RADIX_MAX - 1] = { - 0x000000, 0xa1849d, 0x000000, 0x6e40d2, - 0x6308c9, 0x5b3065, 0x000000, 0x50c24e, - 0x4d104d, 0x4a0027, 0x4768ce, 0x452e54, - 0x433d00, 0x418677, 0x000000, 0x3ea16b, - 0x3d645a, 0x3c43c2, 0x3b3b9a, 0x3a4899, - 0x39680b, 0x3897b3, 0x37d5af, 0x372069, - 0x367686, 0x35d6df, 0x354072, 0x34b261, - 0x342bea, 0x33ac62, 0x000000, 0x32bfd9, - 0x3251dd, 0x31e8d6, 0x318465, -}; - -/* return floor(a / log2(radix)) for -2048 <= a <= 2047 */ -static int mul_log2_radix(int a, int radix) -{ - int radix_bits, mult; - - if ((radix & (radix - 1)) == 0) { - /* if the radix is a power of two better to do it exactly */ - radix_bits = 31 - clz32(radix); - if (a < 0) - a -= radix_bits - 1; - return a / radix_bits; - } else { - mult = mul_log2_radix_table[radix - 2]; - return ((int64_t)a * mult) >> MUL_LOG2_RADIX_BASE_LOG2; - } -} - -#if 0 -static void build_mul_log2_radix_table(void) -{ - int base, radix, mult, col, base_log2; - - base_log2 = 24; - base = 1 << base_log2; - col = 0; - for(radix = 2; radix <= 36; radix++) { - if ((radix & (radix - 1)) == 0) - mult = 0; - else - mult = lrint((double)base / log2(radix)); - printf("0x%06x, ", mult); - if (++col == 4) { - printf("\n"); - col = 0; - } - } - printf("\n"); -} - -static void mul_log2_radix_test(void) -{ - int radix, i, ref, r; - - for(radix = 2; radix <= 36; radix++) { - for(i = -2048; i <= 2047; i++) { - ref = (int)floor((double)i / log2(radix)); - r = mul_log2_radix(i, radix); - if (ref != r) { - printf("ERROR: radix=%d i=%d r=%d ref=%d\n", - radix, i, r, ref); - exit(1); - } - } - } - if (0) - build_mul_log2_radix_table(); -} -#endif - -static void u32toa_len(char *buf, uint32_t n, size_t len) -{ - int digit, i; - for(i = len - 1; i >= 0; i--) { - digit = n % 10; - n = n / 10; - buf[i] = digit + '0'; - } -} - -/* for power of 2 radixes. len >= 1 */ -static void u64toa_bin_len(char *buf, uint64_t n, unsigned int radix_bits, int len) -{ - int digit, i; - unsigned int mask; - - mask = (1 << radix_bits) - 1; - for(i = len - 1; i >= 0; i--) { - digit = n & mask; - n >>= radix_bits; - if (digit < 10) - digit += '0'; - else - digit += 'a' - 10; - buf[i] = digit; - } -} - -/* len >= 1. 2 <= radix <= 36 */ -static void limb_to_a(char *buf, limb_t n, unsigned int radix, int len) -{ - int digit, i; - - if (radix == 10) { - /* specific case with constant divisor */ -#if LIMB_BITS == 32 - u32toa_len(buf, n, len); -#else - /* XXX: optimize */ - for(i = len - 1; i >= 0; i--) { - digit = (limb_t)n % 10; - n = (limb_t)n / 10; - buf[i] = digit + '0'; - } -#endif - } else { - for(i = len - 1; i >= 0; i--) { - digit = (limb_t)n % radix; - n = (limb_t)n / radix; - if (digit < 10) - digit += '0'; - else - digit += 'a' - 10; - buf[i] = digit; - } - } -} - -size_t u32toa(char *buf, uint32_t n) -{ - char buf1[10], *q; - size_t len; - - q = buf1 + sizeof(buf1); - do { - *--q = n % 10 + '0'; - n /= 10; - } while (n != 0); - len = buf1 + sizeof(buf1) - q; - memcpy(buf, q, len); - return len; -} - -size_t i32toa(char *buf, int32_t n) -{ - if (n >= 0) { - return u32toa(buf, n); - } else { - buf[0] = '-'; - return u32toa(buf + 1, -(uint32_t)n) + 1; - } -} - -#ifdef USE_FAST_INT -size_t u64toa(char *buf, uint64_t n) -{ - if (n < 0x100000000) { - return u32toa(buf, n); - } else { - uint64_t n1; - char *q = buf; - uint32_t n2; - - n1 = n / 1000000000; - n %= 1000000000; - if (n1 >= 0x100000000) { - n2 = n1 / 1000000000; - n1 = n1 % 1000000000; - /* at most two digits */ - if (n2 >= 10) { - *q++ = n2 / 10 + '0'; - n2 %= 10; - } - *q++ = n2 + '0'; - u32toa_len(q, n1, 9); - q += 9; - } else { - q += u32toa(q, n1); - } - u32toa_len(q, n, 9); - q += 9; - return q - buf; - } -} - -size_t i64toa(char *buf, int64_t n) -{ - if (n >= 0) { - return u64toa(buf, n); - } else { - buf[0] = '-'; - return u64toa(buf + 1, -(uint64_t)n) + 1; - } -} - -/* XXX: only tested for 1 <= n < 2^53 */ -size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix) -{ - int radix_bits, l; - if (likely(radix == 10)) - return u64toa(buf, n); - if ((radix & (radix - 1)) == 0) { - radix_bits = 31 - clz32(radix); - if (n == 0) - l = 1; - else - l = (64 - clz64(n) + radix_bits - 1) / radix_bits; - u64toa_bin_len(buf, n, radix_bits, l); - return l; - } else { - char buf1[41], *q; /* maximum length for radix = 3 */ - size_t len; - int digit; - q = buf1 + sizeof(buf1); - do { - digit = n % radix; - n /= radix; - if (digit < 10) - digit += '0'; - else - digit += 'a' - 10; - *--q = digit; - } while (n != 0); - len = buf1 + sizeof(buf1) - q; - memcpy(buf, q, len); - return len; - } -} - -size_t i64toa_radix(char *buf, int64_t n, unsigned int radix) -{ - if (n >= 0) { - return u64toa_radix(buf, n, radix); - } else { - buf[0] = '-'; - return u64toa_radix(buf + 1, -(uint64_t)n, radix) + 1; - } -} -#endif /* USE_FAST_INT */ - -static const uint8_t digits_per_limb_table[JS_RADIX_MAX - 1] = { -#if LIMB_BITS == 32 -32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, -#else -64,40,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12, -#endif -}; - -static const uint32_t radix_base_table[JS_RADIX_MAX - 1] = { - 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395, - 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91, - 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021, - 0x57f6c100, 0x98c29b81, 0x00000000, 0x18754571, - 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d, - 0x94ace180, 0xcaf18367, 0x0b640000, 0x0e8d4a51, - 0x1269ae40, 0x17179149, 0x1cb91000, 0x23744899, - 0x2b73a840, 0x34e63b41, 0x40000000, 0x4cfa3cc1, - 0x5c13d840, 0x6d91b519, 0x81bf1000, -}; - -/* XXX: remove the table ? */ -static uint8_t dtoa_max_digits_table[JS_RADIX_MAX - 1] = { - 54, 35, 28, 24, 22, 20, 19, 18, 17, 17, 16, 16, 15, 15, 15, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 12, -}; - -/* we limit the maximum number of significant digits for atod to about - 128 bits of precision for non power of two bases. The only - requirement for Javascript is at least 20 digits in base 10. For - power of two bases, we do an exact rounding in all the cases. */ -static uint8_t atod_max_digits_table[JS_RADIX_MAX - 1] = { - 64, 80, 32, 55, 49, 45, 21, 40, 38, 37, 35, 34, 33, 32, 16, 31, 30, 30, 29, 29, 28, 28, 27, 27, 27, 26, 26, 26, 26, 25, 12, 25, 25, 24, 24, -}; - -/* if abs(d) >= B^max_exponent, it is an overflow */ -static const int16_t max_exponent[JS_RADIX_MAX - 1] = { - 1024, 647, 512, 442, 397, 365, 342, 324, - 309, 297, 286, 277, 269, 263, 256, 251, - 246, 242, 237, 234, 230, 227, 224, 221, - 218, 216, 214, 211, 209, 207, 205, 203, - 202, 200, 199, -}; - -/* if abs(d) <= B^min_exponent, it is an underflow */ -static const int16_t min_exponent[JS_RADIX_MAX - 1] = { --1075, -679, -538, -463, -416, -383, -359, -340, - -324, -311, -300, -291, -283, -276, -269, -263, - -258, -254, -249, -245, -242, -238, -235, -232, - -229, -227, -224, -222, -220, -217, -215, -214, - -212, -210, -208, -}; - -#if 0 -void build_tables(void) -{ - int r, j, radix, n, col, i; - - /* radix_base_table */ - for(radix = 2; radix <= 36; radix++) { - r = 1; - for(j = 0; j < digits_per_limb_table[radix - 2]; j++) { - r *= radix; - } - printf(" 0x%08x,", r); - if ((radix % 4) == 1) - printf("\n"); - } - printf("\n"); - - /* dtoa_max_digits_table */ - for(radix = 2; radix <= 36; radix++) { - /* Note: over estimated when the radix is a power of two */ - printf(" %d,", 1 + (int)ceil(53.0 / log2(radix))); - } - printf("\n"); - - /* atod_max_digits_table */ - for(radix = 2; radix <= 36; radix++) { - if ((radix & (radix - 1)) == 0) { - /* 64 bits is more than enough */ - n = (int)floor(64.0 / log2(radix)); - } else { - n = (int)floor(128.0 / log2(radix)); - } - printf(" %d,", n); - } - printf("\n"); - - printf("static const int16_t max_exponent[JS_RADIX_MAX - 1] = {\n"); - col = 0; - for(radix = 2; radix <= 36; radix++) { - printf("%5d, ", (int)ceil(1024 / log2(radix))); - if (++col == 8) { - col = 0; - printf("\n"); - } - } - printf("\n};\n\n"); - - printf("static const int16_t min_exponent[JS_RADIX_MAX - 1] = {\n"); - col = 0; - for(radix = 2; radix <= 36; radix++) { - printf("%5d, ", (int)floor(-1075 / log2(radix))); - if (++col == 8) { - col = 0; - printf("\n"); - } - } - printf("\n};\n\n"); - - printf("static const uint32_t pow5_table[16] = {\n"); - col = 0; - for(i = 2; i <= 17; i++) { - r = 1; - for(j = 0; j < i; j++) { - r *= 5; - } - printf("0x%08x, ", r); - if (++col == 4) { - col = 0; - printf("\n"); - } - } - printf("\n};\n\n"); - - /* high part */ - printf("static const uint8_t pow5h_table[4] = {\n"); - col = 0; - for(i = 14; i <= 17; i++) { - uint64_t r1; - r1 = 1; - for(j = 0; j < i; j++) { - r1 *= 5; - } - printf("0x%08x, ", (uint32_t)(r1 >> 32)); - if (++col == 4) { - col = 0; - printf("\n"); - } - } - printf("\n};\n\n"); -} -#endif - -/* n_digits >= 1. 0 <= dot_pos <= n_digits. If dot_pos == n_digits, - the dot is not displayed. 'a' is modified. */ -static int output_digits(char *buf, - mpb_t *a, int radix, int n_digits1, - int dot_pos) -{ - int n_digits, digits_per_limb, radix_bits, n, len; - - n_digits = n_digits1; - if ((radix & (radix - 1)) == 0) { - /* radix = 2^radix_bits */ - radix_bits = 31 - clz32(radix); - } else { - radix_bits = 0; - } - digits_per_limb = digits_per_limb_table[radix - 2]; - if (radix_bits != 0) { - for(;;) { - n = min_int(n_digits, digits_per_limb); - n_digits -= n; - u64toa_bin_len(buf + n_digits, a->tab[0], radix_bits, n); - if (n_digits == 0) - break; - mpb_shr_round(a, digits_per_limb * radix_bits, JS_RNDZ); - } - } else { - limb_t r; - while (n_digits != 0) { - n = min_int(n_digits, digits_per_limb); - n_digits -= n; - r = mp_div1(a->tab, a->tab, a->len, radix_base_table[radix - 2], 0); - mpb_renorm(a); - limb_to_a(buf + n_digits, r, radix, n); - } - } - - /* add the dot */ - len = n_digits1; - if (dot_pos != n_digits1) { - memmove(buf + dot_pos + 1, buf + dot_pos, n_digits1 - dot_pos); - buf[dot_pos] = '.'; - len++; - } - return len; -} - -/* return (a, e_offset) such that a = a * (radix1*2^radix_shift)^f * - 2^-e_offset. 'f' can be negative. */ -static int mul_pow(mpb_t *a, int radix1, int radix_shift, int f, bool is_int, int e) -{ - int e_offset, d, n, n0; - - e_offset = -f * radix_shift; - if (radix1 != 1) { - d = digits_per_limb_table[radix1 - 2]; - if (f >= 0) { - limb_t h, b; - - b = 0; - n0 = 0; - while (f != 0) { - n = min_int(f, d); - if (n != n0) { - b = pow_ui(radix1, n); - n0 = n; - } - h = mp_mul1(a->tab, a->tab, a->len, b, 0); - if (h != 0) { - a->tab[a->len++] = h; - } - f -= n; - } - } else { - int extra_bits, l, shift; - limb_t r, rem, b, b_inv; - - f = -f; - l = (f + d - 1) / d; /* high bound for the number of limbs (XXX: make it better) */ - e_offset += l * LIMB_BITS; - if (!is_int) { - /* at least 'e' bits are needed in the final result for rounding */ - extra_bits = max_int(e - mpb_floor_log2(a), 0); - } else { - /* at least two extra bits are needed in the final result - for rounding */ - extra_bits = max_int(2 + e - e_offset, 0); - } - e_offset += extra_bits; - mpb_shr_round(a, -(l * LIMB_BITS + extra_bits), JS_RNDZ); - - b = 0; - b_inv = 0; - shift = 0; - n0 = 0; - rem = 0; - while (f != 0) { - n = min_int(f, d); - if (n != n0) { - b = pow_ui_inv(&b_inv, &shift, radix1, n); - n0 = n; - } - r = mp_div1norm(a->tab, a->tab, a->len, b, 0, b_inv, shift); - rem |= r; - mpb_renorm(a); - f -= n; - } - /* if the remainder is non zero, use it for rounding */ - a->tab[0] |= (rem != 0); - } - } - return e_offset; -} - -/* tmp1 = round(m*2^e*radix^f). 'tmp0' is a temporary storage */ -static void mul_pow_round(mpb_t *tmp1, uint64_t m, int e, int radix1, int radix_shift, int f, - int rnd_mode) -{ - int e_offset; - - mpb_set_u64(tmp1, m); - e_offset = mul_pow(tmp1, radix1, radix_shift, f, true, e); - mpb_shr_round(tmp1, -e + e_offset, rnd_mode); -} - -/* return round(a*2^e_offset) rounded as a float64. 'a' is modified */ -static uint64_t round_to_d(int *pe, mpb_t *a, int e_offset, int rnd_mode) -{ - int e; - uint64_t m; - - if (a->tab[0] == 0 && a->len == 1) { - /* zero result */ - m = 0; - e = 0; /* don't care */ - } else { - int prec, prec1, e_min; - e = mpb_floor_log2(a) + 1 - e_offset; - prec1 = 53; - e_min = -1021; - if (e < e_min) { - /* subnormal result or zero */ - prec = prec1 - (e_min - e); - } else { - prec = prec1; - } - mpb_shr_round(a, e + e_offset - prec, rnd_mode); - m = mpb_get_u64(a); - m <<= (53 - prec); - /* mantissa overflow due to rounding */ - if (m >= (uint64_t)1 << 53) { - m >>= 1; - e++; - } - } - *pe = e; - return m; -} - -/* return (m, e) such that m*2^(e-53) = round(a * radix^f) with 2^52 - <= m < 2^53 or m = 0. - 'a' is modified. */ -static uint64_t mul_pow_round_to_d(int *pe, mpb_t *a, - int radix1, int radix_shift, int f, int rnd_mode) -{ - int e_offset; - - e_offset = mul_pow(a, radix1, radix_shift, f, false, 55); - return round_to_d(pe, a, e_offset, rnd_mode); -} - -#ifdef JS_DTOA_DUMP_STATS -static int out_len_count[17]; - -void js_dtoa_dump_stats(void) -{ - int i, sum; - sum = 0; - for(i = 0; i < 17; i++) - sum += out_len_count[i]; - for(i = 0; i < 17; i++) { - printf("%2d %8d %5.2f%%\n", - i + 1, out_len_count[i], (double)out_len_count[i] / sum * 100); - } -} -#endif - -/* return a maximum bound of the string length. The bound depends on - 'd' only if format = JS_DTOA_FORMAT_FRAC or if JS_DTOA_EXP_DISABLED - is enabled. */ -int js_dtoa_max_len(double d, int radix, int n_digits, int flags) -{ - int fmt = flags & JS_DTOA_FORMAT_MASK; - int n, e; - uint64_t a; - - if (fmt != JS_DTOA_FORMAT_FRAC) { - if (fmt == JS_DTOA_FORMAT_FREE) { - n = dtoa_max_digits_table[radix - 2]; - } else { - n = n_digits; - } - if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_DISABLED) { - /* no exponential */ - a = float64_as_uint64(d); - e = (a >> 52) & 0x7ff; - if (e == 0x7ff) { - /* NaN, Infinity */ - n = 0; - } else { - e -= 1023; - /* XXX: adjust */ - n += 10 + abs(mul_log2_radix(e - 1, radix)); - } - } else { - /* extra: sign, 1 dot and exponent "e-1000" */ - n += 1 + 1 + 6; - } - } else { - a = float64_as_uint64(d); - e = (a >> 52) & 0x7ff; - if (e == 0x7ff) { - /* NaN, Infinity */ - n = 0; - } else { - /* high bound for the integer part */ - e -= 1023; - /* x < 2^(e + 1) */ - if (e < 0) { - n = 1; - } else { - n = 2 + mul_log2_radix(e - 1, radix); - } - /* sign, extra digit, 1 dot */ - n += 1 + 1 + 1 + n_digits; - } - } - return max_int(n, 9); /* also include NaN and [-]Infinity */ -} - -#if defined(__SANITIZE_ADDRESS__) && 0 -static void *dtoa_malloc(uint64_t **pptr, size_t size) -{ - return malloc(size); -} -static void dtoa_free(void *ptr) -{ - free(ptr); -} -#else -static void *dtoa_malloc(uint64_t **pptr, size_t size) -{ - void *ret; - ret = *pptr; - *pptr += (size + 7) / 8; - return ret; -} - -static void dtoa_free(void *ptr) -{ -} -#endif - -/* return the length */ -int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, - JSDTOATempMem *tmp_mem) -{ - uint64_t a, m, *mptr = tmp_mem->mem; - int e, sgn, l, E, P, i, E_max, radix1, radix_shift; - char *q; - mpb_t *tmp1, *mant_max; - int fmt = flags & JS_DTOA_FORMAT_MASK; - - tmp1 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); - mant_max = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * MANT_LEN_MAX); - assert((mptr - tmp_mem->mem) <= sizeof(JSDTOATempMem) / sizeof(mptr[0])); - - radix_shift = ctz32(radix); - radix1 = radix >> radix_shift; - a = float64_as_uint64(d); - sgn = a >> 63; - e = (a >> 52) & 0x7ff; - m = a & (((uint64_t)1 << 52) - 1); - q = buf; - if (e == 0x7ff) { - if (m == 0) { - if (sgn) - *q++ = '-'; - memcpy(q, "Infinity", 8); - q += 8; - } else { - memcpy(q, "NaN", 3); - q += 3; - } - goto done; - } else if (e == 0) { - if (m == 0) { - tmp1->len = 1; - tmp1->tab[0] = 0; - E = 1; - if (fmt == JS_DTOA_FORMAT_FREE) - P = 1; - else if (fmt == JS_DTOA_FORMAT_FRAC) - P = n_digits + 1; - else - P = n_digits; - /* "-0" is displayed as "0" if JS_DTOA_MINUS_ZERO is not present */ - if (sgn && (flags & JS_DTOA_MINUS_ZERO)) - *q++ = '-'; - goto output; - } - /* denormal number: convert to a normal number */ - l = clz64(m) - 11; - e -= l - 1; - m <<= l; - } else { - m |= (uint64_t)1 << 52; - } - if (sgn) - *q++ = '-'; - /* remove the bias */ - e -= 1022; - /* d = 2^(e-53)*m */ - // printf("m=0x%016" PRIx64 " e=%d\n", m, e); -#ifdef USE_FAST_INT - if (fmt == JS_DTOA_FORMAT_FREE && - e >= 1 && e <= 53 && - (m & (((uint64_t)1 << (53 - e)) - 1)) == 0 && - (flags & JS_DTOA_EXP_MASK) != JS_DTOA_EXP_ENABLED) { - m >>= 53 - e; - /* 'm' is never zero */ - q += u64toa_radix(q, m, radix); - goto done; - } -#endif - - /* this choice of E implies F=round(x*B^(P-E) is such as: - B^(P-1) <= F < 2.B^P. */ - E = 1 + mul_log2_radix(e - 1, radix); - - if (fmt == JS_DTOA_FORMAT_FREE) { - int P_max, E0, e1, E_found, P_found; - uint64_t m1, mant_found, mant, mant_max1; - /* P_max is guarranteed to work by construction */ - P_max = dtoa_max_digits_table[radix - 2]; - E0 = E; - E_found = 0; - P_found = 0; - mant_found = 0; - /* find the minimum number of digits by successive tries */ - P = P_max; /* P_max is guarateed to work */ - for(;;) { - /* mant_max always fits on 64 bits */ - mant_max1 = pow_ui(radix, P); - /* compute the mantissa in base B */ - E = E0; - for(;;) { - /* XXX: add inexact flag */ - mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDN); - mant = mpb_get_u64(tmp1); - if (mant < mant_max1) - break; - E++; /* at most one iteration is possible */ - } - /* remove useless trailing zero digits */ - while ((mant % radix) == 0) { - mant /= radix; - P--; - } - /* garanteed to work for P = P_max */ - if (P_found == 0) - goto prec_found; - /* convert back to base 2 */ - mpb_set_u64(tmp1, mant); - m1 = mul_pow_round_to_d(&e1, tmp1, radix1, radix_shift, E - P, JS_RNDN); - // printf("P=%2d: m=0x%016" PRIx64 " e=%d m1=0x%016" PRIx64 " e1=%d\n", P, m, e, m1, e1); - /* Note: (m, e) is never zero here, so the exponent for m1 - = 0 does not matter */ - if (m1 == m && e1 == e) { - prec_found: - P_found = P; - E_found = E; - mant_found = mant; - if (P == 1) - break; - P--; /* try lower exponent */ - } else { - break; - } - } - P = P_found; - E = E_found; - mpb_set_u64(tmp1, mant_found); -#ifdef JS_DTOA_DUMP_STATS - if (radix == 10) { - out_len_count[P - 1]++; - } -#endif - } else if (fmt == JS_DTOA_FORMAT_FRAC) { - int len; - - assert(n_digits >= 0 && n_digits <= JS_DTOA_MAX_DIGITS); - /* P = max_int(E, 1) + n_digits; */ - /* frac is rounded using RNDNA */ - mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, n_digits, JS_RNDNA); - - /* we add one extra digit on the left and remove it if needed - to avoid testing if the result is < radix^P */ - len = output_digits(q, tmp1, radix, max_int(E + 1, 1) + n_digits, - max_int(E + 1, 1)); - if (q[0] == '0' && len >= 2 && q[1] != '.') { - len--; - memmove(q, q + 1, len); - } - q += len; - goto done; - } else { - int pow_shift; - assert(n_digits >= 1 && n_digits <= JS_DTOA_MAX_DIGITS); - P = n_digits; - /* mant_max = radix^P */ - mant_max->len = 1; - mant_max->tab[0] = 1; - pow_shift = mul_pow(mant_max, radix1, radix_shift, P, false, 0); - mpb_shr_round(mant_max, pow_shift, JS_RNDZ); - - for(;;) { - /* fixed and frac are rounded using RNDNA */ - mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDNA); - if (mpb_cmp(tmp1, mant_max) < 0) - break; - E++; /* at most one iteration is possible */ - } - } - output: - if (fmt == JS_DTOA_FORMAT_FIXED) - E_max = n_digits; - else - E_max = dtoa_max_digits_table[radix - 2] + 4; - if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_ENABLED || - ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_AUTO && (E <= -6 || E > E_max))) { - q += output_digits(q, tmp1, radix, P, 1); - E--; - if (radix == 10) { - *q++ = 'e'; - } else if (radix1 == 1 && radix_shift <= 4) { - E *= radix_shift; - *q++ = 'p'; - } else { - *q++ = '@'; - } - if (E < 0) { - *q++ = '-'; - E = -E; - } else { - *q++ = '+'; - } - q += u32toa(q, E); - } else if (E <= 0) { - *q++ = '0'; - *q++ = '.'; - for(i = 0; i < -E; i++) - *q++ = '0'; - q += output_digits(q, tmp1, radix, P, P); - } else { - q += output_digits(q, tmp1, radix, P, min_int(P, E)); - for(i = 0; i < E - P; i++) - *q++ = '0'; - } - done: - *q = '\0'; - dtoa_free(mant_max); - dtoa_free(tmp1); - return q - buf; -} - -static inline int to_digit(int c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'A' && c <= 'Z') - return c - 'A' + 10; - else if (c >= 'a' && c <= 'z') - return c - 'a' + 10; - else - return 36; -} - -/* r = r * radix_base + a. radix_base = 0 means radix_base = 2^32 */ -static void mpb_mul1_base(mpb_t *r, limb_t radix_base, limb_t a) -{ - int i; - if (r->tab[0] == 0 && r->len == 1) { - r->tab[0] = a; - } else { - if (radix_base == 0) { - for(i = r->len; i >= 0; i--) { - r->tab[i + 1] = r->tab[i]; - } - r->tab[0] = a; - } else { - r->tab[r->len] = mp_mul1(r->tab, r->tab, r->len, - radix_base, a); - } - r->len++; - mpb_renorm(r); - } -} - -/* XXX: add fast path for small integers */ -double js_atod(const char *str, const char **pnext, int radix, int flags, - JSATODTempMem *tmp_mem) -{ - uint64_t *mptr = tmp_mem->mem; - const char *p, *p_start; - limb_t cur_limb, radix_base, extra_digits; - int is_neg, digit_count, limb_digit_count, digits_per_limb, sep, radix1, radix_shift; - int radix_bits, expn, e, max_digits, expn_offset, dot_pos, sig_pos, pos; - mpb_t *tmp0; - double dval; - bool is_bin_exp, is_zero, expn_overflow; - uint64_t m, a; - - tmp0 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); - assert((mptr - tmp_mem->mem) <= sizeof(JSATODTempMem) / sizeof(mptr[0])); - /* optional separator between digits */ - sep = (flags & JS_ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; - - p = str; - is_neg = 0; - if (p[0] == '+') { - p++; - p_start = p; - } else if (p[0] == '-') { - is_neg = 1; - p++; - p_start = p; - } else { - p_start = p; - } - - if (p[0] == '0') { - if ((p[1] == 'x' || p[1] == 'X') && - (radix == 0 || radix == 16)) { - p += 2; - radix = 16; - } else if ((p[1] == 'o' || p[1] == 'O') && - radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { - p += 2; - radix = 8; - } else if ((p[1] == 'b' || p[1] == 'B') && - radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { - p += 2; - radix = 2; - } else if ((p[1] >= '0' && p[1] <= '9') && - radix == 0 && (flags & JS_ATOD_ACCEPT_LEGACY_OCTAL)) { - int i; - sep = 256; - for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) - continue; - if (p[i] == '8' || p[i] == '9') - goto no_prefix; - p += 1; - radix = 8; - } else { - goto no_prefix; - } - /* there must be a digit after the prefix */ - if (to_digit((uint8_t)*p) >= radix) - goto fail; - no_prefix: ; - } else { - if (!(flags & JS_ATOD_INT_ONLY) && js__strstart(p, "Infinity", &p)) - goto overflow; - } - if (radix == 0) - radix = 10; - - cur_limb = 0; - expn_offset = 0; - digit_count = 0; - limb_digit_count = 0; - max_digits = atod_max_digits_table[radix - 2]; - digits_per_limb = digits_per_limb_table[radix - 2]; - radix_base = radix_base_table[radix - 2]; - radix_shift = ctz32(radix); - radix1 = radix >> radix_shift; - if (radix1 == 1) { - /* radix = 2^radix_bits */ - radix_bits = radix_shift; - } else { - radix_bits = 0; - } - tmp0->len = 1; - tmp0->tab[0] = 0; - extra_digits = 0; - pos = 0; - dot_pos = -1; - /* skip leading zeros */ - for(;;) { - if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) && - !(flags & JS_ATOD_INT_ONLY)) { - if (*p == sep) - goto fail; - if (dot_pos >= 0) - break; - dot_pos = pos; - p++; - } - if (*p == sep && p > p_start && p[1] == '0') - p++; - if (*p != '0') - break; - p++; - pos++; - } - - sig_pos = pos; - for(;;) { - limb_t c; - if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) && - !(flags & JS_ATOD_INT_ONLY)) { - if (*p == sep) - goto fail; - if (dot_pos >= 0) - break; - dot_pos = pos; - p++; - } - if (*p == sep && p > p_start && to_digit(p[1]) < radix) - p++; - c = to_digit(*p); - if (c >= radix) - break; - p++; - pos++; - if (digit_count < max_digits) { - /* XXX: could be faster when radix_bits != 0 */ - cur_limb = cur_limb * radix + c; - limb_digit_count++; - if (limb_digit_count == digits_per_limb) { - mpb_mul1_base(tmp0, radix_base, cur_limb); - cur_limb = 0; - limb_digit_count = 0; - } - digit_count++; - } else { - extra_digits |= c; - } - } - if (limb_digit_count != 0) { - mpb_mul1_base(tmp0, pow_ui(radix, limb_digit_count), cur_limb); - } - if (digit_count == 0) { - is_zero = true; - expn_offset = 0; - } else { - is_zero = false; - if (dot_pos < 0) - dot_pos = pos; - expn_offset = sig_pos + digit_count - dot_pos; - } - - /* Use the extra digits for rounding if the base is a power of - two. Otherwise they are just truncated. */ - if (radix_bits != 0 && extra_digits != 0) { - tmp0->tab[0] |= 1; - } - - /* parse the exponent, if any */ - expn = 0; - expn_overflow = false; - is_bin_exp = false; - if (!(flags & JS_ATOD_INT_ONLY) && - ((radix == 10 && (*p == 'e' || *p == 'E')) || - (radix != 10 && (*p == '@' || - (radix_bits >= 1 && radix_bits <= 4 && (*p == 'p' || *p == 'P'))))) && - p > p_start) { - bool exp_is_neg; - int c; - is_bin_exp = (*p == 'p' || *p == 'P'); - p++; - exp_is_neg = false; - if (*p == '+') { - p++; - } else if (*p == '-') { - exp_is_neg = true; - p++; - } - c = to_digit(*p); - if (c >= 10) - goto fail; /* XXX: could stop before the exponent part */ - expn = c; - p++; - for(;;) { - if (*p == sep && to_digit(p[1]) < 10) - p++; - c = to_digit(*p); - if (c >= 10) - break; - if (!expn_overflow) { - if (unlikely(expn > ((INT32_MAX - 2 - 9) / 10))) { - expn_overflow = true; - } else { - expn = expn * 10 + c; - } - } - p++; - } - if (exp_is_neg) - expn = -expn; - /* if zero result, the exponent can be arbitrarily large */ - if (!is_zero && expn_overflow) { - if (exp_is_neg) - a = 0; - else - a = (uint64_t)0x7ff << 52; /* infinity */ - goto done; - } - } - - if (p == p_start) - goto fail; - - if (is_zero) { - a = 0; - } else { - int expn1; - if (radix_bits != 0) { - if (!is_bin_exp) - expn *= radix_bits; - expn -= expn_offset * radix_bits; - expn1 = expn + digit_count * radix_bits; - if (expn1 >= 1024 + radix_bits) - goto overflow; - else if (expn1 <= -1075) - goto underflow; - m = round_to_d(&e, tmp0, -expn, JS_RNDN); - } else { - expn -= expn_offset; - expn1 = expn + digit_count; - if (expn1 >= max_exponent[radix - 2] + 1) - goto overflow; - else if (expn1 <= min_exponent[radix - 2]) - goto underflow; - m = mul_pow_round_to_d(&e, tmp0, radix1, radix_shift, expn, JS_RNDN); - } - if (m == 0) { - underflow: - a = 0; - } else if (e > 1024) { - overflow: - /* overflow */ - a = (uint64_t)0x7ff << 52; - } else if (e < -1073) { - /* underflow */ - /* XXX: check rounding */ - a = 0; - } else if (e < -1021) { - /* subnormal */ - a = m >> (-e - 1021); - } else { - a = ((uint64_t)(e + 1022) << 52) | (m & (((uint64_t)1 << 52) - 1)); - } - } - done: - a |= (uint64_t)is_neg << 63; - dval = uint64_as_float64(a); - done1: - if (pnext) - *pnext = p; - dtoa_free(tmp0); - return dval; - fail: - dval = NAN; - goto done1; -} diff --git a/vendor/quickjs/source_ng/dtoa.h b/vendor/quickjs/source_ng/dtoa.h deleted file mode 100755 index 85be04069..000000000 --- a/vendor/quickjs/source_ng/dtoa.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Tiny float64 printing and parsing library - * - * Copyright (c) 2024 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef DTOA_H -#define DTOA_H - -//#define JS_DTOA_DUMP_STATS - -/* maximum number of digits for fixed and frac formats */ -#define JS_DTOA_MAX_DIGITS 101 - -/* radix != 10 is only supported with flags = JS_DTOA_FORMAT_FREE */ -/* use as many digits as necessary */ -#define JS_DTOA_FORMAT_FREE (0 << 0) -/* use n_digits significant digits (1 <= n_digits <= JS_DTOA_MAX_DIGITS) */ -#define JS_DTOA_FORMAT_FIXED (1 << 0) -/* force fractional format: [-]dd.dd with n_digits fractional digits. - 0 <= n_digits <= JS_DTOA_MAX_DIGITS */ -#define JS_DTOA_FORMAT_FRAC (2 << 0) -#define JS_DTOA_FORMAT_MASK (3 << 0) - -/* select exponential notation either in fixed or free format */ -#define JS_DTOA_EXP_AUTO (0 << 2) -#define JS_DTOA_EXP_ENABLED (1 << 2) -#define JS_DTOA_EXP_DISABLED (2 << 2) -#define JS_DTOA_EXP_MASK (3 << 2) - -#define JS_DTOA_MINUS_ZERO (1 << 4) /* show the minus sign for -0 */ - -/* only accepts integers (no dot, no exponent) */ -#define JS_ATOD_INT_ONLY (1 << 0) -/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ -#define JS_ATOD_ACCEPT_BIN_OCT (1 << 1) -/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ -#define JS_ATOD_ACCEPT_LEGACY_OCTAL (1 << 2) -/* accept _ between digits as a digit separator */ -#define JS_ATOD_ACCEPT_UNDERSCORES (1 << 3) - -typedef struct { - uint64_t mem[37]; -} JSDTOATempMem; - -typedef struct { - uint64_t mem[27]; -} JSATODTempMem; - -/* return a maximum bound of the string length */ -int js_dtoa_max_len(double d, int radix, int n_digits, int flags); -/* return the string length */ -int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, - JSDTOATempMem *tmp_mem); -double js_atod(const char *str, const char **pnext, int radix, int flags, - JSATODTempMem *tmp_mem); - -#ifdef JS_DTOA_DUMP_STATS -void js_dtoa_dump_stats(void); -#endif - -/* additional exported functions */ -size_t u32toa(char *buf, uint32_t n); -size_t i32toa(char *buf, int32_t n); -size_t u64toa(char *buf, uint64_t n); -size_t i64toa(char *buf, int64_t n); -size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix); -size_t i64toa_radix(char *buf, int64_t n, unsigned int radix); - -#endif /* DTOA_H */ diff --git a/vendor/quickjs/source_ng/fuzz.c b/vendor/quickjs/source_ng/fuzz.c deleted file mode 100755 index fca8fa413..000000000 --- a/vendor/quickjs/source_ng/fuzz.c +++ /dev/null @@ -1,22 +0,0 @@ -// clang -g -O1 -fsanitize=fuzzer -o fuzz fuzz.c -#include "quickjs.h" -#include "quickjs.c" -#include "cutils.c" -#include "libregexp.c" -#include "libunicode.c" -#include - -int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) -{ - JSRuntime *rt = JS_NewRuntime(); - if (!rt) - exit(1); - JSContext *ctx = JS_NewContext(rt); - if (!ctx) - exit(1); - JSValue val = JS_ReadObject(ctx, buf, len, /*flags*/0); - JS_FreeValue(ctx, val); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); - return 0; -} diff --git a/vendor/quickjs/source_ng/libregexp-opcode.h b/vendor/quickjs/source_ng/libregexp-opcode.h deleted file mode 100755 index 5c1714ab0..000000000 --- a/vendor/quickjs/source_ng/libregexp-opcode.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Regular Expression Engine - * - * Copyright (c) 2017-2018 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifdef DEF - -DEF(invalid, 1) /* never used */ -DEF(char8, 2) /* 7 bits in fact */ -DEF(char16, 3) -DEF(char32, 5) -DEF(dot, 1) -DEF(any, 1) /* same as dot but match any character including line terminator */ -DEF(line_start, 1) -DEF(line_end, 1) -DEF(goto, 5) -DEF(split_goto_first, 5) -DEF(split_next_first, 5) -DEF(match, 1) -DEF(save_start, 2) /* save start position */ -DEF(save_end, 2) /* save end position, must come after saved_start */ -DEF(save_reset, 3) /* reset save positions */ -DEF(loop, 5) /* decrement the top the stack and goto if != 0 */ -DEF(push_i32, 5) /* push integer on the stack */ -DEF(drop, 1) -DEF(word_boundary, 1) -DEF(not_word_boundary, 1) -DEF(back_reference, 2) -DEF(backward_back_reference, 2) /* must come after back_reference */ -DEF(range, 3) /* variable length */ -DEF(range32, 3) /* variable length */ -DEF(lookahead, 5) -DEF(negative_lookahead, 5) -DEF(push_char_pos, 1) /* push the character position on the stack */ -DEF(check_advance, 1) /* pop one stack element and check that it is different from the character position */ -DEF(prev, 1) /* go to the previous char */ -DEF(simple_greedy_quant, 17) - -#endif /* DEF */ diff --git a/vendor/quickjs/source_ng/libregexp.c b/vendor/quickjs/source_ng/libregexp.c deleted file mode 100755 index ac60650c9..000000000 --- a/vendor/quickjs/source_ng/libregexp.c +++ /dev/null @@ -1,2676 +0,0 @@ -/* - * Regular Expression Engine - * - * Copyright (c) 2017-2018 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include -#include -#include -#include -#include -#include - -#include "cutils.h" -#include "libregexp.h" - -/* - TODO: - - - Add a lock step execution mode (=linear time execution guaranteed) - when the regular expression is "simple" i.e. no backreference nor - complicated lookahead. The opcodes are designed for this execution - model. -*/ - -#if defined(TEST) -#define DUMP_REOP -#endif - -typedef enum { -#define DEF(id, size) REOP_ ## id, -#include "libregexp-opcode.h" -#undef DEF - REOP_COUNT, -} REOPCodeEnum; - -#define CAPTURE_COUNT_MAX 255 -#define STACK_SIZE_MAX 255 -/* must be large enough to have a negligible runtime cost and small - enough to call the interrupt callback often. */ -#define INTERRUPT_COUNTER_INIT 10000 - -/* unicode code points */ -#define CP_LS 0x2028 -#define CP_PS 0x2029 - -#define TMP_BUF_SIZE 128 - -// invariant: is_unicode ^ unicode_sets (or neither, but not both) -typedef struct { - DynBuf byte_code; - const uint8_t *buf_ptr; - const uint8_t *buf_end; - const uint8_t *buf_start; - int re_flags; - bool is_unicode; - bool unicode_sets; - bool ignore_case; - bool dotall; - int capture_count; - int total_capture_count; /* -1 = not computed yet */ - int has_named_captures; /* -1 = don't know, 0 = no, 1 = yes */ - void *opaque; - DynBuf group_names; - union { - char error_msg[TMP_BUF_SIZE]; - char tmp_buf[TMP_BUF_SIZE]; - } u; -} REParseState; - -typedef struct { -#ifdef DUMP_REOP - const char *name; -#endif - uint8_t size; -} REOpCode; - -static const REOpCode reopcode_info[REOP_COUNT] = { -#ifdef DUMP_REOP -#define DEF(id, size) { #id, size }, -#else -#define DEF(id, size) { size }, -#endif -#include "libregexp-opcode.h" -#undef DEF -}; - -#define RE_HEADER_FLAGS 0 -#define RE_HEADER_CAPTURE_COUNT 2 -#define RE_HEADER_STACK_SIZE 3 -#define RE_HEADER_BYTECODE_LEN 4 - -#define RE_HEADER_LEN 8 - -static inline int lre_is_digit(int c) { - return c >= '0' && c <= '9'; -} - -/* insert 'len' bytes at position 'pos'. Return < 0 if error. */ -static int dbuf_insert(DynBuf *s, int pos, int len) -{ - if (dbuf_realloc(s, s->size + len)) - return -1; - memmove(s->buf + pos + len, s->buf + pos, s->size - pos); - s->size += len; - return 0; -} - -static const uint16_t char_range_d[] = { - 1, - 0x0030, 0x0039 + 1, -}; - -/* code point ranges for Zs,Zl or Zp property */ -static const uint16_t char_range_s[] = { - 10, - 0x0009, 0x000D + 1, - 0x0020, 0x0020 + 1, - 0x00A0, 0x00A0 + 1, - 0x1680, 0x1680 + 1, - 0x2000, 0x200A + 1, - /* 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; */ - /* 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; */ - 0x2028, 0x2029 + 1, - 0x202F, 0x202F + 1, - 0x205F, 0x205F + 1, - 0x3000, 0x3000 + 1, - /* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; */ - 0xFEFF, 0xFEFF + 1, -}; - -bool lre_is_space(int c) -{ - int i, n, low, high; - n = (countof(char_range_s) - 1) / 2; - for(i = 0; i < n; i++) { - low = char_range_s[2 * i + 1]; - if (c < low) - return false; - high = char_range_s[2 * i + 2]; - if (c < high) - return true; - } - return false; -} - -uint32_t const lre_id_start_table_ascii[4] = { - /* $ A-Z _ a-z */ - 0x00000000, 0x00000010, 0x87FFFFFE, 0x07FFFFFE -}; - -uint32_t const lre_id_continue_table_ascii[4] = { - /* $ 0-9 A-Z _ a-z */ - 0x00000000, 0x03FF0010, 0x87FFFFFE, 0x07FFFFFE -}; - - -static const uint16_t char_range_w[] = { - 4, - 0x0030, 0x0039 + 1, - 0x0041, 0x005A + 1, - 0x005F, 0x005F + 1, - 0x0061, 0x007A + 1, -}; - -#define CLASS_RANGE_BASE 0x40000000 - -typedef enum { - CHAR_RANGE_d, - CHAR_RANGE_D, - CHAR_RANGE_s, - CHAR_RANGE_S, - CHAR_RANGE_w, - CHAR_RANGE_W, -} CharRangeEnum; - -static const uint16_t *char_range_table[] = { - char_range_d, - char_range_s, - char_range_w, -}; - -static int cr_init_char_range(REParseState *s, CharRange *cr, uint32_t c) -{ - bool invert; - const uint16_t *c_pt; - int len, i; - - invert = c & 1; - c_pt = char_range_table[c >> 1]; - len = *c_pt++; - cr_init(cr, s->opaque, lre_realloc); - for(i = 0; i < len * 2; i++) { - if (cr_add_point(cr, c_pt[i])) - goto fail; - } - if (invert) { - if (cr_invert(cr)) - goto fail; - } - return 0; - fail: - cr_free(cr); - return -1; -} - -#ifdef DUMP_REOP -static __maybe_unused void lre_dump_bytecode(const uint8_t *buf, - int buf_len) -{ - int pos, len, opcode, bc_len, re_flags, i; - uint32_t val; - - assert(buf_len >= RE_HEADER_LEN); - - re_flags = lre_get_flags(buf); - bc_len = get_u32(buf + RE_HEADER_BYTECODE_LEN); - assert(bc_len + RE_HEADER_LEN <= buf_len); - printf("flags: 0x%x capture_count=%d stack_size=%d\n", - re_flags, buf[RE_HEADER_CAPTURE_COUNT], buf[RE_HEADER_STACK_SIZE]); - if (re_flags & LRE_FLAG_NAMED_GROUPS) { - const char *p; - p = (char *)buf + RE_HEADER_LEN + bc_len; - printf("named groups: "); - for(i = 1; i < buf[RE_HEADER_CAPTURE_COUNT]; i++) { - if (i != 1) - printf(","); - printf("<%s>", p); - p += strlen(p) + 1; - } - printf("\n"); - assert(p == (char *)(buf + buf_len)); - } - printf("bytecode_len=%d\n", bc_len); - - buf += RE_HEADER_LEN; - pos = 0; - while (pos < bc_len) { - printf("%5u: ", pos); - opcode = buf[pos]; - len = reopcode_info[opcode].size; - if (opcode >= REOP_COUNT) { - printf(" invalid opcode=0x%02x\n", opcode); - break; - } - if ((pos + len) > bc_len) { - printf(" buffer overflow (opcode=0x%02x)\n", opcode); - break; - } - printf("%s", reopcode_info[opcode].name); - switch(opcode) { - case REOP_char8: - val = get_u8(buf + pos + 1); - goto printchar; - case REOP_char16: - val = get_u16(buf + pos + 1); - goto printchar; - case REOP_char32: - val = get_u32(buf + pos + 1); - printchar: - if (val >= ' ' && val <= 126) - printf(" '%c'", val); - else - printf(" 0x%08x", val); - break; - case REOP_goto: - case REOP_split_goto_first: - case REOP_split_next_first: - case REOP_loop: - case REOP_lookahead: - case REOP_negative_lookahead: - val = get_u32(buf + pos + 1); - val += (pos + 5); - printf(" %u", val); - break; - case REOP_simple_greedy_quant: - printf(" %u %u %u %u", - get_u32(buf + pos + 1) + (pos + 17), - get_u32(buf + pos + 1 + 4), - get_u32(buf + pos + 1 + 8), - get_u32(buf + pos + 1 + 12)); - break; - case REOP_save_start: - case REOP_save_end: - case REOP_back_reference: - case REOP_backward_back_reference: - printf(" %u", buf[pos + 1]); - break; - case REOP_save_reset: - printf(" %u %u", buf[pos + 1], buf[pos + 2]); - break; - case REOP_push_i32: - val = get_u32(buf + pos + 1); - printf(" %d", val); - break; - case REOP_range: - { - int n, i; - n = get_u16(buf + pos + 1); - len += n * 4; - for(i = 0; i < n * 2; i++) { - val = get_u16(buf + pos + 3 + i * 2); - printf(" 0x%04x", val); - } - } - break; - case REOP_range32: - { - int n, i; - n = get_u16(buf + pos + 1); - len += n * 8; - for(i = 0; i < n * 2; i++) { - val = get_u32(buf + pos + 3 + i * 4); - printf(" 0x%08x", val); - } - } - break; - default: - break; - } - printf("\n"); - pos += len; - } -} -#endif - -static void re_emit_op(REParseState *s, int op) -{ - dbuf_putc(&s->byte_code, op); -} - -/* return the offset of the u32 value */ -static int re_emit_op_u32(REParseState *s, int op, uint32_t val) -{ - int pos; - dbuf_putc(&s->byte_code, op); - pos = s->byte_code.size; - dbuf_put_u32(&s->byte_code, val); - return pos; -} - -static int re_emit_goto(REParseState *s, int op, uint32_t val) -{ - int pos; - dbuf_putc(&s->byte_code, op); - pos = s->byte_code.size; - dbuf_put_u32(&s->byte_code, val - (pos + 4)); - return pos; -} - -static void re_emit_op_u8(REParseState *s, int op, uint32_t val) -{ - dbuf_putc(&s->byte_code, op); - dbuf_putc(&s->byte_code, val); -} - -static void re_emit_op_u16(REParseState *s, int op, uint32_t val) -{ - dbuf_putc(&s->byte_code, op); - dbuf_put_u16(&s->byte_code, val); -} - -static int JS_PRINTF_FORMAT_ATTR(2, 3) re_parse_error(REParseState *s, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - vsnprintf(s->u.error_msg, sizeof(s->u.error_msg), fmt, ap); - va_end(ap); - return -1; -} - -static int re_parse_out_of_memory(REParseState *s) -{ - return re_parse_error(s, "out of memory"); -} - -static int lre_check_size(REParseState *s) -{ - if (s->byte_code.size < 64*1024*1024) - return 0; - return re_parse_out_of_memory(s); -} - -/* If allow_overflow is false, return -1 in case of - overflow. Otherwise return INT32_MAX. */ -static int parse_digits(const uint8_t **pp, bool allow_overflow) -{ - const uint8_t *p; - uint64_t v; - int c; - - p = *pp; - v = 0; - for(;;) { - c = *p; - if (c < '0' || c > '9') - break; - v = v * 10 + c - '0'; - if (v >= INT32_MAX) { - if (allow_overflow) - v = INT32_MAX; - else - return -1; - } - p++; - } - *pp = p; - return v; -} - -static int re_parse_expect(REParseState *s, const uint8_t **pp, int c) -{ - const uint8_t *p; - p = *pp; - if (*p != c) - return re_parse_error(s, "expecting '%c'", c); - p++; - *pp = p; - return 0; -} - -/* Parse an escape sequence, *pp points after the '\': - allow_utf16 value: - 0 : no UTF-16 escapes allowed - 1 : UTF-16 escapes allowed - 2 : UTF-16 escapes allowed and escapes of surrogate pairs are - converted to a unicode character (unicode regexp case). - - Return the unicode char and update *pp if recognized, - return -1 if malformed escape, - return -2 otherwise. */ -int lre_parse_escape(const uint8_t **pp, int allow_utf16) -{ - const uint8_t *p; - uint32_t c; - - p = *pp; - c = *p++; - switch(c) { - case 'b': - c = '\b'; - break; - case 'f': - c = '\f'; - break; - case 'n': - c = '\n'; - break; - case 'r': - c = '\r'; - break; - case 't': - c = '\t'; - break; - case 'v': - c = '\v'; - break; - case 'x': - case 'u': - { - int h, n, i; - uint32_t c1; - - if (*p == '{' && allow_utf16) { - p++; - c = 0; - for(;;) { - h = from_hex(*p++); - if (h < 0) - return -1; - c = (c << 4) | h; - if (c > 0x10FFFF) - return -1; - if (*p == '}') - break; - } - p++; - } else { - if (c == 'x') { - n = 2; - } else { - n = 4; - } - - c = 0; - for(i = 0; i < n; i++) { - h = from_hex(*p++); - if (h < 0) { - return -1; - } - c = (c << 4) | h; - } - if (is_hi_surrogate(c) && - allow_utf16 == 2 && p[0] == '\\' && p[1] == 'u') { - /* convert an escaped surrogate pair into a - unicode char */ - c1 = 0; - for(i = 0; i < 4; i++) { - h = from_hex(p[2 + i]); - if (h < 0) - break; - c1 = (c1 << 4) | h; - } - if (i == 4 && is_lo_surrogate(c1)) { - p += 6; - c = from_surrogate(c, c1); - } - } - } - } - break; - case '0': case '1': case '2': case '3': - case '4': case '5': case '6': case '7': - c -= '0'; - if (allow_utf16 == 2) { - /* only accept \0 not followed by digit */ - if (c != 0 || lre_is_digit(*p)) - return -1; - } else { - /* parse a legacy octal sequence */ - uint32_t v; - v = *p - '0'; - if (v > 7) - break; - c = (c << 3) | v; - p++; - if (c >= 32) - break; - v = *p - '0'; - if (v > 7) - break; - c = (c << 3) | v; - p++; - } - break; - default: - return -2; - } - *pp = p; - return c; -} - -/* XXX: we use the same chars for name and value */ -static bool is_unicode_char(int c) -{ - return ((c >= '0' && c <= '9') || - (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - (c == '_')); -} - -static int parse_unicode_property(REParseState *s, CharRange *cr, - const uint8_t **pp, bool is_inv) -{ - const uint8_t *p; - char name[64], value[64]; - char *q; - bool script_ext; - int ret; - - p = *pp; - if (*p != '{') - return re_parse_error(s, "expecting '{' after \\p"); - p++; - q = name; - while (is_unicode_char(*p)) { - if ((q - name) >= sizeof(name) - 1) - goto unknown_property_name; - *q++ = *p++; - } - *q = '\0'; - q = value; - if (*p == '=') { - p++; - while (is_unicode_char(*p)) { - if ((q - value) >= sizeof(value) - 1) - return re_parse_error(s, "unknown unicode property value"); - *q++ = *p++; - } - } - *q = '\0'; - if (*p != '}') - return re_parse_error(s, "expecting '}'"); - p++; - // printf("name=%s value=%s\n", name, value); - - if (!strcmp(name, "Script") || !strcmp(name, "sc")) { - script_ext = false; - goto do_script; - } else if (!strcmp(name, "Script_Extensions") || !strcmp(name, "scx")) { - script_ext = true; - do_script: - cr_init(cr, s->opaque, lre_realloc); - ret = unicode_script(cr, value, script_ext); - if (ret) { - cr_free(cr); - if (ret == -2) - return re_parse_error(s, "unknown unicode script"); - else - goto out_of_memory; - } - } else if (!strcmp(name, "General_Category") || !strcmp(name, "gc")) { - cr_init(cr, s->opaque, lre_realloc); - ret = unicode_general_category(cr, value); - if (ret) { - cr_free(cr); - if (ret == -2) - return re_parse_error(s, "unknown unicode general category"); - else - goto out_of_memory; - } - } else if (value[0] == '\0') { - cr_init(cr, s->opaque, lre_realloc); - ret = unicode_general_category(cr, name); - if (ret == -1) { - cr_free(cr); - goto out_of_memory; - } - if (ret < 0) { - ret = unicode_prop(cr, name); - if (ret) { - cr_free(cr); - if (ret == -2) - goto unknown_property_name; - else - goto out_of_memory; - } - } - } else { - unknown_property_name: - return re_parse_error(s, "unknown unicode property name"); - } - - if (is_inv) { - if (cr_invert(cr)) { - cr_free(cr); - return -1; - } - } - *pp = p; - return 0; - out_of_memory: - return re_parse_out_of_memory(s); -} - -/* return -1 if error otherwise the character or a class range - (CLASS_RANGE_BASE). In case of class range, 'cr' is - initialized. Otherwise, it is ignored. */ -static int get_class_atom(REParseState *s, CharRange *cr, - const uint8_t **pp, bool inclass) -{ - const uint8_t *p, *p_next; - uint32_t c; - int ret; - - p = *pp; - - c = *p; - switch(c) { - case '\\': - p++; - if (p >= s->buf_end) - goto unexpected_end; - c = *p++; - switch(c) { - case 'd': - c = CHAR_RANGE_d; - goto class_range; - case 'D': - c = CHAR_RANGE_D; - goto class_range; - case 's': - c = CHAR_RANGE_s; - goto class_range; - case 'S': - c = CHAR_RANGE_S; - goto class_range; - case 'w': - c = CHAR_RANGE_w; - goto class_range; - case 'W': - c = CHAR_RANGE_W; - class_range: - if (cr_init_char_range(s, cr, c)) - return -1; - c = CLASS_RANGE_BASE; - break; - case 'c': - c = *p; - if ((c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (((c >= '0' && c <= '9') || c == '_') && - inclass && !s->is_unicode)) { /* Annex B.1.4 */ - c &= 0x1f; - p++; - } else if (s->is_unicode) { - goto invalid_escape; - } else { - /* otherwise return '\' and 'c' */ - p--; - c = '\\'; - } - break; - case 'p': - case 'P': - if (s->is_unicode) { - if (parse_unicode_property(s, cr, &p, (c == 'P'))) - return -1; - c = CLASS_RANGE_BASE; - break; - } - /* fall thru */ - default: - p--; - ret = lre_parse_escape(&p, s->is_unicode * 2); - if (ret >= 0) { - c = ret; - } else { - if (ret == -2 && *p != '\0' && strchr("^$\\.*+?()[]{}|/", *p)) { - /* always valid to escape these characters */ - goto normal_char; - } else if (s->is_unicode) { - // special case: allowed inside [] but not outside - if (ret == -2 && *p == '-' && inclass) - goto normal_char; - invalid_escape: - return re_parse_error(s, "invalid escape sequence in regular expression"); - } else { - /* just ignore the '\' */ - goto normal_char; - } - } - break; - } - break; - case '\0': - if (p >= s->buf_end) { - unexpected_end: - return re_parse_error(s, "unexpected end"); - } - /* fall thru */ - default: - normal_char: - p++; - if (c >= 0x80) { - c = utf8_decode(p - 1, &p_next); - if (p_next == p) - return re_parse_error(s, "invalid UTF-8 sequence"); - p = p_next; - if (c > 0xFFFF && !s->is_unicode) { - // TODO(chqrlie): should handle non BMP-1 code points in - // the calling function and no require the source string - // to be CESU-8 encoded if not s->is_unicode - return re_parse_error(s, "malformed unicode char"); - } - } - break; - } - *pp = p; - return c; -} - -static int re_emit_range(REParseState *s, const CharRange *cr) -{ - int len, i; - uint32_t high; - - len = (unsigned)cr->len / 2; - if (len >= 65535) - return re_parse_error(s, "too many ranges"); - if (len == 0) { - /* not sure it can really happen. Emit a match that is always - false */ - re_emit_op_u32(s, REOP_char32, -1); - } else { - high = cr->points[cr->len - 1]; - if (high == UINT32_MAX) - high = cr->points[cr->len - 2]; - if (high <= 0xffff) { - /* can use 16 bit ranges with the conversion that 0xffff = - infinity */ - re_emit_op_u16(s, REOP_range, len); - for(i = 0; i < cr->len; i += 2) { - dbuf_put_u16(&s->byte_code, cr->points[i]); - high = cr->points[i + 1] - 1; - if (high == UINT32_MAX - 1) - high = 0xffff; - dbuf_put_u16(&s->byte_code, high); - } - } else { - re_emit_op_u16(s, REOP_range32, len); - for(i = 0; i < cr->len; i += 2) { - dbuf_put_u32(&s->byte_code, cr->points[i]); - dbuf_put_u32(&s->byte_code, cr->points[i + 1] - 1); - } - } - } - return 0; -} - -// s->unicode turns patterns like []] into syntax errors -// s->unicode_sets turns more patterns into errors, like [a-] or [[] -static int re_parse_char_class(REParseState *s, const uint8_t **pp) -{ - const uint8_t *p; - uint32_t c1, c2; - CharRange cr_s, *cr = &cr_s; - CharRange cr1_s, *cr1 = &cr1_s; - bool invert; - - cr_init(cr, s->opaque, lre_realloc); - p = *pp; - p++; /* skip '[' */ - - if (s->unicode_sets) { - static const char verboten[] = - "()[{}/-|" "\0" - "&&!!##$$%%**++,,..::;;<<==>>??@@``~~" "\0" - "^^^_^^"; - const char *s = verboten; - int n = 1; - do { - if (!memcmp(s, p, n)) - if (p[n] == ']') - goto invalid_class_range; - s += n; - if (!*s) { - s++; - n++; - } - } while (n < 4); - } - - invert = false; - if (*p == '^') { - p++; - invert = true; - } - - for(;;) { - if (*p == ']') - break; - c1 = get_class_atom(s, cr1, &p, true); - if ((int)c1 < 0) - goto fail; - if (*p == '-' && p[1] == ']' && s->unicode_sets) { - if (c1 >= CLASS_RANGE_BASE) - cr_free(cr1); - goto invalid_class_range; - } - if (*p == '-' && p[1] != ']') { - const uint8_t *p0 = p + 1; - if (c1 >= CLASS_RANGE_BASE) { - if (s->is_unicode) { - cr_free(cr1); - goto invalid_class_range; - } - /* Annex B: match '-' character */ - goto class_atom; - } - c2 = get_class_atom(s, cr1, &p0, true); - if ((int)c2 < 0) - goto fail; - if (c2 >= CLASS_RANGE_BASE) { - cr_free(cr1); - if (s->is_unicode) { - goto invalid_class_range; - } - /* Annex B: match '-' character */ - goto class_atom; - } - p = p0; - if (c2 < c1) { - invalid_class_range: - re_parse_error(s, "invalid class range"); - goto fail; - } - if (cr_union_interval(cr, c1, c2)) - goto memory_error; - } else { - class_atom: - if (c1 >= CLASS_RANGE_BASE) { - int ret; - ret = cr_union1(cr, cr1->points, cr1->len); - cr_free(cr1); - if (ret) - goto memory_error; - } else { - if (cr_union_interval(cr, c1, c1)) - goto memory_error; - } - } - } - if (s->ignore_case) { - if (cr_regexp_canonicalize(cr, s->is_unicode)) - goto memory_error; - } - if (invert) { - if (cr_invert(cr)) - goto memory_error; - } - if (re_emit_range(s, cr)) - goto fail; - cr_free(cr); - p++; /* skip ']' */ - *pp = p; - return 0; - memory_error: - re_parse_out_of_memory(s); - fail: - cr_free(cr); - return -1; -} - -/* Return: - - true if the opcodes may not advance the char pointer - - false if the opcodes always advance the char pointer -*/ -static bool re_need_check_advance(const uint8_t *bc_buf, int bc_buf_len) -{ - int pos, opcode, len; - uint32_t val; - bool ret; - - ret = true; - pos = 0; - - while (pos < bc_buf_len) { - opcode = bc_buf[pos]; - len = reopcode_info[opcode].size; - switch(opcode) { - case REOP_range: - val = get_u16(bc_buf + pos + 1); - len += val * 4; - goto simple_char; - case REOP_range32: - val = get_u16(bc_buf + pos + 1); - len += val * 8; - goto simple_char; - case REOP_char32: - case REOP_char16: - case REOP_char8: - case REOP_dot: - case REOP_any: - simple_char: - ret = false; - break; - case REOP_line_start: - case REOP_line_end: - case REOP_push_i32: - case REOP_push_char_pos: - case REOP_drop: - case REOP_word_boundary: - case REOP_not_word_boundary: - case REOP_prev: - /* no effect */ - break; - case REOP_save_start: - case REOP_save_end: - case REOP_save_reset: - case REOP_back_reference: - case REOP_backward_back_reference: - break; - default: - /* safe behvior: we cannot predict the outcome */ - return true; - } - pos += len; - } - return ret; -} - -/* return -1 if a simple quantifier cannot be used. Otherwise return - the number of characters in the atom. */ -static int re_is_simple_quantifier(const uint8_t *bc_buf, int bc_buf_len) -{ - int pos, opcode, len, count; - uint32_t val; - - count = 0; - pos = 0; - while (pos < bc_buf_len) { - opcode = bc_buf[pos]; - len = reopcode_info[opcode].size; - switch(opcode) { - case REOP_range: - val = get_u16(bc_buf + pos + 1); - len += val * 4; - goto simple_char; - case REOP_range32: - val = get_u16(bc_buf + pos + 1); - len += val * 8; - goto simple_char; - case REOP_char32: - case REOP_char16: - case REOP_char8: - case REOP_dot: - case REOP_any: - simple_char: - count++; - break; - case REOP_line_start: - case REOP_line_end: - case REOP_word_boundary: - case REOP_not_word_boundary: - break; - default: - return -1; - } - pos += len; - } - return count; -} - -/* '*pp' is the first char after '<' */ -static int re_parse_group_name(char *buf, int buf_size, const uint8_t **pp) -{ - const uint8_t *p, *p_next; - uint32_t c, d; - char *q; - - p = *pp; - q = buf; - for(;;) { - c = *p++; - if (c == '\\') { - if (*p != 'u') - return -1; - c = lre_parse_escape(&p, 2); // accept surrogate pairs - if ((int)c < 0) - return -1; - } else if (c == '>') { - break; - } else if (c >= 0x80) { - c = utf8_decode(p - 1, &p_next); - if (p_next == p) - return -1; - p = p_next; - if (is_hi_surrogate(c)) { - d = utf8_decode(p, &p_next); - if (is_lo_surrogate(d)) { - c = from_surrogate(c, d); - p = p_next; - } - } - } - if (q == buf) { - if (!lre_js_is_ident_first(c)) - return -1; - } else { - if (!lre_js_is_ident_next(c)) - return -1; - } - if ((q - buf + UTF8_CHAR_LEN_MAX + 1) > buf_size) - return -1; - if (c < 0x80) { - *q++ = c; - } else { - q += utf8_encode((uint8_t*)q, c); - } - } - if (q == buf) - return -1; - *q = '\0'; - *pp = p; - return 0; -} - -/* if capture_name = NULL: return the number of captures + 1. - Otherwise, return the capture index corresponding to capture_name - or -1 if none */ -static int re_parse_captures(REParseState *s, int *phas_named_captures, - const char *capture_name) -{ - const uint8_t *p; - int capture_index; - char name[TMP_BUF_SIZE]; - - capture_index = 1; - *phas_named_captures = 0; - for (p = s->buf_start; p < s->buf_end; p++) { - switch (*p) { - case '(': - if (p[1] == '?') { - if (p[2] == '<' && p[3] != '=' && p[3] != '!') { - *phas_named_captures = 1; - /* potential named capture */ - if (capture_name) { - p += 3; - if (re_parse_group_name(name, sizeof(name), &p) == 0) { - if (!strcmp(name, capture_name)) - return capture_index; - } - } - capture_index++; - if (capture_index >= CAPTURE_COUNT_MAX) - goto done; - } - } else { - capture_index++; - if (capture_index >= CAPTURE_COUNT_MAX) - goto done; - } - break; - case '\\': - p++; - break; - case '[': - for (p += 1 + (*p == ']'); p < s->buf_end && *p != ']'; p++) { - if (*p == '\\') - p++; - } - break; - } - } - done: - if (capture_name) - return -1; - else - return capture_index; -} - -static int re_count_captures(REParseState *s) -{ - if (s->total_capture_count < 0) { - s->total_capture_count = re_parse_captures(s, &s->has_named_captures, - NULL); - } - return s->total_capture_count; -} - -static bool re_has_named_captures(REParseState *s) -{ - if (s->has_named_captures < 0) - re_count_captures(s); - return s->has_named_captures; -} - -static int find_group_name(REParseState *s, const char *name) -{ - const char *p, *buf_end; - size_t len, name_len; - int capture_index; - - p = (char *)s->group_names.buf; - if (!p) return -1; - buf_end = (char *)s->group_names.buf + s->group_names.size; - name_len = strlen(name); - capture_index = 1; - while (p < buf_end) { - len = strlen(p); - if (len == name_len && memcmp(name, p, name_len) == 0) - return capture_index; - p += len + 1; - capture_index++; - } - return -1; -} - -static int re_parse_disjunction(REParseState *s, bool is_backward_dir); - -static int re_parse_term(REParseState *s, bool is_backward_dir) -{ - const uint8_t *p; - int c, last_atom_start, quant_min, quant_max, last_capture_count; - bool greedy, add_zero_advance_check, is_neg, is_backward_lookahead; - CharRange cr_s, *cr = &cr_s; - - if (lre_check_size(s)) - return -1; - last_atom_start = -1; - last_capture_count = 0; - p = s->buf_ptr; - c = *p; - switch(c) { - case '^': - p++; - re_emit_op(s, REOP_line_start); - break; - case '$': - p++; - re_emit_op(s, REOP_line_end); - break; - case '.': - p++; - last_atom_start = s->byte_code.size; - last_capture_count = s->capture_count; - if (is_backward_dir) - re_emit_op(s, REOP_prev); - re_emit_op(s, s->dotall ? REOP_any : REOP_dot); - if (is_backward_dir) - re_emit_op(s, REOP_prev); - break; - case '{': - if (s->is_unicode) { - return re_parse_error(s, "syntax error"); - } else if (!lre_is_digit(p[1])) { - /* Annex B: we accept '{' not followed by digits as a - normal atom */ - goto parse_class_atom; - } else { - const uint8_t *p1 = p + 1; - /* Annex B: error if it is like a repetition count */ - parse_digits(&p1, true); - if (*p1 == ',') { - p1++; - if (lre_is_digit(*p1)) { - parse_digits(&p1, true); - } - } - if (*p1 != '}') { - goto parse_class_atom; - } - } - /* fall thru */ - case '*': - case '+': - case '?': - return re_parse_error(s, "nothing to repeat"); - case '(': - if (p[1] == '?') { - if (p[2] == ':') { - p += 3; - last_atom_start = s->byte_code.size; - last_capture_count = s->capture_count; - s->buf_ptr = p; - if (re_parse_disjunction(s, is_backward_dir)) - return -1; - p = s->buf_ptr; - if (re_parse_expect(s, &p, ')')) - return -1; - } else if ((p[2] == '=' || p[2] == '!')) { - is_neg = (p[2] == '!'); - is_backward_lookahead = false; - p += 3; - goto lookahead; - } else if (p[2] == '<' && - (p[3] == '=' || p[3] == '!')) { - int pos; - is_neg = (p[3] == '!'); - is_backward_lookahead = true; - p += 4; - /* lookahead */ - lookahead: - /* Annex B allows lookahead to be used as an atom for - the quantifiers */ - if (!s->is_unicode && !is_backward_lookahead) { - last_atom_start = s->byte_code.size; - last_capture_count = s->capture_count; - } - pos = re_emit_op_u32(s, REOP_lookahead + is_neg, 0); - s->buf_ptr = p; - if (re_parse_disjunction(s, is_backward_lookahead)) - return -1; - p = s->buf_ptr; - if (re_parse_expect(s, &p, ')')) - return -1; - re_emit_op(s, REOP_match); - /* jump after the 'match' after the lookahead is successful */ - if (dbuf_error(&s->byte_code)) - return -1; - put_u32(s->byte_code.buf + pos, s->byte_code.size - (pos + 4)); - } else if (p[2] == '<') { - p += 3; - if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), - &p)) { - return re_parse_error(s, "invalid group name"); - } - if (find_group_name(s, s->u.tmp_buf) > 0) { - return re_parse_error(s, "duplicate group name"); - } - /* group name with a trailing zero */ - dbuf_put(&s->group_names, (uint8_t *)s->u.tmp_buf, - strlen(s->u.tmp_buf) + 1); - s->has_named_captures = 1; - goto parse_capture; - } else { - return re_parse_error(s, "invalid group"); - } - } else { - int capture_index; - p++; - /* capture without group name */ - dbuf_putc(&s->group_names, 0); - parse_capture: - if (s->capture_count >= CAPTURE_COUNT_MAX) - return re_parse_error(s, "too many captures"); - last_atom_start = s->byte_code.size; - last_capture_count = s->capture_count; - capture_index = s->capture_count++; - re_emit_op_u8(s, REOP_save_start + is_backward_dir, - capture_index); - - s->buf_ptr = p; - if (re_parse_disjunction(s, is_backward_dir)) - return -1; - p = s->buf_ptr; - - re_emit_op_u8(s, REOP_save_start + 1 - is_backward_dir, - capture_index); - - if (re_parse_expect(s, &p, ')')) - return -1; - } - break; - case '\\': - switch(p[1]) { - case 'b': - case 'B': - re_emit_op(s, REOP_word_boundary + (p[1] != 'b')); - p += 2; - break; - case 'k': - { - const uint8_t *p1; - int dummy_res; - - p1 = p; - if (p1[2] != '<') { - /* annex B: we tolerate invalid group names in non - unicode mode if there is no named capture - definition */ - if (s->is_unicode || re_has_named_captures(s)) - return re_parse_error(s, "expecting group name"); - else - goto parse_class_atom; - } - p1 += 3; - if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), - &p1)) { - if (s->is_unicode || re_has_named_captures(s)) - return re_parse_error(s, "invalid group name"); - else - goto parse_class_atom; - } - c = find_group_name(s, s->u.tmp_buf); - if (c < 0) { - /* no capture name parsed before, try to look - after (inefficient, but hopefully not common */ - c = re_parse_captures(s, &dummy_res, s->u.tmp_buf); - if (c < 0) { - if (s->is_unicode || re_has_named_captures(s)) - return re_parse_error(s, "group name not defined"); - else - goto parse_class_atom; - } - } - p = p1; - } - goto emit_back_reference; - case '0': - p += 2; - c = 0; - if (s->is_unicode) { - if (lre_is_digit(*p)) { - return re_parse_error(s, "invalid decimal escape in regular expression"); - } - } else { - /* Annex B.1.4: accept legacy octal */ - if (*p >= '0' && *p <= '7') { - c = *p++ - '0'; - if (*p >= '0' && *p <= '7') { - c = (c << 3) + *p++ - '0'; - } - } - } - goto normal_char; - case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': - case '9': - { - const uint8_t *q = ++p; - - c = parse_digits(&p, false); - if (c < 0 || (c >= s->capture_count && c >= re_count_captures(s))) { - if (!s->is_unicode) { - /* Annex B.1.4: accept legacy octal */ - p = q; - if (*p <= '7') { - c = 0; - if (*p <= '3') - c = *p++ - '0'; - if (*p >= '0' && *p <= '7') { - c = (c << 3) + *p++ - '0'; - if (*p >= '0' && *p <= '7') { - c = (c << 3) + *p++ - '0'; - } - } - } else { - c = *p++; - } - goto normal_char; - } - return re_parse_error(s, "back reference out of range in regular expression"); - } - emit_back_reference: - last_atom_start = s->byte_code.size; - last_capture_count = s->capture_count; - re_emit_op_u8(s, REOP_back_reference + is_backward_dir, c); - } - break; - default: - goto parse_class_atom; - } - break; - case '[': - last_atom_start = s->byte_code.size; - last_capture_count = s->capture_count; - if (is_backward_dir) - re_emit_op(s, REOP_prev); - if (re_parse_char_class(s, &p)) - return -1; - if (is_backward_dir) - re_emit_op(s, REOP_prev); - break; - case ']': - case '}': - if (s->is_unicode) - return re_parse_error(s, "syntax error"); - goto parse_class_atom; - default: - parse_class_atom: - c = get_class_atom(s, cr, &p, false); - if ((int)c < 0) - return -1; - normal_char: - last_atom_start = s->byte_code.size; - last_capture_count = s->capture_count; - if (is_backward_dir) - re_emit_op(s, REOP_prev); - if (c >= CLASS_RANGE_BASE) { - int ret; - /* Note: canonicalization is not needed */ - ret = re_emit_range(s, cr); - cr_free(cr); - if (ret) - return -1; - } else { - if (s->ignore_case) - c = lre_canonicalize(c, s->is_unicode); - if (c <= 0x7f) - re_emit_op_u8(s, REOP_char8, c); - else if (c <= 0xffff) - re_emit_op_u16(s, REOP_char16, c); - else - re_emit_op_u32(s, REOP_char32, c); - } - if (is_backward_dir) - re_emit_op(s, REOP_prev); - break; - } - - /* quantifier */ - if (last_atom_start >= 0) { - c = *p; - switch(c) { - case '*': - p++; - quant_min = 0; - quant_max = INT32_MAX; - goto quantifier; - case '+': - p++; - quant_min = 1; - quant_max = INT32_MAX; - goto quantifier; - case '?': - p++; - quant_min = 0; - quant_max = 1; - goto quantifier; - case '{': - { - const uint8_t *p1 = p; - /* As an extension (see ES6 annex B), we accept '{' not - followed by digits as a normal atom */ - if (!lre_is_digit(p[1])) { - if (s->is_unicode) - goto invalid_quant_count; - break; - } - p++; - quant_min = parse_digits(&p, true); - quant_max = quant_min; - if (*p == ',') { - p++; - if (lre_is_digit(*p)) { - quant_max = parse_digits(&p, true); - if (quant_max < quant_min) { - invalid_quant_count: - return re_parse_error(s, "invalid repetition count"); - } - } else { - quant_max = INT32_MAX; /* infinity */ - } - } - if (*p != '}' && !s->is_unicode) { - /* Annex B: normal atom if invalid '{' syntax */ - p = p1; - break; - } - if (re_parse_expect(s, &p, '}')) - return -1; - } - quantifier: - greedy = true; - if (*p == '?') { - p++; - greedy = false; - } - if (last_atom_start < 0) { - return re_parse_error(s, "nothing to repeat"); - } - if (greedy) { - int len, pos; - - if (quant_max > 0) { - /* specific optimization for simple quantifiers */ - if (dbuf_error(&s->byte_code)) - goto out_of_memory; - len = re_is_simple_quantifier(s->byte_code.buf + last_atom_start, - s->byte_code.size - last_atom_start); - if (len > 0) { - re_emit_op(s, REOP_match); - - if (dbuf_insert(&s->byte_code, last_atom_start, 17)) - goto out_of_memory; - pos = last_atom_start; - s->byte_code.buf[pos++] = REOP_simple_greedy_quant; - put_u32(&s->byte_code.buf[pos], - s->byte_code.size - last_atom_start - 17); - pos += 4; - put_u32(&s->byte_code.buf[pos], quant_min); - pos += 4; - put_u32(&s->byte_code.buf[pos], quant_max); - pos += 4; - put_u32(&s->byte_code.buf[pos], len); - pos += 4; - goto done; - } - } - - if (dbuf_error(&s->byte_code)) - goto out_of_memory; - } - /* the spec tells that if there is no advance when - running the atom after the first quant_min times, - then there is no match. We remove this test when we - are sure the atom always advances the position. */ - add_zero_advance_check = re_need_check_advance(s->byte_code.buf + last_atom_start, - s->byte_code.size - last_atom_start); - - { - int len, pos; - len = s->byte_code.size - last_atom_start; - if (quant_min == 0) { - /* need to reset the capture in case the atom is - not executed */ - if (last_capture_count != s->capture_count) { - if (dbuf_insert(&s->byte_code, last_atom_start, 3)) - goto out_of_memory; - s->byte_code.buf[last_atom_start++] = REOP_save_reset; - s->byte_code.buf[last_atom_start++] = last_capture_count; - s->byte_code.buf[last_atom_start++] = s->capture_count - 1; - } - if (quant_max == 0) { - s->byte_code.size = last_atom_start; - } else if (quant_max == 1 || quant_max == INT32_MAX) { - bool has_goto = (quant_max == INT32_MAX); - if (dbuf_insert(&s->byte_code, last_atom_start, 5 + add_zero_advance_check)) - goto out_of_memory; - s->byte_code.buf[last_atom_start] = REOP_split_goto_first + - greedy; - put_u32(s->byte_code.buf + last_atom_start + 1, - len + 5 * has_goto + add_zero_advance_check * 2); - if (add_zero_advance_check) { - s->byte_code.buf[last_atom_start + 1 + 4] = REOP_push_char_pos; - re_emit_op(s, REOP_check_advance); - } - if (has_goto) - re_emit_goto(s, REOP_goto, last_atom_start); - } else { - if (dbuf_insert(&s->byte_code, last_atom_start, 10 + add_zero_advance_check)) - goto out_of_memory; - pos = last_atom_start; - s->byte_code.buf[pos++] = REOP_push_i32; - put_u32(s->byte_code.buf + pos, quant_max); - pos += 4; - s->byte_code.buf[pos++] = REOP_split_goto_first + greedy; - put_u32(s->byte_code.buf + pos, len + 5 + add_zero_advance_check * 2); - pos += 4; - if (add_zero_advance_check) { - s->byte_code.buf[pos++] = REOP_push_char_pos; - re_emit_op(s, REOP_check_advance); - } - re_emit_goto(s, REOP_loop, last_atom_start + 5); - re_emit_op(s, REOP_drop); - } - } else if (quant_min == 1 && quant_max == INT32_MAX && - !add_zero_advance_check) { - re_emit_goto(s, REOP_split_next_first - greedy, - last_atom_start); - } else { - if (quant_min == 1) { - /* nothing to add */ - } else { - if (dbuf_insert(&s->byte_code, last_atom_start, 5)) - goto out_of_memory; - s->byte_code.buf[last_atom_start] = REOP_push_i32; - put_u32(s->byte_code.buf + last_atom_start + 1, - quant_min); - last_atom_start += 5; - re_emit_goto(s, REOP_loop, last_atom_start); - re_emit_op(s, REOP_drop); - } - if (quant_max == INT32_MAX) { - pos = s->byte_code.size; - re_emit_op_u32(s, REOP_split_goto_first + greedy, - len + 5 + add_zero_advance_check * 2); - if (add_zero_advance_check) - re_emit_op(s, REOP_push_char_pos); - /* copy the atom */ - dbuf_put_self(&s->byte_code, last_atom_start, len); - if (add_zero_advance_check) - re_emit_op(s, REOP_check_advance); - re_emit_goto(s, REOP_goto, pos); - } else if (quant_max > quant_min) { - re_emit_op_u32(s, REOP_push_i32, quant_max - quant_min); - pos = s->byte_code.size; - re_emit_op_u32(s, REOP_split_goto_first + greedy, - len + 5 + add_zero_advance_check * 2); - if (add_zero_advance_check) - re_emit_op(s, REOP_push_char_pos); - /* copy the atom */ - dbuf_put_self(&s->byte_code, last_atom_start, len); - if (add_zero_advance_check) - re_emit_op(s, REOP_check_advance); - re_emit_goto(s, REOP_loop, pos); - re_emit_op(s, REOP_drop); - } - } - last_atom_start = -1; - } - break; - default: - break; - } - } - done: - s->buf_ptr = p; - return 0; - out_of_memory: - return re_parse_out_of_memory(s); -} - -static int re_parse_alternative(REParseState *s, bool is_backward_dir) -{ - const uint8_t *p; - int ret; - size_t start, term_start, end, term_size; - - if (lre_check_size(s)) - return -1; - start = s->byte_code.size; - for(;;) { - p = s->buf_ptr; - if (p >= s->buf_end) - break; - if (*p == '|' || *p == ')') - break; - term_start = s->byte_code.size; - ret = re_parse_term(s, is_backward_dir); - if (ret) - return ret; - if (is_backward_dir) { - /* reverse the order of the terms (XXX: inefficient, but - speed is not really critical here) */ - end = s->byte_code.size; - term_size = end - term_start; - if (dbuf_realloc(&s->byte_code, end + term_size)) - return -1; - memmove(s->byte_code.buf + start + term_size, - s->byte_code.buf + start, - end - start); - memcpy(s->byte_code.buf + start, s->byte_code.buf + end, - term_size); - } - } - return 0; -} - -static int re_parse_disjunction(REParseState *s, bool is_backward_dir) -{ - int start, len, pos; - - if (lre_check_stack_overflow(s->opaque, 0)) - return re_parse_error(s, "stack overflow"); - if (lre_check_size(s)) - return -1; - start = s->byte_code.size; - if (re_parse_alternative(s, is_backward_dir)) - return -1; - while (*s->buf_ptr == '|') { - s->buf_ptr++; - - len = s->byte_code.size - start; - - /* insert a split before the first alternative */ - if (dbuf_insert(&s->byte_code, start, 5)) { - return re_parse_out_of_memory(s); - } - s->byte_code.buf[start] = REOP_split_next_first; - put_u32(s->byte_code.buf + start + 1, len + 5); - - pos = re_emit_op_u32(s, REOP_goto, 0); - - if (re_parse_alternative(s, is_backward_dir)) - return -1; - - /* patch the goto */ - len = s->byte_code.size - (pos + 4); - put_u32(s->byte_code.buf + pos, len); - } - return 0; -} - -/* the control flow is recursive so the analysis can be linear */ -static int lre_compute_stack_size(const uint8_t *bc_buf, int bc_buf_len) -{ - int stack_size, stack_size_max, pos, opcode, len; - uint32_t val; - - stack_size = 0; - stack_size_max = 0; - bc_buf += RE_HEADER_LEN; - bc_buf_len -= RE_HEADER_LEN; - pos = 0; - while (pos < bc_buf_len) { - opcode = bc_buf[pos]; - len = reopcode_info[opcode].size; - assert(opcode < REOP_COUNT); - assert((pos + len) <= bc_buf_len); - switch(opcode) { - case REOP_push_i32: - case REOP_push_char_pos: - stack_size++; - if (stack_size > stack_size_max) { - if (stack_size > STACK_SIZE_MAX) - return -1; - stack_size_max = stack_size; - } - break; - case REOP_drop: - case REOP_check_advance: - assert(stack_size > 0); - stack_size--; - break; - case REOP_range: - val = get_u16(bc_buf + pos + 1); - len += val * 4; - break; - case REOP_range32: - val = get_u16(bc_buf + pos + 1); - len += val * 8; - break; - } - pos += len; - } - return stack_size_max; -} - -/* 'buf' must be a zero terminated UTF-8 string of length buf_len. - Return NULL if error and allocate an error message in *perror_msg, - otherwise the compiled bytecode and its length in plen. -*/ -uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, - const char *buf, size_t buf_len, int re_flags, - void *opaque) -{ - REParseState s_s, *s = &s_s; - int stack_size; - bool is_sticky; - - memset(s, 0, sizeof(*s)); - s->opaque = opaque; - s->buf_ptr = (const uint8_t *)buf; - s->buf_end = s->buf_ptr + buf_len; - s->buf_start = s->buf_ptr; - s->re_flags = re_flags; - s->is_unicode = ((re_flags & LRE_FLAG_UNICODE) != 0); - is_sticky = ((re_flags & LRE_FLAG_STICKY) != 0); - s->ignore_case = ((re_flags & LRE_FLAG_IGNORECASE) != 0); - s->dotall = ((re_flags & LRE_FLAG_DOTALL) != 0); - s->unicode_sets = ((re_flags & LRE_FLAG_UNICODE_SETS) != 0); - s->capture_count = 1; - s->total_capture_count = -1; - s->has_named_captures = -1; - - dbuf_init2(&s->byte_code, opaque, lre_realloc); - dbuf_init2(&s->group_names, opaque, lre_realloc); - - dbuf_put_u16(&s->byte_code, re_flags); /* first element is the flags */ - dbuf_putc(&s->byte_code, 0); /* second element is the number of captures */ - dbuf_putc(&s->byte_code, 0); /* stack size */ - dbuf_put_u32(&s->byte_code, 0); /* bytecode length */ - - if (!is_sticky) { - /* iterate thru all positions (about the same as .*?( ... ) ) - . We do it without an explicit loop so that lock step - thread execution will be possible in an optimized - implementation */ - re_emit_op_u32(s, REOP_split_goto_first, 1 + 5); - re_emit_op(s, REOP_any); - re_emit_op_u32(s, REOP_goto, -(5 + 1 + 5)); - } - re_emit_op_u8(s, REOP_save_start, 0); - - if (re_parse_disjunction(s, false)) { - error: - dbuf_free(&s->byte_code); - dbuf_free(&s->group_names); - js__pstrcpy(error_msg, error_msg_size, s->u.error_msg); - *plen = 0; - return NULL; - } - - re_emit_op_u8(s, REOP_save_end, 0); - - re_emit_op(s, REOP_match); - - if (*s->buf_ptr != '\0') { - re_parse_error(s, "extraneous characters at the end"); - goto error; - } - - if (dbuf_error(&s->byte_code)) { - re_parse_out_of_memory(s); - goto error; - } - - stack_size = lre_compute_stack_size(s->byte_code.buf, s->byte_code.size); - if (stack_size < 0) { - re_parse_error(s, "too many imbricated quantifiers"); - goto error; - } - - s->byte_code.buf[RE_HEADER_CAPTURE_COUNT] = s->capture_count; - s->byte_code.buf[RE_HEADER_STACK_SIZE] = stack_size; - put_u32(s->byte_code.buf + RE_HEADER_BYTECODE_LEN, - s->byte_code.size - RE_HEADER_LEN); - - /* add the named groups if needed */ - if (s->group_names.size > (s->capture_count - 1)) { - dbuf_put(&s->byte_code, s->group_names.buf, s->group_names.size); - put_u16(s->byte_code.buf + RE_HEADER_FLAGS, - LRE_FLAG_NAMED_GROUPS | lre_get_flags(s->byte_code.buf)); - } - dbuf_free(&s->group_names); - -#ifdef DUMP_REOP - lre_dump_bytecode(s->byte_code.buf, s->byte_code.size); -#endif - - error_msg[0] = '\0'; - *plen = s->byte_code.size; - return s->byte_code.buf; -} - -static bool is_line_terminator(uint32_t c) -{ - return (c == '\n' || c == '\r' || c == CP_LS || c == CP_PS); -} - -static bool is_word_char(uint32_t c) -{ - return ((c >= '0' && c <= '9') || - (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (c == '_')); -} - -#define GET_CHAR(c, cptr, cbuf_end, cbuf_type) \ - do { \ - if (cbuf_type == 0) { \ - c = *cptr++; \ - } else { \ - const uint16_t *_p = (const uint16_t *)cptr; \ - const uint16_t *_end = (const uint16_t *)cbuf_end; \ - c = *_p++; \ - if (is_hi_surrogate(c)) \ - if (cbuf_type == 2) \ - if (_p < _end) \ - if (is_lo_surrogate(*_p)) \ - c = from_surrogate(c, *_p++); \ - cptr = (const void *)_p; \ - } \ - } while (0) - -#define PEEK_CHAR(c, cptr, cbuf_end, cbuf_type) \ - do { \ - if (cbuf_type == 0) { \ - c = cptr[0]; \ - } else { \ - const uint16_t *_p = (const uint16_t *)cptr; \ - const uint16_t *_end = (const uint16_t *)cbuf_end; \ - c = *_p++; \ - if (is_hi_surrogate(c)) \ - if (cbuf_type == 2) \ - if (_p < _end) \ - if (is_lo_surrogate(*_p)) \ - c = from_surrogate(c, *_p); \ - } \ - } while (0) - -#define PEEK_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ - do { \ - if (cbuf_type == 0) { \ - c = cptr[-1]; \ - } else { \ - const uint16_t *_p = (const uint16_t *)cptr - 1; \ - const uint16_t *_start = (const uint16_t *)cbuf_start; \ - c = *_p; \ - if (is_lo_surrogate(c)) \ - if (cbuf_type == 2) \ - if (_p > _start) \ - if (is_hi_surrogate(_p[-1])) \ - c = from_surrogate(*--_p, c); \ - } \ - } while (0) - -#define GET_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ - do { \ - if (cbuf_type == 0) { \ - cptr--; \ - c = cptr[0]; \ - } else { \ - const uint16_t *_p = (const uint16_t *)cptr - 1; \ - const uint16_t *_start = (const uint16_t *)cbuf_start; \ - c = *_p; \ - if (is_lo_surrogate(c)) \ - if (cbuf_type == 2) \ - if (_p > _start) \ - if (is_hi_surrogate(_p[-1])) \ - c = from_surrogate(*--_p, c); \ - cptr = (const void *)_p; \ - } \ - } while (0) - -#define PREV_CHAR(cptr, cbuf_start, cbuf_type) \ - do { \ - if (cbuf_type == 0) { \ - cptr--; \ - } else { \ - const uint16_t *_p = (const uint16_t *)cptr - 1; \ - const uint16_t *_start = (const uint16_t *)cbuf_start; \ - if (is_lo_surrogate(*_p)) \ - if (cbuf_type == 2) \ - if (_p > _start) \ - if (is_hi_surrogate(_p[-1])) \ - _p--; \ - cptr = (const void *)_p; \ - } \ - } while (0) - -typedef uintptr_t StackInt; - -typedef enum { - RE_EXEC_STATE_SPLIT, - RE_EXEC_STATE_LOOKAHEAD, - RE_EXEC_STATE_NEGATIVE_LOOKAHEAD, - RE_EXEC_STATE_GREEDY_QUANT, -} REExecStateEnum; - -typedef struct REExecState { - REExecStateEnum type : 8; - uint8_t stack_len; - size_t count; /* only used for RE_EXEC_STATE_GREEDY_QUANT */ - const uint8_t *cptr; - const uint8_t *pc; - void *buf[]; -} REExecState; - -typedef struct { - const uint8_t *cbuf; - const uint8_t *cbuf_end; - /* 0 = 8 bit chars, 1 = 16 bit chars, 2 = 16 bit chars, UTF-16 */ - int cbuf_type; - int capture_count; - int stack_size_max; - bool multi_line; - bool ignore_case; - bool is_unicode; - int interrupt_counter; - void *opaque; /* used for stack overflow check */ - - size_t state_size; - uint8_t *state_stack; - size_t state_stack_size; - size_t state_stack_len; -} REExecContext; - -static int push_state(REExecContext *s, - uint8_t **capture, - StackInt *stack, size_t stack_len, - const uint8_t *pc, const uint8_t *cptr, - REExecStateEnum type, size_t count) -{ - REExecState *rs; - uint8_t *new_stack; - size_t new_size, i, n; - StackInt *stack_buf; - - if (unlikely((s->state_stack_len + 1) > s->state_stack_size)) { - /* reallocate the stack */ - new_size = s->state_stack_size * 3 / 2; - if (new_size < 8) - new_size = 8; - new_stack = lre_realloc(s->opaque, s->state_stack, new_size * s->state_size); - if (!new_stack) - return -1; - s->state_stack_size = new_size; - s->state_stack = new_stack; - } - rs = (REExecState *)(s->state_stack + s->state_stack_len * s->state_size); - s->state_stack_len++; - rs->type = type; - rs->count = count; - rs->stack_len = stack_len; - rs->cptr = cptr; - rs->pc = pc; - n = 2 * s->capture_count; - for(i = 0; i < n; i++) - rs->buf[i] = capture[i]; - stack_buf = (StackInt *)(rs->buf + n); - for(i = 0; i < stack_len; i++) - stack_buf[i] = stack[i]; - return 0; -} - -static int lre_poll_timeout(REExecContext *s) -{ - if (unlikely(--s->interrupt_counter <= 0)) { - s->interrupt_counter = INTERRUPT_COUNTER_INIT; - if (lre_check_timeout(s->opaque)) - return LRE_RET_TIMEOUT; - } - return 0; -} - -/* return 1 if match, 0 if not match or < 0 if error. */ -static intptr_t lre_exec_backtrack(REExecContext *s, uint8_t **capture, - StackInt *stack, int stack_len, - const uint8_t *pc, const uint8_t *cptr, - bool no_recurse) -{ - int opcode, ret; - int cbuf_type; - uint32_t val, c; - const uint8_t *cbuf_end; - - cbuf_type = s->cbuf_type; - cbuf_end = s->cbuf_end; - - for(;;) { - // printf("top=%p: pc=%d\n", th_list.top, (int)(pc - (bc_buf + RE_HEADER_LEN))); - opcode = *pc++; - switch(opcode) { - case REOP_match: - { - REExecState *rs; - if (no_recurse) - return (intptr_t)cptr; - ret = 1; - goto recurse; - no_match: - if (no_recurse) - return 0; - ret = 0; - recurse: - for(;;) { - if (lre_poll_timeout(s)) - return LRE_RET_TIMEOUT; - if (s->state_stack_len == 0) - return ret; - rs = (REExecState *)(s->state_stack + - (s->state_stack_len - 1) * s->state_size); - if (rs->type == RE_EXEC_STATE_SPLIT) { - if (!ret) { - pop_state: - memcpy(capture, rs->buf, - sizeof(capture[0]) * 2 * s->capture_count); - pop_state1: - pc = rs->pc; - cptr = rs->cptr; - stack_len = rs->stack_len; - memcpy(stack, rs->buf + 2 * s->capture_count, - stack_len * sizeof(stack[0])); - s->state_stack_len--; - break; - } - } else if (rs->type == RE_EXEC_STATE_GREEDY_QUANT) { - if (!ret) { - uint32_t char_count, i; - memcpy(capture, rs->buf, - sizeof(capture[0]) * 2 * s->capture_count); - stack_len = rs->stack_len; - memcpy(stack, rs->buf + 2 * s->capture_count, - stack_len * sizeof(stack[0])); - pc = rs->pc; - cptr = rs->cptr; - /* go backward */ - char_count = get_u32(pc + 12); - for(i = 0; i < char_count; i++) { - PREV_CHAR(cptr, s->cbuf, cbuf_type); - } - pc = (pc + 16) + (int)get_u32(pc); - rs->cptr = cptr; - rs->count--; - if (rs->count == 0) { - s->state_stack_len--; - } - break; - } - } else { - ret = ((rs->type == RE_EXEC_STATE_LOOKAHEAD && ret) || - (rs->type == RE_EXEC_STATE_NEGATIVE_LOOKAHEAD && !ret)); - if (ret) { - /* keep the capture in case of positive lookahead */ - if (rs->type == RE_EXEC_STATE_LOOKAHEAD) - goto pop_state1; - else - goto pop_state; - } - } - s->state_stack_len--; - } - } - break; - case REOP_char32: - val = get_u32(pc); - pc += 4; - goto test_char; - case REOP_char16: - val = get_u16(pc); - pc += 2; - goto test_char; - case REOP_char8: - val = get_u8(pc); - pc += 1; - test_char: - if (cptr >= cbuf_end) - goto no_match; - GET_CHAR(c, cptr, cbuf_end, cbuf_type); - if (s->ignore_case) { - c = lre_canonicalize(c, s->is_unicode); - } - if (val != c) - goto no_match; - break; - case REOP_split_goto_first: - case REOP_split_next_first: - { - const uint8_t *pc1; - - val = get_u32(pc); - pc += 4; - if (opcode == REOP_split_next_first) { - pc1 = pc + (int)val; - } else { - pc1 = pc; - pc = pc + (int)val; - } - ret = push_state(s, capture, stack, stack_len, - pc1, cptr, RE_EXEC_STATE_SPLIT, 0); - if (ret < 0) - return LRE_RET_MEMORY_ERROR; - break; - } - case REOP_lookahead: - case REOP_negative_lookahead: - val = get_u32(pc); - pc += 4; - ret = push_state(s, capture, stack, stack_len, - pc + (int)val, cptr, - RE_EXEC_STATE_LOOKAHEAD + opcode - REOP_lookahead, - 0); - if (ret < 0) - return LRE_RET_MEMORY_ERROR; - break; - - case REOP_goto: - val = get_u32(pc); - pc += 4 + (int)val; - if (lre_poll_timeout(s)) - return LRE_RET_TIMEOUT; - break; - case REOP_line_start: - if (cptr == s->cbuf) - break; - if (!s->multi_line) - goto no_match; - PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); - if (!is_line_terminator(c)) - goto no_match; - break; - case REOP_line_end: - if (cptr == cbuf_end) - break; - if (!s->multi_line) - goto no_match; - PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); - if (!is_line_terminator(c)) - goto no_match; - break; - case REOP_dot: - if (cptr == cbuf_end) - goto no_match; - GET_CHAR(c, cptr, cbuf_end, cbuf_type); - if (is_line_terminator(c)) - goto no_match; - break; - case REOP_any: - if (cptr == cbuf_end) - goto no_match; - GET_CHAR(c, cptr, cbuf_end, cbuf_type); - break; - case REOP_save_start: - case REOP_save_end: - val = *pc++; - assert(val < s->capture_count); - capture[2 * val + opcode - REOP_save_start] = (uint8_t *)cptr; - break; - case REOP_save_reset: - { - uint32_t val2; - val = pc[0]; - val2 = pc[1]; - pc += 2; - assert(val2 < s->capture_count); - while (val <= val2) { - capture[2 * val] = NULL; - capture[2 * val + 1] = NULL; - val++; - } - } - break; - case REOP_push_i32: - val = get_u32(pc); - pc += 4; - stack[stack_len++] = val; - break; - case REOP_drop: - stack_len--; - break; - case REOP_loop: - val = get_u32(pc); - pc += 4; - if (--stack[stack_len - 1] != 0) { - pc += (int)val; - if (lre_poll_timeout(s)) - return LRE_RET_TIMEOUT; - } - break; - case REOP_push_char_pos: - stack[stack_len++] = (uintptr_t)cptr; - break; - case REOP_check_advance: - if (stack[--stack_len] == (uintptr_t)cptr) - goto no_match; - break; - case REOP_word_boundary: - case REOP_not_word_boundary: - { - bool v1, v2; - /* char before */ - if (cptr == s->cbuf) { - v1 = false; - } else { - PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); - v1 = is_word_char(c); - } - /* current char */ - if (cptr >= cbuf_end) { - v2 = false; - } else { - PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); - v2 = is_word_char(c); - } - if (v1 ^ v2 ^ (REOP_not_word_boundary - opcode)) - goto no_match; - } - break; - case REOP_back_reference: - case REOP_backward_back_reference: - { - const uint8_t *cptr1, *cptr1_end, *cptr1_start; - uint32_t c1, c2; - - val = *pc++; - if (val >= s->capture_count) - goto no_match; - cptr1_start = capture[2 * val]; - cptr1_end = capture[2 * val + 1]; - if (!cptr1_start || !cptr1_end) - break; - if (opcode == REOP_back_reference) { - cptr1 = cptr1_start; - while (cptr1 < cptr1_end) { - if (cptr >= cbuf_end) - goto no_match; - GET_CHAR(c1, cptr1, cptr1_end, cbuf_type); - GET_CHAR(c2, cptr, cbuf_end, cbuf_type); - if (s->ignore_case) { - c1 = lre_canonicalize(c1, s->is_unicode); - c2 = lre_canonicalize(c2, s->is_unicode); - } - if (c1 != c2) - goto no_match; - } - } else { - cptr1 = cptr1_end; - while (cptr1 > cptr1_start) { - if (cptr == s->cbuf) - goto no_match; - GET_PREV_CHAR(c1, cptr1, cptr1_start, cbuf_type); - GET_PREV_CHAR(c2, cptr, s->cbuf, cbuf_type); - if (s->ignore_case) { - c1 = lre_canonicalize(c1, s->is_unicode); - c2 = lre_canonicalize(c2, s->is_unicode); - } - if (c1 != c2) - goto no_match; - } - } - } - break; - case REOP_range: - { - int n; - uint32_t low, high, idx_min, idx_max, idx; - - n = get_u16(pc); /* n must be >= 1 */ - pc += 2; - if (cptr >= cbuf_end) - goto no_match; - GET_CHAR(c, cptr, cbuf_end, cbuf_type); - if (s->ignore_case) { - c = lre_canonicalize(c, s->is_unicode); - } - idx_min = 0; - low = get_u16(pc + 0 * 4); - if (c < low) - goto no_match; - idx_max = n - 1; - high = get_u16(pc + idx_max * 4 + 2); - /* 0xffff in for last value means +infinity */ - if (unlikely(c >= 0xffff) && high == 0xffff) - goto range_match; - if (c > high) - goto no_match; - while (idx_min <= idx_max) { - idx = (idx_min + idx_max) / 2; - low = get_u16(pc + idx * 4); - high = get_u16(pc + idx * 4 + 2); - if (c < low) - idx_max = idx - 1; - else if (c > high) - idx_min = idx + 1; - else - goto range_match; - } - goto no_match; - range_match: - pc += 4 * n; - } - break; - case REOP_range32: - { - int n; - uint32_t low, high, idx_min, idx_max, idx; - - n = get_u16(pc); /* n must be >= 1 */ - pc += 2; - if (cptr >= cbuf_end) - goto no_match; - GET_CHAR(c, cptr, cbuf_end, cbuf_type); - if (s->ignore_case) { - c = lre_canonicalize(c, s->is_unicode); - } - idx_min = 0; - low = get_u32(pc + 0 * 8); - if (c < low) - goto no_match; - idx_max = n - 1; - high = get_u32(pc + idx_max * 8 + 4); - if (c > high) - goto no_match; - while (idx_min <= idx_max) { - idx = (idx_min + idx_max) / 2; - low = get_u32(pc + idx * 8); - high = get_u32(pc + idx * 8 + 4); - if (c < low) - idx_max = idx - 1; - else if (c > high) - idx_min = idx + 1; - else - goto range32_match; - } - goto no_match; - range32_match: - pc += 8 * n; - } - break; - case REOP_prev: - /* go to the previous char */ - if (cptr == s->cbuf) - goto no_match; - PREV_CHAR(cptr, s->cbuf, cbuf_type); - break; - case REOP_simple_greedy_quant: - { - uint32_t next_pos, quant_min, quant_max; - size_t q; - intptr_t res; - const uint8_t *pc1; - - next_pos = get_u32(pc); - quant_min = get_u32(pc + 4); - quant_max = get_u32(pc + 8); - pc += 16; - pc1 = pc; - pc += (int)next_pos; - - q = 0; - for(;;) { - if (lre_poll_timeout(s)) - return LRE_RET_TIMEOUT; - res = lre_exec_backtrack(s, capture, stack, stack_len, - pc1, cptr, true); - if (res == LRE_RET_MEMORY_ERROR || - res == LRE_RET_TIMEOUT) - return res; - if (!res) - break; - cptr = (uint8_t *)res; - q++; - if (q >= quant_max && quant_max != INT32_MAX) - break; - } - if (q < quant_min) - goto no_match; - if (q > quant_min) { - /* will examine all matches down to quant_min */ - ret = push_state(s, capture, stack, stack_len, - pc1 - 16, cptr, - RE_EXEC_STATE_GREEDY_QUANT, - q - quant_min); - if (ret < 0) - return LRE_RET_MEMORY_ERROR; - } - } - break; - default: - abort(); - } - } -} - -/* Return 1 if match, 0 if not match or < 0 if error (see LRE_RET_x). cindex is the - starting position of the match and must be such as 0 <= cindex <= - clen. */ -int lre_exec(uint8_t **capture, - const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, - int cbuf_type, void *opaque) -{ - REExecContext s_s, *s = &s_s; - int re_flags, i, alloca_size, ret; - StackInt *stack_buf; - - re_flags = lre_get_flags(bc_buf); - s->multi_line = (re_flags & LRE_FLAG_MULTILINE) != 0; - s->ignore_case = (re_flags & LRE_FLAG_IGNORECASE) != 0; - s->is_unicode = (re_flags & LRE_FLAG_UNICODE) != 0; - s->capture_count = bc_buf[RE_HEADER_CAPTURE_COUNT]; - s->stack_size_max = bc_buf[RE_HEADER_STACK_SIZE]; - s->cbuf = cbuf; - s->cbuf_end = cbuf + (clen << cbuf_type); - s->cbuf_type = cbuf_type; - if (s->cbuf_type == 1 && s->is_unicode) - s->cbuf_type = 2; - s->interrupt_counter = INTERRUPT_COUNTER_INIT; - s->opaque = opaque; - - s->state_size = sizeof(REExecState) + - s->capture_count * sizeof(capture[0]) * 2 + - s->stack_size_max * sizeof(stack_buf[0]); - s->state_stack = NULL; - s->state_stack_len = 0; - s->state_stack_size = 0; - - for(i = 0; i < s->capture_count * 2; i++) - capture[i] = NULL; - alloca_size = s->stack_size_max * sizeof(stack_buf[0]); - stack_buf = alloca(alloca_size); - ret = lre_exec_backtrack(s, capture, stack_buf, 0, bc_buf + RE_HEADER_LEN, - cbuf + (cindex << cbuf_type), false); - lre_realloc(s->opaque, s->state_stack, 0); - return ret; -} - -int lre_get_capture_count(const uint8_t *bc_buf) -{ - return bc_buf[RE_HEADER_CAPTURE_COUNT]; -} - -int lre_get_flags(const uint8_t *bc_buf) -{ - return get_u16(bc_buf + RE_HEADER_FLAGS); -} - -/* Return NULL if no group names. Otherwise, return a pointer to - 'capture_count - 1' zero terminated UTF-8 strings. */ -const char *lre_get_groupnames(const uint8_t *bc_buf) -{ - uint32_t re_bytecode_len; - if ((lre_get_flags(bc_buf) & LRE_FLAG_NAMED_GROUPS) == 0) - return NULL; - re_bytecode_len = get_u32(bc_buf + RE_HEADER_BYTECODE_LEN); - return (const char *)(bc_buf + RE_HEADER_LEN + re_bytecode_len); -} - -void lre_byte_swap(uint8_t *buf, size_t len, bool is_byte_swapped) -{ - uint8_t *p, *pe; - uint32_t n, r, nw; - - p = buf; - if (len < RE_HEADER_LEN) - abort(); - - // format is: - //
- // - // - // - // etc. - inplace_bswap16(&p[RE_HEADER_FLAGS]); - - n = get_u32(&p[RE_HEADER_BYTECODE_LEN]); - inplace_bswap32(&p[RE_HEADER_BYTECODE_LEN]); - if (is_byte_swapped) - n = bswap32(n); - if (n > len - RE_HEADER_LEN) - abort(); - - p = &buf[RE_HEADER_LEN]; - pe = &p[n]; - - while (p < pe) { - n = reopcode_info[*p].size; - switch (n) { - case 1: - case 2: - break; - case 3: - switch (*p) { - case REOP_save_reset: // has two 8 bit arguments - break; - case REOP_range32: // variable length - nw = get_u16(&p[1]); // number of pairs of uint32_t - if (is_byte_swapped) - n = bswap16(n); - for (r = 3 + 8 * nw; n < r; n += 4) - inplace_bswap32(&p[n]); - goto doswap16; - case REOP_range: // variable length - nw = get_u16(&p[1]); // number of pairs of uint16_t - if (is_byte_swapped) - n = bswap16(n); - for (r = 3 + 4 * nw; n < r; n += 2) - inplace_bswap16(&p[n]); - goto doswap16; - default: - doswap16: - inplace_bswap16(&p[1]); - break; - } - break; - case 5: - inplace_bswap32(&p[1]); - break; - case 17: - assert(*p == REOP_simple_greedy_quant); - inplace_bswap32(&p[1]); - inplace_bswap32(&p[5]); - inplace_bswap32(&p[9]); - inplace_bswap32(&p[13]); - break; - default: - abort(); - } - p = &p[n]; - } -} - -#ifdef TEST - -bool lre_check_stack_overflow(void *opaque, size_t alloca_size) -{ - return false; -} - -void *lre_realloc(void *opaque, void *ptr, size_t size) -{ - return realloc(ptr, size); -} - -int main(int argc, char **argv) -{ - int len, flags, ret, i; - uint8_t *bc; - char error_msg[64]; - uint8_t *capture[CAPTURE_COUNT_MAX * 2]; - const char *input; - int input_len, capture_count; - - if (argc < 4) { - printf("usage: %s regexp flags input\n", argv[0]); - exit(1); - } - flags = atoi(argv[2]); - bc = lre_compile(&len, error_msg, sizeof(error_msg), argv[1], - strlen(argv[1]), flags, NULL); - if (!bc) { - fprintf(stderr, "error: %s\n", error_msg); - exit(1); - } - - input = argv[3]; - input_len = strlen(input); - - ret = lre_exec(capture, bc, (uint8_t *)input, 0, input_len, 0, NULL); - printf("ret=%d\n", ret); - if (ret == 1) { - capture_count = lre_get_capture_count(bc); - for(i = 0; i < 2 * capture_count; i++) { - uint8_t *ptr; - ptr = capture[i]; - printf("%d: ", i); - if (!ptr) - printf(""); - else - printf("%u", (int)(ptr - (uint8_t *)input)); - printf("\n"); - } - } - return 0; -} -#endif diff --git a/vendor/quickjs/source_ng/libregexp.h b/vendor/quickjs/source_ng/libregexp.h deleted file mode 100755 index 898e9a7a3..000000000 --- a/vendor/quickjs/source_ng/libregexp.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Regular Expression Engine - * - * Copyright (c) 2017-2018 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef LIBREGEXP_H -#define LIBREGEXP_H - -#include -#include - -#include "libunicode.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define LRE_FLAG_GLOBAL (1 << 0) -#define LRE_FLAG_IGNORECASE (1 << 1) -#define LRE_FLAG_MULTILINE (1 << 2) -#define LRE_FLAG_DOTALL (1 << 3) -#define LRE_FLAG_UNICODE (1 << 4) -#define LRE_FLAG_STICKY (1 << 5) -#define LRE_FLAG_INDICES (1 << 6) /* Unused by libregexp, just recorded. */ -#define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */ -#define LRE_FLAG_UNICODE_SETS (1 << 8) - -#define LRE_RET_MEMORY_ERROR (-1) -#define LRE_RET_TIMEOUT (-2) - -uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, - const char *buf, size_t buf_len, int re_flags, - void *opaque); -int lre_get_capture_count(const uint8_t *bc_buf); -int lre_get_flags(const uint8_t *bc_buf); -const char *lre_get_groupnames(const uint8_t *bc_buf); -int lre_exec(uint8_t **capture, - const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, - int cbuf_type, void *opaque); - -int lre_parse_escape(const uint8_t **pp, int allow_utf16); -bool lre_is_space(int c); - -void lre_byte_swap(uint8_t *buf, size_t len, bool is_byte_swapped); - -/* must be provided by the user */ -bool lre_check_stack_overflow(void *opaque, size_t alloca_size); -/* must be provided by the user, return non zero if time out */ -int lre_check_timeout(void *opaque); -void *lre_realloc(void *opaque, void *ptr, size_t size); - -/* JS identifier test */ -extern uint32_t const lre_id_start_table_ascii[4]; -extern uint32_t const lre_id_continue_table_ascii[4]; - -static inline int lre_js_is_ident_first(int c) -{ - if ((uint32_t)c < 128) { - return (lre_id_start_table_ascii[c >> 5] >> (c & 31)) & 1; - } else { - return lre_is_id_start(c); - } -} - -static inline int lre_js_is_ident_next(int c) -{ - if ((uint32_t)c < 128) { - return (lre_id_continue_table_ascii[c >> 5] >> (c & 31)) & 1; - } else { - /* ZWNJ and ZWJ are accepted in identifiers */ - return lre_is_id_continue(c) || c == 0x200C || c == 0x200D; - } -} - -#ifdef __cplusplus -} /* extern "C" { */ -#endif - -#endif /* LIBREGEXP_H */ diff --git a/vendor/quickjs/source_ng/libunicode-table.h b/vendor/quickjs/source_ng/libunicode-table.h deleted file mode 100755 index b48a3a746..000000000 --- a/vendor/quickjs/source_ng/libunicode-table.h +++ /dev/null @@ -1,4658 +0,0 @@ -/* Compressed unicode tables */ -/* Automatically generated file - do not edit */ - -#include - -static const uint32_t case_conv_table1[378] = { - 0x00209a30, 0x00309a00, 0x005a8173, 0x00601730, - 0x006c0730, 0x006f81b3, 0x00701700, 0x007c0700, - 0x007f8100, 0x00803040, 0x009801c3, 0x00988190, - 0x00990640, 0x009c9040, 0x00a481b4, 0x00a52e40, - 0x00bc0130, 0x00bc8640, 0x00bf8170, 0x00c00100, - 0x00c08130, 0x00c10440, 0x00c30130, 0x00c38240, - 0x00c48230, 0x00c58240, 0x00c70130, 0x00c78130, - 0x00c80130, 0x00c88240, 0x00c98130, 0x00ca0130, - 0x00ca8100, 0x00cb0130, 0x00cb8130, 0x00cc0240, - 0x00cd0100, 0x00cd8101, 0x00ce0130, 0x00ce8130, - 0x00cf0100, 0x00cf8130, 0x00d00640, 0x00d30130, - 0x00d38240, 0x00d48130, 0x00d60240, 0x00d70130, - 0x00d78240, 0x00d88230, 0x00d98440, 0x00db8130, - 0x00dc0240, 0x00de0240, 0x00df8100, 0x00e20350, - 0x00e38350, 0x00e50350, 0x00e69040, 0x00ee8100, - 0x00ef1240, 0x00f801b4, 0x00f88350, 0x00fa0240, - 0x00fb0130, 0x00fb8130, 0x00fc2840, 0x01100130, - 0x01111240, 0x011d0131, 0x011d8240, 0x011e8130, - 0x011f0131, 0x011f8201, 0x01208240, 0x01218130, - 0x01220130, 0x01228130, 0x01230a40, 0x01280101, - 0x01288101, 0x01290101, 0x01298100, 0x012a0100, - 0x012b0200, 0x012c8100, 0x012d8100, 0x012e0101, - 0x01300100, 0x01308101, 0x01318100, 0x01320101, - 0x01328101, 0x01330101, 0x01340100, 0x01348100, - 0x01350101, 0x01358101, 0x01360101, 0x01378100, - 0x01388101, 0x01390100, 0x013a8100, 0x013e8101, - 0x01400100, 0x01410101, 0x01418100, 0x01438101, - 0x01440100, 0x01448100, 0x01450200, 0x01460100, - 0x01490100, 0x014e8101, 0x014f0101, 0x01a28173, - 0x01b80440, 0x01bb0240, 0x01bd8300, 0x01bf8130, - 0x01c30130, 0x01c40330, 0x01c60130, 0x01c70230, - 0x01c801d0, 0x01c89130, 0x01d18930, 0x01d60100, - 0x01d68300, 0x01d801d3, 0x01d89100, 0x01e10173, - 0x01e18900, 0x01e60100, 0x01e68200, 0x01e78130, - 0x01e80173, 0x01e88173, 0x01ea8173, 0x01eb0173, - 0x01eb8100, 0x01ec1840, 0x01f80173, 0x01f88173, - 0x01f90100, 0x01f98100, 0x01fa01a0, 0x01fa8173, - 0x01fb8240, 0x01fc8130, 0x01fd0240, 0x01fe8330, - 0x02001030, 0x02082030, 0x02182000, 0x02281000, - 0x02302240, 0x02453640, 0x02600130, 0x02608e40, - 0x02678100, 0x02686040, 0x0298a630, 0x02b0a600, - 0x02c381b5, 0x08502631, 0x08638131, 0x08668131, - 0x08682b00, 0x087e8300, 0x09d05011, 0x09f80610, - 0x09fc0620, 0x0e400174, 0x0e408174, 0x0e410174, - 0x0e418174, 0x0e420174, 0x0e428174, 0x0e430174, - 0x0e438180, 0x0e440180, 0x0e448240, 0x0e482b30, - 0x0e5e8330, 0x0ebc8101, 0x0ebe8101, 0x0ec70101, - 0x0f007e40, 0x0f3f1840, 0x0f4b01b5, 0x0f4b81b6, - 0x0f4c01b6, 0x0f4c81b6, 0x0f4d01b7, 0x0f4d8180, - 0x0f4f0130, 0x0f506040, 0x0f800800, 0x0f840830, - 0x0f880600, 0x0f8c0630, 0x0f900800, 0x0f940830, - 0x0f980800, 0x0f9c0830, 0x0fa00600, 0x0fa40630, - 0x0fa801b0, 0x0fa88100, 0x0fa901d3, 0x0fa98100, - 0x0faa01d3, 0x0faa8100, 0x0fab01d3, 0x0fab8100, - 0x0fac8130, 0x0fad8130, 0x0fae8130, 0x0faf8130, - 0x0fb00800, 0x0fb40830, 0x0fb80200, 0x0fb90400, - 0x0fbb0201, 0x0fbc0201, 0x0fbd0201, 0x0fbe0201, - 0x0fc008b7, 0x0fc40867, 0x0fc808b8, 0x0fcc0868, - 0x0fd008b8, 0x0fd40868, 0x0fd80200, 0x0fd901b9, - 0x0fd981b1, 0x0fda01b9, 0x0fdb01b1, 0x0fdb81d7, - 0x0fdc0230, 0x0fdd0230, 0x0fde0161, 0x0fdf0173, - 0x0fe101b9, 0x0fe181b2, 0x0fe201ba, 0x0fe301b2, - 0x0fe381d8, 0x0fe40430, 0x0fe60162, 0x0fe80201, - 0x0fe901d0, 0x0fe981d0, 0x0feb01b0, 0x0feb81d0, - 0x0fec0230, 0x0fed0230, 0x0ff00201, 0x0ff101d3, - 0x0ff181d3, 0x0ff201ba, 0x0ff28101, 0x0ff301b0, - 0x0ff381d3, 0x0ff40231, 0x0ff50230, 0x0ff60131, - 0x0ff901ba, 0x0ff981b2, 0x0ffa01bb, 0x0ffb01b2, - 0x0ffb81d9, 0x0ffc0230, 0x0ffd0230, 0x0ffe0162, - 0x109301a0, 0x109501a0, 0x109581a0, 0x10990131, - 0x10a70101, 0x10b01031, 0x10b81001, 0x10c18240, - 0x125b1a31, 0x12681a01, 0x16003031, 0x16183001, - 0x16300240, 0x16310130, 0x16318130, 0x16320130, - 0x16328100, 0x16330100, 0x16338640, 0x16368130, - 0x16370130, 0x16378130, 0x16380130, 0x16390240, - 0x163a8240, 0x163f0230, 0x16406440, 0x16758440, - 0x16790240, 0x16802600, 0x16938100, 0x16968100, - 0x53202e40, 0x53401c40, 0x53910e40, 0x53993e40, - 0x53bc8440, 0x53be8130, 0x53bf0a40, 0x53c58240, - 0x53c68130, 0x53c80440, 0x53ca0101, 0x53cb1440, - 0x53d50130, 0x53d58130, 0x53d60130, 0x53d68130, - 0x53d70130, 0x53d80130, 0x53d88130, 0x53d90130, - 0x53d98131, 0x53da1040, 0x53e20131, 0x53e28130, - 0x53e30130, 0x53e38440, 0x53e58130, 0x53e60240, - 0x53e80240, 0x53eb0640, 0x53ee0130, 0x53fa8240, - 0x55a98101, 0x55b85020, 0x7d8001b2, 0x7d8081b2, - 0x7d8101b2, 0x7d8181da, 0x7d8201da, 0x7d8281b3, - 0x7d8301b3, 0x7d8981bb, 0x7d8a01bb, 0x7d8a81bb, - 0x7d8b01bc, 0x7d8b81bb, 0x7f909a31, 0x7fa09a01, - 0x82002831, 0x82142801, 0x82582431, 0x826c2401, - 0x82b80b31, 0x82be0f31, 0x82c60731, 0x82ca0231, - 0x82cb8b01, 0x82d18f01, 0x82d98701, 0x82dd8201, - 0x86403331, 0x86603301, 0x86a81631, 0x86b81601, - 0x8c502031, 0x8c602001, 0xb7202031, 0xb7302001, - 0xf4802231, 0xf4912201, -}; - -static const uint8_t case_conv_table2[378] = { - 0x01, 0x00, 0x9c, 0x06, 0x07, 0x4d, 0x03, 0x04, - 0x10, 0x00, 0x8f, 0x0b, 0x00, 0x00, 0x11, 0x00, - 0x08, 0x00, 0x53, 0x4b, 0x52, 0x00, 0x53, 0x00, - 0x54, 0x00, 0x3b, 0x55, 0x56, 0x00, 0x58, 0x5a, - 0x40, 0x5f, 0x5e, 0x00, 0x47, 0x52, 0x63, 0x65, - 0x43, 0x66, 0x00, 0x68, 0x00, 0x6a, 0x00, 0x6c, - 0x00, 0x6e, 0x00, 0x70, 0x00, 0x00, 0x41, 0x00, - 0x00, 0x00, 0x00, 0x1a, 0x00, 0x93, 0x00, 0x00, - 0x20, 0x36, 0x00, 0x28, 0x00, 0x24, 0x00, 0x24, - 0x25, 0x2d, 0x00, 0x13, 0x6d, 0x6f, 0x00, 0x29, - 0x27, 0x2a, 0x14, 0x16, 0x18, 0x1b, 0x1c, 0x41, - 0x1e, 0x42, 0x1f, 0x4e, 0x3c, 0x40, 0x22, 0x21, - 0x44, 0x21, 0x43, 0x26, 0x28, 0x27, 0x29, 0x23, - 0x2b, 0x4b, 0x2d, 0x46, 0x2f, 0x4c, 0x31, 0x4d, - 0x33, 0x47, 0x45, 0x99, 0x00, 0x00, 0x97, 0x91, - 0x7f, 0x80, 0x85, 0x86, 0x12, 0x82, 0x84, 0x78, - 0x79, 0x12, 0x7d, 0xa3, 0x7e, 0x7a, 0x7b, 0x8c, - 0x92, 0x98, 0xa6, 0xa0, 0x87, 0x00, 0x9a, 0xa1, - 0x95, 0x77, 0x33, 0x95, 0x00, 0x90, 0x00, 0x76, - 0x9b, 0x9a, 0x99, 0x98, 0x00, 0x00, 0xa0, 0x00, - 0x9e, 0x00, 0xa3, 0xa2, 0x15, 0x31, 0x32, 0x33, - 0xb7, 0xb8, 0x55, 0xac, 0xab, 0x12, 0x14, 0x1e, - 0x21, 0x22, 0x22, 0x2a, 0x34, 0x35, 0x00, 0xa8, - 0xa9, 0x39, 0x22, 0x4c, 0x00, 0x00, 0x97, 0x01, - 0x5a, 0xda, 0x1d, 0x36, 0x05, 0x00, 0xc7, 0xc6, - 0xc9, 0xc8, 0xcb, 0xca, 0xcd, 0xcc, 0xcf, 0xce, - 0xc4, 0xd8, 0x45, 0xd9, 0x42, 0xda, 0x46, 0xdb, - 0xd1, 0xd3, 0xd5, 0xd7, 0xdd, 0xdc, 0xf1, 0xf9, - 0x01, 0x11, 0x0a, 0x12, 0x80, 0x9f, 0x00, 0x21, - 0x80, 0xa3, 0xf0, 0x00, 0xc0, 0x40, 0xc6, 0x60, - 0xea, 0xde, 0xe6, 0x99, 0xc0, 0x00, 0x00, 0x06, - 0x60, 0xdf, 0x29, 0x00, 0x15, 0x12, 0x06, 0x16, - 0xfb, 0xe0, 0x09, 0x15, 0x12, 0x84, 0x0b, 0xc6, - 0x16, 0x02, 0xe2, 0x06, 0xc0, 0x40, 0x00, 0x46, - 0x60, 0xe1, 0xe3, 0x6d, 0x37, 0x38, 0x39, 0x18, - 0x17, 0x1a, 0x19, 0x00, 0x1d, 0x1c, 0x1f, 0x1e, - 0x00, 0x61, 0xba, 0x67, 0x45, 0x48, 0x00, 0x50, - 0x64, 0x4f, 0x51, 0x00, 0x00, 0x49, 0x00, 0x00, - 0x00, 0xa5, 0xa6, 0xa7, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xb9, 0x00, 0x00, 0x5c, 0x00, 0x4a, 0x00, - 0x5d, 0x57, 0x59, 0x62, 0x60, 0x72, 0x6b, 0x71, - 0x54, 0x00, 0x3e, 0x69, 0xbb, 0x00, 0x5b, 0x00, - 0x00, 0x00, 0x25, 0x00, 0x48, 0xaa, 0x8a, 0x8b, - 0x8c, 0xab, 0xac, 0x58, 0x58, 0xaf, 0x94, 0xb0, - 0x6f, 0xb2, 0x63, 0x62, 0x65, 0x64, 0x67, 0x66, - 0x6c, 0x6d, 0x6e, 0x6f, 0x68, 0x69, 0x6a, 0x6b, - 0x71, 0x70, 0x73, 0x72, 0x75, 0x74, 0x77, 0x76, - 0x79, 0x78, -}; - -static const uint16_t case_conv_ext[58] = { - 0x0399, 0x0308, 0x0301, 0x03a5, 0x0313, 0x0300, 0x0342, 0x0391, - 0x0397, 0x03a9, 0x0046, 0x0049, 0x004c, 0x0053, 0x0069, 0x0307, - 0x02bc, 0x004e, 0x004a, 0x030c, 0x0535, 0x0552, 0x0048, 0x0331, - 0x0054, 0x0057, 0x030a, 0x0059, 0x0041, 0x02be, 0x1f08, 0x1f80, - 0x1f28, 0x1f90, 0x1f68, 0x1fa0, 0x1fba, 0x0386, 0x1fb3, 0x1fca, - 0x0389, 0x1fc3, 0x03a1, 0x1ffa, 0x038f, 0x1ff3, 0x0544, 0x0546, - 0x053b, 0x054e, 0x053d, 0x03b8, 0x0462, 0xa64a, 0x1e60, 0x03c9, - 0x006b, 0x00e5, -}; - -static const uint8_t unicode_prop_Cased1_table[193] = { - 0x40, 0xa9, 0x80, 0x8e, 0x80, 0xfc, 0x80, 0xd3, - 0x80, 0x9b, 0x81, 0x8d, 0x02, 0x80, 0xe1, 0x80, - 0x91, 0x85, 0x9a, 0x01, 0x00, 0x01, 0x11, 0x03, - 0x04, 0x08, 0x01, 0x08, 0x30, 0x08, 0x01, 0x15, - 0x20, 0x00, 0x39, 0x99, 0x31, 0x9d, 0x84, 0x40, - 0x94, 0x80, 0xd6, 0x82, 0xa6, 0x80, 0x41, 0x62, - 0x80, 0xa6, 0x80, 0x4b, 0x72, 0x80, 0x4c, 0x02, - 0xf8, 0x02, 0x80, 0x8f, 0x80, 0xb0, 0x40, 0xdb, - 0x08, 0x80, 0x41, 0xd0, 0x80, 0x8c, 0x80, 0x8f, - 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, 0x14, 0x28, - 0x10, 0x11, 0x02, 0x01, 0x18, 0x0b, 0x24, 0x4b, - 0x26, 0x01, 0x01, 0x86, 0xe5, 0x80, 0x60, 0x79, - 0xb6, 0x81, 0x40, 0x91, 0x81, 0xbd, 0x88, 0x94, - 0x05, 0x80, 0x98, 0x80, 0xa2, 0x00, 0x80, 0x9b, - 0x12, 0x82, 0x43, 0x34, 0xa2, 0x06, 0x80, 0x8d, - 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, 0x88, - 0x60, 0xcc, 0x44, 0xd4, 0x80, 0xc6, 0x01, 0x08, - 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, - 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, - 0x16, 0x80, 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, - 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, - 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, - 0x07, 0x47, 0x33, 0x89, 0x80, 0x93, 0x2d, 0x41, - 0x04, 0xbd, 0x50, 0xc1, 0x99, 0x85, 0x99, 0x85, - 0x99, -}; - -static const uint8_t unicode_prop_Cased1_index[18] = { - 0xb9, 0x02, 0x80, 0xa0, 0x1e, 0x40, 0x9e, 0xa6, - 0x40, 0xbb, 0x07, 0x01, 0xdb, 0xd6, 0x01, 0x8a, - 0xf1, 0x01, -}; - -static const uint8_t unicode_prop_Case_Ignorable_table[764] = { - 0xa6, 0x05, 0x80, 0x8a, 0x80, 0xa2, 0x00, 0x80, - 0xc6, 0x03, 0x00, 0x03, 0x01, 0x81, 0x41, 0xf6, - 0x40, 0xbf, 0x19, 0x18, 0x88, 0x08, 0x80, 0x40, - 0xfa, 0x86, 0x40, 0xce, 0x04, 0x80, 0xb0, 0xac, - 0x00, 0x01, 0x01, 0x00, 0xab, 0x80, 0x8a, 0x85, - 0x89, 0x8a, 0x00, 0xa2, 0x80, 0x89, 0x94, 0x8f, - 0x80, 0xe4, 0x38, 0x89, 0x03, 0xa0, 0x00, 0x80, - 0x9d, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0x18, 0x08, - 0x97, 0x97, 0xaa, 0x82, 0xab, 0x06, 0x0c, 0x88, - 0xa8, 0xb9, 0xb6, 0x00, 0x03, 0x3b, 0x02, 0x86, - 0x89, 0x81, 0x8c, 0x80, 0x8e, 0x80, 0xb9, 0x03, - 0x1f, 0x80, 0x93, 0x81, 0x99, 0x01, 0x81, 0xb8, - 0x03, 0x0b, 0x09, 0x12, 0x80, 0x9d, 0x0a, 0x80, - 0x8a, 0x81, 0xb8, 0x03, 0x20, 0x0b, 0x80, 0x93, - 0x81, 0x95, 0x28, 0x80, 0xb9, 0x01, 0x00, 0x1f, - 0x06, 0x81, 0x8a, 0x81, 0x9d, 0x80, 0xbc, 0x80, - 0x8b, 0x80, 0xb1, 0x02, 0x80, 0xb6, 0x00, 0x14, - 0x10, 0x1e, 0x81, 0x8a, 0x81, 0x9c, 0x80, 0xb9, - 0x01, 0x05, 0x04, 0x81, 0x93, 0x81, 0x9b, 0x81, - 0xb8, 0x0b, 0x1f, 0x80, 0x93, 0x81, 0x9c, 0x80, - 0xc7, 0x06, 0x10, 0x80, 0xd9, 0x01, 0x86, 0x8a, - 0x88, 0xe1, 0x01, 0x88, 0x88, 0x00, 0x86, 0xc8, - 0x81, 0x9a, 0x00, 0x00, 0x80, 0xb6, 0x8d, 0x04, - 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, 0x80, 0xe5, - 0x18, 0x28, 0x09, 0x81, 0x98, 0x0b, 0x82, 0x8f, - 0x83, 0x8c, 0x01, 0x0d, 0x80, 0x8e, 0x80, 0xdd, - 0x80, 0x42, 0x5f, 0x82, 0x43, 0xb1, 0x82, 0x9c, - 0x81, 0x9d, 0x81, 0x9d, 0x81, 0xbf, 0x08, 0x37, - 0x01, 0x8a, 0x10, 0x20, 0xac, 0x84, 0xb2, 0x80, - 0xc0, 0x81, 0xa1, 0x80, 0xf5, 0x13, 0x81, 0x88, - 0x05, 0x82, 0x40, 0xda, 0x09, 0x80, 0xb9, 0x00, - 0x30, 0x00, 0x01, 0x3d, 0x89, 0x08, 0xa6, 0x07, - 0x9e, 0xb0, 0x83, 0xaf, 0x00, 0x20, 0x04, 0x80, - 0xa7, 0x88, 0x8b, 0x81, 0x9f, 0x19, 0x08, 0x82, - 0xb7, 0x00, 0x0a, 0x00, 0x82, 0xb9, 0x39, 0x81, - 0xbf, 0x85, 0xd1, 0x10, 0x8c, 0x06, 0x18, 0x28, - 0x11, 0xb1, 0xbe, 0x8c, 0x80, 0xa1, 0xe4, 0x41, - 0xbc, 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, - 0x82, 0x8c, 0x81, 0x8b, 0x27, 0x81, 0x89, 0x01, - 0x01, 0x84, 0xb0, 0x20, 0x89, 0x00, 0x8c, 0x80, - 0x8f, 0x8c, 0xb2, 0xa0, 0x4b, 0x8a, 0x81, 0xf0, - 0x82, 0xfc, 0x80, 0x8e, 0x80, 0xdf, 0x9f, 0xae, - 0x80, 0x41, 0xd4, 0x80, 0xa3, 0x1a, 0x24, 0x80, - 0xdc, 0x85, 0xdc, 0x82, 0x60, 0x6f, 0x15, 0x80, - 0x44, 0xe1, 0x85, 0x41, 0x0d, 0x80, 0xe1, 0x18, - 0x89, 0x00, 0x9b, 0x83, 0xcf, 0x81, 0x8d, 0xa1, - 0xcd, 0x80, 0x96, 0x82, 0xe6, 0x12, 0x0f, 0x02, - 0x03, 0x80, 0x98, 0x0c, 0x80, 0x40, 0x96, 0x81, - 0x99, 0x91, 0x8c, 0x80, 0xa5, 0x87, 0x98, 0x8a, - 0xad, 0x82, 0xaf, 0x01, 0x19, 0x81, 0x90, 0x80, - 0x94, 0x81, 0xc1, 0x29, 0x09, 0x81, 0x8b, 0x07, - 0x80, 0xa2, 0x80, 0x8a, 0x80, 0xb2, 0x00, 0x11, - 0x0c, 0x08, 0x80, 0x9a, 0x80, 0x8d, 0x0c, 0x08, - 0x80, 0xe3, 0x84, 0x88, 0x82, 0xf8, 0x01, 0x03, - 0x80, 0x60, 0x4f, 0x2f, 0x80, 0x40, 0x92, 0x90, - 0x42, 0x3c, 0x8f, 0x10, 0x8b, 0x8f, 0xa1, 0x01, - 0x80, 0x40, 0xa8, 0x06, 0x05, 0x80, 0x8a, 0x80, - 0xa2, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, - 0x80, 0x94, 0x82, 0x42, 0x00, 0x80, 0x40, 0xe1, - 0x80, 0x40, 0x94, 0x84, 0x44, 0x04, 0x28, 0xa9, - 0x80, 0x88, 0x42, 0x45, 0x10, 0x0c, 0x83, 0xa7, - 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x83, - 0xa5, 0x80, 0x99, 0x20, 0x80, 0x41, 0x3a, 0x81, - 0xce, 0x83, 0xc5, 0x8a, 0xb0, 0x83, 0xfa, 0x80, - 0xb5, 0x8e, 0xa8, 0x01, 0x81, 0x89, 0x82, 0xb0, - 0x19, 0x09, 0x03, 0x80, 0x89, 0x80, 0xb1, 0x82, - 0xa3, 0x20, 0x87, 0xbd, 0x80, 0x8b, 0x81, 0xb3, - 0x88, 0x89, 0x19, 0x80, 0xde, 0x11, 0x00, 0x0d, - 0x01, 0x80, 0x40, 0x9c, 0x02, 0x87, 0x94, 0x81, - 0xb8, 0x0a, 0x80, 0xa4, 0x32, 0x84, 0xc5, 0x85, - 0x8c, 0x00, 0x00, 0x80, 0x8d, 0x81, 0xd4, 0x39, - 0x10, 0x80, 0x96, 0x80, 0xd3, 0x28, 0x03, 0x08, - 0x81, 0x40, 0xed, 0x1d, 0x08, 0x81, 0x9a, 0x81, - 0xd4, 0x39, 0x00, 0x81, 0xe9, 0x00, 0x01, 0x28, - 0x80, 0xe4, 0x00, 0x01, 0x18, 0x84, 0x41, 0x02, - 0x88, 0x01, 0x40, 0xff, 0x08, 0x03, 0x80, 0x40, - 0x8f, 0x19, 0x0b, 0x80, 0x9f, 0x89, 0xa7, 0x29, - 0x1f, 0x80, 0x88, 0x29, 0x82, 0xad, 0x8c, 0x01, - 0x41, 0x95, 0x30, 0x28, 0x80, 0xd1, 0x95, 0x0e, - 0x01, 0x01, 0xf9, 0x2a, 0x00, 0x08, 0x30, 0x80, - 0xc7, 0x0a, 0x00, 0x80, 0x41, 0x5a, 0x81, 0x8a, - 0x81, 0xb3, 0x24, 0x00, 0x80, 0x96, 0x80, 0x54, - 0xd4, 0x90, 0x85, 0x8e, 0x60, 0x2c, 0xc7, 0x8b, - 0x12, 0x49, 0xbf, 0x84, 0xba, 0x86, 0x88, 0x83, - 0x41, 0xfb, 0x82, 0xa7, 0x81, 0x41, 0xe1, 0x80, - 0xbe, 0x90, 0xbf, 0x08, 0x81, 0x60, 0x40, 0x0a, - 0x18, 0x30, 0x81, 0x4c, 0x9d, 0x08, 0x83, 0x52, - 0x5b, 0xad, 0x81, 0x96, 0x42, 0x1f, 0x82, 0x88, - 0x8f, 0x0e, 0x9d, 0x83, 0x40, 0x93, 0x82, 0x47, - 0xba, 0xb6, 0x83, 0xb1, 0x38, 0x8d, 0x80, 0x95, - 0x20, 0x8e, 0x45, 0x4f, 0x30, 0x90, 0x0e, 0x01, - 0x04, 0x84, 0xbd, 0xa0, 0x80, 0x40, 0x9f, 0x8d, - 0x41, 0x6f, 0x80, 0xbc, 0x83, 0x41, 0xfa, 0x84, - 0x40, 0xfd, 0x81, 0x42, 0xdf, 0x86, 0xec, 0x87, - 0x4a, 0xae, 0x84, 0x6c, 0x0c, 0x00, 0x80, 0x9d, - 0xdf, 0xff, 0x40, 0xef, -}; - -static const uint8_t unicode_prop_Case_Ignorable_index[72] = { - 0xbe, 0x05, 0x00, 0xfe, 0x07, 0x00, 0x52, 0x0a, - 0xa0, 0xc1, 0x0b, 0x00, 0x82, 0x0d, 0x00, 0x3f, - 0x10, 0x80, 0xd4, 0x17, 0x40, 0xcf, 0x1a, 0x20, - 0xf5, 0x1c, 0x00, 0x80, 0x20, 0x00, 0x16, 0xa0, - 0x00, 0xc6, 0xa8, 0x00, 0xc2, 0xaa, 0x60, 0x56, - 0xfe, 0x20, 0xb1, 0x07, 0x01, 0x02, 0x10, 0x01, - 0x42, 0x12, 0x41, 0xc4, 0x14, 0x21, 0xe1, 0x19, - 0x81, 0x48, 0x1d, 0x01, 0x44, 0x6b, 0x01, 0x83, - 0xd1, 0x21, 0x3e, 0xe1, 0x01, 0xf0, 0x01, 0x0e, -}; - -static const uint8_t unicode_prop_ID_Start_table[1133] = { - 0xc0, 0x99, 0x85, 0x99, 0xae, 0x80, 0x89, 0x03, - 0x04, 0x96, 0x80, 0x9e, 0x80, 0x41, 0xc9, 0x83, - 0x8b, 0x8d, 0x26, 0x00, 0x80, 0x40, 0x80, 0x20, - 0x09, 0x18, 0x05, 0x00, 0x10, 0x00, 0x93, 0x80, - 0xd2, 0x80, 0x40, 0x8a, 0x87, 0x40, 0xa5, 0x80, - 0xa5, 0x08, 0x85, 0xa8, 0xc6, 0x9a, 0x1b, 0xac, - 0xaa, 0xa2, 0x08, 0xe2, 0x00, 0x8e, 0x0e, 0x81, - 0x89, 0x11, 0x80, 0x8f, 0x00, 0x9d, 0x9c, 0xd8, - 0x8a, 0x80, 0x97, 0xa0, 0x88, 0x0b, 0x04, 0x95, - 0x18, 0x88, 0x02, 0x80, 0x96, 0x98, 0x86, 0x8a, - 0x84, 0x97, 0x05, 0x90, 0xa9, 0xb9, 0xb5, 0x10, - 0x91, 0x06, 0x89, 0x8e, 0x8f, 0x1f, 0x09, 0x81, - 0x95, 0x06, 0x00, 0x13, 0x10, 0x8f, 0x80, 0x8c, - 0x08, 0x82, 0x8d, 0x81, 0x89, 0x07, 0x2b, 0x09, - 0x95, 0x06, 0x01, 0x01, 0x01, 0x9e, 0x18, 0x80, - 0x92, 0x82, 0x8f, 0x88, 0x02, 0x80, 0x95, 0x06, - 0x01, 0x04, 0x10, 0x91, 0x80, 0x8e, 0x81, 0x96, - 0x80, 0x8a, 0x39, 0x09, 0x95, 0x06, 0x01, 0x04, - 0x10, 0x9d, 0x08, 0x82, 0x8e, 0x80, 0x90, 0x00, - 0x2a, 0x10, 0x1a, 0x08, 0x00, 0x0a, 0x0a, 0x12, - 0x8b, 0x95, 0x80, 0xb3, 0x38, 0x10, 0x96, 0x80, - 0x8f, 0x10, 0x99, 0x11, 0x01, 0x81, 0x9d, 0x03, - 0x38, 0x10, 0x96, 0x80, 0x89, 0x04, 0x10, 0x9e, - 0x08, 0x81, 0x8e, 0x81, 0x90, 0x88, 0x02, 0x80, - 0xa8, 0x08, 0x8f, 0x04, 0x17, 0x82, 0x97, 0x2c, - 0x91, 0x82, 0x97, 0x80, 0x88, 0x00, 0x0e, 0xb9, - 0xaf, 0x01, 0x8b, 0x86, 0xb9, 0x08, 0x00, 0x20, - 0x97, 0x00, 0x80, 0x89, 0x01, 0x88, 0x01, 0x20, - 0x80, 0x94, 0x83, 0x9f, 0x80, 0xbe, 0x38, 0xa3, - 0x9a, 0x84, 0xf2, 0xaa, 0x93, 0x80, 0x8f, 0x2b, - 0x1a, 0x02, 0x0e, 0x13, 0x8c, 0x8b, 0x80, 0x90, - 0xa5, 0x00, 0x20, 0x81, 0xaa, 0x80, 0x41, 0x4c, - 0x03, 0x0e, 0x00, 0x03, 0x81, 0xa8, 0x03, 0x81, - 0xa0, 0x03, 0x0e, 0x00, 0x03, 0x81, 0x8e, 0x80, - 0xb8, 0x03, 0x81, 0xc2, 0xa4, 0x8f, 0x8f, 0xd5, - 0x0d, 0x82, 0x42, 0x6b, 0x81, 0x90, 0x80, 0x99, - 0x84, 0xca, 0x82, 0x8a, 0x86, 0x91, 0x8c, 0x92, - 0x8d, 0x91, 0x8d, 0x8c, 0x02, 0x8e, 0xb3, 0xa2, - 0x03, 0x80, 0xc2, 0xd8, 0x86, 0xa8, 0x00, 0x84, - 0xc5, 0x89, 0x9e, 0xb0, 0x9d, 0x0c, 0x8a, 0xab, - 0x83, 0x99, 0xb5, 0x96, 0x88, 0xb4, 0xd1, 0x80, - 0xdc, 0xae, 0x90, 0x87, 0xb5, 0x9d, 0x8c, 0x81, - 0x89, 0xab, 0x99, 0xa3, 0xa8, 0x82, 0x89, 0xa3, - 0x81, 0x8a, 0x84, 0xaa, 0x0a, 0xa8, 0x18, 0x28, - 0x0a, 0x04, 0x40, 0xbf, 0xbf, 0x41, 0x15, 0x0d, - 0x81, 0xa5, 0x0d, 0x0f, 0x00, 0x00, 0x00, 0x80, - 0x9e, 0x81, 0xb4, 0x06, 0x00, 0x12, 0x06, 0x13, - 0x0d, 0x83, 0x8c, 0x22, 0x06, 0xf3, 0x80, 0x8c, - 0x80, 0x8f, 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, - 0x0d, 0x28, 0x00, 0x00, 0x80, 0x8f, 0x0b, 0x24, - 0x18, 0x90, 0xa8, 0x4a, 0x76, 0x40, 0xe4, 0x2b, - 0x11, 0x8b, 0xa5, 0x00, 0x20, 0x81, 0xb7, 0x30, - 0x8f, 0x96, 0x88, 0x30, 0x30, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x86, 0x42, 0x25, 0x82, 0x98, 0x88, - 0x34, 0x0c, 0x83, 0xd5, 0x1c, 0x80, 0xd9, 0x03, - 0x84, 0xaa, 0x80, 0xdd, 0x90, 0x9f, 0xaf, 0x8f, - 0x41, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x56, 0x8c, - 0xc2, 0xad, 0x81, 0x41, 0x0c, 0x82, 0x8f, 0x89, - 0x81, 0x93, 0xae, 0x8f, 0x9e, 0x81, 0xcf, 0xa6, - 0x88, 0x81, 0xe6, 0x81, 0xc2, 0x09, 0x00, 0x07, - 0x94, 0x8f, 0x02, 0x03, 0x80, 0x96, 0x9c, 0xb3, - 0x8d, 0xb1, 0xbd, 0x2a, 0x00, 0x81, 0x8a, 0x9b, - 0x89, 0x96, 0x98, 0x9c, 0x86, 0xae, 0x9b, 0x80, - 0x8f, 0x20, 0x89, 0x89, 0x20, 0xa8, 0x96, 0x10, - 0x87, 0x93, 0x96, 0x10, 0x82, 0xb1, 0x00, 0x11, - 0x0c, 0x08, 0x00, 0x97, 0x11, 0x8a, 0x32, 0x8b, - 0x29, 0x29, 0x85, 0x88, 0x30, 0x30, 0xaa, 0x80, - 0x8d, 0x85, 0xf2, 0x9c, 0x60, 0x2b, 0xa3, 0x8b, - 0x96, 0x83, 0xb0, 0x60, 0x21, 0x03, 0x41, 0x6d, - 0x81, 0xe9, 0xa5, 0x86, 0x8b, 0x24, 0x00, 0x89, - 0x80, 0x8c, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, - 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, - 0x8b, 0xf3, 0x20, 0x40, 0x86, 0xa3, 0x99, 0x85, - 0x99, 0x8a, 0xd8, 0x15, 0x0d, 0x0d, 0x0a, 0xa2, - 0x8b, 0x80, 0x99, 0x80, 0x92, 0x01, 0x80, 0x8e, - 0x81, 0x8d, 0xa1, 0xfa, 0xc4, 0xb4, 0x41, 0x0a, - 0x9c, 0x82, 0xb0, 0xae, 0x9f, 0x8c, 0x9d, 0x84, - 0xa5, 0x89, 0x9d, 0x81, 0xa3, 0x1f, 0x04, 0xa9, - 0x40, 0x9d, 0x91, 0xa3, 0x83, 0xa3, 0x83, 0xa7, - 0x87, 0xb3, 0x8b, 0x8a, 0x80, 0x8e, 0x06, 0x01, - 0x80, 0x8a, 0x80, 0x8e, 0x06, 0x01, 0x82, 0xb3, - 0x8b, 0x41, 0x36, 0x88, 0x95, 0x89, 0x87, 0x97, - 0x28, 0xa9, 0x80, 0x88, 0xc4, 0x29, 0x00, 0xab, - 0x01, 0x10, 0x81, 0x96, 0x89, 0x96, 0x88, 0x9e, - 0xc0, 0x92, 0x01, 0x89, 0x95, 0x89, 0x99, 0xc5, - 0xb7, 0x29, 0xbf, 0x80, 0x8e, 0x18, 0x10, 0x9c, - 0xa9, 0x9c, 0x82, 0x9c, 0xa2, 0x38, 0x9b, 0x9a, - 0xb5, 0x89, 0x95, 0x89, 0x92, 0x8c, 0x91, 0xed, - 0xc8, 0xb6, 0xb2, 0x8c, 0xb2, 0x8c, 0xa3, 0xa5, - 0x9b, 0x88, 0x96, 0x40, 0xf9, 0xa9, 0x29, 0x8f, - 0x82, 0xba, 0x9c, 0x89, 0x07, 0x95, 0xa9, 0x91, - 0xad, 0x94, 0x9a, 0x96, 0x8b, 0xb4, 0xb8, 0x09, - 0x80, 0x8c, 0xac, 0x9f, 0x98, 0x99, 0xa3, 0x9c, - 0x01, 0x07, 0xa2, 0x10, 0x8b, 0xaf, 0x8d, 0x83, - 0x94, 0x00, 0x80, 0xa2, 0x91, 0x80, 0x98, 0x92, - 0x81, 0xbe, 0x30, 0x00, 0x18, 0x8e, 0x80, 0x89, - 0x86, 0xae, 0xa5, 0x39, 0x09, 0x95, 0x06, 0x01, - 0x04, 0x10, 0x91, 0x80, 0x8b, 0x84, 0x9d, 0x89, - 0x00, 0x08, 0x80, 0xa5, 0x00, 0x98, 0x00, 0x80, - 0xab, 0xb4, 0x91, 0x83, 0x93, 0x82, 0x9d, 0xaf, - 0x93, 0x08, 0x80, 0x40, 0xb7, 0xae, 0xa8, 0x83, - 0xa3, 0xaf, 0x93, 0x80, 0xba, 0xaa, 0x8c, 0x80, - 0xc6, 0x9a, 0xa4, 0x86, 0x40, 0xb8, 0xab, 0xf3, - 0xbf, 0x9e, 0x39, 0x01, 0x38, 0x08, 0x97, 0x8e, - 0x00, 0x80, 0xdd, 0x39, 0xa6, 0x8f, 0x00, 0x80, - 0x9b, 0x80, 0x89, 0xa7, 0x30, 0x94, 0x80, 0x8a, - 0xad, 0x92, 0x80, 0x91, 0xc8, 0x40, 0xc6, 0xa0, - 0x9e, 0x88, 0x80, 0xa4, 0x90, 0x80, 0xb0, 0x9d, - 0xef, 0x30, 0x08, 0xa5, 0x94, 0x80, 0x98, 0x28, - 0x08, 0x9f, 0x8d, 0x80, 0x41, 0x46, 0x92, 0x8e, - 0x00, 0x8c, 0x80, 0xa1, 0xfb, 0x80, 0xce, 0x43, - 0x99, 0xe5, 0xee, 0x90, 0x40, 0xc3, 0x4a, 0x4b, - 0xe0, 0x8e, 0x44, 0x2f, 0x90, 0x85, 0x98, 0x4f, - 0x9a, 0x84, 0x42, 0x46, 0x5a, 0xb8, 0x9d, 0x46, - 0xe1, 0x42, 0x38, 0x86, 0x9e, 0x90, 0xce, 0x90, - 0x9d, 0x91, 0xaf, 0x8f, 0x83, 0x9e, 0x94, 0x84, - 0x92, 0x41, 0xaf, 0xac, 0x40, 0xd2, 0xbf, 0xff, - 0xca, 0x20, 0xc1, 0x8c, 0xbf, 0x08, 0x80, 0x9b, - 0x57, 0xf7, 0x87, 0x44, 0xd5, 0xa8, 0x89, 0x60, - 0x22, 0xe6, 0x18, 0x30, 0x08, 0x41, 0x22, 0x8e, - 0x80, 0x9c, 0x11, 0x80, 0x8d, 0x1f, 0x41, 0x8b, - 0x49, 0x03, 0xea, 0x84, 0x8c, 0x82, 0x88, 0x86, - 0x89, 0x57, 0x65, 0xd4, 0x80, 0xc6, 0x01, 0x08, - 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, - 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, - 0x16, 0x80, 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, - 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, - 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, - 0x07, 0x47, 0x33, 0x9e, 0x2d, 0x41, 0x04, 0xbd, - 0x40, 0x91, 0xac, 0x89, 0x86, 0x8f, 0x80, 0x41, - 0x40, 0x9d, 0x91, 0xab, 0x41, 0xe3, 0x9b, 0x40, - 0xe3, 0x9d, 0x08, 0x41, 0xee, 0x30, 0x18, 0x08, - 0x8e, 0x80, 0x40, 0xc4, 0xba, 0xc3, 0x30, 0x44, - 0xb3, 0x18, 0x9a, 0x01, 0x00, 0x08, 0x80, 0x89, - 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, 0x00, 0x02, - 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, 0x80, 0x89, - 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, 0x51, 0x43, - 0x60, 0xa6, 0xdf, 0x9f, 0x50, 0x39, 0x85, 0x40, - 0xdd, 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x8e, - 0x42, 0x6d, 0x49, 0xa1, 0x42, 0x1d, 0x45, 0xe1, - 0x53, 0x4a, 0x84, 0x50, 0x5f, -}; - -static const uint8_t unicode_prop_ID_Start_index[108] = { - 0xf6, 0x03, 0x20, 0xa6, 0x07, 0x00, 0xa9, 0x09, - 0x20, 0xb1, 0x0a, 0x00, 0xba, 0x0b, 0x20, 0x3b, - 0x0d, 0x20, 0xc7, 0x0e, 0x20, 0x49, 0x12, 0x00, - 0x9b, 0x16, 0x00, 0xac, 0x19, 0x00, 0xc0, 0x1d, - 0x80, 0x80, 0x20, 0x20, 0x70, 0x2d, 0x00, 0x00, - 0x32, 0x00, 0xdd, 0xa7, 0x00, 0x4c, 0xaa, 0x20, - 0xc7, 0xd7, 0x20, 0xfc, 0xfd, 0x20, 0x9d, 0x02, - 0x21, 0x96, 0x05, 0x01, 0x9f, 0x08, 0x01, 0x49, - 0x0c, 0x21, 0x76, 0x10, 0x21, 0xa9, 0x12, 0x01, - 0xb0, 0x14, 0x01, 0x42, 0x19, 0x41, 0x90, 0x1c, - 0x01, 0xf1, 0x2f, 0x21, 0x90, 0x6b, 0x21, 0x33, - 0xb1, 0x21, 0x06, 0xd5, 0x01, 0xc3, 0xd7, 0x01, - 0xff, 0xe7, 0x21, 0x63, 0xee, 0x01, 0x5e, 0xee, - 0x42, 0xb0, 0x23, 0x03, -}; - -static const uint8_t unicode_prop_ID_Continue1_table[695] = { - 0xaf, 0x89, 0xa4, 0x80, 0xd6, 0x80, 0x42, 0x47, - 0xef, 0x96, 0x80, 0x40, 0xfa, 0x84, 0x41, 0x08, - 0xac, 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, - 0x9e, 0x28, 0xe4, 0x31, 0x29, 0x08, 0x19, 0x89, - 0x96, 0x80, 0x9d, 0x9a, 0xda, 0x8a, 0x8e, 0x89, - 0xa0, 0x88, 0x88, 0x80, 0x97, 0x18, 0x88, 0x02, - 0x04, 0xaa, 0x82, 0xba, 0x88, 0xa9, 0x97, 0x80, - 0xa0, 0xb5, 0x10, 0x91, 0x06, 0x89, 0x09, 0x89, - 0x90, 0x82, 0xb7, 0x00, 0x31, 0x09, 0x82, 0x88, - 0x80, 0x89, 0x09, 0x89, 0x8d, 0x01, 0x82, 0xb7, - 0x00, 0x23, 0x09, 0x12, 0x80, 0x93, 0x8b, 0x10, - 0x8a, 0x82, 0xb7, 0x00, 0x38, 0x10, 0x82, 0x93, - 0x09, 0x89, 0x89, 0x28, 0x82, 0xb7, 0x00, 0x31, - 0x09, 0x16, 0x82, 0x89, 0x09, 0x89, 0x91, 0x80, - 0xba, 0x22, 0x10, 0x83, 0x88, 0x80, 0x8d, 0x89, - 0x8f, 0x84, 0xb6, 0x00, 0x30, 0x10, 0x1e, 0x81, - 0x8a, 0x09, 0x89, 0x90, 0x82, 0xb7, 0x00, 0x30, - 0x10, 0x1e, 0x81, 0x8a, 0x09, 0x89, 0x10, 0x8b, - 0x83, 0xb6, 0x08, 0x30, 0x10, 0x83, 0x88, 0x80, - 0x89, 0x09, 0x89, 0x90, 0x82, 0xc5, 0x03, 0x28, - 0x00, 0x3d, 0x89, 0x09, 0xbc, 0x01, 0x86, 0x8b, - 0x38, 0x89, 0xd6, 0x01, 0x88, 0x8a, 0x30, 0x89, - 0xbd, 0x0d, 0x89, 0x8a, 0x00, 0x00, 0x03, 0x81, - 0xb0, 0x93, 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, - 0x80, 0xe3, 0x93, 0x80, 0x89, 0x8b, 0x1b, 0x10, - 0x11, 0x32, 0x83, 0x8c, 0x8b, 0x80, 0x8e, 0x42, - 0xbe, 0x82, 0x88, 0x88, 0x43, 0x9f, 0x83, 0x9b, - 0x82, 0x9c, 0x81, 0x9d, 0x81, 0xbf, 0x9f, 0x88, - 0x01, 0x89, 0xa0, 0x10, 0x8a, 0x40, 0x8e, 0x80, - 0xf5, 0x8b, 0x83, 0x8b, 0x89, 0x89, 0xff, 0x8a, - 0xbb, 0x84, 0xb8, 0x89, 0x80, 0x9c, 0x81, 0x8a, - 0x85, 0x89, 0x95, 0x8d, 0x80, 0x8f, 0xb0, 0x84, - 0xae, 0x90, 0x8a, 0x89, 0x90, 0x88, 0x8b, 0x82, - 0x9d, 0x8c, 0x81, 0x89, 0xab, 0x8d, 0xaf, 0x93, - 0x87, 0x89, 0x85, 0x89, 0xf5, 0x10, 0x94, 0x18, - 0x28, 0x0a, 0x40, 0xc5, 0xbf, 0x42, 0x0b, 0x81, - 0xb0, 0x81, 0x92, 0x80, 0xfa, 0x8c, 0x18, 0x82, - 0x8b, 0x4b, 0xfd, 0x82, 0x40, 0x8c, 0x80, 0xdf, - 0x9f, 0x42, 0x29, 0x85, 0xe8, 0x81, 0xdf, 0x80, - 0x60, 0x75, 0x23, 0x89, 0xc4, 0x03, 0x89, 0x9f, - 0x81, 0xcf, 0x81, 0x41, 0x0f, 0x02, 0x03, 0x80, - 0x96, 0x23, 0x80, 0xd2, 0x81, 0xb1, 0x91, 0x89, - 0x89, 0x85, 0x91, 0x8c, 0x8a, 0x9b, 0x87, 0x98, - 0x8c, 0xab, 0x83, 0xae, 0x8d, 0x8e, 0x89, 0x8a, - 0x80, 0x89, 0x89, 0xae, 0x8d, 0x8b, 0x07, 0x09, - 0x89, 0xa0, 0x82, 0xb1, 0x00, 0x11, 0x0c, 0x08, - 0x80, 0xa8, 0x24, 0x81, 0x40, 0xeb, 0x38, 0x09, - 0x89, 0x60, 0x4f, 0x23, 0x80, 0x42, 0xe0, 0x8f, - 0x8f, 0x8f, 0x11, 0x97, 0x82, 0x40, 0xbf, 0x89, - 0xa4, 0x80, 0xa4, 0x80, 0x42, 0x96, 0x80, 0x40, - 0xe1, 0x80, 0x40, 0x94, 0x84, 0x41, 0x24, 0x89, - 0x45, 0x56, 0x10, 0x0c, 0x83, 0xa7, 0x13, 0x80, - 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x1f, 0x89, 0x85, - 0x89, 0x9e, 0x84, 0x41, 0x3c, 0x81, 0xce, 0x83, - 0xc5, 0x8a, 0xb0, 0x83, 0xf9, 0x82, 0xb4, 0x8e, - 0x9e, 0x8a, 0x09, 0x89, 0x83, 0xac, 0x8a, 0x30, - 0xac, 0x89, 0x2a, 0xa3, 0x8d, 0x80, 0x89, 0x21, - 0xab, 0x80, 0x8b, 0x82, 0xaf, 0x8d, 0x3b, 0x80, - 0x8b, 0xd1, 0x8b, 0x28, 0x08, 0x40, 0x9c, 0x8b, - 0x84, 0x89, 0x2b, 0xb6, 0x08, 0x31, 0x09, 0x82, - 0x88, 0x80, 0x89, 0x09, 0x32, 0x84, 0xc2, 0x88, - 0x00, 0x08, 0x03, 0x04, 0x00, 0x8d, 0x81, 0xd1, - 0x91, 0x88, 0x89, 0x18, 0xd0, 0x93, 0x8b, 0x89, - 0x40, 0xd4, 0x31, 0x88, 0x9a, 0x81, 0xd1, 0x90, - 0x8e, 0x89, 0xd0, 0x8c, 0x87, 0x89, 0x85, 0x93, - 0xb8, 0x8e, 0x83, 0x89, 0x40, 0xf1, 0x8e, 0x40, - 0xa4, 0x89, 0xc5, 0x28, 0x09, 0x18, 0x00, 0x81, - 0x8b, 0x89, 0xf6, 0x31, 0x32, 0x80, 0x9b, 0x89, - 0xa7, 0x30, 0x1f, 0x80, 0x88, 0x8a, 0xad, 0x8f, - 0x41, 0x55, 0x89, 0xb4, 0x38, 0x87, 0x8f, 0x89, - 0xb7, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, - 0x30, 0x07, 0x89, 0xaf, 0x20, 0x08, 0x27, 0x89, - 0x41, 0x48, 0x83, 0x88, 0x08, 0x80, 0xaf, 0x32, - 0x84, 0x8c, 0x8a, 0x54, 0xe4, 0x05, 0x8e, 0x60, - 0x2c, 0xc7, 0x9b, 0x49, 0x25, 0x89, 0xd5, 0x89, - 0xa5, 0x84, 0xba, 0x86, 0x98, 0x89, 0x42, 0x15, - 0x89, 0x41, 0xd4, 0x00, 0xb6, 0x33, 0xd0, 0x80, - 0x8a, 0x81, 0x60, 0x4c, 0xaa, 0x81, 0x50, 0x50, - 0x89, 0x42, 0x05, 0xad, 0x81, 0x96, 0x42, 0x1d, - 0x22, 0x2f, 0x39, 0x86, 0x9d, 0x83, 0x40, 0x93, - 0x82, 0x45, 0x88, 0xb1, 0x41, 0xff, 0xb6, 0x83, - 0xb1, 0x38, 0x8d, 0x80, 0x95, 0x20, 0x8e, 0x45, - 0x4f, 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, 0x80, - 0x40, 0x9f, 0x86, 0x88, 0x89, 0x41, 0x63, 0x80, - 0xbc, 0x8d, 0x41, 0xf1, 0x8d, 0x40, 0xf3, 0x08, - 0x89, 0x42, 0xd4, 0x86, 0xec, 0x34, 0x89, 0x52, - 0x95, 0x89, 0x6c, 0x05, 0x05, 0x40, 0xef, -}; - -static const uint8_t unicode_prop_ID_Continue1_index[66] = { - 0xfa, 0x06, 0x00, 0x70, 0x09, 0x00, 0xf0, 0x0a, - 0x40, 0x57, 0x0c, 0x00, 0xf0, 0x0d, 0x60, 0xc7, - 0x0f, 0x20, 0xea, 0x17, 0x40, 0x05, 0x1b, 0x00, - 0x0e, 0x20, 0x00, 0xa0, 0xa6, 0x20, 0xe6, 0xa9, - 0x20, 0x10, 0xfe, 0x00, 0x40, 0x0a, 0x01, 0xc3, - 0x10, 0x01, 0x4e, 0x13, 0x01, 0x41, 0x16, 0x01, - 0x0b, 0x1a, 0x01, 0xaa, 0x1d, 0x01, 0x7a, 0x6d, - 0x21, 0x45, 0xd2, 0x21, 0xaf, 0xe2, 0x01, 0xf0, - 0x01, 0x0e, -}; - -static const uint8_t unicode_prop_White_Space_table[22] = { - 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x99, 0x80, - 0x55, 0xde, 0x80, 0x49, 0x7e, 0x8a, 0x9c, 0x0c, - 0x80, 0xae, 0x80, 0x4f, 0x9f, 0x80, -}; - -static const uint8_t unicode_prop_White_Space_index[3] = { - 0x01, 0x30, 0x00, -}; - -static const uint8_t unicode_cc_table[916] = { - 0xb2, 0xcf, 0xd4, 0x00, 0xe8, 0x03, 0xdc, 0x00, - 0xe8, 0x00, 0xd8, 0x04, 0xdc, 0x01, 0xca, 0x03, - 0xdc, 0x01, 0xca, 0x0a, 0xdc, 0x04, 0x01, 0x03, - 0xdc, 0xc7, 0x00, 0xf0, 0xc0, 0x02, 0xdc, 0xc2, - 0x01, 0xdc, 0x80, 0xc2, 0x03, 0xdc, 0xc0, 0x00, - 0xe8, 0x01, 0xdc, 0xc0, 0x41, 0xe9, 0x00, 0xea, - 0x41, 0xe9, 0x00, 0xea, 0x00, 0xe9, 0xcc, 0xb0, - 0xe2, 0xc4, 0xb0, 0xd8, 0x00, 0xdc, 0xc3, 0x00, - 0xdc, 0xc2, 0x00, 0xde, 0x00, 0xdc, 0xc5, 0x05, - 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xde, 0x00, - 0xe4, 0xc0, 0x49, 0x0a, 0x43, 0x13, 0x80, 0x00, - 0x17, 0x80, 0x41, 0x18, 0x80, 0xc0, 0x00, 0xdc, - 0x80, 0x00, 0x12, 0xb0, 0x17, 0xc7, 0x42, 0x1e, - 0xaf, 0x47, 0x1b, 0xc1, 0x01, 0xdc, 0xc4, 0x00, - 0xdc, 0xc1, 0x00, 0xdc, 0x8f, 0x00, 0x23, 0xb0, - 0x34, 0xc6, 0x81, 0xc3, 0x00, 0xdc, 0xc0, 0x81, - 0xc1, 0x80, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xa2, - 0x00, 0x24, 0x9d, 0xc0, 0x00, 0xdc, 0xc1, 0x00, - 0xdc, 0xc1, 0x02, 0xdc, 0xc0, 0x01, 0xdc, 0xc0, - 0x00, 0xdc, 0xc2, 0x00, 0xdc, 0xc0, 0x00, 0xdc, - 0xc0, 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, - 0x6f, 0xc6, 0x00, 0xdc, 0xc0, 0x88, 0x00, 0xdc, - 0x97, 0xc3, 0x80, 0xc8, 0x80, 0xc2, 0x80, 0xc4, - 0xaa, 0x02, 0xdc, 0xb0, 0x0a, 0xc1, 0x02, 0xdc, - 0xc3, 0xa9, 0xc4, 0x04, 0xdc, 0xcd, 0x80, 0x00, - 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xc2, - 0x02, 0xdc, 0x42, 0x1b, 0xc2, 0x00, 0xdc, 0xc1, - 0x01, 0xdc, 0xc4, 0xb0, 0x0b, 0x00, 0x07, 0x8f, - 0x00, 0x09, 0x82, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, - 0x36, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xaf, 0xc0, - 0xb0, 0x0c, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, - 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3d, - 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x4e, 0x00, - 0x09, 0xb0, 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, - 0x86, 0x00, 0x54, 0x00, 0x5b, 0xb0, 0x34, 0x00, - 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3c, 0x01, 0x09, - 0x8f, 0x00, 0x09, 0xb0, 0x4b, 0x00, 0x09, 0xb0, - 0x3c, 0x01, 0x67, 0x00, 0x09, 0x8c, 0x03, 0x6b, - 0xb0, 0x3b, 0x01, 0x76, 0x00, 0x09, 0x8c, 0x03, - 0x7a, 0xb0, 0x1b, 0x01, 0xdc, 0x9a, 0x00, 0xdc, - 0x80, 0x00, 0xdc, 0x80, 0x00, 0xd8, 0xb0, 0x06, - 0x41, 0x81, 0x80, 0x00, 0x84, 0x84, 0x03, 0x82, - 0x81, 0x00, 0x82, 0x80, 0xc1, 0x00, 0x09, 0x80, - 0xc1, 0xb0, 0x0d, 0x00, 0xdc, 0xb0, 0x3f, 0x00, - 0x07, 0x80, 0x01, 0x09, 0xb0, 0x21, 0x00, 0xdc, - 0xb2, 0x9e, 0xc2, 0xb3, 0x83, 0x01, 0x09, 0x9d, - 0x00, 0x09, 0xb0, 0x6c, 0x00, 0x09, 0x89, 0xc0, - 0xb0, 0x9a, 0x00, 0xe4, 0xb0, 0x5e, 0x00, 0xde, - 0xc0, 0x00, 0xdc, 0xb0, 0xaa, 0xc0, 0x00, 0xdc, - 0xb0, 0x16, 0x00, 0x09, 0x93, 0xc7, 0x81, 0x00, - 0xdc, 0xaf, 0xc4, 0x05, 0xdc, 0xc1, 0x00, 0xdc, - 0x80, 0x01, 0xdc, 0xc1, 0x01, 0xdc, 0xc4, 0x00, - 0xdc, 0xc3, 0xb0, 0x34, 0x00, 0x07, 0x8e, 0x00, - 0x09, 0xa5, 0xc0, 0x00, 0xdc, 0xc6, 0xb0, 0x05, - 0x01, 0x09, 0xb0, 0x09, 0x00, 0x07, 0x8a, 0x01, - 0x09, 0xb0, 0x12, 0x00, 0x07, 0xb0, 0x67, 0xc2, - 0x41, 0x00, 0x04, 0xdc, 0xc1, 0x03, 0xdc, 0xc0, - 0x41, 0x00, 0x05, 0x01, 0x83, 0x00, 0xdc, 0x85, - 0xc0, 0x82, 0xc1, 0xb0, 0x95, 0xc1, 0x00, 0xdc, - 0xc6, 0x00, 0xdc, 0xc1, 0x00, 0xea, 0x00, 0xd6, - 0x00, 0xdc, 0x00, 0xca, 0xe4, 0x00, 0xe8, 0x01, - 0xe4, 0x00, 0xdc, 0x00, 0xda, 0xc0, 0x00, 0xe9, - 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xb2, 0x9f, 0xc1, - 0x01, 0x01, 0xc3, 0x02, 0x01, 0xc1, 0x83, 0xc0, - 0x82, 0x01, 0x01, 0xc0, 0x00, 0xdc, 0xc0, 0x01, - 0x01, 0x03, 0xdc, 0xc0, 0xb8, 0x03, 0xcd, 0xc2, - 0xb0, 0x5c, 0x00, 0x09, 0xb0, 0x2f, 0xdf, 0xb1, - 0xf9, 0x00, 0xda, 0x00, 0xe4, 0x00, 0xe8, 0x00, - 0xde, 0x01, 0xe0, 0xb0, 0x38, 0x01, 0x08, 0xb8, - 0x6d, 0xa3, 0xc0, 0x83, 0xc9, 0x9f, 0xc1, 0xb0, - 0x1f, 0xc1, 0xb0, 0xe3, 0x00, 0x09, 0xa4, 0x00, - 0x09, 0xb0, 0x66, 0x00, 0x09, 0x9a, 0xd1, 0xb0, - 0x08, 0x02, 0xdc, 0xa4, 0x00, 0x09, 0xb0, 0x2e, - 0x00, 0x07, 0x8b, 0x00, 0x09, 0xb0, 0xbe, 0xc0, - 0x80, 0xc1, 0x00, 0xdc, 0x81, 0xc1, 0x84, 0xc1, - 0x80, 0xc0, 0xb0, 0x03, 0x00, 0x09, 0xb0, 0xc5, - 0x00, 0x09, 0xb8, 0x46, 0xff, 0x00, 0x1a, 0xb2, - 0xd0, 0xc6, 0x06, 0xdc, 0xc1, 0xb3, 0x9c, 0x00, - 0xdc, 0xb0, 0xb1, 0x00, 0xdc, 0xb0, 0x64, 0xc4, - 0xb6, 0x61, 0x00, 0xdc, 0x80, 0xc0, 0xa7, 0xc0, - 0x00, 0x01, 0x00, 0xdc, 0x83, 0x00, 0x09, 0xb0, - 0x74, 0xc0, 0x00, 0xdc, 0xb2, 0x0c, 0xc3, 0xb0, - 0x10, 0xc4, 0xb1, 0x0c, 0xc1, 0xb0, 0x1f, 0x02, - 0xdc, 0xb0, 0x15, 0x01, 0xdc, 0xc2, 0x00, 0xdc, - 0xc0, 0x03, 0xdc, 0xb0, 0x00, 0xc0, 0x00, 0xdc, - 0xc0, 0x00, 0xdc, 0xb0, 0x8f, 0x00, 0x09, 0xa8, - 0x00, 0x09, 0x8d, 0x00, 0x09, 0xb0, 0x08, 0x00, - 0x09, 0x00, 0x07, 0xb0, 0x14, 0xc2, 0xaf, 0x01, - 0x09, 0xb0, 0x0d, 0x00, 0x07, 0xb0, 0x1b, 0x00, - 0x09, 0x88, 0x00, 0x07, 0xb0, 0x39, 0x00, 0x09, - 0x00, 0x07, 0xb0, 0x81, 0x00, 0x07, 0x00, 0x09, - 0xb0, 0x1f, 0x01, 0x07, 0x8f, 0x00, 0x09, 0x97, - 0xc6, 0x82, 0xc4, 0xb0, 0x28, 0x02, 0x09, 0xb0, - 0x40, 0x00, 0x09, 0x82, 0x00, 0x07, 0x96, 0xc0, - 0xb0, 0x32, 0x00, 0x09, 0x00, 0x07, 0xb0, 0xca, - 0x00, 0x09, 0x00, 0x07, 0xb0, 0x4d, 0x00, 0x09, - 0xb0, 0x45, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x42, - 0x00, 0x09, 0xb0, 0xdc, 0x00, 0x09, 0x00, 0x07, - 0xb0, 0xd1, 0x01, 0x09, 0x83, 0x00, 0x07, 0xb0, - 0x6b, 0x00, 0x09, 0xb0, 0x22, 0x00, 0x09, 0x91, - 0x00, 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x74, - 0x00, 0x09, 0xb0, 0xd1, 0x00, 0x07, 0x80, 0x01, - 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x78, 0x01, - 0x09, 0xb8, 0x39, 0xbb, 0x00, 0x09, 0xb8, 0x01, - 0x8f, 0x04, 0x01, 0xb0, 0x0a, 0xc6, 0xb4, 0x88, - 0x01, 0x06, 0xb8, 0x44, 0x7b, 0x00, 0x01, 0xb8, - 0x0c, 0x95, 0x01, 0xd8, 0x02, 0x01, 0x82, 0x00, - 0xe2, 0x04, 0xd8, 0x87, 0x07, 0xdc, 0x81, 0xc4, - 0x01, 0xdc, 0x9d, 0xc3, 0xb0, 0x63, 0xc2, 0xb8, - 0x05, 0x8a, 0xc6, 0x80, 0xd0, 0x81, 0xc6, 0x80, - 0xc1, 0x80, 0xc4, 0xb0, 0x33, 0xc0, 0xb0, 0x6f, - 0xc6, 0xb1, 0x46, 0xc0, 0xb0, 0x0c, 0xc3, 0xb1, - 0xcb, 0x01, 0xe8, 0x00, 0xdc, 0xc0, 0xb0, 0xcd, - 0xc0, 0x00, 0xdc, 0xb2, 0xaf, 0x06, 0xdc, 0xb0, - 0x3c, 0xc5, 0x00, 0x07, -}; - -static const uint8_t unicode_cc_index[87] = { - 0x4d, 0x03, 0x00, 0x97, 0x05, 0x20, 0xc6, 0x05, - 0x00, 0xe7, 0x06, 0x00, 0x45, 0x07, 0x00, 0x9c, - 0x08, 0x00, 0x4d, 0x09, 0x00, 0x3c, 0x0b, 0x00, - 0x3d, 0x0d, 0x00, 0x36, 0x0f, 0x00, 0x38, 0x10, - 0x20, 0x3a, 0x19, 0x00, 0xcb, 0x1a, 0x20, 0xd3, - 0x1c, 0x00, 0xcf, 0x1d, 0x00, 0xe2, 0x20, 0x00, - 0x2e, 0x30, 0x20, 0x2b, 0xa9, 0x20, 0xed, 0xab, - 0x00, 0x39, 0x0a, 0x01, 0x4c, 0x0f, 0x01, 0x35, - 0x11, 0x21, 0x66, 0x13, 0x01, 0x40, 0x16, 0x01, - 0x47, 0x1a, 0x01, 0xf0, 0x6a, 0x21, 0x8a, 0xd1, - 0x01, 0xec, 0xe4, 0x21, 0x4b, 0xe9, 0x01, -}; - -static const uint32_t unicode_decomp_table1[709] = { - 0x00280081, 0x002a0097, 0x002a8081, 0x002bc097, - 0x002c8115, 0x002d0097, 0x002d4081, 0x002e0097, - 0x002e4115, 0x002f0199, 0x00302016, 0x00400842, - 0x00448a42, 0x004a0442, 0x004c0096, 0x004c8117, - 0x004d0242, 0x004e4342, 0x004fc12f, 0x0050c342, - 0x005240bf, 0x00530342, 0x00550942, 0x005a0842, - 0x005e0096, 0x005e4342, 0x005fc081, 0x00680142, - 0x006bc142, 0x00710185, 0x0071c317, 0x00734844, - 0x00778344, 0x00798342, 0x007b02be, 0x007c4197, - 0x007d0142, 0x007e0444, 0x00800e42, 0x00878142, - 0x00898744, 0x00ac0483, 0x00b60317, 0x00b80283, - 0x00d00214, 0x00d10096, 0x00dd0080, 0x00de8097, - 0x00df8080, 0x00e10097, 0x00e1413e, 0x00e1c080, - 0x00e204be, 0x00ea83ae, 0x00f282ae, 0x00f401ad, - 0x00f4c12e, 0x00f54103, 0x00fc0303, 0x00fe4081, - 0x0100023e, 0x0101c0be, 0x010301be, 0x010640be, - 0x010e40be, 0x0114023e, 0x0115c0be, 0x011701be, - 0x011d8144, 0x01304144, 0x01340244, 0x01358144, - 0x01368344, 0x01388344, 0x013a8644, 0x013e0144, - 0x0161c085, 0x018882ae, 0x019d422f, 0x01b00184, - 0x01b4c084, 0x024a4084, 0x024c4084, 0x024d0084, - 0x0256042e, 0x0272c12e, 0x02770120, 0x0277c084, - 0x028cc084, 0x028d8084, 0x029641ae, 0x02978084, - 0x02d20084, 0x02d2c12e, 0x02d70120, 0x02e50084, - 0x02f281ae, 0x03120084, 0x03300084, 0x0331c122, - 0x0332812e, 0x035281ae, 0x03768084, 0x037701ae, - 0x038cc085, 0x03acc085, 0x03b7012f, 0x03c30081, - 0x03d0c084, 0x03d34084, 0x03d48084, 0x03d5c084, - 0x03d70084, 0x03da4084, 0x03dcc084, 0x03dd412e, - 0x03ddc085, 0x03de0084, 0x03de4085, 0x03e04084, - 0x03e4c084, 0x03e74084, 0x03e88084, 0x03e9c084, - 0x03eb0084, 0x03ee4084, 0x04098084, 0x043f0081, - 0x06c18484, 0x06c48084, 0x06cec184, 0x06d00120, - 0x06d0c084, 0x074b0383, 0x074cc41f, 0x074f1783, - 0x075e0081, 0x0766d283, 0x07801d44, 0x078e8942, - 0x07931844, 0x079f0d42, 0x07a58216, 0x07a68085, - 0x07a6c0be, 0x07a80d44, 0x07aea044, 0x07c00122, - 0x07c08344, 0x07c20122, 0x07c28344, 0x07c40122, - 0x07c48244, 0x07c60122, 0x07c68244, 0x07c8113e, - 0x07d08244, 0x07d20122, 0x07d28244, 0x07d40122, - 0x07d48344, 0x07d64c3e, 0x07dc4080, 0x07dc80be, - 0x07dcc080, 0x07dd00be, 0x07dd4080, 0x07dd80be, - 0x07ddc080, 0x07de00be, 0x07de4080, 0x07de80be, - 0x07dec080, 0x07df00be, 0x07df4080, 0x07e00820, - 0x07e40820, 0x07e80820, 0x07ec05be, 0x07eec080, - 0x07ef00be, 0x07ef4097, 0x07ef8080, 0x07efc117, - 0x07f0443e, 0x07f24080, 0x07f280be, 0x07f2c080, - 0x07f303be, 0x07f4c080, 0x07f582ae, 0x07f6c080, - 0x07f7433e, 0x07f8c080, 0x07f903ae, 0x07fac080, - 0x07fb013e, 0x07fb8102, 0x07fc83be, 0x07fe4080, - 0x07fe80be, 0x07fec080, 0x07ff00be, 0x07ff4080, - 0x07ff8097, 0x0800011e, 0x08008495, 0x08044081, - 0x0805c097, 0x08090081, 0x08094097, 0x08098099, - 0x080bc081, 0x080cc085, 0x080d00b1, 0x080d8085, - 0x080dc0b1, 0x080f0197, 0x0811c197, 0x0815c0b3, - 0x0817c081, 0x081c0595, 0x081ec081, 0x081f0215, - 0x0820051f, 0x08228583, 0x08254415, 0x082a0097, - 0x08400119, 0x08408081, 0x0840c0bf, 0x08414119, - 0x0841c081, 0x084240bf, 0x0842852d, 0x08454081, - 0x08458097, 0x08464295, 0x08480097, 0x08484099, - 0x08488097, 0x08490081, 0x08498080, 0x084a0081, - 0x084a8102, 0x084b0495, 0x084d421f, 0x084e4081, - 0x084ec099, 0x084f0283, 0x08514295, 0x08540119, - 0x0854809b, 0x0854c619, 0x0857c097, 0x08580081, - 0x08584097, 0x08588099, 0x0858c097, 0x08590081, - 0x08594097, 0x08598099, 0x0859c09b, 0x085a0097, - 0x085a4081, 0x085a8097, 0x085ac099, 0x085b0295, - 0x085c4097, 0x085c8099, 0x085cc097, 0x085d0081, - 0x085d4097, 0x085d8099, 0x085dc09b, 0x085e0097, - 0x085e4081, 0x085e8097, 0x085ec099, 0x085f0215, - 0x08624099, 0x0866813e, 0x086b80be, 0x087341be, - 0x088100be, 0x088240be, 0x088300be, 0x088901be, - 0x088b0085, 0x088b40b1, 0x088bc085, 0x088c00b1, - 0x089040be, 0x089100be, 0x0891c1be, 0x089801be, - 0x089b42be, 0x089d0144, 0x089e0144, 0x08a00144, - 0x08a10144, 0x08a20144, 0x08ab023e, 0x08b80244, - 0x08ba8220, 0x08ca411e, 0x0918049f, 0x091a4523, - 0x091cc097, 0x091d04a5, 0x091f452b, 0x0921c09b, - 0x092204a1, 0x09244525, 0x0926c099, 0x09270d25, - 0x092d8d1f, 0x09340d1f, 0x093a8081, 0x0a8300b3, - 0x0a9d0099, 0x0a9d4097, 0x0a9d8099, 0x0ab700be, - 0x0b1f0115, 0x0b5bc081, 0x0ba7c081, 0x0bbcc081, - 0x0bc004ad, 0x0bc244ad, 0x0bc484ad, 0x0bc6f383, - 0x0be0852d, 0x0be31d03, 0x0bf1882d, 0x0c000081, - 0x0c0d8283, 0x0c130b84, 0x0c194284, 0x0c1c0122, - 0x0c1cc122, 0x0c1d8122, 0x0c1e4122, 0x0c1f0122, - 0x0c250084, 0x0c26c123, 0x0c278084, 0x0c27c085, - 0x0c2b0b84, 0x0c314284, 0x0c340122, 0x0c34c122, - 0x0c358122, 0x0c364122, 0x0c370122, 0x0c3d0084, - 0x0c3dc220, 0x0c3f8084, 0x0c3fc085, 0x0c4c4a2d, - 0x0c51451f, 0x0c53ca9f, 0x0c5915ad, 0x0c648703, - 0x0c800741, 0x0c838089, 0x0c83c129, 0x0c8441a9, - 0x0c850089, 0x0c854129, 0x0c85c2a9, 0x0c870089, - 0x0c87408f, 0x0c87808d, 0x0c881241, 0x0c910203, - 0x0c940099, 0x0c9444a3, 0x0c968323, 0x0c98072d, - 0x0c9b84af, 0x0c9dc2a1, 0x0c9f00b5, 0x0c9f40b3, - 0x0c9f8085, 0x0ca01883, 0x0cac4223, 0x0cad4523, - 0x0cafc097, 0x0cb004a1, 0x0cb241a5, 0x0cb30097, - 0x0cb34099, 0x0cb38097, 0x0cb3c099, 0x0cb417ad, - 0x0cbfc085, 0x0cc001b3, 0x0cc0c0b1, 0x0cc100b3, - 0x0cc14131, 0x0cc1c0b5, 0x0cc200b3, 0x0cc241b1, - 0x0cc30133, 0x0cc38131, 0x0cc40085, 0x0cc440b1, - 0x0cc48133, 0x0cc50085, 0x0cc540b5, 0x0cc580b7, - 0x0cc5c0b5, 0x0cc600b1, 0x0cc64135, 0x0cc6c0b3, - 0x0cc701b1, 0x0cc7c0b3, 0x0cc800b5, 0x0cc840b3, - 0x0cc881b1, 0x0cc9422f, 0x0cca4131, 0x0ccac0b5, - 0x0ccb00b1, 0x0ccb40b3, 0x0ccb80b5, 0x0ccbc0b1, - 0x0ccc012f, 0x0ccc80b5, 0x0cccc0b3, 0x0ccd00b5, - 0x0ccd40b1, 0x0ccd80b5, 0x0ccdc085, 0x0cce02b1, - 0x0ccf40b3, 0x0ccf80b1, 0x0ccfc085, 0x0cd001b1, - 0x0cd0c0b3, 0x0cd101b1, 0x0cd1c0b5, 0x0cd200b3, - 0x0cd24085, 0x0cd280b5, 0x0cd2c085, 0x0cd30133, - 0x0cd381b1, 0x0cd440b3, 0x0cd48085, 0x0cd4c0b1, - 0x0cd500b3, 0x0cd54085, 0x0cd580b5, 0x0cd5c0b1, - 0x0cd60521, 0x0cd88525, 0x0cdb02a5, 0x0cdc4099, - 0x0cdc8117, 0x0cdd0099, 0x0cdd4197, 0x0cde0127, - 0x0cde8285, 0x0cdfc089, 0x0ce0043f, 0x0ce20099, - 0x0ce2409b, 0x0ce283bf, 0x0ce44219, 0x0ce54205, - 0x0ce6433f, 0x0ce7c131, 0x0ce84085, 0x0ce881b1, - 0x0ce94085, 0x0ce98107, 0x0cea0089, 0x0cea4097, - 0x0cea8219, 0x0ceb809d, 0x0cebc08d, 0x0cec083f, - 0x0cf00105, 0x0cf0809b, 0x0cf0c197, 0x0cf1809b, - 0x0cf1c099, 0x0cf20517, 0x0cf48099, 0x0cf4c117, - 0x0cf54119, 0x0cf5c097, 0x0cf6009b, 0x0cf64099, - 0x0cf68217, 0x0cf78119, 0x0cf804a1, 0x0cfa4525, - 0x0cfcc525, 0x0cff4125, 0x0cffc099, 0x29a70103, - 0x29dc0081, 0x29fc8195, 0x29fe0103, 0x2ad70203, - 0x2ada4081, 0x3e401482, 0x3e4a7f82, 0x3e6a3f82, - 0x3e8aa102, 0x3e9b0110, 0x3e9c2f82, 0x3eb3c590, - 0x3ec00197, 0x3ec0c119, 0x3ec1413f, 0x3ec4c2af, - 0x3ec74184, 0x3ec804ad, 0x3eca4081, 0x3eca8304, - 0x3ecc03a0, 0x3ece02a0, 0x3ecf8084, 0x3ed00120, - 0x3ed0c120, 0x3ed184ae, 0x3ed3c085, 0x3ed4312d, - 0x3ef4cbad, 0x3efa892f, 0x3eff022d, 0x3f002f2f, - 0x3f1782a5, 0x3f18c0b1, 0x3f1907af, 0x3f1cffaf, - 0x3f3c81a5, 0x3f3d64af, 0x3f542031, 0x3f649b31, - 0x3f7c0131, 0x3f7c83b3, 0x3f7e40b1, 0x3f7e80bd, - 0x3f7ec0bb, 0x3f7f00b3, 0x3f840503, 0x3f8c01ad, - 0x3f8cc315, 0x3f8e462d, 0x3f91cc03, 0x3f97c695, - 0x3f9c01af, 0x3f9d0085, 0x3f9d852f, 0x3fa03aad, - 0x3fbd442f, 0x3fc06f1f, 0x3fd7c11f, 0x3fd85fad, - 0x3fe80081, 0x3fe84f1f, 0x3ff0831f, 0x3ff2831f, - 0x3ff4831f, 0x3ff6819f, 0x3ff80783, 0x41724092, - 0x41790092, 0x41e04d83, 0x41e70f91, 0x44268192, - 0x442ac092, 0x444b8112, 0x44d2c112, 0x44e0c192, - 0x44e38092, 0x44e44092, 0x44f14212, 0x452ec212, - 0x456e8112, 0x464e0092, 0x58484412, 0x5b5a0192, - 0x73358d1f, 0x733c051f, 0x74578392, 0x746ec312, - 0x75000d1f, 0x75068d1f, 0x750d0d1f, 0x7513839f, - 0x7515891f, 0x751a0d1f, 0x75208d1f, 0x75271015, - 0x752f439f, 0x7531459f, 0x75340d1f, 0x753a8d1f, - 0x75410395, 0x7543441f, 0x7545839f, 0x75478d1f, - 0x754e0795, 0x7552839f, 0x75548d1f, 0x755b0d1f, - 0x75618d1f, 0x75680d1f, 0x756e8d1f, 0x75750d1f, - 0x757b8d1f, 0x75820d1f, 0x75888d1f, 0x758f0d1f, - 0x75958d1f, 0x759c0d1f, 0x75a28d1f, 0x75a90103, - 0x75aa089f, 0x75ae4081, 0x75ae839f, 0x75b04081, - 0x75b08c9f, 0x75b6c081, 0x75b7032d, 0x75b8889f, - 0x75bcc081, 0x75bd039f, 0x75bec081, 0x75bf0c9f, - 0x75c54081, 0x75c5832d, 0x75c7089f, 0x75cb4081, - 0x75cb839f, 0x75cd4081, 0x75cd8c9f, 0x75d3c081, - 0x75d4032d, 0x75d5889f, 0x75d9c081, 0x75da039f, - 0x75dbc081, 0x75dc0c9f, 0x75e24081, 0x75e2832d, - 0x75e4089f, 0x75e84081, 0x75e8839f, 0x75ea4081, - 0x75ea8c9f, 0x75f0c081, 0x75f1042d, 0x75f3851f, - 0x75f6051f, 0x75f8851f, 0x75fb051f, 0x75fd851f, - 0x780c049f, 0x780e419f, 0x780f059f, 0x7811c203, - 0x7812d0ad, 0x781b0103, 0x7b80022d, 0x7b814dad, - 0x7b884203, 0x7b89c081, 0x7b8a452d, 0x7b8d0403, - 0x7b908081, 0x7b91dc03, 0x7ba0052d, 0x7ba2c8ad, - 0x7ba84483, 0x7baac8ad, 0x7c400097, 0x7c404521, - 0x7c440d25, 0x7c4a8087, 0x7c4ac115, 0x7c4b4117, - 0x7c4c0d1f, 0x7c528217, 0x7c538099, 0x7c53c097, - 0x7c5a8197, 0x7c640097, 0x7c80012f, 0x7c808081, - 0x7c841603, 0x7c9004c1, 0x7c940103, 0x7efc051f, - 0xbe0001ac, 0xbe00d110, 0xbe0947ac, 0xbe0d3910, - 0xbe29872c, 0xbe2d022c, 0xbe2e3790, 0xbe49ff90, - 0xbe69bc10, -}; - -static const uint16_t unicode_decomp_table2[709] = { - 0x0020, 0x0000, 0x0061, 0x0002, 0x0004, 0x0006, 0x03bc, 0x0008, - 0x000a, 0x000c, 0x0015, 0x0095, 0x00a5, 0x00b9, 0x00c1, 0x00c3, - 0x00c7, 0x00cb, 0x00d1, 0x00d7, 0x00dd, 0x00e0, 0x00e6, 0x00f8, - 0x0108, 0x010a, 0x0073, 0x0110, 0x0112, 0x0114, 0x0120, 0x012c, - 0x0144, 0x014d, 0x0153, 0x0162, 0x0168, 0x016a, 0x0176, 0x0192, - 0x0194, 0x01a9, 0x01bb, 0x01c7, 0x01d1, 0x01d5, 0x02b9, 0x01d7, - 0x003b, 0x01d9, 0x01db, 0x00b7, 0x01e1, 0x01fc, 0x020c, 0x0218, - 0x021d, 0x0223, 0x0227, 0x03a3, 0x0233, 0x023f, 0x0242, 0x024b, - 0x024e, 0x0251, 0x025d, 0x0260, 0x0269, 0x026c, 0x026f, 0x0275, - 0x0278, 0x0281, 0x028a, 0x029c, 0x029f, 0x02a3, 0x02af, 0x02b9, - 0x02c5, 0x02c9, 0x02cd, 0x02d1, 0x02d5, 0x02e7, 0x02ed, 0x02f1, - 0x02f5, 0x02f9, 0x02fd, 0x0305, 0x0309, 0x030d, 0x0313, 0x0317, - 0x031b, 0x0323, 0x0327, 0x032b, 0x032f, 0x0335, 0x033d, 0x0341, - 0x0349, 0x034d, 0x0351, 0x0f0b, 0x0357, 0x035b, 0x035f, 0x0363, - 0x0367, 0x036b, 0x036f, 0x0373, 0x0379, 0x037d, 0x0381, 0x0385, - 0x0389, 0x038d, 0x0391, 0x0395, 0x0399, 0x039d, 0x03a1, 0x10dc, - 0x03a5, 0x03c9, 0x03cd, 0x03d9, 0x03dd, 0x03e1, 0x03ef, 0x03f1, - 0x043d, 0x044f, 0x0499, 0x04f0, 0x0502, 0x054a, 0x0564, 0x056c, - 0x0570, 0x0573, 0x059a, 0x05fa, 0x05fe, 0x0607, 0x060b, 0x0614, - 0x0618, 0x061e, 0x0622, 0x0628, 0x068e, 0x0694, 0x0698, 0x069e, - 0x06a2, 0x06ab, 0x03ac, 0x06f3, 0x03ad, 0x06f6, 0x03ae, 0x06f9, - 0x03af, 0x06fc, 0x03cc, 0x06ff, 0x03cd, 0x0702, 0x03ce, 0x0705, - 0x0709, 0x070d, 0x0711, 0x0386, 0x0732, 0x0735, 0x03b9, 0x0737, - 0x073b, 0x0388, 0x0753, 0x0389, 0x0756, 0x0390, 0x076b, 0x038a, - 0x0777, 0x03b0, 0x0789, 0x038e, 0x0799, 0x079f, 0x07a3, 0x038c, - 0x07b8, 0x038f, 0x07bb, 0x00b4, 0x07be, 0x07c0, 0x07c2, 0x2010, - 0x07cb, 0x002e, 0x07cd, 0x07cf, 0x0020, 0x07d2, 0x07d6, 0x07db, - 0x07df, 0x07e4, 0x07ea, 0x07f0, 0x0020, 0x07f6, 0x2212, 0x0801, - 0x0805, 0x0807, 0x081d, 0x0825, 0x0827, 0x0043, 0x082d, 0x0830, - 0x0190, 0x0836, 0x0839, 0x004e, 0x0845, 0x0847, 0x084c, 0x084e, - 0x0851, 0x005a, 0x03a9, 0x005a, 0x0853, 0x0857, 0x0860, 0x0069, - 0x0862, 0x0865, 0x086f, 0x0874, 0x087a, 0x087e, 0x08a2, 0x0049, - 0x08a4, 0x08a6, 0x08a9, 0x0056, 0x08ab, 0x08ad, 0x08b0, 0x08b4, - 0x0058, 0x08b6, 0x08b8, 0x08bb, 0x08c0, 0x08c2, 0x08c5, 0x0076, - 0x08c7, 0x08c9, 0x08cc, 0x08d0, 0x0078, 0x08d2, 0x08d4, 0x08d7, - 0x08db, 0x08de, 0x08e4, 0x08e7, 0x08f0, 0x08f3, 0x08f6, 0x08f9, - 0x0902, 0x0906, 0x090b, 0x090f, 0x0914, 0x0917, 0x091a, 0x0923, - 0x092c, 0x093b, 0x093e, 0x0941, 0x0944, 0x0947, 0x094a, 0x0956, - 0x095c, 0x0960, 0x0962, 0x0964, 0x0968, 0x096a, 0x0970, 0x0978, - 0x097c, 0x0980, 0x0986, 0x0989, 0x098f, 0x0991, 0x0030, 0x0993, - 0x0999, 0x099c, 0x099e, 0x09a1, 0x09a4, 0x2d61, 0x6bcd, 0x9f9f, - 0x09a6, 0x09b1, 0x09bc, 0x09c7, 0x0a95, 0x0aa1, 0x0b15, 0x0020, - 0x0b27, 0x0b31, 0x0b8d, 0x0ba1, 0x0ba5, 0x0ba9, 0x0bad, 0x0bb1, - 0x0bb5, 0x0bb9, 0x0bbd, 0x0bc1, 0x0bc5, 0x0c21, 0x0c35, 0x0c39, - 0x0c3d, 0x0c41, 0x0c45, 0x0c49, 0x0c4d, 0x0c51, 0x0c55, 0x0c59, - 0x0c6f, 0x0c71, 0x0c73, 0x0ca0, 0x0cbc, 0x0cdc, 0x0ce4, 0x0cec, - 0x0cf4, 0x0cfc, 0x0d04, 0x0d0c, 0x0d14, 0x0d22, 0x0d2e, 0x0d7a, - 0x0d82, 0x0d85, 0x0d89, 0x0d8d, 0x0d9d, 0x0db1, 0x0db5, 0x0dbc, - 0x0dc2, 0x0dc6, 0x0e28, 0x0e2c, 0x0e30, 0x0e32, 0x0e36, 0x0e3c, - 0x0e3e, 0x0e41, 0x0e43, 0x0e46, 0x0e77, 0x0e7b, 0x0e89, 0x0e8e, - 0x0e94, 0x0e9c, 0x0ea3, 0x0ea9, 0x0eb4, 0x0ebe, 0x0ec6, 0x0eca, - 0x0ecf, 0x0ed9, 0x0edd, 0x0ee4, 0x0eec, 0x0ef3, 0x0ef8, 0x0f04, - 0x0f0a, 0x0f15, 0x0f1b, 0x0f22, 0x0f28, 0x0f33, 0x0f3d, 0x0f45, - 0x0f4c, 0x0f51, 0x0f57, 0x0f5e, 0x0f63, 0x0f69, 0x0f70, 0x0f76, - 0x0f7d, 0x0f82, 0x0f89, 0x0f8d, 0x0f9e, 0x0fa4, 0x0fa9, 0x0fad, - 0x0fb8, 0x0fbe, 0x0fc9, 0x0fd0, 0x0fd6, 0x0fda, 0x0fe1, 0x0fe5, - 0x0fef, 0x0ffa, 0x1000, 0x1004, 0x1009, 0x100f, 0x1013, 0x101a, - 0x101f, 0x1023, 0x1029, 0x102f, 0x1032, 0x1036, 0x1039, 0x103f, - 0x1045, 0x1059, 0x1061, 0x1079, 0x107c, 0x1080, 0x1095, 0x10a1, - 0x10b1, 0x10c3, 0x10cb, 0x10cf, 0x10da, 0x10de, 0x10ea, 0x10f2, - 0x10f4, 0x1100, 0x1105, 0x1111, 0x1141, 0x1149, 0x114d, 0x1153, - 0x1157, 0x115a, 0x116e, 0x1171, 0x1175, 0x117b, 0x117d, 0x1181, - 0x1184, 0x118c, 0x1192, 0x1196, 0x119c, 0x11a2, 0x11a8, 0x11ab, - 0xa76f, 0x11af, 0x11b2, 0x11b6, 0x028d, 0x11be, 0x1210, 0x130e, - 0x140c, 0x1490, 0x1495, 0x1553, 0x156c, 0x1572, 0x1578, 0x157e, - 0x158a, 0x1596, 0x002b, 0x15a1, 0x15b9, 0x15bd, 0x15c1, 0x15c5, - 0x15c9, 0x15cd, 0x15e1, 0x15e5, 0x1649, 0x1662, 0x1688, 0x168e, - 0x174c, 0x1752, 0x1757, 0x1777, 0x1877, 0x187d, 0x1911, 0x19d3, - 0x1a77, 0x1a7f, 0x1a9d, 0x1aa2, 0x1ab6, 0x1ac0, 0x1ac6, 0x1ada, - 0x1adf, 0x1ae5, 0x1af3, 0x1b23, 0x1b30, 0x1b38, 0x1b3c, 0x1b52, - 0x1bc9, 0x1bdb, 0x1bdd, 0x1bdf, 0x3164, 0x1c20, 0x1c22, 0x1c24, - 0x1c26, 0x1c28, 0x1c2a, 0x1c48, 0x1c4d, 0x1c52, 0x1c88, 0x1cce, - 0x1cdc, 0x1ce1, 0x1cea, 0x1cf3, 0x1d01, 0x1d06, 0x1d0b, 0x1d1d, - 0x1d2f, 0x1d38, 0x1d3d, 0x1d61, 0x1d6f, 0x1d71, 0x1d73, 0x1d93, - 0x1dae, 0x1db0, 0x1db2, 0x1db4, 0x1db6, 0x1db8, 0x1dba, 0x1dbc, - 0x1ddc, 0x1dde, 0x1de0, 0x1de2, 0x1de4, 0x1deb, 0x1ded, 0x1def, - 0x1df1, 0x1e00, 0x1e02, 0x1e04, 0x1e06, 0x1e08, 0x1e0a, 0x1e0c, - 0x1e0e, 0x1e10, 0x1e12, 0x1e14, 0x1e16, 0x1e18, 0x1e1a, 0x1e1c, - 0x1e20, 0x03f4, 0x1e22, 0x2207, 0x1e24, 0x2202, 0x1e26, 0x1e2e, - 0x03f4, 0x1e30, 0x2207, 0x1e32, 0x2202, 0x1e34, 0x1e3c, 0x03f4, - 0x1e3e, 0x2207, 0x1e40, 0x2202, 0x1e42, 0x1e4a, 0x03f4, 0x1e4c, - 0x2207, 0x1e4e, 0x2202, 0x1e50, 0x1e58, 0x03f4, 0x1e5a, 0x2207, - 0x1e5c, 0x2202, 0x1e5e, 0x1e68, 0x1e6a, 0x1e6c, 0x1e6e, 0x1e70, - 0x1e72, 0x1e74, 0x1e76, 0x1e78, 0x1e80, 0x1ea3, 0x1ea7, 0x1ead, - 0x1eca, 0x062d, 0x1ed2, 0x1ede, 0x062c, 0x1eee, 0x1f5e, 0x1f6a, - 0x1f7d, 0x1f8f, 0x1fa2, 0x1fa4, 0x1fa8, 0x1fae, 0x1fb4, 0x1fb6, - 0x1fba, 0x1fbc, 0x1fc4, 0x1fc7, 0x1fc9, 0x1fcf, 0x1fd1, 0x30b5, - 0x1fd7, 0x202f, 0x2045, 0x2049, 0x204b, 0x2050, 0x209d, 0x20ae, - 0x21af, 0x21bf, 0x21c5, 0x22bf, 0x23dd, -}; - -static const uint8_t unicode_decomp_data[9451] = { - 0x20, 0x88, 0x20, 0x84, 0x32, 0x33, 0x20, 0x81, - 0x20, 0xa7, 0x31, 0x6f, 0x31, 0xd0, 0x34, 0x31, - 0xd0, 0x32, 0x33, 0xd0, 0x34, 0x41, 0x80, 0x41, - 0x81, 0x41, 0x82, 0x41, 0x83, 0x41, 0x88, 0x41, - 0x8a, 0x00, 0x00, 0x43, 0xa7, 0x45, 0x80, 0x45, - 0x81, 0x45, 0x82, 0x45, 0x88, 0x49, 0x80, 0x49, - 0x81, 0x49, 0x82, 0x49, 0x88, 0x00, 0x00, 0x4e, - 0x83, 0x4f, 0x80, 0x4f, 0x81, 0x4f, 0x82, 0x4f, - 0x83, 0x4f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x55, - 0x80, 0x55, 0x81, 0x55, 0x82, 0x55, 0x88, 0x59, - 0x81, 0x00, 0x00, 0x00, 0x00, 0x61, 0x80, 0x61, - 0x81, 0x61, 0x82, 0x61, 0x83, 0x61, 0x88, 0x61, - 0x8a, 0x00, 0x00, 0x63, 0xa7, 0x65, 0x80, 0x65, - 0x81, 0x65, 0x82, 0x65, 0x88, 0x69, 0x80, 0x69, - 0x81, 0x69, 0x82, 0x69, 0x88, 0x00, 0x00, 0x6e, - 0x83, 0x6f, 0x80, 0x6f, 0x81, 0x6f, 0x82, 0x6f, - 0x83, 0x6f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x75, - 0x80, 0x75, 0x81, 0x75, 0x82, 0x75, 0x88, 0x79, - 0x81, 0x00, 0x00, 0x79, 0x88, 0x41, 0x84, 0x41, - 0x86, 0x41, 0xa8, 0x43, 0x81, 0x43, 0x82, 0x43, - 0x87, 0x43, 0x8c, 0x44, 0x8c, 0x45, 0x84, 0x45, - 0x86, 0x45, 0x87, 0x45, 0xa8, 0x45, 0x8c, 0x47, - 0x82, 0x47, 0x86, 0x47, 0x87, 0x47, 0xa7, 0x48, - 0x82, 0x49, 0x83, 0x49, 0x84, 0x49, 0x86, 0x49, - 0xa8, 0x49, 0x87, 0x49, 0x4a, 0x69, 0x6a, 0x4a, - 0x82, 0x4b, 0xa7, 0x4c, 0x81, 0x4c, 0xa7, 0x4c, - 0x8c, 0x4c, 0x00, 0x00, 0x6b, 0x20, 0x6b, 0x4e, - 0x81, 0x4e, 0xa7, 0x4e, 0x8c, 0xbc, 0x02, 0x6e, - 0x4f, 0x84, 0x4f, 0x86, 0x4f, 0x8b, 0x52, 0x81, - 0x52, 0xa7, 0x52, 0x8c, 0x53, 0x81, 0x53, 0x82, - 0x53, 0xa7, 0x53, 0x8c, 0x54, 0xa7, 0x54, 0x8c, - 0x55, 0x83, 0x55, 0x84, 0x55, 0x86, 0x55, 0x8a, - 0x55, 0x8b, 0x55, 0xa8, 0x57, 0x82, 0x59, 0x82, - 0x59, 0x88, 0x5a, 0x81, 0x5a, 0x87, 0x5a, 0x8c, - 0x4f, 0x9b, 0x55, 0x9b, 0x44, 0x00, 0x7d, 0x01, - 0x44, 0x00, 0x7e, 0x01, 0x64, 0x00, 0x7e, 0x01, - 0x4c, 0x4a, 0x4c, 0x6a, 0x6c, 0x6a, 0x4e, 0x4a, - 0x4e, 0x6a, 0x6e, 0x6a, 0x41, 0x00, 0x8c, 0x49, - 0x00, 0x8c, 0x4f, 0x00, 0x8c, 0x55, 0x00, 0x8c, - 0xdc, 0x00, 0x84, 0xdc, 0x00, 0x81, 0xdc, 0x00, - 0x8c, 0xdc, 0x00, 0x80, 0xc4, 0x00, 0x84, 0x26, - 0x02, 0x84, 0xc6, 0x00, 0x84, 0x47, 0x8c, 0x4b, - 0x8c, 0x4f, 0xa8, 0xea, 0x01, 0x84, 0xeb, 0x01, - 0x84, 0xb7, 0x01, 0x8c, 0x92, 0x02, 0x8c, 0x6a, - 0x00, 0x8c, 0x44, 0x5a, 0x44, 0x7a, 0x64, 0x7a, - 0x47, 0x81, 0x4e, 0x00, 0x80, 0xc5, 0x00, 0x81, - 0xc6, 0x00, 0x81, 0xd8, 0x00, 0x81, 0x41, 0x8f, - 0x41, 0x91, 0x45, 0x8f, 0x45, 0x91, 0x49, 0x8f, - 0x49, 0x91, 0x4f, 0x8f, 0x4f, 0x91, 0x52, 0x8f, - 0x52, 0x91, 0x55, 0x8f, 0x55, 0x91, 0x53, 0xa6, - 0x54, 0xa6, 0x48, 0x8c, 0x41, 0x00, 0x87, 0x45, - 0x00, 0xa7, 0xd6, 0x00, 0x84, 0xd5, 0x00, 0x84, - 0x4f, 0x00, 0x87, 0x2e, 0x02, 0x84, 0x59, 0x00, - 0x84, 0x68, 0x00, 0x66, 0x02, 0x6a, 0x00, 0x72, - 0x00, 0x79, 0x02, 0x7b, 0x02, 0x81, 0x02, 0x77, - 0x00, 0x79, 0x00, 0x20, 0x86, 0x20, 0x87, 0x20, - 0x8a, 0x20, 0xa8, 0x20, 0x83, 0x20, 0x8b, 0x63, - 0x02, 0x6c, 0x00, 0x73, 0x00, 0x78, 0x00, 0x95, - 0x02, 0x80, 0x81, 0x00, 0x93, 0x88, 0x81, 0x20, - 0xc5, 0x20, 0x81, 0xa8, 0x00, 0x81, 0x91, 0x03, - 0x81, 0x95, 0x03, 0x81, 0x97, 0x03, 0x81, 0x99, - 0x03, 0x81, 0x00, 0x00, 0x00, 0x9f, 0x03, 0x81, - 0x00, 0x00, 0x00, 0xa5, 0x03, 0x81, 0xa9, 0x03, - 0x81, 0xca, 0x03, 0x81, 0x01, 0x03, 0x98, 0x07, - 0xa4, 0x07, 0xb0, 0x00, 0xb4, 0x00, 0xb6, 0x00, - 0xb8, 0x00, 0xca, 0x00, 0x01, 0x03, 0xb8, 0x07, - 0xc4, 0x07, 0xbe, 0x00, 0xc4, 0x00, 0xc8, 0x00, - 0xa5, 0x03, 0x0d, 0x13, 0x00, 0x01, 0x03, 0xd1, - 0x00, 0xd1, 0x07, 0xc6, 0x03, 0xc0, 0x03, 0xba, - 0x03, 0xc1, 0x03, 0xc2, 0x03, 0x00, 0x00, 0x98, - 0x03, 0xb5, 0x03, 0x15, 0x04, 0x80, 0x15, 0x04, - 0x88, 0x00, 0x00, 0x00, 0x13, 0x04, 0x81, 0x06, - 0x04, 0x88, 0x1a, 0x04, 0x81, 0x18, 0x04, 0x80, - 0x23, 0x04, 0x86, 0x18, 0x04, 0x86, 0x38, 0x04, - 0x86, 0x35, 0x04, 0x80, 0x35, 0x04, 0x88, 0x00, - 0x00, 0x00, 0x33, 0x04, 0x81, 0x56, 0x04, 0x88, - 0x3a, 0x04, 0x81, 0x38, 0x04, 0x80, 0x43, 0x04, - 0x86, 0x74, 0x04, 0x8f, 0x16, 0x04, 0x86, 0x10, - 0x04, 0x86, 0x10, 0x04, 0x88, 0x15, 0x04, 0x86, - 0xd8, 0x04, 0x88, 0x16, 0x04, 0x88, 0x17, 0x04, - 0x88, 0x18, 0x04, 0x84, 0x18, 0x04, 0x88, 0x1e, - 0x04, 0x88, 0xe8, 0x04, 0x88, 0x2d, 0x04, 0x88, - 0x23, 0x04, 0x84, 0x23, 0x04, 0x88, 0x23, 0x04, - 0x8b, 0x27, 0x04, 0x88, 0x2b, 0x04, 0x88, 0x65, - 0x05, 0x82, 0x05, 0x27, 0x06, 0x00, 0x2c, 0x00, - 0x2d, 0x21, 0x2d, 0x00, 0x2e, 0x23, 0x2d, 0x27, - 0x06, 0x00, 0x4d, 0x21, 0x4d, 0xa0, 0x4d, 0x23, - 0x4d, 0xd5, 0x06, 0x54, 0x06, 0x00, 0x00, 0x00, - 0x00, 0xc1, 0x06, 0x54, 0x06, 0xd2, 0x06, 0x54, - 0x06, 0x28, 0x09, 0x3c, 0x09, 0x30, 0x09, 0x3c, - 0x09, 0x33, 0x09, 0x3c, 0x09, 0x15, 0x09, 0x00, - 0x27, 0x01, 0x27, 0x02, 0x27, 0x07, 0x27, 0x0c, - 0x27, 0x0d, 0x27, 0x16, 0x27, 0x1a, 0x27, 0xbe, - 0x09, 0x09, 0x00, 0x09, 0x19, 0xa1, 0x09, 0xbc, - 0x09, 0xaf, 0x09, 0xbc, 0x09, 0x32, 0x0a, 0x3c, - 0x0a, 0x38, 0x0a, 0x3c, 0x0a, 0x16, 0x0a, 0x00, - 0x26, 0x01, 0x26, 0x06, 0x26, 0x2b, 0x0a, 0x3c, - 0x0a, 0x47, 0x0b, 0x56, 0x0b, 0x3e, 0x0b, 0x09, - 0x00, 0x09, 0x19, 0x21, 0x0b, 0x3c, 0x0b, 0x92, - 0x0b, 0xd7, 0x0b, 0xbe, 0x0b, 0x08, 0x00, 0x09, - 0x00, 0x08, 0x19, 0x46, 0x0c, 0x56, 0x0c, 0xbf, - 0x0c, 0xd5, 0x0c, 0xc6, 0x0c, 0xd5, 0x0c, 0xc2, - 0x0c, 0x04, 0x00, 0x08, 0x13, 0x3e, 0x0d, 0x08, - 0x00, 0x09, 0x00, 0x08, 0x19, 0xd9, 0x0d, 0xca, - 0x0d, 0xca, 0x0d, 0x0f, 0x05, 0x12, 0x00, 0x0f, - 0x15, 0x4d, 0x0e, 0x32, 0x0e, 0xcd, 0x0e, 0xb2, - 0x0e, 0x99, 0x0e, 0x12, 0x00, 0x12, 0x08, 0x42, - 0x0f, 0xb7, 0x0f, 0x4c, 0x0f, 0xb7, 0x0f, 0x51, - 0x0f, 0xb7, 0x0f, 0x56, 0x0f, 0xb7, 0x0f, 0x5b, - 0x0f, 0xb7, 0x0f, 0x40, 0x0f, 0xb5, 0x0f, 0x71, - 0x0f, 0x72, 0x0f, 0x71, 0x0f, 0x00, 0x03, 0x41, - 0x0f, 0xb2, 0x0f, 0x81, 0x0f, 0xb3, 0x0f, 0x80, - 0x0f, 0xb3, 0x0f, 0x81, 0x0f, 0x71, 0x0f, 0x80, - 0x0f, 0x92, 0x0f, 0xb7, 0x0f, 0x9c, 0x0f, 0xb7, - 0x0f, 0xa1, 0x0f, 0xb7, 0x0f, 0xa6, 0x0f, 0xb7, - 0x0f, 0xab, 0x0f, 0xb7, 0x0f, 0x90, 0x0f, 0xb5, - 0x0f, 0x25, 0x10, 0x2e, 0x10, 0x05, 0x1b, 0x35, - 0x1b, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1b, 0x35, - 0x1b, 0x00, 0x00, 0x00, 0x00, 0x09, 0x1b, 0x35, - 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x1b, 0x35, - 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x1b, 0x35, - 0x1b, 0x11, 0x1b, 0x35, 0x1b, 0x3a, 0x1b, 0x35, - 0x1b, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x1b, 0x35, - 0x1b, 0x3e, 0x1b, 0x35, 0x1b, 0x42, 0x1b, 0x35, - 0x1b, 0x41, 0x00, 0xc6, 0x00, 0x42, 0x00, 0x00, - 0x00, 0x44, 0x00, 0x45, 0x00, 0x8e, 0x01, 0x47, - 0x00, 0x4f, 0x00, 0x22, 0x02, 0x50, 0x00, 0x52, - 0x00, 0x54, 0x00, 0x55, 0x00, 0x57, 0x00, 0x61, - 0x00, 0x50, 0x02, 0x51, 0x02, 0x02, 0x1d, 0x62, - 0x00, 0x64, 0x00, 0x65, 0x00, 0x59, 0x02, 0x5b, - 0x02, 0x5c, 0x02, 0x67, 0x00, 0x00, 0x00, 0x6b, - 0x00, 0x6d, 0x00, 0x4b, 0x01, 0x6f, 0x00, 0x54, - 0x02, 0x16, 0x1d, 0x17, 0x1d, 0x70, 0x00, 0x74, - 0x00, 0x75, 0x00, 0x1d, 0x1d, 0x6f, 0x02, 0x76, - 0x00, 0x25, 0x1d, 0xb2, 0x03, 0xb3, 0x03, 0xb4, - 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x69, 0x00, 0x72, - 0x00, 0x75, 0x00, 0x76, 0x00, 0xb2, 0x03, 0xb3, - 0x03, 0xc1, 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x52, - 0x02, 0x63, 0x00, 0x55, 0x02, 0xf0, 0x00, 0x5c, - 0x02, 0x66, 0x00, 0x5f, 0x02, 0x61, 0x02, 0x65, - 0x02, 0x68, 0x02, 0x69, 0x02, 0x6a, 0x02, 0x7b, - 0x1d, 0x9d, 0x02, 0x6d, 0x02, 0x85, 0x1d, 0x9f, - 0x02, 0x71, 0x02, 0x70, 0x02, 0x72, 0x02, 0x73, - 0x02, 0x74, 0x02, 0x75, 0x02, 0x78, 0x02, 0x82, - 0x02, 0x83, 0x02, 0xab, 0x01, 0x89, 0x02, 0x8a, - 0x02, 0x1c, 0x1d, 0x8b, 0x02, 0x8c, 0x02, 0x7a, - 0x00, 0x90, 0x02, 0x91, 0x02, 0x92, 0x02, 0xb8, - 0x03, 0x41, 0x00, 0xa5, 0x42, 0x00, 0x87, 0x42, - 0x00, 0xa3, 0x42, 0x00, 0xb1, 0xc7, 0x00, 0x81, - 0x44, 0x00, 0x87, 0x44, 0x00, 0xa3, 0x44, 0x00, - 0xb1, 0x44, 0x00, 0xa7, 0x44, 0x00, 0xad, 0x12, - 0x01, 0x80, 0x12, 0x01, 0x81, 0x45, 0x00, 0xad, - 0x45, 0x00, 0xb0, 0x28, 0x02, 0x86, 0x46, 0x00, - 0x87, 0x47, 0x00, 0x84, 0x48, 0x00, 0x87, 0x48, - 0x00, 0xa3, 0x48, 0x00, 0x88, 0x48, 0x00, 0xa7, - 0x48, 0x00, 0xae, 0x49, 0x00, 0xb0, 0xcf, 0x00, - 0x81, 0x4b, 0x00, 0x81, 0x4b, 0x00, 0xa3, 0x4b, - 0x00, 0xb1, 0x4c, 0x00, 0xa3, 0x36, 0x1e, 0x84, - 0x4c, 0xb1, 0x4c, 0xad, 0x4d, 0x81, 0x4d, 0x87, - 0x4d, 0xa3, 0x4e, 0x87, 0x4e, 0xa3, 0x4e, 0xb1, - 0x4e, 0xad, 0xd5, 0x00, 0x81, 0xd5, 0x00, 0x88, - 0x4c, 0x01, 0x80, 0x4c, 0x01, 0x81, 0x50, 0x00, - 0x81, 0x50, 0x00, 0x87, 0x52, 0x00, 0x87, 0x52, - 0x00, 0xa3, 0x5a, 0x1e, 0x84, 0x52, 0x00, 0xb1, - 0x53, 0x00, 0x87, 0x53, 0x00, 0xa3, 0x5a, 0x01, - 0x87, 0x60, 0x01, 0x87, 0x62, 0x1e, 0x87, 0x54, - 0x00, 0x87, 0x54, 0x00, 0xa3, 0x54, 0x00, 0xb1, - 0x54, 0x00, 0xad, 0x55, 0x00, 0xa4, 0x55, 0x00, - 0xb0, 0x55, 0x00, 0xad, 0x68, 0x01, 0x81, 0x6a, - 0x01, 0x88, 0x56, 0x83, 0x56, 0xa3, 0x57, 0x80, - 0x57, 0x81, 0x57, 0x88, 0x57, 0x87, 0x57, 0xa3, - 0x58, 0x87, 0x58, 0x88, 0x59, 0x87, 0x5a, 0x82, - 0x5a, 0xa3, 0x5a, 0xb1, 0x68, 0xb1, 0x74, 0x88, - 0x77, 0x8a, 0x79, 0x8a, 0x61, 0x00, 0xbe, 0x02, - 0x7f, 0x01, 0x87, 0x41, 0x00, 0xa3, 0x41, 0x00, - 0x89, 0xc2, 0x00, 0x81, 0xc2, 0x00, 0x80, 0xc2, - 0x00, 0x89, 0xc2, 0x00, 0x83, 0xa0, 0x1e, 0x82, - 0x02, 0x01, 0x81, 0x02, 0x01, 0x80, 0x02, 0x01, - 0x89, 0x02, 0x01, 0x83, 0xa0, 0x1e, 0x86, 0x45, - 0x00, 0xa3, 0x45, 0x00, 0x89, 0x45, 0x00, 0x83, - 0xca, 0x00, 0x81, 0xca, 0x00, 0x80, 0xca, 0x00, - 0x89, 0xca, 0x00, 0x83, 0xb8, 0x1e, 0x82, 0x49, - 0x00, 0x89, 0x49, 0x00, 0xa3, 0x4f, 0x00, 0xa3, - 0x4f, 0x00, 0x89, 0xd4, 0x00, 0x81, 0xd4, 0x00, - 0x80, 0xd4, 0x00, 0x89, 0xd4, 0x00, 0x83, 0xcc, - 0x1e, 0x82, 0xa0, 0x01, 0x81, 0xa0, 0x01, 0x80, - 0xa0, 0x01, 0x89, 0xa0, 0x01, 0x83, 0xa0, 0x01, - 0xa3, 0x55, 0x00, 0xa3, 0x55, 0x00, 0x89, 0xaf, - 0x01, 0x81, 0xaf, 0x01, 0x80, 0xaf, 0x01, 0x89, - 0xaf, 0x01, 0x83, 0xaf, 0x01, 0xa3, 0x59, 0x00, - 0x80, 0x59, 0x00, 0xa3, 0x59, 0x00, 0x89, 0x59, - 0x00, 0x83, 0xb1, 0x03, 0x13, 0x03, 0x00, 0x1f, - 0x80, 0x00, 0x1f, 0x81, 0x00, 0x1f, 0xc2, 0x91, - 0x03, 0x13, 0x03, 0x08, 0x1f, 0x80, 0x08, 0x1f, - 0x81, 0x08, 0x1f, 0xc2, 0xb5, 0x03, 0x13, 0x03, - 0x10, 0x1f, 0x80, 0x10, 0x1f, 0x81, 0x95, 0x03, - 0x13, 0x03, 0x18, 0x1f, 0x80, 0x18, 0x1f, 0x81, - 0xb7, 0x03, 0x93, 0xb7, 0x03, 0x94, 0x20, 0x1f, - 0x80, 0x21, 0x1f, 0x80, 0x20, 0x1f, 0x81, 0x21, - 0x1f, 0x81, 0x20, 0x1f, 0xc2, 0x21, 0x1f, 0xc2, - 0x97, 0x03, 0x93, 0x97, 0x03, 0x94, 0x28, 0x1f, - 0x80, 0x29, 0x1f, 0x80, 0x28, 0x1f, 0x81, 0x29, - 0x1f, 0x81, 0x28, 0x1f, 0xc2, 0x29, 0x1f, 0xc2, - 0xb9, 0x03, 0x93, 0xb9, 0x03, 0x94, 0x30, 0x1f, - 0x80, 0x31, 0x1f, 0x80, 0x30, 0x1f, 0x81, 0x31, - 0x1f, 0x81, 0x30, 0x1f, 0xc2, 0x31, 0x1f, 0xc2, - 0x99, 0x03, 0x93, 0x99, 0x03, 0x94, 0x38, 0x1f, - 0x80, 0x39, 0x1f, 0x80, 0x38, 0x1f, 0x81, 0x39, - 0x1f, 0x81, 0x38, 0x1f, 0xc2, 0x39, 0x1f, 0xc2, - 0xbf, 0x03, 0x93, 0xbf, 0x03, 0x94, 0x40, 0x1f, - 0x80, 0x40, 0x1f, 0x81, 0x9f, 0x03, 0x13, 0x03, - 0x48, 0x1f, 0x80, 0x48, 0x1f, 0x81, 0xc5, 0x03, - 0x13, 0x03, 0x50, 0x1f, 0x80, 0x50, 0x1f, 0x81, - 0x50, 0x1f, 0xc2, 0xa5, 0x03, 0x94, 0x00, 0x00, - 0x00, 0x59, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x59, - 0x1f, 0x81, 0x00, 0x00, 0x00, 0x59, 0x1f, 0xc2, - 0xc9, 0x03, 0x93, 0xc9, 0x03, 0x94, 0x60, 0x1f, - 0x80, 0x61, 0x1f, 0x80, 0x60, 0x1f, 0x81, 0x61, - 0x1f, 0x81, 0x60, 0x1f, 0xc2, 0x61, 0x1f, 0xc2, - 0xa9, 0x03, 0x93, 0xa9, 0x03, 0x94, 0x68, 0x1f, - 0x80, 0x69, 0x1f, 0x80, 0x68, 0x1f, 0x81, 0x69, - 0x1f, 0x81, 0x68, 0x1f, 0xc2, 0x69, 0x1f, 0xc2, - 0xb1, 0x03, 0x80, 0xb5, 0x03, 0x80, 0xb7, 0x03, - 0x80, 0xb9, 0x03, 0x80, 0xbf, 0x03, 0x80, 0xc5, - 0x03, 0x80, 0xc9, 0x03, 0x80, 0x00, 0x1f, 0x45, - 0x03, 0x20, 0x1f, 0x45, 0x03, 0x60, 0x1f, 0x45, - 0x03, 0xb1, 0x03, 0x86, 0xb1, 0x03, 0x84, 0x70, - 0x1f, 0xc5, 0xb1, 0x03, 0xc5, 0xac, 0x03, 0xc5, - 0x00, 0x00, 0x00, 0xb1, 0x03, 0xc2, 0xb6, 0x1f, - 0xc5, 0x91, 0x03, 0x86, 0x91, 0x03, 0x84, 0x91, - 0x03, 0x80, 0x91, 0x03, 0xc5, 0x20, 0x93, 0x20, - 0x93, 0x20, 0xc2, 0xa8, 0x00, 0xc2, 0x74, 0x1f, - 0xc5, 0xb7, 0x03, 0xc5, 0xae, 0x03, 0xc5, 0x00, - 0x00, 0x00, 0xb7, 0x03, 0xc2, 0xc6, 0x1f, 0xc5, - 0x95, 0x03, 0x80, 0x97, 0x03, 0x80, 0x97, 0x03, - 0xc5, 0xbf, 0x1f, 0x80, 0xbf, 0x1f, 0x81, 0xbf, - 0x1f, 0xc2, 0xb9, 0x03, 0x86, 0xb9, 0x03, 0x84, - 0xca, 0x03, 0x80, 0x00, 0x03, 0xb9, 0x42, 0xca, - 0x42, 0x99, 0x06, 0x99, 0x04, 0x99, 0x00, 0xfe, - 0x1f, 0x80, 0xfe, 0x1f, 0x81, 0xfe, 0x1f, 0xc2, - 0xc5, 0x03, 0x86, 0xc5, 0x03, 0x84, 0xcb, 0x03, - 0x80, 0x00, 0x03, 0xc1, 0x13, 0xc1, 0x14, 0xc5, - 0x42, 0xcb, 0x42, 0xa5, 0x06, 0xa5, 0x04, 0xa5, - 0x00, 0xa1, 0x03, 0x94, 0xa8, 0x00, 0x80, 0x85, - 0x03, 0x60, 0x00, 0x7c, 0x1f, 0xc5, 0xc9, 0x03, - 0xc5, 0xce, 0x03, 0xc5, 0x00, 0x00, 0x00, 0xc9, - 0x03, 0xc2, 0xf6, 0x1f, 0xc5, 0x9f, 0x03, 0x80, - 0xa9, 0x03, 0x80, 0xa9, 0x03, 0xc5, 0x20, 0x94, - 0x02, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0xb3, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x32, 0x20, 0x32, 0x20, 0x32, 0x20, - 0x00, 0x00, 0x00, 0x35, 0x20, 0x35, 0x20, 0x35, - 0x20, 0x00, 0x00, 0x00, 0x21, 0x21, 0x00, 0x00, - 0x20, 0x85, 0x3f, 0x3f, 0x3f, 0x21, 0x21, 0x3f, - 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x30, 0x69, - 0x00, 0x00, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, - 0x2b, 0x3d, 0x28, 0x29, 0x6e, 0x30, 0x00, 0x2b, - 0x00, 0x12, 0x22, 0x3d, 0x00, 0x28, 0x00, 0x29, - 0x00, 0x00, 0x00, 0x61, 0x00, 0x65, 0x00, 0x6f, - 0x00, 0x78, 0x00, 0x59, 0x02, 0x68, 0x6b, 0x6c, - 0x6d, 0x6e, 0x70, 0x73, 0x74, 0x52, 0x73, 0x61, - 0x2f, 0x63, 0x61, 0x2f, 0x73, 0xb0, 0x00, 0x43, - 0x63, 0x2f, 0x6f, 0x63, 0x2f, 0x75, 0xb0, 0x00, - 0x46, 0x48, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, - 0xdf, 0x01, 0x01, 0x04, 0x24, 0x4e, 0x6f, 0x50, - 0x51, 0x52, 0x52, 0x52, 0x53, 0x4d, 0x54, 0x45, - 0x4c, 0x54, 0x4d, 0x4b, 0x00, 0xc5, 0x00, 0x42, - 0x43, 0x00, 0x65, 0x45, 0x46, 0x00, 0x4d, 0x6f, - 0xd0, 0x05, 0x46, 0x41, 0x58, 0xc0, 0x03, 0xb3, - 0x03, 0x93, 0x03, 0xa0, 0x03, 0x11, 0x22, 0x44, - 0x64, 0x65, 0x69, 0x6a, 0x31, 0xd0, 0x37, 0x31, - 0xd0, 0x39, 0x31, 0xd0, 0x31, 0x30, 0x31, 0xd0, - 0x33, 0x32, 0xd0, 0x33, 0x31, 0xd0, 0x35, 0x32, - 0xd0, 0x35, 0x33, 0xd0, 0x35, 0x34, 0xd0, 0x35, - 0x31, 0xd0, 0x36, 0x35, 0xd0, 0x36, 0x31, 0xd0, - 0x38, 0x33, 0xd0, 0x38, 0x35, 0xd0, 0x38, 0x37, - 0xd0, 0x38, 0x31, 0xd0, 0x49, 0x49, 0x49, 0x49, - 0x49, 0x49, 0x56, 0x56, 0x49, 0x56, 0x49, 0x49, - 0x56, 0x49, 0x49, 0x49, 0x49, 0x58, 0x58, 0x49, - 0x58, 0x49, 0x49, 0x4c, 0x43, 0x44, 0x4d, 0x69, - 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x76, 0x76, - 0x69, 0x76, 0x69, 0x69, 0x76, 0x69, 0x69, 0x69, - 0x69, 0x78, 0x78, 0x69, 0x78, 0x69, 0x69, 0x6c, - 0x63, 0x64, 0x6d, 0x30, 0xd0, 0x33, 0x90, 0x21, - 0xb8, 0x92, 0x21, 0xb8, 0x94, 0x21, 0xb8, 0xd0, - 0x21, 0xb8, 0xd4, 0x21, 0xb8, 0xd2, 0x21, 0xb8, - 0x03, 0x22, 0xb8, 0x08, 0x22, 0xb8, 0x0b, 0x22, - 0xb8, 0x23, 0x22, 0xb8, 0x00, 0x00, 0x00, 0x25, - 0x22, 0xb8, 0x2b, 0x22, 0x2b, 0x22, 0x2b, 0x22, - 0x00, 0x00, 0x00, 0x2e, 0x22, 0x2e, 0x22, 0x2e, - 0x22, 0x00, 0x00, 0x00, 0x3c, 0x22, 0xb8, 0x43, - 0x22, 0xb8, 0x45, 0x22, 0xb8, 0x00, 0x00, 0x00, - 0x48, 0x22, 0xb8, 0x3d, 0x00, 0xb8, 0x00, 0x00, - 0x00, 0x61, 0x22, 0xb8, 0x4d, 0x22, 0xb8, 0x3c, - 0x00, 0xb8, 0x3e, 0x00, 0xb8, 0x64, 0x22, 0xb8, - 0x65, 0x22, 0xb8, 0x72, 0x22, 0xb8, 0x76, 0x22, - 0xb8, 0x7a, 0x22, 0xb8, 0x82, 0x22, 0xb8, 0x86, - 0x22, 0xb8, 0xa2, 0x22, 0xb8, 0xa8, 0x22, 0xb8, - 0xa9, 0x22, 0xb8, 0xab, 0x22, 0xb8, 0x7c, 0x22, - 0xb8, 0x91, 0x22, 0xb8, 0xb2, 0x22, 0x38, 0x03, - 0x08, 0x30, 0x31, 0x00, 0x31, 0x00, 0x30, 0x00, - 0x32, 0x30, 0x28, 0x00, 0x31, 0x00, 0x29, 0x00, - 0x28, 0x00, 0x31, 0x00, 0x30, 0x00, 0x29, 0x00, - 0x28, 0x32, 0x30, 0x29, 0x31, 0x00, 0x2e, 0x00, - 0x31, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x32, 0x30, - 0x2e, 0x28, 0x00, 0x61, 0x00, 0x29, 0x00, 0x41, - 0x00, 0x61, 0x00, 0x2b, 0x22, 0x00, 0x00, 0x00, - 0x00, 0x3a, 0x3a, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, - 0x3d, 0xdd, 0x2a, 0xb8, 0x6a, 0x56, 0x00, 0x4e, - 0x00, 0x28, 0x36, 0x3f, 0x59, 0x85, 0x8c, 0xa0, - 0xba, 0x3f, 0x51, 0x00, 0x26, 0x2c, 0x43, 0x57, - 0x6c, 0xa1, 0xb6, 0xc1, 0x9b, 0x52, 0x00, 0x5e, - 0x7a, 0x7f, 0x9d, 0xa6, 0xc1, 0xce, 0xe7, 0xb6, - 0x53, 0xc8, 0x53, 0xe3, 0x53, 0xd7, 0x56, 0x1f, - 0x57, 0xeb, 0x58, 0x02, 0x59, 0x0a, 0x59, 0x15, - 0x59, 0x27, 0x59, 0x73, 0x59, 0x50, 0x5b, 0x80, - 0x5b, 0xf8, 0x5b, 0x0f, 0x5c, 0x22, 0x5c, 0x38, - 0x5c, 0x6e, 0x5c, 0x71, 0x5c, 0xdb, 0x5d, 0xe5, - 0x5d, 0xf1, 0x5d, 0xfe, 0x5d, 0x72, 0x5e, 0x7a, - 0x5e, 0x7f, 0x5e, 0xf4, 0x5e, 0xfe, 0x5e, 0x0b, - 0x5f, 0x13, 0x5f, 0x50, 0x5f, 0x61, 0x5f, 0x73, - 0x5f, 0xc3, 0x5f, 0x08, 0x62, 0x36, 0x62, 0x4b, - 0x62, 0x2f, 0x65, 0x34, 0x65, 0x87, 0x65, 0x97, - 0x65, 0xa4, 0x65, 0xb9, 0x65, 0xe0, 0x65, 0xe5, - 0x65, 0xf0, 0x66, 0x08, 0x67, 0x28, 0x67, 0x20, - 0x6b, 0x62, 0x6b, 0x79, 0x6b, 0xb3, 0x6b, 0xcb, - 0x6b, 0xd4, 0x6b, 0xdb, 0x6b, 0x0f, 0x6c, 0x14, - 0x6c, 0x34, 0x6c, 0x6b, 0x70, 0x2a, 0x72, 0x36, - 0x72, 0x3b, 0x72, 0x3f, 0x72, 0x47, 0x72, 0x59, - 0x72, 0x5b, 0x72, 0xac, 0x72, 0x84, 0x73, 0x89, - 0x73, 0xdc, 0x74, 0xe6, 0x74, 0x18, 0x75, 0x1f, - 0x75, 0x28, 0x75, 0x30, 0x75, 0x8b, 0x75, 0x92, - 0x75, 0x76, 0x76, 0x7d, 0x76, 0xae, 0x76, 0xbf, - 0x76, 0xee, 0x76, 0xdb, 0x77, 0xe2, 0x77, 0xf3, - 0x77, 0x3a, 0x79, 0xb8, 0x79, 0xbe, 0x79, 0x74, - 0x7a, 0xcb, 0x7a, 0xf9, 0x7a, 0x73, 0x7c, 0xf8, - 0x7c, 0x36, 0x7f, 0x51, 0x7f, 0x8a, 0x7f, 0xbd, - 0x7f, 0x01, 0x80, 0x0c, 0x80, 0x12, 0x80, 0x33, - 0x80, 0x7f, 0x80, 0x89, 0x80, 0xe3, 0x81, 0x00, - 0x07, 0x10, 0x19, 0x29, 0x38, 0x3c, 0x8b, 0x8f, - 0x95, 0x4d, 0x86, 0x6b, 0x86, 0x40, 0x88, 0x4c, - 0x88, 0x63, 0x88, 0x7e, 0x89, 0x8b, 0x89, 0xd2, - 0x89, 0x00, 0x8a, 0x37, 0x8c, 0x46, 0x8c, 0x55, - 0x8c, 0x78, 0x8c, 0x9d, 0x8c, 0x64, 0x8d, 0x70, - 0x8d, 0xb3, 0x8d, 0xab, 0x8e, 0xca, 0x8e, 0x9b, - 0x8f, 0xb0, 0x8f, 0xb5, 0x8f, 0x91, 0x90, 0x49, - 0x91, 0xc6, 0x91, 0xcc, 0x91, 0xd1, 0x91, 0x77, - 0x95, 0x80, 0x95, 0x1c, 0x96, 0xb6, 0x96, 0xb9, - 0x96, 0xe8, 0x96, 0x51, 0x97, 0x5e, 0x97, 0x62, - 0x97, 0x69, 0x97, 0xcb, 0x97, 0xed, 0x97, 0xf3, - 0x97, 0x01, 0x98, 0xa8, 0x98, 0xdb, 0x98, 0xdf, - 0x98, 0x96, 0x99, 0x99, 0x99, 0xac, 0x99, 0xa8, - 0x9a, 0xd8, 0x9a, 0xdf, 0x9a, 0x25, 0x9b, 0x2f, - 0x9b, 0x32, 0x9b, 0x3c, 0x9b, 0x5a, 0x9b, 0xe5, - 0x9c, 0x75, 0x9e, 0x7f, 0x9e, 0xa5, 0x9e, 0x00, - 0x16, 0x1e, 0x28, 0x2c, 0x54, 0x58, 0x69, 0x6e, - 0x7b, 0x96, 0xa5, 0xad, 0xe8, 0xf7, 0xfb, 0x12, - 0x30, 0x00, 0x00, 0x41, 0x53, 0x44, 0x53, 0x45, - 0x53, 0x4b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x4d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x4f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x51, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x53, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x55, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x57, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x59, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x5b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x5d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x5f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x61, 0x30, 0x99, 0x30, 0x64, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x66, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x68, 0x30, 0x99, - 0x30, 0x6f, 0x30, 0x99, 0x30, 0x72, 0x30, 0x99, - 0x30, 0x75, 0x30, 0x99, 0x30, 0x78, 0x30, 0x99, - 0x30, 0x7b, 0x30, 0x99, 0x30, 0x46, 0x30, 0x99, - 0x30, 0x20, 0x00, 0x99, 0x30, 0x9d, 0x30, 0x99, - 0x30, 0x88, 0x30, 0x8a, 0x30, 0xab, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xad, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xaf, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x30, 0x99, - 0x30, 0xc4, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0xc6, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0xc8, 0x30, 0x99, 0x30, 0xcf, 0x30, 0x99, - 0x30, 0xd2, 0x30, 0x99, 0x30, 0xd5, 0x30, 0x99, - 0x30, 0xd8, 0x30, 0x99, 0x30, 0xdb, 0x30, 0x99, - 0x30, 0xa6, 0x30, 0x99, 0x30, 0xef, 0x30, 0x99, - 0x30, 0xfd, 0x30, 0x99, 0x30, 0xb3, 0x30, 0xc8, - 0x30, 0x00, 0x11, 0x00, 0x01, 0xaa, 0x02, 0xac, - 0xad, 0x03, 0x04, 0x05, 0xb0, 0xb1, 0xb2, 0xb3, - 0xb4, 0xb5, 0x1a, 0x06, 0x07, 0x08, 0x21, 0x09, - 0x11, 0x61, 0x11, 0x14, 0x11, 0x4c, 0x00, 0x01, - 0xb3, 0xb4, 0xb8, 0xba, 0xbf, 0xc3, 0xc5, 0x08, - 0xc9, 0xcb, 0x09, 0x0a, 0x0c, 0x0e, 0x0f, 0x13, - 0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x22, - 0x2c, 0x33, 0x38, 0xdd, 0xde, 0x43, 0x44, 0x45, - 0x70, 0x71, 0x74, 0x7d, 0x7e, 0x80, 0x8a, 0x8d, - 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, - 0x0a, 0x4e, 0x2d, 0x4e, 0x0b, 0x4e, 0x32, 0x75, - 0x59, 0x4e, 0x19, 0x4e, 0x01, 0x4e, 0x29, 0x59, - 0x30, 0x57, 0xba, 0x4e, 0x28, 0x00, 0x29, 0x00, - 0x00, 0x11, 0x02, 0x11, 0x03, 0x11, 0x05, 0x11, - 0x06, 0x11, 0x07, 0x11, 0x09, 0x11, 0x0b, 0x11, - 0x0c, 0x11, 0x0e, 0x11, 0x0f, 0x11, 0x10, 0x11, - 0x11, 0x11, 0x12, 0x11, 0x28, 0x00, 0x00, 0x11, - 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x02, 0x11, - 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x05, 0x11, - 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x09, 0x11, - 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, - 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0e, 0x11, - 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0c, 0x11, - 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, - 0x69, 0x11, 0x0c, 0x11, 0x65, 0x11, 0xab, 0x11, - 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, 0x69, 0x11, - 0x12, 0x11, 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, - 0x29, 0x00, 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, - 0xdb, 0x56, 0x94, 0x4e, 0x6d, 0x51, 0x03, 0x4e, - 0x6b, 0x51, 0x5d, 0x4e, 0x41, 0x53, 0x08, 0x67, - 0x6b, 0x70, 0x34, 0x6c, 0x28, 0x67, 0xd1, 0x91, - 0x1f, 0x57, 0xe5, 0x65, 0x2a, 0x68, 0x09, 0x67, - 0x3e, 0x79, 0x0d, 0x54, 0x79, 0x72, 0xa1, 0x8c, - 0x5d, 0x79, 0xb4, 0x52, 0xe3, 0x4e, 0x7c, 0x54, - 0x66, 0x5b, 0xe3, 0x76, 0x01, 0x4f, 0xc7, 0x8c, - 0x54, 0x53, 0x6d, 0x79, 0x11, 0x4f, 0xea, 0x81, - 0xf3, 0x81, 0x4f, 0x55, 0x7c, 0x5e, 0x87, 0x65, - 0x8f, 0x7b, 0x50, 0x54, 0x45, 0x32, 0x00, 0x31, - 0x00, 0x33, 0x00, 0x30, 0x00, 0x00, 0x11, 0x00, - 0x02, 0x03, 0x05, 0x06, 0x07, 0x09, 0x0b, 0x0c, - 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x00, 0x11, 0x00, - 0x61, 0x02, 0x61, 0x03, 0x61, 0x05, 0x61, 0x06, - 0x61, 0x07, 0x61, 0x09, 0x61, 0x0b, 0x61, 0x0c, - 0x61, 0x0e, 0x11, 0x61, 0x11, 0x00, 0x11, 0x0e, - 0x61, 0xb7, 0x00, 0x69, 0x0b, 0x11, 0x01, 0x63, - 0x00, 0x69, 0x0b, 0x11, 0x6e, 0x11, 0x00, 0x4e, - 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, 0x94, 0x4e, - 0x6d, 0x51, 0x03, 0x4e, 0x6b, 0x51, 0x5d, 0x4e, - 0x41, 0x53, 0x08, 0x67, 0x6b, 0x70, 0x34, 0x6c, - 0x28, 0x67, 0xd1, 0x91, 0x1f, 0x57, 0xe5, 0x65, - 0x2a, 0x68, 0x09, 0x67, 0x3e, 0x79, 0x0d, 0x54, - 0x79, 0x72, 0xa1, 0x8c, 0x5d, 0x79, 0xb4, 0x52, - 0xd8, 0x79, 0x37, 0x75, 0x73, 0x59, 0x69, 0x90, - 0x2a, 0x51, 0x70, 0x53, 0xe8, 0x6c, 0x05, 0x98, - 0x11, 0x4f, 0x99, 0x51, 0x63, 0x6b, 0x0a, 0x4e, - 0x2d, 0x4e, 0x0b, 0x4e, 0xe6, 0x5d, 0xf3, 0x53, - 0x3b, 0x53, 0x97, 0x5b, 0x66, 0x5b, 0xe3, 0x76, - 0x01, 0x4f, 0xc7, 0x8c, 0x54, 0x53, 0x1c, 0x59, - 0x33, 0x00, 0x36, 0x00, 0x34, 0x00, 0x30, 0x00, - 0x35, 0x30, 0x31, 0x00, 0x08, 0x67, 0x31, 0x00, - 0x30, 0x00, 0x08, 0x67, 0x48, 0x67, 0x65, 0x72, - 0x67, 0x65, 0x56, 0x4c, 0x54, 0x44, 0xa2, 0x30, - 0x00, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0b, 0x0d, - 0x0f, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d, - 0x1f, 0x22, 0x24, 0x26, 0x28, 0x29, 0x2a, 0x2b, - 0x2c, 0x2d, 0x30, 0x33, 0x36, 0x39, 0x3c, 0x3d, - 0x3e, 0x3f, 0x40, 0x42, 0x44, 0x46, 0x47, 0x48, - 0x49, 0x4a, 0x4b, 0x4d, 0x4e, 0x4f, 0x50, 0xe4, - 0x4e, 0x8c, 0x54, 0xa1, 0x30, 0x01, 0x30, 0x5b, - 0x27, 0x01, 0x4a, 0x34, 0x00, 0x01, 0x52, 0x39, - 0x01, 0xa2, 0x30, 0x00, 0x5a, 0x49, 0xa4, 0x30, - 0x00, 0x27, 0x4f, 0x0c, 0xa4, 0x30, 0x00, 0x4f, - 0x1d, 0x02, 0x05, 0x4f, 0xa8, 0x30, 0x00, 0x11, - 0x07, 0x54, 0x21, 0xa8, 0x30, 0x00, 0x54, 0x03, - 0x54, 0xa4, 0x30, 0x06, 0x4f, 0x15, 0x06, 0x58, - 0x3c, 0x07, 0x00, 0x46, 0xab, 0x30, 0x00, 0x3e, - 0x18, 0x1d, 0x00, 0x42, 0x3f, 0x51, 0xac, 0x30, - 0x00, 0x41, 0x47, 0x00, 0x47, 0x32, 0xae, 0x30, - 0xac, 0x30, 0xae, 0x30, 0x00, 0x1d, 0x4e, 0xad, - 0x30, 0x00, 0x38, 0x3d, 0x4f, 0x01, 0x3e, 0x13, - 0x4f, 0xad, 0x30, 0xed, 0x30, 0xad, 0x30, 0x00, - 0x40, 0x03, 0x3c, 0x33, 0xad, 0x30, 0x00, 0x40, - 0x34, 0x4f, 0x1b, 0x3e, 0xad, 0x30, 0x00, 0x40, - 0x42, 0x16, 0x1b, 0xb0, 0x30, 0x00, 0x39, 0x30, - 0xa4, 0x30, 0x0c, 0x45, 0x3c, 0x24, 0x4f, 0x0b, - 0x47, 0x18, 0x00, 0x49, 0xaf, 0x30, 0x00, 0x3e, - 0x4d, 0x1e, 0xb1, 0x30, 0x00, 0x4b, 0x08, 0x02, - 0x3a, 0x19, 0x02, 0x4b, 0x2c, 0xa4, 0x30, 0x11, - 0x00, 0x0b, 0x47, 0xb5, 0x30, 0x00, 0x3e, 0x0c, - 0x47, 0x2b, 0xb0, 0x30, 0x07, 0x3a, 0x43, 0x00, - 0xb9, 0x30, 0x02, 0x3a, 0x08, 0x02, 0x3a, 0x0f, - 0x07, 0x43, 0x00, 0xb7, 0x30, 0x10, 0x00, 0x12, - 0x34, 0x11, 0x3c, 0x13, 0x17, 0xa4, 0x30, 0x2a, - 0x1f, 0x24, 0x2b, 0x00, 0x20, 0xbb, 0x30, 0x16, - 0x41, 0x00, 0x38, 0x0d, 0xc4, 0x30, 0x0d, 0x38, - 0x00, 0xd0, 0x30, 0x00, 0x2c, 0x1c, 0x1b, 0xa2, - 0x30, 0x32, 0x00, 0x17, 0x26, 0x49, 0xaf, 0x30, - 0x25, 0x00, 0x3c, 0xb3, 0x30, 0x21, 0x00, 0x20, - 0x38, 0xa1, 0x30, 0x34, 0x00, 0x48, 0x22, 0x28, - 0xa3, 0x30, 0x32, 0x00, 0x59, 0x25, 0xa7, 0x30, - 0x2f, 0x1c, 0x10, 0x00, 0x44, 0xd5, 0x30, 0x00, - 0x14, 0x1e, 0xaf, 0x30, 0x29, 0x00, 0x10, 0x4d, - 0x3c, 0xda, 0x30, 0xbd, 0x30, 0xb8, 0x30, 0x22, - 0x13, 0x1a, 0x20, 0x33, 0x0c, 0x22, 0x3b, 0x01, - 0x22, 0x44, 0x00, 0x21, 0x44, 0x07, 0xa4, 0x30, - 0x39, 0x00, 0x4f, 0x24, 0xc8, 0x30, 0x14, 0x23, - 0x00, 0xdb, 0x30, 0xf3, 0x30, 0xc9, 0x30, 0x14, - 0x2a, 0x00, 0x12, 0x33, 0x22, 0x12, 0x33, 0x2a, - 0xa4, 0x30, 0x3a, 0x00, 0x0b, 0x49, 0xa4, 0x30, - 0x3a, 0x00, 0x47, 0x3a, 0x1f, 0x2b, 0x3a, 0x47, - 0x0b, 0xb7, 0x30, 0x27, 0x3c, 0x00, 0x30, 0x3c, - 0xaf, 0x30, 0x30, 0x00, 0x3e, 0x44, 0xdf, 0x30, - 0xea, 0x30, 0xd0, 0x30, 0x0f, 0x1a, 0x00, 0x2c, - 0x1b, 0xe1, 0x30, 0xac, 0x30, 0xac, 0x30, 0x35, - 0x00, 0x1c, 0x47, 0x35, 0x50, 0x1c, 0x3f, 0xa2, - 0x30, 0x42, 0x5a, 0x27, 0x42, 0x5a, 0x49, 0x44, - 0x00, 0x51, 0xc3, 0x30, 0x27, 0x00, 0x05, 0x28, - 0xea, 0x30, 0xe9, 0x30, 0xd4, 0x30, 0x17, 0x00, - 0x28, 0xd6, 0x30, 0x15, 0x26, 0x00, 0x15, 0xec, - 0x30, 0xe0, 0x30, 0xb2, 0x30, 0x3a, 0x41, 0x16, - 0x00, 0x41, 0xc3, 0x30, 0x2c, 0x00, 0x05, 0x30, - 0x00, 0xb9, 0x70, 0x31, 0x00, 0x30, 0x00, 0xb9, - 0x70, 0x32, 0x00, 0x30, 0x00, 0xb9, 0x70, 0x68, - 0x50, 0x61, 0x64, 0x61, 0x41, 0x55, 0x62, 0x61, - 0x72, 0x6f, 0x56, 0x70, 0x63, 0x64, 0x6d, 0x64, - 0x00, 0x6d, 0x00, 0xb2, 0x00, 0x49, 0x00, 0x55, - 0x00, 0x73, 0x5e, 0x10, 0x62, 0x2d, 0x66, 0x8c, - 0x54, 0x27, 0x59, 0x63, 0x6b, 0x0e, 0x66, 0xbb, - 0x6c, 0x2a, 0x68, 0x0f, 0x5f, 0x1a, 0x4f, 0x3e, - 0x79, 0x70, 0x00, 0x41, 0x6e, 0x00, 0x41, 0xbc, - 0x03, 0x41, 0x6d, 0x00, 0x41, 0x6b, 0x00, 0x41, - 0x4b, 0x00, 0x42, 0x4d, 0x00, 0x42, 0x47, 0x00, - 0x42, 0x63, 0x61, 0x6c, 0x6b, 0x63, 0x61, 0x6c, - 0x70, 0x00, 0x46, 0x6e, 0x00, 0x46, 0xbc, 0x03, - 0x46, 0xbc, 0x03, 0x67, 0x6d, 0x00, 0x67, 0x6b, - 0x00, 0x67, 0x48, 0x00, 0x7a, 0x6b, 0x48, 0x7a, - 0x4d, 0x48, 0x7a, 0x47, 0x48, 0x7a, 0x54, 0x48, - 0x7a, 0xbc, 0x03, 0x13, 0x21, 0x6d, 0x00, 0x13, - 0x21, 0x64, 0x00, 0x13, 0x21, 0x6b, 0x00, 0x13, - 0x21, 0x66, 0x00, 0x6d, 0x6e, 0x00, 0x6d, 0xbc, - 0x03, 0x6d, 0x6d, 0x00, 0x6d, 0x63, 0x00, 0x6d, - 0x6b, 0x00, 0x6d, 0x63, 0x00, 0x0a, 0x0a, 0x4f, - 0x00, 0x0a, 0x4f, 0x6d, 0x00, 0xb2, 0x00, 0x63, - 0x00, 0x08, 0x0a, 0x4f, 0x0a, 0x0a, 0x50, 0x00, - 0x0a, 0x50, 0x6d, 0x00, 0xb3, 0x00, 0x6b, 0x00, - 0x6d, 0x00, 0xb3, 0x00, 0x6d, 0x00, 0x15, 0x22, - 0x73, 0x00, 0x6d, 0x00, 0x15, 0x22, 0x73, 0x00, - 0xb2, 0x00, 0x50, 0x61, 0x6b, 0x50, 0x61, 0x4d, - 0x50, 0x61, 0x47, 0x50, 0x61, 0x72, 0x61, 0x64, - 0x72, 0x61, 0x64, 0xd1, 0x73, 0x72, 0x00, 0x61, - 0x00, 0x64, 0x00, 0x15, 0x22, 0x73, 0x00, 0xb2, - 0x00, 0x70, 0x00, 0x73, 0x6e, 0x00, 0x73, 0xbc, - 0x03, 0x73, 0x6d, 0x00, 0x73, 0x70, 0x00, 0x56, - 0x6e, 0x00, 0x56, 0xbc, 0x03, 0x56, 0x6d, 0x00, - 0x56, 0x6b, 0x00, 0x56, 0x4d, 0x00, 0x56, 0x70, - 0x00, 0x57, 0x6e, 0x00, 0x57, 0xbc, 0x03, 0x57, - 0x6d, 0x00, 0x57, 0x6b, 0x00, 0x57, 0x4d, 0x00, - 0x57, 0x6b, 0x00, 0xa9, 0x03, 0x4d, 0x00, 0xa9, - 0x03, 0x61, 0x2e, 0x6d, 0x2e, 0x42, 0x71, 0x63, - 0x63, 0x63, 0x64, 0x43, 0xd1, 0x6b, 0x67, 0x43, - 0x6f, 0x2e, 0x64, 0x42, 0x47, 0x79, 0x68, 0x61, - 0x48, 0x50, 0x69, 0x6e, 0x4b, 0x4b, 0x4b, 0x4d, - 0x6b, 0x74, 0x6c, 0x6d, 0x6c, 0x6e, 0x6c, 0x6f, - 0x67, 0x6c, 0x78, 0x6d, 0x62, 0x6d, 0x69, 0x6c, - 0x6d, 0x6f, 0x6c, 0x50, 0x48, 0x70, 0x2e, 0x6d, - 0x2e, 0x50, 0x50, 0x4d, 0x50, 0x52, 0x73, 0x72, - 0x53, 0x76, 0x57, 0x62, 0x56, 0xd1, 0x6d, 0x41, - 0xd1, 0x6d, 0x31, 0x00, 0xe5, 0x65, 0x31, 0x00, - 0x30, 0x00, 0xe5, 0x65, 0x32, 0x00, 0x30, 0x00, - 0xe5, 0x65, 0x33, 0x00, 0x30, 0x00, 0xe5, 0x65, - 0x67, 0x61, 0x6c, 0x4a, 0x04, 0x4c, 0x04, 0x43, - 0x46, 0x51, 0x26, 0x01, 0x53, 0x01, 0x27, 0xa7, - 0x37, 0xab, 0x6b, 0x02, 0x52, 0xab, 0x48, 0x8c, - 0xf4, 0x66, 0xca, 0x8e, 0xc8, 0x8c, 0xd1, 0x6e, - 0x32, 0x4e, 0xe5, 0x53, 0x9c, 0x9f, 0x9c, 0x9f, - 0x51, 0x59, 0xd1, 0x91, 0x87, 0x55, 0x48, 0x59, - 0xf6, 0x61, 0x69, 0x76, 0x85, 0x7f, 0x3f, 0x86, - 0xba, 0x87, 0xf8, 0x88, 0x8f, 0x90, 0x02, 0x6a, - 0x1b, 0x6d, 0xd9, 0x70, 0xde, 0x73, 0x3d, 0x84, - 0x6a, 0x91, 0xf1, 0x99, 0x82, 0x4e, 0x75, 0x53, - 0x04, 0x6b, 0x1b, 0x72, 0x2d, 0x86, 0x1e, 0x9e, - 0x50, 0x5d, 0xeb, 0x6f, 0xcd, 0x85, 0x64, 0x89, - 0xc9, 0x62, 0xd8, 0x81, 0x1f, 0x88, 0xca, 0x5e, - 0x17, 0x67, 0x6a, 0x6d, 0xfc, 0x72, 0xce, 0x90, - 0x86, 0x4f, 0xb7, 0x51, 0xde, 0x52, 0xc4, 0x64, - 0xd3, 0x6a, 0x10, 0x72, 0xe7, 0x76, 0x01, 0x80, - 0x06, 0x86, 0x5c, 0x86, 0xef, 0x8d, 0x32, 0x97, - 0x6f, 0x9b, 0xfa, 0x9d, 0x8c, 0x78, 0x7f, 0x79, - 0xa0, 0x7d, 0xc9, 0x83, 0x04, 0x93, 0x7f, 0x9e, - 0xd6, 0x8a, 0xdf, 0x58, 0x04, 0x5f, 0x60, 0x7c, - 0x7e, 0x80, 0x62, 0x72, 0xca, 0x78, 0xc2, 0x8c, - 0xf7, 0x96, 0xd8, 0x58, 0x62, 0x5c, 0x13, 0x6a, - 0xda, 0x6d, 0x0f, 0x6f, 0x2f, 0x7d, 0x37, 0x7e, - 0x4b, 0x96, 0xd2, 0x52, 0x8b, 0x80, 0xdc, 0x51, - 0xcc, 0x51, 0x1c, 0x7a, 0xbe, 0x7d, 0xf1, 0x83, - 0x75, 0x96, 0x80, 0x8b, 0xcf, 0x62, 0x02, 0x6a, - 0xfe, 0x8a, 0x39, 0x4e, 0xe7, 0x5b, 0x12, 0x60, - 0x87, 0x73, 0x70, 0x75, 0x17, 0x53, 0xfb, 0x78, - 0xbf, 0x4f, 0xa9, 0x5f, 0x0d, 0x4e, 0xcc, 0x6c, - 0x78, 0x65, 0x22, 0x7d, 0xc3, 0x53, 0x5e, 0x58, - 0x01, 0x77, 0x49, 0x84, 0xaa, 0x8a, 0xba, 0x6b, - 0xb0, 0x8f, 0x88, 0x6c, 0xfe, 0x62, 0xe5, 0x82, - 0xa0, 0x63, 0x65, 0x75, 0xae, 0x4e, 0x69, 0x51, - 0xc9, 0x51, 0x81, 0x68, 0xe7, 0x7c, 0x6f, 0x82, - 0xd2, 0x8a, 0xcf, 0x91, 0xf5, 0x52, 0x42, 0x54, - 0x73, 0x59, 0xec, 0x5e, 0xc5, 0x65, 0xfe, 0x6f, - 0x2a, 0x79, 0xad, 0x95, 0x6a, 0x9a, 0x97, 0x9e, - 0xce, 0x9e, 0x9b, 0x52, 0xc6, 0x66, 0x77, 0x6b, - 0x62, 0x8f, 0x74, 0x5e, 0x90, 0x61, 0x00, 0x62, - 0x9a, 0x64, 0x23, 0x6f, 0x49, 0x71, 0x89, 0x74, - 0xca, 0x79, 0xf4, 0x7d, 0x6f, 0x80, 0x26, 0x8f, - 0xee, 0x84, 0x23, 0x90, 0x4a, 0x93, 0x17, 0x52, - 0xa3, 0x52, 0xbd, 0x54, 0xc8, 0x70, 0xc2, 0x88, - 0xaa, 0x8a, 0xc9, 0x5e, 0xf5, 0x5f, 0x7b, 0x63, - 0xae, 0x6b, 0x3e, 0x7c, 0x75, 0x73, 0xe4, 0x4e, - 0xf9, 0x56, 0xe7, 0x5b, 0xba, 0x5d, 0x1c, 0x60, - 0xb2, 0x73, 0x69, 0x74, 0x9a, 0x7f, 0x46, 0x80, - 0x34, 0x92, 0xf6, 0x96, 0x48, 0x97, 0x18, 0x98, - 0x8b, 0x4f, 0xae, 0x79, 0xb4, 0x91, 0xb8, 0x96, - 0xe1, 0x60, 0x86, 0x4e, 0xda, 0x50, 0xee, 0x5b, - 0x3f, 0x5c, 0x99, 0x65, 0x02, 0x6a, 0xce, 0x71, - 0x42, 0x76, 0xfc, 0x84, 0x7c, 0x90, 0x8d, 0x9f, - 0x88, 0x66, 0x2e, 0x96, 0x89, 0x52, 0x7b, 0x67, - 0xf3, 0x67, 0x41, 0x6d, 0x9c, 0x6e, 0x09, 0x74, - 0x59, 0x75, 0x6b, 0x78, 0x10, 0x7d, 0x5e, 0x98, - 0x6d, 0x51, 0x2e, 0x62, 0x78, 0x96, 0x2b, 0x50, - 0x19, 0x5d, 0xea, 0x6d, 0x2a, 0x8f, 0x8b, 0x5f, - 0x44, 0x61, 0x17, 0x68, 0x87, 0x73, 0x86, 0x96, - 0x29, 0x52, 0x0f, 0x54, 0x65, 0x5c, 0x13, 0x66, - 0x4e, 0x67, 0xa8, 0x68, 0xe5, 0x6c, 0x06, 0x74, - 0xe2, 0x75, 0x79, 0x7f, 0xcf, 0x88, 0xe1, 0x88, - 0xcc, 0x91, 0xe2, 0x96, 0x3f, 0x53, 0xba, 0x6e, - 0x1d, 0x54, 0xd0, 0x71, 0x98, 0x74, 0xfa, 0x85, - 0xa3, 0x96, 0x57, 0x9c, 0x9f, 0x9e, 0x97, 0x67, - 0xcb, 0x6d, 0xe8, 0x81, 0xcb, 0x7a, 0x20, 0x7b, - 0x92, 0x7c, 0xc0, 0x72, 0x99, 0x70, 0x58, 0x8b, - 0xc0, 0x4e, 0x36, 0x83, 0x3a, 0x52, 0x07, 0x52, - 0xa6, 0x5e, 0xd3, 0x62, 0xd6, 0x7c, 0x85, 0x5b, - 0x1e, 0x6d, 0xb4, 0x66, 0x3b, 0x8f, 0x4c, 0x88, - 0x4d, 0x96, 0x8b, 0x89, 0xd3, 0x5e, 0x40, 0x51, - 0xc0, 0x55, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x58, - 0x00, 0x00, 0x74, 0x66, 0x00, 0x00, 0x00, 0x00, - 0xde, 0x51, 0x2a, 0x73, 0xca, 0x76, 0x3c, 0x79, - 0x5e, 0x79, 0x65, 0x79, 0x8f, 0x79, 0x56, 0x97, - 0xbe, 0x7c, 0xbd, 0x7f, 0x00, 0x00, 0x12, 0x86, - 0x00, 0x00, 0xf8, 0x8a, 0x00, 0x00, 0x00, 0x00, - 0x38, 0x90, 0xfd, 0x90, 0xef, 0x98, 0xfc, 0x98, - 0x28, 0x99, 0xb4, 0x9d, 0xde, 0x90, 0xb7, 0x96, - 0xae, 0x4f, 0xe7, 0x50, 0x4d, 0x51, 0xc9, 0x52, - 0xe4, 0x52, 0x51, 0x53, 0x9d, 0x55, 0x06, 0x56, - 0x68, 0x56, 0x40, 0x58, 0xa8, 0x58, 0x64, 0x5c, - 0x6e, 0x5c, 0x94, 0x60, 0x68, 0x61, 0x8e, 0x61, - 0xf2, 0x61, 0x4f, 0x65, 0xe2, 0x65, 0x91, 0x66, - 0x85, 0x68, 0x77, 0x6d, 0x1a, 0x6e, 0x22, 0x6f, - 0x6e, 0x71, 0x2b, 0x72, 0x22, 0x74, 0x91, 0x78, - 0x3e, 0x79, 0x49, 0x79, 0x48, 0x79, 0x50, 0x79, - 0x56, 0x79, 0x5d, 0x79, 0x8d, 0x79, 0x8e, 0x79, - 0x40, 0x7a, 0x81, 0x7a, 0xc0, 0x7b, 0xf4, 0x7d, - 0x09, 0x7e, 0x41, 0x7e, 0x72, 0x7f, 0x05, 0x80, - 0xed, 0x81, 0x79, 0x82, 0x79, 0x82, 0x57, 0x84, - 0x10, 0x89, 0x96, 0x89, 0x01, 0x8b, 0x39, 0x8b, - 0xd3, 0x8c, 0x08, 0x8d, 0xb6, 0x8f, 0x38, 0x90, - 0xe3, 0x96, 0xff, 0x97, 0x3b, 0x98, 0x75, 0x60, - 0xee, 0x42, 0x18, 0x82, 0x02, 0x26, 0x4e, 0xb5, - 0x51, 0x68, 0x51, 0x80, 0x4f, 0x45, 0x51, 0x80, - 0x51, 0xc7, 0x52, 0xfa, 0x52, 0x9d, 0x55, 0x55, - 0x55, 0x99, 0x55, 0xe2, 0x55, 0x5a, 0x58, 0xb3, - 0x58, 0x44, 0x59, 0x54, 0x59, 0x62, 0x5a, 0x28, - 0x5b, 0xd2, 0x5e, 0xd9, 0x5e, 0x69, 0x5f, 0xad, - 0x5f, 0xd8, 0x60, 0x4e, 0x61, 0x08, 0x61, 0x8e, - 0x61, 0x60, 0x61, 0xf2, 0x61, 0x34, 0x62, 0xc4, - 0x63, 0x1c, 0x64, 0x52, 0x64, 0x56, 0x65, 0x74, - 0x66, 0x17, 0x67, 0x1b, 0x67, 0x56, 0x67, 0x79, - 0x6b, 0xba, 0x6b, 0x41, 0x6d, 0xdb, 0x6e, 0xcb, - 0x6e, 0x22, 0x6f, 0x1e, 0x70, 0x6e, 0x71, 0xa7, - 0x77, 0x35, 0x72, 0xaf, 0x72, 0x2a, 0x73, 0x71, - 0x74, 0x06, 0x75, 0x3b, 0x75, 0x1d, 0x76, 0x1f, - 0x76, 0xca, 0x76, 0xdb, 0x76, 0xf4, 0x76, 0x4a, - 0x77, 0x40, 0x77, 0xcc, 0x78, 0xb1, 0x7a, 0xc0, - 0x7b, 0x7b, 0x7c, 0x5b, 0x7d, 0xf4, 0x7d, 0x3e, - 0x7f, 0x05, 0x80, 0x52, 0x83, 0xef, 0x83, 0x79, - 0x87, 0x41, 0x89, 0x86, 0x89, 0x96, 0x89, 0xbf, - 0x8a, 0xf8, 0x8a, 0xcb, 0x8a, 0x01, 0x8b, 0xfe, - 0x8a, 0xed, 0x8a, 0x39, 0x8b, 0x8a, 0x8b, 0x08, - 0x8d, 0x38, 0x8f, 0x72, 0x90, 0x99, 0x91, 0x76, - 0x92, 0x7c, 0x96, 0xe3, 0x96, 0x56, 0x97, 0xdb, - 0x97, 0xff, 0x97, 0x0b, 0x98, 0x3b, 0x98, 0x12, - 0x9b, 0x9c, 0x9f, 0x4a, 0x28, 0x44, 0x28, 0xd5, - 0x33, 0x9d, 0x3b, 0x18, 0x40, 0x39, 0x40, 0x49, - 0x52, 0xd0, 0x5c, 0xd3, 0x7e, 0x43, 0x9f, 0x8e, - 0x9f, 0x2a, 0xa0, 0x02, 0x66, 0x66, 0x66, 0x69, - 0x66, 0x6c, 0x66, 0x66, 0x69, 0x66, 0x66, 0x6c, - 0x7f, 0x01, 0x74, 0x73, 0x00, 0x74, 0x65, 0x05, - 0x0f, 0x11, 0x0f, 0x00, 0x0f, 0x06, 0x19, 0x11, - 0x0f, 0x08, 0xd9, 0x05, 0xb4, 0x05, 0x00, 0x00, - 0x00, 0x00, 0xf2, 0x05, 0xb7, 0x05, 0xd0, 0x05, - 0x12, 0x00, 0x03, 0x04, 0x0b, 0x0c, 0x0d, 0x18, - 0x1a, 0xe9, 0x05, 0xc1, 0x05, 0xe9, 0x05, 0xc2, - 0x05, 0x49, 0xfb, 0xc1, 0x05, 0x49, 0xfb, 0xc2, - 0x05, 0xd0, 0x05, 0xb7, 0x05, 0xd0, 0x05, 0xb8, - 0x05, 0xd0, 0x05, 0xbc, 0x05, 0xd8, 0x05, 0xbc, - 0x05, 0xde, 0x05, 0xbc, 0x05, 0xe0, 0x05, 0xbc, - 0x05, 0xe3, 0x05, 0xbc, 0x05, 0xb9, 0x05, 0x2d, - 0x03, 0x2e, 0x03, 0x2f, 0x03, 0x30, 0x03, 0x31, - 0x03, 0x1c, 0x00, 0x18, 0x06, 0x22, 0x06, 0x2b, - 0x06, 0xd0, 0x05, 0xdc, 0x05, 0x71, 0x06, 0x00, - 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0d, 0x0d, 0x0d, - 0x0d, 0x0f, 0x0f, 0x0f, 0x0f, 0x09, 0x09, 0x09, - 0x09, 0x0e, 0x0e, 0x0e, 0x0e, 0x08, 0x08, 0x08, - 0x08, 0x33, 0x33, 0x33, 0x33, 0x35, 0x35, 0x35, - 0x35, 0x13, 0x13, 0x13, 0x13, 0x12, 0x12, 0x12, - 0x12, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, - 0x16, 0x1c, 0x1c, 0x1b, 0x1b, 0x1d, 0x1d, 0x17, - 0x17, 0x27, 0x27, 0x20, 0x20, 0x38, 0x38, 0x38, - 0x38, 0x3e, 0x3e, 0x3e, 0x3e, 0x42, 0x42, 0x42, - 0x42, 0x40, 0x40, 0x40, 0x40, 0x49, 0x49, 0x4a, - 0x4a, 0x4a, 0x4a, 0x4f, 0x4f, 0x50, 0x50, 0x50, - 0x50, 0x4d, 0x4d, 0x4d, 0x4d, 0x61, 0x61, 0x62, - 0x62, 0x49, 0x06, 0x64, 0x64, 0x64, 0x64, 0x7e, - 0x7e, 0x7d, 0x7d, 0x7f, 0x7f, 0x2e, 0x82, 0x82, - 0x7c, 0x7c, 0x80, 0x80, 0x87, 0x87, 0x87, 0x87, - 0x00, 0x00, 0x26, 0x06, 0x00, 0x01, 0x00, 0x01, - 0x00, 0xaf, 0x00, 0xaf, 0x00, 0x22, 0x00, 0x22, - 0x00, 0xa1, 0x00, 0xa1, 0x00, 0xa0, 0x00, 0xa0, - 0x00, 0xa2, 0x00, 0xa2, 0x00, 0xaa, 0x00, 0xaa, - 0x00, 0xaa, 0x00, 0x23, 0x00, 0x23, 0x00, 0x23, - 0xcc, 0x06, 0x00, 0x00, 0x00, 0x00, 0x26, 0x06, - 0x00, 0x06, 0x00, 0x07, 0x00, 0x1f, 0x00, 0x23, - 0x00, 0x24, 0x02, 0x06, 0x02, 0x07, 0x02, 0x08, - 0x02, 0x1f, 0x02, 0x23, 0x02, 0x24, 0x04, 0x06, - 0x04, 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x23, - 0x04, 0x24, 0x05, 0x06, 0x05, 0x1f, 0x05, 0x23, - 0x05, 0x24, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, - 0x07, 0x1f, 0x08, 0x06, 0x08, 0x07, 0x08, 0x1f, - 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x08, 0x0d, 0x1f, - 0x0f, 0x07, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, - 0x10, 0x08, 0x10, 0x1f, 0x11, 0x07, 0x11, 0x1f, - 0x12, 0x1f, 0x13, 0x06, 0x13, 0x1f, 0x14, 0x06, - 0x14, 0x1f, 0x1b, 0x06, 0x1b, 0x07, 0x1b, 0x08, - 0x1b, 0x1f, 0x1b, 0x23, 0x1b, 0x24, 0x1c, 0x07, - 0x1c, 0x1f, 0x1c, 0x23, 0x1c, 0x24, 0x1d, 0x01, - 0x1d, 0x06, 0x1d, 0x07, 0x1d, 0x08, 0x1d, 0x1e, - 0x1d, 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x06, - 0x1e, 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x23, - 0x1e, 0x24, 0x1f, 0x06, 0x1f, 0x07, 0x1f, 0x08, - 0x1f, 0x1f, 0x1f, 0x23, 0x1f, 0x24, 0x20, 0x06, - 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, 0x23, - 0x20, 0x24, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x23, - 0x21, 0x24, 0x24, 0x06, 0x24, 0x07, 0x24, 0x08, - 0x24, 0x1f, 0x24, 0x23, 0x24, 0x24, 0x0a, 0x4a, - 0x0b, 0x4a, 0x23, 0x4a, 0x20, 0x00, 0x4c, 0x06, - 0x51, 0x06, 0x51, 0x06, 0xff, 0x00, 0x1f, 0x26, - 0x06, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x1f, 0x00, - 0x20, 0x00, 0x23, 0x00, 0x24, 0x02, 0x0b, 0x02, - 0x0c, 0x02, 0x1f, 0x02, 0x20, 0x02, 0x23, 0x02, - 0x24, 0x04, 0x0b, 0x04, 0x0c, 0x04, 0x1f, 0x26, - 0x06, 0x04, 0x20, 0x04, 0x23, 0x04, 0x24, 0x05, - 0x0b, 0x05, 0x0c, 0x05, 0x1f, 0x05, 0x20, 0x05, - 0x23, 0x05, 0x24, 0x1b, 0x23, 0x1b, 0x24, 0x1c, - 0x23, 0x1c, 0x24, 0x1d, 0x01, 0x1d, 0x1e, 0x1d, - 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x1f, 0x1e, - 0x23, 0x1e, 0x24, 0x1f, 0x01, 0x1f, 0x1f, 0x20, - 0x0b, 0x20, 0x0c, 0x20, 0x1f, 0x20, 0x20, 0x20, - 0x23, 0x20, 0x24, 0x23, 0x4a, 0x24, 0x0b, 0x24, - 0x0c, 0x24, 0x1f, 0x24, 0x20, 0x24, 0x23, 0x24, - 0x24, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, - 0x1f, 0x00, 0x21, 0x02, 0x06, 0x02, 0x07, 0x02, - 0x08, 0x02, 0x1f, 0x02, 0x21, 0x04, 0x06, 0x04, - 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x21, 0x05, - 0x1f, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, 0x07, - 0x1f, 0x08, 0x06, 0x08, 0x1f, 0x0d, 0x06, 0x0d, - 0x07, 0x0d, 0x08, 0x0d, 0x1f, 0x0f, 0x07, 0x0f, - 0x08, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, 0x10, - 0x08, 0x10, 0x1f, 0x11, 0x07, 0x12, 0x1f, 0x13, - 0x06, 0x13, 0x1f, 0x14, 0x06, 0x14, 0x1f, 0x1b, - 0x06, 0x1b, 0x07, 0x1b, 0x08, 0x1b, 0x1f, 0x1c, - 0x07, 0x1c, 0x1f, 0x1d, 0x06, 0x1d, 0x07, 0x1d, - 0x08, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x06, 0x1e, - 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x21, 0x1f, - 0x06, 0x1f, 0x07, 0x1f, 0x08, 0x1f, 0x1f, 0x20, - 0x06, 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, - 0x21, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x4a, 0x24, - 0x06, 0x24, 0x07, 0x24, 0x08, 0x24, 0x1f, 0x24, - 0x21, 0x00, 0x1f, 0x00, 0x21, 0x02, 0x1f, 0x02, - 0x21, 0x04, 0x1f, 0x04, 0x21, 0x05, 0x1f, 0x05, - 0x21, 0x0d, 0x1f, 0x0d, 0x21, 0x0e, 0x1f, 0x0e, - 0x21, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x1f, 0x20, - 0x1f, 0x20, 0x21, 0x24, 0x1f, 0x24, 0x21, 0x40, - 0x06, 0x4e, 0x06, 0x51, 0x06, 0x27, 0x06, 0x10, - 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, - 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, - 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, - 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, - 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, - 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, - 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x10, - 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, - 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, - 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, - 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, - 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, - 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, - 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x0d, - 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0c, - 0x20, 0x0d, 0x20, 0x10, 0x1e, 0x0c, 0x05, 0x0c, - 0x06, 0x0c, 0x07, 0x0d, 0x05, 0x0d, 0x06, 0x0d, - 0x07, 0x10, 0x1e, 0x11, 0x1e, 0x00, 0x24, 0x00, - 0x24, 0x2a, 0x06, 0x00, 0x02, 0x1b, 0x00, 0x03, - 0x02, 0x00, 0x03, 0x02, 0x00, 0x03, 0x1b, 0x00, - 0x04, 0x1b, 0x00, 0x1b, 0x02, 0x00, 0x1b, 0x03, - 0x00, 0x1b, 0x04, 0x02, 0x1b, 0x03, 0x02, 0x1b, - 0x03, 0x03, 0x1b, 0x20, 0x03, 0x1b, 0x1f, 0x09, - 0x03, 0x02, 0x09, 0x02, 0x03, 0x09, 0x02, 0x1f, - 0x09, 0x1b, 0x03, 0x09, 0x1b, 0x03, 0x09, 0x1b, - 0x02, 0x09, 0x1b, 0x1b, 0x09, 0x1b, 0x1b, 0x0b, - 0x03, 0x03, 0x0b, 0x03, 0x03, 0x0b, 0x1b, 0x1b, - 0x0a, 0x03, 0x1b, 0x0a, 0x03, 0x1b, 0x0a, 0x02, - 0x20, 0x0a, 0x1b, 0x04, 0x0a, 0x1b, 0x04, 0x0a, - 0x1b, 0x1b, 0x0a, 0x1b, 0x1b, 0x0c, 0x03, 0x1f, - 0x0c, 0x04, 0x1b, 0x0c, 0x04, 0x1b, 0x0d, 0x1b, - 0x03, 0x0d, 0x1b, 0x03, 0x0d, 0x1b, 0x1b, 0x0d, - 0x1b, 0x20, 0x0f, 0x02, 0x1b, 0x0f, 0x1b, 0x1b, - 0x0f, 0x1b, 0x1b, 0x0f, 0x1b, 0x1f, 0x10, 0x1b, - 0x1b, 0x10, 0x1b, 0x20, 0x10, 0x1b, 0x1f, 0x17, - 0x04, 0x1b, 0x17, 0x04, 0x1b, 0x18, 0x1b, 0x03, - 0x18, 0x1b, 0x1b, 0x1a, 0x03, 0x1b, 0x1a, 0x03, - 0x20, 0x1a, 0x03, 0x1f, 0x1a, 0x02, 0x02, 0x1a, - 0x02, 0x02, 0x1a, 0x04, 0x1b, 0x1a, 0x04, 0x1b, - 0x1a, 0x1b, 0x03, 0x1a, 0x1b, 0x03, 0x1b, 0x03, - 0x02, 0x1b, 0x03, 0x1b, 0x1b, 0x03, 0x20, 0x1b, - 0x02, 0x03, 0x1b, 0x02, 0x1b, 0x1b, 0x04, 0x02, - 0x1b, 0x04, 0x1b, 0x28, 0x06, 0x1d, 0x04, 0x06, - 0x1f, 0x1d, 0x04, 0x1f, 0x1d, 0x1d, 0x1e, 0x05, - 0x1d, 0x1e, 0x05, 0x21, 0x1e, 0x04, 0x1d, 0x1e, - 0x04, 0x1d, 0x1e, 0x04, 0x21, 0x1e, 0x1d, 0x22, - 0x1e, 0x1d, 0x21, 0x22, 0x1d, 0x1d, 0x22, 0x1d, - 0x1d, 0x00, 0x06, 0x22, 0x02, 0x04, 0x22, 0x02, - 0x04, 0x21, 0x02, 0x06, 0x22, 0x02, 0x06, 0x21, - 0x02, 0x1d, 0x22, 0x02, 0x1d, 0x21, 0x04, 0x1d, - 0x22, 0x04, 0x05, 0x21, 0x04, 0x1d, 0x21, 0x0b, - 0x06, 0x21, 0x0d, 0x05, 0x22, 0x0c, 0x05, 0x22, - 0x0e, 0x05, 0x22, 0x1c, 0x04, 0x22, 0x1c, 0x1d, - 0x22, 0x22, 0x05, 0x22, 0x22, 0x04, 0x22, 0x22, - 0x1d, 0x22, 0x1d, 0x1d, 0x22, 0x1a, 0x1d, 0x22, - 0x1e, 0x05, 0x22, 0x1a, 0x1d, 0x05, 0x1c, 0x05, - 0x1d, 0x11, 0x1d, 0x22, 0x1b, 0x1d, 0x22, 0x1e, - 0x04, 0x05, 0x1d, 0x06, 0x22, 0x1c, 0x04, 0x1d, - 0x1b, 0x1d, 0x1d, 0x1c, 0x04, 0x1d, 0x1e, 0x04, - 0x05, 0x04, 0x05, 0x22, 0x05, 0x04, 0x22, 0x1d, - 0x04, 0x22, 0x19, 0x1d, 0x22, 0x00, 0x05, 0x22, - 0x1b, 0x1d, 0x1d, 0x11, 0x04, 0x1d, 0x0d, 0x1d, - 0x1d, 0x0b, 0x06, 0x22, 0x1e, 0x04, 0x22, 0x35, - 0x06, 0x00, 0x0f, 0x9d, 0x0d, 0x0f, 0x9d, 0x27, - 0x06, 0x00, 0x1d, 0x1d, 0x20, 0x00, 0x1c, 0x01, - 0x0a, 0x1e, 0x06, 0x1e, 0x08, 0x0e, 0x1d, 0x12, - 0x1e, 0x0a, 0x0c, 0x21, 0x1d, 0x12, 0x1d, 0x23, - 0x20, 0x21, 0x0c, 0x1d, 0x1e, 0x35, 0x06, 0x00, - 0x0f, 0x14, 0x27, 0x06, 0x0e, 0x1d, 0x22, 0xff, - 0x00, 0x1d, 0x1d, 0x20, 0xff, 0x12, 0x1d, 0x23, - 0x20, 0xff, 0x21, 0x0c, 0x1d, 0x1e, 0x27, 0x06, - 0x05, 0x1d, 0xff, 0x05, 0x1d, 0x00, 0x1d, 0x20, - 0x27, 0x06, 0x0a, 0xa5, 0x00, 0x1d, 0x2c, 0x00, - 0x01, 0x30, 0x02, 0x30, 0x3a, 0x00, 0x3b, 0x00, - 0x21, 0x00, 0x3f, 0x00, 0x16, 0x30, 0x17, 0x30, - 0x26, 0x20, 0x13, 0x20, 0x12, 0x01, 0x00, 0x5f, - 0x5f, 0x28, 0x29, 0x7b, 0x7d, 0x08, 0x30, 0x0c, - 0x0d, 0x08, 0x09, 0x02, 0x03, 0x00, 0x01, 0x04, - 0x05, 0x06, 0x07, 0x5b, 0x00, 0x5d, 0x00, 0x3e, - 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x5f, - 0x00, 0x5f, 0x00, 0x5f, 0x00, 0x2c, 0x00, 0x01, - 0x30, 0x2e, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x3a, - 0x00, 0x3f, 0x00, 0x21, 0x00, 0x14, 0x20, 0x28, - 0x00, 0x29, 0x00, 0x7b, 0x00, 0x7d, 0x00, 0x14, - 0x30, 0x15, 0x30, 0x23, 0x26, 0x2a, 0x2b, 0x2d, - 0x3c, 0x3e, 0x3d, 0x00, 0x5c, 0x24, 0x25, 0x40, - 0x40, 0x06, 0xff, 0x0b, 0x00, 0x0b, 0xff, 0x0c, - 0x20, 0x00, 0x4d, 0x06, 0x40, 0x06, 0xff, 0x0e, - 0x00, 0x0e, 0xff, 0x0f, 0x00, 0x0f, 0xff, 0x10, - 0x00, 0x10, 0xff, 0x11, 0x00, 0x11, 0xff, 0x12, - 0x00, 0x12, 0x21, 0x06, 0x00, 0x01, 0x01, 0x02, - 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x05, - 0x05, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, - 0x08, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, - 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, - 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, - 0x0f, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x12, - 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, - 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, - 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, - 0x18, 0x19, 0x19, 0x19, 0x19, 0x20, 0x20, 0x20, - 0x20, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, - 0x22, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, 0x24, - 0x24, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, - 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x29, - 0x29, 0x22, 0x06, 0x22, 0x00, 0x22, 0x00, 0x22, - 0x01, 0x22, 0x01, 0x22, 0x03, 0x22, 0x03, 0x22, - 0x05, 0x22, 0x05, 0x21, 0x00, 0x85, 0x29, 0x01, - 0x30, 0x01, 0x0b, 0x0c, 0x00, 0xfa, 0xf1, 0xa0, - 0xa2, 0xa4, 0xa6, 0xa8, 0xe2, 0xe4, 0xe6, 0xc2, - 0xfb, 0xa1, 0xa3, 0xa5, 0xa7, 0xa9, 0xaa, 0xac, - 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, - 0xbe, 0xc0, 0xc3, 0xc5, 0xc7, 0xc9, 0xca, 0xcb, - 0xcc, 0xcd, 0xce, 0xd1, 0xd4, 0xd7, 0xda, 0xdd, - 0xde, 0xdf, 0xe0, 0xe1, 0xe3, 0xe5, 0xe7, 0xe8, - 0xe9, 0xea, 0xeb, 0xec, 0xee, 0xf2, 0x98, 0x99, - 0x31, 0x31, 0x4f, 0x31, 0x55, 0x31, 0x5b, 0x31, - 0x61, 0x31, 0xa2, 0x00, 0xa3, 0x00, 0xac, 0x00, - 0xaf, 0x00, 0xa6, 0x00, 0xa5, 0x00, 0xa9, 0x20, - 0x00, 0x00, 0x02, 0x25, 0x90, 0x21, 0x91, 0x21, - 0x92, 0x21, 0x93, 0x21, 0xa0, 0x25, 0xcb, 0x25, - 0xd2, 0x05, 0x07, 0x03, 0x01, 0xda, 0x05, 0x07, - 0x03, 0x01, 0xd0, 0x02, 0xd1, 0x02, 0xe6, 0x00, - 0x99, 0x02, 0x53, 0x02, 0x00, 0x00, 0xa3, 0x02, - 0x66, 0xab, 0xa5, 0x02, 0xa4, 0x02, 0x56, 0x02, - 0x57, 0x02, 0x91, 0x1d, 0x58, 0x02, 0x5e, 0x02, - 0xa9, 0x02, 0x64, 0x02, 0x62, 0x02, 0x60, 0x02, - 0x9b, 0x02, 0x27, 0x01, 0x9c, 0x02, 0x67, 0x02, - 0x84, 0x02, 0xaa, 0x02, 0xab, 0x02, 0x6c, 0x02, - 0x04, 0xdf, 0x8e, 0xa7, 0x6e, 0x02, 0x05, 0xdf, - 0x8e, 0x02, 0x06, 0xdf, 0xf8, 0x00, 0x76, 0x02, - 0x77, 0x02, 0x71, 0x00, 0x7a, 0x02, 0x08, 0xdf, - 0x7d, 0x02, 0x7e, 0x02, 0x80, 0x02, 0xa8, 0x02, - 0xa6, 0x02, 0x67, 0xab, 0xa7, 0x02, 0x88, 0x02, - 0x71, 0x2c, 0x00, 0x00, 0x8f, 0x02, 0xa1, 0x02, - 0xa2, 0x02, 0x98, 0x02, 0xc0, 0x01, 0xc1, 0x01, - 0xc2, 0x01, 0x0a, 0xdf, 0x1e, 0xdf, 0x41, 0x04, - 0x40, 0x00, 0x00, 0x00, 0x00, 0x14, 0x99, 0x10, - 0xba, 0x10, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x10, - 0xba, 0x10, 0x05, 0x05, 0xa5, 0x10, 0xba, 0x10, - 0x05, 0x31, 0x11, 0x27, 0x11, 0x32, 0x11, 0x27, - 0x11, 0x55, 0x47, 0x13, 0x3e, 0x13, 0x47, 0x13, - 0x57, 0x13, 0x55, 0x82, 0x13, 0xc9, 0x13, 0x00, - 0x00, 0x00, 0x00, 0x84, 0x13, 0xbb, 0x13, 0x05, - 0x05, 0x8b, 0x13, 0xc2, 0x13, 0x05, 0x90, 0x13, - 0xc9, 0x13, 0x05, 0xc2, 0x13, 0xc2, 0x13, 0x00, - 0x00, 0x00, 0x00, 0xc2, 0x13, 0xb8, 0x13, 0xc2, - 0x13, 0xc9, 0x13, 0x05, 0x55, 0xb9, 0x14, 0xba, - 0x14, 0xb9, 0x14, 0xb0, 0x14, 0x00, 0x00, 0x00, - 0x00, 0xb9, 0x14, 0xbd, 0x14, 0x55, 0x50, 0xb8, - 0x15, 0xaf, 0x15, 0xb9, 0x15, 0xaf, 0x15, 0x55, - 0x35, 0x19, 0x30, 0x19, 0x05, 0x1e, 0x61, 0x1e, - 0x61, 0x1e, 0x61, 0x29, 0x61, 0x1e, 0x61, 0x1f, - 0x61, 0x29, 0x61, 0x1f, 0x61, 0x1e, 0x61, 0x20, - 0x61, 0x21, 0x61, 0x1f, 0x61, 0x22, 0x61, 0x1f, - 0x61, 0x21, 0x61, 0x20, 0x61, 0x55, 0x55, 0x55, - 0x55, 0x67, 0x6d, 0x67, 0x6d, 0x63, 0x6d, 0x67, - 0x6d, 0x69, 0x6d, 0x67, 0x6d, 0x55, 0x05, 0x41, - 0x00, 0x30, 0x00, 0x57, 0xd1, 0x65, 0xd1, 0x58, - 0xd1, 0x65, 0xd1, 0x5f, 0xd1, 0x6e, 0xd1, 0x5f, - 0xd1, 0x6f, 0xd1, 0x5f, 0xd1, 0x70, 0xd1, 0x5f, - 0xd1, 0x71, 0xd1, 0x5f, 0xd1, 0x72, 0xd1, 0x55, - 0x55, 0x55, 0x05, 0xb9, 0xd1, 0x65, 0xd1, 0xba, - 0xd1, 0x65, 0xd1, 0xbb, 0xd1, 0x6e, 0xd1, 0xbc, - 0xd1, 0x6e, 0xd1, 0xbb, 0xd1, 0x6f, 0xd1, 0xbc, - 0xd1, 0x6f, 0xd1, 0x55, 0x55, 0x55, 0x41, 0x00, - 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x69, 0x00, - 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x43, 0x44, - 0x00, 0x00, 0x47, 0x00, 0x00, 0x4a, 0x4b, 0x00, - 0x00, 0x4e, 0x4f, 0x50, 0x51, 0x00, 0x53, 0x54, - 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, - 0x63, 0x64, 0x00, 0x66, 0x68, 0x00, 0x70, 0x00, - 0x41, 0x00, 0x61, 0x00, 0x41, 0x42, 0x00, 0x44, - 0x45, 0x46, 0x47, 0x4a, 0x00, 0x53, 0x00, 0x61, - 0x00, 0x41, 0x42, 0x00, 0x44, 0x45, 0x46, 0x47, - 0x00, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x00, 0x4f, - 0x53, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, - 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, - 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, - 0x41, 0x00, 0x61, 0x00, 0x31, 0x01, 0x37, 0x02, - 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, - 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, - 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, - 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, - 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, - 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, - 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, - 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, - 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x0b, 0x0c, - 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, - 0x30, 0x00, 0x30, 0x04, 0x3a, 0x04, 0x3e, 0x04, - 0x4b, 0x04, 0x4d, 0x04, 0x4e, 0x04, 0x89, 0xa6, - 0x30, 0x04, 0xa9, 0x26, 0x28, 0xb9, 0x7f, 0x9f, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x0a, 0x0b, 0x0e, 0x0f, 0x11, 0x13, 0x14, - 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x61, 0x26, - 0x25, 0x2f, 0x7b, 0x51, 0xa6, 0xb1, 0x04, 0x27, - 0x06, 0x00, 0x01, 0x05, 0x08, 0x2a, 0x06, 0x1e, - 0x08, 0x03, 0x0d, 0x20, 0x19, 0x1a, 0x1b, 0x1c, - 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, - 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x44, 0x90, - 0x77, 0x45, 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, - 0x47, 0x06, 0x33, 0x06, 0x17, 0x10, 0x11, 0x12, - 0x13, 0x00, 0x06, 0x0e, 0x02, 0x0f, 0x34, 0x06, - 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, 0x00, 0x00, - 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, 0x2d, 0x06, - 0x00, 0x00, 0x4a, 0x06, 0x00, 0x00, 0x44, 0x06, - 0x00, 0x00, 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, - 0x00, 0x00, 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, - 0x34, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x06, - 0x00, 0x00, 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, - 0x00, 0x00, 0xba, 0x06, 0x00, 0x00, 0x6f, 0x06, - 0x00, 0x00, 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, - 0x47, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x06, - 0x37, 0x06, 0x4a, 0x06, 0x43, 0x06, 0x00, 0x00, - 0x45, 0x06, 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, - 0x41, 0x06, 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, - 0x34, 0x06, 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, - 0x00, 0x00, 0x36, 0x06, 0x38, 0x06, 0x3a, 0x06, - 0x6e, 0x06, 0x00, 0x00, 0xa1, 0x06, 0x27, 0x06, - 0x00, 0x01, 0x05, 0x08, 0x20, 0x21, 0x0b, 0x06, - 0x10, 0x23, 0x2a, 0x06, 0x1a, 0x1b, 0x1c, 0x09, - 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, 0x01, - 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x28, 0x06, 0x2c, - 0x06, 0x2f, 0x06, 0x00, 0x00, 0x48, 0x06, 0x32, - 0x06, 0x2d, 0x06, 0x37, 0x06, 0x4a, 0x06, 0x2a, - 0x06, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, 0x17, 0x0b, - 0x18, 0x07, 0x0a, 0x00, 0x01, 0x04, 0x06, 0x0c, - 0x0e, 0x10, 0x30, 0x2e, 0x30, 0x00, 0x2c, 0x00, - 0x28, 0x00, 0x41, 0x00, 0x29, 0x00, 0x14, 0x30, - 0x53, 0x00, 0x15, 0x30, 0x43, 0x52, 0x43, 0x44, - 0x57, 0x5a, 0x41, 0x00, 0x48, 0x56, 0x4d, 0x56, - 0x53, 0x44, 0x53, 0x53, 0x50, 0x50, 0x56, 0x57, - 0x43, 0x4d, 0x43, 0x4d, 0x44, 0x4d, 0x52, 0x44, - 0x4a, 0x4b, 0x30, 0x30, 0x00, 0x68, 0x68, 0x4b, - 0x62, 0x57, 0x5b, 0xcc, 0x53, 0xc7, 0x30, 0x8c, - 0x4e, 0x1a, 0x59, 0xe3, 0x89, 0x29, 0x59, 0xa4, - 0x4e, 0x20, 0x66, 0x21, 0x71, 0x99, 0x65, 0x4d, - 0x52, 0x8c, 0x5f, 0x8d, 0x51, 0xb0, 0x65, 0x1d, - 0x52, 0x42, 0x7d, 0x1f, 0x75, 0xa9, 0x8c, 0xf0, - 0x58, 0x39, 0x54, 0x14, 0x6f, 0x95, 0x62, 0x55, - 0x63, 0x00, 0x4e, 0x09, 0x4e, 0x4a, 0x90, 0xe6, - 0x5d, 0x2d, 0x4e, 0xf3, 0x53, 0x07, 0x63, 0x70, - 0x8d, 0x53, 0x62, 0x81, 0x79, 0x7a, 0x7a, 0x08, - 0x54, 0x80, 0x6e, 0x09, 0x67, 0x08, 0x67, 0x33, - 0x75, 0x72, 0x52, 0xb6, 0x55, 0x4d, 0x91, 0x14, - 0x30, 0x15, 0x30, 0x2c, 0x67, 0x09, 0x4e, 0x8c, - 0x4e, 0x89, 0x5b, 0xb9, 0x70, 0x53, 0x62, 0xd7, - 0x76, 0xdd, 0x52, 0x57, 0x65, 0x97, 0x5f, 0xef, - 0x53, 0x30, 0x00, 0x38, 0x4e, 0x05, 0x00, 0x09, - 0x22, 0x01, 0x60, 0x4f, 0xae, 0x4f, 0xbb, 0x4f, - 0x02, 0x50, 0x7a, 0x50, 0x99, 0x50, 0xe7, 0x50, - 0xcf, 0x50, 0x9e, 0x34, 0x3a, 0x06, 0x4d, 0x51, - 0x54, 0x51, 0x64, 0x51, 0x77, 0x51, 0x1c, 0x05, - 0xb9, 0x34, 0x67, 0x51, 0x8d, 0x51, 0x4b, 0x05, - 0x97, 0x51, 0xa4, 0x51, 0xcc, 0x4e, 0xac, 0x51, - 0xb5, 0x51, 0xdf, 0x91, 0xf5, 0x51, 0x03, 0x52, - 0xdf, 0x34, 0x3b, 0x52, 0x46, 0x52, 0x72, 0x52, - 0x77, 0x52, 0x15, 0x35, 0x02, 0x00, 0x20, 0x80, - 0x80, 0x00, 0x08, 0x00, 0x00, 0xc7, 0x52, 0x00, - 0x02, 0x1d, 0x33, 0x3e, 0x3f, 0x50, 0x82, 0x8a, - 0x93, 0xac, 0xb6, 0xb8, 0xb8, 0xb8, 0x2c, 0x0a, - 0x70, 0x70, 0xca, 0x53, 0xdf, 0x53, 0x63, 0x0b, - 0xeb, 0x53, 0xf1, 0x53, 0x06, 0x54, 0x9e, 0x54, - 0x38, 0x54, 0x48, 0x54, 0x68, 0x54, 0xa2, 0x54, - 0xf6, 0x54, 0x10, 0x55, 0x53, 0x55, 0x63, 0x55, - 0x84, 0x55, 0x84, 0x55, 0x99, 0x55, 0xab, 0x55, - 0xb3, 0x55, 0xc2, 0x55, 0x16, 0x57, 0x06, 0x56, - 0x17, 0x57, 0x51, 0x56, 0x74, 0x56, 0x07, 0x52, - 0xee, 0x58, 0xce, 0x57, 0xf4, 0x57, 0x0d, 0x58, - 0x8b, 0x57, 0x32, 0x58, 0x31, 0x58, 0xac, 0x58, - 0xe4, 0x14, 0xf2, 0x58, 0xf7, 0x58, 0x06, 0x59, - 0x1a, 0x59, 0x22, 0x59, 0x62, 0x59, 0xa8, 0x16, - 0xea, 0x16, 0xec, 0x59, 0x1b, 0x5a, 0x27, 0x5a, - 0xd8, 0x59, 0x66, 0x5a, 0xee, 0x36, 0xfc, 0x36, - 0x08, 0x5b, 0x3e, 0x5b, 0x3e, 0x5b, 0xc8, 0x19, - 0xc3, 0x5b, 0xd8, 0x5b, 0xe7, 0x5b, 0xf3, 0x5b, - 0x18, 0x1b, 0xff, 0x5b, 0x06, 0x5c, 0x53, 0x5f, - 0x22, 0x5c, 0x81, 0x37, 0x60, 0x5c, 0x6e, 0x5c, - 0xc0, 0x5c, 0x8d, 0x5c, 0xe4, 0x1d, 0x43, 0x5d, - 0xe6, 0x1d, 0x6e, 0x5d, 0x6b, 0x5d, 0x7c, 0x5d, - 0xe1, 0x5d, 0xe2, 0x5d, 0x2f, 0x38, 0xfd, 0x5d, - 0x28, 0x5e, 0x3d, 0x5e, 0x69, 0x5e, 0x62, 0x38, - 0x83, 0x21, 0x7c, 0x38, 0xb0, 0x5e, 0xb3, 0x5e, - 0xb6, 0x5e, 0xca, 0x5e, 0x92, 0xa3, 0xfe, 0x5e, - 0x31, 0x23, 0x31, 0x23, 0x01, 0x82, 0x22, 0x5f, - 0x22, 0x5f, 0xc7, 0x38, 0xb8, 0x32, 0xda, 0x61, - 0x62, 0x5f, 0x6b, 0x5f, 0xe3, 0x38, 0x9a, 0x5f, - 0xcd, 0x5f, 0xd7, 0x5f, 0xf9, 0x5f, 0x81, 0x60, - 0x3a, 0x39, 0x1c, 0x39, 0x94, 0x60, 0xd4, 0x26, - 0xc7, 0x60, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, - 0x02, 0x08, 0x00, 0x80, 0x08, 0x00, 0x00, 0x08, - 0x80, 0x28, 0x80, 0x02, 0x00, 0x00, 0x02, 0x48, - 0x61, 0x00, 0x04, 0x06, 0x04, 0x32, 0x46, 0x6a, - 0x5c, 0x67, 0x96, 0xaa, 0xae, 0xc8, 0xd3, 0x5d, - 0x62, 0x00, 0x54, 0x77, 0xf3, 0x0c, 0x2b, 0x3d, - 0x63, 0xfc, 0x62, 0x68, 0x63, 0x83, 0x63, 0xe4, - 0x63, 0xf1, 0x2b, 0x22, 0x64, 0xc5, 0x63, 0xa9, - 0x63, 0x2e, 0x3a, 0x69, 0x64, 0x7e, 0x64, 0x9d, - 0x64, 0x77, 0x64, 0x6c, 0x3a, 0x4f, 0x65, 0x6c, - 0x65, 0x0a, 0x30, 0xe3, 0x65, 0xf8, 0x66, 0x49, - 0x66, 0x19, 0x3b, 0x91, 0x66, 0x08, 0x3b, 0xe4, - 0x3a, 0x92, 0x51, 0x95, 0x51, 0x00, 0x67, 0x9c, - 0x66, 0xad, 0x80, 0xd9, 0x43, 0x17, 0x67, 0x1b, - 0x67, 0x21, 0x67, 0x5e, 0x67, 0x53, 0x67, 0xc3, - 0x33, 0x49, 0x3b, 0xfa, 0x67, 0x85, 0x67, 0x52, - 0x68, 0x85, 0x68, 0x6d, 0x34, 0x8e, 0x68, 0x1f, - 0x68, 0x14, 0x69, 0x9d, 0x3b, 0x42, 0x69, 0xa3, - 0x69, 0xea, 0x69, 0xa8, 0x6a, 0xa3, 0x36, 0xdb, - 0x6a, 0x18, 0x3c, 0x21, 0x6b, 0xa7, 0x38, 0x54, - 0x6b, 0x4e, 0x3c, 0x72, 0x6b, 0x9f, 0x6b, 0xba, - 0x6b, 0xbb, 0x6b, 0x8d, 0x3a, 0x0b, 0x1d, 0xfa, - 0x3a, 0x4e, 0x6c, 0xbc, 0x3c, 0xbf, 0x6c, 0xcd, - 0x6c, 0x67, 0x6c, 0x16, 0x6d, 0x3e, 0x6d, 0x77, - 0x6d, 0x41, 0x6d, 0x69, 0x6d, 0x78, 0x6d, 0x85, - 0x6d, 0x1e, 0x3d, 0x34, 0x6d, 0x2f, 0x6e, 0x6e, - 0x6e, 0x33, 0x3d, 0xcb, 0x6e, 0xc7, 0x6e, 0xd1, - 0x3e, 0xf9, 0x6d, 0x6e, 0x6f, 0x5e, 0x3f, 0x8e, - 0x3f, 0xc6, 0x6f, 0x39, 0x70, 0x1e, 0x70, 0x1b, - 0x70, 0x96, 0x3d, 0x4a, 0x70, 0x7d, 0x70, 0x77, - 0x70, 0xad, 0x70, 0x25, 0x05, 0x45, 0x71, 0x63, - 0x42, 0x9c, 0x71, 0xab, 0x43, 0x28, 0x72, 0x35, - 0x72, 0x50, 0x72, 0x08, 0x46, 0x80, 0x72, 0x95, - 0x72, 0x35, 0x47, 0x02, 0x20, 0x00, 0x00, 0x20, - 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, - 0x02, 0x02, 0x80, 0x8a, 0x00, 0x00, 0x20, 0x00, - 0x08, 0x0a, 0x00, 0x80, 0x88, 0x80, 0x20, 0x14, - 0x48, 0x7a, 0x73, 0x8b, 0x73, 0xac, 0x3e, 0xa5, - 0x73, 0xb8, 0x3e, 0xb8, 0x3e, 0x47, 0x74, 0x5c, - 0x74, 0x71, 0x74, 0x85, 0x74, 0xca, 0x74, 0x1b, - 0x3f, 0x24, 0x75, 0x36, 0x4c, 0x3e, 0x75, 0x92, - 0x4c, 0x70, 0x75, 0x9f, 0x21, 0x10, 0x76, 0xa1, - 0x4f, 0xb8, 0x4f, 0x44, 0x50, 0xfc, 0x3f, 0x08, - 0x40, 0xf4, 0x76, 0xf3, 0x50, 0xf2, 0x50, 0x19, - 0x51, 0x33, 0x51, 0x1e, 0x77, 0x1f, 0x77, 0x1f, - 0x77, 0x4a, 0x77, 0x39, 0x40, 0x8b, 0x77, 0x46, - 0x40, 0x96, 0x40, 0x1d, 0x54, 0x4e, 0x78, 0x8c, - 0x78, 0xcc, 0x78, 0xe3, 0x40, 0x26, 0x56, 0x56, - 0x79, 0x9a, 0x56, 0xc5, 0x56, 0x8f, 0x79, 0xeb, - 0x79, 0x2f, 0x41, 0x40, 0x7a, 0x4a, 0x7a, 0x4f, - 0x7a, 0x7c, 0x59, 0xa7, 0x5a, 0xa7, 0x5a, 0xee, - 0x7a, 0x02, 0x42, 0xab, 0x5b, 0xc6, 0x7b, 0xc9, - 0x7b, 0x27, 0x42, 0x80, 0x5c, 0xd2, 0x7c, 0xa0, - 0x42, 0xe8, 0x7c, 0xe3, 0x7c, 0x00, 0x7d, 0x86, - 0x5f, 0x63, 0x7d, 0x01, 0x43, 0xc7, 0x7d, 0x02, - 0x7e, 0x45, 0x7e, 0x34, 0x43, 0x28, 0x62, 0x47, - 0x62, 0x59, 0x43, 0xd9, 0x62, 0x7a, 0x7f, 0x3e, - 0x63, 0x95, 0x7f, 0xfa, 0x7f, 0x05, 0x80, 0xda, - 0x64, 0x23, 0x65, 0x60, 0x80, 0xa8, 0x65, 0x70, - 0x80, 0x5f, 0x33, 0xd5, 0x43, 0xb2, 0x80, 0x03, - 0x81, 0x0b, 0x44, 0x3e, 0x81, 0xb5, 0x5a, 0xa7, - 0x67, 0xb5, 0x67, 0x93, 0x33, 0x9c, 0x33, 0x01, - 0x82, 0x04, 0x82, 0x9e, 0x8f, 0x6b, 0x44, 0x91, - 0x82, 0x8b, 0x82, 0x9d, 0x82, 0xb3, 0x52, 0xb1, - 0x82, 0xb3, 0x82, 0xbd, 0x82, 0xe6, 0x82, 0x3c, - 0x6b, 0xe5, 0x82, 0x1d, 0x83, 0x63, 0x83, 0xad, - 0x83, 0x23, 0x83, 0xbd, 0x83, 0xe7, 0x83, 0x57, - 0x84, 0x53, 0x83, 0xca, 0x83, 0xcc, 0x83, 0xdc, - 0x83, 0x36, 0x6c, 0x6b, 0x6d, 0x02, 0x00, 0x00, - 0x20, 0x22, 0x2a, 0xa0, 0x0a, 0x00, 0x20, 0x80, - 0x28, 0x00, 0xa8, 0x20, 0x20, 0x00, 0x02, 0x80, - 0x22, 0x02, 0x8a, 0x08, 0x00, 0xaa, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x00, 0x28, 0xd5, 0x6c, 0x2b, - 0x45, 0xf1, 0x84, 0xf3, 0x84, 0x16, 0x85, 0xca, - 0x73, 0x64, 0x85, 0x2c, 0x6f, 0x5d, 0x45, 0x61, - 0x45, 0xb1, 0x6f, 0xd2, 0x70, 0x6b, 0x45, 0x50, - 0x86, 0x5c, 0x86, 0x67, 0x86, 0x69, 0x86, 0xa9, - 0x86, 0x88, 0x86, 0x0e, 0x87, 0xe2, 0x86, 0x79, - 0x87, 0x28, 0x87, 0x6b, 0x87, 0x86, 0x87, 0xd7, - 0x45, 0xe1, 0x87, 0x01, 0x88, 0xf9, 0x45, 0x60, - 0x88, 0x63, 0x88, 0x67, 0x76, 0xd7, 0x88, 0xde, - 0x88, 0x35, 0x46, 0xfa, 0x88, 0xbb, 0x34, 0xae, - 0x78, 0x66, 0x79, 0xbe, 0x46, 0xc7, 0x46, 0xa0, - 0x8a, 0xed, 0x8a, 0x8a, 0x8b, 0x55, 0x8c, 0xa8, - 0x7c, 0xab, 0x8c, 0xc1, 0x8c, 0x1b, 0x8d, 0x77, - 0x8d, 0x2f, 0x7f, 0x04, 0x08, 0xcb, 0x8d, 0xbc, - 0x8d, 0xf0, 0x8d, 0xde, 0x08, 0xd4, 0x8e, 0x38, - 0x8f, 0xd2, 0x85, 0xed, 0x85, 0x94, 0x90, 0xf1, - 0x90, 0x11, 0x91, 0x2e, 0x87, 0x1b, 0x91, 0x38, - 0x92, 0xd7, 0x92, 0xd8, 0x92, 0x7c, 0x92, 0xf9, - 0x93, 0x15, 0x94, 0xfa, 0x8b, 0x8b, 0x95, 0x95, - 0x49, 0xb7, 0x95, 0x77, 0x8d, 0xe6, 0x49, 0xc3, - 0x96, 0xb2, 0x5d, 0x23, 0x97, 0x45, 0x91, 0x1a, - 0x92, 0x6e, 0x4a, 0x76, 0x4a, 0xe0, 0x97, 0x0a, - 0x94, 0xb2, 0x4a, 0x96, 0x94, 0x0b, 0x98, 0x0b, - 0x98, 0x29, 0x98, 0xb6, 0x95, 0xe2, 0x98, 0x33, - 0x4b, 0x29, 0x99, 0xa7, 0x99, 0xc2, 0x99, 0xfe, - 0x99, 0xce, 0x4b, 0x30, 0x9b, 0x12, 0x9b, 0x40, - 0x9c, 0xfd, 0x9c, 0xce, 0x4c, 0xed, 0x4c, 0x67, - 0x9d, 0xce, 0xa0, 0xf8, 0x4c, 0x05, 0xa1, 0x0e, - 0xa2, 0x91, 0xa2, 0xbb, 0x9e, 0x56, 0x4d, 0xf9, - 0x9e, 0xfe, 0x9e, 0x05, 0x9f, 0x0f, 0x9f, 0x16, - 0x9f, 0x3b, 0x9f, 0x00, 0xa6, 0x02, 0x88, 0xa0, - 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x28, 0x00, - 0x08, 0xa0, 0x80, 0xa0, 0x80, 0x00, 0x80, 0x80, - 0x00, 0x0a, 0x88, 0x80, 0x00, 0x80, 0x00, 0x20, - 0x2a, 0x00, 0x80, -}; - -static const uint16_t unicode_comp_table[965] = { - 0x4a01, 0x49c0, 0x4a02, 0x0280, 0x0281, 0x0282, 0x0283, 0x02c0, - 0x02c2, 0x0a00, 0x0284, 0x2442, 0x0285, 0x07c0, 0x0980, 0x0982, - 0x2440, 0x2280, 0x02c4, 0x2282, 0x2284, 0x2286, 0x02c6, 0x02c8, - 0x02ca, 0x02cc, 0x0287, 0x228a, 0x02ce, 0x228c, 0x2290, 0x2292, - 0x228e, 0x0288, 0x0289, 0x028a, 0x2482, 0x0300, 0x0302, 0x0304, - 0x028b, 0x2480, 0x0308, 0x0984, 0x0986, 0x2458, 0x0a02, 0x0306, - 0x2298, 0x229a, 0x229e, 0x0900, 0x030a, 0x22a0, 0x030c, 0x030e, - 0x0840, 0x0310, 0x0312, 0x22a2, 0x22a6, 0x09c0, 0x22a4, 0x22a8, - 0x22aa, 0x028c, 0x028d, 0x028e, 0x0340, 0x0342, 0x0344, 0x0380, - 0x028f, 0x248e, 0x07c2, 0x0988, 0x098a, 0x2490, 0x0346, 0x22ac, - 0x0400, 0x22b0, 0x0842, 0x22b2, 0x0402, 0x22b4, 0x0440, 0x0444, - 0x22b6, 0x0442, 0x22c2, 0x22c0, 0x22c4, 0x22c6, 0x22c8, 0x0940, - 0x04c0, 0x0291, 0x22ca, 0x04c4, 0x22cc, 0x04c2, 0x22d0, 0x22ce, - 0x0292, 0x0293, 0x0294, 0x0295, 0x0540, 0x0542, 0x0a08, 0x0296, - 0x2494, 0x0544, 0x07c4, 0x098c, 0x098e, 0x06c0, 0x2492, 0x0844, - 0x2308, 0x230a, 0x0580, 0x230c, 0x0584, 0x0990, 0x0992, 0x230e, - 0x0582, 0x2312, 0x0586, 0x0588, 0x2314, 0x058c, 0x2316, 0x0998, - 0x058a, 0x231e, 0x0590, 0x2320, 0x099a, 0x058e, 0x2324, 0x2322, - 0x0299, 0x029a, 0x029b, 0x05c0, 0x05c2, 0x05c4, 0x029c, 0x24ac, - 0x05c6, 0x05c8, 0x07c6, 0x0994, 0x0996, 0x0700, 0x24aa, 0x2326, - 0x05ca, 0x232a, 0x2328, 0x2340, 0x2342, 0x2344, 0x2346, 0x05cc, - 0x234a, 0x2348, 0x234c, 0x234e, 0x2350, 0x24b8, 0x029d, 0x05ce, - 0x24be, 0x0a0c, 0x2352, 0x0600, 0x24bc, 0x24ba, 0x0640, 0x2354, - 0x0642, 0x0644, 0x2356, 0x2358, 0x02a0, 0x02a1, 0x02a2, 0x02a3, - 0x02c1, 0x02c3, 0x0a01, 0x02a4, 0x2443, 0x02a5, 0x07c1, 0x0981, - 0x0983, 0x2441, 0x2281, 0x02c5, 0x2283, 0x2285, 0x2287, 0x02c7, - 0x02c9, 0x02cb, 0x02cd, 0x02a7, 0x228b, 0x02cf, 0x228d, 0x2291, - 0x2293, 0x228f, 0x02a8, 0x02a9, 0x02aa, 0x2483, 0x0301, 0x0303, - 0x0305, 0x02ab, 0x2481, 0x0309, 0x0985, 0x0987, 0x2459, 0x0a03, - 0x0307, 0x2299, 0x229b, 0x229f, 0x0901, 0x030b, 0x22a1, 0x030d, - 0x030f, 0x0841, 0x0311, 0x0313, 0x22a3, 0x22a7, 0x09c1, 0x22a5, - 0x22a9, 0x22ab, 0x2380, 0x02ac, 0x02ad, 0x02ae, 0x0341, 0x0343, - 0x0345, 0x02af, 0x248f, 0x07c3, 0x0989, 0x098b, 0x2491, 0x0347, - 0x22ad, 0x0401, 0x0884, 0x22b1, 0x0843, 0x22b3, 0x0403, 0x22b5, - 0x0441, 0x0445, 0x22b7, 0x0443, 0x22c3, 0x22c1, 0x22c5, 0x22c7, - 0x22c9, 0x0941, 0x04c1, 0x02b1, 0x22cb, 0x04c5, 0x22cd, 0x04c3, - 0x22d1, 0x22cf, 0x02b2, 0x02b3, 0x02b4, 0x02b5, 0x0541, 0x0543, - 0x0a09, 0x02b6, 0x2495, 0x0545, 0x07c5, 0x098d, 0x098f, 0x06c1, - 0x2493, 0x0845, 0x2309, 0x230b, 0x0581, 0x230d, 0x0585, 0x0991, - 0x0993, 0x230f, 0x0583, 0x2313, 0x0587, 0x0589, 0x2315, 0x058d, - 0x2317, 0x0999, 0x058b, 0x231f, 0x2381, 0x0591, 0x2321, 0x099b, - 0x058f, 0x2325, 0x2323, 0x02b9, 0x02ba, 0x02bb, 0x05c1, 0x05c3, - 0x05c5, 0x02bc, 0x24ad, 0x05c7, 0x05c9, 0x07c7, 0x0995, 0x0997, - 0x0701, 0x24ab, 0x2327, 0x05cb, 0x232b, 0x2329, 0x2341, 0x2343, - 0x2345, 0x2347, 0x05cd, 0x234b, 0x2349, 0x2382, 0x234d, 0x234f, - 0x2351, 0x24b9, 0x02bd, 0x05cf, 0x24bf, 0x0a0d, 0x2353, 0x02bf, - 0x24bd, 0x2383, 0x24bb, 0x0641, 0x2355, 0x0643, 0x0645, 0x2357, - 0x2359, 0x3101, 0x0c80, 0x2e00, 0x2446, 0x2444, 0x244a, 0x2448, - 0x0800, 0x0942, 0x0944, 0x0804, 0x2288, 0x2486, 0x2484, 0x248a, - 0x2488, 0x22ae, 0x2498, 0x2496, 0x249c, 0x249a, 0x2300, 0x0a06, - 0x2302, 0x0a04, 0x0946, 0x07ce, 0x07ca, 0x07c8, 0x07cc, 0x2447, - 0x2445, 0x244b, 0x2449, 0x0801, 0x0943, 0x0945, 0x0805, 0x2289, - 0x2487, 0x2485, 0x248b, 0x2489, 0x22af, 0x2499, 0x2497, 0x249d, - 0x249b, 0x2301, 0x0a07, 0x2303, 0x0a05, 0x0947, 0x07cf, 0x07cb, - 0x07c9, 0x07cd, 0x2450, 0x244e, 0x2454, 0x2452, 0x2451, 0x244f, - 0x2455, 0x2453, 0x2294, 0x2296, 0x2295, 0x2297, 0x2304, 0x2306, - 0x2305, 0x2307, 0x2318, 0x2319, 0x231a, 0x231b, 0x232c, 0x232d, - 0x232e, 0x232f, 0x2400, 0x24a2, 0x24a0, 0x24a6, 0x24a4, 0x24a8, - 0x24a3, 0x24a1, 0x24a7, 0x24a5, 0x24a9, 0x24b0, 0x24ae, 0x24b4, - 0x24b2, 0x24b6, 0x24b1, 0x24af, 0x24b5, 0x24b3, 0x24b7, 0x0882, - 0x0880, 0x0881, 0x0802, 0x0803, 0x229c, 0x229d, 0x0a0a, 0x0a0b, - 0x0883, 0x0b40, 0x2c8a, 0x0c81, 0x2c89, 0x2c88, 0x2540, 0x2541, - 0x2d00, 0x2e07, 0x0d00, 0x2640, 0x2641, 0x2e80, 0x0d01, 0x26c8, - 0x26c9, 0x2f00, 0x2f84, 0x0d02, 0x2f83, 0x2f82, 0x0d40, 0x26d8, - 0x26d9, 0x3186, 0x0d04, 0x2740, 0x2741, 0x3100, 0x3086, 0x0d06, - 0x3085, 0x3084, 0x0d41, 0x2840, 0x3200, 0x0d07, 0x284f, 0x2850, - 0x3280, 0x2c84, 0x2e03, 0x2857, 0x0d42, 0x2c81, 0x2c80, 0x24c0, - 0x24c1, 0x2c86, 0x2c83, 0x28c0, 0x0d43, 0x25c0, 0x25c1, 0x2940, - 0x0d44, 0x26c0, 0x26c1, 0x2e05, 0x2e02, 0x29c0, 0x0d45, 0x2f05, - 0x2f04, 0x0d80, 0x26d0, 0x26d1, 0x2f80, 0x2a40, 0x0d82, 0x26e0, - 0x26e1, 0x3080, 0x3081, 0x2ac0, 0x0d83, 0x3004, 0x3003, 0x0d81, - 0x27c0, 0x27c1, 0x3082, 0x2b40, 0x0d84, 0x2847, 0x2848, 0x3184, - 0x3181, 0x2f06, 0x0d08, 0x2f81, 0x3005, 0x0d46, 0x3083, 0x3182, - 0x0e00, 0x0e01, 0x0f40, 0x1180, 0x1182, 0x0f03, 0x0f00, 0x11c0, - 0x0f01, 0x1140, 0x1202, 0x1204, 0x0f81, 0x1240, 0x0fc0, 0x1242, - 0x0f80, 0x1244, 0x1284, 0x0f82, 0x1286, 0x1288, 0x128a, 0x12c0, - 0x1282, 0x1181, 0x1183, 0x1043, 0x1040, 0x11c1, 0x1041, 0x1141, - 0x1203, 0x1205, 0x10c1, 0x1241, 0x1000, 0x1243, 0x10c0, 0x1245, - 0x1285, 0x10c2, 0x1287, 0x1289, 0x128b, 0x12c1, 0x1283, 0x1080, - 0x1100, 0x1101, 0x1200, 0x1201, 0x1280, 0x1281, 0x1340, 0x1341, - 0x1343, 0x1342, 0x1344, 0x13c2, 0x1400, 0x13c0, 0x1440, 0x1480, - 0x14c0, 0x1540, 0x1541, 0x1740, 0x1700, 0x1741, 0x17c0, 0x1800, - 0x1802, 0x1801, 0x1840, 0x1880, 0x1900, 0x18c0, 0x18c1, 0x1901, - 0x1940, 0x1942, 0x1941, 0x1980, 0x19c0, 0x19c2, 0x19c1, 0x1c80, - 0x1cc0, 0x1dc0, 0x1f80, 0x2000, 0x2002, 0x2004, 0x2006, 0x2008, - 0x2040, 0x2080, 0x2082, 0x20c0, 0x20c1, 0x2100, 0x22b8, 0x22b9, - 0x2310, 0x2311, 0x231c, 0x231d, 0x244c, 0x2456, 0x244d, 0x2457, - 0x248c, 0x248d, 0x249e, 0x249f, 0x2500, 0x2502, 0x2504, 0x2bc0, - 0x2501, 0x2503, 0x2505, 0x2bc1, 0x2bc2, 0x2bc3, 0x2bc4, 0x2bc5, - 0x2bc6, 0x2bc7, 0x2580, 0x2582, 0x2584, 0x2bc8, 0x2581, 0x2583, - 0x2585, 0x2bc9, 0x2bca, 0x2bcb, 0x2bcc, 0x2bcd, 0x2bce, 0x2bcf, - 0x2600, 0x2602, 0x2601, 0x2603, 0x2680, 0x2682, 0x2681, 0x2683, - 0x26c2, 0x26c4, 0x26c6, 0x2c00, 0x26c3, 0x26c5, 0x26c7, 0x2c01, - 0x2c02, 0x2c03, 0x2c04, 0x2c05, 0x2c06, 0x2c07, 0x26ca, 0x26cc, - 0x26ce, 0x2c08, 0x26cb, 0x26cd, 0x26cf, 0x2c09, 0x2c0a, 0x2c0b, - 0x2c0c, 0x2c0d, 0x2c0e, 0x2c0f, 0x26d2, 0x26d4, 0x26d6, 0x26d3, - 0x26d5, 0x26d7, 0x26da, 0x26dc, 0x26de, 0x26db, 0x26dd, 0x26df, - 0x2700, 0x2702, 0x2701, 0x2703, 0x2780, 0x2782, 0x2781, 0x2783, - 0x2800, 0x2802, 0x2804, 0x2801, 0x2803, 0x2805, 0x2842, 0x2844, - 0x2846, 0x2849, 0x284b, 0x284d, 0x2c40, 0x284a, 0x284c, 0x284e, - 0x2c41, 0x2c42, 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47, 0x2851, - 0x2853, 0x2855, 0x2c48, 0x2852, 0x2854, 0x2856, 0x2c49, 0x2c4a, - 0x2c4b, 0x2c4c, 0x2c4d, 0x2c4e, 0x2c4f, 0x2c82, 0x2e01, 0x3180, - 0x2c87, 0x2f01, 0x2f02, 0x2f03, 0x2e06, 0x3185, 0x3000, 0x3001, - 0x3002, 0x4640, 0x4641, 0x4680, 0x46c0, 0x46c2, 0x46c1, 0x4700, - 0x4740, 0x4780, 0x47c0, 0x47c2, 0x4900, 0x4940, 0x4980, 0x4982, - 0x4a00, 0x49c2, 0x4a03, 0x4a04, 0x4a40, 0x4a41, 0x4a80, 0x4a81, - 0x4ac0, 0x4ac1, 0x4bc0, 0x4bc1, 0x4b00, 0x4b01, 0x4b40, 0x4b41, - 0x4bc2, 0x4bc3, 0x4b80, 0x4b81, 0x4b82, 0x4b83, 0x4c00, 0x4c01, - 0x4c02, 0x4c03, 0x5600, 0x5440, 0x5442, 0x5444, 0x5446, 0x5448, - 0x544a, 0x544c, 0x544e, 0x5450, 0x5452, 0x5454, 0x5456, 0x5480, - 0x5482, 0x5484, 0x54c0, 0x54c1, 0x5500, 0x5501, 0x5540, 0x5541, - 0x5580, 0x5581, 0x55c0, 0x55c1, 0x5680, 0x58c0, 0x5700, 0x5702, - 0x5704, 0x5706, 0x5708, 0x570a, 0x570c, 0x570e, 0x5710, 0x5712, - 0x5714, 0x5716, 0x5740, 0x5742, 0x5744, 0x5780, 0x5781, 0x57c0, - 0x57c1, 0x5800, 0x5801, 0x5840, 0x5841, 0x5880, 0x5881, 0x5900, - 0x5901, 0x5902, 0x5903, 0x5940, 0x8ec0, 0x8f00, 0x8fc0, 0x8fc2, - 0x9000, 0x9040, 0x9041, 0x9080, 0x9081, 0x90c0, 0x90c2, 0x9100, - 0x9140, 0x9182, 0x9180, 0x9183, 0x91c1, 0x91c0, 0x91c3, 0x9200, - 0x9201, 0x9240, 0x9280, 0x9282, 0x9284, 0x9281, 0x9285, 0x9287, - 0x9286, 0x9283, 0x92c1, 0x92c0, 0x92c2, -}; - -typedef enum { - UNICODE_GC_Cn, - UNICODE_GC_Lu, - UNICODE_GC_Ll, - UNICODE_GC_Lt, - UNICODE_GC_Lm, - UNICODE_GC_Lo, - UNICODE_GC_Mn, - UNICODE_GC_Mc, - UNICODE_GC_Me, - UNICODE_GC_Nd, - UNICODE_GC_Nl, - UNICODE_GC_No, - UNICODE_GC_Sm, - UNICODE_GC_Sc, - UNICODE_GC_Sk, - UNICODE_GC_So, - UNICODE_GC_Pc, - UNICODE_GC_Pd, - UNICODE_GC_Ps, - UNICODE_GC_Pe, - UNICODE_GC_Pi, - UNICODE_GC_Pf, - UNICODE_GC_Po, - UNICODE_GC_Zs, - UNICODE_GC_Zl, - UNICODE_GC_Zp, - UNICODE_GC_Cc, - UNICODE_GC_Cf, - UNICODE_GC_Cs, - UNICODE_GC_Co, - UNICODE_GC_LC, - UNICODE_GC_L, - UNICODE_GC_M, - UNICODE_GC_N, - UNICODE_GC_S, - UNICODE_GC_P, - UNICODE_GC_Z, - UNICODE_GC_C, - UNICODE_GC_COUNT, -} UnicodeGCEnum; - -static const char unicode_gc_name_table[] = - "Cn,Unassigned" "\0" - "Lu,Uppercase_Letter" "\0" - "Ll,Lowercase_Letter" "\0" - "Lt,Titlecase_Letter" "\0" - "Lm,Modifier_Letter" "\0" - "Lo,Other_Letter" "\0" - "Mn,Nonspacing_Mark" "\0" - "Mc,Spacing_Mark" "\0" - "Me,Enclosing_Mark" "\0" - "Nd,Decimal_Number,digit" "\0" - "Nl,Letter_Number" "\0" - "No,Other_Number" "\0" - "Sm,Math_Symbol" "\0" - "Sc,Currency_Symbol" "\0" - "Sk,Modifier_Symbol" "\0" - "So,Other_Symbol" "\0" - "Pc,Connector_Punctuation" "\0" - "Pd,Dash_Punctuation" "\0" - "Ps,Open_Punctuation" "\0" - "Pe,Close_Punctuation" "\0" - "Pi,Initial_Punctuation" "\0" - "Pf,Final_Punctuation" "\0" - "Po,Other_Punctuation" "\0" - "Zs,Space_Separator" "\0" - "Zl,Line_Separator" "\0" - "Zp,Paragraph_Separator" "\0" - "Cc,Control,cntrl" "\0" - "Cf,Format" "\0" - "Cs,Surrogate" "\0" - "Co,Private_Use" "\0" - "LC,Cased_Letter" "\0" - "L,Letter" "\0" - "M,Mark,Combining_Mark" "\0" - "N,Number" "\0" - "S,Symbol" "\0" - "P,Punctuation,punct" "\0" - "Z,Separator" "\0" - "C,Other" "\0" -; - -static const uint8_t unicode_gc_table[4070] = { - 0xfa, 0x18, 0x17, 0x56, 0x0d, 0x56, 0x12, 0x13, - 0x16, 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, - 0x4c, 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, - 0x10, 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, - 0xfa, 0x19, 0x17, 0x16, 0x6d, 0x0f, 0x16, 0x0e, - 0x0f, 0x05, 0x14, 0x0c, 0x1b, 0x0f, 0x0e, 0x0f, - 0x0c, 0x2b, 0x0e, 0x02, 0x36, 0x0e, 0x0b, 0x05, - 0x15, 0x4b, 0x16, 0xe1, 0x0f, 0x0c, 0xc1, 0xe2, - 0x10, 0x0c, 0xe2, 0x00, 0xff, 0x30, 0x02, 0xff, - 0x08, 0x02, 0xff, 0x27, 0xbf, 0x22, 0x21, 0x02, - 0x5f, 0x5f, 0x21, 0x22, 0x61, 0x02, 0x21, 0x02, - 0x41, 0x42, 0x21, 0x02, 0x21, 0x02, 0x9f, 0x7f, - 0x02, 0x5f, 0x5f, 0x21, 0x02, 0x5f, 0x3f, 0x02, - 0x05, 0x3f, 0x22, 0x65, 0x01, 0x03, 0x02, 0x01, - 0x03, 0x02, 0x01, 0x03, 0x02, 0xff, 0x08, 0x02, - 0xff, 0x0a, 0x02, 0x01, 0x03, 0x02, 0x5f, 0x21, - 0x02, 0xff, 0x32, 0xa2, 0x21, 0x02, 0x21, 0x22, - 0x5f, 0x41, 0x02, 0xff, 0x00, 0xe2, 0x3c, 0x05, - 0xe2, 0x13, 0xe4, 0x0a, 0x6e, 0xe4, 0x04, 0xee, - 0x06, 0x84, 0xce, 0x04, 0x0e, 0x04, 0xee, 0x09, - 0xe6, 0x68, 0x7f, 0x04, 0x0e, 0x3f, 0x20, 0x04, - 0x42, 0x16, 0x01, 0x60, 0x2e, 0x01, 0x16, 0x41, - 0x00, 0x01, 0x00, 0x21, 0x02, 0xe1, 0x09, 0x00, - 0xe1, 0x01, 0xe2, 0x1b, 0x3f, 0x02, 0x41, 0x42, - 0xff, 0x10, 0x62, 0x3f, 0x0c, 0x5f, 0x3f, 0x02, - 0xe1, 0x2b, 0xe2, 0x28, 0xff, 0x1a, 0x0f, 0x86, - 0x28, 0xff, 0x2f, 0xff, 0x06, 0x02, 0xff, 0x58, - 0x00, 0xe1, 0x1e, 0x20, 0x04, 0xb6, 0xe2, 0x21, - 0x16, 0x11, 0x20, 0x2f, 0x0d, 0x00, 0xe6, 0x25, - 0x11, 0x06, 0x16, 0x26, 0x16, 0x26, 0x16, 0x06, - 0xe0, 0x00, 0xe5, 0x13, 0x60, 0x65, 0x36, 0xe0, - 0x03, 0xbb, 0x4c, 0x36, 0x0d, 0x36, 0x2f, 0xe6, - 0x03, 0x16, 0x1b, 0x56, 0xe5, 0x18, 0x04, 0xe5, - 0x02, 0xe6, 0x0d, 0xe9, 0x02, 0x76, 0x25, 0x06, - 0xe5, 0x5b, 0x16, 0x05, 0xc6, 0x1b, 0x0f, 0xa6, - 0x24, 0x26, 0x0f, 0x66, 0x25, 0xe9, 0x02, 0x45, - 0x2f, 0x05, 0xf6, 0x06, 0x00, 0x1b, 0x05, 0x06, - 0xe5, 0x16, 0xe6, 0x13, 0x20, 0xe5, 0x51, 0xe6, - 0x03, 0x05, 0xe0, 0x06, 0xe9, 0x02, 0xe5, 0x19, - 0xe6, 0x01, 0x24, 0x0f, 0x56, 0x04, 0x20, 0x06, - 0x2d, 0xe5, 0x0e, 0x66, 0x04, 0xe6, 0x01, 0x04, - 0x46, 0x04, 0x86, 0x20, 0xf6, 0x07, 0x00, 0xe5, - 0x11, 0x46, 0x20, 0x16, 0x00, 0xe5, 0x03, 0x80, - 0xe5, 0x10, 0x0e, 0xa5, 0x00, 0x3b, 0x80, 0xe6, - 0x01, 0xe5, 0x21, 0x04, 0xe6, 0x10, 0x1b, 0xe6, - 0x18, 0x07, 0xe5, 0x2e, 0x06, 0x07, 0x06, 0x05, - 0x47, 0xe6, 0x00, 0x67, 0x06, 0x27, 0x05, 0xc6, - 0xe5, 0x02, 0x26, 0x36, 0xe9, 0x02, 0x16, 0x04, - 0xe5, 0x07, 0x06, 0x27, 0x00, 0xe5, 0x00, 0x20, - 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x05, - 0x40, 0x65, 0x20, 0x06, 0x05, 0x47, 0x66, 0x20, - 0x27, 0x20, 0x27, 0x06, 0x05, 0xe0, 0x00, 0x07, - 0x60, 0x25, 0x00, 0x45, 0x26, 0x20, 0xe9, 0x02, - 0x25, 0x2d, 0xab, 0x0f, 0x0d, 0x05, 0x16, 0x06, - 0x20, 0x26, 0x07, 0x00, 0xa5, 0x60, 0x25, 0x20, - 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, 0x25, - 0x00, 0x25, 0x20, 0x06, 0x00, 0x47, 0x26, 0x60, - 0x26, 0x20, 0x46, 0x40, 0x06, 0xc0, 0x65, 0x00, - 0x05, 0xc0, 0xe9, 0x02, 0x26, 0x45, 0x06, 0x16, - 0xe0, 0x02, 0x26, 0x07, 0x00, 0xe5, 0x01, 0x00, - 0x45, 0x00, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, - 0x00, 0x85, 0x20, 0x06, 0x05, 0x47, 0x86, 0x00, - 0x26, 0x07, 0x00, 0x27, 0x06, 0x20, 0x05, 0xe0, - 0x07, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x16, 0x0d, - 0xc0, 0x05, 0xa6, 0x00, 0x06, 0x27, 0x00, 0xe5, - 0x00, 0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, - 0x00, 0x25, 0x00, 0x85, 0x20, 0x06, 0x05, 0x07, - 0x06, 0x07, 0x66, 0x20, 0x27, 0x20, 0x27, 0x06, - 0xc0, 0x26, 0x07, 0x60, 0x25, 0x00, 0x45, 0x26, - 0x20, 0xe9, 0x02, 0x0f, 0x05, 0xab, 0xe0, 0x02, - 0x06, 0x05, 0x00, 0xa5, 0x40, 0x45, 0x00, 0x65, - 0x40, 0x25, 0x00, 0x05, 0x00, 0x25, 0x40, 0x25, - 0x40, 0x45, 0x40, 0xe5, 0x04, 0x60, 0x27, 0x06, - 0x27, 0x40, 0x47, 0x00, 0x47, 0x06, 0x20, 0x05, - 0xa0, 0x07, 0xe0, 0x06, 0xe9, 0x02, 0x4b, 0xaf, - 0x0d, 0x0f, 0x80, 0x06, 0x47, 0x06, 0xe5, 0x00, - 0x00, 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x08, - 0x20, 0x06, 0x05, 0x46, 0x67, 0x00, 0x46, 0x00, - 0x66, 0xc0, 0x26, 0x00, 0x45, 0x20, 0x05, 0x20, - 0x25, 0x26, 0x20, 0xe9, 0x02, 0xc0, 0x16, 0xcb, - 0x0f, 0x05, 0x06, 0x27, 0x16, 0xe5, 0x00, 0x00, - 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x02, 0x00, - 0x85, 0x20, 0x06, 0x05, 0x07, 0x06, 0x87, 0x00, - 0x06, 0x27, 0x00, 0x27, 0x26, 0xc0, 0x27, 0xa0, - 0x25, 0x00, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x00, - 0x25, 0x07, 0xe0, 0x04, 0x26, 0x27, 0xe5, 0x01, - 0x00, 0x45, 0x00, 0xe5, 0x21, 0x26, 0x05, 0x47, - 0x66, 0x00, 0x47, 0x00, 0x47, 0x06, 0x05, 0x0f, - 0x60, 0x45, 0x07, 0xcb, 0x45, 0x26, 0x20, 0xe9, - 0x02, 0xeb, 0x01, 0x0f, 0xa5, 0x00, 0x06, 0x27, - 0x00, 0xe5, 0x0a, 0x40, 0xe5, 0x10, 0x00, 0xe5, - 0x01, 0x00, 0x05, 0x20, 0xc5, 0x40, 0x06, 0x60, - 0x47, 0x46, 0x00, 0x06, 0x00, 0xe7, 0x00, 0xa0, - 0xe9, 0x02, 0x20, 0x27, 0x16, 0xe0, 0x04, 0xe5, - 0x28, 0x06, 0x25, 0xc6, 0x60, 0x0d, 0xa5, 0x04, - 0xe6, 0x00, 0x16, 0xe9, 0x02, 0x36, 0xe0, 0x1d, - 0x25, 0x00, 0x05, 0x00, 0x85, 0x00, 0xe5, 0x10, - 0x00, 0x05, 0x00, 0xe5, 0x02, 0x06, 0x25, 0xe6, - 0x01, 0x05, 0x20, 0x85, 0x00, 0x04, 0x00, 0xc6, - 0x00, 0xe9, 0x02, 0x20, 0x65, 0xe0, 0x18, 0x05, - 0x4f, 0xf6, 0x07, 0x0f, 0x16, 0x4f, 0x26, 0xaf, - 0xe9, 0x02, 0xeb, 0x02, 0x0f, 0x06, 0x0f, 0x06, - 0x0f, 0x06, 0x12, 0x13, 0x12, 0x13, 0x27, 0xe5, - 0x00, 0x00, 0xe5, 0x1c, 0x60, 0xe6, 0x06, 0x07, - 0x86, 0x16, 0x26, 0x85, 0xe6, 0x03, 0x00, 0xe6, - 0x1c, 0x00, 0xef, 0x00, 0x06, 0xaf, 0x00, 0x2f, - 0x96, 0x6f, 0x36, 0xe0, 0x1d, 0xe5, 0x23, 0x27, - 0x66, 0x07, 0xa6, 0x07, 0x26, 0x27, 0x26, 0x05, - 0xe9, 0x02, 0xb6, 0xa5, 0x27, 0x26, 0x65, 0x46, - 0x05, 0x47, 0x25, 0xc7, 0x45, 0x66, 0xe5, 0x05, - 0x06, 0x27, 0x26, 0xa7, 0x06, 0x05, 0x07, 0xe9, - 0x02, 0x47, 0x06, 0x2f, 0xe1, 0x1e, 0x00, 0x01, - 0x80, 0x01, 0x20, 0xe2, 0x23, 0x16, 0x04, 0x42, - 0xe5, 0x80, 0xc1, 0x00, 0x65, 0x20, 0xc5, 0x00, - 0x05, 0x00, 0x65, 0x20, 0xe5, 0x21, 0x00, 0x65, - 0x20, 0xe5, 0x19, 0x00, 0x65, 0x20, 0xc5, 0x00, - 0x05, 0x00, 0x65, 0x20, 0xe5, 0x07, 0x00, 0xe5, - 0x31, 0x00, 0x65, 0x20, 0xe5, 0x3b, 0x20, 0x46, - 0xf6, 0x01, 0xeb, 0x0c, 0x40, 0xe5, 0x08, 0xef, - 0x02, 0xa0, 0xe1, 0x4e, 0x20, 0xa2, 0x20, 0x11, - 0xe5, 0x81, 0xe4, 0x0f, 0x16, 0xe5, 0x09, 0x17, - 0xe5, 0x12, 0x12, 0x13, 0x40, 0xe5, 0x43, 0x56, - 0x4a, 0xe5, 0x00, 0xc0, 0xe5, 0x0a, 0x46, 0x07, - 0xe0, 0x01, 0xe5, 0x0b, 0x26, 0x07, 0x36, 0xe0, - 0x01, 0xe5, 0x0a, 0x26, 0xe0, 0x04, 0xe5, 0x05, - 0x00, 0x45, 0x00, 0x26, 0xe0, 0x04, 0xe5, 0x2c, - 0x26, 0x07, 0xc6, 0xe7, 0x00, 0x06, 0x27, 0xe6, - 0x03, 0x56, 0x04, 0x56, 0x0d, 0x05, 0x06, 0x20, - 0xe9, 0x02, 0xa0, 0xeb, 0x02, 0xa0, 0xb6, 0x11, - 0x76, 0x46, 0x1b, 0x06, 0xe9, 0x02, 0xa0, 0xe5, - 0x1b, 0x04, 0xe5, 0x2d, 0xc0, 0x85, 0x26, 0xe5, - 0x1a, 0x06, 0x05, 0x80, 0xe5, 0x3e, 0xe0, 0x02, - 0xe5, 0x17, 0x00, 0x46, 0x67, 0x26, 0x47, 0x60, - 0x27, 0x06, 0xa7, 0x46, 0x60, 0x0f, 0x40, 0x36, - 0xe9, 0x02, 0xe5, 0x16, 0x20, 0x85, 0xe0, 0x03, - 0xe5, 0x24, 0x60, 0xe5, 0x12, 0xa0, 0xe9, 0x02, - 0x0b, 0x40, 0xef, 0x1a, 0xe5, 0x0f, 0x26, 0x27, - 0x06, 0x20, 0x36, 0xe5, 0x2d, 0x07, 0x06, 0x07, - 0xc6, 0x00, 0x06, 0x07, 0x06, 0x27, 0xe6, 0x00, - 0xa7, 0xe6, 0x02, 0x20, 0x06, 0xe9, 0x02, 0xa0, - 0xe9, 0x02, 0xa0, 0xd6, 0x04, 0xb6, 0x20, 0xe6, - 0x06, 0x08, 0xe6, 0x08, 0xe0, 0x29, 0x66, 0x07, - 0xe5, 0x27, 0x06, 0x07, 0x86, 0x07, 0x06, 0x87, - 0x06, 0x27, 0xe5, 0x00, 0x00, 0x36, 0xe9, 0x02, - 0xd6, 0xef, 0x02, 0xe6, 0x01, 0xef, 0x01, 0x56, - 0x26, 0x07, 0xe5, 0x16, 0x07, 0x66, 0x27, 0x26, - 0x07, 0x46, 0x25, 0xe9, 0x02, 0xe5, 0x24, 0x06, - 0x07, 0x26, 0x47, 0x06, 0x07, 0x46, 0x27, 0xe0, - 0x00, 0x76, 0xe5, 0x1c, 0xe7, 0x00, 0xe6, 0x00, - 0x27, 0x26, 0x40, 0x96, 0xe9, 0x02, 0x40, 0x45, - 0xe9, 0x02, 0xe5, 0x16, 0xa4, 0x36, 0xe2, 0x01, - 0x3f, 0x80, 0xe1, 0x23, 0x20, 0x41, 0xf6, 0x00, - 0xe0, 0x00, 0x46, 0x16, 0xe6, 0x05, 0x07, 0xc6, - 0x65, 0x06, 0xa5, 0x06, 0x25, 0x07, 0x26, 0x05, - 0x80, 0xe2, 0x24, 0xe4, 0x37, 0xe2, 0x05, 0x04, - 0xe2, 0x1a, 0xe4, 0x1d, 0xe6, 0x38, 0xff, 0x80, - 0x0e, 0xe2, 0x00, 0xff, 0x5a, 0xe2, 0x00, 0xe1, - 0x00, 0xa2, 0x20, 0xa1, 0x20, 0xe2, 0x00, 0xe1, - 0x00, 0xe2, 0x00, 0xe1, 0x00, 0xa2, 0x20, 0xa1, - 0x20, 0xe2, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, - 0x01, 0x00, 0x3f, 0xc2, 0xe1, 0x00, 0xe2, 0x06, - 0x20, 0xe2, 0x00, 0xe3, 0x00, 0xe2, 0x00, 0xe3, - 0x00, 0xe2, 0x00, 0xe3, 0x00, 0x82, 0x00, 0x22, - 0x61, 0x03, 0x0e, 0x02, 0x4e, 0x42, 0x00, 0x22, - 0x61, 0x03, 0x4e, 0x62, 0x20, 0x22, 0x61, 0x00, - 0x4e, 0xe2, 0x00, 0x81, 0x4e, 0x20, 0x42, 0x00, - 0x22, 0x61, 0x03, 0x2e, 0x00, 0xf7, 0x03, 0x9b, - 0xb1, 0x36, 0x14, 0x15, 0x12, 0x34, 0x15, 0x12, - 0x14, 0xf6, 0x00, 0x18, 0x19, 0x9b, 0x17, 0xf6, - 0x01, 0x14, 0x15, 0x76, 0x30, 0x56, 0x0c, 0x12, - 0x13, 0xf6, 0x03, 0x0c, 0x16, 0x10, 0xf6, 0x02, - 0x17, 0x9b, 0x00, 0xfb, 0x02, 0x0b, 0x04, 0x20, - 0xab, 0x4c, 0x12, 0x13, 0x04, 0xeb, 0x02, 0x4c, - 0x12, 0x13, 0x00, 0xe4, 0x05, 0x40, 0xed, 0x19, - 0xe0, 0x07, 0xe6, 0x05, 0x68, 0x06, 0x48, 0xe6, - 0x04, 0xe0, 0x07, 0x2f, 0x01, 0x6f, 0x01, 0x2f, - 0x02, 0x41, 0x22, 0x41, 0x02, 0x0f, 0x01, 0x2f, - 0x0c, 0x81, 0xaf, 0x01, 0x0f, 0x01, 0x0f, 0x01, - 0x0f, 0x61, 0x0f, 0x02, 0x61, 0x02, 0x65, 0x02, - 0x2f, 0x22, 0x21, 0x8c, 0x3f, 0x42, 0x0f, 0x0c, - 0x2f, 0x02, 0x0f, 0xeb, 0x08, 0xea, 0x1b, 0x3f, - 0x6a, 0x0b, 0x2f, 0x60, 0x8c, 0x8f, 0x2c, 0x6f, - 0x0c, 0x2f, 0x0c, 0x2f, 0x0c, 0xcf, 0x0c, 0xef, - 0x17, 0x2c, 0x2f, 0x0c, 0x0f, 0x0c, 0xef, 0x17, - 0xec, 0x80, 0x84, 0xef, 0x00, 0x12, 0x13, 0x12, - 0x13, 0xef, 0x0c, 0x2c, 0xcf, 0x12, 0x13, 0xef, - 0x49, 0x0c, 0xef, 0x16, 0xec, 0x11, 0xef, 0x20, - 0xac, 0xef, 0x40, 0xe0, 0x0e, 0xef, 0x03, 0xe0, - 0x0d, 0xeb, 0x34, 0xef, 0x46, 0xeb, 0x0e, 0xef, - 0x80, 0x2f, 0x0c, 0xef, 0x01, 0x0c, 0xef, 0x2e, - 0xec, 0x00, 0xef, 0x67, 0x0c, 0xef, 0x80, 0x70, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xeb, 0x16, - 0xef, 0x24, 0x8c, 0x12, 0x13, 0xec, 0x17, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, - 0x13, 0xec, 0x08, 0xef, 0x80, 0x78, 0xec, 0x7b, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xec, 0x37, - 0x12, 0x13, 0x12, 0x13, 0xec, 0x18, 0x12, 0x13, - 0xec, 0x80, 0x7a, 0xef, 0x28, 0xec, 0x0d, 0x2f, - 0xac, 0xef, 0x1f, 0x20, 0xef, 0x18, 0x00, 0xef, - 0x61, 0xe1, 0x28, 0xe2, 0x28, 0x5f, 0x21, 0x22, - 0xdf, 0x41, 0x02, 0x3f, 0x02, 0x3f, 0x82, 0x24, - 0x41, 0x02, 0xff, 0x5a, 0x02, 0xaf, 0x7f, 0x46, - 0x3f, 0x80, 0x76, 0x0b, 0x36, 0xe2, 0x1e, 0x00, - 0x02, 0x80, 0x02, 0x20, 0xe5, 0x30, 0xc0, 0x04, - 0x16, 0xe0, 0x06, 0x06, 0xe5, 0x0f, 0xe0, 0x01, - 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, - 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, - 0xe6, 0x18, 0x36, 0x14, 0x15, 0x14, 0x15, 0x56, - 0x14, 0x15, 0x16, 0x14, 0x15, 0xf6, 0x01, 0x11, - 0x36, 0x11, 0x16, 0x14, 0x15, 0x36, 0x14, 0x15, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, - 0x96, 0x04, 0xf6, 0x02, 0x31, 0x76, 0x11, 0x16, - 0x12, 0xf6, 0x05, 0x2f, 0x56, 0x12, 0x13, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, 0xe0, 0x1a, - 0xef, 0x12, 0x00, 0xef, 0x51, 0xe0, 0x04, 0xef, - 0x80, 0x4e, 0xe0, 0x12, 0xef, 0x08, 0x17, 0x56, - 0x0f, 0x04, 0x05, 0x0a, 0x12, 0x13, 0x12, 0x13, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x2f, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, - 0x12, 0x33, 0x0f, 0xea, 0x01, 0x66, 0x27, 0x11, - 0x84, 0x2f, 0x4a, 0x04, 0x05, 0x16, 0x2f, 0x00, - 0xe5, 0x4e, 0x20, 0x26, 0x2e, 0x24, 0x05, 0x11, - 0xe5, 0x52, 0x16, 0x44, 0x05, 0x80, 0xe5, 0x23, - 0x00, 0xe5, 0x56, 0x00, 0x2f, 0x6b, 0xef, 0x02, - 0xe5, 0x18, 0xef, 0x1e, 0xe0, 0x01, 0x0f, 0xe5, - 0x08, 0xef, 0x17, 0x00, 0xeb, 0x02, 0xef, 0x16, - 0xeb, 0x00, 0x0f, 0xeb, 0x07, 0xef, 0x18, 0xeb, - 0x02, 0xef, 0x1f, 0xeb, 0x07, 0xef, 0x80, 0xb8, - 0xe5, 0x99, 0x38, 0xef, 0x38, 0xe5, 0xc0, 0x11, - 0x8d, 0x04, 0xe5, 0x83, 0xef, 0x40, 0xef, 0x2f, - 0xe0, 0x01, 0xe5, 0x20, 0xa4, 0x36, 0xe5, 0x80, - 0x84, 0x04, 0x56, 0xe5, 0x08, 0xe9, 0x02, 0x25, - 0xe0, 0x0c, 0xff, 0x26, 0x05, 0x06, 0x48, 0x16, - 0xe6, 0x02, 0x16, 0x04, 0xff, 0x14, 0x24, 0x26, - 0xe5, 0x3e, 0xea, 0x02, 0x26, 0xb6, 0xe0, 0x00, - 0xee, 0x0f, 0xe4, 0x01, 0x2e, 0xff, 0x06, 0x22, - 0xff, 0x36, 0x04, 0xe2, 0x00, 0x9f, 0xff, 0x02, - 0x04, 0x2e, 0x7f, 0x05, 0x7f, 0x22, 0xff, 0x0d, - 0x61, 0x02, 0x81, 0x02, 0xff, 0x07, 0x41, 0x02, - 0x5f, 0x3f, 0x20, 0x3f, 0x00, 0x02, 0x00, 0x02, - 0xdf, 0xe0, 0x0d, 0x44, 0x3f, 0x05, 0x24, 0x02, - 0xc5, 0x06, 0x45, 0x06, 0x65, 0x06, 0xe5, 0x0f, - 0x27, 0x26, 0x07, 0x6f, 0x06, 0x40, 0xab, 0x2f, - 0x0d, 0x0f, 0xa0, 0xe5, 0x2c, 0x76, 0xe0, 0x00, - 0x27, 0xe5, 0x2a, 0xe7, 0x08, 0x26, 0xe0, 0x00, - 0x36, 0xe9, 0x02, 0xa0, 0xe6, 0x0a, 0xa5, 0x56, - 0x05, 0x16, 0x25, 0x06, 0xe9, 0x02, 0xe5, 0x14, - 0xe6, 0x00, 0x36, 0xe5, 0x0f, 0xe6, 0x03, 0x27, - 0xe0, 0x03, 0x16, 0xe5, 0x15, 0x40, 0x46, 0x07, - 0xe5, 0x27, 0x06, 0x27, 0x66, 0x27, 0x26, 0x47, - 0xf6, 0x05, 0x00, 0x04, 0xe9, 0x02, 0x60, 0x36, - 0x85, 0x06, 0x04, 0xe5, 0x01, 0xe9, 0x02, 0x85, - 0x00, 0xe5, 0x21, 0xa6, 0x27, 0x26, 0x27, 0x26, - 0xe0, 0x01, 0x45, 0x06, 0xe5, 0x00, 0x06, 0x07, - 0x20, 0xe9, 0x02, 0x20, 0x76, 0xe5, 0x08, 0x04, - 0xa5, 0x4f, 0x05, 0x07, 0x06, 0x07, 0xe5, 0x2a, - 0x06, 0x05, 0x46, 0x25, 0x26, 0x85, 0x26, 0x05, - 0x06, 0x05, 0xe0, 0x10, 0x25, 0x04, 0x36, 0xe5, - 0x03, 0x07, 0x26, 0x27, 0x36, 0x05, 0x24, 0x07, - 0x06, 0xe0, 0x02, 0xa5, 0x20, 0xa5, 0x20, 0xa5, - 0xe0, 0x01, 0xc5, 0x00, 0xc5, 0x00, 0xe2, 0x23, - 0x0e, 0x64, 0xe2, 0x01, 0x04, 0x2e, 0x60, 0xe2, - 0x48, 0xe5, 0x1b, 0x27, 0x06, 0x27, 0x06, 0x27, - 0x16, 0x07, 0x06, 0x20, 0xe9, 0x02, 0xa0, 0xe5, - 0xab, 0x1c, 0xe0, 0x04, 0xe5, 0x0f, 0x60, 0xe5, - 0x29, 0x60, 0xfc, 0x87, 0x78, 0xfd, 0x98, 0x78, - 0xe5, 0x80, 0xe6, 0x20, 0xe5, 0x62, 0xe0, 0x1e, - 0xc2, 0xe0, 0x04, 0x82, 0x80, 0x05, 0x06, 0xe5, - 0x02, 0x0c, 0xe5, 0x05, 0x00, 0x85, 0x00, 0x05, - 0x00, 0x25, 0x00, 0x25, 0x00, 0xe5, 0x64, 0xee, - 0x09, 0xe0, 0x08, 0xe5, 0x80, 0xe3, 0x13, 0x12, - 0xef, 0x08, 0xe5, 0x38, 0x20, 0xe5, 0x2e, 0xc0, - 0x0f, 0xe0, 0x18, 0xe5, 0x04, 0x0d, 0x4f, 0xe6, - 0x08, 0xd6, 0x12, 0x13, 0x16, 0xa0, 0xe6, 0x08, - 0x16, 0x31, 0x30, 0x12, 0x13, 0x12, 0x13, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, - 0x13, 0x12, 0x13, 0x36, 0x12, 0x13, 0x76, 0x50, - 0x56, 0x00, 0x76, 0x11, 0x12, 0x13, 0x12, 0x13, - 0x12, 0x13, 0x56, 0x0c, 0x11, 0x4c, 0x00, 0x16, - 0x0d, 0x36, 0x60, 0x85, 0x00, 0xe5, 0x7f, 0x20, - 0x1b, 0x00, 0x56, 0x0d, 0x56, 0x12, 0x13, 0x16, - 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, 0x4c, - 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, 0x10, - 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, 0x12, - 0x13, 0x16, 0x12, 0x13, 0x36, 0xe5, 0x02, 0x04, - 0xe5, 0x25, 0x24, 0xe5, 0x17, 0x40, 0xa5, 0x20, - 0xa5, 0x20, 0xa5, 0x20, 0x45, 0x40, 0x2d, 0x0c, - 0x0e, 0x0f, 0x2d, 0x00, 0x0f, 0x6c, 0x2f, 0xe0, - 0x02, 0x5b, 0x2f, 0x20, 0xe5, 0x04, 0x00, 0xe5, - 0x12, 0x00, 0xe5, 0x0b, 0x00, 0x25, 0x00, 0xe5, - 0x07, 0x20, 0xe5, 0x06, 0xe0, 0x1a, 0xe5, 0x73, - 0x80, 0x56, 0x60, 0xeb, 0x25, 0x40, 0xef, 0x01, - 0xea, 0x2d, 0x6b, 0xef, 0x09, 0x2b, 0x4f, 0x00, - 0xef, 0x05, 0x40, 0x0f, 0xe0, 0x27, 0xef, 0x25, - 0x06, 0xe0, 0x7a, 0xe5, 0x15, 0x40, 0xe5, 0x29, - 0xe0, 0x07, 0x06, 0xeb, 0x13, 0x60, 0xe5, 0x18, - 0x6b, 0xe0, 0x01, 0xe5, 0x0c, 0x0a, 0xe5, 0x00, - 0x0a, 0x80, 0xe5, 0x1e, 0x86, 0x80, 0xe5, 0x16, - 0x00, 0x16, 0xe5, 0x1c, 0x60, 0xe5, 0x00, 0x16, - 0x8a, 0xe0, 0x22, 0xe1, 0x20, 0xe2, 0x20, 0xe5, - 0x46, 0x20, 0xe9, 0x02, 0xa0, 0xe1, 0x1c, 0x60, - 0xe2, 0x1c, 0x60, 0xe5, 0x20, 0xe0, 0x00, 0xe5, - 0x2c, 0xe0, 0x03, 0x16, 0xe1, 0x03, 0x00, 0xe1, - 0x07, 0x00, 0xc1, 0x00, 0x21, 0x00, 0xe2, 0x03, - 0x00, 0xe2, 0x07, 0x00, 0xc2, 0x00, 0x22, 0x40, - 0xe5, 0x2c, 0xe0, 0x04, 0xe5, 0x80, 0xaf, 0xe0, - 0x01, 0xe5, 0x0e, 0xe0, 0x02, 0xe5, 0x00, 0xe0, - 0x10, 0xa4, 0x00, 0xe4, 0x22, 0x00, 0xe4, 0x01, - 0xe0, 0x3d, 0xa5, 0x20, 0x05, 0x00, 0xe5, 0x24, - 0x00, 0x25, 0x40, 0x05, 0x20, 0xe5, 0x0f, 0x00, - 0x16, 0xeb, 0x00, 0xe5, 0x0f, 0x2f, 0xcb, 0xe5, - 0x17, 0xe0, 0x00, 0xeb, 0x01, 0xe0, 0x28, 0xe5, - 0x0b, 0x00, 0x25, 0x80, 0x8b, 0xe5, 0x0e, 0xab, - 0x40, 0x16, 0xe5, 0x12, 0x80, 0x16, 0xe0, 0x38, - 0xe5, 0x30, 0x60, 0x2b, 0x25, 0xeb, 0x08, 0x20, - 0xeb, 0x26, 0x05, 0x46, 0x00, 0x26, 0x80, 0x66, - 0x65, 0x00, 0x45, 0x00, 0xe5, 0x15, 0x20, 0x46, - 0x60, 0x06, 0xeb, 0x01, 0xc0, 0xf6, 0x01, 0xc0, - 0xe5, 0x15, 0x2b, 0x16, 0xe5, 0x15, 0x4b, 0xe0, - 0x18, 0xe5, 0x00, 0x0f, 0xe5, 0x14, 0x26, 0x60, - 0x8b, 0xd6, 0xe0, 0x01, 0xe5, 0x2e, 0x40, 0xd6, - 0xe5, 0x0e, 0x20, 0xeb, 0x00, 0xe5, 0x0b, 0x80, - 0xeb, 0x00, 0xe5, 0x0a, 0xc0, 0x76, 0xe0, 0x04, - 0xcb, 0xe0, 0x48, 0xe5, 0x41, 0xe0, 0x2f, 0xe1, - 0x2b, 0xe0, 0x05, 0xe2, 0x2b, 0xc0, 0xab, 0xe5, - 0x1c, 0x66, 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xe9, - 0x02, 0x65, 0x04, 0x05, 0xe1, 0x0e, 0x40, 0x86, - 0x11, 0x04, 0xe2, 0x0e, 0xe0, 0x00, 0x2c, 0xe0, - 0x80, 0x48, 0xeb, 0x17, 0x00, 0xe5, 0x22, 0x00, - 0x26, 0x11, 0x20, 0x25, 0xe0, 0x08, 0x45, 0xe0, - 0x2f, 0x66, 0xe5, 0x15, 0xeb, 0x02, 0x05, 0xe0, - 0x00, 0xe5, 0x0e, 0xe6, 0x03, 0x6b, 0x96, 0xe0, - 0x0e, 0xe5, 0x0a, 0x66, 0x76, 0xe0, 0x1e, 0xe5, - 0x0d, 0xcb, 0xe0, 0x0c, 0xe5, 0x0f, 0xe0, 0x01, - 0x07, 0x06, 0x07, 0xe5, 0x2d, 0xe6, 0x07, 0xd6, - 0x60, 0xeb, 0x0c, 0xe9, 0x02, 0x06, 0x25, 0x26, - 0x05, 0xe0, 0x01, 0x46, 0x07, 0xe5, 0x25, 0x47, - 0x66, 0x27, 0x26, 0x36, 0x1b, 0x76, 0x06, 0xe0, - 0x02, 0x1b, 0x20, 0xe5, 0x11, 0xc0, 0xe9, 0x02, - 0xa0, 0x46, 0xe5, 0x1c, 0x86, 0x07, 0xe6, 0x00, - 0x00, 0xe9, 0x02, 0x76, 0x05, 0x27, 0x05, 0xe0, - 0x00, 0xe5, 0x1b, 0x06, 0x36, 0x05, 0xe0, 0x01, - 0x26, 0x07, 0xe5, 0x28, 0x47, 0xe6, 0x01, 0x27, - 0x65, 0x76, 0x66, 0x16, 0x07, 0x06, 0xe9, 0x02, - 0x05, 0x16, 0x05, 0x56, 0x00, 0xeb, 0x0c, 0xe0, - 0x03, 0xe5, 0x0a, 0x00, 0xe5, 0x11, 0x47, 0x46, - 0x27, 0x06, 0x07, 0x26, 0xb6, 0x06, 0x25, 0x06, - 0xe0, 0x36, 0xc5, 0x00, 0x05, 0x00, 0x65, 0x00, - 0xe5, 0x07, 0x00, 0xe5, 0x02, 0x16, 0xa0, 0xe5, - 0x27, 0x06, 0x47, 0xe6, 0x00, 0x80, 0xe9, 0x02, - 0xa0, 0x26, 0x27, 0x00, 0xe5, 0x00, 0x20, 0x25, - 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, - 0x85, 0x00, 0x26, 0x05, 0x27, 0x06, 0x67, 0x20, - 0x27, 0x20, 0x47, 0x20, 0x05, 0xa0, 0x07, 0x80, - 0x85, 0x27, 0x20, 0xc6, 0x40, 0x86, 0xe0, 0x03, - 0xe5, 0x02, 0x00, 0x05, 0x20, 0x05, 0x00, 0xe5, - 0x1e, 0x00, 0x05, 0x47, 0xa6, 0x00, 0x07, 0x20, - 0x07, 0x00, 0x67, 0x00, 0x27, 0x06, 0x07, 0x06, - 0x05, 0x06, 0x05, 0x36, 0x00, 0x36, 0xe0, 0x00, - 0x26, 0xe0, 0x15, 0xe5, 0x2d, 0x47, 0xe6, 0x00, - 0x27, 0x46, 0x07, 0x06, 0x65, 0x96, 0xe9, 0x02, - 0x36, 0x00, 0x16, 0x06, 0x45, 0xe0, 0x16, 0xe5, - 0x28, 0x47, 0xa6, 0x07, 0x06, 0x67, 0x26, 0x07, - 0x26, 0x25, 0x16, 0x05, 0xe0, 0x00, 0xe9, 0x02, - 0xe0, 0x80, 0x1e, 0xe5, 0x27, 0x47, 0x66, 0x20, - 0x67, 0x26, 0x07, 0x26, 0xf6, 0x0f, 0x65, 0x26, - 0xe0, 0x1a, 0xe5, 0x28, 0x47, 0xe6, 0x00, 0x27, - 0x06, 0x07, 0x26, 0x56, 0x05, 0xe0, 0x03, 0xe9, - 0x02, 0xa0, 0xf6, 0x05, 0xe0, 0x0b, 0xe5, 0x23, - 0x06, 0x07, 0x06, 0x27, 0xa6, 0x07, 0x06, 0x05, - 0x16, 0xa0, 0xe9, 0x02, 0xa0, 0xe9, 0x0c, 0xe0, - 0x14, 0xe5, 0x13, 0x20, 0x06, 0x07, 0x06, 0x27, - 0x66, 0x07, 0x86, 0x60, 0xe9, 0x02, 0x2b, 0x56, - 0x0f, 0xc5, 0xe0, 0x80, 0x31, 0xe5, 0x24, 0x47, - 0xe6, 0x01, 0x07, 0x26, 0x16, 0xe0, 0x5c, 0xe1, - 0x18, 0xe2, 0x18, 0xe9, 0x02, 0xeb, 0x01, 0xe0, - 0x04, 0xe5, 0x00, 0x20, 0x05, 0x20, 0xe5, 0x00, - 0x00, 0x25, 0x00, 0xe5, 0x10, 0xa7, 0x00, 0x27, - 0x20, 0x26, 0x07, 0x06, 0x05, 0x07, 0x05, 0x07, - 0x06, 0x56, 0xe0, 0x01, 0xe9, 0x02, 0xe0, 0x3e, - 0xe5, 0x00, 0x20, 0xe5, 0x1f, 0x47, 0x66, 0x20, - 0x26, 0x67, 0x06, 0x05, 0x16, 0x05, 0x07, 0xe0, - 0x13, 0x05, 0xe6, 0x02, 0xe5, 0x20, 0xa6, 0x07, - 0x05, 0x66, 0xf6, 0x00, 0x06, 0xe0, 0x00, 0x05, - 0xa6, 0x27, 0x46, 0xe5, 0x26, 0xe6, 0x05, 0x07, - 0x26, 0x56, 0x05, 0x96, 0xe0, 0x05, 0xe5, 0x41, - 0xc0, 0xf6, 0x02, 0xe0, 0x80, 0x2e, 0xe5, 0x19, - 0x16, 0xe0, 0x06, 0xe9, 0x02, 0xa0, 0xe5, 0x01, - 0x00, 0xe5, 0x1d, 0x07, 0xc6, 0x00, 0xa6, 0x07, - 0x06, 0x05, 0x96, 0xe0, 0x02, 0xe9, 0x02, 0xeb, - 0x0b, 0x40, 0x36, 0xe5, 0x16, 0x20, 0xe6, 0x0e, - 0x00, 0x07, 0xc6, 0x07, 0x26, 0x07, 0x26, 0xe0, - 0x41, 0xc5, 0x00, 0x25, 0x00, 0xe5, 0x1e, 0xa6, - 0x40, 0x06, 0x00, 0x26, 0x00, 0xc6, 0x05, 0x06, - 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xa5, 0x00, 0x25, - 0x00, 0xe5, 0x18, 0x87, 0x00, 0x26, 0x00, 0x27, - 0x06, 0x07, 0x06, 0x05, 0xc0, 0xe9, 0x02, 0xe0, - 0x80, 0xae, 0xe5, 0x0b, 0x26, 0x27, 0x36, 0xc0, - 0x26, 0x05, 0x07, 0xe5, 0x05, 0x00, 0xe5, 0x1a, - 0x27, 0x86, 0x40, 0x27, 0x06, 0x07, 0x06, 0xf6, - 0x05, 0xe9, 0x02, 0x06, 0xe0, 0x4d, 0x05, 0xe0, - 0x07, 0xeb, 0x0d, 0xef, 0x00, 0x6d, 0xef, 0x09, - 0xe0, 0x05, 0x16, 0xe5, 0x83, 0x12, 0xe0, 0x5e, - 0xea, 0x67, 0x00, 0x96, 0xe0, 0x03, 0xe5, 0x80, - 0x3c, 0xe0, 0x89, 0xc4, 0xe5, 0x59, 0x36, 0xe0, - 0x05, 0xe5, 0x83, 0xa8, 0xfb, 0x08, 0x06, 0xa5, - 0xe6, 0x07, 0xe0, 0x02, 0xe5, 0x8f, 0x13, 0x80, - 0xe5, 0x81, 0xbf, 0xe0, 0x9a, 0x31, 0xe5, 0x16, - 0xe6, 0x04, 0x47, 0x46, 0xe9, 0x02, 0xe0, 0x86, - 0x3e, 0xe5, 0x81, 0xb1, 0xc0, 0xe5, 0x17, 0x00, - 0xe9, 0x02, 0x60, 0x36, 0xe5, 0x47, 0x00, 0xe9, - 0x02, 0xa0, 0xe5, 0x16, 0x20, 0x86, 0x16, 0xe0, - 0x02, 0xe5, 0x28, 0xc6, 0x96, 0x6f, 0x64, 0x16, - 0x0f, 0xe0, 0x02, 0xe9, 0x02, 0x00, 0xcb, 0x00, - 0xe5, 0x0d, 0x80, 0xe5, 0x0b, 0xe0, 0x81, 0x28, - 0x44, 0xe5, 0x20, 0x24, 0x56, 0xe9, 0x02, 0xe0, - 0x80, 0x3e, 0xe1, 0x18, 0xe2, 0x18, 0xeb, 0x0f, - 0x76, 0xe0, 0x5d, 0xe5, 0x43, 0x60, 0x06, 0x05, - 0xe7, 0x2f, 0xc0, 0x66, 0xe4, 0x05, 0xe0, 0x38, - 0x24, 0x16, 0x04, 0x06, 0xe0, 0x03, 0x27, 0xe0, - 0x06, 0xe5, 0x97, 0x70, 0xe0, 0x00, 0xe5, 0x84, - 0x4e, 0xe0, 0x21, 0xe5, 0x02, 0xe0, 0xa2, 0x5f, - 0x64, 0x00, 0xc4, 0x00, 0x24, 0x00, 0xe5, 0x80, - 0x9b, 0xe0, 0x07, 0x05, 0xe0, 0x15, 0x45, 0x20, - 0x05, 0xe0, 0x06, 0x65, 0xe0, 0x00, 0xe5, 0x81, - 0x04, 0xe0, 0x88, 0x7c, 0xe5, 0x63, 0x80, 0xe5, - 0x05, 0x40, 0xe5, 0x01, 0xc0, 0xe5, 0x02, 0x20, - 0x0f, 0x26, 0x16, 0x7b, 0xe0, 0x8e, 0xd4, 0xef, - 0x80, 0x68, 0xe9, 0x02, 0xa0, 0xef, 0x81, 0x2c, - 0xe0, 0x44, 0xe6, 0x26, 0x20, 0xe6, 0x0f, 0xe0, - 0x01, 0xef, 0x6c, 0xe0, 0x34, 0xef, 0x80, 0x6e, - 0xe0, 0x02, 0xef, 0x1f, 0x20, 0xef, 0x34, 0x27, - 0x46, 0x4f, 0xa7, 0xfb, 0x00, 0xe6, 0x00, 0x2f, - 0xc6, 0xef, 0x16, 0x66, 0xef, 0x35, 0xe0, 0x0d, - 0xef, 0x3a, 0x46, 0x0f, 0xe0, 0x72, 0xeb, 0x0c, - 0xe0, 0x04, 0xeb, 0x0c, 0xe0, 0x04, 0xef, 0x4f, - 0xe0, 0x01, 0xeb, 0x11, 0xe0, 0x7f, 0xe1, 0x12, - 0xe2, 0x12, 0xe1, 0x12, 0xc2, 0x00, 0xe2, 0x0a, - 0xe1, 0x12, 0xe2, 0x12, 0x01, 0x00, 0x21, 0x20, - 0x01, 0x20, 0x21, 0x20, 0x61, 0x00, 0xe1, 0x00, - 0x62, 0x00, 0x02, 0x00, 0xc2, 0x00, 0xe2, 0x03, - 0xe1, 0x12, 0xe2, 0x12, 0x21, 0x00, 0x61, 0x20, - 0xe1, 0x00, 0x00, 0xc1, 0x00, 0xe2, 0x12, 0x21, - 0x00, 0x61, 0x00, 0x81, 0x00, 0x01, 0x40, 0xc1, - 0x00, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, - 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, - 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, - 0x12, 0xe2, 0x14, 0x20, 0xe1, 0x11, 0x0c, 0xe2, - 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, - 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, - 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, - 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0x3f, - 0x20, 0xe9, 0x2a, 0xef, 0x81, 0x78, 0xe6, 0x2f, - 0x6f, 0xe6, 0x2a, 0xef, 0x00, 0x06, 0xef, 0x06, - 0x06, 0x2f, 0x96, 0xe0, 0x07, 0x86, 0x00, 0xe6, - 0x07, 0xe0, 0x83, 0xc8, 0xe2, 0x02, 0x05, 0xe2, - 0x0c, 0xa0, 0xa2, 0xe0, 0x80, 0x4d, 0xc6, 0x00, - 0xe6, 0x09, 0x20, 0xc6, 0x00, 0x26, 0x00, 0x86, - 0x80, 0xe4, 0x36, 0xe0, 0x19, 0x06, 0xe0, 0x68, - 0xe5, 0x25, 0x40, 0xc6, 0xc4, 0x20, 0xe9, 0x02, - 0x60, 0x05, 0x0f, 0xe0, 0x80, 0xb8, 0xe5, 0x16, - 0x06, 0xe0, 0x09, 0xe5, 0x24, 0x66, 0xe9, 0x02, - 0x80, 0x0d, 0xe0, 0x81, 0x48, 0xe5, 0x13, 0x04, - 0x66, 0xe9, 0x02, 0xe0, 0x80, 0x4e, 0xe5, 0x16, - 0x26, 0x05, 0xe9, 0x02, 0x60, 0x16, 0xe0, 0x81, - 0x58, 0xc5, 0x00, 0x65, 0x00, 0x25, 0x00, 0xe5, - 0x07, 0x00, 0xe5, 0x80, 0x3d, 0x20, 0xeb, 0x01, - 0xc6, 0xe0, 0x21, 0xe1, 0x1a, 0xe2, 0x1a, 0xc6, - 0x04, 0x60, 0xe9, 0x02, 0x60, 0x36, 0xe0, 0x82, - 0x89, 0xeb, 0x33, 0x0f, 0x4b, 0x0d, 0x6b, 0xe0, - 0x44, 0xeb, 0x25, 0x0f, 0xeb, 0x07, 0xe0, 0x80, - 0x3a, 0x65, 0x00, 0xe5, 0x13, 0x00, 0x25, 0x00, - 0x05, 0x20, 0x05, 0x00, 0xe5, 0x02, 0x00, 0x65, - 0x00, 0x05, 0x00, 0x05, 0xa0, 0x05, 0x60, 0x05, - 0x00, 0x05, 0x00, 0x05, 0x00, 0x45, 0x00, 0x25, - 0x00, 0x05, 0x20, 0x05, 0x00, 0x05, 0x00, 0x05, - 0x00, 0x05, 0x00, 0x05, 0x00, 0x25, 0x00, 0x05, - 0x20, 0x65, 0x00, 0xc5, 0x00, 0x65, 0x00, 0x65, - 0x00, 0x05, 0x00, 0xe5, 0x02, 0x00, 0xe5, 0x09, - 0x80, 0x45, 0x00, 0x85, 0x00, 0xe5, 0x09, 0xe0, - 0x2c, 0x2c, 0xe0, 0x80, 0x86, 0xef, 0x24, 0x60, - 0xef, 0x5c, 0xe0, 0x04, 0xef, 0x07, 0x20, 0xef, - 0x07, 0x00, 0xef, 0x07, 0x00, 0xef, 0x1d, 0xe0, - 0x02, 0xeb, 0x05, 0xef, 0x80, 0x19, 0xe0, 0x30, - 0xef, 0x15, 0xe0, 0x05, 0xef, 0x24, 0x60, 0xef, - 0x01, 0xc0, 0x2f, 0xe0, 0x06, 0xaf, 0xe0, 0x80, - 0x12, 0xef, 0x80, 0x73, 0x8e, 0xef, 0x82, 0x50, - 0x60, 0xef, 0x09, 0x40, 0xef, 0x05, 0x40, 0xef, - 0x6f, 0x60, 0xef, 0x57, 0xa0, 0xef, 0x04, 0x60, - 0x0f, 0xe0, 0x07, 0xef, 0x04, 0x60, 0xef, 0x30, - 0xe0, 0x00, 0xef, 0x02, 0xa0, 0xef, 0x20, 0xe0, - 0x00, 0xef, 0x16, 0x20, 0xef, 0x04, 0x60, 0x2f, - 0xe0, 0x36, 0xef, 0x80, 0xcc, 0xe0, 0x04, 0xef, - 0x06, 0x20, 0xef, 0x05, 0x40, 0xef, 0x02, 0x80, - 0xef, 0x30, 0xc0, 0xef, 0x07, 0x20, 0xef, 0x03, - 0xa0, 0xef, 0x01, 0xc0, 0xef, 0x80, 0x0b, 0x00, - 0xef, 0x54, 0xe9, 0x02, 0xe0, 0x83, 0x7e, 0xe5, - 0xc0, 0x66, 0x58, 0xe0, 0x18, 0xe5, 0x8f, 0xb2, - 0xa0, 0xe5, 0x80, 0x56, 0x20, 0xe5, 0x95, 0xfa, - 0xe0, 0x06, 0xe5, 0x9c, 0xa9, 0xe0, 0x07, 0xe5, - 0x81, 0xe6, 0xe0, 0x89, 0x1a, 0xe5, 0x81, 0x96, - 0xe0, 0x85, 0x5a, 0xe5, 0x92, 0xc3, 0x80, 0xe5, - 0x8f, 0xd8, 0xe0, 0xca, 0x9b, 0xc9, 0x1b, 0xe0, - 0x16, 0xfb, 0x58, 0xe0, 0x78, 0xe6, 0x80, 0x68, - 0xe0, 0xc0, 0xbd, 0x88, 0xfd, 0xc0, 0xbf, 0x76, - 0x20, 0xfd, 0xc0, 0xbf, 0x76, 0x20, -}; - -typedef enum { - UNICODE_SCRIPT_Unknown, - UNICODE_SCRIPT_Adlam, - UNICODE_SCRIPT_Ahom, - UNICODE_SCRIPT_Anatolian_Hieroglyphs, - UNICODE_SCRIPT_Arabic, - UNICODE_SCRIPT_Armenian, - UNICODE_SCRIPT_Avestan, - UNICODE_SCRIPT_Balinese, - UNICODE_SCRIPT_Bamum, - UNICODE_SCRIPT_Bassa_Vah, - UNICODE_SCRIPT_Batak, - UNICODE_SCRIPT_Bengali, - UNICODE_SCRIPT_Bhaiksuki, - UNICODE_SCRIPT_Bopomofo, - UNICODE_SCRIPT_Brahmi, - UNICODE_SCRIPT_Braille, - UNICODE_SCRIPT_Buginese, - UNICODE_SCRIPT_Buhid, - UNICODE_SCRIPT_Canadian_Aboriginal, - UNICODE_SCRIPT_Carian, - UNICODE_SCRIPT_Caucasian_Albanian, - UNICODE_SCRIPT_Chakma, - UNICODE_SCRIPT_Cham, - UNICODE_SCRIPT_Cherokee, - UNICODE_SCRIPT_Chorasmian, - UNICODE_SCRIPT_Common, - UNICODE_SCRIPT_Coptic, - UNICODE_SCRIPT_Cuneiform, - UNICODE_SCRIPT_Cypriot, - UNICODE_SCRIPT_Cyrillic, - UNICODE_SCRIPT_Cypro_Minoan, - UNICODE_SCRIPT_Deseret, - UNICODE_SCRIPT_Devanagari, - UNICODE_SCRIPT_Dives_Akuru, - UNICODE_SCRIPT_Dogra, - UNICODE_SCRIPT_Duployan, - UNICODE_SCRIPT_Egyptian_Hieroglyphs, - UNICODE_SCRIPT_Elbasan, - UNICODE_SCRIPT_Elymaic, - UNICODE_SCRIPT_Ethiopic, - UNICODE_SCRIPT_Georgian, - UNICODE_SCRIPT_Glagolitic, - UNICODE_SCRIPT_Gothic, - UNICODE_SCRIPT_Garay, - UNICODE_SCRIPT_Grantha, - UNICODE_SCRIPT_Greek, - UNICODE_SCRIPT_Gujarati, - UNICODE_SCRIPT_Gunjala_Gondi, - UNICODE_SCRIPT_Gurmukhi, - UNICODE_SCRIPT_Gurung_Khema, - UNICODE_SCRIPT_Han, - UNICODE_SCRIPT_Hangul, - UNICODE_SCRIPT_Hanifi_Rohingya, - UNICODE_SCRIPT_Hanunoo, - UNICODE_SCRIPT_Hatran, - UNICODE_SCRIPT_Hebrew, - UNICODE_SCRIPT_Hiragana, - UNICODE_SCRIPT_Imperial_Aramaic, - UNICODE_SCRIPT_Inherited, - UNICODE_SCRIPT_Inscriptional_Pahlavi, - UNICODE_SCRIPT_Inscriptional_Parthian, - UNICODE_SCRIPT_Javanese, - UNICODE_SCRIPT_Kaithi, - UNICODE_SCRIPT_Kannada, - UNICODE_SCRIPT_Katakana, - UNICODE_SCRIPT_Kawi, - UNICODE_SCRIPT_Kayah_Li, - UNICODE_SCRIPT_Kharoshthi, - UNICODE_SCRIPT_Khmer, - UNICODE_SCRIPT_Khojki, - UNICODE_SCRIPT_Khitan_Small_Script, - UNICODE_SCRIPT_Khudawadi, - UNICODE_SCRIPT_Kirat_Rai, - UNICODE_SCRIPT_Lao, - UNICODE_SCRIPT_Latin, - UNICODE_SCRIPT_Lepcha, - UNICODE_SCRIPT_Limbu, - UNICODE_SCRIPT_Linear_A, - UNICODE_SCRIPT_Linear_B, - UNICODE_SCRIPT_Lisu, - UNICODE_SCRIPT_Lycian, - UNICODE_SCRIPT_Lydian, - UNICODE_SCRIPT_Makasar, - UNICODE_SCRIPT_Mahajani, - UNICODE_SCRIPT_Malayalam, - UNICODE_SCRIPT_Mandaic, - UNICODE_SCRIPT_Manichaean, - UNICODE_SCRIPT_Marchen, - UNICODE_SCRIPT_Masaram_Gondi, - UNICODE_SCRIPT_Medefaidrin, - UNICODE_SCRIPT_Meetei_Mayek, - UNICODE_SCRIPT_Mende_Kikakui, - UNICODE_SCRIPT_Meroitic_Cursive, - UNICODE_SCRIPT_Meroitic_Hieroglyphs, - UNICODE_SCRIPT_Miao, - UNICODE_SCRIPT_Modi, - UNICODE_SCRIPT_Mongolian, - UNICODE_SCRIPT_Mro, - UNICODE_SCRIPT_Multani, - UNICODE_SCRIPT_Myanmar, - UNICODE_SCRIPT_Nabataean, - UNICODE_SCRIPT_Nag_Mundari, - UNICODE_SCRIPT_Nandinagari, - UNICODE_SCRIPT_New_Tai_Lue, - UNICODE_SCRIPT_Newa, - UNICODE_SCRIPT_Nko, - UNICODE_SCRIPT_Nushu, - UNICODE_SCRIPT_Nyiakeng_Puachue_Hmong, - UNICODE_SCRIPT_Ogham, - UNICODE_SCRIPT_Ol_Chiki, - UNICODE_SCRIPT_Ol_Onal, - UNICODE_SCRIPT_Old_Hungarian, - UNICODE_SCRIPT_Old_Italic, - UNICODE_SCRIPT_Old_North_Arabian, - UNICODE_SCRIPT_Old_Permic, - UNICODE_SCRIPT_Old_Persian, - UNICODE_SCRIPT_Old_Sogdian, - UNICODE_SCRIPT_Old_South_Arabian, - UNICODE_SCRIPT_Old_Turkic, - UNICODE_SCRIPT_Old_Uyghur, - UNICODE_SCRIPT_Oriya, - UNICODE_SCRIPT_Osage, - UNICODE_SCRIPT_Osmanya, - UNICODE_SCRIPT_Pahawh_Hmong, - UNICODE_SCRIPT_Palmyrene, - UNICODE_SCRIPT_Pau_Cin_Hau, - UNICODE_SCRIPT_Phags_Pa, - UNICODE_SCRIPT_Phoenician, - UNICODE_SCRIPT_Psalter_Pahlavi, - UNICODE_SCRIPT_Rejang, - UNICODE_SCRIPT_Runic, - UNICODE_SCRIPT_Samaritan, - UNICODE_SCRIPT_Saurashtra, - UNICODE_SCRIPT_Sharada, - UNICODE_SCRIPT_Shavian, - UNICODE_SCRIPT_Siddham, - UNICODE_SCRIPT_SignWriting, - UNICODE_SCRIPT_Sinhala, - UNICODE_SCRIPT_Sogdian, - UNICODE_SCRIPT_Sora_Sompeng, - UNICODE_SCRIPT_Soyombo, - UNICODE_SCRIPT_Sundanese, - UNICODE_SCRIPT_Sunuwar, - UNICODE_SCRIPT_Syloti_Nagri, - UNICODE_SCRIPT_Syriac, - UNICODE_SCRIPT_Tagalog, - UNICODE_SCRIPT_Tagbanwa, - UNICODE_SCRIPT_Tai_Le, - UNICODE_SCRIPT_Tai_Tham, - UNICODE_SCRIPT_Tai_Viet, - UNICODE_SCRIPT_Takri, - UNICODE_SCRIPT_Tamil, - UNICODE_SCRIPT_Tangut, - UNICODE_SCRIPT_Telugu, - UNICODE_SCRIPT_Thaana, - UNICODE_SCRIPT_Thai, - UNICODE_SCRIPT_Tibetan, - UNICODE_SCRIPT_Tifinagh, - UNICODE_SCRIPT_Tirhuta, - UNICODE_SCRIPT_Tangsa, - UNICODE_SCRIPT_Todhri, - UNICODE_SCRIPT_Toto, - UNICODE_SCRIPT_Tulu_Tigalari, - UNICODE_SCRIPT_Ugaritic, - UNICODE_SCRIPT_Vai, - UNICODE_SCRIPT_Vithkuqi, - UNICODE_SCRIPT_Wancho, - UNICODE_SCRIPT_Warang_Citi, - UNICODE_SCRIPT_Yezidi, - UNICODE_SCRIPT_Yi, - UNICODE_SCRIPT_Zanabazar_Square, - UNICODE_SCRIPT_COUNT, -} UnicodeScriptEnum; - -static const char unicode_script_name_table[] = - "Adlam,Adlm" "\0" - "Ahom,Ahom" "\0" - "Anatolian_Hieroglyphs,Hluw" "\0" - "Arabic,Arab" "\0" - "Armenian,Armn" "\0" - "Avestan,Avst" "\0" - "Balinese,Bali" "\0" - "Bamum,Bamu" "\0" - "Bassa_Vah,Bass" "\0" - "Batak,Batk" "\0" - "Bengali,Beng" "\0" - "Bhaiksuki,Bhks" "\0" - "Bopomofo,Bopo" "\0" - "Brahmi,Brah" "\0" - "Braille,Brai" "\0" - "Buginese,Bugi" "\0" - "Buhid,Buhd" "\0" - "Canadian_Aboriginal,Cans" "\0" - "Carian,Cari" "\0" - "Caucasian_Albanian,Aghb" "\0" - "Chakma,Cakm" "\0" - "Cham,Cham" "\0" - "Cherokee,Cher" "\0" - "Chorasmian,Chrs" "\0" - "Common,Zyyy" "\0" - "Coptic,Copt,Qaac" "\0" - "Cuneiform,Xsux" "\0" - "Cypriot,Cprt" "\0" - "Cyrillic,Cyrl" "\0" - "Cypro_Minoan,Cpmn" "\0" - "Deseret,Dsrt" "\0" - "Devanagari,Deva" "\0" - "Dives_Akuru,Diak" "\0" - "Dogra,Dogr" "\0" - "Duployan,Dupl" "\0" - "Egyptian_Hieroglyphs,Egyp" "\0" - "Elbasan,Elba" "\0" - "Elymaic,Elym" "\0" - "Ethiopic,Ethi" "\0" - "Georgian,Geor" "\0" - "Glagolitic,Glag" "\0" - "Gothic,Goth" "\0" - "Garay,Gara" "\0" - "Grantha,Gran" "\0" - "Greek,Grek" "\0" - "Gujarati,Gujr" "\0" - "Gunjala_Gondi,Gong" "\0" - "Gurmukhi,Guru" "\0" - "Gurung_Khema,Gukh" "\0" - "Han,Hani" "\0" - "Hangul,Hang" "\0" - "Hanifi_Rohingya,Rohg" "\0" - "Hanunoo,Hano" "\0" - "Hatran,Hatr" "\0" - "Hebrew,Hebr" "\0" - "Hiragana,Hira" "\0" - "Imperial_Aramaic,Armi" "\0" - "Inherited,Zinh,Qaai" "\0" - "Inscriptional_Pahlavi,Phli" "\0" - "Inscriptional_Parthian,Prti" "\0" - "Javanese,Java" "\0" - "Kaithi,Kthi" "\0" - "Kannada,Knda" "\0" - "Katakana,Kana" "\0" - "Kawi,Kawi" "\0" - "Kayah_Li,Kali" "\0" - "Kharoshthi,Khar" "\0" - "Khmer,Khmr" "\0" - "Khojki,Khoj" "\0" - "Khitan_Small_Script,Kits" "\0" - "Khudawadi,Sind" "\0" - "Kirat_Rai,Krai" "\0" - "Lao,Laoo" "\0" - "Latin,Latn" "\0" - "Lepcha,Lepc" "\0" - "Limbu,Limb" "\0" - "Linear_A,Lina" "\0" - "Linear_B,Linb" "\0" - "Lisu,Lisu" "\0" - "Lycian,Lyci" "\0" - "Lydian,Lydi" "\0" - "Makasar,Maka" "\0" - "Mahajani,Mahj" "\0" - "Malayalam,Mlym" "\0" - "Mandaic,Mand" "\0" - "Manichaean,Mani" "\0" - "Marchen,Marc" "\0" - "Masaram_Gondi,Gonm" "\0" - "Medefaidrin,Medf" "\0" - "Meetei_Mayek,Mtei" "\0" - "Mende_Kikakui,Mend" "\0" - "Meroitic_Cursive,Merc" "\0" - "Meroitic_Hieroglyphs,Mero" "\0" - "Miao,Plrd" "\0" - "Modi,Modi" "\0" - "Mongolian,Mong" "\0" - "Mro,Mroo" "\0" - "Multani,Mult" "\0" - "Myanmar,Mymr" "\0" - "Nabataean,Nbat" "\0" - "Nag_Mundari,Nagm" "\0" - "Nandinagari,Nand" "\0" - "New_Tai_Lue,Talu" "\0" - "Newa,Newa" "\0" - "Nko,Nkoo" "\0" - "Nushu,Nshu" "\0" - "Nyiakeng_Puachue_Hmong,Hmnp" "\0" - "Ogham,Ogam" "\0" - "Ol_Chiki,Olck" "\0" - "Ol_Onal,Onao" "\0" - "Old_Hungarian,Hung" "\0" - "Old_Italic,Ital" "\0" - "Old_North_Arabian,Narb" "\0" - "Old_Permic,Perm" "\0" - "Old_Persian,Xpeo" "\0" - "Old_Sogdian,Sogo" "\0" - "Old_South_Arabian,Sarb" "\0" - "Old_Turkic,Orkh" "\0" - "Old_Uyghur,Ougr" "\0" - "Oriya,Orya" "\0" - "Osage,Osge" "\0" - "Osmanya,Osma" "\0" - "Pahawh_Hmong,Hmng" "\0" - "Palmyrene,Palm" "\0" - "Pau_Cin_Hau,Pauc" "\0" - "Phags_Pa,Phag" "\0" - "Phoenician,Phnx" "\0" - "Psalter_Pahlavi,Phlp" "\0" - "Rejang,Rjng" "\0" - "Runic,Runr" "\0" - "Samaritan,Samr" "\0" - "Saurashtra,Saur" "\0" - "Sharada,Shrd" "\0" - "Shavian,Shaw" "\0" - "Siddham,Sidd" "\0" - "SignWriting,Sgnw" "\0" - "Sinhala,Sinh" "\0" - "Sogdian,Sogd" "\0" - "Sora_Sompeng,Sora" "\0" - "Soyombo,Soyo" "\0" - "Sundanese,Sund" "\0" - "Sunuwar,Sunu" "\0" - "Syloti_Nagri,Sylo" "\0" - "Syriac,Syrc" "\0" - "Tagalog,Tglg" "\0" - "Tagbanwa,Tagb" "\0" - "Tai_Le,Tale" "\0" - "Tai_Tham,Lana" "\0" - "Tai_Viet,Tavt" "\0" - "Takri,Takr" "\0" - "Tamil,Taml" "\0" - "Tangut,Tang" "\0" - "Telugu,Telu" "\0" - "Thaana,Thaa" "\0" - "Thai,Thai" "\0" - "Tibetan,Tibt" "\0" - "Tifinagh,Tfng" "\0" - "Tirhuta,Tirh" "\0" - "Tangsa,Tnsa" "\0" - "Todhri,Todr" "\0" - "Toto,Toto" "\0" - "Tulu_Tigalari,Tutg" "\0" - "Ugaritic,Ugar" "\0" - "Vai,Vaii" "\0" - "Vithkuqi,Vith" "\0" - "Wancho,Wcho" "\0" - "Warang_Citi,Wara" "\0" - "Yezidi,Yezi" "\0" - "Yi,Yiii" "\0" - "Zanabazar_Square,Zanb" "\0" -; - -static const uint8_t unicode_script_table[2803] = { - 0xc0, 0x19, 0x99, 0x4a, 0x85, 0x19, 0x99, 0x4a, - 0xae, 0x19, 0x80, 0x4a, 0x8e, 0x19, 0x80, 0x4a, - 0x84, 0x19, 0x96, 0x4a, 0x80, 0x19, 0x9e, 0x4a, - 0x80, 0x19, 0xe1, 0x60, 0x4a, 0xa6, 0x19, 0x84, - 0x4a, 0x84, 0x19, 0x81, 0x0d, 0x93, 0x19, 0xe0, - 0x0f, 0x3a, 0x83, 0x2d, 0x80, 0x19, 0x82, 0x2d, - 0x01, 0x83, 0x2d, 0x80, 0x19, 0x80, 0x2d, 0x03, - 0x80, 0x2d, 0x80, 0x19, 0x80, 0x2d, 0x80, 0x19, - 0x82, 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x93, 0x2d, - 0x00, 0xbe, 0x2d, 0x8d, 0x1a, 0x8f, 0x2d, 0xe0, - 0x24, 0x1d, 0x81, 0x3a, 0xe0, 0x48, 0x1d, 0x00, - 0xa5, 0x05, 0x01, 0xb1, 0x05, 0x01, 0x82, 0x05, - 0x00, 0xb6, 0x37, 0x07, 0x9a, 0x37, 0x03, 0x85, - 0x37, 0x0a, 0x84, 0x04, 0x80, 0x19, 0x85, 0x04, - 0x80, 0x19, 0x8d, 0x04, 0x80, 0x19, 0x82, 0x04, - 0x80, 0x19, 0x9f, 0x04, 0x80, 0x19, 0x89, 0x04, - 0x8a, 0x3a, 0x99, 0x04, 0x80, 0x3a, 0xe0, 0x0b, - 0x04, 0x80, 0x19, 0xa1, 0x04, 0x8d, 0x90, 0x00, - 0xbb, 0x90, 0x01, 0x82, 0x90, 0xaf, 0x04, 0xb1, - 0x9a, 0x0d, 0xba, 0x69, 0x01, 0x82, 0x69, 0xad, - 0x83, 0x01, 0x8e, 0x83, 0x00, 0x9b, 0x55, 0x01, - 0x80, 0x55, 0x00, 0x8a, 0x90, 0x04, 0x9e, 0x04, - 0x00, 0x81, 0x04, 0x04, 0xca, 0x04, 0x80, 0x19, - 0x9c, 0x04, 0xd0, 0x20, 0x83, 0x3a, 0x8e, 0x20, - 0x81, 0x19, 0x99, 0x20, 0x83, 0x0b, 0x00, 0x87, - 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x95, 0x0b, 0x00, - 0x86, 0x0b, 0x00, 0x80, 0x0b, 0x02, 0x83, 0x0b, - 0x01, 0x88, 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x83, - 0x0b, 0x07, 0x80, 0x0b, 0x03, 0x81, 0x0b, 0x00, - 0x84, 0x0b, 0x01, 0x98, 0x0b, 0x01, 0x82, 0x30, - 0x00, 0x85, 0x30, 0x03, 0x81, 0x30, 0x01, 0x95, - 0x30, 0x00, 0x86, 0x30, 0x00, 0x81, 0x30, 0x00, - 0x81, 0x30, 0x00, 0x81, 0x30, 0x01, 0x80, 0x30, - 0x00, 0x84, 0x30, 0x03, 0x81, 0x30, 0x01, 0x82, - 0x30, 0x02, 0x80, 0x30, 0x06, 0x83, 0x30, 0x00, - 0x80, 0x30, 0x06, 0x90, 0x30, 0x09, 0x82, 0x2e, - 0x00, 0x88, 0x2e, 0x00, 0x82, 0x2e, 0x00, 0x95, - 0x2e, 0x00, 0x86, 0x2e, 0x00, 0x81, 0x2e, 0x00, - 0x84, 0x2e, 0x01, 0x89, 0x2e, 0x00, 0x82, 0x2e, - 0x00, 0x82, 0x2e, 0x01, 0x80, 0x2e, 0x0e, 0x83, - 0x2e, 0x01, 0x8b, 0x2e, 0x06, 0x86, 0x2e, 0x00, - 0x82, 0x78, 0x00, 0x87, 0x78, 0x01, 0x81, 0x78, - 0x01, 0x95, 0x78, 0x00, 0x86, 0x78, 0x00, 0x81, - 0x78, 0x00, 0x84, 0x78, 0x01, 0x88, 0x78, 0x01, - 0x81, 0x78, 0x01, 0x82, 0x78, 0x06, 0x82, 0x78, - 0x03, 0x81, 0x78, 0x00, 0x84, 0x78, 0x01, 0x91, - 0x78, 0x09, 0x81, 0x97, 0x00, 0x85, 0x97, 0x02, - 0x82, 0x97, 0x00, 0x83, 0x97, 0x02, 0x81, 0x97, - 0x00, 0x80, 0x97, 0x00, 0x81, 0x97, 0x02, 0x81, - 0x97, 0x02, 0x82, 0x97, 0x02, 0x8b, 0x97, 0x03, - 0x84, 0x97, 0x02, 0x82, 0x97, 0x00, 0x83, 0x97, - 0x01, 0x80, 0x97, 0x05, 0x80, 0x97, 0x0d, 0x94, - 0x97, 0x04, 0x8c, 0x99, 0x00, 0x82, 0x99, 0x00, - 0x96, 0x99, 0x00, 0x8f, 0x99, 0x01, 0x88, 0x99, - 0x00, 0x82, 0x99, 0x00, 0x83, 0x99, 0x06, 0x81, - 0x99, 0x00, 0x82, 0x99, 0x01, 0x80, 0x99, 0x01, - 0x83, 0x99, 0x01, 0x89, 0x99, 0x06, 0x88, 0x99, - 0x8c, 0x3f, 0x00, 0x82, 0x3f, 0x00, 0x96, 0x3f, - 0x00, 0x89, 0x3f, 0x00, 0x84, 0x3f, 0x01, 0x88, - 0x3f, 0x00, 0x82, 0x3f, 0x00, 0x83, 0x3f, 0x06, - 0x81, 0x3f, 0x05, 0x81, 0x3f, 0x00, 0x83, 0x3f, - 0x01, 0x89, 0x3f, 0x00, 0x82, 0x3f, 0x0b, 0x8c, - 0x54, 0x00, 0x82, 0x54, 0x00, 0xb2, 0x54, 0x00, - 0x82, 0x54, 0x00, 0x85, 0x54, 0x03, 0x8f, 0x54, - 0x01, 0x99, 0x54, 0x00, 0x82, 0x89, 0x00, 0x91, - 0x89, 0x02, 0x97, 0x89, 0x00, 0x88, 0x89, 0x00, - 0x80, 0x89, 0x01, 0x86, 0x89, 0x02, 0x80, 0x89, - 0x03, 0x85, 0x89, 0x00, 0x80, 0x89, 0x00, 0x87, - 0x89, 0x05, 0x89, 0x89, 0x01, 0x82, 0x89, 0x0b, - 0xb9, 0x9b, 0x03, 0x80, 0x19, 0x9b, 0x9b, 0x24, - 0x81, 0x49, 0x00, 0x80, 0x49, 0x00, 0x84, 0x49, - 0x00, 0x97, 0x49, 0x00, 0x80, 0x49, 0x00, 0x96, - 0x49, 0x01, 0x84, 0x49, 0x00, 0x80, 0x49, 0x00, - 0x86, 0x49, 0x00, 0x89, 0x49, 0x01, 0x83, 0x49, - 0x1f, 0xc7, 0x9c, 0x00, 0xa3, 0x9c, 0x03, 0xa6, - 0x9c, 0x00, 0xa3, 0x9c, 0x00, 0x8e, 0x9c, 0x00, - 0x86, 0x9c, 0x83, 0x19, 0x81, 0x9c, 0x24, 0xe0, - 0x3f, 0x63, 0xa5, 0x28, 0x00, 0x80, 0x28, 0x04, - 0x80, 0x28, 0x01, 0xaa, 0x28, 0x80, 0x19, 0x83, - 0x28, 0xe0, 0x9f, 0x33, 0xc8, 0x27, 0x00, 0x83, - 0x27, 0x01, 0x86, 0x27, 0x00, 0x80, 0x27, 0x00, - 0x83, 0x27, 0x01, 0xa8, 0x27, 0x00, 0x83, 0x27, - 0x01, 0xa0, 0x27, 0x00, 0x83, 0x27, 0x01, 0x86, - 0x27, 0x00, 0x80, 0x27, 0x00, 0x83, 0x27, 0x01, - 0x8e, 0x27, 0x00, 0xb8, 0x27, 0x00, 0x83, 0x27, - 0x01, 0xc2, 0x27, 0x01, 0x9f, 0x27, 0x02, 0x99, - 0x27, 0x05, 0xd5, 0x17, 0x01, 0x85, 0x17, 0x01, - 0xe2, 0x1f, 0x12, 0x9c, 0x6c, 0x02, 0xca, 0x82, - 0x82, 0x19, 0x8a, 0x82, 0x06, 0x95, 0x91, 0x08, - 0x80, 0x91, 0x94, 0x35, 0x81, 0x19, 0x08, 0x93, - 0x11, 0x0b, 0x8c, 0x92, 0x00, 0x82, 0x92, 0x00, - 0x81, 0x92, 0x0b, 0xdd, 0x44, 0x01, 0x89, 0x44, - 0x05, 0x89, 0x44, 0x05, 0x81, 0x60, 0x81, 0x19, - 0x80, 0x60, 0x80, 0x19, 0x93, 0x60, 0x05, 0xd8, - 0x60, 0x06, 0xaa, 0x60, 0x04, 0xc5, 0x12, 0x09, - 0x9e, 0x4c, 0x00, 0x8b, 0x4c, 0x03, 0x8b, 0x4c, - 0x03, 0x80, 0x4c, 0x02, 0x8b, 0x4c, 0x9d, 0x93, - 0x01, 0x84, 0x93, 0x0a, 0xab, 0x67, 0x03, 0x99, - 0x67, 0x05, 0x8a, 0x67, 0x02, 0x81, 0x67, 0x9f, - 0x44, 0x9b, 0x10, 0x01, 0x81, 0x10, 0xbe, 0x94, - 0x00, 0x9c, 0x94, 0x01, 0x8a, 0x94, 0x05, 0x89, - 0x94, 0x05, 0x8d, 0x94, 0x01, 0x9e, 0x3a, 0x30, - 0xcc, 0x07, 0x00, 0xb1, 0x07, 0xbf, 0x8d, 0xb3, - 0x0a, 0x07, 0x83, 0x0a, 0xb7, 0x4b, 0x02, 0x8e, - 0x4b, 0x02, 0x82, 0x4b, 0xaf, 0x6d, 0x8a, 0x1d, - 0x04, 0xaa, 0x28, 0x01, 0x82, 0x28, 0x87, 0x8d, - 0x07, 0x82, 0x3a, 0x80, 0x19, 0x8c, 0x3a, 0x80, - 0x19, 0x86, 0x3a, 0x83, 0x19, 0x80, 0x3a, 0x85, - 0x19, 0x80, 0x3a, 0x82, 0x19, 0x81, 0x3a, 0x80, - 0x19, 0x04, 0xa5, 0x4a, 0x84, 0x2d, 0x80, 0x1d, - 0xb0, 0x4a, 0x84, 0x2d, 0x83, 0x4a, 0x84, 0x2d, - 0x8c, 0x4a, 0x80, 0x1d, 0xc5, 0x4a, 0x80, 0x2d, - 0xbf, 0x3a, 0xe0, 0x9f, 0x4a, 0x95, 0x2d, 0x01, - 0x85, 0x2d, 0x01, 0xa5, 0x2d, 0x01, 0x85, 0x2d, - 0x01, 0x87, 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x80, - 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x9e, 0x2d, 0x01, - 0xb4, 0x2d, 0x00, 0x8e, 0x2d, 0x00, 0x8d, 0x2d, - 0x01, 0x85, 0x2d, 0x00, 0x92, 0x2d, 0x01, 0x82, - 0x2d, 0x00, 0x88, 0x2d, 0x00, 0x8b, 0x19, 0x81, - 0x3a, 0xd6, 0x19, 0x00, 0x8a, 0x19, 0x80, 0x4a, - 0x01, 0x8a, 0x19, 0x80, 0x4a, 0x8e, 0x19, 0x00, - 0x8c, 0x4a, 0x02, 0xa0, 0x19, 0x0e, 0xa0, 0x3a, - 0x0e, 0xa5, 0x19, 0x80, 0x2d, 0x82, 0x19, 0x81, - 0x4a, 0x85, 0x19, 0x80, 0x4a, 0x9a, 0x19, 0x80, - 0x4a, 0x90, 0x19, 0xa8, 0x4a, 0x82, 0x19, 0x03, - 0xe2, 0x39, 0x19, 0x15, 0x8a, 0x19, 0x14, 0xe3, - 0x3f, 0x19, 0xe0, 0x9f, 0x0f, 0xe2, 0x13, 0x19, - 0x01, 0x9f, 0x19, 0x00, 0xe0, 0x08, 0x19, 0xdf, - 0x29, 0x9f, 0x4a, 0xe0, 0x13, 0x1a, 0x04, 0x86, - 0x1a, 0xa5, 0x28, 0x00, 0x80, 0x28, 0x04, 0x80, - 0x28, 0x01, 0xb7, 0x9d, 0x06, 0x81, 0x9d, 0x0d, - 0x80, 0x9d, 0x96, 0x27, 0x08, 0x86, 0x27, 0x00, - 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, - 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, - 0x27, 0x00, 0x86, 0x27, 0x00, 0x9f, 0x1d, 0xdd, - 0x19, 0x21, 0x99, 0x32, 0x00, 0xd8, 0x32, 0x0b, - 0xe0, 0x75, 0x32, 0x19, 0x94, 0x19, 0x80, 0x32, - 0x80, 0x19, 0x80, 0x32, 0x98, 0x19, 0x88, 0x32, - 0x83, 0x3a, 0x81, 0x33, 0x87, 0x19, 0x83, 0x32, - 0x83, 0x19, 0x00, 0xd5, 0x38, 0x01, 0x81, 0x3a, - 0x81, 0x19, 0x82, 0x38, 0x80, 0x19, 0xd9, 0x40, - 0x81, 0x19, 0x82, 0x40, 0x04, 0xaa, 0x0d, 0x00, - 0xdd, 0x33, 0x00, 0x8f, 0x19, 0x9f, 0x0d, 0xa5, - 0x19, 0x08, 0x80, 0x19, 0x8f, 0x40, 0x9e, 0x33, - 0x00, 0xbf, 0x19, 0x9e, 0x33, 0xd0, 0x19, 0xae, - 0x40, 0x80, 0x19, 0xd7, 0x40, 0xe0, 0x47, 0x19, - 0xf0, 0x09, 0x5f, 0x32, 0xbf, 0x19, 0xf0, 0x41, - 0x9f, 0x32, 0xe4, 0x2c, 0xa9, 0x02, 0xb6, 0xa9, - 0x08, 0xaf, 0x4f, 0xe0, 0xcb, 0xa4, 0x13, 0xdf, - 0x1d, 0xd7, 0x08, 0x07, 0xa1, 0x19, 0xe0, 0x05, - 0x4a, 0x82, 0x19, 0xc2, 0x4a, 0x01, 0x81, 0x4a, - 0x00, 0x80, 0x4a, 0x00, 0x87, 0x4a, 0x14, 0x8d, - 0x4a, 0xac, 0x8f, 0x02, 0x89, 0x19, 0x05, 0xb7, - 0x7e, 0x07, 0xc5, 0x84, 0x07, 0x8b, 0x84, 0x05, - 0x9f, 0x20, 0xad, 0x42, 0x80, 0x19, 0x80, 0x42, - 0xa3, 0x81, 0x0a, 0x80, 0x81, 0x9c, 0x33, 0x02, - 0xcd, 0x3d, 0x00, 0x80, 0x19, 0x89, 0x3d, 0x03, - 0x81, 0x3d, 0x9e, 0x63, 0x00, 0xb6, 0x16, 0x08, - 0x8d, 0x16, 0x01, 0x89, 0x16, 0x01, 0x83, 0x16, - 0x9f, 0x63, 0xc2, 0x95, 0x17, 0x84, 0x95, 0x96, - 0x5a, 0x09, 0x85, 0x27, 0x01, 0x85, 0x27, 0x01, - 0x85, 0x27, 0x08, 0x86, 0x27, 0x00, 0x86, 0x27, - 0x00, 0xaa, 0x4a, 0x80, 0x19, 0x88, 0x4a, 0x80, - 0x2d, 0x83, 0x4a, 0x81, 0x19, 0x03, 0xcf, 0x17, - 0xad, 0x5a, 0x01, 0x89, 0x5a, 0x05, 0xf0, 0x1b, - 0x43, 0x33, 0x0b, 0x96, 0x33, 0x03, 0xb0, 0x33, - 0x70, 0x10, 0xa3, 0xe1, 0x0d, 0x32, 0x01, 0xe0, - 0x09, 0x32, 0x25, 0x86, 0x4a, 0x0b, 0x84, 0x05, - 0x04, 0x99, 0x37, 0x00, 0x84, 0x37, 0x00, 0x80, - 0x37, 0x00, 0x81, 0x37, 0x00, 0x81, 0x37, 0x00, - 0x89, 0x37, 0xe0, 0x12, 0x04, 0x0f, 0xe1, 0x0a, - 0x04, 0x81, 0x19, 0xcf, 0x04, 0x01, 0xb5, 0x04, - 0x06, 0x80, 0x04, 0x1f, 0x8f, 0x04, 0x8f, 0x3a, - 0x89, 0x19, 0x05, 0x8d, 0x3a, 0x81, 0x1d, 0xa2, - 0x19, 0x00, 0x92, 0x19, 0x00, 0x83, 0x19, 0x03, - 0x84, 0x04, 0x00, 0xe0, 0x26, 0x04, 0x01, 0x80, - 0x19, 0x00, 0x9f, 0x19, 0x99, 0x4a, 0x85, 0x19, - 0x99, 0x4a, 0x8a, 0x19, 0x89, 0x40, 0x80, 0x19, - 0xac, 0x40, 0x81, 0x19, 0x9e, 0x33, 0x02, 0x85, - 0x33, 0x01, 0x85, 0x33, 0x01, 0x85, 0x33, 0x01, - 0x82, 0x33, 0x02, 0x86, 0x19, 0x00, 0x86, 0x19, - 0x09, 0x84, 0x19, 0x01, 0x8b, 0x4e, 0x00, 0x99, - 0x4e, 0x00, 0x92, 0x4e, 0x00, 0x81, 0x4e, 0x00, - 0x8e, 0x4e, 0x01, 0x8d, 0x4e, 0x21, 0xe0, 0x1a, - 0x4e, 0x04, 0x82, 0x19, 0x03, 0xac, 0x19, 0x02, - 0x88, 0x19, 0xce, 0x2d, 0x00, 0x8c, 0x19, 0x02, - 0x80, 0x2d, 0x2e, 0xac, 0x19, 0x80, 0x3a, 0x60, - 0x21, 0x9c, 0x50, 0x02, 0xb0, 0x13, 0x0e, 0x80, - 0x3a, 0x9a, 0x19, 0x03, 0xa3, 0x70, 0x08, 0x82, - 0x70, 0x9a, 0x2a, 0x04, 0xaa, 0x72, 0x04, 0x9d, - 0xa3, 0x00, 0x80, 0xa3, 0xa3, 0x73, 0x03, 0x8d, - 0x73, 0x29, 0xcf, 0x1f, 0xaf, 0x86, 0x9d, 0x7a, - 0x01, 0x89, 0x7a, 0x05, 0xa3, 0x79, 0x03, 0xa3, - 0x79, 0x03, 0xa7, 0x25, 0x07, 0xb3, 0x14, 0x0a, - 0x80, 0x14, 0x8a, 0xa5, 0x00, 0x8e, 0xa5, 0x00, - 0x86, 0xa5, 0x00, 0x81, 0xa5, 0x00, 0x8a, 0xa5, - 0x00, 0x8e, 0xa5, 0x00, 0x86, 0xa5, 0x00, 0x81, - 0xa5, 0x02, 0xb3, 0xa0, 0x0b, 0xe0, 0xd6, 0x4d, - 0x08, 0x95, 0x4d, 0x09, 0x87, 0x4d, 0x17, 0x85, - 0x4a, 0x00, 0xa9, 0x4a, 0x00, 0x88, 0x4a, 0x44, - 0x85, 0x1c, 0x01, 0x80, 0x1c, 0x00, 0xab, 0x1c, - 0x00, 0x81, 0x1c, 0x02, 0x80, 0x1c, 0x01, 0x80, - 0x1c, 0x95, 0x39, 0x00, 0x88, 0x39, 0x9f, 0x7c, - 0x9e, 0x64, 0x07, 0x88, 0x64, 0x2f, 0x92, 0x36, - 0x00, 0x81, 0x36, 0x04, 0x84, 0x36, 0x9b, 0x7f, - 0x02, 0x80, 0x7f, 0x99, 0x51, 0x04, 0x80, 0x51, - 0x3f, 0x9f, 0x5d, 0x97, 0x5c, 0x03, 0x93, 0x5c, - 0x01, 0xad, 0x5c, 0x83, 0x43, 0x00, 0x81, 0x43, - 0x04, 0x87, 0x43, 0x00, 0x82, 0x43, 0x00, 0x9c, - 0x43, 0x01, 0x82, 0x43, 0x03, 0x89, 0x43, 0x06, - 0x88, 0x43, 0x06, 0x9f, 0x75, 0x9f, 0x71, 0x1f, - 0xa6, 0x56, 0x03, 0x8b, 0x56, 0x08, 0xb5, 0x06, - 0x02, 0x86, 0x06, 0x95, 0x3c, 0x01, 0x87, 0x3c, - 0x92, 0x3b, 0x04, 0x87, 0x3b, 0x91, 0x80, 0x06, - 0x83, 0x80, 0x0b, 0x86, 0x80, 0x4f, 0xc8, 0x76, - 0x36, 0xb2, 0x6f, 0x0c, 0xb2, 0x6f, 0x06, 0x85, - 0x6f, 0xa7, 0x34, 0x07, 0x89, 0x34, 0x05, 0xa5, - 0x2b, 0x02, 0x9c, 0x2b, 0x07, 0x81, 0x2b, 0x60, - 0x6f, 0x9e, 0x04, 0x00, 0xa9, 0xa8, 0x00, 0x82, - 0xa8, 0x01, 0x81, 0xa8, 0x0f, 0x82, 0x04, 0x36, - 0x83, 0x04, 0xa7, 0x74, 0x07, 0xa9, 0x8a, 0x15, - 0x99, 0x77, 0x25, 0x9b, 0x18, 0x13, 0x96, 0x26, - 0x08, 0xcd, 0x0e, 0x03, 0xa3, 0x0e, 0x08, 0x80, - 0x0e, 0xc2, 0x3e, 0x09, 0x80, 0x3e, 0x01, 0x98, - 0x8b, 0x06, 0x89, 0x8b, 0x05, 0xb4, 0x15, 0x00, - 0x91, 0x15, 0x07, 0xa6, 0x53, 0x08, 0xdf, 0x85, - 0x00, 0x93, 0x89, 0x0a, 0x91, 0x45, 0x00, 0xae, - 0x45, 0x3d, 0x86, 0x62, 0x00, 0x80, 0x62, 0x00, - 0x83, 0x62, 0x00, 0x8e, 0x62, 0x00, 0x8a, 0x62, - 0x05, 0xba, 0x47, 0x04, 0x89, 0x47, 0x05, 0x83, - 0x2c, 0x00, 0x87, 0x2c, 0x01, 0x81, 0x2c, 0x01, - 0x95, 0x2c, 0x00, 0x86, 0x2c, 0x00, 0x81, 0x2c, - 0x00, 0x84, 0x2c, 0x00, 0x80, 0x3a, 0x88, 0x2c, - 0x01, 0x81, 0x2c, 0x01, 0x82, 0x2c, 0x01, 0x80, - 0x2c, 0x05, 0x80, 0x2c, 0x04, 0x86, 0x2c, 0x01, - 0x86, 0x2c, 0x02, 0x84, 0x2c, 0x0a, 0x89, 0xa2, - 0x00, 0x80, 0xa2, 0x01, 0x80, 0xa2, 0x00, 0xa5, - 0xa2, 0x00, 0x89, 0xa2, 0x00, 0x80, 0xa2, 0x01, - 0x80, 0xa2, 0x00, 0x83, 0xa2, 0x00, 0x89, 0xa2, - 0x00, 0x81, 0xa2, 0x07, 0x81, 0xa2, 0x1c, 0xdb, - 0x68, 0x00, 0x84, 0x68, 0x1d, 0xc7, 0x9e, 0x07, - 0x89, 0x9e, 0x60, 0x45, 0xb5, 0x87, 0x01, 0xa5, - 0x87, 0x21, 0xc4, 0x5f, 0x0a, 0x89, 0x5f, 0x05, - 0x8c, 0x60, 0x12, 0xb9, 0x96, 0x05, 0x89, 0x96, - 0x05, 0x93, 0x63, 0x1b, 0x9a, 0x02, 0x01, 0x8e, - 0x02, 0x03, 0x96, 0x02, 0x60, 0x58, 0xbb, 0x22, - 0x60, 0x03, 0xd2, 0xa7, 0x0b, 0x80, 0xa7, 0x86, - 0x21, 0x01, 0x80, 0x21, 0x01, 0x87, 0x21, 0x00, - 0x81, 0x21, 0x00, 0x9d, 0x21, 0x00, 0x81, 0x21, - 0x01, 0x8b, 0x21, 0x08, 0x89, 0x21, 0x45, 0x87, - 0x66, 0x01, 0xad, 0x66, 0x01, 0x8a, 0x66, 0x1a, - 0xc7, 0xaa, 0x07, 0xd2, 0x8c, 0x0c, 0x8f, 0x12, - 0xb8, 0x7d, 0x06, 0x89, 0x20, 0x60, 0x55, 0xa1, - 0x8e, 0x0d, 0x89, 0x8e, 0x05, 0x88, 0x0c, 0x00, - 0xac, 0x0c, 0x00, 0x8d, 0x0c, 0x09, 0x9c, 0x0c, - 0x02, 0x9f, 0x57, 0x01, 0x95, 0x57, 0x00, 0x8d, - 0x57, 0x48, 0x86, 0x58, 0x00, 0x81, 0x58, 0x00, - 0xab, 0x58, 0x02, 0x80, 0x58, 0x00, 0x81, 0x58, - 0x00, 0x88, 0x58, 0x07, 0x89, 0x58, 0x05, 0x85, - 0x2f, 0x00, 0x81, 0x2f, 0x00, 0xa4, 0x2f, 0x00, - 0x81, 0x2f, 0x00, 0x85, 0x2f, 0x06, 0x89, 0x2f, - 0x60, 0xd5, 0x98, 0x52, 0x06, 0x90, 0x41, 0x00, - 0xa8, 0x41, 0x02, 0x9c, 0x41, 0x54, 0x80, 0x4f, - 0x0e, 0xb1, 0x97, 0x0c, 0x80, 0x97, 0xe3, 0x39, - 0x1b, 0x60, 0x05, 0xe0, 0x0e, 0x1b, 0x00, 0x84, - 0x1b, 0x0a, 0xe0, 0x63, 0x1b, 0x69, 0xeb, 0xe0, - 0x02, 0x1e, 0x0c, 0xe3, 0xf5, 0x24, 0x09, 0xef, - 0x3a, 0x24, 0x04, 0xe1, 0xe6, 0x03, 0x70, 0x0a, - 0x58, 0xb9, 0x31, 0x66, 0x65, 0xe1, 0xd8, 0x08, - 0x06, 0x9e, 0x61, 0x00, 0x89, 0x61, 0x03, 0x81, - 0x61, 0xce, 0x9f, 0x00, 0x89, 0x9f, 0x05, 0x9d, - 0x09, 0x01, 0x85, 0x09, 0x09, 0xc5, 0x7b, 0x09, - 0x89, 0x7b, 0x00, 0x86, 0x7b, 0x00, 0x94, 0x7b, - 0x04, 0x92, 0x7b, 0x61, 0x4f, 0xb9, 0x48, 0x60, - 0x65, 0xda, 0x59, 0x60, 0x04, 0xca, 0x5e, 0x03, - 0xb8, 0x5e, 0x06, 0x90, 0x5e, 0x3f, 0x80, 0x98, - 0x80, 0x6a, 0x81, 0x32, 0x80, 0x46, 0x0a, 0x81, - 0x32, 0x0d, 0xf0, 0x07, 0x97, 0x98, 0x07, 0xe2, - 0x9f, 0x98, 0xe1, 0x75, 0x46, 0x28, 0x80, 0x46, - 0x88, 0x98, 0x70, 0x12, 0x86, 0x83, 0x40, 0x00, - 0x86, 0x40, 0x00, 0x81, 0x40, 0x00, 0x80, 0x40, - 0xe0, 0xbe, 0x38, 0x82, 0x40, 0x0e, 0x80, 0x38, - 0x1c, 0x82, 0x38, 0x01, 0x80, 0x40, 0x0d, 0x83, - 0x40, 0x07, 0xe1, 0x2b, 0x6a, 0x68, 0xa3, 0xe0, - 0x0a, 0x23, 0x04, 0x8c, 0x23, 0x02, 0x88, 0x23, - 0x06, 0x89, 0x23, 0x01, 0x83, 0x23, 0x83, 0x19, - 0x6e, 0xfb, 0xe0, 0x99, 0x19, 0x05, 0xe1, 0x53, - 0x19, 0x4b, 0xad, 0x3a, 0x01, 0x96, 0x3a, 0x08, - 0xe0, 0x13, 0x19, 0x3b, 0xe0, 0x95, 0x19, 0x09, - 0xa6, 0x19, 0x01, 0xbd, 0x19, 0x82, 0x3a, 0x90, - 0x19, 0x87, 0x3a, 0x81, 0x19, 0x86, 0x3a, 0x9d, - 0x19, 0x83, 0x3a, 0xbc, 0x19, 0x14, 0xc5, 0x2d, - 0x60, 0x19, 0x93, 0x19, 0x0b, 0x93, 0x19, 0x0b, - 0xd6, 0x19, 0x08, 0x98, 0x19, 0x60, 0x26, 0xd4, - 0x19, 0x00, 0xc6, 0x19, 0x00, 0x81, 0x19, 0x01, - 0x80, 0x19, 0x01, 0x81, 0x19, 0x01, 0x83, 0x19, - 0x00, 0x8b, 0x19, 0x00, 0x80, 0x19, 0x00, 0x86, - 0x19, 0x00, 0xc0, 0x19, 0x00, 0x83, 0x19, 0x01, - 0x87, 0x19, 0x00, 0x86, 0x19, 0x00, 0x9b, 0x19, - 0x00, 0x83, 0x19, 0x00, 0x84, 0x19, 0x00, 0x80, - 0x19, 0x02, 0x86, 0x19, 0x00, 0xe0, 0xf3, 0x19, - 0x01, 0xe0, 0xc3, 0x19, 0x01, 0xb1, 0x19, 0xe2, - 0x2b, 0x88, 0x0e, 0x84, 0x88, 0x00, 0x8e, 0x88, - 0x63, 0xef, 0x9e, 0x4a, 0x05, 0x85, 0x4a, 0x60, - 0x74, 0x86, 0x29, 0x00, 0x90, 0x29, 0x01, 0x86, - 0x29, 0x00, 0x81, 0x29, 0x00, 0x84, 0x29, 0x04, - 0xbd, 0x1d, 0x20, 0x80, 0x1d, 0x60, 0x0f, 0xac, - 0x6b, 0x02, 0x8d, 0x6b, 0x01, 0x89, 0x6b, 0x03, - 0x81, 0x6b, 0x60, 0xdf, 0x9e, 0xa1, 0x10, 0xb9, - 0xa6, 0x04, 0x80, 0xa6, 0x61, 0x6f, 0xa9, 0x65, - 0x60, 0x75, 0xaa, 0x6e, 0x03, 0x80, 0x6e, 0x61, - 0x7f, 0x86, 0x27, 0x00, 0x83, 0x27, 0x00, 0x81, - 0x27, 0x00, 0x8e, 0x27, 0x00, 0xe0, 0x64, 0x5b, - 0x01, 0x8f, 0x5b, 0x28, 0xcb, 0x01, 0x03, 0x89, - 0x01, 0x03, 0x81, 0x01, 0x62, 0xb0, 0xc3, 0x19, - 0x4b, 0xbc, 0x19, 0x60, 0x61, 0x83, 0x04, 0x00, - 0x9a, 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, 0x04, - 0x01, 0x80, 0x04, 0x00, 0x89, 0x04, 0x00, 0x83, - 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x05, - 0x80, 0x04, 0x03, 0x80, 0x04, 0x00, 0x80, 0x04, - 0x00, 0x80, 0x04, 0x00, 0x82, 0x04, 0x00, 0x81, - 0x04, 0x00, 0x80, 0x04, 0x01, 0x80, 0x04, 0x00, - 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, - 0x00, 0x80, 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, - 0x04, 0x01, 0x83, 0x04, 0x00, 0x86, 0x04, 0x00, - 0x83, 0x04, 0x00, 0x83, 0x04, 0x00, 0x80, 0x04, - 0x00, 0x89, 0x04, 0x00, 0x90, 0x04, 0x04, 0x82, - 0x04, 0x00, 0x84, 0x04, 0x00, 0x90, 0x04, 0x33, - 0x81, 0x04, 0x60, 0xad, 0xab, 0x19, 0x03, 0xe0, - 0x03, 0x19, 0x0b, 0x8e, 0x19, 0x01, 0x8e, 0x19, - 0x00, 0x8e, 0x19, 0x00, 0xa4, 0x19, 0x09, 0xe0, - 0x4d, 0x19, 0x37, 0x99, 0x19, 0x80, 0x38, 0x81, - 0x19, 0x0c, 0xab, 0x19, 0x03, 0x88, 0x19, 0x06, - 0x81, 0x19, 0x0d, 0x85, 0x19, 0x60, 0x39, 0xe3, - 0x77, 0x19, 0x03, 0x90, 0x19, 0x02, 0x8c, 0x19, - 0x02, 0xe0, 0x16, 0x19, 0x03, 0xde, 0x19, 0x05, - 0x8b, 0x19, 0x03, 0x80, 0x19, 0x0e, 0x8b, 0x19, - 0x03, 0xb7, 0x19, 0x07, 0x89, 0x19, 0x05, 0xa7, - 0x19, 0x07, 0x9d, 0x19, 0x01, 0x8b, 0x19, 0x03, - 0x81, 0x19, 0x3d, 0xe0, 0xf3, 0x19, 0x0b, 0x8d, - 0x19, 0x01, 0x8c, 0x19, 0x02, 0x89, 0x19, 0x04, - 0xb7, 0x19, 0x06, 0x8e, 0x19, 0x01, 0x8a, 0x19, - 0x05, 0x88, 0x19, 0x06, 0xe0, 0x32, 0x19, 0x00, - 0xe0, 0x05, 0x19, 0x63, 0xa5, 0xf0, 0x96, 0x7f, - 0x32, 0x1f, 0xef, 0xd9, 0x32, 0x05, 0xe0, 0x7d, - 0x32, 0x01, 0xf0, 0x06, 0x21, 0x32, 0x0d, 0xf0, - 0x0c, 0xd0, 0x32, 0x0e, 0xe2, 0x0d, 0x32, 0x69, - 0x41, 0xe1, 0xbd, 0x32, 0x65, 0x81, 0xf0, 0x02, - 0xea, 0x32, 0x04, 0xef, 0xff, 0x32, 0x7a, 0xcb, - 0xf0, 0x80, 0x19, 0x1d, 0xdf, 0x19, 0x60, 0x1f, - 0xe0, 0x8f, 0x3a, -}; - -static const uint8_t unicode_script_ext_table[1253] = { - 0x80, 0x36, 0x00, 0x00, 0x10, 0x06, 0x13, 0x1a, - 0x23, 0x25, 0x28, 0x29, 0x2f, 0x2a, 0x2d, 0x32, - 0x4a, 0x51, 0x53, 0x72, 0x86, 0x81, 0x83, 0x00, - 0x00, 0x07, 0x0b, 0x1d, 0x20, 0x4a, 0x4f, 0x9b, - 0xa1, 0x09, 0x00, 0x00, 0x02, 0x0d, 0x4a, 0x00, - 0x00, 0x02, 0x02, 0x0d, 0x4a, 0x00, 0x00, 0x00, - 0x02, 0x4a, 0x4f, 0x08, 0x00, 0x00, 0x02, 0x4a, - 0x9b, 0x00, 0x00, 0x00, 0x02, 0x0d, 0x4a, 0x25, - 0x00, 0x00, 0x08, 0x17, 0x1a, 0x1d, 0x2d, 0x4a, - 0x72, 0x8e, 0x93, 0x00, 0x08, 0x17, 0x1d, 0x2d, - 0x4a, 0x79, 0x8e, 0x93, 0xa0, 0x00, 0x04, 0x17, - 0x1d, 0x4a, 0x9d, 0x00, 0x05, 0x29, 0x4a, 0x8e, - 0x90, 0x9b, 0x00, 0x0b, 0x14, 0x17, 0x1a, 0x1d, - 0x2a, 0x2d, 0x4a, 0x79, 0x90, 0x9d, 0xa0, 0x00, - 0x06, 0x1a, 0x25, 0x29, 0x2a, 0x40, 0x4a, 0x00, - 0x04, 0x1d, 0x2d, 0x4a, 0x72, 0x00, 0x09, 0x1a, - 0x23, 0x37, 0x4a, 0x72, 0x90, 0x93, 0x9d, 0xa0, - 0x00, 0x0a, 0x05, 0x1d, 0x23, 0x2a, 0x2d, 0x37, - 0x4a, 0x72, 0x90, 0x93, 0x00, 0x02, 0x4a, 0x9d, - 0x00, 0x03, 0x23, 0x4a, 0x90, 0x00, 0x04, 0x17, - 0x1d, 0x4a, 0x79, 0x00, 0x03, 0x17, 0x4a, 0x93, - 0x00, 0x02, 0x4a, 0x8e, 0x00, 0x02, 0x27, 0x4a, - 0x00, 0x00, 0x00, 0x02, 0x4a, 0x8e, 0x00, 0x03, - 0x1d, 0x4a, 0xa0, 0x00, 0x00, 0x00, 0x04, 0x2d, - 0x4a, 0x72, 0xa0, 0x0b, 0x00, 0x00, 0x02, 0x4a, - 0x90, 0x01, 0x00, 0x00, 0x05, 0x17, 0x23, 0x40, - 0x4a, 0x90, 0x00, 0x04, 0x17, 0x23, 0x4a, 0x90, - 0x00, 0x02, 0x4a, 0x90, 0x06, 0x00, 0x00, 0x03, - 0x4a, 0x8e, 0x90, 0x00, 0x02, 0x4a, 0x90, 0x00, - 0x00, 0x00, 0x03, 0x17, 0x4a, 0x90, 0x00, 0x06, - 0x14, 0x17, 0x2a, 0x4a, 0x8e, 0x9b, 0x0f, 0x00, - 0x00, 0x01, 0x2d, 0x01, 0x00, 0x00, 0x01, 0x2d, - 0x11, 0x00, 0x00, 0x02, 0x4a, 0x79, 0x04, 0x00, - 0x00, 0x03, 0x14, 0x4a, 0xa0, 0x03, 0x00, 0x0c, - 0x01, 0x4a, 0x03, 0x00, 0x01, 0x02, 0x1a, 0x2d, - 0x80, 0x8c, 0x00, 0x00, 0x02, 0x1d, 0x72, 0x00, - 0x02, 0x1d, 0x29, 0x01, 0x02, 0x1d, 0x4a, 0x00, - 0x02, 0x1d, 0x29, 0x80, 0x80, 0x00, 0x00, 0x03, - 0x05, 0x28, 0x29, 0x80, 0x01, 0x00, 0x00, 0x07, - 0x04, 0x2b, 0x69, 0x34, 0x90, 0x9a, 0xa8, 0x0d, - 0x00, 0x00, 0x07, 0x04, 0x2b, 0x69, 0x34, 0x90, - 0x9a, 0xa8, 0x00, 0x03, 0x04, 0x90, 0x9a, 0x01, - 0x00, 0x00, 0x08, 0x01, 0x04, 0x2b, 0x69, 0x34, - 0x90, 0x9a, 0xa8, 0x1f, 0x00, 0x00, 0x09, 0x01, - 0x04, 0x55, 0x56, 0x77, 0x80, 0x34, 0x8a, 0x90, - 0x09, 0x00, 0x0a, 0x02, 0x04, 0x90, 0x09, 0x00, - 0x09, 0x03, 0x04, 0x9a, 0xa8, 0x05, 0x00, 0x00, - 0x02, 0x04, 0x90, 0x62, 0x00, 0x00, 0x02, 0x04, - 0x34, 0x81, 0xfb, 0x00, 0x00, 0x0d, 0x0b, 0x20, - 0x2c, 0x2e, 0x30, 0x3f, 0x4a, 0x54, 0x78, 0x85, - 0x97, 0x99, 0x9e, 0x00, 0x0c, 0x0b, 0x20, 0x2c, - 0x2e, 0x30, 0x3f, 0x4a, 0x54, 0x78, 0x97, 0x99, - 0x9e, 0x10, 0x00, 0x00, 0x15, 0x0b, 0x20, 0x22, - 0x2f, 0x58, 0x2c, 0x2e, 0x30, 0x3f, 0x53, 0x54, - 0x66, 0x6e, 0x78, 0x47, 0x89, 0x8f, 0x96, 0x97, - 0x99, 0x9e, 0x00, 0x17, 0x0b, 0x20, 0x22, 0x2f, - 0x58, 0x2c, 0x2e, 0x31, 0x30, 0x3f, 0x4c, 0x53, - 0x54, 0x66, 0x6e, 0x78, 0x47, 0x89, 0x8f, 0x96, - 0x97, 0x99, 0x9e, 0x09, 0x04, 0x20, 0x22, 0x3e, - 0x53, 0x75, 0x00, 0x09, 0x03, 0x0b, 0x15, 0x8f, - 0x75, 0x00, 0x09, 0x02, 0x30, 0x62, 0x75, 0x00, - 0x09, 0x02, 0x2e, 0x45, 0x80, 0x75, 0x00, 0x0d, - 0x02, 0x2c, 0x97, 0x80, 0x71, 0x00, 0x09, 0x03, - 0x3f, 0x66, 0xa2, 0x82, 0xcf, 0x00, 0x09, 0x03, - 0x15, 0x63, 0x93, 0x80, 0x30, 0x00, 0x00, 0x03, - 0x28, 0x29, 0x4a, 0x85, 0x6e, 0x00, 0x02, 0x01, - 0x82, 0x46, 0x00, 0x01, 0x04, 0x11, 0x35, 0x92, - 0x91, 0x80, 0x4a, 0x00, 0x01, 0x02, 0x60, 0x7e, - 0x00, 0x00, 0x00, 0x02, 0x60, 0x7e, 0x84, 0x49, - 0x00, 0x00, 0x04, 0x0b, 0x20, 0x2c, 0x3f, 0x00, - 0x01, 0x20, 0x00, 0x04, 0x0b, 0x20, 0x2c, 0x3f, - 0x00, 0x03, 0x20, 0x2c, 0x3f, 0x00, 0x01, 0x20, - 0x01, 0x02, 0x0b, 0x20, 0x00, 0x02, 0x20, 0x85, - 0x00, 0x02, 0x0b, 0x20, 0x00, 0x02, 0x20, 0x85, - 0x00, 0x06, 0x20, 0x3f, 0x54, 0x78, 0x97, 0x99, - 0x00, 0x01, 0x20, 0x01, 0x02, 0x20, 0x85, 0x01, - 0x01, 0x20, 0x00, 0x02, 0x20, 0x85, 0x00, 0x02, - 0x0b, 0x20, 0x06, 0x01, 0x20, 0x00, 0x02, 0x20, - 0x66, 0x00, 0x02, 0x0b, 0x20, 0x01, 0x01, 0x20, - 0x00, 0x02, 0x0b, 0x20, 0x03, 0x01, 0x20, 0x00, - 0x0b, 0x0b, 0x20, 0x2c, 0x3f, 0x54, 0x66, 0x78, - 0x89, 0x99, 0x9e, 0xa2, 0x00, 0x02, 0x20, 0x2c, - 0x00, 0x04, 0x20, 0x2c, 0x3f, 0xa2, 0x01, 0x02, - 0x0b, 0x20, 0x00, 0x01, 0x0b, 0x01, 0x02, 0x20, - 0x2c, 0x00, 0x01, 0x66, 0x80, 0x44, 0x00, 0x01, - 0x01, 0x2d, 0x35, 0x00, 0x00, 0x03, 0x1d, 0x4a, - 0x90, 0x00, 0x00, 0x00, 0x01, 0x90, 0x81, 0xb3, - 0x00, 0x00, 0x03, 0x4a, 0x60, 0x7e, 0x1e, 0x00, - 0x00, 0x02, 0x01, 0x04, 0x09, 0x00, 0x00, 0x06, - 0x13, 0x28, 0x29, 0x6f, 0x50, 0x76, 0x01, 0x00, - 0x00, 0x04, 0x13, 0x2d, 0x6f, 0x5d, 0x80, 0x11, - 0x00, 0x00, 0x03, 0x20, 0x2c, 0x4a, 0x8c, 0xa5, - 0x00, 0x00, 0x02, 0x1a, 0x4a, 0x17, 0x00, 0x00, - 0x02, 0x06, 0x76, 0x00, 0x07, 0x06, 0x13, 0x28, - 0x6f, 0x3e, 0x51, 0x83, 0x09, 0x00, 0x00, 0x01, - 0x23, 0x03, 0x00, 0x00, 0x03, 0x01, 0x04, 0x6f, - 0x00, 0x00, 0x00, 0x02, 0x1d, 0x29, 0x81, 0x2b, - 0x00, 0x0f, 0x02, 0x32, 0x98, 0x00, 0x00, 0x00, - 0x07, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, 0xa9, - 0x00, 0x08, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, - 0x7e, 0xa9, 0x00, 0x05, 0x0d, 0x33, 0x32, 0x38, - 0x40, 0x01, 0x00, 0x00, 0x01, 0x32, 0x00, 0x00, - 0x01, 0x08, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, - 0x9c, 0xa9, 0x01, 0x09, 0x0d, 0x33, 0x32, 0x38, - 0x40, 0x4f, 0x60, 0x9c, 0xa9, 0x05, 0x06, 0x0d, - 0x33, 0x32, 0x38, 0x40, 0xa9, 0x00, 0x00, 0x00, - 0x05, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x07, 0x06, - 0x0d, 0x33, 0x32, 0x38, 0x40, 0xa9, 0x03, 0x05, - 0x0d, 0x33, 0x32, 0x38, 0x40, 0x09, 0x00, 0x03, - 0x02, 0x0d, 0x32, 0x01, 0x00, 0x00, 0x05, 0x0d, - 0x33, 0x32, 0x38, 0x40, 0x04, 0x02, 0x38, 0x40, - 0x00, 0x00, 0x00, 0x05, 0x0d, 0x33, 0x32, 0x38, - 0x40, 0x03, 0x00, 0x01, 0x03, 0x32, 0x38, 0x40, - 0x01, 0x01, 0x32, 0x58, 0x00, 0x03, 0x02, 0x38, - 0x40, 0x02, 0x00, 0x00, 0x02, 0x38, 0x40, 0x59, - 0x00, 0x00, 0x06, 0x0d, 0x33, 0x32, 0x38, 0x40, - 0xa9, 0x00, 0x02, 0x38, 0x40, 0x80, 0x12, 0x00, - 0x0f, 0x01, 0x32, 0x1f, 0x00, 0x25, 0x01, 0x32, - 0x08, 0x00, 0x00, 0x02, 0x32, 0x98, 0x2f, 0x00, - 0x27, 0x01, 0x32, 0x37, 0x00, 0x30, 0x01, 0x32, - 0x0e, 0x00, 0x0b, 0x01, 0x32, 0x32, 0x00, 0x00, - 0x01, 0x32, 0x57, 0x00, 0x18, 0x01, 0x32, 0x09, - 0x00, 0x04, 0x01, 0x32, 0x5f, 0x00, 0x1e, 0x01, - 0x32, 0xc0, 0x31, 0xef, 0x00, 0x00, 0x02, 0x1d, - 0x29, 0x80, 0x0f, 0x00, 0x07, 0x02, 0x32, 0x4a, - 0x80, 0xa7, 0x00, 0x02, 0x10, 0x20, 0x22, 0x2e, - 0x30, 0x45, 0x3f, 0x3e, 0x53, 0x54, 0x5f, 0x66, - 0x85, 0x47, 0x96, 0x9e, 0xa2, 0x02, 0x0f, 0x20, - 0x22, 0x2e, 0x30, 0x45, 0x3f, 0x3e, 0x53, 0x5f, - 0x66, 0x85, 0x47, 0x96, 0x9e, 0xa2, 0x01, 0x0b, - 0x20, 0x22, 0x2e, 0x30, 0x45, 0x3e, 0x53, 0x5f, - 0x47, 0x96, 0x9e, 0x00, 0x0c, 0x20, 0x22, 0x2e, - 0x30, 0x45, 0x3e, 0x53, 0x5f, 0x85, 0x47, 0x96, - 0x9e, 0x00, 0x0b, 0x20, 0x22, 0x2e, 0x30, 0x45, - 0x3e, 0x53, 0x5f, 0x47, 0x96, 0x9e, 0x80, 0x36, - 0x00, 0x00, 0x03, 0x0b, 0x20, 0xa2, 0x00, 0x00, - 0x00, 0x02, 0x20, 0x97, 0x39, 0x00, 0x00, 0x03, - 0x42, 0x4a, 0x63, 0x80, 0x1f, 0x00, 0x00, 0x02, - 0x10, 0x3d, 0xc0, 0x12, 0xed, 0x00, 0x01, 0x02, - 0x04, 0x69, 0x80, 0x31, 0x00, 0x00, 0x02, 0x04, - 0x9a, 0x09, 0x00, 0x00, 0x02, 0x04, 0x9a, 0x46, - 0x00, 0x01, 0x05, 0x0d, 0x33, 0x32, 0x38, 0x40, - 0x80, 0x99, 0x00, 0x04, 0x06, 0x0d, 0x33, 0x32, - 0x38, 0x40, 0xa9, 0x09, 0x00, 0x00, 0x02, 0x38, - 0x40, 0x2c, 0x00, 0x01, 0x02, 0x38, 0x40, 0x80, - 0xdf, 0x00, 0x01, 0x03, 0x1e, 0x1c, 0x4e, 0x00, - 0x02, 0x1c, 0x4e, 0x03, 0x00, 0x2c, 0x03, 0x1c, - 0x4d, 0x4e, 0x02, 0x00, 0x08, 0x02, 0x1c, 0x4e, - 0x81, 0x1f, 0x00, 0x1b, 0x02, 0x04, 0x1a, 0x87, - 0x75, 0x00, 0x00, 0x02, 0x56, 0x77, 0x87, 0x8d, - 0x00, 0x00, 0x02, 0x2c, 0x97, 0x00, 0x00, 0x00, - 0x02, 0x2c, 0x97, 0x36, 0x00, 0x01, 0x02, 0x2c, - 0x97, 0x8c, 0x12, 0x00, 0x01, 0x02, 0x2c, 0x97, - 0x00, 0x00, 0x00, 0x02, 0x2c, 0x97, 0xc0, 0x5c, - 0x4b, 0x00, 0x03, 0x01, 0x23, 0x96, 0x3b, 0x00, - 0x11, 0x01, 0x32, 0x9e, 0x5d, 0x00, 0x01, 0x01, - 0x32, 0xce, 0xcd, 0x2d, 0x00, -}; - -static const uint8_t unicode_prop_Hyphen_table[28] = { - 0xac, 0x80, 0xfe, 0x80, 0x44, 0xdb, 0x80, 0x52, - 0x7a, 0x80, 0x48, 0x08, 0x81, 0x4e, 0x04, 0x80, - 0x42, 0xe2, 0x80, 0x60, 0xcd, 0x66, 0x80, 0x40, - 0xa8, 0x80, 0xd6, 0x80, -}; - -static const uint8_t unicode_prop_Other_Math_table[200] = { - 0xdd, 0x80, 0x43, 0x70, 0x11, 0x80, 0x99, 0x09, - 0x81, 0x5c, 0x1f, 0x80, 0x9a, 0x82, 0x8a, 0x80, - 0x9f, 0x83, 0x97, 0x81, 0x8d, 0x81, 0xc0, 0x8c, - 0x18, 0x11, 0x1c, 0x91, 0x03, 0x01, 0x89, 0x00, - 0x14, 0x28, 0x11, 0x09, 0x02, 0x05, 0x13, 0x24, - 0xca, 0x21, 0x18, 0x08, 0x08, 0x00, 0x21, 0x0b, - 0x0b, 0x91, 0x09, 0x00, 0x06, 0x00, 0x29, 0x41, - 0x21, 0x83, 0x40, 0xa7, 0x08, 0x80, 0x97, 0x80, - 0x90, 0x80, 0x41, 0xbc, 0x81, 0x8b, 0x88, 0x24, - 0x21, 0x09, 0x14, 0x8d, 0x00, 0x01, 0x85, 0x97, - 0x81, 0xb8, 0x00, 0x80, 0x9c, 0x83, 0x88, 0x81, - 0x41, 0x55, 0x81, 0x9e, 0x89, 0x41, 0x92, 0x95, - 0xbe, 0x83, 0x9f, 0x81, 0x60, 0xd4, 0x62, 0x00, - 0x03, 0x80, 0x40, 0xd2, 0x00, 0x80, 0x60, 0xd4, - 0xc0, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, - 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, - 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, - 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, - 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, - 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x07, 0x81, - 0xb1, 0x55, 0xff, 0x18, 0x9a, 0x01, 0x00, 0x08, - 0x80, 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, - 0x00, 0x02, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, - 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, -}; - -static const uint8_t unicode_prop_Other_Alphabetic_table[443] = { - 0x43, 0x44, 0x80, 0x9c, 0x8c, 0x42, 0x3f, 0x8d, - 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, 0x8c, - 0x06, 0x8f, 0x80, 0xe4, 0x33, 0x19, 0x0b, 0x80, - 0xa2, 0x80, 0x9d, 0x8f, 0xe5, 0x8a, 0xe4, 0x0a, - 0x88, 0x02, 0x03, 0xe9, 0x80, 0xbb, 0x8b, 0x16, - 0x85, 0x93, 0xb5, 0x09, 0x8e, 0x01, 0x22, 0x89, - 0x81, 0x9c, 0x82, 0xb9, 0x31, 0x09, 0x81, 0x89, - 0x80, 0x89, 0x81, 0x9c, 0x82, 0xb9, 0x23, 0x09, - 0x0b, 0x80, 0x9d, 0x0a, 0x80, 0x8a, 0x82, 0xb9, - 0x38, 0x10, 0x81, 0x94, 0x81, 0x95, 0x13, 0x82, - 0xb9, 0x31, 0x09, 0x81, 0x88, 0x81, 0x89, 0x81, - 0x9d, 0x80, 0xba, 0x22, 0x10, 0x82, 0x89, 0x80, - 0xa7, 0x84, 0xb8, 0x30, 0x10, 0x17, 0x81, 0x8a, - 0x81, 0x9c, 0x82, 0xb9, 0x30, 0x10, 0x17, 0x81, - 0x8a, 0x81, 0x8e, 0x80, 0x8b, 0x83, 0xb9, 0x30, - 0x10, 0x82, 0x89, 0x80, 0x89, 0x81, 0x9c, 0x82, - 0xca, 0x28, 0x00, 0x87, 0x91, 0x81, 0xbc, 0x01, - 0x86, 0x91, 0x80, 0xe2, 0x01, 0x28, 0x81, 0x8f, - 0x80, 0x40, 0xa2, 0x92, 0x88, 0x8a, 0x80, 0xa3, - 0xed, 0x8b, 0x00, 0x0b, 0x96, 0x1b, 0x10, 0x11, - 0x32, 0x83, 0x8c, 0x8b, 0x00, 0x89, 0x83, 0x46, - 0x73, 0x81, 0x9d, 0x81, 0x9d, 0x81, 0x9d, 0x81, - 0xc1, 0x92, 0x40, 0xbb, 0x81, 0xa1, 0x80, 0xf5, - 0x8b, 0x83, 0x88, 0x40, 0xdd, 0x84, 0xb8, 0x89, - 0x81, 0x93, 0xc9, 0x81, 0x8a, 0x82, 0xb0, 0x84, - 0xaf, 0x8e, 0xbb, 0x82, 0x9d, 0x88, 0x09, 0xb8, - 0x8a, 0xb1, 0x92, 0x41, 0x9b, 0xa1, 0x46, 0xc0, - 0xb3, 0x48, 0xf5, 0x9f, 0x60, 0x78, 0x73, 0x87, - 0xa1, 0x81, 0x41, 0x61, 0x07, 0x80, 0x96, 0x84, - 0xd7, 0x81, 0xb1, 0x8f, 0x00, 0xb8, 0x80, 0xa5, - 0x84, 0x9b, 0x8b, 0xac, 0x83, 0xaf, 0x8b, 0xa4, - 0x80, 0xc2, 0x8d, 0x8b, 0x07, 0x81, 0xac, 0x82, - 0xb1, 0x00, 0x11, 0x0c, 0x80, 0xab, 0x24, 0x80, - 0x40, 0xec, 0x87, 0x60, 0x4f, 0x32, 0x80, 0x48, - 0x56, 0x84, 0x46, 0x85, 0x10, 0x0c, 0x83, 0x43, - 0x13, 0x83, 0xc0, 0x80, 0x41, 0x40, 0x81, 0xce, - 0x80, 0x41, 0x02, 0x82, 0xb4, 0x8d, 0xac, 0x81, - 0x8a, 0x82, 0xac, 0x88, 0x88, 0x80, 0xbc, 0x82, - 0xa3, 0x8b, 0x91, 0x81, 0xb8, 0x82, 0xaf, 0x8c, - 0x8d, 0x81, 0xdb, 0x88, 0x08, 0x28, 0x08, 0x40, - 0x9c, 0x89, 0x96, 0x83, 0xb9, 0x31, 0x09, 0x81, - 0x89, 0x80, 0x89, 0x81, 0xd3, 0x88, 0x00, 0x08, - 0x03, 0x01, 0xe6, 0x8c, 0x02, 0xe9, 0x91, 0x40, - 0xec, 0x31, 0x86, 0x9c, 0x81, 0xd1, 0x8e, 0x00, - 0xe9, 0x8a, 0xe6, 0x8d, 0x41, 0x00, 0x8c, 0x40, - 0xf6, 0x28, 0x09, 0x0a, 0x00, 0x80, 0x40, 0x8d, - 0x31, 0x2b, 0x80, 0x9b, 0x89, 0xa9, 0x20, 0x83, - 0x91, 0x8a, 0xad, 0x8d, 0x41, 0x96, 0x38, 0x86, - 0xd2, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, - 0x10, 0x02, 0x80, 0xc1, 0x20, 0x08, 0x83, 0x41, - 0x5b, 0x83, 0x88, 0x08, 0x80, 0xaf, 0x32, 0x82, - 0x60, 0x41, 0xdc, 0x90, 0x4e, 0x1f, 0x00, 0xb6, - 0x33, 0xdc, 0x81, 0x60, 0x4c, 0xab, 0x80, 0x60, - 0x23, 0x60, 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, - 0x80, 0x48, 0xb6, 0x80, 0x47, 0xe7, 0x99, 0x85, - 0x99, 0x85, 0x99, -}; - -static const uint8_t unicode_prop_Other_Lowercase_table[69] = { - 0x40, 0xa9, 0x80, 0x8e, 0x80, 0x41, 0xf4, 0x88, - 0x31, 0x9d, 0x84, 0xdf, 0x80, 0xb3, 0x80, 0x4d, - 0x80, 0x80, 0x4c, 0x2e, 0xbe, 0x8c, 0x80, 0xa1, - 0xa4, 0x42, 0xb0, 0x80, 0x8c, 0x80, 0x8f, 0x8c, - 0x40, 0xd2, 0x8f, 0x43, 0x4f, 0x99, 0x47, 0x91, - 0x81, 0x60, 0x7a, 0x1d, 0x81, 0x40, 0xd1, 0x80, - 0x40, 0x80, 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, - 0x80, 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, - 0x88, 0x60, 0xd8, 0x74, 0xbd, -}; - -static const uint8_t unicode_prop_Other_Uppercase_table[15] = { - 0x60, 0x21, 0x5f, 0x8f, 0x43, 0x45, 0x99, 0x61, - 0xcc, 0x5f, 0x99, 0x85, 0x99, 0x85, 0x99, -}; - -static const uint8_t unicode_prop_Other_Grapheme_Extend_table[112] = { - 0x49, 0xbd, 0x80, 0x97, 0x80, 0x41, 0x65, 0x80, - 0x97, 0x80, 0xe5, 0x80, 0x97, 0x80, 0x40, 0xe7, - 0x00, 0x03, 0x08, 0x81, 0x88, 0x81, 0xe6, 0x80, - 0x97, 0x80, 0xf6, 0x80, 0x8e, 0x80, 0x49, 0x34, - 0x80, 0x9d, 0x80, 0x43, 0xff, 0x04, 0x00, 0x04, - 0x81, 0xe4, 0x80, 0xc6, 0x81, 0x44, 0x17, 0x80, - 0x50, 0x20, 0x81, 0x60, 0x79, 0x22, 0x80, 0xeb, - 0x80, 0x60, 0x55, 0xdc, 0x81, 0x52, 0x1f, 0x80, - 0xf3, 0x80, 0x41, 0x07, 0x80, 0x8d, 0x80, 0x88, - 0x80, 0xdf, 0x80, 0x88, 0x01, 0x00, 0x14, 0x80, - 0x40, 0xdf, 0x80, 0x8b, 0x80, 0x40, 0xf0, 0x80, - 0x41, 0x05, 0x80, 0x42, 0x78, 0x80, 0x8b, 0x80, - 0x46, 0x02, 0x80, 0x60, 0x50, 0xad, 0x81, 0x60, - 0x61, 0x72, 0x0d, 0x85, 0x6c, 0x2e, 0xac, 0xdf, -}; - -static const uint8_t unicode_prop_Other_Default_Ignorable_Code_Point_table[32] = { - 0x43, 0x4e, 0x80, 0x4e, 0x0e, 0x81, 0x46, 0x52, - 0x81, 0x48, 0xae, 0x80, 0x50, 0xfd, 0x80, 0x60, - 0xce, 0x3a, 0x80, 0xce, 0x88, 0x6d, 0x00, 0x06, - 0x00, 0x9d, 0xdf, 0xff, 0x40, 0xef, 0x4e, 0x0f, -}; - -static const uint8_t unicode_prop_Other_ID_Start_table[11] = { - 0x58, 0x84, 0x81, 0x48, 0x90, 0x80, 0x94, 0x80, - 0x4f, 0x6b, 0x81, -}; - -static const uint8_t unicode_prop_Other_ID_Continue_table[22] = { - 0x40, 0xb6, 0x80, 0x42, 0xce, 0x80, 0x4f, 0xe0, - 0x88, 0x46, 0x67, 0x80, 0x46, 0x30, 0x81, 0x50, - 0xec, 0x80, 0x60, 0xce, 0x68, 0x80, -}; - -static const uint8_t unicode_prop_Prepended_Concatenation_Mark_table[19] = { - 0x45, 0xff, 0x85, 0x40, 0xd6, 0x80, 0xb0, 0x80, - 0x41, 0x7f, 0x81, 0xcf, 0x80, 0x61, 0x07, 0xd9, - 0x80, 0x8e, 0x80, -}; - -static const uint8_t unicode_prop_XID_Start1_table[31] = { - 0x43, 0x79, 0x80, 0x4a, 0xb7, 0x80, 0xfe, 0x80, - 0x60, 0x21, 0xe6, 0x81, 0x60, 0xcb, 0xc0, 0x85, - 0x41, 0x95, 0x81, 0xf3, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x80, 0x41, 0x1e, 0x81, -}; - -static const uint8_t unicode_prop_XID_Continue1_table[23] = { - 0x43, 0x79, 0x80, 0x60, 0x2d, 0x1f, 0x81, 0x60, - 0xcb, 0xc0, 0x85, 0x41, 0x95, 0x81, 0xf3, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -}; - -static const uint8_t unicode_prop_Changes_When_Titlecased1_table[22] = { - 0x41, 0xc3, 0x08, 0x08, 0x81, 0xa4, 0x81, 0x4e, - 0xdc, 0xaa, 0x0a, 0x4e, 0x87, 0x3f, 0x3f, 0x87, - 0x8b, 0x80, 0x8e, 0x80, 0xae, 0x80, -}; - -static const uint8_t unicode_prop_Changes_When_Casefolded1_table[29] = { - 0x41, 0xef, 0x80, 0x41, 0x9e, 0x80, 0x9e, 0x80, - 0x5a, 0xe4, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, - 0x80, 0xde, 0x06, 0x06, 0x80, 0x8a, 0x09, 0x81, - 0x89, 0x10, 0x81, 0x8d, 0x80, -}; - -static const uint8_t unicode_prop_Changes_When_NFKC_Casefolded1_table[450] = { - 0x40, 0x9f, 0x06, 0x00, 0x01, 0x00, 0x01, 0x12, - 0x10, 0x82, 0xf3, 0x80, 0x8b, 0x80, 0x40, 0x84, - 0x01, 0x01, 0x80, 0xa2, 0x01, 0x80, 0x40, 0xbb, - 0x88, 0x9e, 0x29, 0x84, 0xda, 0x08, 0x81, 0x89, - 0x80, 0xa3, 0x04, 0x02, 0x04, 0x08, 0x07, 0x80, - 0x9e, 0x80, 0xa0, 0x82, 0x9c, 0x80, 0x42, 0x28, - 0x80, 0xd7, 0x83, 0x42, 0xde, 0x87, 0xfb, 0x08, - 0x80, 0xd2, 0x01, 0x80, 0xa1, 0x11, 0x80, 0x40, - 0xfc, 0x81, 0x42, 0xd4, 0x80, 0xfe, 0x80, 0xa7, - 0x81, 0xad, 0x80, 0xb5, 0x80, 0x88, 0x03, 0x03, - 0x03, 0x80, 0x8b, 0x80, 0x88, 0x00, 0x26, 0x80, - 0x90, 0x80, 0x88, 0x03, 0x03, 0x03, 0x80, 0x8b, - 0x80, 0x41, 0x41, 0x80, 0xe1, 0x81, 0x46, 0x52, - 0x81, 0xd4, 0x84, 0x45, 0x1b, 0x10, 0x8a, 0x80, - 0x91, 0x80, 0x9b, 0x8c, 0x80, 0xa1, 0xa4, 0x40, - 0xd5, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, 0x80, - 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, - 0xb7, 0x05, 0x00, 0x13, 0x05, 0x11, 0x02, 0x0c, - 0x11, 0x00, 0x00, 0x0c, 0x15, 0x05, 0x08, 0x8f, - 0x00, 0x20, 0x8b, 0x12, 0x2a, 0x08, 0x0b, 0x00, - 0x07, 0x82, 0x8c, 0x06, 0x92, 0x81, 0x9a, 0x80, - 0x8c, 0x8a, 0x80, 0xd6, 0x18, 0x10, 0x8a, 0x01, - 0x0c, 0x0a, 0x00, 0x10, 0x11, 0x02, 0x06, 0x05, - 0x1c, 0x85, 0x8f, 0x8f, 0x8f, 0x88, 0x80, 0x40, - 0xa1, 0x08, 0x81, 0x40, 0xf7, 0x81, 0x41, 0x34, - 0xd5, 0x99, 0x9a, 0x45, 0x20, 0x80, 0xe6, 0x82, - 0xe4, 0x80, 0x41, 0x9e, 0x81, 0x40, 0xf0, 0x80, - 0x41, 0x2e, 0x80, 0xd2, 0x80, 0x8b, 0x40, 0xd5, - 0xa9, 0x80, 0xb4, 0x00, 0x82, 0xdf, 0x09, 0x80, - 0xde, 0x80, 0xb0, 0xdd, 0x82, 0x8d, 0xdf, 0x9e, - 0x80, 0xa7, 0x87, 0xae, 0x80, 0x41, 0x7f, 0x60, - 0x72, 0x9b, 0x81, 0x40, 0xd1, 0x80, 0x40, 0x80, - 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, 0x80, 0x60, - 0x4d, 0x95, 0x41, 0x0d, 0x08, 0x00, 0x81, 0x89, - 0x00, 0x00, 0x09, 0x82, 0xc3, 0x81, 0xe9, 0xc2, - 0x00, 0x97, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, - 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, - 0x8c, 0x82, 0x99, 0x95, 0x94, 0x81, 0x8b, 0x80, - 0x92, 0x03, 0x1a, 0x00, 0x80, 0x40, 0x86, 0x08, - 0x80, 0x9f, 0x99, 0x40, 0x83, 0x15, 0x0d, 0x0d, - 0x0a, 0x16, 0x06, 0x80, 0x88, 0x47, 0x87, 0x20, - 0xa9, 0x80, 0x88, 0x60, 0xb4, 0xe4, 0x83, 0x50, - 0x31, 0xa3, 0x44, 0x63, 0x86, 0x8d, 0x87, 0xbf, - 0x85, 0x42, 0x3e, 0xd4, 0x80, 0xc6, 0x01, 0x08, - 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, - 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, - 0x16, 0x80, 0x41, 0x53, 0x81, 0x41, 0x23, 0x81, - 0xb1, 0x48, 0x2f, 0xbd, 0x4d, 0x91, 0x18, 0x9a, - 0x01, 0x00, 0x08, 0x80, 0x89, 0x03, 0x00, 0x00, - 0x28, 0x18, 0x00, 0x00, 0x02, 0x01, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x06, - 0x03, 0x03, 0x00, 0x80, 0x89, 0x80, 0x90, 0x22, - 0x04, 0x80, 0x90, 0x42, 0x43, 0x8a, 0x84, 0x9e, - 0x80, 0x9f, 0x99, 0x82, 0xa2, 0x80, 0xee, 0x82, - 0x8c, 0xab, 0x83, 0x88, 0x31, 0x49, 0x9d, 0x89, - 0x60, 0xfc, 0x05, 0x42, 0x1d, 0x6b, 0x05, 0xe1, - 0x4f, 0xff, -}; - -static const uint8_t unicode_prop_ASCII_Hex_Digit_table[5] = { - 0xaf, 0x89, 0x35, 0x99, 0x85, -}; - -static const uint8_t unicode_prop_Bidi_Control_table[10] = { - 0x46, 0x1b, 0x80, 0x59, 0xf0, 0x81, 0x99, 0x84, - 0xb6, 0x83, -}; - -static const uint8_t unicode_prop_Dash_table[58] = { - 0xac, 0x80, 0x45, 0x5b, 0x80, 0xb2, 0x80, 0x4e, - 0x40, 0x80, 0x44, 0x04, 0x80, 0x48, 0x08, 0x85, - 0xbc, 0x80, 0xa6, 0x80, 0x8e, 0x80, 0x41, 0x85, - 0x80, 0x4c, 0x03, 0x01, 0x80, 0x9e, 0x0b, 0x80, - 0x9b, 0x80, 0x41, 0xbd, 0x80, 0x92, 0x80, 0xee, - 0x80, 0x60, 0xcd, 0x8f, 0x81, 0xa4, 0x80, 0x89, - 0x80, 0x40, 0xa8, 0x80, 0x4e, 0x5f, 0x80, 0x41, - 0x3d, 0x80, -}; - -static const uint8_t unicode_prop_Deprecated_table[23] = { - 0x41, 0x48, 0x80, 0x45, 0x28, 0x80, 0x49, 0x02, - 0x00, 0x80, 0x48, 0x28, 0x81, 0x48, 0xc4, 0x85, - 0x42, 0xb8, 0x81, 0x6d, 0xdc, 0xd5, 0x80, -}; - -static const uint8_t unicode_prop_Diacritic_table[438] = { - 0xdd, 0x00, 0x80, 0xc6, 0x05, 0x03, 0x01, 0x81, - 0x41, 0xf6, 0x40, 0x9e, 0x07, 0x25, 0x90, 0x0b, - 0x80, 0x88, 0x81, 0x40, 0xfc, 0x84, 0x40, 0xd0, - 0x80, 0xb6, 0x90, 0x80, 0x9a, 0x00, 0x01, 0x00, - 0x40, 0x85, 0x3b, 0x81, 0x40, 0x85, 0x0b, 0x0a, - 0x82, 0xc2, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0xa1, - 0x81, 0xfd, 0x87, 0xa8, 0x89, 0x8f, 0x9b, 0xbc, - 0x80, 0x8f, 0x02, 0x83, 0x9b, 0x80, 0xc9, 0x80, - 0x8f, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, 0x80, - 0x8f, 0x80, 0xae, 0x82, 0xbb, 0x80, 0x8f, 0x06, - 0x80, 0xf6, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, - 0x80, 0x8f, 0x80, 0xec, 0x81, 0x8f, 0x80, 0xfb, - 0x80, 0xee, 0x80, 0x8b, 0x28, 0x80, 0xea, 0x80, - 0x8c, 0x84, 0xca, 0x81, 0x9a, 0x00, 0x00, 0x03, - 0x81, 0xc1, 0x10, 0x81, 0xbd, 0x80, 0xef, 0x00, - 0x81, 0xa7, 0x0b, 0x84, 0x98, 0x30, 0x80, 0x89, - 0x81, 0x42, 0xc0, 0x82, 0x43, 0xb3, 0x81, 0x9d, - 0x80, 0x40, 0x93, 0x8a, 0x88, 0x80, 0x41, 0x5a, - 0x82, 0x41, 0x23, 0x80, 0x93, 0x39, 0x80, 0xaf, - 0x8e, 0x81, 0x8a, 0xe7, 0x80, 0x8e, 0x80, 0xa5, - 0x88, 0xb5, 0x81, 0xb9, 0x80, 0x8a, 0x81, 0xc1, - 0x81, 0xbf, 0x85, 0xd1, 0x98, 0x18, 0x28, 0x0a, - 0xb1, 0xbe, 0xd8, 0x8b, 0xa4, 0x8a, 0x41, 0xbc, - 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, 0x82, - 0x8c, 0x81, 0x4c, 0xef, 0x82, 0x41, 0x3c, 0x80, - 0x41, 0xf9, 0x85, 0xe8, 0x83, 0xde, 0x80, 0x60, - 0x75, 0x71, 0x80, 0x8b, 0x08, 0x80, 0x9b, 0x81, - 0xd1, 0x81, 0x8d, 0xa1, 0xe5, 0x82, 0xec, 0x81, - 0x8b, 0x80, 0xa4, 0x80, 0x40, 0x96, 0x80, 0x9a, - 0x91, 0xb8, 0x83, 0xa3, 0x80, 0xde, 0x80, 0x8b, - 0x80, 0xa3, 0x80, 0x40, 0x94, 0x82, 0xc0, 0x83, - 0xb2, 0x80, 0xe3, 0x84, 0x88, 0x82, 0xff, 0x81, - 0x60, 0x4f, 0x2f, 0x80, 0x43, 0x00, 0x8f, 0x41, - 0x0d, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, - 0x80, 0x42, 0xfb, 0x80, 0x44, 0x9e, 0x28, 0xa9, - 0x80, 0x88, 0x42, 0x7c, 0x13, 0x80, 0x40, 0xa4, - 0x81, 0x42, 0x3a, 0x85, 0xa5, 0x80, 0x99, 0x84, - 0x41, 0x8e, 0x82, 0xc5, 0x8a, 0xb0, 0x83, 0x40, - 0xbf, 0x80, 0xa8, 0x80, 0xc7, 0x81, 0xf7, 0x81, - 0xbd, 0x80, 0xcb, 0x80, 0x88, 0x82, 0xe7, 0x81, - 0x40, 0xb1, 0x81, 0xcf, 0x81, 0x8f, 0x80, 0x97, - 0x32, 0x84, 0xd8, 0x10, 0x81, 0x8c, 0x81, 0xde, - 0x02, 0x80, 0xfa, 0x81, 0x40, 0xfa, 0x81, 0xfd, - 0x80, 0xf5, 0x81, 0xf2, 0x80, 0x41, 0x0c, 0x81, - 0x41, 0x01, 0x0b, 0x80, 0x40, 0x9b, 0x80, 0xd2, - 0x80, 0x91, 0x80, 0xd0, 0x80, 0x41, 0xa4, 0x80, - 0x41, 0x01, 0x00, 0x81, 0xd0, 0x80, 0x41, 0xa8, - 0x81, 0x96, 0x80, 0x54, 0xeb, 0x8e, 0x60, 0x2c, - 0xd8, 0x80, 0x49, 0xbf, 0x84, 0xba, 0x86, 0x42, - 0x33, 0x81, 0x42, 0x21, 0x90, 0xcf, 0x81, 0x60, - 0x3f, 0xfd, 0x18, 0x30, 0x81, 0x5f, 0x00, 0xad, - 0x81, 0x96, 0x42, 0x1f, 0x12, 0x2f, 0x39, 0x86, - 0x9d, 0x83, 0x4e, 0x81, 0xbd, 0x40, 0xc1, 0x86, - 0x41, 0x76, 0x80, 0xbc, 0x83, 0x42, 0xfd, 0x81, - 0x42, 0xdf, 0x86, 0xec, 0x10, 0x82, -}; - -static const uint8_t unicode_prop_Extender_table[111] = { - 0x40, 0xb6, 0x80, 0x42, 0x17, 0x81, 0x43, 0x6d, - 0x80, 0x41, 0xb8, 0x80, 0x42, 0x75, 0x80, 0x40, - 0x88, 0x80, 0xd8, 0x80, 0x42, 0xef, 0x80, 0xfe, - 0x80, 0x49, 0x42, 0x80, 0xb7, 0x80, 0x42, 0x62, - 0x80, 0x41, 0x8d, 0x80, 0xc3, 0x80, 0x53, 0x88, - 0x80, 0xaa, 0x84, 0xe6, 0x81, 0xdc, 0x82, 0x60, - 0x6f, 0x15, 0x80, 0x45, 0xf5, 0x80, 0x43, 0xc1, - 0x80, 0x95, 0x80, 0x40, 0x88, 0x80, 0xeb, 0x80, - 0x94, 0x81, 0x60, 0x54, 0x7a, 0x80, 0x48, 0x0f, - 0x81, 0x45, 0xca, 0x80, 0x9a, 0x03, 0x80, 0x44, - 0xc6, 0x80, 0x41, 0x24, 0x80, 0xf3, 0x81, 0x41, - 0xf1, 0x82, 0x44, 0xce, 0x80, 0x60, 0x50, 0xa8, - 0x81, 0x44, 0x9b, 0x08, 0x80, 0x60, 0x71, 0x57, - 0x81, 0x44, 0xb0, 0x80, 0x43, 0x53, 0x82, -}; - -static const uint8_t unicode_prop_Hex_Digit_table[12] = { - 0xaf, 0x89, 0x35, 0x99, 0x85, 0x60, 0xfe, 0xa8, - 0x89, 0x35, 0x99, 0x85, -}; - -static const uint8_t unicode_prop_IDS_Unary_Operator_table[4] = { - 0x60, 0x2f, 0xfd, 0x81, -}; - -static const uint8_t unicode_prop_IDS_Binary_Operator_table[8] = { - 0x60, 0x2f, 0xef, 0x09, 0x89, 0x41, 0xf0, 0x80, -}; - -static const uint8_t unicode_prop_IDS_Trinary_Operator_table[4] = { - 0x60, 0x2f, 0xf1, 0x81, -}; - -static const uint8_t unicode_prop_Ideographic_table[72] = { - 0x60, 0x30, 0x05, 0x81, 0x98, 0x88, 0x8d, 0x82, - 0x43, 0xc4, 0x59, 0xbf, 0xbf, 0x60, 0x51, 0xff, - 0x60, 0x58, 0xff, 0x41, 0x6d, 0x81, 0xe9, 0x60, - 0x75, 0x09, 0x80, 0x9a, 0x57, 0xf7, 0x87, 0x44, - 0xd5, 0xa8, 0x89, 0x60, 0x24, 0x66, 0x41, 0x8b, - 0x60, 0x4d, 0x03, 0x60, 0xa6, 0xdf, 0x9f, 0x50, - 0x39, 0x85, 0x40, 0xdd, 0x81, 0x56, 0x81, 0x8d, - 0x5d, 0x30, 0x8e, 0x42, 0x6d, 0x49, 0xa1, 0x42, - 0x1d, 0x45, 0xe1, 0x53, 0x4a, 0x84, 0x50, 0x5f, -}; - -static const uint8_t unicode_prop_Join_Control_table[4] = { - 0x60, 0x20, 0x0b, 0x81, -}; - -static const uint8_t unicode_prop_Logical_Order_Exception_table[15] = { - 0x4e, 0x3f, 0x84, 0xfa, 0x84, 0x4a, 0xef, 0x11, - 0x80, 0x60, 0x90, 0xf9, 0x09, 0x00, 0x81, -}; - -static const uint8_t unicode_prop_Modifier_Combining_Mark_table[16] = { - 0x46, 0x53, 0x09, 0x80, 0x40, 0x82, 0x05, 0x02, - 0x81, 0x41, 0xe0, 0x08, 0x12, 0x80, 0x9e, 0x80, -}; - -static const uint8_t unicode_prop_Noncharacter_Code_Point_table[71] = { - 0x60, 0xfd, 0xcf, 0x9f, 0x42, 0x0d, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, -}; - -static const uint8_t unicode_prop_Pattern_Syntax_table[58] = { - 0xa0, 0x8e, 0x89, 0x86, 0x99, 0x18, 0x80, 0x99, - 0x83, 0xa1, 0x30, 0x00, 0x08, 0x00, 0x0b, 0x03, - 0x02, 0x80, 0x96, 0x80, 0x9e, 0x80, 0x5f, 0x17, - 0x97, 0x87, 0x8e, 0x81, 0x92, 0x80, 0x89, 0x41, - 0x30, 0x42, 0xcf, 0x40, 0x9f, 0x42, 0x75, 0x9d, - 0x44, 0x6b, 0x41, 0xff, 0xff, 0x41, 0x80, 0x13, - 0x98, 0x8e, 0x80, 0x60, 0xcd, 0x0c, 0x81, 0x41, - 0x04, 0x81, -}; - -static const uint8_t unicode_prop_Pattern_White_Space_table[11] = { - 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x5f, 0x87, - 0x81, 0x97, 0x81, -}; - -static const uint8_t unicode_prop_Quotation_Mark_table[31] = { - 0xa1, 0x03, 0x80, 0x40, 0x82, 0x80, 0x8e, 0x80, - 0x5f, 0x5b, 0x87, 0x98, 0x81, 0x4e, 0x06, 0x80, - 0x41, 0xc8, 0x83, 0x8c, 0x82, 0x60, 0xce, 0x20, - 0x83, 0x40, 0xbc, 0x03, 0x80, 0xd9, 0x81, -}; - -static const uint8_t unicode_prop_Radical_table[9] = { - 0x60, 0x2e, 0x7f, 0x99, 0x80, 0xd8, 0x8b, 0x40, - 0xd5, -}; - -static const uint8_t unicode_prop_Regional_Indicator_table[4] = { - 0x61, 0xf1, 0xe5, 0x99, -}; - -static const uint8_t unicode_prop_Sentence_Terminal_table[213] = { - 0xa0, 0x80, 0x8b, 0x80, 0x8f, 0x80, 0x45, 0x48, - 0x80, 0x40, 0x92, 0x82, 0x40, 0xb3, 0x80, 0xaa, - 0x82, 0x40, 0xf5, 0x80, 0xbc, 0x00, 0x02, 0x81, - 0x41, 0x24, 0x81, 0x46, 0xe3, 0x81, 0x43, 0x15, - 0x03, 0x81, 0x43, 0x04, 0x80, 0x40, 0xc5, 0x81, - 0x40, 0x9c, 0x81, 0xac, 0x04, 0x80, 0x41, 0x39, - 0x81, 0x41, 0x61, 0x83, 0x40, 0xa1, 0x81, 0x89, - 0x09, 0x81, 0x9c, 0x82, 0x40, 0xba, 0x81, 0xc0, - 0x81, 0x43, 0xa3, 0x80, 0x96, 0x81, 0x88, 0x82, - 0x4c, 0xae, 0x82, 0x41, 0x31, 0x80, 0x8c, 0x80, - 0x95, 0x81, 0x41, 0xac, 0x80, 0x60, 0x74, 0xfb, - 0x80, 0x41, 0x0d, 0x81, 0x40, 0xe2, 0x02, 0x80, - 0x41, 0x7d, 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, - 0x97, 0x81, 0x40, 0x92, 0x82, 0x40, 0x8f, 0x81, - 0x40, 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, - 0xba, 0x02, 0x81, 0x40, 0xa8, 0x80, 0x8b, 0x80, - 0x8f, 0x80, 0xc0, 0x80, 0x4a, 0xf3, 0x81, 0x44, - 0xfc, 0x84, 0xab, 0x83, 0x40, 0xbc, 0x81, 0xf4, - 0x83, 0xfe, 0x82, 0x40, 0x80, 0x0d, 0x80, 0x8f, - 0x81, 0xd7, 0x08, 0x81, 0xeb, 0x80, 0x41, 0x29, - 0x81, 0xf4, 0x81, 0x41, 0x74, 0x0c, 0x8e, 0xe8, - 0x81, 0x40, 0xf8, 0x82, 0x42, 0x04, 0x00, 0x80, - 0x40, 0xfa, 0x81, 0xd6, 0x81, 0x41, 0xa3, 0x81, - 0x42, 0xb3, 0x81, 0xc9, 0x81, 0x60, 0x4b, 0x28, - 0x81, 0x40, 0x84, 0x80, 0xc0, 0x81, 0x8a, 0x80, - 0x42, 0x28, 0x81, 0x41, 0x27, 0x80, 0x60, 0x4e, - 0x05, 0x80, 0x5d, 0xe7, 0x80, -}; - -static const uint8_t unicode_prop_Soft_Dotted_table[79] = { - 0xe8, 0x81, 0x40, 0xc3, 0x80, 0x41, 0x18, 0x80, - 0x9d, 0x80, 0xb3, 0x80, 0x93, 0x80, 0x41, 0x3f, - 0x80, 0xe1, 0x00, 0x80, 0x59, 0x08, 0x80, 0xb2, - 0x80, 0x8c, 0x02, 0x80, 0x40, 0x83, 0x80, 0x40, - 0x9c, 0x80, 0x41, 0xa4, 0x80, 0x40, 0xd5, 0x81, - 0x4b, 0x31, 0x80, 0x61, 0xa7, 0xa4, 0x81, 0xb1, - 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, - 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, - 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0x48, - 0x85, 0x80, 0x41, 0x30, 0x81, 0x99, 0x80, -}; - -static const uint8_t unicode_prop_Terminal_Punctuation_table[264] = { - 0xa0, 0x80, 0x89, 0x00, 0x80, 0x8a, 0x0a, 0x80, - 0x43, 0x3d, 0x07, 0x80, 0x42, 0x00, 0x80, 0xb8, - 0x80, 0xc7, 0x80, 0x8d, 0x00, 0x82, 0x40, 0xb3, - 0x80, 0xaa, 0x8a, 0x00, 0x40, 0xea, 0x81, 0xb5, - 0x28, 0x87, 0x9e, 0x80, 0x41, 0x04, 0x81, 0x44, - 0xf3, 0x81, 0x40, 0xab, 0x03, 0x85, 0x41, 0x36, - 0x81, 0x43, 0x14, 0x87, 0x43, 0x04, 0x80, 0xfb, - 0x82, 0xc6, 0x81, 0x40, 0x9c, 0x12, 0x80, 0xa6, - 0x19, 0x81, 0x41, 0x39, 0x81, 0x41, 0x61, 0x83, - 0x40, 0xa1, 0x81, 0x89, 0x08, 0x82, 0x9c, 0x82, - 0x40, 0xba, 0x84, 0xbd, 0x81, 0x43, 0xa3, 0x80, - 0x96, 0x81, 0x88, 0x82, 0x4c, 0xae, 0x82, 0x41, - 0x31, 0x80, 0x8c, 0x03, 0x80, 0x89, 0x00, 0x0a, - 0x81, 0x41, 0xab, 0x81, 0x60, 0x74, 0xfa, 0x81, - 0x41, 0x0c, 0x82, 0x40, 0xe2, 0x84, 0x41, 0x7d, - 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, 0x96, 0x82, - 0x40, 0x92, 0x82, 0xfe, 0x80, 0x8f, 0x81, 0x40, - 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, 0xb8, - 0x10, 0x83, 0x40, 0xa8, 0x80, 0x89, 0x00, 0x80, - 0x8a, 0x0a, 0x80, 0xc0, 0x01, 0x80, 0x44, 0x39, - 0x80, 0xaf, 0x80, 0x44, 0x85, 0x80, 0x40, 0xc6, - 0x80, 0x41, 0x35, 0x81, 0x40, 0x97, 0x85, 0xc3, - 0x85, 0xd8, 0x83, 0x43, 0xb7, 0x84, 0xab, 0x83, - 0x40, 0xbc, 0x86, 0xef, 0x83, 0xfe, 0x82, 0x40, - 0x80, 0x0d, 0x80, 0x8f, 0x81, 0xd7, 0x84, 0xeb, - 0x80, 0x41, 0x29, 0x81, 0xf4, 0x82, 0x8b, 0x81, - 0x41, 0x65, 0x1a, 0x8e, 0xe8, 0x81, 0x40, 0xf8, - 0x82, 0x42, 0x04, 0x00, 0x80, 0x40, 0xfa, 0x81, - 0xd6, 0x0b, 0x81, 0x41, 0x9d, 0x82, 0xac, 0x80, - 0x42, 0x84, 0x81, 0xc9, 0x81, 0x45, 0x2a, 0x84, - 0x60, 0x45, 0xf8, 0x81, 0x40, 0x84, 0x80, 0xc0, - 0x82, 0x89, 0x80, 0x42, 0x28, 0x81, 0x41, 0x26, - 0x81, 0x60, 0x4e, 0x05, 0x80, 0x5d, 0xe6, 0x83, -}; - -static const uint8_t unicode_prop_Unified_Ideograph_table[48] = { - 0x60, 0x33, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x51, - 0xff, 0x60, 0x5a, 0x0d, 0x08, 0x00, 0x81, 0x89, - 0x00, 0x00, 0x09, 0x82, 0x61, 0x05, 0xd5, 0x60, - 0xa6, 0xdf, 0x9f, 0x50, 0x39, 0x85, 0x40, 0xdd, - 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x8e, 0x42, - 0x6d, 0x51, 0xa1, 0x53, 0x4a, 0x84, 0x50, 0x5f, -}; - -static const uint8_t unicode_prop_Variation_Selector_table[13] = { - 0x58, 0x0a, 0x10, 0x80, 0x60, 0xe5, 0xef, 0x8f, - 0x6d, 0x02, 0xef, 0x40, 0xef, -}; - -static const uint8_t unicode_prop_Bidi_Mirrored_table[173] = { - 0xa7, 0x81, 0x91, 0x00, 0x80, 0x9b, 0x00, 0x80, - 0x9c, 0x00, 0x80, 0xac, 0x80, 0x8e, 0x80, 0x4e, - 0x7d, 0x83, 0x47, 0x5c, 0x81, 0x49, 0x9b, 0x81, - 0x89, 0x81, 0xb5, 0x81, 0x8d, 0x81, 0x40, 0xb0, - 0x80, 0x40, 0xbf, 0x1a, 0x2a, 0x02, 0x0a, 0x18, - 0x18, 0x00, 0x03, 0x88, 0x20, 0x80, 0x91, 0x23, - 0x88, 0x08, 0x00, 0x38, 0x9f, 0x0b, 0x20, 0x88, - 0x09, 0x92, 0x21, 0x88, 0x21, 0x0b, 0x97, 0x81, - 0x8f, 0x3b, 0x93, 0x0e, 0x81, 0x44, 0x3c, 0x8d, - 0xc9, 0x01, 0x18, 0x08, 0x14, 0x1c, 0x12, 0x8d, - 0x41, 0x92, 0x95, 0x0d, 0x80, 0x8d, 0x38, 0x35, - 0x10, 0x1c, 0x01, 0x0c, 0x18, 0x02, 0x09, 0x89, - 0x29, 0x81, 0x8b, 0x92, 0x03, 0x08, 0x00, 0x08, - 0x03, 0x21, 0x2a, 0x97, 0x81, 0x8a, 0x0b, 0x18, - 0x09, 0x0b, 0xaa, 0x0f, 0x80, 0xa7, 0x20, 0x00, - 0x14, 0x22, 0x18, 0x14, 0x00, 0x40, 0xff, 0x80, - 0x42, 0x02, 0x1a, 0x08, 0x81, 0x8d, 0x09, 0x89, - 0xaa, 0x87, 0x41, 0xaa, 0x89, 0x0f, 0x60, 0xce, - 0x3c, 0x2c, 0x81, 0x40, 0xa1, 0x81, 0x91, 0x00, - 0x80, 0x9b, 0x00, 0x80, 0x9c, 0x00, 0x00, 0x08, - 0x81, 0x60, 0xd7, 0x76, 0x80, 0xb8, 0x80, 0xb8, - 0x80, 0xb8, 0x80, 0xb8, 0x80, -}; - -static const uint8_t unicode_prop_Emoji_table[238] = { - 0xa2, 0x05, 0x04, 0x89, 0xee, 0x03, 0x80, 0x5f, - 0x8c, 0x80, 0x8b, 0x80, 0x40, 0xd7, 0x80, 0x95, - 0x80, 0xd9, 0x85, 0x8e, 0x81, 0x41, 0x6e, 0x81, - 0x8b, 0x80, 0x40, 0xa5, 0x80, 0x98, 0x8a, 0x1a, - 0x40, 0xc6, 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, - 0x88, 0x80, 0xb9, 0x18, 0x84, 0x88, 0x01, 0x01, - 0x09, 0x03, 0x01, 0x00, 0x09, 0x02, 0x02, 0x0f, - 0x14, 0x00, 0x04, 0x8b, 0x8a, 0x09, 0x00, 0x08, - 0x80, 0x91, 0x01, 0x81, 0x91, 0x28, 0x00, 0x0a, - 0x0c, 0x01, 0x0b, 0x81, 0x8a, 0x0c, 0x09, 0x04, - 0x08, 0x00, 0x81, 0x93, 0x0c, 0x28, 0x19, 0x03, - 0x01, 0x01, 0x28, 0x01, 0x00, 0x00, 0x05, 0x02, - 0x05, 0x80, 0x89, 0x81, 0x8e, 0x01, 0x03, 0x00, - 0x03, 0x10, 0x80, 0x8a, 0x81, 0xaf, 0x82, 0x88, - 0x80, 0x8d, 0x80, 0x8d, 0x80, 0x41, 0x73, 0x81, - 0x41, 0xce, 0x82, 0x92, 0x81, 0xb2, 0x03, 0x80, - 0x44, 0xd9, 0x80, 0x8b, 0x80, 0x42, 0x58, 0x00, - 0x80, 0x61, 0xbd, 0x69, 0x80, 0x40, 0xc9, 0x80, - 0x40, 0x9f, 0x81, 0x8b, 0x81, 0x8d, 0x01, 0x89, - 0xca, 0x99, 0x01, 0x96, 0x80, 0x93, 0x01, 0x88, - 0x94, 0x81, 0x40, 0xad, 0xa1, 0x81, 0xef, 0x09, - 0x02, 0x81, 0xd2, 0x0a, 0x80, 0x41, 0x06, 0x80, - 0xbe, 0x8a, 0x28, 0x97, 0x31, 0x0f, 0x8b, 0x01, - 0x19, 0x03, 0x81, 0x8c, 0x09, 0x07, 0x81, 0x88, - 0x04, 0x82, 0x8b, 0x17, 0x11, 0x00, 0x03, 0x05, - 0x02, 0x05, 0xd5, 0xaf, 0xc5, 0x27, 0x0a, 0x83, - 0x89, 0x10, 0x01, 0x10, 0x81, 0x89, 0x40, 0xe2, - 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, 0x89, 0x80, - 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, 0x84, 0xb7, - 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, -}; - -static const uint8_t unicode_prop_Emoji_Component_table[28] = { - 0xa2, 0x05, 0x04, 0x89, 0x5f, 0xd2, 0x80, 0x40, - 0xd4, 0x80, 0x60, 0xdd, 0x2a, 0x80, 0x60, 0xf3, - 0xd5, 0x99, 0x41, 0xfa, 0x84, 0x45, 0xaf, 0x83, - 0x6c, 0x06, 0x6b, 0xdf, -}; - -static const uint8_t unicode_prop_Emoji_Modifier_table[4] = { - 0x61, 0xf3, 0xfa, 0x84, -}; - -static const uint8_t unicode_prop_Emoji_Modifier_Base_table[71] = { - 0x60, 0x26, 0x1c, 0x80, 0x40, 0xda, 0x80, 0x8f, - 0x83, 0x61, 0xcc, 0x76, 0x80, 0xbb, 0x11, 0x01, - 0x82, 0xf4, 0x09, 0x8a, 0x94, 0x92, 0x10, 0x1a, - 0x02, 0x30, 0x00, 0x97, 0x80, 0x40, 0xc8, 0x0b, - 0x80, 0x94, 0x03, 0x81, 0x40, 0xad, 0x12, 0x84, - 0xd2, 0x80, 0x8f, 0x82, 0x88, 0x80, 0x8a, 0x80, - 0x42, 0x3e, 0x01, 0x07, 0x3d, 0x80, 0x88, 0x89, - 0x0a, 0xb7, 0x80, 0xbc, 0x08, 0x08, 0x80, 0x90, - 0x10, 0x8c, 0x40, 0xe4, 0x82, 0xa9, 0x88, -}; - -static const uint8_t unicode_prop_Emoji_Presentation_table[144] = { - 0x60, 0x23, 0x19, 0x81, 0x40, 0xcc, 0x1a, 0x01, - 0x80, 0x42, 0x08, 0x81, 0x94, 0x81, 0xb1, 0x8b, - 0xaa, 0x80, 0x92, 0x80, 0x8c, 0x07, 0x81, 0x90, - 0x0c, 0x0f, 0x04, 0x80, 0x94, 0x06, 0x08, 0x03, - 0x01, 0x06, 0x03, 0x81, 0x9b, 0x80, 0xa2, 0x00, - 0x03, 0x10, 0x80, 0xbc, 0x82, 0x97, 0x80, 0x8d, - 0x80, 0x43, 0x5a, 0x81, 0xb2, 0x03, 0x80, 0x61, - 0xc4, 0xad, 0x80, 0x40, 0xc9, 0x80, 0x40, 0xbd, - 0x01, 0x89, 0xca, 0x99, 0x00, 0x97, 0x80, 0x93, - 0x01, 0x20, 0x82, 0x94, 0x81, 0x40, 0xad, 0xa0, - 0x8b, 0x88, 0x80, 0xc5, 0x80, 0x95, 0x8b, 0xaa, - 0x1c, 0x8b, 0x90, 0x10, 0x82, 0xc6, 0x00, 0x80, - 0x40, 0xba, 0x81, 0xbe, 0x8c, 0x18, 0x97, 0x91, - 0x80, 0x99, 0x81, 0x8c, 0x80, 0xd5, 0xd4, 0xaf, - 0xc5, 0x28, 0x12, 0x0a, 0x1b, 0x8a, 0x0e, 0x88, - 0x40, 0xe2, 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, - 0x89, 0x80, 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, - 0x84, 0xb7, 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, -}; - -static const uint8_t unicode_prop_Extended_Pictographic_table[156] = { - 0x40, 0xa8, 0x03, 0x80, 0x5f, 0x8c, 0x80, 0x8b, - 0x80, 0x40, 0xd7, 0x80, 0x95, 0x80, 0xd9, 0x85, - 0x8e, 0x81, 0x41, 0x6e, 0x81, 0x8b, 0x80, 0xde, - 0x80, 0xc5, 0x80, 0x98, 0x8a, 0x1a, 0x40, 0xc6, - 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, 0x88, 0x80, - 0xb9, 0x18, 0x28, 0x8b, 0x80, 0xf1, 0x89, 0xf5, - 0x81, 0x8a, 0x00, 0x00, 0x28, 0x10, 0x28, 0x89, - 0x81, 0x8e, 0x01, 0x03, 0x00, 0x03, 0x10, 0x80, - 0x8a, 0x84, 0xac, 0x82, 0x88, 0x80, 0x8d, 0x80, - 0x8d, 0x80, 0x41, 0x73, 0x81, 0x41, 0xce, 0x82, - 0x92, 0x81, 0xb2, 0x03, 0x80, 0x44, 0xd9, 0x80, - 0x8b, 0x80, 0x42, 0x58, 0x00, 0x80, 0x61, 0xbd, - 0x65, 0x40, 0xff, 0x8c, 0x82, 0x9e, 0x80, 0xbb, - 0x85, 0x8b, 0x81, 0x8d, 0x01, 0x89, 0x91, 0xb8, - 0x9a, 0x8e, 0x89, 0x80, 0x93, 0x01, 0x88, 0x03, - 0x88, 0x41, 0xb1, 0x84, 0x41, 0x3d, 0x87, 0x41, - 0x09, 0xaf, 0xff, 0xf3, 0x8b, 0xd4, 0xaa, 0x8b, - 0x83, 0xb7, 0x87, 0x89, 0x85, 0xa7, 0x87, 0x9d, - 0xd1, 0x8b, 0xae, 0x80, 0x89, 0x80, 0x41, 0xb8, - 0x40, 0xff, 0x43, 0xfd, -}; - -static const uint8_t unicode_prop_Default_Ignorable_Code_Point_table[51] = { - 0x40, 0xac, 0x80, 0x42, 0xa0, 0x80, 0x42, 0xcb, - 0x80, 0x4b, 0x41, 0x81, 0x46, 0x52, 0x81, 0xd4, - 0x84, 0x47, 0xfa, 0x84, 0x99, 0x84, 0xb0, 0x8f, - 0x50, 0xf3, 0x80, 0x60, 0xcc, 0x9a, 0x8f, 0x40, - 0xee, 0x80, 0x40, 0x9f, 0x80, 0xce, 0x88, 0x60, - 0xbc, 0xa6, 0x83, 0x54, 0xce, 0x87, 0x6c, 0x2e, - 0x84, 0x4f, 0xff, -}; - -typedef enum { - UNICODE_PROP_Hyphen, - UNICODE_PROP_Other_Math, - UNICODE_PROP_Other_Alphabetic, - UNICODE_PROP_Other_Lowercase, - UNICODE_PROP_Other_Uppercase, - UNICODE_PROP_Other_Grapheme_Extend, - UNICODE_PROP_Other_Default_Ignorable_Code_Point, - UNICODE_PROP_Other_ID_Start, - UNICODE_PROP_Other_ID_Continue, - UNICODE_PROP_Prepended_Concatenation_Mark, - UNICODE_PROP_ID_Continue1, - UNICODE_PROP_XID_Start1, - UNICODE_PROP_XID_Continue1, - UNICODE_PROP_Changes_When_Titlecased1, - UNICODE_PROP_Changes_When_Casefolded1, - UNICODE_PROP_Changes_When_NFKC_Casefolded1, - UNICODE_PROP_ASCII_Hex_Digit, - UNICODE_PROP_Bidi_Control, - UNICODE_PROP_Dash, - UNICODE_PROP_Deprecated, - UNICODE_PROP_Diacritic, - UNICODE_PROP_Extender, - UNICODE_PROP_Hex_Digit, - UNICODE_PROP_IDS_Unary_Operator, - UNICODE_PROP_IDS_Binary_Operator, - UNICODE_PROP_IDS_Trinary_Operator, - UNICODE_PROP_Ideographic, - UNICODE_PROP_Join_Control, - UNICODE_PROP_Logical_Order_Exception, - UNICODE_PROP_Modifier_Combining_Mark, - UNICODE_PROP_Noncharacter_Code_Point, - UNICODE_PROP_Pattern_Syntax, - UNICODE_PROP_Pattern_White_Space, - UNICODE_PROP_Quotation_Mark, - UNICODE_PROP_Radical, - UNICODE_PROP_Regional_Indicator, - UNICODE_PROP_Sentence_Terminal, - UNICODE_PROP_Soft_Dotted, - UNICODE_PROP_Terminal_Punctuation, - UNICODE_PROP_Unified_Ideograph, - UNICODE_PROP_Variation_Selector, - UNICODE_PROP_White_Space, - UNICODE_PROP_Bidi_Mirrored, - UNICODE_PROP_Emoji, - UNICODE_PROP_Emoji_Component, - UNICODE_PROP_Emoji_Modifier, - UNICODE_PROP_Emoji_Modifier_Base, - UNICODE_PROP_Emoji_Presentation, - UNICODE_PROP_Extended_Pictographic, - UNICODE_PROP_Default_Ignorable_Code_Point, - UNICODE_PROP_ID_Start, - UNICODE_PROP_Case_Ignorable, - UNICODE_PROP_ASCII, - UNICODE_PROP_Alphabetic, - UNICODE_PROP_Any, - UNICODE_PROP_Assigned, - UNICODE_PROP_Cased, - UNICODE_PROP_Changes_When_Casefolded, - UNICODE_PROP_Changes_When_Casemapped, - UNICODE_PROP_Changes_When_Lowercased, - UNICODE_PROP_Changes_When_NFKC_Casefolded, - UNICODE_PROP_Changes_When_Titlecased, - UNICODE_PROP_Changes_When_Uppercased, - UNICODE_PROP_Grapheme_Base, - UNICODE_PROP_Grapheme_Extend, - UNICODE_PROP_ID_Continue, - UNICODE_PROP_ID_Compat_Math_Start, - UNICODE_PROP_ID_Compat_Math_Continue, - UNICODE_PROP_Lowercase, - UNICODE_PROP_Math, - UNICODE_PROP_Uppercase, - UNICODE_PROP_XID_Continue, - UNICODE_PROP_XID_Start, - UNICODE_PROP_Cased1, - UNICODE_PROP_InCB, - UNICODE_PROP_COUNT, -} UnicodePropertyEnum; - -static const char unicode_prop_name_table[] = - "ASCII_Hex_Digit,AHex" "\0" - "Bidi_Control,Bidi_C" "\0" - "Dash" "\0" - "Deprecated,Dep" "\0" - "Diacritic,Dia" "\0" - "Extender,Ext" "\0" - "Hex_Digit,Hex" "\0" - "IDS_Unary_Operator,IDSU" "\0" - "IDS_Binary_Operator,IDSB" "\0" - "IDS_Trinary_Operator,IDST" "\0" - "Ideographic,Ideo" "\0" - "Join_Control,Join_C" "\0" - "Logical_Order_Exception,LOE" "\0" - "Modifier_Combining_Mark,MCM" "\0" - "Noncharacter_Code_Point,NChar" "\0" - "Pattern_Syntax,Pat_Syn" "\0" - "Pattern_White_Space,Pat_WS" "\0" - "Quotation_Mark,QMark" "\0" - "Radical" "\0" - "Regional_Indicator,RI" "\0" - "Sentence_Terminal,STerm" "\0" - "Soft_Dotted,SD" "\0" - "Terminal_Punctuation,Term" "\0" - "Unified_Ideograph,UIdeo" "\0" - "Variation_Selector,VS" "\0" - "White_Space,space" "\0" - "Bidi_Mirrored,Bidi_M" "\0" - "Emoji" "\0" - "Emoji_Component,EComp" "\0" - "Emoji_Modifier,EMod" "\0" - "Emoji_Modifier_Base,EBase" "\0" - "Emoji_Presentation,EPres" "\0" - "Extended_Pictographic,ExtPict" "\0" - "Default_Ignorable_Code_Point,DI" "\0" - "ID_Start,IDS" "\0" - "Case_Ignorable,CI" "\0" - "ASCII" "\0" - "Alphabetic,Alpha" "\0" - "Any" "\0" - "Assigned" "\0" - "Cased" "\0" - "Changes_When_Casefolded,CWCF" "\0" - "Changes_When_Casemapped,CWCM" "\0" - "Changes_When_Lowercased,CWL" "\0" - "Changes_When_NFKC_Casefolded,CWKCF" "\0" - "Changes_When_Titlecased,CWT" "\0" - "Changes_When_Uppercased,CWU" "\0" - "Grapheme_Base,Gr_Base" "\0" - "Grapheme_Extend,Gr_Ext" "\0" - "ID_Continue,IDC" "\0" - "ID_Compat_Math_Start" "\0" - "ID_Compat_Math_Continue" "\0" - "Lowercase,Lower" "\0" - "Math" "\0" - "Uppercase,Upper" "\0" - "XID_Continue,XIDC" "\0" - "XID_Start,XIDS" "\0" -; - -static const uint8_t * const unicode_prop_table[] = { - unicode_prop_Hyphen_table, - unicode_prop_Other_Math_table, - unicode_prop_Other_Alphabetic_table, - unicode_prop_Other_Lowercase_table, - unicode_prop_Other_Uppercase_table, - unicode_prop_Other_Grapheme_Extend_table, - unicode_prop_Other_Default_Ignorable_Code_Point_table, - unicode_prop_Other_ID_Start_table, - unicode_prop_Other_ID_Continue_table, - unicode_prop_Prepended_Concatenation_Mark_table, - unicode_prop_ID_Continue1_table, - unicode_prop_XID_Start1_table, - unicode_prop_XID_Continue1_table, - unicode_prop_Changes_When_Titlecased1_table, - unicode_prop_Changes_When_Casefolded1_table, - unicode_prop_Changes_When_NFKC_Casefolded1_table, - unicode_prop_ASCII_Hex_Digit_table, - unicode_prop_Bidi_Control_table, - unicode_prop_Dash_table, - unicode_prop_Deprecated_table, - unicode_prop_Diacritic_table, - unicode_prop_Extender_table, - unicode_prop_Hex_Digit_table, - unicode_prop_IDS_Unary_Operator_table, - unicode_prop_IDS_Binary_Operator_table, - unicode_prop_IDS_Trinary_Operator_table, - unicode_prop_Ideographic_table, - unicode_prop_Join_Control_table, - unicode_prop_Logical_Order_Exception_table, - unicode_prop_Modifier_Combining_Mark_table, - unicode_prop_Noncharacter_Code_Point_table, - unicode_prop_Pattern_Syntax_table, - unicode_prop_Pattern_White_Space_table, - unicode_prop_Quotation_Mark_table, - unicode_prop_Radical_table, - unicode_prop_Regional_Indicator_table, - unicode_prop_Sentence_Terminal_table, - unicode_prop_Soft_Dotted_table, - unicode_prop_Terminal_Punctuation_table, - unicode_prop_Unified_Ideograph_table, - unicode_prop_Variation_Selector_table, - unicode_prop_White_Space_table, - unicode_prop_Bidi_Mirrored_table, - unicode_prop_Emoji_table, - unicode_prop_Emoji_Component_table, - unicode_prop_Emoji_Modifier_table, - unicode_prop_Emoji_Modifier_Base_table, - unicode_prop_Emoji_Presentation_table, - unicode_prop_Extended_Pictographic_table, - unicode_prop_Default_Ignorable_Code_Point_table, - unicode_prop_ID_Start_table, - unicode_prop_Case_Ignorable_table, -}; - -static const uint16_t unicode_prop_len_table[] = { - countof(unicode_prop_Hyphen_table), - countof(unicode_prop_Other_Math_table), - countof(unicode_prop_Other_Alphabetic_table), - countof(unicode_prop_Other_Lowercase_table), - countof(unicode_prop_Other_Uppercase_table), - countof(unicode_prop_Other_Grapheme_Extend_table), - countof(unicode_prop_Other_Default_Ignorable_Code_Point_table), - countof(unicode_prop_Other_ID_Start_table), - countof(unicode_prop_Other_ID_Continue_table), - countof(unicode_prop_Prepended_Concatenation_Mark_table), - countof(unicode_prop_ID_Continue1_table), - countof(unicode_prop_XID_Start1_table), - countof(unicode_prop_XID_Continue1_table), - countof(unicode_prop_Changes_When_Titlecased1_table), - countof(unicode_prop_Changes_When_Casefolded1_table), - countof(unicode_prop_Changes_When_NFKC_Casefolded1_table), - countof(unicode_prop_ASCII_Hex_Digit_table), - countof(unicode_prop_Bidi_Control_table), - countof(unicode_prop_Dash_table), - countof(unicode_prop_Deprecated_table), - countof(unicode_prop_Diacritic_table), - countof(unicode_prop_Extender_table), - countof(unicode_prop_Hex_Digit_table), - countof(unicode_prop_IDS_Unary_Operator_table), - countof(unicode_prop_IDS_Binary_Operator_table), - countof(unicode_prop_IDS_Trinary_Operator_table), - countof(unicode_prop_Ideographic_table), - countof(unicode_prop_Join_Control_table), - countof(unicode_prop_Logical_Order_Exception_table), - countof(unicode_prop_Modifier_Combining_Mark_table), - countof(unicode_prop_Noncharacter_Code_Point_table), - countof(unicode_prop_Pattern_Syntax_table), - countof(unicode_prop_Pattern_White_Space_table), - countof(unicode_prop_Quotation_Mark_table), - countof(unicode_prop_Radical_table), - countof(unicode_prop_Regional_Indicator_table), - countof(unicode_prop_Sentence_Terminal_table), - countof(unicode_prop_Soft_Dotted_table), - countof(unicode_prop_Terminal_Punctuation_table), - countof(unicode_prop_Unified_Ideograph_table), - countof(unicode_prop_Variation_Selector_table), - countof(unicode_prop_White_Space_table), - countof(unicode_prop_Bidi_Mirrored_table), - countof(unicode_prop_Emoji_table), - countof(unicode_prop_Emoji_Component_table), - countof(unicode_prop_Emoji_Modifier_table), - countof(unicode_prop_Emoji_Modifier_Base_table), - countof(unicode_prop_Emoji_Presentation_table), - countof(unicode_prop_Extended_Pictographic_table), - countof(unicode_prop_Default_Ignorable_Code_Point_table), - countof(unicode_prop_ID_Start_table), - countof(unicode_prop_Case_Ignorable_table), -}; - diff --git a/vendor/quickjs/source_ng/libunicode.c b/vendor/quickjs/source_ng/libunicode.c deleted file mode 100755 index a621c5235..000000000 --- a/vendor/quickjs/source_ng/libunicode.c +++ /dev/null @@ -1,1746 +0,0 @@ -/* - * Unicode utilities - * - * Copyright (c) 2017-2018 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include -#include -#include -#include -#include - -#include "cutils.h" -#include "libunicode.h" -#include "libunicode-table.h" - -// note: stored as 4 bit tag, not much room left -enum { - RUN_TYPE_U, - RUN_TYPE_L, - RUN_TYPE_UF, - RUN_TYPE_LF, - RUN_TYPE_UL, - RUN_TYPE_LSU, - RUN_TYPE_U2L_399_EXT2, - RUN_TYPE_UF_D20, - RUN_TYPE_UF_D1_EXT, - RUN_TYPE_U_EXT, - RUN_TYPE_LF_EXT, - RUN_TYPE_UF_EXT2, - RUN_TYPE_LF_EXT2, - RUN_TYPE_UF_EXT3, -}; - -static int lre_case_conv1(uint32_t c, int conv_type) -{ - uint32_t res[LRE_CC_RES_LEN_MAX]; - lre_case_conv(res, c, conv_type); - return res[0]; -} - -/* case conversion using the table entry 'idx' with value 'v' */ -static int lre_case_conv_entry(uint32_t *res, uint32_t c, int conv_type, uint32_t idx, uint32_t v) -{ - uint32_t code, data, type, a, is_lower; - is_lower = (conv_type != 0); - type = (v >> (32 - 17 - 7 - 4)) & 0xf; - data = ((v & 0xf) << 8) | case_conv_table2[idx]; - code = v >> (32 - 17); - switch(type) { - case RUN_TYPE_U: - case RUN_TYPE_L: - case RUN_TYPE_UF: - case RUN_TYPE_LF: - if (conv_type == (type & 1) || - (type >= RUN_TYPE_UF && conv_type == 2)) { - c = c - code + (case_conv_table1[data] >> (32 - 17)); - } - break; - case RUN_TYPE_UL: - a = c - code; - if ((a & 1) != (1 - is_lower)) - break; - c = (a ^ 1) + code; - break; - case RUN_TYPE_LSU: - a = c - code; - if (a == 1) { - c += 2 * is_lower - 1; - } else if (a == (1 - is_lower) * 2) { - c += (2 * is_lower - 1) * 2; - } - break; - case RUN_TYPE_U2L_399_EXT2: - if (!is_lower) { - res[0] = c - code + case_conv_ext[data >> 6]; - res[1] = 0x399; - return 2; - } else { - c = c - code + case_conv_ext[data & 0x3f]; - } - break; - case RUN_TYPE_UF_D20: - if (conv_type == 1) - break; - c = data + (conv_type == 2) * 0x20; - break; - case RUN_TYPE_UF_D1_EXT: - if (conv_type == 1) - break; - c = case_conv_ext[data] + (conv_type == 2); - break; - case RUN_TYPE_U_EXT: - case RUN_TYPE_LF_EXT: - if (is_lower != (type - RUN_TYPE_U_EXT)) - break; - c = case_conv_ext[data]; - break; - case RUN_TYPE_LF_EXT2: - if (!is_lower) - break; - res[0] = c - code + case_conv_ext[data >> 6]; - res[1] = case_conv_ext[data & 0x3f]; - return 2; - case RUN_TYPE_UF_EXT2: - if (conv_type == 1) - break; - res[0] = c - code + case_conv_ext[data >> 6]; - res[1] = case_conv_ext[data & 0x3f]; - if (conv_type == 2) { - /* convert to lower */ - res[0] = lre_case_conv1(res[0], 1); - res[1] = lre_case_conv1(res[1], 1); - } - return 2; - default: - case RUN_TYPE_UF_EXT3: - if (conv_type == 1) - break; - res[0] = case_conv_ext[data >> 8]; - res[1] = case_conv_ext[(data >> 4) & 0xf]; - res[2] = case_conv_ext[data & 0xf]; - if (conv_type == 2) { - /* convert to lower */ - res[0] = lre_case_conv1(res[0], 1); - res[1] = lre_case_conv1(res[1], 1); - res[2] = lre_case_conv1(res[2], 1); - } - return 3; - } - res[0] = c; - return 1; -} - -/* conv_type: - 0 = to upper - 1 = to lower - 2 = case folding (= to lower with modifications) -*/ -int lre_case_conv(uint32_t *res, uint32_t c, int conv_type) -{ - if (c < 128) { - if (conv_type) { - if (c >= 'A' && c <= 'Z') { - c = c - 'A' + 'a'; - } - } else { - if (c >= 'a' && c <= 'z') { - c = c - 'a' + 'A'; - } - } - } else { - uint32_t v, code, len; - int idx, idx_min, idx_max; - - idx_min = 0; - idx_max = countof(case_conv_table1) - 1; - while (idx_min <= idx_max) { - idx = (unsigned)(idx_max + idx_min) / 2; - v = case_conv_table1[idx]; - code = v >> (32 - 17); - len = (v >> (32 - 17 - 7)) & 0x7f; - if (c < code) { - idx_max = idx - 1; - } else if (c >= code + len) { - idx_min = idx + 1; - } else { - return lre_case_conv_entry(res, c, conv_type, idx, v); - } - } - } - res[0] = c; - return 1; -} - -static int lre_case_folding_entry(uint32_t c, uint32_t idx, uint32_t v, bool is_unicode) -{ - uint32_t res[LRE_CC_RES_LEN_MAX]; - int len; - - if (is_unicode) { - len = lre_case_conv_entry(res, c, 2, idx, v); - if (len == 1) { - c = res[0]; - } else { - /* handle the few specific multi-character cases (see - unicode_gen.c:dump_case_folding_special_cases()) */ - if (c == 0xfb06) { - c = 0xfb05; - } else if (c == 0x01fd3) { - c = 0x390; - } else if (c == 0x01fe3) { - c = 0x3b0; - } - } - } else { - if (likely(c < 128)) { - if (c >= 'a' && c <= 'z') - c = c - 'a' + 'A'; - } else { - /* legacy regexp: to upper case if single char >= 128 */ - len = lre_case_conv_entry(res, c, false, idx, v); - if (len == 1 && res[0] >= 128) - c = res[0]; - } - } - return c; -} - -/* JS regexp specific rules for case folding */ -int lre_canonicalize(uint32_t c, bool is_unicode) -{ - if (c < 128) { - /* fast case */ - if (is_unicode) { - if (c >= 'A' && c <= 'Z') { - c = c - 'A' + 'a'; - } - } else { - if (c >= 'a' && c <= 'z') { - c = c - 'a' + 'A'; - } - } - } else { - uint32_t v, code, len; - int idx, idx_min, idx_max; - - idx_min = 0; - idx_max = countof(case_conv_table1) - 1; - while (idx_min <= idx_max) { - idx = (unsigned)(idx_max + idx_min) / 2; - v = case_conv_table1[idx]; - code = v >> (32 - 17); - len = (v >> (32 - 17 - 7)) & 0x7f; - if (c < code) { - idx_max = idx - 1; - } else if (c >= code + len) { - idx_min = idx + 1; - } else { - return lre_case_folding_entry(c, idx, v, is_unicode); - } - } - } - return c; -} - -static uint32_t get_le24(const uint8_t *ptr) -{ - return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16); -} - -#define UNICODE_INDEX_BLOCK_LEN 32 - -/* return -1 if not in table, otherwise the offset in the block */ -static int get_index_pos(uint32_t *pcode, uint32_t c, - const uint8_t *index_table, int index_table_len) -{ - uint32_t code, v; - int idx_min, idx_max, idx; - - idx_min = 0; - v = get_le24(index_table); - code = v & ((1 << 21) - 1); - if (c < code) { - *pcode = 0; - return 0; - } - idx_max = index_table_len - 1; - code = get_le24(index_table + idx_max * 3); - if (c >= code) - return -1; - /* invariant: tab[idx_min] <= c < tab2[idx_max] */ - while ((idx_max - idx_min) > 1) { - idx = (idx_max + idx_min) / 2; - v = get_le24(index_table + idx * 3); - code = v & ((1 << 21) - 1); - if (c < code) { - idx_max = idx; - } else { - idx_min = idx; - } - } - v = get_le24(index_table + idx_min * 3); - *pcode = v & ((1 << 21) - 1); - return (idx_min + 1) * UNICODE_INDEX_BLOCK_LEN + (v >> 21); -} - -static bool lre_is_in_table(uint32_t c, const uint8_t *table, - const uint8_t *index_table, int index_table_len) -{ - uint32_t code, b, bit; - int pos; - const uint8_t *p; - - pos = get_index_pos(&code, c, index_table, index_table_len); - if (pos < 0) - return false; /* outside the table */ - p = table + pos; - bit = 0; - for(;;) { - b = *p++; - if (b < 64) { - code += (b >> 3) + 1; - if (c < code) - return bit; - bit ^= 1; - code += (b & 7) + 1; - } else if (b >= 0x80) { - code += b - 0x80 + 1; - } else if (b < 0x60) { - code += (((b - 0x40) << 8) | p[0]) + 1; - p++; - } else { - code += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; - p += 2; - } - if (c < code) - return bit; - bit ^= 1; - } -} - -bool lre_is_cased(uint32_t c) -{ - uint32_t v, code, len; - int idx, idx_min, idx_max; - - idx_min = 0; - idx_max = countof(case_conv_table1) - 1; - while (idx_min <= idx_max) { - idx = (unsigned)(idx_max + idx_min) / 2; - v = case_conv_table1[idx]; - code = v >> (32 - 17); - len = (v >> (32 - 17 - 7)) & 0x7f; - if (c < code) { - idx_max = idx - 1; - } else if (c >= code + len) { - idx_min = idx + 1; - } else { - return true; - } - } - return lre_is_in_table(c, unicode_prop_Cased1_table, - unicode_prop_Cased1_index, - sizeof(unicode_prop_Cased1_index) / 3); -} - -bool lre_is_case_ignorable(uint32_t c) -{ - return lre_is_in_table(c, unicode_prop_Case_Ignorable_table, - unicode_prop_Case_Ignorable_index, - sizeof(unicode_prop_Case_Ignorable_index) / 3); -} - -/* character range */ - -static __maybe_unused void cr_dump(CharRange *cr) -{ - int i; - for(i = 0; i < cr->len; i++) - printf("%d: 0x%04x\n", i, cr->points[i]); -} - -static void *cr_default_realloc(void *opaque, void *ptr, size_t size) -{ - return realloc(ptr, size); -} - -void cr_init(CharRange *cr, void *mem_opaque, DynBufReallocFunc *realloc_func) -{ - cr->len = cr->size = 0; - cr->points = NULL; - cr->mem_opaque = mem_opaque; - cr->realloc_func = realloc_func ? realloc_func : cr_default_realloc; -} - -void cr_free(CharRange *cr) -{ - cr->realloc_func(cr->mem_opaque, cr->points, 0); -} - -int cr_realloc(CharRange *cr, int size) -{ - int new_size; - uint32_t *new_buf; - - if (size > cr->size) { - new_size = max_int(size, cr->size * 3 / 2); - new_buf = cr->realloc_func(cr->mem_opaque, cr->points, - new_size * sizeof(cr->points[0])); - if (!new_buf) - return -1; - cr->points = new_buf; - cr->size = new_size; - } - return 0; -} - -int cr_copy(CharRange *cr, const CharRange *cr1) -{ - if (cr_realloc(cr, cr1->len)) - return -1; - memcpy(cr->points, cr1->points, sizeof(cr->points[0]) * cr1->len); - cr->len = cr1->len; - return 0; -} - -/* merge consecutive intervals and remove empty intervals */ -static void cr_compress(CharRange *cr) -{ - int i, j, k, len; - uint32_t *pt; - - pt = cr->points; - len = cr->len; - i = 0; - j = 0; - k = 0; - while ((i + 1) < len) { - if (pt[i] == pt[i + 1]) { - /* empty interval */ - i += 2; - } else { - j = i; - while ((j + 3) < len && pt[j + 1] == pt[j + 2]) - j += 2; - /* just copy */ - pt[k] = pt[i]; - pt[k + 1] = pt[j + 1]; - k += 2; - i = j + 2; - } - } - cr->len = k; -} - -/* union or intersection */ -int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, - const uint32_t *b_pt, int b_len, int op) -{ - int a_idx, b_idx, is_in; - uint32_t v; - - a_idx = 0; - b_idx = 0; - for(;;) { - /* get one more point from a or b in increasing order */ - if (a_idx < a_len && b_idx < b_len) { - if (a_pt[a_idx] < b_pt[b_idx]) { - goto a_add; - } else if (a_pt[a_idx] == b_pt[b_idx]) { - v = a_pt[a_idx]; - a_idx++; - b_idx++; - } else { - goto b_add; - } - } else if (a_idx < a_len) { - a_add: - v = a_pt[a_idx++]; - } else if (b_idx < b_len) { - b_add: - v = b_pt[b_idx++]; - } else { - break; - } - /* add the point if the in/out status changes */ - switch(op) { - case CR_OP_UNION: - is_in = (a_idx & 1) | (b_idx & 1); - break; - case CR_OP_INTER: - is_in = (a_idx & 1) & (b_idx & 1); - break; - case CR_OP_XOR: - is_in = (a_idx & 1) ^ (b_idx & 1); - break; - default: - abort(); - } - if (is_in != (cr->len & 1)) { - if (cr_add_point(cr, v)) - return -1; - } - } - cr_compress(cr); - return 0; -} - -int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len) -{ - CharRange a = *cr; - int ret; - cr->len = 0; - cr->size = 0; - cr->points = NULL; - ret = cr_op(cr, a.points, a.len, b_pt, b_len, CR_OP_UNION); - cr_free(&a); - return ret; -} - -int cr_invert(CharRange *cr) -{ - int len; - len = cr->len; - if (cr_realloc(cr, len + 2)) - return -1; - memmove(cr->points + 1, cr->points, len * sizeof(cr->points[0])); - cr->points[0] = 0; - cr->points[len + 1] = UINT32_MAX; - cr->len = len + 2; - cr_compress(cr); - return 0; -} - -bool lre_is_id_start(uint32_t c) -{ - return lre_is_in_table(c, unicode_prop_ID_Start_table, - unicode_prop_ID_Start_index, - sizeof(unicode_prop_ID_Start_index) / 3); -} - -bool lre_is_id_continue(uint32_t c) -{ - return lre_is_id_start(c) || - lre_is_in_table(c, unicode_prop_ID_Continue1_table, - unicode_prop_ID_Continue1_index, - sizeof(unicode_prop_ID_Continue1_index) / 3); -} - -bool lre_is_white_space(uint32_t c) -{ - return lre_is_in_table(c, unicode_prop_White_Space_table, - unicode_prop_White_Space_index, - sizeof(unicode_prop_White_Space_index) / 3); -} - -#define UNICODE_DECOMP_LEN_MAX 18 - -typedef enum { - DECOMP_TYPE_C1, /* 16 bit char */ - DECOMP_TYPE_L1, /* 16 bit char table */ - DECOMP_TYPE_L2, - DECOMP_TYPE_L3, - DECOMP_TYPE_L4, - DECOMP_TYPE_L5, /* XXX: not used */ - DECOMP_TYPE_L6, /* XXX: could remove */ - DECOMP_TYPE_L7, /* XXX: could remove */ - DECOMP_TYPE_LL1, /* 18 bit char table */ - DECOMP_TYPE_LL2, - DECOMP_TYPE_S1, /* 8 bit char table */ - DECOMP_TYPE_S2, - DECOMP_TYPE_S3, - DECOMP_TYPE_S4, - DECOMP_TYPE_S5, - DECOMP_TYPE_I1, /* increment 16 bit char value */ - DECOMP_TYPE_I2_0, - DECOMP_TYPE_I2_1, - DECOMP_TYPE_I3_1, - DECOMP_TYPE_I3_2, - DECOMP_TYPE_I4_1, - DECOMP_TYPE_I4_2, - DECOMP_TYPE_B1, /* 16 bit base + 8 bit offset */ - DECOMP_TYPE_B2, - DECOMP_TYPE_B3, - DECOMP_TYPE_B4, - DECOMP_TYPE_B5, - DECOMP_TYPE_B6, - DECOMP_TYPE_B7, - DECOMP_TYPE_B8, - DECOMP_TYPE_B18, - DECOMP_TYPE_LS2, - DECOMP_TYPE_PAT3, - DECOMP_TYPE_S2_UL, - DECOMP_TYPE_LS2_UL, -} DecompTypeEnum; - -static uint32_t unicode_get_short_code(uint32_t c) -{ - static const uint16_t unicode_short_table[2] = { 0x2044, 0x2215 }; - - if (c < 0x80) - return c; - else if (c < 0x80 + 0x50) - return c - 0x80 + 0x300; - else - return unicode_short_table[c - 0x80 - 0x50]; -} - -static uint32_t unicode_get_lower_simple(uint32_t c) -{ - if (c < 0x100 || (c >= 0x410 && c <= 0x42f)) - c += 0x20; - else - c++; - return c; -} - -static uint16_t unicode_get16(const uint8_t *p) -{ - return p[0] | (p[1] << 8); -} - -static int unicode_decomp_entry(uint32_t *res, uint32_t c, - int idx, uint32_t code, uint32_t len, - uint32_t type) -{ - uint32_t c1; - int l, i, p; - const uint8_t *d; - - if (type == DECOMP_TYPE_C1) { - res[0] = unicode_decomp_table2[idx]; - return 1; - } else { - d = unicode_decomp_data + unicode_decomp_table2[idx]; - switch(type) { - case DECOMP_TYPE_L1: - case DECOMP_TYPE_L2: - case DECOMP_TYPE_L3: - case DECOMP_TYPE_L4: - case DECOMP_TYPE_L5: - case DECOMP_TYPE_L6: - case DECOMP_TYPE_L7: - l = type - DECOMP_TYPE_L1 + 1; - d += (c - code) * l * 2; - for(i = 0; i < l; i++) { - if ((res[i] = unicode_get16(d + 2 * i)) == 0) - return 0; - } - return l; - case DECOMP_TYPE_LL1: - case DECOMP_TYPE_LL2: - { - uint32_t k, p; - l = type - DECOMP_TYPE_LL1 + 1; - k = (c - code) * l; - p = len * l * 2; - for(i = 0; i < l; i++) { - c1 = unicode_get16(d + 2 * k) | - (((d[p + (k / 4)] >> ((k % 4) * 2)) & 3) << 16); - if (!c1) - return 0; - res[i] = c1; - k++; - } - } - return l; - case DECOMP_TYPE_S1: - case DECOMP_TYPE_S2: - case DECOMP_TYPE_S3: - case DECOMP_TYPE_S4: - case DECOMP_TYPE_S5: - l = type - DECOMP_TYPE_S1 + 1; - d += (c - code) * l; - for(i = 0; i < l; i++) { - if ((res[i] = unicode_get_short_code(d[i])) == 0) - return 0; - } - return l; - case DECOMP_TYPE_I1: - l = 1; - p = 0; - goto decomp_type_i; - case DECOMP_TYPE_I2_0: - case DECOMP_TYPE_I2_1: - case DECOMP_TYPE_I3_1: - case DECOMP_TYPE_I3_2: - case DECOMP_TYPE_I4_1: - case DECOMP_TYPE_I4_2: - l = 2 + ((type - DECOMP_TYPE_I2_0) >> 1); - p = ((type - DECOMP_TYPE_I2_0) & 1) + (l > 2); - decomp_type_i: - for(i = 0; i < l; i++) { - c1 = unicode_get16(d + 2 * i); - if (i == p) - c1 += c - code; - res[i] = c1; - } - return l; - case DECOMP_TYPE_B18: - l = 18; - goto decomp_type_b; - case DECOMP_TYPE_B1: - case DECOMP_TYPE_B2: - case DECOMP_TYPE_B3: - case DECOMP_TYPE_B4: - case DECOMP_TYPE_B5: - case DECOMP_TYPE_B6: - case DECOMP_TYPE_B7: - case DECOMP_TYPE_B8: - l = type - DECOMP_TYPE_B1 + 1; - decomp_type_b: - { - uint32_t c_min; - c_min = unicode_get16(d); - d += 2 + (c - code) * l; - for(i = 0; i < l; i++) { - c1 = d[i]; - if (c1 == 0xff) - c1 = 0x20; - else - c1 += c_min; - res[i] = c1; - } - } - return l; - case DECOMP_TYPE_LS2: - d += (c - code) * 3; - if (!(res[0] = unicode_get16(d))) - return 0; - res[1] = unicode_get_short_code(d[2]); - return 2; - case DECOMP_TYPE_PAT3: - res[0] = unicode_get16(d); - res[2] = unicode_get16(d + 2); - d += 4 + (c - code) * 2; - res[1] = unicode_get16(d); - return 3; - case DECOMP_TYPE_S2_UL: - case DECOMP_TYPE_LS2_UL: - c1 = c - code; - if (type == DECOMP_TYPE_S2_UL) { - d += c1 & ~1; - c = unicode_get_short_code(*d); - d++; - } else { - d += (c1 >> 1) * 3; - c = unicode_get16(d); - d += 2; - } - if (c1 & 1) - c = unicode_get_lower_simple(c); - res[0] = c; - res[1] = unicode_get_short_code(*d); - return 2; - } - } - return 0; -} - - -/* return the length of the decomposition (length <= - UNICODE_DECOMP_LEN_MAX) or 0 if no decomposition */ -static int unicode_decomp_char(uint32_t *res, uint32_t c, bool is_compat1) -{ - uint32_t v, type, is_compat, code, len; - int idx_min, idx_max, idx; - - idx_min = 0; - idx_max = countof(unicode_decomp_table1) - 1; - while (idx_min <= idx_max) { - idx = (idx_max + idx_min) / 2; - v = unicode_decomp_table1[idx]; - code = v >> (32 - 18); - len = (v >> (32 - 18 - 7)) & 0x7f; - // printf("idx=%d code=%05x len=%d\n", idx, code, len); - if (c < code) { - idx_max = idx - 1; - } else if (c >= code + len) { - idx_min = idx + 1; - } else { - is_compat = v & 1; - if (is_compat1 < is_compat) - break; - type = (v >> (32 - 18 - 7 - 6)) & 0x3f; - return unicode_decomp_entry(res, c, idx, code, len, type); - } - } - return 0; -} - -/* return 0 if no pair found */ -static int unicode_compose_pair(uint32_t c0, uint32_t c1) -{ - uint32_t code, len, type, v, idx1, d_idx, d_offset, ch; - int idx_min, idx_max, idx, d; - uint32_t pair[2]; - - idx_min = 0; - idx_max = countof(unicode_comp_table) - 1; - while (idx_min <= idx_max) { - idx = (idx_max + idx_min) / 2; - idx1 = unicode_comp_table[idx]; - - /* idx1 represent an entry of the decomposition table */ - d_idx = idx1 >> 6; - d_offset = idx1 & 0x3f; - v = unicode_decomp_table1[d_idx]; - code = v >> (32 - 18); - len = (v >> (32 - 18 - 7)) & 0x7f; - type = (v >> (32 - 18 - 7 - 6)) & 0x3f; - ch = code + d_offset; - unicode_decomp_entry(pair, ch, d_idx, code, len, type); - d = c0 - pair[0]; - if (d == 0) - d = c1 - pair[1]; - if (d < 0) { - idx_max = idx - 1; - } else if (d > 0) { - idx_min = idx + 1; - } else { - return ch; - } - } - return 0; -} - -/* return the combining class of character c (between 0 and 255) */ -static int unicode_get_cc(uint32_t c) -{ - uint32_t code, n, type, cc, c1, b; - int pos; - const uint8_t *p; - - pos = get_index_pos(&code, c, - unicode_cc_index, sizeof(unicode_cc_index) / 3); - if (pos < 0) - return 0; - p = unicode_cc_table + pos; - for(;;) { - b = *p++; - type = b >> 6; - n = b & 0x3f; - if (n < 48) { - } else if (n < 56) { - n = (n - 48) << 8; - n |= *p++; - n += 48; - } else { - n = (n - 56) << 8; - n |= *p++ << 8; - n |= *p++; - n += 48 + (1 << 11); - } - if (type <= 1) - p++; - c1 = code + n + 1; - if (c < c1) { - switch(type) { - case 0: - cc = p[-1]; - break; - case 1: - cc = p[-1] + c - code; - break; - case 2: - cc = 0; - break; - default: - case 3: - cc = 230; - break; - } - return cc; - } - code = c1; - } -} - -static void sort_cc(int *buf, int len) -{ - int i, j, k, cc, cc1, start, ch1; - - for(i = 0; i < len; i++) { - cc = unicode_get_cc(buf[i]); - if (cc != 0) { - start = i; - j = i + 1; - while (j < len) { - ch1 = buf[j]; - cc1 = unicode_get_cc(ch1); - if (cc1 == 0) - break; - k = j - 1; - while (k >= start) { - if (unicode_get_cc(buf[k]) <= cc1) - break; - buf[k + 1] = buf[k]; - k--; - } - buf[k + 1] = ch1; - j++; - } - i = j; - } - } -} - -static void to_nfd_rec(DynBuf *dbuf, - const int *src, int src_len, int is_compat) -{ - uint32_t c, v; - int i, l; - uint32_t res[UNICODE_DECOMP_LEN_MAX]; - - for(i = 0; i < src_len; i++) { - c = src[i]; - if (c >= 0xac00 && c < 0xd7a4) { - /* Hangul decomposition */ - c -= 0xac00; - dbuf_put_u32(dbuf, 0x1100 + c / 588); - dbuf_put_u32(dbuf, 0x1161 + (c % 588) / 28); - v = c % 28; - if (v != 0) - dbuf_put_u32(dbuf, 0x11a7 + v); - } else { - l = unicode_decomp_char(res, c, is_compat); - if (l) { - to_nfd_rec(dbuf, (int *)res, l, is_compat); - } else { - dbuf_put_u32(dbuf, c); - } - } - } -} - -/* return 0 if not found */ -static int compose_pair(uint32_t c0, uint32_t c1) -{ - /* Hangul composition */ - if (c0 >= 0x1100 && c0 < 0x1100 + 19 && - c1 >= 0x1161 && c1 < 0x1161 + 21) { - return 0xac00 + (c0 - 0x1100) * 588 + (c1 - 0x1161) * 28; - } else if (c0 >= 0xac00 && c0 < 0xac00 + 11172 && - (c0 - 0xac00) % 28 == 0 && - c1 >= 0x11a7 && c1 < 0x11a7 + 28) { - return c0 + c1 - 0x11a7; - } else { - return unicode_compose_pair(c0, c1); - } -} - -int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, - UnicodeNormalizationEnum n_type, - void *opaque, DynBufReallocFunc *realloc_func) -{ - int *buf, buf_len, i, p, starter_pos, cc, last_cc, out_len; - bool is_compat; - DynBuf dbuf_s, *dbuf = &dbuf_s; - - is_compat = n_type >> 1; - - dbuf_init2(dbuf, opaque, realloc_func); - if (dbuf_realloc(dbuf, sizeof(int) * src_len)) - goto fail; - - /* common case: latin1 is unaffected by NFC */ - if (n_type == UNICODE_NFC) { - for(i = 0; i < src_len; i++) { - if (src[i] >= 0x100) - goto not_latin1; - } - buf = (int *)dbuf->buf; - memcpy(buf, src, src_len * sizeof(int)); - *pdst = (uint32_t *)buf; - return src_len; - not_latin1: ; - } - - to_nfd_rec(dbuf, (const int *)src, src_len, is_compat); - if (dbuf_error(dbuf)) { - fail: - *pdst = NULL; - return -1; - } - buf = (int *)dbuf->buf; - buf_len = dbuf->size / sizeof(int); - - sort_cc(buf, buf_len); - - if (buf_len <= 1 || (n_type & 1) != 0) { - /* NFD / NFKD */ - *pdst = (uint32_t *)buf; - return buf_len; - } - - i = 1; - out_len = 1; - while (i < buf_len) { - /* find the starter character and test if it is blocked from - the character at 'i' */ - last_cc = unicode_get_cc(buf[i]); - starter_pos = out_len - 1; - while (starter_pos >= 0) { - cc = unicode_get_cc(buf[starter_pos]); - if (cc == 0) - break; - if (cc >= last_cc) - goto next; - last_cc = 256; - starter_pos--; - } - if (starter_pos >= 0 && - (p = compose_pair(buf[starter_pos], buf[i])) != 0) { - buf[starter_pos] = p; - i++; - } else { - next: - buf[out_len++] = buf[i++]; - } - } - *pdst = (uint32_t *)buf; - return out_len; -} - -/* char ranges for various unicode properties */ - -static int unicode_find_name(const char *name_table, const char *name) -{ - const char *p, *r; - int pos; - size_t name_len, len; - - p = name_table; - pos = 0; - name_len = strlen(name); - while (*p) { - for(;;) { - r = strchr(p, ','); - if (!r) - len = strlen(p); - else - len = r - p; - if (len == name_len && !memcmp(p, name, name_len)) - return pos; - p += len + 1; - if (!r) - break; - } - pos++; - } - return -1; -} - -/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 - if not found */ -int unicode_script(CharRange *cr, - const char *script_name, bool is_ext) -{ - int script_idx; - const uint8_t *p, *p_end; - uint32_t c, c1, b, n, v, v_len, i, type; - CharRange cr1_s = { 0 }, *cr1 = NULL; - CharRange cr2_s = { 0 }, *cr2 = &cr2_s; - bool is_common; - - script_idx = unicode_find_name(unicode_script_name_table, script_name); - if (script_idx < 0) - return -2; - /* Note: we remove the "Unknown" Script */ - script_idx += UNICODE_SCRIPT_Unknown + 1; - - is_common = (script_idx == UNICODE_SCRIPT_Common || - script_idx == UNICODE_SCRIPT_Inherited); - if (is_ext) { - cr1 = &cr1_s; - cr_init(cr1, cr->mem_opaque, cr->realloc_func); - cr_init(cr2, cr->mem_opaque, cr->realloc_func); - } else { - cr1 = cr; - } - - p = unicode_script_table; - p_end = unicode_script_table + countof(unicode_script_table); - c = 0; - while (p < p_end) { - b = *p++; - type = b >> 7; - n = b & 0x7f; - if (n < 96) { - } else if (n < 112) { - n = (n - 96) << 8; - n |= *p++; - n += 96; - } else { - n = (n - 112) << 16; - n |= *p++ << 8; - n |= *p++; - n += 96 + (1 << 12); - } - if (type == 0) - v = 0; - else - v = *p++; - c1 = c + n + 1; - if (v == script_idx) { - if (cr_add_interval(cr1, c, c1)) - goto fail; - } - c = c1; - } - - if (is_ext) { - /* add the script extensions */ - p = unicode_script_ext_table; - p_end = unicode_script_ext_table + countof(unicode_script_ext_table); - c = 0; - while (p < p_end) { - b = *p++; - if (b < 128) { - n = b; - } else if (b < 128 + 64) { - n = (b - 128) << 8; - n |= *p++; - n += 128; - } else { - n = (b - 128 - 64) << 16; - n |= *p++ << 8; - n |= *p++; - n += 128 + (1 << 14); - } - c1 = c + n + 1; - v_len = *p++; - if (is_common) { - if (v_len != 0) { - if (cr_add_interval(cr2, c, c1)) - goto fail; - } - } else { - for(i = 0; i < v_len; i++) { - if (p[i] == script_idx) { - if (cr_add_interval(cr2, c, c1)) - goto fail; - break; - } - } - } - p += v_len; - c = c1; - } - if (is_common) { - /* remove all the characters with script extensions */ - if (cr_invert(cr2)) - goto fail; - if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, - CR_OP_INTER)) - goto fail; - } else { - if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, - CR_OP_UNION)) - goto fail; - } - cr_free(cr1); - cr_free(cr2); - } - return 0; - fail: - if (is_ext) { - cr_free(cr1); - cr_free(cr2); - } - goto fail; -} - -#define M(id) (1U << UNICODE_GC_ ## id) - -static int unicode_general_category1(CharRange *cr, uint32_t gc_mask) -{ - const uint8_t *p, *p_end; - uint32_t c, c0, b, n, v; - - p = unicode_gc_table; - p_end = unicode_gc_table + countof(unicode_gc_table); - c = 0; - while (p < p_end) { - b = *p++; - n = b >> 5; - v = b & 0x1f; - if (n == 7) { - n = *p++; - if (n < 128) { - n += 7; - } else if (n < 128 + 64) { - n = (n - 128) << 8; - n |= *p++; - n += 7 + 128; - } else { - n = (n - 128 - 64) << 16; - n |= *p++ << 8; - n |= *p++; - n += 7 + 128 + (1 << 14); - } - } - c0 = c; - c += n + 1; - if (v == 31) { - /* run of Lu / Ll */ - b = gc_mask & (M(Lu) | M(Ll)); - if (b != 0) { - if (b == (M(Lu) | M(Ll))) { - goto add_range; - } else { - c0 += ((gc_mask & M(Ll)) != 0); - for(; c0 < c; c0 += 2) { - if (cr_add_interval(cr, c0, c0 + 1)) - return -1; - } - } - } - } else if ((gc_mask >> v) & 1) { - add_range: - if (cr_add_interval(cr, c0, c)) - return -1; - } - } - return 0; -} - -static int unicode_prop1(CharRange *cr, int prop_idx) -{ - const uint8_t *p, *p_end; - uint32_t c, c0, b, bit; - - p = unicode_prop_table[prop_idx]; - p_end = p + unicode_prop_len_table[prop_idx]; - c = 0; - bit = 0; - while (p < p_end) { - c0 = c; - b = *p++; - if (b < 64) { - c += (b >> 3) + 1; - if (bit) { - if (cr_add_interval(cr, c0, c)) - return -1; - } - bit ^= 1; - c0 = c; - c += (b & 7) + 1; - } else if (b >= 0x80) { - c += b - 0x80 + 1; - } else if (b < 0x60) { - c += (((b - 0x40) << 8) | p[0]) + 1; - p++; - } else { - c += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; - p += 2; - } - if (bit) { - if (cr_add_interval(cr, c0, c)) - return -1; - } - bit ^= 1; - } - return 0; -} - -#define CASE_U (1 << 0) -#define CASE_L (1 << 1) -#define CASE_F (1 << 2) - -/* use the case conversion table to generate range of characters. - CASE_U: set char if modified by uppercasing, - CASE_L: set char if modified by lowercasing, - CASE_F: set char if modified by case folding, - */ -static int unicode_case1(CharRange *cr, int case_mask) -{ -#define MR(x) (1 << RUN_TYPE_ ## x) - const uint32_t tab_run_mask[3] = { - MR(U) | MR(UF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(UF_D20) | - MR(UF_D1_EXT) | MR(U_EXT) | MR(UF_EXT2) | MR(UF_EXT3), - - MR(L) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2), - - MR(UF) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2) | MR(UF_D20) | MR(UF_D1_EXT) | MR(LF_EXT) | MR(UF_EXT2) | MR(UF_EXT3), - }; -#undef MR - uint32_t mask, v, code, type, len, i, idx; - - if (case_mask == 0) - return 0; - mask = 0; - for(i = 0; i < 3; i++) { - if ((case_mask >> i) & 1) - mask |= tab_run_mask[i]; - } - for(idx = 0; idx < countof(case_conv_table1); idx++) { - v = case_conv_table1[idx]; - type = (v >> (32 - 17 - 7 - 4)) & 0xf; - code = v >> (32 - 17); - len = (v >> (32 - 17 - 7)) & 0x7f; - if ((mask >> type) & 1) { - // printf("%d: type=%d %04x %04x\n", idx, type, code, code + len - 1); - switch(type) { - case RUN_TYPE_UL: - if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) - goto def_case; - code += ((case_mask & CASE_U) != 0); - for(i = 0; i < len; i += 2) { - if (cr_add_interval(cr, code + i, code + i + 1)) - return -1; - } - break; - case RUN_TYPE_LSU: - if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) - goto def_case; - if (!(case_mask & CASE_U)) { - if (cr_add_interval(cr, code, code + 1)) - return -1; - } - if (cr_add_interval(cr, code + 1, code + 2)) - return -1; - if (case_mask & CASE_U) { - if (cr_add_interval(cr, code + 2, code + 3)) - return -1; - } - break; - default: - def_case: - if (cr_add_interval(cr, code, code + len)) - return -1; - break; - } - } - } - return 0; -} - -static int point_cmp(const void *p1, const void *p2, void *arg) -{ - uint32_t v1 = *(uint32_t *)p1; - uint32_t v2 = *(uint32_t *)p2; - return (v1 > v2) - (v1 < v2); -} - -static void cr_sort_and_remove_overlap(CharRange *cr) -{ - uint32_t start, end, start1, end1, i, j; - - /* the resulting ranges are not necessarily sorted and may overlap */ - rqsort(cr->points, cr->len / 2, sizeof(cr->points[0]) * 2, point_cmp, NULL); - j = 0; - for(i = 0; i < cr->len; ) { - start = cr->points[i]; - end = cr->points[i + 1]; - i += 2; - while (i < cr->len) { - start1 = cr->points[i]; - end1 = cr->points[i + 1]; - if (start1 > end) { - /* |------| - * |-------| */ - break; - } else if (end1 <= end) { - /* |------| - * |--| */ - i += 2; - } else { - /* |------| - * |-------| */ - end = end1; - i += 2; - } - } - cr->points[j] = start; - cr->points[j + 1] = end; - j += 2; - } - cr->len = j; -} - -/* canonicalize a character set using the JS regex case folding rules - (see lre_canonicalize()) */ -int cr_regexp_canonicalize(CharRange *cr, bool is_unicode) -{ - CharRange cr_inter, cr_mask, cr_result, cr_sub; - uint32_t v, code, len, i, idx, start, end, c, d_start, d_end, d; - - cr_init(&cr_mask, cr->mem_opaque, cr->realloc_func); - cr_init(&cr_inter, cr->mem_opaque, cr->realloc_func); - cr_init(&cr_result, cr->mem_opaque, cr->realloc_func); - cr_init(&cr_sub, cr->mem_opaque, cr->realloc_func); - - if (unicode_case1(&cr_mask, is_unicode ? CASE_F : CASE_U)) - goto fail; - if (cr_op(&cr_inter, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) - goto fail; - - if (cr_invert(&cr_mask)) - goto fail; - if (cr_op(&cr_sub, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) - goto fail; - - /* cr_inter = cr & cr_mask */ - /* cr_sub = cr & ~cr_mask */ - - /* use the case conversion table to compute the result */ - d_start = -1; - d_end = -1; - idx = 0; - v = case_conv_table1[idx]; - code = v >> (32 - 17); - len = (v >> (32 - 17 - 7)) & 0x7f; - for(i = 0; i < cr_inter.len; i += 2) { - start = cr_inter.points[i]; - end = cr_inter.points[i + 1]; - - for(c = start; c < end; c++) { - for(;;) { - if (c >= code && c < code + len) - break; - idx++; - assert(idx < countof(case_conv_table1)); - v = case_conv_table1[idx]; - code = v >> (32 - 17); - len = (v >> (32 - 17 - 7)) & 0x7f; - } - d = lre_case_folding_entry(c, idx, v, is_unicode); - /* try to merge with the current interval */ - if (d_start == -1) { - d_start = d; - d_end = d + 1; - } else if (d_end == d) { - d_end++; - } else { - cr_add_interval(&cr_result, d_start, d_end); - d_start = d; - d_end = d + 1; - } - } - } - if (d_start != -1) { - if (cr_add_interval(&cr_result, d_start, d_end)) - goto fail; - } - - /* the resulting ranges are not necessarily sorted and may overlap */ - cr_sort_and_remove_overlap(&cr_result); - - /* or with the character not affected by the case folding */ - cr->len = 0; - if (cr_op(cr, cr_result.points, cr_result.len, cr_sub.points, cr_sub.len, CR_OP_UNION)) - goto fail; - - cr_free(&cr_inter); - cr_free(&cr_mask); - cr_free(&cr_result); - cr_free(&cr_sub); - return 0; - fail: - cr_free(&cr_inter); - cr_free(&cr_mask); - cr_free(&cr_result); - cr_free(&cr_sub); - return -1; -} - -typedef enum { - POP_GC, - POP_PROP, - POP_CASE, - POP_UNION, - POP_INTER, - POP_XOR, - POP_INVERT, - POP_END, -} PropOPEnum; - -#define POP_STACK_LEN_MAX 4 - -static int unicode_prop_ops(CharRange *cr, ...) -{ - va_list ap; - CharRange stack[POP_STACK_LEN_MAX]; - int stack_len, op, ret, i; - uint32_t a; - - va_start(ap, cr); - stack_len = 0; - for(;;) { - op = va_arg(ap, int); - switch(op) { - case POP_GC: - assert(stack_len < POP_STACK_LEN_MAX); - a = va_arg(ap, int); - cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); - if (unicode_general_category1(&stack[stack_len - 1], a)) - goto fail; - break; - case POP_PROP: - assert(stack_len < POP_STACK_LEN_MAX); - a = va_arg(ap, int); - cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); - if (unicode_prop1(&stack[stack_len - 1], a)) - goto fail; - break; - case POP_CASE: - assert(stack_len < POP_STACK_LEN_MAX); - a = va_arg(ap, int); - cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); - if (unicode_case1(&stack[stack_len - 1], a)) - goto fail; - break; - case POP_UNION: - case POP_INTER: - case POP_XOR: - { - CharRange *cr1, *cr2, *cr3; - assert(stack_len >= 2); - assert(stack_len < POP_STACK_LEN_MAX); - cr1 = &stack[stack_len - 2]; - cr2 = &stack[stack_len - 1]; - cr3 = &stack[stack_len++]; - cr_init(cr3, cr->mem_opaque, cr->realloc_func); - if (cr_op(cr3, cr1->points, cr1->len, - cr2->points, cr2->len, op - POP_UNION + CR_OP_UNION)) - goto fail; - cr_free(cr1); - cr_free(cr2); - *cr1 = *cr3; - stack_len -= 2; - } - break; - case POP_INVERT: - assert(stack_len >= 1); - if (cr_invert(&stack[stack_len - 1])) - goto fail; - break; - case POP_END: - goto done; - default: - abort(); - } - } - done: - va_end(ap); - assert(stack_len == 1); - ret = cr_copy(cr, &stack[0]); - cr_free(&stack[0]); - return ret; - fail: - va_end(ap); - for(i = 0; i < stack_len; i++) - cr_free(&stack[i]); - return -1; -} - -static const uint32_t unicode_gc_mask_table[] = { - M(Lu) | M(Ll) | M(Lt), /* LC */ - M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo), /* L */ - M(Mn) | M(Mc) | M(Me), /* M */ - M(Nd) | M(Nl) | M(No), /* N */ - M(Sm) | M(Sc) | M(Sk) | M(So), /* S */ - M(Pc) | M(Pd) | M(Ps) | M(Pe) | M(Pi) | M(Pf) | M(Po), /* P */ - M(Zs) | M(Zl) | M(Zp), /* Z */ - M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn), /* C */ -}; - -/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 - if not found */ -int unicode_general_category(CharRange *cr, const char *gc_name) -{ - int gc_idx; - uint32_t gc_mask; - - gc_idx = unicode_find_name(unicode_gc_name_table, gc_name); - if (gc_idx < 0) - return -2; - if (gc_idx <= UNICODE_GC_Co) { - gc_mask = (uint64_t)1 << gc_idx; - } else { - gc_mask = unicode_gc_mask_table[gc_idx - UNICODE_GC_LC]; - } - return unicode_general_category1(cr, gc_mask); -} - - -/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 - if not found */ -int unicode_prop(CharRange *cr, const char *prop_name) -{ - int prop_idx, ret; - - prop_idx = unicode_find_name(unicode_prop_name_table, prop_name); - if (prop_idx < 0) - return -2; - prop_idx += UNICODE_PROP_ASCII_Hex_Digit; - - ret = 0; - switch(prop_idx) { - case UNICODE_PROP_ASCII: - if (cr_add_interval(cr, 0x00, 0x7f + 1)) - return -1; - break; - case UNICODE_PROP_Any: - if (cr_add_interval(cr, 0x00000, 0x10ffff + 1)) - return -1; - break; - case UNICODE_PROP_Assigned: - ret = unicode_prop_ops(cr, - POP_GC, M(Cn), - POP_INVERT, - POP_END); - break; - case UNICODE_PROP_Math: - ret = unicode_prop_ops(cr, - POP_GC, M(Sm), - POP_PROP, UNICODE_PROP_Other_Math, - POP_UNION, - POP_END); - break; - case UNICODE_PROP_Lowercase: - ret = unicode_prop_ops(cr, - POP_GC, M(Ll), - POP_PROP, UNICODE_PROP_Other_Lowercase, - POP_UNION, - POP_END); - break; - case UNICODE_PROP_Uppercase: - ret = unicode_prop_ops(cr, - POP_GC, M(Lu), - POP_PROP, UNICODE_PROP_Other_Uppercase, - POP_UNION, - POP_END); - break; - case UNICODE_PROP_Cased: - ret = unicode_prop_ops(cr, - POP_GC, M(Lu) | M(Ll) | M(Lt), - POP_PROP, UNICODE_PROP_Other_Uppercase, - POP_UNION, - POP_PROP, UNICODE_PROP_Other_Lowercase, - POP_UNION, - POP_END); - break; - case UNICODE_PROP_Alphabetic: - ret = unicode_prop_ops(cr, - POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), - POP_PROP, UNICODE_PROP_Other_Uppercase, - POP_UNION, - POP_PROP, UNICODE_PROP_Other_Lowercase, - POP_UNION, - POP_PROP, UNICODE_PROP_Other_Alphabetic, - POP_UNION, - POP_END); - break; - case UNICODE_PROP_Grapheme_Base: - ret = unicode_prop_ops(cr, - POP_GC, M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn) | M(Zl) | M(Zp) | M(Me) | M(Mn), - POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, - POP_UNION, - POP_INVERT, - POP_END); - break; - case UNICODE_PROP_Grapheme_Extend: - ret = unicode_prop_ops(cr, - POP_GC, M(Me) | M(Mn), - POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, - POP_UNION, - POP_END); - break; - case UNICODE_PROP_XID_Start: - ret = unicode_prop_ops(cr, - POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), - POP_PROP, UNICODE_PROP_Other_ID_Start, - POP_UNION, - POP_PROP, UNICODE_PROP_Pattern_Syntax, - POP_PROP, UNICODE_PROP_Pattern_White_Space, - POP_UNION, - POP_PROP, UNICODE_PROP_XID_Start1, - POP_UNION, - POP_INVERT, - POP_INTER, - POP_END); - break; - case UNICODE_PROP_XID_Continue: - ret = unicode_prop_ops(cr, - POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl) | - M(Mn) | M(Mc) | M(Nd) | M(Pc), - POP_PROP, UNICODE_PROP_Other_ID_Start, - POP_UNION, - POP_PROP, UNICODE_PROP_Other_ID_Continue, - POP_UNION, - POP_PROP, UNICODE_PROP_Pattern_Syntax, - POP_PROP, UNICODE_PROP_Pattern_White_Space, - POP_UNION, - POP_PROP, UNICODE_PROP_XID_Continue1, - POP_UNION, - POP_INVERT, - POP_INTER, - POP_END); - break; - case UNICODE_PROP_Changes_When_Uppercased: - ret = unicode_case1(cr, CASE_U); - break; - case UNICODE_PROP_Changes_When_Lowercased: - ret = unicode_case1(cr, CASE_L); - break; - case UNICODE_PROP_Changes_When_Casemapped: - ret = unicode_case1(cr, CASE_U | CASE_L | CASE_F); - break; - case UNICODE_PROP_Changes_When_Titlecased: - ret = unicode_prop_ops(cr, - POP_CASE, CASE_U, - POP_PROP, UNICODE_PROP_Changes_When_Titlecased1, - POP_XOR, - POP_END); - break; - case UNICODE_PROP_Changes_When_Casefolded: - ret = unicode_prop_ops(cr, - POP_CASE, CASE_F, - POP_PROP, UNICODE_PROP_Changes_When_Casefolded1, - POP_XOR, - POP_END); - break; - case UNICODE_PROP_Changes_When_NFKC_Casefolded: - ret = unicode_prop_ops(cr, - POP_CASE, CASE_F, - POP_PROP, UNICODE_PROP_Changes_When_NFKC_Casefolded1, - POP_XOR, - POP_END); - break; - /* we use the existing tables */ - case UNICODE_PROP_ID_Continue: - ret = unicode_prop_ops(cr, - POP_PROP, UNICODE_PROP_ID_Start, - POP_PROP, UNICODE_PROP_ID_Continue1, - POP_XOR, - POP_END); - break; - default: - if (prop_idx >= countof(unicode_prop_table)) - return -2; - ret = unicode_prop1(cr, prop_idx); - break; - } - return ret; -} diff --git a/vendor/quickjs/source_ng/libunicode.h b/vendor/quickjs/source_ng/libunicode.h deleted file mode 100755 index 8e6f2a01d..000000000 --- a/vendor/quickjs/source_ng/libunicode.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Unicode utilities - * - * Copyright (c) 2017-2018 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef LIBUNICODE_H -#define LIBUNICODE_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define LRE_CC_RES_LEN_MAX 3 - -typedef enum { - UNICODE_NFC, - UNICODE_NFD, - UNICODE_NFKC, - UNICODE_NFKD, -} UnicodeNormalizationEnum; - -int lre_case_conv(uint32_t *res, uint32_t c, int conv_type); -int lre_canonicalize(uint32_t c, bool is_unicode); -bool lre_is_cased(uint32_t c); -bool lre_is_case_ignorable(uint32_t c); - -/* char ranges */ - -typedef struct { - int len; /* in points, always even */ - int size; - uint32_t *points; /* points sorted by increasing value */ - void *mem_opaque; - void *(*realloc_func)(void *opaque, void *ptr, size_t size); -} CharRange; - -typedef enum { - CR_OP_UNION, - CR_OP_INTER, - CR_OP_XOR, -} CharRangeOpEnum; - -void cr_init(CharRange *cr, void *mem_opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); -void cr_free(CharRange *cr); -int cr_realloc(CharRange *cr, int size); -int cr_copy(CharRange *cr, const CharRange *cr1); - -static inline int cr_add_point(CharRange *cr, uint32_t v) -{ - if (cr->len >= cr->size) { - if (cr_realloc(cr, cr->len + 1)) - return -1; - } - cr->points[cr->len++] = v; - return 0; -} - -static inline int cr_add_interval(CharRange *cr, uint32_t c1, uint32_t c2) -{ - if ((cr->len + 2) > cr->size) { - if (cr_realloc(cr, cr->len + 2)) - return -1; - } - cr->points[cr->len++] = c1; - cr->points[cr->len++] = c2; - return 0; -} - -int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len); - -static inline int cr_union_interval(CharRange *cr, uint32_t c1, uint32_t c2) -{ - uint32_t b_pt[2]; - b_pt[0] = c1; - b_pt[1] = c2 + 1; - return cr_union1(cr, b_pt, 2); -} - -int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, - const uint32_t *b_pt, int b_len, int op); - -int cr_invert(CharRange *cr); -int cr_regexp_canonicalize(CharRange *cr, bool is_unicode); - -bool lre_is_id_start(uint32_t c); -bool lre_is_id_continue(uint32_t c); -bool lre_is_white_space(uint32_t c); - -int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, - UnicodeNormalizationEnum n_type, - void *opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); - -/* Unicode character range functions */ - -int unicode_script(CharRange *cr, - const char *script_name, bool is_ext); -int unicode_general_category(CharRange *cr, const char *gc_name); -int unicode_prop(CharRange *cr, const char *prop_name); - -#ifdef __cplusplus -} /* extern "C" { */ -#endif - -#endif /* LIBUNICODE_H */ diff --git a/vendor/quickjs/source_ng/list.h b/vendor/quickjs/source_ng/list.h deleted file mode 100755 index b8dd71681..000000000 --- a/vendor/quickjs/source_ng/list.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Linux klist like system - * - * Copyright (c) 2016-2017 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef LIST_H -#define LIST_H - -#ifndef NULL -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -struct list_head { - struct list_head *prev; - struct list_head *next; -}; - -#define LIST_HEAD_INIT(el) { &(el), &(el) } - -/* return the pointer of type 'type *' containing 'el' as field 'member' */ -#define list_entry(el, type, member) container_of(el, type, member) - -static inline void init_list_head(struct list_head *head) -{ - head->prev = head; - head->next = head; -} - -/* insert 'el' between 'prev' and 'next' */ -static inline void __list_add(struct list_head *el, - struct list_head *prev, struct list_head *next) -{ - prev->next = el; - el->prev = prev; - el->next = next; - next->prev = el; -} - -/* add 'el' at the head of the list 'head' (= after element head) */ -static inline void list_add(struct list_head *el, struct list_head *head) -{ - __list_add(el, head, head->next); -} - -/* add 'el' at the end of the list 'head' (= before element head) */ -static inline void list_add_tail(struct list_head *el, struct list_head *head) -{ - __list_add(el, head->prev, head); -} - -static inline void list_del(struct list_head *el) -{ - struct list_head *prev, *next; - prev = el->prev; - next = el->next; - prev->next = next; - next->prev = prev; - el->prev = NULL; /* fail safe */ - el->next = NULL; /* fail safe */ -} - -static inline int list_empty(struct list_head *el) -{ - return el->next == el; -} - -#define list_for_each(el, head) \ - for(el = (head)->next; el != (head); el = el->next) - -#define list_for_each_safe(el, el1, head) \ - for(el = (head)->next, el1 = el->next; el != (head); \ - el = el1, el1 = el->next) - -#define list_for_each_prev(el, head) \ - for(el = (head)->prev; el != (head); el = el->prev) - -#define list_for_each_prev_safe(el, el1, head) \ - for(el = (head)->prev, el1 = el->prev; el != (head); \ - el = el1, el1 = el->prev) - -#ifdef __cplusplus -} /* extern "C" { */ -#endif - -#endif /* LIST_H */ diff --git a/vendor/quickjs/source_ng/meson.build b/vendor/quickjs/source_ng/meson.build deleted file mode 100755 index 1c178513c..000000000 --- a/vendor/quickjs/source_ng/meson.build +++ /dev/null @@ -1,635 +0,0 @@ -project( - 'quickjs-ng', - 'c', - version: '0.11.0', - default_options: [ - 'c_std=gnu11,c11', - 'warning_level=3', - 'default_library=static', - ], - license: 'MIT', - license_files: 'LICENSE', - meson_version: '>=1.3.0', -) - -host_system = host_machine.system() -cc = meson.get_compiler('c') - -qjs_gcc_warning_args = [ - '-Wno-unsafe-buffer-usage', - '-Wno-sign-conversion', - '-Wno-nonportable-system-include-path', - '-Wno-implicit-int-conversion', - '-Wno-shorten-64-to-32', - '-Wno-reserved-macro-identifier', - '-Wno-reserved-identifier', - '-Wdeprecated-declarations', - - '-Wno-implicit-fallthrough', - '-Wno-sign-compare', - '-Wno-missing-field-initializers', - '-Wno-unused-parameter', - '-Wno-unused-but-set-variable', - '-Wno-array-bounds', - '-Wno-format-truncation', -] -qjs_gcc_args = [ - '-funsigned-char', -] - -if host_system == 'darwin' - # https://github.com/quickjs-ng/quickjs/issues/453 - qjs_gcc_warning_args += '-Wno-maybe-uninitialized' -endif - -# https://github.com/microsoft/cpp-docs/tree/main/docs/error-messages/compiler-warnings -qjs_msvc_warning_args = [ - '/wd4018', # -Wno-sign-conversion - '/wd4061', # -Wno-implicit-fallthrough - '/wd4100', # -Wno-unused-parameter - '/wd4200', # -Wno-zero-length-array - '/wd4242', # -Wno-shorten-64-to-32 - '/wd4244', # -Wno-shorten-64-to-32 - '/wd4245', # -Wno-sign-compare - '/wd4267', # -Wno-shorten-64-to-32 - '/wd4388', # -Wno-sign-compare - '/wd4389', # -Wno-sign-compare - '/wd4710', # Function not inlined - '/wd4711', # Function was inlined - '/wd4820', # Padding added after construct - '/wd4996', # -Wdeprecated-declarations - '/wd5045', # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified -] -qjs_msvc_args = [ - '/experimental:c11atomics', - '/J', # -funsigned-char -] - -if cc.get_argument_syntax() == 'msvc' - add_project_arguments( - cc.get_supported_arguments(qjs_msvc_warning_args), - cc.get_id().contains('clang') ? cc.get_supported_arguments(qjs_gcc_warning_args) : [], - qjs_msvc_args, - language: 'c', - ) -else - add_project_arguments( - cc.get_supported_arguments(qjs_gcc_warning_args), - qjs_gcc_args, - language: 'c', - ) -endif - -if host_system == 'windows' - # Set a 8MB default stack size on Windows. - # It defaults to 1MB on MSVC, which is the same as our current JS stack size, - # so it will overflow and crash otherwise. - # On MinGW it defaults to 2MB. - stack_size = 8 * 1024 * 1024 - if cc.get_argument_syntax() == 'msvc' - add_project_link_arguments(f'/STACK:@stack_size@', language: 'c') - else - add_project_link_arguments(f'-Wl,--stack,@stack_size@', language: 'c') - endif -endif - -if meson.is_cross_build() - native_cc = meson.get_compiler('c', native: true) - - if native_cc.get_argument_syntax() == 'msvc' - # https://github.com/microsoft/cpp-docs/tree/main/docs/error-messages/compiler-warnings - add_project_arguments( - native_cc.get_supported_arguments(qjs_msvc_warning_args), - native_cc.get_id().contains('clang') ? native_cc.get_supported_arguments(qjs_gcc_warning_args) : [], - qjs_msvc_args, - - language: 'c', - native: true, - ) - else - add_project_arguments( - native_cc.get_supported_arguments(qjs_gcc_warning_args), - qjs_gcc_args, - - language: 'c', - native: true, - ) - endif -endif -if get_option('debug') - add_project_arguments( - cc.get_supported_arguments('-fno-omit-frame-pointer'), - language: 'c', - ) -endif - -qjs_sys_deps = [] - -m_dep = cc.find_library('m', required: false) -qjs_sys_deps += m_dep -qjs_sys_deps += dependency('threads', required: false) -qjs_sys_deps += dependency('dl', required: false) - -qjs_srcs = files( - 'cutils.c', - 'dtoa.c', - 'libregexp.c', - 'libunicode.c', - 'quickjs.c', -) -qjs_hdrs = files( - 'quickjs.h', -) - -qjs_libc = get_option('libc') -qjs_libc_srcs = files('quickjs-libc.c') -qjs_libc_hdrs = files('quickjs-libc.h') - -if qjs_libc - qjs_hdrs += qjs_libc_hdrs -endif - -qjs_parser = get_option('parser') - -qjs_c_args = ['-D_GNU_SOURCE'] - -if host_system == 'windows' - qjs_c_args += ['-DWIN32_LEAN_AND_MEAN', '-D_WIN32_WINNT=0x0601'] -endif - -if not qjs_parser - qjs_c_args += ['-DQJS_DISABLE_PARSER'] -endif - -qjs_libc_lib = static_library( - 'quickjs-libc', - qjs_libc_srcs, - - dependencies: qjs_sys_deps, - c_args: qjs_c_args, - gnu_symbol_visibility: 'hidden', -) - -qjs_lib = library( - 'qjs', - qjs_srcs, - - # export public headers - generator( - find_program('cp', 'xcopy'), - output: ['@PLAINNAME@'], - arguments: ['@INPUT@', '@OUTPUT@'], - ).process(qjs_hdrs), - - dependencies: qjs_sys_deps, - link_whole: qjs_libc ? qjs_libc_lib : [], - c_args: qjs_c_args, - gnu_symbol_visibility: 'hidden', - - install: true, - version: meson.project_version(), -) - -qjs_export_variables = [ - f'have_parser=@qjs_parser@' -] - -qjs_dep = declare_dependency( - link_with: qjs_lib, - dependencies: qjs_sys_deps, - include_directories: qjs_lib.private_dir_include(), - variables: qjs_export_variables, -) - -if host_system == 'emscripten' - qjs_wasm_export_name = 'getQuickJs' - executable( - 'qjs_wasm', - qjs_srcs, - link_args: cc.get_supported_link_arguments( - # in emscripten 3.x, this will be set to 16k which is too small for quickjs. - '-sSTACK_SIZE=@0@'.format(2 * 1024 * 1024), # let it be 2m = 2 * 1024 * 1024, otherwise, stack overflow may be occured at bootstrap - '-sNO_INVOKE_RUN', - '-sNO_EXIT_RUNTIME', - '-sMODULARIZE', # do not mess the global - '-sEXPORT_ES6', # export js file to morden es module - '-sEXPORT_NAME=@0@'.format(qjs_wasm_export_name), # give a name - '-sTEXTDECODER=1', # it will be 2 if we use -Oz, and that will cause js -> c string convertion fail - '-sNO_DEFAULT_TO_CXX', # this project is pure c project, no need for c plus plus handle - '-sEXPORTED_RUNTIME_METHODS=ccall,cwrap', - ), - dependencies: m_dep, - c_args: qjs_c_args, - ) -endif - -install_headers(qjs_hdrs, subdir: 'quickjs-ng') - -if not meson.is_subproject() - docdir = get_option('docdir') - datadir = get_option('datadir') - - if docdir == '' - docdir = datadir / 'doc' / meson.project_name() - endif - install_data( - 'LICENSE', - install_dir: docdir, - - install_tag: 'doc', - ) - install_subdir( - 'examples', - install_dir: docdir, - - strip_directory: false, - install_tag: 'doc', - ) -endif - -meson.override_dependency('quickjs-ng', qjs_dep) - -import('pkgconfig').generate( - qjs_lib, - subdirs: 'quickjs-ng', - name: 'quickjs-ng', - description: 'QuickJS, the Next Generation: a mighty JavaScript engine', - url: 'https://github.com/quickjs-ng/quickjs', - version: meson.project_version(), - variables: qjs_export_variables, -) - -if not qjs_parser - subdir_done() -endif - -# QuickJS bytecode compiler -qjsc_srcs = files( - 'qjsc.c', -) -qjsc_exe = executable( - 'qjsc', - qjsc_srcs, - - c_args: qjs_c_args, - link_with: qjs_libc ? [] : qjs_libc_lib, - dependencies: qjs_dep, - - install: true, -) - -mimalloc_dep = [] -mimalloc_sys_dep = dependency('mimalloc', required: get_option('cli_mimalloc')) -if mimalloc_sys_dep.found() - mimalloc_dep = declare_dependency( - dependencies: mimalloc_sys_dep, - compile_args: '-DQJS_USE_MIMALLOC', - ) -endif - -# QuickJS CLI -qjs_exe_srcs = files( - 'gen/repl.c', - 'gen/standalone.c', - 'qjs.c', -) -qjs_exe = executable( - 'qjs', - qjs_exe_srcs, - - c_args: qjs_c_args, - link_with: qjs_libc ? [] : qjs_libc_lib, - dependencies: [qjs_dep, mimalloc_dep], - export_dynamic: true, - - install: true, -) - -if meson.is_cross_build() - mimalloc_native_dep = [] - mimalloc_sys_native_dep = dependency('mimalloc', required: get_option('cli_mimalloc'), native: true) - if mimalloc_sys_dep.found() - mimalloc_native_dep = declare_dependency( - dependencies: mimalloc_sys_native_dep, - compile_args: '-DQJS_USE_MIMALLOC', - ) - endif - - qjs_sys_native_deps = [ - native_cc.find_library('m', required: false), - dependency('threads', required: false, native: true), - dependency('dl', required: false, native: true), - ] - qjs_native_lib = static_library( - 'qjs_native', - qjs_srcs, - qjs_libc_srcs, - - dependencies: qjs_sys_native_deps, - c_args: qjs_c_args, - gnu_symbol_visibility: 'hidden', - - build_by_default: false, - native: true, - install: false, - ) - - meson.override_find_program( - 'qjsc', - executable( - 'qjsc_native', - qjsc_srcs, - - c_args: qjs_c_args, - link_with: qjs_native_lib, - dependencies: qjs_sys_native_deps, - - build_by_default: false, - native: true, - install: false, - ), - ) - meson.override_find_program( - 'qjs', - executable( - 'qjs_native', - qjs_exe_srcs, - - c_args: qjs_c_args, - link_with: qjs_native_lib, - dependencies: [qjs_sys_native_deps, mimalloc_native_dep], - export_dynamic: true, - - build_by_default: false, - native: true, - install: false, - ), - ) -else - meson.override_find_program('qjsc', qjsc_exe) - meson.override_find_program('qjs', qjs_exe) -endif - -tests = get_option('tests').disable_auto_if(meson.is_subproject()) -examples = get_option('examples').disable_auto_if(meson.is_subproject()) - -if tests.allowed() - if host_system != 'emscripten' - # Test262 runner - run262_exe = executable( - 'run-test262', - 'run-test262.c', - qjs_libc_srcs, - - c_args: qjs_c_args, - dependencies: qjs_dep, - ) - - test( - 'test', - run262_exe, - args: ['-c', files('tests.conf')], - workdir: meson.current_source_dir(), - ) - - foreach bench : [ - 'empty_loop', - - 'date_now', - - 'prop_read', - 'prop_write', - 'prop_create', - 'prop_delete', - - 'array_read', - 'array_write', - 'array_prop_create', - 'array_length_decr', - 'array_hole_length_decr', - 'array_push', - 'array_pop', - - 'typed_array_read', - 'typed_array_write', - - 'global_read', - 'global_write', - 'global_write_strict', - - 'local_destruct', - 'global_destruct', - 'global_destruct_strict', - - 'func_call', - 'closure_var', - - 'int_arith', - 'float_arith', - - 'map_set', - 'map_delete', - 'weak_map_set', - 'weak_map_delete', - - 'array_for', - 'array_for_in', - 'array_for_of', - - 'math_min', - - 'object_null', - - 'regexp_ascii', - 'regexp_utf16', - - 'string_build1', - 'string_build2', - - 'string_slice1', - 'string_slice2', - 'string_slice3', - - 'sort_bench', - - 'int_to_string', - 'int_toString', - - 'float_to_string', - 'float_toString', - 'float_toFixed', - 'float_toPrecision', - 'float_toExponential', - - 'string_to_int', - 'string_to_float', - - 'bigint64_arith', - 'bigint256_arith', - ] - benchmark( - bench, - qjs_exe, - args: [files('tests/microbench.js'), bench], - ) - endforeach - endif - - # API test - test( - 'api', - executable( - 'api-test', - 'api-test.c', - - c_args: qjs_c_args, - dependencies: qjs_dep, - build_by_default: false, - ), - ) - - # Function.toString() test - test( - 'function_source', - executable( - 'function_source', - 'gen/function_source.c', - qjs_libc_srcs, - - c_args: qjs_c_args, - dependencies: qjs_dep, - build_by_default: false, - ), - ) -endif - -# Unicode generator -unicode_gen = executable( - 'unicode_gen', - 'cutils.c', - 'unicode_gen.c', - - c_args: qjs_c_args, - build_by_default: false, -) - -run_target( - 'libunicode-table.h', - command: [ - unicode_gen, - meson.current_source_dir() / 'unicode', - files('libunicode-table.h'), - ], -) - -# bytecode to c source code for builtin and examples -alias_target('codegen', - run_target( - 'codegen_repl.c', - command: [ - qjsc_exe, - '-ss', - '-o', files('gen/repl.c'), - '-m', - '-n', 'repl.js', - files('repl.js'), - ], - ), - run_target( - 'codegen_standalone.c', - command: [ - qjsc_exe, - '-ss', - '-o', files('gen/standalone.c'), - '-m', - '-n', 'standalone.js', - files('standalone.js'), - ], - ), - run_target( - 'codegen_function_source.c', - command: [ - qjsc_exe, - '-e', - '-o', files('gen/function_source.c'), - '-n', 'tests/function_source.js', - files('tests/function_source.js'), - ], - ), - run_target( - 'codegen_hello.c', - command: [ - qjsc_exe, - '-e', - '-o', files('gen/hello.c'), - '-n', 'examples/hello.js', - files('examples/hello.js'), - ], - ), - run_target( - 'codegen_hello_module.c', - command: [ - qjsc_exe, - '-e', - '-o', files('gen/hello_module.c'), - '-m', - '-n', 'examples/hello_module.js', - files('examples/hello_module.js'), - ], - ), - run_target( - 'codegen_test_fib.c', - command: [ - qjsc_exe, - '-e', - '-o', files('gen/test_fib.c'), - '-m', - '-n', 'examples/test_fib.js', - files('examples/test_fib.js'), - ], - ), - run_target( - 'codegen_builtin-array-fromasync.h', - command: [ - qjsc_exe, - '-C', - '-ss', - '-o', files('builtin-array-fromasync.h'), - '-n', 'builtin-array-fromasync.js', - files('builtin-array-fromasync.js'), - ], - ), -) - -if examples.allowed() - executable( - 'hello', - 'gen/hello.c', - qjs_libc_srcs, - - c_args: qjs_c_args, - dependencies: qjs_dep, - ) - - executable( - 'hello_module', - 'gen/hello_module.c', - qjs_libc_srcs, - - c_args: qjs_c_args, - dependencies: qjs_dep, - ) - - subdir('examples') - - executable( - 'test_fib', - 'examples/fib.c', - 'gen/test_fib.c', - qjs_libc_srcs, - - c_args: qjs_c_args, - dependencies: qjs_dep, - export_dynamic: true, - ) -endif diff --git a/vendor/quickjs/source_ng/meson_options.txt b/vendor/quickjs/source_ng/meson_options.txt deleted file mode 100755 index 20e661e1a..000000000 --- a/vendor/quickjs/source_ng/meson_options.txt +++ /dev/null @@ -1,6 +0,0 @@ -option('tests', type: 'feature', description: 'build tests') -option('examples', type: 'feature', description: 'build examples') -option('libc', type: 'boolean', value: false, description: 'build qjs standard library modules as part of the library') -option('cli_mimalloc', type: 'feature', value: 'disabled', description: 'build qjs cli with mimalloc') -option('docdir', type: 'string', description: 'documentation directory') -option('parser', type: 'boolean', value: true, description: 'Enable JS source code parser') diff --git a/vendor/quickjs/source_ng/qjs.c b/vendor/quickjs/source_ng/qjs.c deleted file mode 100755 index 4aed6837e..000000000 --- a/vendor/quickjs/source_ng/qjs.c +++ /dev/null @@ -1,748 +0,0 @@ -/* - * QuickJS stand alone interpreter - * - * Copyright (c) 2017-2021 Fabrice Bellard - * Copyright (c) 2017-2021 Charlie Gordon - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cutils.h" -#include "quickjs.h" -#include "quickjs-libc.h" - -#ifdef QJS_USE_MIMALLOC -#include -#endif - -extern const uint8_t qjsc_repl[]; -extern const uint32_t qjsc_repl_size; -extern const uint8_t qjsc_standalone[]; -extern const uint32_t qjsc_standalone_size; - -// Must match standalone.js -#define TRAILER_SIZE 12 -static const char trailer_magic[] = "quickjs2"; -static const int trailer_magic_size = sizeof(trailer_magic) - 1; -static const int trailer_size = TRAILER_SIZE; - -static int qjs__argc; -static char **qjs__argv; - - -static bool is_standalone(const char *exe) -{ - FILE *exe_f = fopen(exe, "rb"); - if (!exe_f) - return false; - if (fseek(exe_f, -trailer_size, SEEK_END) < 0) - goto fail; - uint8_t buf[TRAILER_SIZE]; - if (fread(buf, 1, trailer_size, exe_f) != trailer_size) - goto fail; - fclose(exe_f); - return !memcmp(buf, trailer_magic, trailer_magic_size); -fail: - fclose(exe_f); - return false; -} - -static JSValue load_standalone_module(JSContext *ctx) -{ - JSModuleDef *m; - JSValue obj, val; - obj = JS_ReadObject(ctx, qjsc_standalone, qjsc_standalone_size, JS_READ_OBJ_BYTECODE); - if (JS_IsException(obj)) - goto exception; - assert(JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE); - if (JS_ResolveModule(ctx, obj) < 0) { - JS_FreeValue(ctx, obj); - goto exception; - } - if (js_module_set_import_meta(ctx, obj, false, true) < 0) { - JS_FreeValue(ctx, obj); - goto exception; - } - val = JS_EvalFunction(ctx, JS_DupValue(ctx, obj)); - val = js_std_await(ctx, val); - - if (JS_IsException(val)) { - JS_FreeValue(ctx, obj); - exception: - js_std_dump_error(ctx); - exit(1); - } - JS_FreeValue(ctx, val); - - m = JS_VALUE_GET_PTR(obj); - JS_FreeValue(ctx, obj); - return JS_GetModuleNamespace(ctx, m); -} - -static int eval_buf(JSContext *ctx, const void *buf, int buf_len, - const char *filename, int eval_flags) -{ - bool use_realpath; - JSValue val; - int ret; - - if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE) { - /* for the modules, we compile then run to be able to set - import.meta */ - val = JS_Eval(ctx, buf, buf_len, filename, - eval_flags | JS_EVAL_FLAG_COMPILE_ONLY); - if (!JS_IsException(val)) { - // ex. "" pr "/dev/stdin" - use_realpath = - !(*filename == '<' || !strncmp(filename, "/dev/", 5)); - if (js_module_set_import_meta(ctx, val, use_realpath, true) < 0) { - js_std_dump_error(ctx); - ret = -1; - goto end; - } - val = JS_EvalFunction(ctx, val); - } - val = js_std_await(ctx, val); - } else { - val = JS_Eval(ctx, buf, buf_len, filename, eval_flags); - } - if (JS_IsException(val)) { - js_std_dump_error(ctx); - ret = -1; - } else { - ret = 0; - } -end: - JS_FreeValue(ctx, val); - return ret; -} - -static int eval_file(JSContext *ctx, const char *filename, int module) -{ - uint8_t *buf; - int ret, eval_flags; - size_t buf_len; - - buf = js_load_file(ctx, &buf_len, filename); - if (!buf) { - perror(filename); - exit(1); - } - - if (module < 0) { - module = (js__has_suffix(filename, ".mjs") || - JS_DetectModule((const char *)buf, buf_len)); - } - if (module) - eval_flags = JS_EVAL_TYPE_MODULE; - else - eval_flags = JS_EVAL_TYPE_GLOBAL; - ret = eval_buf(ctx, buf, buf_len, filename, eval_flags); - js_free(ctx, buf); - return ret; -} - -static int64_t parse_limit(const char *arg) { - char *p; - unsigned long unit = 1024; /* default to traditional KB */ - double d = strtod(arg, &p); - - if (p == arg) { - fprintf(stderr, "Invalid limit: %s\n", arg); - return -1; - } - - if (*p) { - switch (*p++) { - case 'b': case 'B': unit = 1UL << 0; break; - case 'k': case 'K': unit = 1UL << 10; break; /* IEC kibibytes */ - case 'm': case 'M': unit = 1UL << 20; break; /* IEC mebibytes */ - case 'g': case 'G': unit = 1UL << 30; break; /* IEC gigibytes */ - default: - fprintf(stderr, "Invalid limit: %s, unrecognized suffix, only k,m,g are allowed\n", arg); - return -1; - } - if (*p) { - fprintf(stderr, "Invalid limit: %s, only one suffix allowed\n", arg); - return -1; - } - } - - return (int64_t)(d * unit); -} - -static JSValue js_gc(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JS_RunGC(JS_GetRuntime(ctx)); - return JS_UNDEFINED; -} - -static JSValue js_navigator_get_userAgent(JSContext *ctx, JSValueConst this_val) -{ - char version[32]; - snprintf(version, sizeof(version), "quickjs-ng/%s", JS_GetVersion()); - return JS_NewString(ctx, version); -} - -static const JSCFunctionListEntry navigator_proto_funcs[] = { - JS_CGETSET_DEF2("userAgent", js_navigator_get_userAgent, NULL, JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE), - JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Navigator", JS_PROP_CONFIGURABLE), -}; - -static const JSCFunctionListEntry global_obj[] = { - JS_CFUNC_DEF("gc", 0, js_gc), -}; - -/* also used to initialize the worker context */ -static JSContext *JS_NewCustomContext(JSRuntime *rt) -{ - JSContext *ctx; - ctx = JS_NewContext(rt); - if (!ctx) - return NULL; - /* system modules */ - js_init_module_std(ctx, "qjs:std"); - js_init_module_os(ctx, "qjs:os"); - js_init_module_bjson(ctx, "qjs:bjson"); - - JSValue global = JS_GetGlobalObject(ctx); - JS_SetPropertyFunctionList(ctx, global, global_obj, countof(global_obj)); - JSValue args = JS_NewArray(ctx); - int i; - for(i = 0; i < qjs__argc; i++) { - JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, qjs__argv[i])); - } - JS_SetPropertyStr(ctx, global, "execArgv", args); - JS_SetPropertyStr(ctx, global, "argv0", JS_NewString(ctx, qjs__argv[0])); - JSValue navigator_proto = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, navigator_proto, navigator_proto_funcs, countof(navigator_proto_funcs)); - JSValue navigator = JS_NewObjectProto(ctx, navigator_proto); - JS_DefinePropertyValueStr(ctx, global, "navigator", navigator, JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE); - JS_FreeValue(ctx, global); - JS_FreeValue(ctx, navigator_proto); - - return ctx; -} - -struct trace_malloc_data { - uint8_t *base; -}; - -static inline unsigned long long js_trace_malloc_ptr_offset(uint8_t *ptr, - struct trace_malloc_data *dp) -{ - return ptr - dp->base; -} - -static void JS_PRINTF_FORMAT_ATTR(2, 3) js_trace_malloc_printf(void *opaque, JS_PRINTF_FORMAT const char *fmt, ...) -{ - va_list ap; - int c; - - va_start(ap, fmt); - while ((c = *fmt++) != '\0') { - if (c == '%') { - /* only handle %p and %zd */ - if (*fmt == 'p') { - uint8_t *ptr = va_arg(ap, void *); - if (ptr == NULL) { - printf("NULL"); - } else { - printf("H%+06lld.%zd", - js_trace_malloc_ptr_offset(ptr, opaque), - js__malloc_usable_size(ptr)); - } - fmt++; - continue; - } - if (fmt[0] == 'z' && fmt[1] == 'd') { - size_t sz = va_arg(ap, size_t); - printf("%zd", sz); - fmt += 2; - continue; - } - } - putc(c, stdout); - } - va_end(ap); -} - -static void js_trace_malloc_init(struct trace_malloc_data *s) -{ - free(s->base = malloc(8)); -} - -static void *js_trace_calloc(void *opaque, size_t count, size_t size) -{ - void *ptr; - ptr = calloc(count, size); - js_trace_malloc_printf(opaque, "C %zd %zd -> %p\n", count, size, ptr); - return ptr; -} - -static void *js_trace_malloc(void *opaque, size_t size) -{ - void *ptr; - ptr = malloc(size); - js_trace_malloc_printf(opaque, "A %zd -> %p\n", size, ptr); - return ptr; -} - -static void js_trace_free(void *opaque, void *ptr) -{ - if (!ptr) - return; - js_trace_malloc_printf(opaque, "F %p\n", ptr); - free(ptr); -} - -static void *js_trace_realloc(void *opaque, void *ptr, size_t size) -{ - js_trace_malloc_printf(opaque, "R %zd %p", size, ptr); - ptr = realloc(ptr, size); - js_trace_malloc_printf(opaque, " -> %p\n", ptr); - return ptr; -} - -static const JSMallocFunctions trace_mf = { - js_trace_calloc, - js_trace_malloc, - js_trace_free, - js_trace_realloc, - js__malloc_usable_size -}; - -#ifdef QJS_USE_MIMALLOC -static void *js_mi_calloc(void *opaque, size_t count, size_t size) -{ - return mi_calloc(count, size); -} - -static void *js_mi_malloc(void *opaque, size_t size) -{ - return mi_malloc(size); -} - -static void js_mi_free(void *opaque, void *ptr) -{ - if (!ptr) - return; - mi_free(ptr); -} - -static void *js_mi_realloc(void *opaque, void *ptr, size_t size) -{ - return mi_realloc(ptr, size); -} - -static const JSMallocFunctions mi_mf = { - js_mi_calloc, - js_mi_malloc, - js_mi_free, - js_mi_realloc, - mi_malloc_usable_size -}; -#endif - -#define PROG_NAME "qjs" - -void help(void) -{ - printf("QuickJS-ng version %s\n" - "usage: " PROG_NAME " [options] [file [args]]\n" - "-h --help list options\n" - "-e --eval EXPR evaluate EXPR\n" - "-i --interactive go to interactive mode\n" - "-C --script load as JS classic script (default=autodetect)\n" - "-m --module load as ES module (default=autodetect)\n" - "-I --include file include an additional file\n" - " --std make 'std', 'os' and 'bjson' available to script\n" - "-T --trace trace memory allocation\n" - "-d --dump dump the memory usage stats\n" - "-D --dump-flags flags for dumping debug data (see DUMP_* defines)\n" - "-c --compile FILE compile the given JS file as a standalone executable\n" - "-o --out FILE output file for standalone executables\n" - " --exe select the executable to use as the base, defaults to the current one\n" - " --memory-limit n limit the memory usage to 'n' Kbytes\n" - " --stack-size n limit the stack size to 'n' Kbytes\n" - "-q --quit just instantiate the interpreter and quit\n", JS_GetVersion()); - exit(1); -} - -int main(int argc, char **argv) -{ - JSRuntime *rt; - JSContext *ctx; - JSValue ret = JS_UNDEFINED; - struct trace_malloc_data trace_data = { NULL }; - int r = 0; - int optind = 1; - char exebuf[JS__PATH_MAX]; - size_t exebuf_size = sizeof(exebuf); - char *compile_file = NULL; - char *exe = NULL; - char *expr = NULL; - char *dump_flags_str = NULL; - char *out = NULL; - int standalone = 0; - int interactive = 0; - int dump_memory = 0; - int dump_flags = 0; - int trace_memory = 0; - int empty_run = 0; - int module = -1; - int load_std = 0; - char *include_list[32]; - int i, include_count = 0; - int64_t memory_limit = -1; - int64_t stack_size = -1; - - /* save for later */ - qjs__argc = argc; - qjs__argv = argv; - - /* check if this is a standalone executable */ - - if (!js_exepath(exebuf, &exebuf_size) && is_standalone(exebuf)) { - standalone = 1; - goto start; - } - - dump_flags_str = getenv("QJS_DUMP_FLAGS"); - dump_flags = dump_flags_str ? strtol(dump_flags_str, NULL, 16) : 0; - - /* cannot use getopt because we want to pass the command line to - the script */ - while (optind < argc && *argv[optind] == '-') { - char *arg = argv[optind] + 1; - const char *longopt = ""; - char *optarg = NULL; - /* a single - is not an option, it also stops argument scanning */ - if (!*arg) - break; - optind++; - if (*arg == '-') { - longopt = arg + 1; - optarg = strchr(longopt, '='); - if (optarg) - *optarg++ = '\0'; - arg += strlen(arg); - /* -- stops argument scanning */ - if (!*longopt) - break; - } - for (; *arg || *longopt; longopt = "") { - char opt = *arg; - if (opt) { - arg++; - if (!optarg && *arg) - optarg = arg; - } - if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) { - help(); - continue; - } - if (opt == 'e' || !strcmp(longopt, "eval")) { - if (!optarg) { - if (optind >= argc) { - fprintf(stderr, "qjs: missing expression for -e\n"); - exit(1); - } - optarg = argv[optind++]; - } - expr = optarg; - break; - } - if (opt == 'I' || !strcmp(longopt, "include")) { - if (optind >= argc) { - fprintf(stderr, "expecting filename"); - exit(1); - } - if (include_count >= countof(include_list)) { - fprintf(stderr, "too many included files"); - exit(1); - } - include_list[include_count++] = argv[optind++]; - continue; - } - if (opt == 'i' || !strcmp(longopt, "interactive")) { - interactive++; - continue; - } - if (opt == 'm' || !strcmp(longopt, "module")) { - module = 1; - continue; - } - if (opt == 'C' || !strcmp(longopt, "script")) { - module = 0; - continue; - } - if (opt == 'd' || !strcmp(longopt, "dump")) { - dump_memory++; - continue; - } - if (opt == 'D' || !strcmp(longopt, "dump-flags")) { - dump_flags = optarg ? strtol(optarg, NULL, 16) : 0; - break; - } - if (opt == 'T' || !strcmp(longopt, "trace")) { - trace_memory++; - continue; - } - if (!strcmp(longopt, "std")) { - load_std = 1; - continue; - } - if (opt == 'q' || !strcmp(longopt, "quit")) { - empty_run++; - continue; - } - if (!strcmp(longopt, "memory-limit")) { - if (!optarg) { - if (optind >= argc) { - fprintf(stderr, "expecting memory limit"); - exit(1); - } - optarg = argv[optind++]; - } - memory_limit = parse_limit(optarg); - break; - } - if (!strcmp(longopt, "stack-size")) { - if (!optarg) { - if (optind >= argc) { - fprintf(stderr, "expecting stack size"); - exit(1); - } - optarg = argv[optind++]; - } - stack_size = parse_limit(optarg); - break; - } - if (opt == 'c' || !strcmp(longopt, "compile")) { - if (!optarg) { - if (optind >= argc) { - fprintf(stderr, "qjs: missing file for -c\n"); - exit(1); - } - optarg = argv[optind++]; - } - compile_file = optarg; - break; - } - if (opt == 'o' || !strcmp(longopt, "out")) { - if (!optarg) { - if (optind >= argc) { - fprintf(stderr, "qjs: missing file for -o\n"); - exit(1); - } - optarg = argv[optind++]; - } - out = optarg; - break; - } - if (!strcmp(longopt, "exe")) { - if (!optarg) { - if (optind >= argc) { - fprintf(stderr, "qjs: missing file for --exe\n"); - exit(1); - } - optarg = argv[optind++]; - } - exe = optarg; - break; - } - if (opt) { - fprintf(stderr, "qjs: unknown option '-%c'\n", opt); - } else { - fprintf(stderr, "qjs: unknown option '--%s'\n", longopt); - } - help(); - } - } - - if (compile_file && !out) - help(); - -start: - - if (trace_memory) { - js_trace_malloc_init(&trace_data); - rt = JS_NewRuntime2(&trace_mf, &trace_data); - } else { -#ifdef QJS_USE_MIMALLOC - rt = JS_NewRuntime2(&mi_mf, NULL); -#else - rt = JS_NewRuntime(); -#endif - } - if (!rt) { - fprintf(stderr, "qjs: cannot allocate JS runtime\n"); - exit(2); - } - if (memory_limit >= 0) - JS_SetMemoryLimit(rt, (size_t)memory_limit); - if (stack_size >= 0) - JS_SetMaxStackSize(rt, (size_t)stack_size); - if (dump_flags != 0) - JS_SetDumpFlags(rt, dump_flags); - js_std_set_worker_new_context_func(JS_NewCustomContext); - js_std_init_handlers(rt); - ctx = JS_NewCustomContext(rt); - if (!ctx) { - fprintf(stderr, "qjs: cannot allocate JS context\n"); - exit(2); - } - - /* loader for ES6 modules */ - JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); - - /* exit on unhandled promise rejections */ - JS_SetHostPromiseRejectionTracker(rt, js_std_promise_rejection_tracker, NULL); - - if (!empty_run) { - js_std_add_helpers(ctx, argc - optind, argv + optind); - - /* make 'std' and 'os' visible to non module code */ - if (load_std) { - const char *str = - "import * as bjson from 'qjs:bjson';\n" - "import * as std from 'qjs:std';\n" - "import * as os from 'qjs:os';\n" - "globalThis.bjson = bjson;\n" - "globalThis.std = std;\n" - "globalThis.os = os;\n"; - eval_buf(ctx, str, strlen(str), "", JS_EVAL_TYPE_MODULE); - } - - for(i = 0; i < include_count; i++) { - if (eval_file(ctx, include_list[i], 0)) - goto fail; - } - - if (standalone) { - JSValue ns = load_standalone_module(ctx); - if (JS_IsException(ns)) - goto fail; - JSValue func = JS_GetPropertyStr(ctx, ns, "runStandalone"); - JS_FreeValue(ctx, ns); - if (JS_IsException(func)) - goto fail; - ret = JS_Call(ctx, func, JS_UNDEFINED, 0, NULL); - JS_FreeValue(ctx, func); - } else if (compile_file) { - JSValue ns = load_standalone_module(ctx); - if (JS_IsException(ns)) - goto fail; - JSValue func = JS_GetPropertyStr(ctx, ns, "compileStandalone"); - JS_FreeValue(ctx, ns); - if (JS_IsException(func)) - goto fail; - JSValue args[3]; - args[0] = JS_NewString(ctx, compile_file); - args[1] = JS_NewString(ctx, out); - args[2] = exe != NULL ? JS_NewString(ctx, exe) : JS_UNDEFINED; - ret = JS_Call(ctx, func, JS_UNDEFINED, 3, (JSValueConst *)args); - JS_FreeValue(ctx, func); - JS_FreeValue(ctx, args[0]); - JS_FreeValue(ctx, args[1]); - JS_FreeValue(ctx, args[2]); - } else if (expr) { - int flags = module ? JS_EVAL_TYPE_MODULE : 0; - if (eval_buf(ctx, expr, strlen(expr), "", flags)) - goto fail; - } else if (optind >= argc) { - /* interactive mode */ - interactive = 1; - } else { - const char *filename; - filename = argv[optind]; - if (eval_file(ctx, filename, module)) - goto fail; - } - if (interactive) { - JS_SetHostPromiseRejectionTracker(rt, NULL, NULL); - js_std_eval_binary(ctx, qjsc_repl, qjsc_repl_size, 0); - } - if (standalone || compile_file) { - if (JS_IsException(ret)) { - r = 1; - } else { - JS_FreeValue(ctx, ret); - r = js_std_loop(ctx); - } - } else { - r = js_std_loop(ctx); - } - if (r) { - js_std_dump_error(ctx); - goto fail; - } - } - - if (dump_memory) { - JSMemoryUsage stats; - JS_ComputeMemoryUsage(rt, &stats); - JS_DumpMemoryUsage(stdout, &stats, rt); - } - js_std_free_handlers(rt); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); - - if (empty_run && dump_memory) { - clock_t t[5]; - double best[5] = {0}; - int i, j; - for (i = 0; i < 100; i++) { - t[0] = clock(); - rt = JS_NewRuntime(); - t[1] = clock(); - ctx = JS_NewContext(rt); - t[2] = clock(); - JS_FreeContext(ctx); - t[3] = clock(); - JS_FreeRuntime(rt); - t[4] = clock(); - for (j = 4; j > 0; j--) { - double ms = 1000.0 * (t[j] - t[j - 1]) / CLOCKS_PER_SEC; - if (i == 0 || best[j] > ms) - best[j] = ms; - } - } - printf("\nInstantiation times (ms): %.3f = %.3f+%.3f+%.3f+%.3f\n", - best[1] + best[2] + best[3] + best[4], - best[1], best[2], best[3], best[4]); - } - return 0; - fail: - js_std_free_handlers(rt); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); - return 1; -} diff --git a/vendor/quickjs/source_ng/qjsc.c b/vendor/quickjs/source_ng/qjsc.c deleted file mode 100755 index 395bcb34d..000000000 --- a/vendor/quickjs/source_ng/qjsc.c +++ /dev/null @@ -1,673 +0,0 @@ -/* - * QuickJS command line compiler - * - * Copyright (c) 2018-2024 Fabrice Bellard - * Copyright (c) 2023-2025 Ben Noordhuis - * Copyright (c) 2023-2025 Saúl Ibarra Corretgé - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include -#include -#include -#include -#include -#include -#include - -#include "cutils.h" -#include "quickjs-libc.h" - -typedef enum { - OUTPUT_C, - OUTPUT_C_MAIN, - OUTPUT_RAW, -} OutputTypeEnum; - -typedef struct { - char *name; - char *short_name; - int flags; -} namelist_entry_t; - -typedef struct namelist_t { - namelist_entry_t *array; - int count; - int size; -} namelist_t; - -static namelist_t cname_list; -static namelist_t cmodule_list; -static namelist_t init_module_list; -static OutputTypeEnum output_type; -static FILE *outfile; -static const char *c_ident_prefix = "qjsc_"; -static int strip; - -void namelist_add(namelist_t *lp, const char *name, const char *short_name, - int flags) -{ - namelist_entry_t *e; - if (lp->count == lp->size) { - size_t newsize = lp->size + (lp->size >> 1) + 4; - namelist_entry_t *a = - realloc(lp->array, sizeof(lp->array[0]) * newsize); - /* XXX: check for realloc failure */ - lp->array = a; - lp->size = newsize; - } - e = &lp->array[lp->count++]; - e->name = strdup(name); - if (short_name) - e->short_name = strdup(short_name); - else - e->short_name = NULL; - e->flags = flags; -} - -void namelist_free(namelist_t *lp) -{ - while (lp->count > 0) { - namelist_entry_t *e = &lp->array[--lp->count]; - free(e->name); - free(e->short_name); - } - free(lp->array); - lp->array = NULL; - lp->size = 0; -} - -namelist_entry_t *namelist_find(namelist_t *lp, const char *name) -{ - int i; - for(i = 0; i < lp->count; i++) { - namelist_entry_t *e = &lp->array[i]; - if (!strcmp(e->name, name)) - return e; - } - return NULL; -} - -static void get_c_name(char *buf, size_t buf_size, const char *file) -{ - const char *p, *r; - size_t len, i; - int c; - char *q; - - p = strrchr(file, '/'); - if (!p) - p = file; - else - p++; - r = strrchr(p, '.'); - if (!r) - len = strlen(p); - else - len = r - p; - js__pstrcpy(buf, buf_size, c_ident_prefix); - q = buf + strlen(buf); - for(i = 0; i < len; i++) { - c = p[i]; - if (!((c >= '0' && c <= '9') || - (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z'))) { - c = '_'; - } - if ((q - buf) < buf_size - 1) - *q++ = c; - } - *q = '\0'; -} - -static void dump_hex(FILE *f, const uint8_t *buf, size_t len) -{ - size_t i, col; - col = 0; - for(i = 0; i < len; i++) { - fprintf(f, " 0x%02x,", buf[i]); - if (++col == 8) { - fprintf(f, "\n"); - col = 0; - } - } - if (col != 0) - fprintf(f, "\n"); -} - -static void output_object_code(JSContext *ctx, - FILE *fo, JSValue obj, const char *c_name, - bool load_only) -{ - uint8_t *out_buf; - size_t out_buf_len; - int flags = JS_WRITE_OBJ_BYTECODE; - - if (strip) { - flags |= JS_WRITE_OBJ_STRIP_SOURCE; - if (strip > 1) - flags |= JS_WRITE_OBJ_STRIP_DEBUG; - } - - out_buf = JS_WriteObject(ctx, &out_buf_len, obj, flags); - if (!out_buf) { - js_std_dump_error(ctx); - exit(1); - } - - namelist_add(&cname_list, c_name, NULL, load_only); - - if (output_type == OUTPUT_RAW) { - fwrite(out_buf, 1, out_buf_len, fo); - } else { - fprintf(fo, "const uint32_t %s_size = %u;\n\n", - c_name, (unsigned int)out_buf_len); - fprintf(fo, "const uint8_t %s[%u] = {\n", - c_name, (unsigned int)out_buf_len); - dump_hex(fo, out_buf, out_buf_len); - fprintf(fo, "};\n\n"); - } - - js_free(ctx, out_buf); -} - -static int js_module_dummy_init(JSContext *ctx, JSModuleDef *m) -{ - /* should never be called when compiling JS code */ - abort(); - return -1; // pacify compiler -} - -static void find_unique_cname(char *cname, size_t cname_size) -{ - char cname1[1024]; - int suffix_num; - size_t len, max_len; - assert(cname_size >= 32); - /* find a C name not matching an existing module C name by - adding a numeric suffix */ - len = strlen(cname); - max_len = cname_size - 16; - if (len > max_len) - cname[max_len] = '\0'; - suffix_num = 1; - for(;;) { - snprintf(cname1, sizeof(cname1), "%s_%d", cname, suffix_num); - if (!namelist_find(&cname_list, cname1)) - break; - suffix_num++; - } - js__pstrcpy(cname, cname_size, cname1); -} - -JSModuleDef *jsc_module_loader(JSContext *ctx, - const char *module_name, void *opaque) -{ - JSModuleDef *m; - namelist_entry_t *e; - - /* check if it is a declared C or system module */ - e = namelist_find(&cmodule_list, module_name); - if (e) { - /* add in the static init module list */ - namelist_add(&init_module_list, e->name, e->short_name, 0); - /* create a dummy module */ - m = JS_NewCModule(ctx, module_name, js_module_dummy_init); - } else if (js__has_suffix(module_name, ".so")) { - JS_ThrowReferenceError(ctx, "%s: dynamically linking to shared libraries not supported", - module_name); - return NULL; - } else { - size_t buf_len; - uint8_t *buf; - JSValue func_val; - char cname[1000]; - - buf = js_load_file(ctx, &buf_len, module_name); - if (!buf) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s'", - module_name); - return NULL; - } - - /* compile the module */ - func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, - JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); - js_free(ctx, buf); - if (JS_IsException(func_val)) - return NULL; - get_c_name(cname, sizeof(cname), module_name); - if (namelist_find(&cname_list, cname)) { - find_unique_cname(cname, sizeof(cname)); - } - output_object_code(ctx, outfile, func_val, cname, true); - - /* the module is already referenced, so we must free it */ - m = JS_VALUE_GET_PTR(func_val); - JS_FreeValue(ctx, func_val); - } - return m; -} - -static void compile_file(JSContext *ctx, FILE *fo, - const char *filename, - const char *script_name, - const char *c_name1, - int module) -{ - uint8_t *buf; - char c_name[1024]; - int eval_flags; - JSValue obj; - size_t buf_len; - - buf = js_load_file(ctx, &buf_len, filename); - if (!buf) { - fprintf(stderr, "Could not load '%s'\n", filename); - exit(1); - } - eval_flags = JS_EVAL_FLAG_COMPILE_ONLY; - if (module < 0) { - module = (js__has_suffix(filename, ".mjs") || - JS_DetectModule((const char *)buf, buf_len)); - } - if (module) - eval_flags |= JS_EVAL_TYPE_MODULE; - else - eval_flags |= JS_EVAL_TYPE_GLOBAL; - obj = JS_Eval(ctx, (const char *)buf, buf_len, script_name ? script_name : filename, eval_flags); - if (JS_IsException(obj)) { - js_std_dump_error(ctx); - exit(1); - } - js_free(ctx, buf); - if (c_name1) { - js__pstrcpy(c_name, sizeof(c_name), c_name1); - } else { - get_c_name(c_name, sizeof(c_name), filename); - } - output_object_code(ctx, fo, obj, c_name, false); - JS_FreeValue(ctx, obj); -} - -static const char main_c_template1[] = - "int main(int argc, char **argv)\n" - "{\n" - " int r;\n" - " JSRuntime *rt;\n" - " JSContext *ctx;\n" - " r = 0;\n" - " rt = JS_NewRuntime();\n" - " js_std_set_worker_new_context_func(JS_NewCustomContext);\n" - " js_std_init_handlers(rt);\n" - ; - -static const char main_c_template2[] = - " r = js_std_loop(ctx);\n" - " if (r) {\n" - " js_std_dump_error(ctx);\n" - " }\n" - " js_std_free_handlers(rt);\n" - " JS_FreeContext(ctx);\n" - " JS_FreeRuntime(rt);\n" - " return r;\n" - "}\n"; - -#define PROG_NAME "qjsc" - -void help(void) -{ - printf("QuickJS-ng Compiler version %s\n" - "usage: " PROG_NAME " [options] [files]\n" - "\n" - "options are:\n" - "-b output raw bytecode instead of C code\n" - "-e output main() and bytecode in a C file\n" - "-o output set the output filename\n" - "-n script_name set the script name (as used in stack traces)\n" - "-N cname set the C name of the generated data\n" - "-C compile as JS classic script (default=autodetect)\n" - "-m compile as ES module (default=autodetect)\n" - "-D module_name compile a dynamically loaded module or worker\n" - "-M module_name[,cname] add initialization code for an external C module\n" - "-p prefix set the prefix of the generated C names\n" - "-P do not add default system modules\n" - "-s strip the source code, specify twice to also strip debug info\n" - "-S n set the maximum stack size to 'n' bytes (default=%d)\n", - JS_GetVersion(), - JS_DEFAULT_STACK_SIZE); - exit(1); -} - -// TODO(bnoordhuis) share with qjs.c maybe -static int64_t parse_limit(const char *arg) { - char *p; - unsigned long unit = 1; // bytes for backcompat; qjs defaults to kilobytes - double d = strtod(arg, &p); - - if (p == arg) { - fprintf(stderr, "qjsc: invalid limit: %s\n", arg); - return -1; - } - - if (*p) { - switch (*p++) { - case 'b': case 'B': unit = 1UL << 0; break; - case 'k': case 'K': unit = 1UL << 10; break; /* IEC kibibytes */ - case 'm': case 'M': unit = 1UL << 20; break; /* IEC mebibytes */ - case 'g': case 'G': unit = 1UL << 30; break; /* IEC gigibytes */ - default: - fprintf(stderr, "qjsc: invalid limit: %s, unrecognized suffix, only k,m,g are allowed\n", arg); - return -1; - } - if (*p) { - fprintf(stderr, "qjsc: invalid limit: %s, only one suffix allowed\n", arg); - return -1; - } - } - - return (int64_t)(d * unit); -} - -static void check_hasarg(int optind, int argc, int opt) -{ - if (optind >= argc) { - fprintf(stderr, "qjsc: missing file for -%c\n", opt); - exit(1); - } -} - -int main(int argc, char **argv) -{ - int optind = 1; - int i; - const char *out_filename, *cname, *script_name; - char cfilename[1024]; - FILE *fo; - JSRuntime *rt; - JSContext *ctx; - int module; - size_t stack_size; - namelist_t dynamic_module_list; - bool load_system_modules = true; - - out_filename = NULL; - script_name = NULL; - output_type = OUTPUT_C; - cname = NULL; - module = -1; - strip = 0; - stack_size = 0; - memset(&dynamic_module_list, 0, sizeof(dynamic_module_list)); - - - while (optind < argc && *argv[optind] == '-') { - char *arg = argv[optind] + 1; - const char *longopt = ""; - char *optarg = NULL; - /* a single - is not an option, it also stops argument scanning */ - if (!*arg) - break; - optind++; - if (*arg == '-') { - longopt = arg + 1; - optarg = strchr(longopt, '='); - if (optarg) - *optarg++ = '\0'; - arg += strlen(arg); - /* -- stops argument scanning */ - if (!*longopt) - break; - } - for (; *arg || *longopt; longopt = "") { - char opt = *arg; - if (opt) { - arg++; - if (!optarg && *arg) - optarg = arg; - } - if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) { - help(); - continue; - } - if (opt == 'b') { - output_type = OUTPUT_RAW; - continue; - } - if (opt == 'o') { - if (!optarg) { - check_hasarg(optind, argc, opt); - optarg = argv[optind++]; - } - out_filename = optarg; - continue; - } - if (opt == 'e') { - output_type = OUTPUT_C_MAIN; - continue; - } - if (opt == 'n') { - if (!optarg) { - check_hasarg(optind, argc, opt); - optarg = argv[optind++]; - } - script_name = optarg; - continue; - } - if (opt == 'N') { - if (!optarg) { - check_hasarg(optind, argc, opt); - optarg = argv[optind++]; - } - cname = optarg; - continue; - } - if (opt == 'C') { - module = 0; - continue; - } - if (opt == 'm') { - module = 1; - continue; - } - if (opt == 'M') { - char *p; - char path[1024]; - char cname[1024]; - if (!optarg) { - check_hasarg(optind, argc, opt); - optarg = argv[optind++]; - } - js__pstrcpy(path, sizeof(path), optarg); - p = strchr(path, ','); - if (p) { - *p = '\0'; - js__pstrcpy(cname, sizeof(cname), p + 1); - } else { - get_c_name(cname, sizeof(cname), path); - } - namelist_add(&cmodule_list, path, cname, 0); - continue; - } - if (opt == 'D') { - if (!optarg) { - check_hasarg(optind, argc, opt); - optarg = argv[optind++]; - } - namelist_add(&dynamic_module_list, optarg, NULL, 0); - continue; - } - if (opt == 'P') { - load_system_modules = false; - continue; - } - if (opt == 's') { - strip++; - continue; - } - if (opt == 'p') { - if (!optarg) { - check_hasarg(optind, argc, opt); - optarg = argv[optind++]; - } - c_ident_prefix = optarg; - continue; - } - if (opt == 'S') { - if (!optarg) { - check_hasarg(optind, argc, opt); - optarg = argv[optind++]; - } - stack_size = parse_limit(optarg); - continue; - } - help(); - } - } - - if (load_system_modules) { - /* add system modules */ - namelist_add(&cmodule_list, "qjs:std", "std", 0); - namelist_add(&cmodule_list, "qjs:os", "os", 0); - namelist_add(&cmodule_list, "qjs:bjson", "bjson", 0); - namelist_add(&cmodule_list, "std", "std", 0); - namelist_add(&cmodule_list, "os", "os", 0); - namelist_add(&cmodule_list, "bjson", "bjson", 0); - } - - if (optind >= argc) - help(); - - if (!out_filename) - out_filename = "out.c"; - - js__pstrcpy(cfilename, sizeof(cfilename), out_filename); - - if (output_type == OUTPUT_RAW) - fo = fopen(cfilename, "wb"); - else - fo = fopen(cfilename, "w"); - - if (!fo) { - perror(cfilename); - exit(1); - } - outfile = fo; - - rt = JS_NewRuntime(); - ctx = JS_NewContext(rt); - - /* loader for ES6 modules */ - JS_SetModuleLoaderFunc(rt, NULL, jsc_module_loader, NULL); - - if (output_type != OUTPUT_RAW) { - fprintf(fo, "/* File generated automatically by the QuickJS-ng compiler. */\n" - "\n" - ); - } - - if (output_type == OUTPUT_C_MAIN) { - fprintf(fo, "#include \"quickjs-libc.h\"\n" - "\n" - ); - } else if (output_type == OUTPUT_C) { - fprintf(fo, "#include \n" - "\n" - ); - } - - for(i = optind; i < argc; i++) { - const char *filename = argv[i]; - compile_file(ctx, fo, filename, script_name, cname, module); - cname = NULL; - } - - for(i = 0; i < dynamic_module_list.count; i++) { - if (!jsc_module_loader(ctx, dynamic_module_list.array[i].name, NULL)) { - fprintf(stderr, "Could not load dynamic module '%s'\n", - dynamic_module_list.array[i].name); - exit(1); - } - } - - if (output_type == OUTPUT_C_MAIN) { - fprintf(fo, - "static JSContext *JS_NewCustomContext(JSRuntime *rt)\n" - "{\n" - " JSContext *ctx = JS_NewContext(rt);\n" - " if (!ctx)\n" - " return NULL;\n"); - /* add the precompiled modules (XXX: could modify the module - loader instead) */ - for(i = 0; i < init_module_list.count; i++) { - namelist_entry_t *e = &init_module_list.array[i]; - /* initialize the static C modules */ - - fprintf(fo, - " {\n" - " extern JSModuleDef *js_init_module_%s(JSContext *ctx, const char *name);\n" - " js_init_module_%s(ctx, \"%s\");\n" - " }\n", - e->short_name, e->short_name, e->name); - } - for(i = 0; i < cname_list.count; i++) { - namelist_entry_t *e = &cname_list.array[i]; - if (e->flags) { - fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 1);\n", - e->name, e->name); - } - } - fprintf(fo, - " return ctx;\n" - "}\n\n"); - - fputs(main_c_template1, fo); - - if (stack_size != 0) { - fprintf(fo, " JS_SetMaxStackSize(rt, %u);\n", - (unsigned int)stack_size); - } - - /* add the module loader */ - fprintf(fo, " JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);\n"); - - fprintf(fo, - " ctx = JS_NewCustomContext(rt);\n" - " js_std_add_helpers(ctx, argc, argv);\n"); - - for(i = 0; i < cname_list.count; i++) { - namelist_entry_t *e = &cname_list.array[i]; - if (!e->flags) { - fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 0);\n", - e->name, e->name); - } - } - fputs(main_c_template2, fo); - } - - JS_FreeContext(ctx); - JS_FreeRuntime(rt); - - fclose(fo); - - namelist_free(&cname_list); - namelist_free(&cmodule_list); - namelist_free(&init_module_list); - return 0; -} diff --git a/vendor/quickjs/source_ng/quickjs-atom.h b/vendor/quickjs/source_ng/quickjs-atom.h deleted file mode 100755 index 2a1232843..000000000 --- a/vendor/quickjs/source_ng/quickjs-atom.h +++ /dev/null @@ -1,265 +0,0 @@ -/* - * QuickJS atom definitions - * - * Copyright (c) 2017-2018 Fabrice Bellard - * Copyright (c) 2017-2018 Charlie Gordon - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifdef DEF - -/* Note: first atoms are considered as keywords in the parser */ -DEF(null, "null") /* must be first */ -DEF(false, "false") -DEF(true, "true") -DEF(if, "if") -DEF(else, "else") -DEF(return, "return") -DEF(var, "var") -DEF(this, "this") -DEF(delete, "delete") -DEF(void, "void") -DEF(typeof, "typeof") -DEF(new, "new") -DEF(in, "in") -DEF(instanceof, "instanceof") -DEF(do, "do") -DEF(while, "while") -DEF(for, "for") -DEF(break, "break") -DEF(continue, "continue") -DEF(switch, "switch") -DEF(case, "case") -DEF(default, "default") -DEF(throw, "throw") -DEF(try, "try") -DEF(catch, "catch") -DEF(finally, "finally") -DEF(function, "function") -DEF(debugger, "debugger") -DEF(with, "with") -/* FutureReservedWord */ -DEF(class, "class") -DEF(const, "const") -DEF(enum, "enum") -DEF(export, "export") -DEF(extends, "extends") -DEF(import, "import") -DEF(super, "super") -/* FutureReservedWords when parsing strict mode code */ -DEF(implements, "implements") -DEF(interface, "interface") -DEF(let, "let") -DEF(package, "package") -DEF(private, "private") -DEF(protected, "protected") -DEF(public, "public") -DEF(static, "static") -DEF(yield, "yield") -DEF(await, "await") - -/* empty string */ -DEF(empty_string, "") -/* identifiers */ -DEF(keys, "keys") -DEF(size, "size") -DEF(length, "length") -DEF(message, "message") -DEF(cause, "cause") -DEF(errors, "errors") -DEF(stack, "stack") -DEF(name, "name") -DEF(toString, "toString") -DEF(toLocaleString, "toLocaleString") -DEF(valueOf, "valueOf") -DEF(eval, "eval") -DEF(prototype, "prototype") -DEF(constructor, "constructor") -DEF(configurable, "configurable") -DEF(writable, "writable") -DEF(enumerable, "enumerable") -DEF(value, "value") -DEF(get, "get") -DEF(set, "set") -DEF(of, "of") -DEF(__proto__, "__proto__") -DEF(undefined, "undefined") -DEF(number, "number") -DEF(boolean, "boolean") -DEF(string, "string") -DEF(object, "object") -DEF(symbol, "symbol") -DEF(integer, "integer") -DEF(unknown, "unknown") -DEF(arguments, "arguments") -DEF(callee, "callee") -DEF(caller, "caller") -DEF(_eval_, "") -DEF(_ret_, "") -DEF(_var_, "") -DEF(_arg_var_, "") -DEF(_with_, "") -DEF(lastIndex, "lastIndex") -DEF(target, "target") -DEF(index, "index") -DEF(input, "input") -DEF(defineProperties, "defineProperties") -DEF(apply, "apply") -DEF(join, "join") -DEF(concat, "concat") -DEF(split, "split") -DEF(construct, "construct") -DEF(getPrototypeOf, "getPrototypeOf") -DEF(setPrototypeOf, "setPrototypeOf") -DEF(isExtensible, "isExtensible") -DEF(preventExtensions, "preventExtensions") -DEF(has, "has") -DEF(deleteProperty, "deleteProperty") -DEF(defineProperty, "defineProperty") -DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor") -DEF(ownKeys, "ownKeys") -DEF(add, "add") -DEF(done, "done") -DEF(next, "next") -DEF(values, "values") -DEF(source, "source") -DEF(flags, "flags") -DEF(global, "global") -DEF(unicode, "unicode") -DEF(raw, "raw") -DEF(new_target, "new.target") -DEF(this_active_func, "this.active_func") -DEF(home_object, "") -DEF(computed_field, "") -DEF(static_computed_field, "") /* must come after computed_fields */ -DEF(class_fields_init, "") -DEF(brand, "") -DEF(hash_constructor, "#constructor") -DEF(as, "as") -DEF(from, "from") -DEF(fromAsync, "fromAsync") -DEF(meta, "meta") -DEF(_default_, "*default*") -DEF(_star_, "*") -DEF(Module, "Module") -DEF(then, "then") -DEF(resolve, "resolve") -DEF(reject, "reject") -DEF(promise, "promise") -DEF(proxy, "proxy") -DEF(revoke, "revoke") -DEF(async, "async") -DEF(exec, "exec") -DEF(groups, "groups") -DEF(indices, "indices") -DEF(status, "status") -DEF(reason, "reason") -DEF(globalThis, "globalThis") -DEF(bigint, "bigint") -DEF(not_equal, "not-equal") -DEF(timed_out, "timed-out") -DEF(ok, "ok") -DEF(toJSON, "toJSON") -DEF(maxByteLength, "maxByteLength") -/* class names */ -DEF(Object, "Object") -DEF(Array, "Array") -DEF(Error, "Error") -DEF(Number, "Number") -DEF(String, "String") -DEF(Boolean, "Boolean") -DEF(Symbol, "Symbol") -DEF(Arguments, "Arguments") -DEF(Math, "Math") -DEF(JSON, "JSON") -DEF(Date, "Date") -DEF(Function, "Function") -DEF(GeneratorFunction, "GeneratorFunction") -DEF(ForInIterator, "ForInIterator") -DEF(RegExp, "RegExp") -DEF(ArrayBuffer, "ArrayBuffer") -DEF(SharedArrayBuffer, "SharedArrayBuffer") -/* must keep same order as class IDs for typed arrays */ -DEF(Uint8ClampedArray, "Uint8ClampedArray") -DEF(Int8Array, "Int8Array") -DEF(Uint8Array, "Uint8Array") -DEF(Int16Array, "Int16Array") -DEF(Uint16Array, "Uint16Array") -DEF(Int32Array, "Int32Array") -DEF(Uint32Array, "Uint32Array") -DEF(BigInt64Array, "BigInt64Array") -DEF(BigUint64Array, "BigUint64Array") -DEF(Float16Array, "Float16Array") -DEF(Float32Array, "Float32Array") -DEF(Float64Array, "Float64Array") -DEF(DataView, "DataView") -DEF(BigInt, "BigInt") -DEF(WeakRef, "WeakRef") -DEF(FinalizationRegistry, "FinalizationRegistry") -DEF(Map, "Map") -DEF(Set, "Set") /* Map + 1 */ -DEF(WeakMap, "WeakMap") /* Map + 2 */ -DEF(WeakSet, "WeakSet") /* Map + 3 */ -DEF(Iterator, "Iterator") -DEF(IteratorConcat, "Iterator Concat") -DEF(IteratorHelper, "Iterator Helper") -DEF(IteratorWrap, "Iterator Wrap") -DEF(Map_Iterator, "Map Iterator") -DEF(Set_Iterator, "Set Iterator") -DEF(Array_Iterator, "Array Iterator") -DEF(String_Iterator, "String Iterator") -DEF(RegExp_String_Iterator, "RegExp String Iterator") -DEF(Generator, "Generator") -DEF(Proxy, "Proxy") -DEF(Promise, "Promise") -DEF(PromiseResolveFunction, "PromiseResolveFunction") -DEF(PromiseRejectFunction, "PromiseRejectFunction") -DEF(AsyncFunction, "AsyncFunction") -DEF(AsyncFunctionResolve, "AsyncFunctionResolve") -DEF(AsyncFunctionReject, "AsyncFunctionReject") -DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction") -DEF(AsyncGenerator, "AsyncGenerator") -DEF(EvalError, "EvalError") -DEF(RangeError, "RangeError") -DEF(ReferenceError, "ReferenceError") -DEF(SyntaxError, "SyntaxError") -DEF(TypeError, "TypeError") -DEF(URIError, "URIError") -DEF(InternalError, "InternalError") -DEF(DOMException, "DOMException") -DEF(CallSite, "CallSite") -/* private symbols */ -DEF(Private_brand, "") -/* symbols */ -DEF(Symbol_toPrimitive, "Symbol.toPrimitive") -DEF(Symbol_iterator, "Symbol.iterator") -DEF(Symbol_match, "Symbol.match") -DEF(Symbol_matchAll, "Symbol.matchAll") -DEF(Symbol_replace, "Symbol.replace") -DEF(Symbol_search, "Symbol.search") -DEF(Symbol_split, "Symbol.split") -DEF(Symbol_toStringTag, "Symbol.toStringTag") -DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") -DEF(Symbol_hasInstance, "Symbol.hasInstance") -DEF(Symbol_species, "Symbol.species") -DEF(Symbol_unscopables, "Symbol.unscopables") -DEF(Symbol_asyncIterator, "Symbol.asyncIterator") - -#endif /* DEF */ diff --git a/vendor/quickjs/source_ng/quickjs-c-atomics.h b/vendor/quickjs/source_ng/quickjs-c-atomics.h deleted file mode 100755 index 8fc6b7203..000000000 --- a/vendor/quickjs/source_ng/quickjs-c-atomics.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * QuickJS C atomics definitions - * - * Copyright (c) 2023 Marcin Kolny - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) - // Use GCC builtins for version < 4.9 -# if((__GNUC__ << 16) + __GNUC_MINOR__ < ((4) << 16) + 9) -# define GCC_BUILTIN_ATOMICS -# endif -#endif - -#ifdef GCC_BUILTIN_ATOMICS -#define atomic_fetch_add(obj, arg) \ - __atomic_fetch_add(obj, arg, __ATOMIC_SEQ_CST) -#define atomic_compare_exchange_strong(obj, expected, desired) \ - __atomic_compare_exchange_n(obj, expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) -#define atomic_exchange(obj, desired) \ - __atomic_exchange_n (obj, desired, __ATOMIC_SEQ_CST) -#define atomic_load(obj) \ - __atomic_load_n(obj, __ATOMIC_SEQ_CST) -#define atomic_store(obj, desired) \ - __atomic_store_n(obj, desired, __ATOMIC_SEQ_CST) -#define atomic_fetch_or(obj, arg) \ - __atomic_fetch_or(obj, arg, __ATOMIC_SEQ_CST) -#define atomic_fetch_xor(obj, arg) \ - __atomic_fetch_xor(obj, arg, __ATOMIC_SEQ_CST) -#define atomic_fetch_and(obj, arg) \ - __atomic_fetch_and(obj, arg, __ATOMIC_SEQ_CST) -#define atomic_fetch_sub(obj, arg) \ - __atomic_fetch_sub(obj, arg, __ATOMIC_SEQ_CST) -#define _Atomic -#else -#include -#endif diff --git a/vendor/quickjs/source_ng/quickjs-libc.c b/vendor/quickjs/source_ng/quickjs-libc.c deleted file mode 100755 index 2c7ac7704..000000000 --- a/vendor/quickjs/source_ng/quickjs-libc.c +++ /dev/null @@ -1,4685 +0,0 @@ -/* - * QuickJS C library - * - * Copyright (c) 2017-2021 Fabrice Bellard - * Copyright (c) 2017-2021 Charlie Gordon - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include "quickjs.h" -#include -#include -#include -#include -#include -#include -#include -#include -#if !defined(_MSC_VER) -#include -#include -#endif -#include -#include -#include -#include -#if !defined(_MSC_VER) -#include -#endif -#if defined(_WIN32) -#include -#include -#include -#include -#include -#include -#include -#define popen _popen -#define pclose _pclose -#define rmdir _rmdir -#define getcwd _getcwd -#define chdir _chdir -#else -#include -#include -#if !defined(__wasi__) -#include -#include -#include -#include -#include -#endif - -#if defined(__APPLE__) -typedef sig_t sighandler_t; -#include -#include -#define environ (*_NSGetEnviron()) -#endif - -#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) -typedef sig_t sighandler_t; -extern char **environ; -#endif - -#endif /* _WIN32 */ - -#include "cutils.h" -#include "list.h" -#include "quickjs-libc.h" - -#if JS_HAVE_THREADS -#include "quickjs-c-atomics.h" -#define USE_WORKER // enable os.Worker -#endif - -#ifndef S_IFBLK -#define S_IFBLK 0 -#endif - -#ifndef S_IFIFO -#define S_IFIFO 0 -#endif - -#ifndef MAX_SAFE_INTEGER // already defined in amalgamation builds -#define MAX_SAFE_INTEGER (((int64_t) 1 << 53) - 1) -#endif - -#ifndef QJS_NATIVE_MODULE_SUFFIX -#ifdef _WIN32 -#define QJS_NATIVE_MODULE_SUFFIX ".dll" -#else -#define QJS_NATIVE_MODULE_SUFFIX ".so" -#endif -#endif - -/* TODO: - - add socket calls -*/ - -typedef struct { - struct list_head link; - int fd; - JSValue rw_func[2]; -} JSOSRWHandler; - -typedef struct { - struct list_head link; - int sig_num; - JSValue func; -} JSOSSignalHandler; - -typedef struct { - struct list_head link; - int64_t timer_id; - uint8_t repeats:1; - int64_t timeout; - int64_t delay; - JSValue func; -} JSOSTimer; - -typedef struct { - struct list_head link; - JSValue promise; - JSValue reason; -} JSRejectedPromiseEntry; - -#ifdef USE_WORKER - -typedef struct { - struct list_head link; - uint8_t *data; - size_t data_len; - /* list of SharedArrayBuffers, necessary to free the message */ - uint8_t **sab_tab; - size_t sab_tab_len; -} JSWorkerMessage; - -typedef struct JSWaker { -#ifdef _WIN32 - HANDLE handle; -#else - int read_fd; - int write_fd; -#endif -} JSWaker; - -typedef struct { - int ref_count; - js_mutex_t mutex; - struct list_head msg_queue; /* list of JSWorkerMessage.link */ - JSWaker waker; -} JSWorkerMessagePipe; - -typedef struct { - struct list_head link; - JSWorkerMessagePipe *recv_pipe; - JSValue on_message_func; -} JSWorkerMessageHandler; - -#endif // USE_WORKER - -typedef struct JSThreadState { - struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */ - struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */ - struct list_head os_timers; /* list of JSOSTimer.link */ - struct list_head port_list; /* list of JSWorkerMessageHandler.link */ - struct list_head rejected_promise_list; /* list of JSRejectedPromiseEntry.link */ - int eval_script_recurse; /* only used in the main thread */ - int64_t next_timer_id; /* for setTimeout / setInterval */ - bool can_js_os_poll; - /* not used in the main thread */ -#ifdef USE_WORKER - JSWorkerMessagePipe *recv_pipe, *send_pipe; -#else - void *recv_pipe; -#endif // USE_WORKER - JSClassID std_file_class_id; - JSClassID worker_class_id; -} JSThreadState; - -static uint64_t os_pending_signals; - -static void *js_std_dbuf_realloc(void *opaque, void *ptr, size_t size) -{ - JSRuntime *rt = opaque; - return js_realloc_rt(rt, ptr, size); -} - -static void js_std_dbuf_init(JSContext *ctx, DynBuf *s) -{ - dbuf_init2(s, JS_GetRuntime(ctx), js_std_dbuf_realloc); -} - -static bool my_isdigit(int c) -{ - return (c >= '0' && c <= '9'); -} - -static JSThreadState *js_get_thread_state(JSRuntime *rt) -{ - return (JSThreadState *)js_std_cmd(/*GetOpaque*/0, rt); -} - -static void js_set_thread_state(JSRuntime *rt, JSThreadState *ts) -{ - js_std_cmd(/*SetOpaque*/1, rt, ts); -} - -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" -#endif // __GNUC__ -static JSValue js_printf_internal(JSContext *ctx, - int argc, JSValueConst *argv, FILE *fp) -{ - char fmtbuf[32]; - uint8_t cbuf[UTF8_CHAR_LEN_MAX+1]; - JSValue res; - DynBuf dbuf; - const char *fmt_str = NULL; - const uint8_t *fmt, *fmt_end; - const uint8_t *p; - char *q; - int i, c, len, mod; - size_t fmt_len; - int32_t int32_arg; - int64_t int64_arg; - double double_arg; - const char *string_arg; - - js_std_dbuf_init(ctx, &dbuf); - - if (argc > 0) { - fmt_str = JS_ToCStringLen(ctx, &fmt_len, argv[0]); - if (!fmt_str) - goto fail; - - i = 1; - fmt = (const uint8_t *)fmt_str; - fmt_end = fmt + fmt_len; - while (fmt < fmt_end) { - for (p = fmt; fmt < fmt_end && *fmt != '%'; fmt++) - continue; - dbuf_put(&dbuf, p, fmt - p); - if (fmt >= fmt_end) - break; - q = fmtbuf; - *q++ = *fmt++; /* copy '%' */ - - /* flags */ - for(;;) { - c = *fmt; - if (c == '0' || c == '#' || c == '+' || c == '-' || c == ' ' || - c == '\'') { - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = c; - fmt++; - } else { - break; - } - } - /* width */ - if (*fmt == '*') { - if (i >= argc) - goto missing; - if (JS_ToInt32(ctx, &int32_arg, argv[i++])) - goto fail; - q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); - fmt++; - } else { - while (my_isdigit(*fmt)) { - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = *fmt++; - } - } - if (*fmt == '.') { - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = *fmt++; - if (*fmt == '*') { - if (i >= argc) - goto missing; - if (JS_ToInt32(ctx, &int32_arg, argv[i++])) - goto fail; - q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); - fmt++; - } else { - while (my_isdigit(*fmt)) { - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = *fmt++; - } - } - } - - /* we only support the "l" modifier for 64 bit numbers */ - mod = ' '; - if (*fmt == 'l') { - mod = *fmt++; - } - - /* type */ - c = *fmt++; - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = c; - *q = '\0'; - - switch (c) { - case 'c': - if (i >= argc) - goto missing; - if (JS_IsString(argv[i])) { - // TODO(chqrlie) need an API to wrap charCodeAt and codePointAt */ - string_arg = JS_ToCString(ctx, argv[i++]); - if (!string_arg) - goto fail; - int32_arg = utf8_decode((const uint8_t *)string_arg, &p); - JS_FreeCString(ctx, string_arg); - } else { - if (JS_ToInt32(ctx, &int32_arg, argv[i++])) - goto fail; - } - // XXX: throw an exception? - if ((unsigned)int32_arg > 0x10FFFF) - int32_arg = 0xFFFD; - /* ignore conversion flags, width and precision */ - len = utf8_encode(cbuf, int32_arg); - dbuf_put(&dbuf, cbuf, len); - break; - - case 'd': - case 'i': - case 'o': - case 'u': - case 'x': - case 'X': - if (i >= argc) - goto missing; - if (JS_ToInt64Ext(ctx, &int64_arg, argv[i++])) - goto fail; - if (mod == 'l') { - /* 64 bit number */ -#if defined(_WIN32) - if (q >= fmtbuf + sizeof(fmtbuf) - 3) - goto invalid; - q[2] = q[-1]; - q[-1] = 'I'; - q[0] = '6'; - q[1] = '4'; - q[3] = '\0'; - dbuf_printf(&dbuf, fmtbuf, (int64_t)int64_arg); -#else - if (q >= fmtbuf + sizeof(fmtbuf) - 2) - goto invalid; - q[1] = q[-1]; - q[-1] = q[0] = 'l'; - q[2] = '\0'; - dbuf_printf(&dbuf, fmtbuf, (long long)int64_arg); -#endif - } else { - dbuf_printf(&dbuf, fmtbuf, (int)int64_arg); - } - break; - - case 's': - if (i >= argc) - goto missing; - /* XXX: handle strings containing null characters */ - string_arg = JS_ToCString(ctx, argv[i++]); - if (!string_arg) - goto fail; - dbuf_printf(&dbuf, fmtbuf, string_arg); - JS_FreeCString(ctx, string_arg); - break; - - case 'e': - case 'f': - case 'g': - case 'a': - case 'E': - case 'F': - case 'G': - case 'A': - if (i >= argc) - goto missing; - if (JS_ToFloat64(ctx, &double_arg, argv[i++])) - goto fail; - dbuf_printf(&dbuf, fmtbuf, double_arg); - break; - - case '%': - dbuf_putc(&dbuf, '%'); - break; - - default: - /* XXX: should support an extension mechanism */ - invalid: - JS_ThrowTypeError(ctx, "invalid conversion specifier in format string"); - goto fail; - missing: - JS_ThrowReferenceError(ctx, "missing argument for conversion specifier"); - goto fail; - } - } - JS_FreeCString(ctx, fmt_str); - } - if (dbuf.error) { - res = JS_ThrowOutOfMemory(ctx); - } else { - if (fp) { - len = fwrite(dbuf.buf, 1, dbuf.size, fp); - res = JS_NewInt32(ctx, len); - } else { - res = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size); - } - } - dbuf_free(&dbuf); - return res; - -fail: - JS_FreeCString(ctx, fmt_str); - dbuf_free(&dbuf); - return JS_EXCEPTION; -} -#ifdef __GNUC__ -#pragma GCC diagnostic pop // ignored "-Wformat-nonliteral" -#endif // __GNUC__ - -uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename) -{ - FILE *f; - size_t n, len; - uint8_t *p, *buf, tmp[8192]; - - f = fopen(filename, "rb"); - if (!f) - return NULL; - buf = NULL; - len = 0; - do { - n = fread(tmp, 1, sizeof(tmp), f); - if (ctx) { - p = js_realloc(ctx, buf, len + n + 1); - } else { - p = realloc(buf, len + n + 1); - } - if (!p) { - if (ctx) { - js_free(ctx, buf); - } else { - free(buf); - } - fclose(f); - return NULL; - } - memcpy(&p[len], tmp, n); - buf = p; - len += n; - buf[len] = '\0'; - } while (n == sizeof(tmp)); - fclose(f); - *pbuf_len = len; - return buf; -} - -/* load and evaluate a file */ -static JSValue js_loadScript(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - uint8_t *buf; - const char *filename; - JSValue ret; - size_t buf_len; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - return JS_EXCEPTION; - buf = js_load_file(ctx, &buf_len, filename); - if (!buf) { - JS_ThrowReferenceError(ctx, "could not load '%s'", filename); - JS_FreeCString(ctx, filename); - return JS_EXCEPTION; - } - ret = JS_Eval(ctx, (char *)buf, buf_len, filename, - JS_EVAL_TYPE_GLOBAL); - js_free(ctx, buf); - JS_FreeCString(ctx, filename); - return ret; -} - -static int get_bool_option(JSContext *ctx, bool *pbool, - JSValueConst obj, - const char *option) -{ - JSValue val; - val = JS_GetPropertyStr(ctx, obj, option); - if (JS_IsException(val)) - return -1; - if (!JS_IsUndefined(val)) { - *pbool = JS_ToBool(ctx, val); - } - JS_FreeValue(ctx, val); - return 0; -} - -static void free_buf(JSRuntime *rt, void *opaque, void *ptr) { - js_free_rt(rt, ptr); -} - -/* load a file as a UTF-8 encoded string or Uint8Array */ -static JSValue js_std_loadFile(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - uint8_t *buf; - const char *filename; - JSValueConst options_obj; - JSValue ret; - size_t buf_len; - bool binary = false; - - if (argc >= 2) { - options_obj = argv[1]; - if (get_bool_option(ctx, &binary, options_obj, - "binary")) - return JS_EXCEPTION; - } - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - return JS_EXCEPTION; - buf = js_load_file(ctx, &buf_len, filename); - JS_FreeCString(ctx, filename); - if (!buf) - return JS_NULL; - if (binary) { - ret = JS_NewUint8Array(ctx, buf, buf_len, free_buf, NULL, false); - } else { - ret = JS_NewStringLen(ctx, (char *)buf, buf_len); - js_free(ctx, buf); - } - - return ret; -} - -static JSValue js_std_writeFile(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *filename; - const char *mode; - const void *buf; - size_t len, n; - JSValueConst data; - JSValue val, ret, unref; - bool release; - FILE *fp; - - ret = JS_EXCEPTION; - len = 0; - buf = ""; - mode = "w"; - data = argv[1]; - unref = JS_UNDEFINED; - release = false; - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - return JS_EXCEPTION; - if (JS_IsObject(data)) { - val = JS_GetPropertyStr(ctx, data, "buffer"); - if (JS_IsException(val)) - goto exception; - if (JS_IsArrayBuffer(val)) { - data = unref = val; - } else { - JS_FreeValue(ctx, val); - } - } - if (JS_IsArrayBuffer(data)) { - buf = JS_GetArrayBuffer(ctx, &len, data); - mode = "wb"; - } else if (!JS_IsUndefined(data)) { - buf = JS_ToCStringLen(ctx, &len, data); - release = true; - } - if (!buf) - goto exception; - fp = fopen(filename, mode); - if (!fp) { - JS_ThrowPlainError(ctx, "error opening %s for writing", filename); - goto exception; - } - n = fwrite(buf, len, 1, fp); - fclose(fp); - if (n != 1) { - JS_ThrowPlainError(ctx, "error writing to %s", filename); - goto exception; - } - ret = JS_UNDEFINED; -exception: - JS_FreeCString(ctx, filename); - if (release) - JS_FreeCString(ctx, buf); - JS_FreeValue(ctx, unref); - return ret; -} - -typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx, - const char *module_name); - - -#if defined(_WIN32) -static JSModuleDef *js_module_loader_so(JSContext *ctx, - const char *module_name) -{ - JSModuleDef *m; - HINSTANCE hd; - JSInitModuleFunc *init; - char *filename = NULL; - size_t len = strlen(module_name); - bool is_absolute = len > 2 && ((module_name[0] >= 'A' && module_name[0] <= 'Z') || - (module_name[0] >= 'a' && module_name[0] <= 'z')) && module_name[1] == ':'; - bool is_relative = len > 2 && module_name[0] == '.' && (module_name[1] == '/' || module_name[1] == '\\'); - if (is_absolute || is_relative) { - filename = (char *)module_name; - } else { - filename = js_malloc(ctx, len + 2 + 1); - if (!filename) - return NULL; - strcpy(filename, "./"); - strcpy(filename + 2, module_name); - } - hd = LoadLibraryA(filename); - if (filename != module_name) - js_free(ctx, filename); - if (hd == NULL) { - JS_ThrowReferenceError(ctx, "js_load_module '%s' error: %lu", - module_name, GetLastError()); - goto fail; - } - init = (JSInitModuleFunc *)(uintptr_t)GetProcAddress(hd, "js_init_module"); - if (!init) { - JS_ThrowReferenceError(ctx, "js_init_module '%s' not found: %lu", - module_name, GetLastError()); - goto fail; - } - m = init(ctx, module_name); - if (!m) { - JS_ThrowReferenceError(ctx, "js_call_module '%s' initialization error", - module_name); - fail: - if (hd != NULL) - FreeLibrary(hd); - return NULL; - } - return m; -} -#elif defined(__wasi__) -static JSModuleDef *js_module_loader_so(JSContext *ctx, - const char *module_name) -{ - JS_ThrowReferenceError(ctx, "shared library modules are not supported yet"); - return NULL; -} -#else -static JSModuleDef *js_module_loader_so(JSContext *ctx, - const char *module_name) -{ - JSModuleDef *m; - void *hd; - JSInitModuleFunc *init; - char *filename; - - if (!strchr(module_name, '/')) { - /* must add a '/' so that the DLL is not searched in the - system library paths */ - filename = js_malloc(ctx, strlen(module_name) + 2 + 1); - if (!filename) - return NULL; - strcpy(filename, "./"); - strcpy(filename + 2, module_name); - } else { - filename = (char *)module_name; - } - - /* C module */ - hd = dlopen(filename, RTLD_NOW | RTLD_LOCAL); - if (filename != module_name) - js_free(ctx, filename); - if (!hd) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s' as shared library: %s", - module_name, dlerror()); - goto fail; - } - - *(void **) (&init) = dlsym(hd, "js_init_module"); - if (!init) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s': js_init_module not found", - module_name); - goto fail; - } - - m = init(ctx, module_name); - if (!m) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s': initialization error", - module_name); - fail: - if (hd) - dlclose(hd); - return NULL; - } - return m; -} -#endif /* !_WIN32 */ - -int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, - bool use_realpath, bool is_main) -{ - JSModuleDef *m; - char buf[JS__PATH_MAX + 16]; - JSValue meta_obj; - JSAtom module_name_atom; - const char *module_name; - - assert(JS_VALUE_GET_TAG(func_val) == JS_TAG_MODULE); - m = JS_VALUE_GET_PTR(func_val); - - module_name_atom = JS_GetModuleName(ctx, m); - module_name = JS_AtomToCString(ctx, module_name_atom); - JS_FreeAtom(ctx, module_name_atom); - if (!module_name) - return -1; - if (!strchr(module_name, ':')) { - strcpy(buf, "file://"); -#if !defined(_WIN32) && !defined(__wasi__) - /* realpath() cannot be used with modules compiled with qjsc - because the corresponding module source code is not - necessarily present */ - if (use_realpath) { - char *res = realpath(module_name, buf + strlen(buf)); - if (!res) { - JS_ThrowTypeError(ctx, "realpath failure"); - JS_FreeCString(ctx, module_name); - return -1; - } - } else -#endif - { - js__pstrcat(buf, sizeof(buf), module_name); - } - } else { - js__pstrcpy(buf, sizeof(buf), module_name); - } - JS_FreeCString(ctx, module_name); - - meta_obj = JS_GetImportMeta(ctx, m); - if (JS_IsException(meta_obj)) - return -1; - JS_DefinePropertyValueStr(ctx, meta_obj, "url", - JS_NewString(ctx, buf), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, meta_obj, "main", - JS_NewBool(ctx, is_main), - JS_PROP_C_W_E); - JS_FreeValue(ctx, meta_obj); - return 0; -} - -JSModuleDef *js_module_loader(JSContext *ctx, - const char *module_name, void *opaque) -{ - JSModuleDef *m; - - if (js__has_suffix(module_name, QJS_NATIVE_MODULE_SUFFIX)) { - m = js_module_loader_so(ctx, module_name); - } else { - size_t buf_len; - uint8_t *buf; - JSValue func_val; - - buf = js_load_file(ctx, &buf_len, module_name); - if (!buf) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s'", - module_name); - return NULL; - } - - /* compile the module */ - func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, - JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); - js_free(ctx, buf); - if (JS_IsException(func_val)) - return NULL; - if (js_module_set_import_meta(ctx, func_val, true, false) < 0) { - JS_FreeValue(ctx, func_val); - return NULL; - } - /* the module is already referenced, so we must free it */ - m = JS_VALUE_GET_PTR(func_val); - JS_FreeValue(ctx, func_val); - } - return m; -} - -static JSValue js_std_exit(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int status; - if (JS_ToInt32(ctx, &status, argv[0])) - status = -1; - exit(status); - return JS_UNDEFINED; -} - -static JSValue js_std_getenv(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *name, *str; - name = JS_ToCString(ctx, argv[0]); - if (!name) - return JS_EXCEPTION; - str = getenv(name); - JS_FreeCString(ctx, name); - if (!str) - return JS_UNDEFINED; - else - return JS_NewString(ctx, str); -} - -#if defined(_WIN32) -static void setenv(const char *name, const char *value, int overwrite) -{ - char *str; - size_t name_len, value_len; - name_len = strlen(name); - value_len = strlen(value); - str = malloc(name_len + 1 + value_len + 1); - memcpy(str, name, name_len); - str[name_len] = '='; - memcpy(str + name_len + 1, value, value_len); - str[name_len + 1 + value_len] = '\0'; - _putenv(str); - free(str); -} - -static void unsetenv(const char *name) -{ - setenv(name, "", true); -} -#endif /* _WIN32 */ - -static JSValue js_std_setenv(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *name, *value; - name = JS_ToCString(ctx, argv[0]); - if (!name) - return JS_EXCEPTION; - value = JS_ToCString(ctx, argv[1]); - if (!value) { - JS_FreeCString(ctx, name); - return JS_EXCEPTION; - } - setenv(name, value, true); - JS_FreeCString(ctx, name); - JS_FreeCString(ctx, value); - return JS_UNDEFINED; -} - -static JSValue js_std_unsetenv(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *name; - name = JS_ToCString(ctx, argv[0]); - if (!name) - return JS_EXCEPTION; - unsetenv(name); - JS_FreeCString(ctx, name); - return JS_UNDEFINED; -} - -/* return an object containing the list of the available environment - variables. */ -static JSValue js_std_getenviron(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - char **envp; - const char *name, *p, *value; - JSValue obj; - uint32_t idx; - size_t name_len; - JSAtom atom; - int ret; - - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) - return JS_EXCEPTION; - envp = environ; - for(idx = 0; envp[idx] != NULL; idx++) { - name = envp[idx]; - p = strchr(name, '='); - name_len = p - name; - if (!p) - continue; - value = p + 1; - atom = JS_NewAtomLen(ctx, name, name_len); - if (atom == JS_ATOM_NULL) - goto fail; - ret = JS_DefinePropertyValue(ctx, obj, atom, JS_NewString(ctx, value), - JS_PROP_C_W_E); - JS_FreeAtom(ctx, atom); - if (ret < 0) - goto fail; - } - return obj; - fail: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -static JSValue js_std_gc(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JS_RunGC(JS_GetRuntime(ctx)); - return JS_UNDEFINED; -} - -static int interrupt_handler(JSRuntime *rt, void *opaque) -{ - return (os_pending_signals >> SIGINT) & 1; -} - -static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - const char *str = NULL; - size_t len; - JSValue ret, obj; - JSValueConst options_obj, arg; - bool backtrace_barrier = false; - bool eval_function = false; - bool eval_module = false; - bool compile_only = false; - bool compile_module = false; - bool is_async = false; - int flags; - - if (argc >= 2) { - options_obj = argv[1]; - if (get_bool_option(ctx, &backtrace_barrier, options_obj, - "backtrace_barrier")) - return JS_EXCEPTION; - if (get_bool_option(ctx, &eval_function, options_obj, - "eval_function")) - return JS_EXCEPTION; - if (get_bool_option(ctx, &eval_module, options_obj, - "eval_module")) - return JS_EXCEPTION; - if (get_bool_option(ctx, &compile_only, options_obj, - "compile_only")) - return JS_EXCEPTION; - if (get_bool_option(ctx, &compile_module, options_obj, - "compile_module")) - return JS_EXCEPTION; - if (get_bool_option(ctx, &is_async, options_obj, - "async")) - return JS_EXCEPTION; - } - - if (eval_module) { - arg = argv[0]; - if (JS_VALUE_GET_TAG(arg) != JS_TAG_MODULE) - return JS_ThrowTypeError(ctx, "not a module"); - - if (JS_ResolveModule(ctx, arg) < 0) - return JS_EXCEPTION; - - if (js_module_set_import_meta(ctx, arg, false, false) < 0) - return JS_EXCEPTION; - - return JS_EvalFunction(ctx, JS_DupValue(ctx, arg)); - } - - if (!eval_function) { - str = JS_ToCStringLen(ctx, &len, argv[0]); - if (!str) - return JS_EXCEPTION; - } - if (!ts->recv_pipe && ++ts->eval_script_recurse == 1) { - /* install the interrupt handler */ - JS_SetInterruptHandler(JS_GetRuntime(ctx), interrupt_handler, NULL); - } - flags = compile_module ? JS_EVAL_TYPE_MODULE : JS_EVAL_TYPE_GLOBAL; - if (backtrace_barrier) - flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER; - if (compile_only) - flags |= JS_EVAL_FLAG_COMPILE_ONLY; - if (is_async) - flags |= JS_EVAL_FLAG_ASYNC; - if (eval_function) { - obj = JS_DupValue(ctx, argv[0]); - ret = JS_EvalFunction(ctx, obj); // takes ownership of |obj| - } else { - ret = JS_Eval(ctx, str, len, "", flags); - } - JS_FreeCString(ctx, str); - if (!ts->recv_pipe && --ts->eval_script_recurse == 0) { - /* remove the interrupt handler */ - JS_SetInterruptHandler(JS_GetRuntime(ctx), NULL, NULL); - os_pending_signals &= ~((uint64_t)1 << SIGINT); - /* convert the uncatchable "interrupted" error into a normal error - so that it can be caught by the REPL */ - if (JS_IsException(ret)) - JS_ResetUncatchableError(ctx); - } - return ret; -} - -typedef struct { - FILE *f; - bool is_popen; -} JSSTDFile; - -static bool is_stdio(FILE *f) -{ - return f == stdin || f == stdout || f == stderr; -} - -static void js_std_file_finalizer(JSRuntime *rt, JSValueConst val) -{ - JSThreadState *ts = js_get_thread_state(rt); - JSSTDFile *s = JS_GetOpaque(val, ts->std_file_class_id); - if (s) { - if (s->f && !is_stdio(s->f)) { -#if !defined(__wasi__) - if (s->is_popen) - pclose(s->f); - else -#endif - fclose(s->f); - } - js_free_rt(rt, s); - } -} - -static ssize_t js_get_errno(ssize_t ret) -{ - if (ret == -1) - ret = -errno; - return ret; -} - -static JSValue js_std_strerror(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int err; - if (JS_ToInt32(ctx, &err, argv[0])) - return JS_EXCEPTION; - return JS_NewString(ctx, strerror(err)); -} - -static JSValue js_new_std_file(JSContext *ctx, FILE *f, bool is_popen) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSSTDFile *s; - JSValue obj; - obj = JS_NewObjectClass(ctx, ts->std_file_class_id); - if (JS_IsException(obj)) - return obj; - s = js_mallocz(ctx, sizeof(*s)); - if (!s) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - s->is_popen = is_popen; - s->f = f; - JS_SetOpaque(obj, s); - return obj; -} - -static void js_set_error_object(JSContext *ctx, JSValueConst obj, int err) -{ - if (!JS_IsUndefined(obj)) { - JS_SetPropertyStr(ctx, obj, "errno", JS_NewInt32(ctx, err)); - } -} - -static JSValue js_std_open(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *filename, *mode = NULL; - FILE *f; - int err; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - goto fail; - mode = JS_ToCString(ctx, argv[1]); - if (!mode) - goto fail; - if (mode[strspn(mode, "rwa+bx")] != '\0') { - JS_ThrowTypeError(ctx, "invalid file mode"); - goto fail; - } - - f = fopen(filename, mode); - if (!f) - err = errno; - else - err = 0; - if (argc >= 3) - js_set_error_object(ctx, argv[2], err); - JS_FreeCString(ctx, filename); - JS_FreeCString(ctx, mode); - if (!f) - return JS_NULL; - return js_new_std_file(ctx, f, false); - fail: - JS_FreeCString(ctx, filename); - JS_FreeCString(ctx, mode); - return JS_EXCEPTION; -} - -#if !defined(__wasi__) -static JSValue js_std_popen(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *filename, *mode = NULL; - FILE *f; - int err; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - goto fail; - mode = JS_ToCString(ctx, argv[1]); - if (!mode) - goto fail; - if (strcmp(mode, "r") && strcmp(mode, "w")) { - JS_ThrowTypeError(ctx, "invalid file mode"); - goto fail; - } - - f = popen(filename, mode); - if (!f) - err = errno; - else - err = 0; - if (argc >= 3) - js_set_error_object(ctx, argv[2], err); - JS_FreeCString(ctx, filename); - JS_FreeCString(ctx, mode); - if (!f) - return JS_NULL; - return js_new_std_file(ctx, f, true); - fail: - JS_FreeCString(ctx, filename); - JS_FreeCString(ctx, mode); - return JS_EXCEPTION; -} -#endif // !defined(__wasi__) - -static JSValue js_std_fdopen(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *mode; - FILE *f; - int fd, err; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - mode = JS_ToCString(ctx, argv[1]); - if (!mode) - goto fail; - if (mode[strspn(mode, "rwa+")] != '\0') { - JS_ThrowTypeError(ctx, "invalid file mode"); - goto fail; - } - - f = fdopen(fd, mode); - if (!f) - err = errno; - else - err = 0; - if (argc >= 3) - js_set_error_object(ctx, argv[2], err); - JS_FreeCString(ctx, mode); - if (!f) - return JS_NULL; - return js_new_std_file(ctx, f, false); - fail: - JS_FreeCString(ctx, mode); - return JS_EXCEPTION; -} - -#if !defined(__wasi__) -static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f; - f = tmpfile(); - if (argc >= 1) - js_set_error_object(ctx, argv[0], f ? 0 : errno); - if (!f) - return JS_NULL; - return js_new_std_file(ctx, f, false); -} -#endif - -static JSValue js_std_sprintf(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return js_printf_internal(ctx, argc, argv, NULL); -} - -static JSValue js_std_printf(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return js_printf_internal(ctx, argc, argv, stdout); -} - -static FILE *js_std_file_get(JSContext *ctx, JSValueConst obj) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSSTDFile *s = JS_GetOpaque2(ctx, obj, ts->std_file_class_id); - if (!s) - return NULL; - if (!s->f) { - JS_ThrowTypeError(ctx, "invalid file handle"); - return NULL; - } - return s->f; -} - -static JSValue js_std_file_puts(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - FILE *f; - int i; - const char *str; - size_t len; - - if (magic == 0) { - f = stdout; - } else { - f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - } - - for(i = 0; i < argc; i++) { - str = JS_ToCStringLen(ctx, &len, argv[i]); - if (!str) - return JS_EXCEPTION; - fwrite(str, 1, len, f); - JS_FreeCString(ctx, str); - } - return JS_UNDEFINED; -} - -static JSValue js_std_file_close(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSSTDFile *s = JS_GetOpaque2(ctx, this_val, ts->std_file_class_id); - int err; - if (!s) - return JS_EXCEPTION; - if (!s->f) - return JS_ThrowTypeError(ctx, "invalid file handle"); - if (is_stdio(s->f)) - return JS_ThrowTypeError(ctx, "cannot close stdio"); -#if !defined(__wasi__) - if (s->is_popen) - err = js_get_errno(pclose(s->f)); - else -#endif - err = js_get_errno(fclose(s->f)); - s->f = NULL; - return JS_NewInt32(ctx, err); -} - -static JSValue js_std_file_printf(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return js_printf_internal(ctx, argc, argv, f); -} - -static JSValue js_std_file_flush(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - fflush(f); - return JS_UNDEFINED; -} - -static JSValue js_std_file_tell(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int is_bigint) -{ - FILE *f = js_std_file_get(ctx, this_val); - int64_t pos; - if (!f) - return JS_EXCEPTION; -#if defined(__linux__) || defined(__GLIBC__) - pos = ftello(f); -#else - pos = ftell(f); -#endif - if (is_bigint) - return JS_NewBigInt64(ctx, pos); - else - return JS_NewInt64(ctx, pos); -} - -static JSValue js_std_file_seek(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - int64_t pos; - int whence, ret; - if (!f) - return JS_EXCEPTION; - if (JS_ToInt64Ext(ctx, &pos, argv[0])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &whence, argv[1])) - return JS_EXCEPTION; -#if defined(__linux__) || defined(__GLIBC__) - ret = fseeko(f, pos, whence); -#else - ret = fseek(f, pos, whence); -#endif - if (ret < 0) - ret = -errno; - return JS_NewInt32(ctx, ret); -} - -static JSValue js_std_file_eof(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return JS_NewBool(ctx, feof(f)); -} - -static JSValue js_std_file_error(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return JS_NewBool(ctx, ferror(f)); -} - -static JSValue js_std_file_clearerr(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - clearerr(f); - return JS_UNDEFINED; -} - -static JSValue js_std_file_fileno(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return JS_NewInt32(ctx, fileno(f)); -} - -static JSValue js_std_file_read_write(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - FILE *f = js_std_file_get(ctx, this_val); - uint64_t pos, len; - size_t size, ret; - uint8_t *buf; - - if (!f) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &pos, argv[1])) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &len, argv[2])) - return JS_EXCEPTION; - buf = JS_GetArrayBuffer(ctx, &size, argv[0]); - if (!buf) - return JS_EXCEPTION; - if (pos + len > size) - return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); - if (magic) - ret = fwrite(buf + pos, 1, len, f); - else - ret = fread(buf + pos, 1, len, f); - return JS_NewInt64(ctx, ret); -} - -/* XXX: could use less memory and go faster */ -static JSValue js_std_file_getline(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - int c; - DynBuf dbuf; - JSValue obj; - - if (!f) - return JS_EXCEPTION; - - js_std_dbuf_init(ctx, &dbuf); - for(;;) { - c = fgetc(f); - if (c == EOF) { - if (dbuf.size == 0) { - /* EOF */ - dbuf_free(&dbuf); - return JS_NULL; - } else { - break; - } - } - if (c == '\n') - break; - if (dbuf_putc(&dbuf, c)) { - dbuf_free(&dbuf); - return JS_ThrowOutOfMemory(ctx); - } - } - obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); - dbuf_free(&dbuf); - return obj; -} - -/* XXX: could use less memory and go faster */ -static JSValue js_std_file_readAs(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - FILE *f = js_std_file_get(ctx, this_val); - int c; - DynBuf dbuf; - JSValue obj; - uint64_t max_size64; - size_t max_size; - JSValueConst max_size_val; - - if (!f) - return JS_EXCEPTION; - - if (argc >= 1) - max_size_val = argv[0]; - else - max_size_val = JS_UNDEFINED; - max_size = (size_t)-1; - if (!JS_IsUndefined(max_size_val)) { - if (JS_ToIndex(ctx, &max_size64, max_size_val)) - return JS_EXCEPTION; - if (max_size64 < max_size) - max_size = max_size64; - } - - js_std_dbuf_init(ctx, &dbuf); - while (max_size != 0) { - c = fgetc(f); - if (c == EOF) - break; - if (dbuf_putc(&dbuf, c)) { - dbuf_free(&dbuf); - return JS_EXCEPTION; - } - max_size--; - } - if (magic) { - obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); - } else { - obj = JS_NewArrayBufferCopy(ctx, dbuf.buf, dbuf.size); - } - dbuf_free(&dbuf); - return obj; -} - -static JSValue js_std_file_getByte(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return JS_NewInt32(ctx, fgetc(f)); -} - -static JSValue js_std_file_putByte(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - int c; - if (!f) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &c, argv[0])) - return JS_EXCEPTION; - c = fputc(c, f); - return JS_NewInt32(ctx, c); -} - -/* urlGet */ -#if !defined(__wasi__) - -#define URL_GET_PROGRAM "curl -s -i --" -#define URL_GET_BUF_SIZE 4096 - -static int http_get_header_line(FILE *f, char *buf, size_t buf_size, - DynBuf *dbuf) -{ - int c; - char *p; - - p = buf; - for(;;) { - c = fgetc(f); - if (c < 0) - return -1; - if ((p - buf) < buf_size - 1) - *p++ = c; - if (dbuf) - dbuf_putc(dbuf, c); - if (c == '\n') - break; - } - *p = '\0'; - return 0; -} - -static int http_get_status(const char *buf) -{ - const char *p = buf; - while (*p != ' ' && *p != '\0') - p++; - if (*p != ' ') - return 0; - while (*p == ' ') - p++; - return atoi(p); -} - -static JSValue js_std_urlGet(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *url; - DynBuf cmd_buf; - DynBuf data_buf_s, *data_buf = &data_buf_s; - DynBuf header_buf_s, *header_buf = &header_buf_s; - char *buf; - size_t i, len; - int status; - JSValue response = JS_UNDEFINED, ret_obj; - JSValueConst options_obj; - FILE *f; - bool binary_flag, full_flag; - - url = JS_ToCString(ctx, argv[0]); - if (!url) - return JS_EXCEPTION; - - binary_flag = false; - full_flag = false; - - if (argc >= 2) { - options_obj = argv[1]; - - if (get_bool_option(ctx, &binary_flag, options_obj, "binary")) - goto fail_obj; - - if (get_bool_option(ctx, &full_flag, options_obj, "full")) { - fail_obj: - JS_FreeCString(ctx, url); - return JS_EXCEPTION; - } - } - - js_std_dbuf_init(ctx, &cmd_buf); - dbuf_printf(&cmd_buf, "%s '", URL_GET_PROGRAM); - for(i = 0; url[i] != '\0'; i++) { - unsigned char c = url[i]; - switch (c) { - case '\'': - /* shell single quoted string does not support \' */ - dbuf_putstr(&cmd_buf, "'\\''"); - break; - case '[': case ']': case '{': case '}': case '\\': - /* prevent interpretation by curl as range or set specification */ - dbuf_putc(&cmd_buf, '\\'); - /* FALLTHROUGH */ - default: - dbuf_putc(&cmd_buf, c); - break; - } - } - JS_FreeCString(ctx, url); - dbuf_putstr(&cmd_buf, "'"); - dbuf_putc(&cmd_buf, '\0'); - if (dbuf_error(&cmd_buf)) { - dbuf_free(&cmd_buf); - return JS_EXCEPTION; - } - // printf("%s\n", (char *)cmd_buf.buf); - f = popen((char *)cmd_buf.buf, "r"); - dbuf_free(&cmd_buf); - if (!f) { - return JS_ThrowTypeError(ctx, "could not start curl"); - } - - js_std_dbuf_init(ctx, data_buf); - js_std_dbuf_init(ctx, header_buf); - - buf = js_malloc(ctx, URL_GET_BUF_SIZE); - if (!buf) - goto fail; - - /* get the HTTP status */ - if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, NULL) < 0) { - status = 0; - goto bad_header; - } - status = http_get_status(buf); - if (!full_flag && !(status >= 200 && status <= 299)) { - goto bad_header; - } - - /* wait until there is an empty line */ - for(;;) { - if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, header_buf) < 0) { - bad_header: - response = JS_NULL; - goto done; - } - if (!strcmp(buf, "\r\n")) - break; - } - if (dbuf_error(header_buf)) - goto fail; - header_buf->size -= 2; /* remove the trailing CRLF */ - - /* download the data */ - for(;;) { - len = fread(buf, 1, URL_GET_BUF_SIZE, f); - if (len == 0) - break; - dbuf_put(data_buf, (uint8_t *)buf, len); - } - if (dbuf_error(data_buf)) - goto fail; - if (binary_flag) { - response = JS_NewArrayBufferCopy(ctx, - data_buf->buf, data_buf->size); - } else { - response = JS_NewStringLen(ctx, (char *)data_buf->buf, data_buf->size); - } - if (JS_IsException(response)) - goto fail; - done: - js_free(ctx, buf); - buf = NULL; - pclose(f); - f = NULL; - dbuf_free(data_buf); - data_buf = NULL; - - if (full_flag) { - ret_obj = JS_NewObject(ctx); - if (JS_IsException(ret_obj)) - goto fail; - JS_DefinePropertyValueStr(ctx, ret_obj, "response", - response, - JS_PROP_C_W_E); - if (!JS_IsNull(response)) { - JS_DefinePropertyValueStr(ctx, ret_obj, "responseHeaders", - JS_NewStringLen(ctx, (char *)header_buf->buf, - header_buf->size), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, ret_obj, "status", - JS_NewInt32(ctx, status), - JS_PROP_C_W_E); - } - } else { - ret_obj = response; - } - dbuf_free(header_buf); - return ret_obj; - fail: - if (f) - pclose(f); - js_free(ctx, buf); - if (data_buf) - dbuf_free(data_buf); - if (header_buf) - dbuf_free(header_buf); - JS_FreeValue(ctx, response); - return JS_EXCEPTION; -} -#endif // !defined(__wasi__) - -static JSClassDef js_std_file_class = { - "FILE", - .finalizer = js_std_file_finalizer, -}; - -static const JSCFunctionListEntry js_std_error_props[] = { - /* various errno values */ -#define DEF(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) - DEF(EINVAL), - DEF(EIO), - DEF(EACCES), - DEF(EEXIST), - DEF(ENOSPC), - DEF(ENOSYS), - DEF(EBUSY), - DEF(ENOENT), - DEF(EPERM), - DEF(EPIPE), - DEF(EBADF), -#undef DEF -}; - -static const JSCFunctionListEntry js_std_funcs[] = { - JS_CFUNC_DEF("exit", 1, js_std_exit ), - JS_CFUNC_DEF("gc", 0, js_std_gc ), - JS_CFUNC_DEF("evalScript", 1, js_evalScript ), - JS_CFUNC_DEF("loadScript", 1, js_loadScript ), - JS_CFUNC_DEF("getenv", 1, js_std_getenv ), - JS_CFUNC_DEF("setenv", 1, js_std_setenv ), - JS_CFUNC_DEF("unsetenv", 1, js_std_unsetenv ), - JS_CFUNC_DEF("getenviron", 1, js_std_getenviron ), -#if !defined(__wasi__) - JS_CFUNC_DEF("urlGet", 1, js_std_urlGet ), -#endif - JS_CFUNC_DEF("loadFile", 1, js_std_loadFile ), - JS_CFUNC_DEF("writeFile", 2, js_std_writeFile ), - JS_CFUNC_DEF("strerror", 1, js_std_strerror ), - - /* FILE I/O */ - JS_CFUNC_DEF("open", 2, js_std_open ), -#if !defined(__wasi__) - JS_CFUNC_DEF("popen", 2, js_std_popen ), - JS_CFUNC_DEF("tmpfile", 0, js_std_tmpfile ), -#endif - JS_CFUNC_DEF("fdopen", 2, js_std_fdopen ), - JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 0 ), - JS_CFUNC_DEF("printf", 1, js_std_printf ), - JS_CFUNC_DEF("sprintf", 1, js_std_sprintf ), - JS_PROP_INT32_DEF("SEEK_SET", SEEK_SET, JS_PROP_CONFIGURABLE ), - JS_PROP_INT32_DEF("SEEK_CUR", SEEK_CUR, JS_PROP_CONFIGURABLE ), - JS_PROP_INT32_DEF("SEEK_END", SEEK_END, JS_PROP_CONFIGURABLE ), - JS_OBJECT_DEF("Error", js_std_error_props, countof(js_std_error_props), JS_PROP_CONFIGURABLE), -}; - -static const JSCFunctionListEntry js_std_file_proto_funcs[] = { - JS_CFUNC_DEF("close", 0, js_std_file_close ), - JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 1 ), - JS_CFUNC_DEF("printf", 1, js_std_file_printf ), - JS_CFUNC_DEF("flush", 0, js_std_file_flush ), - JS_CFUNC_MAGIC_DEF("tell", 0, js_std_file_tell, 0 ), - JS_CFUNC_MAGIC_DEF("tello", 0, js_std_file_tell, 1 ), - JS_CFUNC_DEF("seek", 2, js_std_file_seek ), - JS_CFUNC_DEF("eof", 0, js_std_file_eof ), - JS_CFUNC_DEF("fileno", 0, js_std_file_fileno ), - JS_CFUNC_DEF("error", 0, js_std_file_error ), - JS_CFUNC_DEF("clearerr", 0, js_std_file_clearerr ), - JS_CFUNC_MAGIC_DEF("read", 3, js_std_file_read_write, 0 ), - JS_CFUNC_MAGIC_DEF("write", 3, js_std_file_read_write, 1 ), - JS_CFUNC_DEF("getline", 0, js_std_file_getline ), - JS_CFUNC_MAGIC_DEF("readAsArrayBuffer", 0, js_std_file_readAs, 0 ), - JS_CFUNC_MAGIC_DEF("readAsString", 0, js_std_file_readAs, 1 ), - JS_CFUNC_DEF("getByte", 0, js_std_file_getByte ), - JS_CFUNC_DEF("putByte", 1, js_std_file_putByte ), - /* setvbuf, ... */ -}; - -static int js_std_init(JSContext *ctx, JSModuleDef *m) -{ - JSValue proto; - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - - /* FILE class */ - /* the class ID is created once */ - JS_NewClassID(rt, &ts->std_file_class_id); - /* the class is created once per runtime */ - JS_NewClass(rt, ts->std_file_class_id, &js_std_file_class); - proto = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, proto, js_std_file_proto_funcs, - countof(js_std_file_proto_funcs)); - JS_SetClassProto(ctx, ts->std_file_class_id, proto); - - JS_SetModuleExportList(ctx, m, js_std_funcs, - countof(js_std_funcs)); - JS_SetModuleExport(ctx, m, "in", js_new_std_file(ctx, stdin, false)); - JS_SetModuleExport(ctx, m, "out", js_new_std_file(ctx, stdout, false)); - JS_SetModuleExport(ctx, m, "err", js_new_std_file(ctx, stderr, false)); - return 0; -} - -JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name) -{ - JSModuleDef *m; - m = JS_NewCModule(ctx, module_name, js_std_init); - if (!m) - return NULL; - JS_AddModuleExportList(ctx, m, js_std_funcs, countof(js_std_funcs)); - JS_AddModuleExport(ctx, m, "in"); - JS_AddModuleExport(ctx, m, "out"); - JS_AddModuleExport(ctx, m, "err"); - return m; -} - -/**********************************************************/ -/* 'os' object */ - -static JSValue js_os_open(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *filename; - int flags, mode, ret; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &flags, argv[1])) - goto fail; - if (argc >= 3 && !JS_IsUndefined(argv[2])) { - if (JS_ToInt32(ctx, &mode, argv[2])) { - fail: - JS_FreeCString(ctx, filename); - return JS_EXCEPTION; - } - } else { - mode = 0666; - } -#if defined(_WIN32) - /* force binary mode by default */ - if (!(flags & O_TEXT)) - flags |= O_BINARY; -#endif - ret = js_get_errno(open(filename, flags, mode)); - JS_FreeCString(ctx, filename); - return JS_NewInt32(ctx, ret); -} - -static JSValue js_os_close(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd, ret; - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - ret = js_get_errno(close(fd)); - return JS_NewInt32(ctx, ret); -} - -static JSValue js_os_seek(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd, whence; - int64_t pos, ret; - bool is_bigint; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - is_bigint = JS_IsBigInt(argv[1]); - if (JS_ToInt64Ext(ctx, &pos, argv[1])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &whence, argv[2])) - return JS_EXCEPTION; - ret = lseek(fd, pos, whence); - if (ret == -1) - ret = -errno; - if (is_bigint) - return JS_NewBigInt64(ctx, ret); - else - return JS_NewInt64(ctx, ret); -} - -static JSValue js_os_read_write(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - int fd; - uint64_t pos, len; - size_t size; - ssize_t ret; - uint8_t *buf; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &pos, argv[2])) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &len, argv[3])) - return JS_EXCEPTION; - buf = JS_GetArrayBuffer(ctx, &size, argv[1]); - if (!buf) - return JS_EXCEPTION; - if (pos + len > size) - return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); - if (magic) - ret = js_get_errno(write(fd, buf + pos, len)); - else - ret = js_get_errno(read(fd, buf + pos, len)); - return JS_NewInt64(ctx, ret); -} - -static JSValue js_os_isatty(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd; - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - return JS_NewBool(ctx, (isatty(fd) != 0)); -} - -#if defined(_WIN32) -static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd; - HANDLE handle; - CONSOLE_SCREEN_BUFFER_INFO info; - JSValue obj; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - handle = (HANDLE)_get_osfhandle(fd); - - if (!GetConsoleScreenBufferInfo(handle, &info)) - return JS_NULL; - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return obj; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, info.dwSize.X), JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, info.dwSize.Y), JS_PROP_C_W_E); - return obj; -} - -/* Windows 10 built-in VT100 emulation */ -#define __ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 -#define __ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 - -static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd; - HANDLE handle; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - handle = (HANDLE)_get_osfhandle(fd); - SetConsoleMode(handle, ENABLE_WINDOW_INPUT | __ENABLE_VIRTUAL_TERMINAL_INPUT); - _setmode(fd, _O_BINARY); - if (fd == 0) { - handle = (HANDLE)_get_osfhandle(1); /* corresponding output */ - SetConsoleMode(handle, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | __ENABLE_VIRTUAL_TERMINAL_PROCESSING); - } - return JS_UNDEFINED; -} -#elif !defined(__wasi__) -static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd; - struct winsize ws; - JSValue obj; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - if (ioctl(fd, TIOCGWINSZ, &ws) == 0 && - ws.ws_col >= 4 && ws.ws_row >= 4) { - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return obj; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ws.ws_col), JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, ws.ws_row), JS_PROP_C_W_E); - return obj; - } else { - return JS_NULL; - } -} - -static struct termios oldtty; - -static void term_exit(void) -{ - tcsetattr(0, TCSANOW, &oldtty); -} - -/* XXX: should add a way to go back to normal mode */ -static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - struct termios tty; - int fd; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - - memset(&tty, 0, sizeof(tty)); - tcgetattr(fd, &tty); - oldtty = tty; - - tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP - |INLCR|IGNCR|ICRNL|IXON); - tty.c_oflag |= OPOST; - tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); - tty.c_cflag &= ~(CSIZE|PARENB); - tty.c_cflag |= CS8; - tty.c_cc[VMIN] = 1; - tty.c_cc[VTIME] = 0; - - tcsetattr(fd, TCSANOW, &tty); - - atexit(term_exit); - return JS_UNDEFINED; -} - -#endif /* !_WIN32 && !__wasi__ */ - -static JSValue js_os_remove(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *filename; - int ret; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - return JS_EXCEPTION; -#if defined(_WIN32) - { - struct stat st; - if (stat(filename, &st) == 0 && (st.st_mode & _S_IFDIR)) { - ret = rmdir(filename); - } else { - ret = unlink(filename); - } - } -#else - ret = remove(filename); -#endif - ret = js_get_errno(ret); - JS_FreeCString(ctx, filename); - return JS_NewInt32(ctx, ret); -} - -static JSValue js_os_rename(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *oldpath, *newpath; - int ret; - - oldpath = JS_ToCString(ctx, argv[0]); - if (!oldpath) - return JS_EXCEPTION; - newpath = JS_ToCString(ctx, argv[1]); - if (!newpath) { - JS_FreeCString(ctx, oldpath); - return JS_EXCEPTION; - } - ret = js_get_errno(rename(oldpath, newpath)); - JS_FreeCString(ctx, oldpath); - JS_FreeCString(ctx, newpath); - return JS_NewInt32(ctx, ret); -} - -static bool is_main_thread(JSRuntime *rt) -{ - JSThreadState *ts = js_get_thread_state(rt); - return !ts->recv_pipe; -} - -static JSOSRWHandler *find_rh(JSThreadState *ts, int fd) -{ - JSOSRWHandler *rh; - struct list_head *el; - - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - if (rh->fd == fd) - return rh; - } - return NULL; -} - -static void free_rw_handler(JSRuntime *rt, JSOSRWHandler *rh) -{ - int i; - list_del(&rh->link); - for(i = 0; i < 2; i++) { - JS_FreeValueRT(rt, rh->rw_func[i]); - } - js_free_rt(rt, rh); -} - -static JSValue js_os_setReadHandler(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSOSRWHandler *rh; - int fd; - JSValueConst func; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - func = argv[1]; - if (JS_IsNull(func)) { - rh = find_rh(ts, fd); - if (rh) { - JS_FreeValue(ctx, rh->rw_func[magic]); - rh->rw_func[magic] = JS_NULL; - if (JS_IsNull(rh->rw_func[0]) && - JS_IsNull(rh->rw_func[1])) { - /* remove the entry */ - free_rw_handler(JS_GetRuntime(ctx), rh); - } - } - } else { - if (!JS_IsFunction(ctx, func)) - return JS_ThrowTypeError(ctx, "not a function"); - rh = find_rh(ts, fd); - if (!rh) { - rh = js_mallocz(ctx, sizeof(*rh)); - if (!rh) - return JS_EXCEPTION; - rh->fd = fd; - rh->rw_func[0] = JS_NULL; - rh->rw_func[1] = JS_NULL; - list_add_tail(&rh->link, &ts->os_rw_handlers); - } - JS_FreeValue(ctx, rh->rw_func[magic]); - rh->rw_func[magic] = JS_DupValue(ctx, func); - } - return JS_UNDEFINED; -} - -static JSOSSignalHandler *find_sh(JSThreadState *ts, int sig_num) -{ - JSOSSignalHandler *sh; - struct list_head *el; - list_for_each(el, &ts->os_signal_handlers) { - sh = list_entry(el, JSOSSignalHandler, link); - if (sh->sig_num == sig_num) - return sh; - } - return NULL; -} - -static void free_sh(JSRuntime *rt, JSOSSignalHandler *sh) -{ - list_del(&sh->link); - JS_FreeValueRT(rt, sh->func); - js_free_rt(rt, sh); -} - -static void os_signal_handler(int sig_num) -{ - os_pending_signals |= ((uint64_t)1 << sig_num); -} - -#if defined(_WIN32) -typedef void (*sighandler_t)(int sig_num); -#endif - -static JSValue js_os_signal(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSOSSignalHandler *sh; - uint32_t sig_num; - JSValueConst func; - sighandler_t handler; - - if (!is_main_thread(rt)) - return JS_ThrowTypeError(ctx, "signal handler can only be set in the main thread"); - - if (JS_ToUint32(ctx, &sig_num, argv[0])) - return JS_EXCEPTION; - if (sig_num >= 64) - return JS_ThrowRangeError(ctx, "invalid signal number"); - func = argv[1]; - /* func = null: SIG_DFL, func = undefined, SIG_IGN */ - if (JS_IsNull(func) || JS_IsUndefined(func)) { - sh = find_sh(ts, sig_num); - if (sh) { - free_sh(JS_GetRuntime(ctx), sh); - } - if (JS_IsNull(func)) - handler = SIG_DFL; - else - handler = SIG_IGN; - signal(sig_num, handler); - } else { - if (!JS_IsFunction(ctx, func)) - return JS_ThrowTypeError(ctx, "not a function"); - sh = find_sh(ts, sig_num); - if (!sh) { - sh = js_mallocz(ctx, sizeof(*sh)); - if (!sh) - return JS_EXCEPTION; - sh->sig_num = sig_num; - list_add_tail(&sh->link, &ts->os_signal_handlers); - } - JS_FreeValue(ctx, sh->func); - sh->func = JS_DupValue(ctx, func); - signal(sig_num, os_signal_handler); - } - return JS_UNDEFINED; -} - -#if !defined(_WIN32) && !defined(__wasi__) -static JSValue js_os_cputime(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - struct rusage ru; - int64_t cputime; - - cputime = 0; - if (!getrusage(RUSAGE_SELF, &ru)) - cputime = (int64_t)ru.ru_utime.tv_sec * 1000000 + ru.ru_utime.tv_usec; - - return JS_NewInt64(ctx, cputime); -} -#endif - -static JSValue js_os_exepath(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - char buf[JS__PATH_MAX]; - size_t len = sizeof(buf); - if (js_exepath(buf, &len)) - return JS_UNDEFINED; - return JS_NewStringLen(ctx, buf, len); -} - -static JSValue js_os_now(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_NewInt64(ctx, js__hrtime_ns() / 1000); -} - -static uint64_t js__hrtime_ms(void) -{ - return js__hrtime_ns() / (1000 * 1000); -} - -static void free_timer(JSRuntime *rt, JSOSTimer *th) -{ - list_del(&th->link); - JS_FreeValueRT(rt, th->func); - js_free_rt(rt, th); -} - -// TODO(bnoordhuis) accept string as first arg and eval at timer expiry -// TODO(bnoordhuis) retain argv[2..] as args for callback if argc > 2 -static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - int64_t delay; - JSValueConst func; - JSOSTimer *th; - - func = argv[0]; - if (!JS_IsFunction(ctx, func)) - return JS_ThrowTypeError(ctx, "not a function"); - if (JS_ToInt64(ctx, &delay, argv[1])) - return JS_EXCEPTION; - if (delay < 1) - delay = 1; - th = js_mallocz(ctx, sizeof(*th)); - if (!th) - return JS_EXCEPTION; - th->timer_id = ts->next_timer_id++; - if (ts->next_timer_id > MAX_SAFE_INTEGER) - ts->next_timer_id = 1; - th->repeats = (magic > 0); - th->timeout = js__hrtime_ms() + delay; - th->delay = delay; - th->func = JS_DupValue(ctx, func); - list_add_tail(&th->link, &ts->os_timers); - return JS_NewInt64(ctx, th->timer_id); -} - -static JSOSTimer *find_timer_by_id(JSThreadState *ts, int timer_id) -{ - struct list_head *el; - if (timer_id <= 0) - return NULL; - list_for_each(el, &ts->os_timers) { - JSOSTimer *th = list_entry(el, JSOSTimer, link); - if (th->timer_id == timer_id) - return th; - } - return NULL; -} - -static JSValue js_os_clearTimeout(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSOSTimer *th; - int64_t timer_id; - - if (JS_ToInt64(ctx, &timer_id, argv[0])) - return JS_EXCEPTION; - th = find_timer_by_id(ts, timer_id); - if (!th) - return JS_UNDEFINED; - free_timer(rt, th); - return JS_UNDEFINED; -} - -/* return a promise */ -static JSValue js_os_sleepAsync(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - int64_t delay; - JSOSTimer *th; - JSValue promise, resolving_funcs[2]; - - if (JS_ToInt64(ctx, &delay, argv[0])) - return JS_EXCEPTION; - promise = JS_NewPromiseCapability(ctx, resolving_funcs); - if (JS_IsException(promise)) - return JS_EXCEPTION; - - th = js_mallocz(ctx, sizeof(*th)); - if (!th) { - JS_FreeValue(ctx, promise); - JS_FreeValue(ctx, resolving_funcs[0]); - JS_FreeValue(ctx, resolving_funcs[1]); - return JS_EXCEPTION; - } - th->timer_id = -1; - th->timeout = js__hrtime_ms() + delay; - th->func = JS_DupValue(ctx, resolving_funcs[0]); - list_add_tail(&th->link, &ts->os_timers); - JS_FreeValue(ctx, resolving_funcs[0]); - JS_FreeValue(ctx, resolving_funcs[1]); - return promise; -} - -static int call_handler(JSContext *ctx, JSValue func) -{ - int r; - JSValue ret, func1; - /* 'func' might be destroyed when calling itself (if it frees the - handler), so must take extra care */ - func1 = JS_DupValue(ctx, func); - ret = JS_Call(ctx, func1, JS_UNDEFINED, 0, NULL); - JS_FreeValue(ctx, func1); - r = 0; - if (JS_IsException(ret)) - r = -1; - JS_FreeValue(ctx, ret); - return r; -} - -static int js_os_run_timers(JSRuntime *rt, JSContext *ctx, JSThreadState *ts, int *min_delay) -{ - JSValue func; - JSOSTimer *th; - int64_t cur_time, delay; - struct list_head *el; - int r; - - if (list_empty(&ts->os_timers)) { - *min_delay = -1; - return 0; - } - - cur_time = js__hrtime_ms(); - *min_delay = INT32_MAX; - - list_for_each(el, &ts->os_timers) { - th = list_entry(el, JSOSTimer, link); - delay = th->timeout - cur_time; - if (delay > 0) { - *min_delay = min_int(*min_delay, delay); - } else { - *min_delay = 0; - func = JS_DupValueRT(rt, th->func); - if (th->repeats) - th->timeout = cur_time + th->delay; - else - free_timer(rt, th); - r = call_handler(ctx, func); - JS_FreeValueRT(rt, func); - return r; - } - } - - return 0; -} - -#ifdef USE_WORKER - -#ifdef _WIN32 - -static int js_waker_init(JSWaker *w) -{ - w->handle = CreateEvent(NULL, TRUE, FALSE, NULL); - return w->handle ? 0 : -1; -} - -static void js_waker_signal(JSWaker *w) -{ - SetEvent(w->handle); -} - -static void js_waker_clear(JSWaker *w) -{ - ResetEvent(w->handle); -} - -static void js_waker_close(JSWaker *w) -{ - CloseHandle(w->handle); - w->handle = INVALID_HANDLE_VALUE; -} - -#else // !_WIN32 - -static int js_waker_init(JSWaker *w) -{ - int fds[2]; - - if (pipe(fds) < 0) - return -1; - w->read_fd = fds[0]; - w->write_fd = fds[1]; - return 0; -} - -static void js_waker_signal(JSWaker *w) -{ - int ret; - - for(;;) { - ret = write(w->write_fd, "", 1); - if (ret == 1) - break; - if (ret < 0 && (errno != EAGAIN || errno != EINTR)) - break; - } -} - -static void js_waker_clear(JSWaker *w) -{ - uint8_t buf[16]; - int ret; - - for(;;) { - ret = read(w->read_fd, buf, sizeof(buf)); - if (ret >= 0) - break; - if (errno != EAGAIN && errno != EINTR) - break; - } -} - -static void js_waker_close(JSWaker *w) -{ - close(w->read_fd); - close(w->write_fd); - w->read_fd = -1; - w->write_fd = -1; -} - -#endif // _WIN32 - -static void js_free_message(JSWorkerMessage *msg); - -/* return 1 if a message was handled, 0 if no message */ -static int handle_posted_message(JSRuntime *rt, JSContext *ctx, - JSWorkerMessageHandler *port) -{ - JSWorkerMessagePipe *ps = port->recv_pipe; - int ret; - struct list_head *el; - JSWorkerMessage *msg; - JSValue obj, data_obj, func, retval; - - js_mutex_lock(&ps->mutex); - if (!list_empty(&ps->msg_queue)) { - el = ps->msg_queue.next; - msg = list_entry(el, JSWorkerMessage, link); - - /* remove the message from the queue */ - list_del(&msg->link); - - // drain read end of pipe - if (list_empty(&ps->msg_queue)) - js_waker_clear(&ps->waker); - - js_mutex_unlock(&ps->mutex); - - data_obj = JS_ReadObject(ctx, msg->data, msg->data_len, - JS_READ_OBJ_SAB | JS_READ_OBJ_REFERENCE); - - js_free_message(msg); - - if (JS_IsException(data_obj)) - goto fail; - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) { - JS_FreeValue(ctx, data_obj); - goto fail; - } - JS_DefinePropertyValueStr(ctx, obj, "data", data_obj, JS_PROP_C_W_E); - - /* 'func' might be destroyed when calling itself (if it frees the - handler), so must take extra care */ - func = JS_DupValue(ctx, port->on_message_func); - retval = JS_Call(ctx, func, JS_UNDEFINED, 1, (JSValueConst *)&obj); - JS_FreeValue(ctx, obj); - JS_FreeValue(ctx, func); - if (JS_IsException(retval)) { - fail: - js_std_dump_error(ctx); - } else { - JS_FreeValue(ctx, retval); - } - ret = 1; - } else { - js_mutex_unlock(&ps->mutex); - ret = 0; - } - return ret; -} - -#endif // USE_WORKER - -#if defined(_WIN32) -static int js_os_poll(JSContext *ctx) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - int min_delay, count; - JSOSRWHandler *rh; - struct list_head *el; - HANDLE handles[MAXIMUM_WAIT_OBJECTS]; // 64 - - /* XXX: handle signals if useful */ - - if (js_os_run_timers(rt, ctx, ts, &min_delay)) - return -1; - if (min_delay == 0) - return 0; // expired timer - if (min_delay < 0) - if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->port_list)) - return -1; /* no more events */ - - count = 0; - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) - handles[count++] = (HANDLE)_get_osfhandle(rh->fd); // stdin - if (count == (int)countof(handles)) - break; - } - - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - if (JS_IsNull(port->on_message_func)) - continue; - handles[count++] = port->recv_pipe->waker.handle; - if (count == (int)countof(handles)) - break; - } - - if (count > 0) { - DWORD ret, timeout = INFINITE; - if (min_delay != -1) - timeout = min_delay; - ret = WaitForMultipleObjects(count, handles, FALSE, timeout); - if (ret < count) { - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) { - return call_handler(ctx, rh->rw_func[0]); - /* must stop because the list may have been modified */ - } - } - - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - if (!JS_IsNull(port->on_message_func)) { - JSWorkerMessagePipe *ps = port->recv_pipe; - if (ps->waker.handle == handles[ret]) { - if (handle_posted_message(rt, ctx, port)) - goto done; - } - } - } - } - } else { - Sleep(min_delay); - } -done: - return 0; -} -#else // !defined(_WIN32) -static int js_os_poll(JSContext *ctx) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - int r, w, ret, nfds, min_delay; - JSOSRWHandler *rh; - struct list_head *el; - struct pollfd *pfd, *pfds, pfds_local[64]; - - /* only check signals in the main thread */ - if (!ts->recv_pipe && - unlikely(os_pending_signals != 0)) { - JSOSSignalHandler *sh; - uint64_t mask; - - list_for_each(el, &ts->os_signal_handlers) { - sh = list_entry(el, JSOSSignalHandler, link); - mask = (uint64_t)1 << sh->sig_num; - if (os_pending_signals & mask) { - os_pending_signals &= ~mask; - return call_handler(ctx, sh->func); - } - } - } - - if (js_os_run_timers(rt, ctx, ts, &min_delay)) - return -1; - if (min_delay == 0) - return 0; // expired timer - if (min_delay < 0) - if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->port_list)) - return -1; /* no more events */ - - nfds = 0; - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - nfds += (!JS_IsNull(rh->rw_func[0]) || !JS_IsNull(rh->rw_func[1])); - } - -#ifdef USE_WORKER - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - nfds += !JS_IsNull(port->on_message_func); - } -#endif // USE_WORKER - - pfd = pfds = pfds_local; - if (nfds > (int)countof(pfds_local)) { - pfd = pfds = js_malloc(ctx, nfds * sizeof(*pfd)); - if (!pfd) - return -1; - } - - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - r = POLLIN * !JS_IsNull(rh->rw_func[0]); - w = POLLOUT * !JS_IsNull(rh->rw_func[1]); - if (r || w) - *pfd++ = (struct pollfd){rh->fd, r|w, 0}; - } - -#ifdef USE_WORKER - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - if (!JS_IsNull(port->on_message_func)) { - JSWorkerMessagePipe *ps = port->recv_pipe; - *pfd++ = (struct pollfd){ps->waker.read_fd, POLLIN, 0}; - } - } -#endif // USE_WORKER - - // FIXME(bnoordhuis) the loop below is quadratic in theory but - // linear-ish in practice because we bail out on the first hit, - // i.e., it's probably good enough for now - ret = 0; - nfds = poll(pfds, nfds, min_delay); - for (pfd = pfds; nfds-- > 0; pfd++) { - rh = find_rh(ts, pfd->fd); - if (rh) { - r = (POLLERR|POLLHUP|POLLNVAL|POLLIN) * !JS_IsNull(rh->rw_func[0]); - w = (POLLERR|POLLHUP|POLLNVAL|POLLOUT) * !JS_IsNull(rh->rw_func[1]); - if (r & pfd->revents) { - ret = call_handler(ctx, rh->rw_func[0]); - goto done; - /* must stop because the list may have been modified */ - } - if (w & pfd->revents) { - ret = call_handler(ctx, rh->rw_func[1]); - goto done; - /* must stop because the list may have been modified */ - } - } else { -#ifdef USE_WORKER - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - if (!JS_IsNull(port->on_message_func)) { - JSWorkerMessagePipe *ps = port->recv_pipe; - if (pfd->fd == ps->waker.read_fd) { - if (handle_posted_message(rt, ctx, port)) - goto done; - } - } - } -#endif // USE_WORKER - } - } -done: - if (pfds != pfds_local) - js_free(ctx, pfds); - return ret; -} -#endif // defined(_WIN32) - - -static JSValue make_obj_error(JSContext *ctx, - JSValue obj, - int err) -{ - JSValue arr; - if (JS_IsException(obj)) - return obj; - arr = JS_NewArray(ctx); - if (JS_IsException(arr)) - return JS_EXCEPTION; - JS_DefinePropertyValueUint32(ctx, arr, 0, obj, - JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, arr, 1, JS_NewInt32(ctx, err), - JS_PROP_C_W_E); - return arr; -} - -static JSValue make_string_error(JSContext *ctx, - const char *buf, - int err) -{ - return make_obj_error(ctx, JS_NewString(ctx, buf), err); -} - -/* return [cwd, errorcode] */ -static JSValue js_os_getcwd(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - char buf[JS__PATH_MAX]; - int err; - - if (!getcwd(buf, sizeof(buf))) { - buf[0] = '\0'; - err = errno; - } else { - err = 0; - } - return make_string_error(ctx, buf, err); -} - -static JSValue js_os_chdir(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *target; - int err; - - target = JS_ToCString(ctx, argv[0]); - if (!target) - return JS_EXCEPTION; - err = js_get_errno(chdir(target)); - JS_FreeCString(ctx, target); - return JS_NewInt32(ctx, err); -} - -static JSValue js_os_mkdir(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int mode, ret; - const char *path; - - if (argc >= 2) { - if (JS_ToInt32(ctx, &mode, argv[1])) - return JS_EXCEPTION; - } else { - mode = 0777; - } - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; -#if defined(_WIN32) - (void)mode; - ret = js_get_errno(mkdir(path)); -#else - ret = js_get_errno(mkdir(path, mode)); -#endif - JS_FreeCString(ctx, path); - return JS_NewInt32(ctx, ret); -} - -/* return [array, errorcode] */ -static JSValue js_os_readdir(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ -#ifdef _WIN32 - const char *path; - JSValue obj; - int err; - uint32_t len; - HANDLE h; - WIN32_FIND_DATAA d; - char s[1024]; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) { - JS_FreeCString(ctx, path); - return JS_EXCEPTION; - } - snprintf(s, sizeof(s), "%s/*", path); - JS_FreeCString(ctx, path); - err = 0; - h = FindFirstFileA(s, &d); - if (h == INVALID_HANDLE_VALUE) - err = GetLastError(); - if (err) - goto done; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewString(ctx, "."), - JS_PROP_C_W_E); - for (len = 1; FindNextFileA(h, &d); len++) { - JS_DefinePropertyValueUint32(ctx, obj, len, - JS_NewString(ctx, d.cFileName), - JS_PROP_C_W_E); - } - FindClose(h); -done: - return make_obj_error(ctx, obj, err); -#else - const char *path; - DIR *f; - struct dirent *d; - JSValue obj; - int err; - uint32_t len; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) { - JS_FreeCString(ctx, path); - return JS_EXCEPTION; - } - f = opendir(path); - if (!f) - err = errno; - else - err = 0; - JS_FreeCString(ctx, path); - if (!f) - goto done; - len = 0; - for(;;) { - errno = 0; - d = readdir(f); - if (!d) { - err = errno; - break; - } - JS_DefinePropertyValueUint32(ctx, obj, len++, - JS_NewString(ctx, d->d_name), - JS_PROP_C_W_E); - } - closedir(f); - done: - return make_obj_error(ctx, obj, err); -#endif -} - -#if !defined(_WIN32) -static int64_t timespec_to_ms(const struct timespec *tv) -{ - return (int64_t)tv->tv_sec * 1000 + (tv->tv_nsec / 1000000); -} -#endif - -/* return [obj, errcode] */ -static JSValue js_os_stat(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int is_lstat) -{ - const char *path; - int err, res; - struct stat st; - JSValue obj; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; -#if defined(_WIN32) - res = stat(path, &st); -#else - if (is_lstat) - res = lstat(path, &st); - else - res = stat(path, &st); -#endif - err = (res < 0) ? errno : 0; - JS_FreeCString(ctx, path); - if (res < 0) { - obj = JS_NULL; - } else { - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) - return JS_EXCEPTION; - JS_DefinePropertyValueStr(ctx, obj, "dev", - JS_NewInt64(ctx, st.st_dev), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "ino", - JS_NewInt64(ctx, st.st_ino), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "mode", - JS_NewInt32(ctx, st.st_mode), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "nlink", - JS_NewInt64(ctx, st.st_nlink), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "uid", - JS_NewInt64(ctx, st.st_uid), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "gid", - JS_NewInt64(ctx, st.st_gid), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "rdev", - JS_NewInt64(ctx, st.st_rdev), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "size", - JS_NewInt64(ctx, st.st_size), - JS_PROP_C_W_E); -#if !defined(_WIN32) - JS_DefinePropertyValueStr(ctx, obj, "blocks", - JS_NewInt64(ctx, st.st_blocks), - JS_PROP_C_W_E); -#endif -#if defined(_WIN32) - JS_DefinePropertyValueStr(ctx, obj, "atime", - JS_NewInt64(ctx, (int64_t)st.st_atime * 1000), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "mtime", - JS_NewInt64(ctx, (int64_t)st.st_mtime * 1000), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "ctime", - JS_NewInt64(ctx, (int64_t)st.st_ctime * 1000), - JS_PROP_C_W_E); -#elif defined(__APPLE__) - JS_DefinePropertyValueStr(ctx, obj, "atime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_atimespec)), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "mtime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_mtimespec)), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "ctime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_ctimespec)), - JS_PROP_C_W_E); -#else - JS_DefinePropertyValueStr(ctx, obj, "atime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_atim)), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "mtime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_mtim)), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "ctime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_ctim)), - JS_PROP_C_W_E); -#endif - } - return make_obj_error(ctx, obj, err); -} - -#if !defined(_WIN32) -static void ms_to_timeval(struct timeval *tv, uint64_t v) -{ - tv->tv_sec = v / 1000; - tv->tv_usec = (v % 1000) * 1000; -} -#endif - -static JSValue js_os_utimes(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *path; - int64_t atime, mtime; - int ret; - - if (JS_ToInt64(ctx, &atime, argv[1])) - return JS_EXCEPTION; - if (JS_ToInt64(ctx, &mtime, argv[2])) - return JS_EXCEPTION; - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; -#if defined(_WIN32) - { - struct _utimbuf times; - times.actime = atime / 1000; - times.modtime = mtime / 1000; - ret = js_get_errno(_utime(path, ×)); - } -#else - { - struct timeval times[2]; - ms_to_timeval(×[0], atime); - ms_to_timeval(×[1], mtime); - ret = js_get_errno(utimes(path, times)); - } -#endif - JS_FreeCString(ctx, path); - return JS_NewInt32(ctx, ret); -} - -/* sleep(delay_ms) */ -static JSValue js_os_sleep(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int64_t delay; - int ret; - - if (JS_ToInt64(ctx, &delay, argv[0])) - return JS_EXCEPTION; - if (delay < 0) - delay = 0; -#if defined(_WIN32) - { - if (delay > INT32_MAX) - delay = INT32_MAX; - Sleep(delay); - ret = 0; - } -#else - { - struct timespec ts; - - ts.tv_sec = delay / 1000; - ts.tv_nsec = (delay % 1000) * 1000000; - ret = js_get_errno(nanosleep(&ts, NULL)); - } -#endif - return JS_NewInt32(ctx, ret); -} - -#if defined(_WIN32) -static char *realpath(const char *path, char *buf) -{ - if (!_fullpath(buf, path, JS__PATH_MAX)) { - errno = ENOENT; - return NULL; - } else { - return buf; - } -} -#endif - -#if !defined(__wasi__) -/* return [path, errorcode] */ -static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *path; - char buf[JS__PATH_MAX], *res; - int err; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; - res = realpath(path, buf); - JS_FreeCString(ctx, path); - if (!res) { - buf[0] = '\0'; - err = errno; - } else { - err = 0; - } - return make_string_error(ctx, buf, err); -} -#endif - -#if !defined(_WIN32) && !defined(__wasi__) && !(defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH)) -static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *target, *linkpath; - int err; - - target = JS_ToCString(ctx, argv[0]); - if (!target) - return JS_EXCEPTION; - linkpath = JS_ToCString(ctx, argv[1]); - if (!linkpath) { - JS_FreeCString(ctx, target); - return JS_EXCEPTION; - } - err = js_get_errno(symlink(target, linkpath)); - JS_FreeCString(ctx, target); - JS_FreeCString(ctx, linkpath); - return JS_NewInt32(ctx, err); -} - -/* return [path, errorcode] */ -static JSValue js_os_readlink(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *path; - char buf[JS__PATH_MAX]; - int err; - ssize_t res; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; - res = readlink(path, buf, sizeof(buf) - 1); - if (res < 0) { - buf[0] = '\0'; - err = errno; - } else { - buf[res] = '\0'; - err = 0; - } - JS_FreeCString(ctx, path); - return make_string_error(ctx, buf, err); -} - -static char **build_envp(JSContext *ctx, JSValue obj) -{ - uint32_t len, i; - JSPropertyEnum *tab; - char **envp, *pair; - const char *key, *str; - JSValue val; - size_t key_len, str_len; - - if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj, - JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) - return NULL; - envp = js_mallocz(ctx, sizeof(envp[0]) * ((size_t)len + 1)); - if (!envp) - goto fail; - for(i = 0; i < len; i++) { - val = JS_GetProperty(ctx, obj, tab[i].atom); - if (JS_IsException(val)) - goto fail; - str = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!str) - goto fail; - key = JS_AtomToCString(ctx, tab[i].atom); - if (!key) { - JS_FreeCString(ctx, str); - goto fail; - } - key_len = strlen(key); - str_len = strlen(str); - pair = js_malloc(ctx, key_len + str_len + 2); - if (!pair) { - JS_FreeCString(ctx, key); - JS_FreeCString(ctx, str); - goto fail; - } - memcpy(pair, key, key_len); - pair[key_len] = '='; - memcpy(pair + key_len + 1, str, str_len); - pair[key_len + 1 + str_len] = '\0'; - envp[i] = pair; - JS_FreeCString(ctx, key); - JS_FreeCString(ctx, str); - } - done: - for(i = 0; i < len; i++) - JS_FreeAtom(ctx, tab[i].atom); - js_free(ctx, tab); - return envp; - fail: - if (envp) { - for(i = 0; i < len; i++) - js_free(ctx, envp[i]); - js_free(ctx, envp); - envp = NULL; - } - goto done; -} - -/* execvpe is not available on non GNU systems */ -static int my_execvpe(const char *filename, char **argv, char **envp) -{ - char *path, *p, *p_next, *p1; - char buf[JS__PATH_MAX]; - size_t filename_len, path_len; - bool eacces_error; - - filename_len = strlen(filename); - if (filename_len == 0) { - errno = ENOENT; - return -1; - } - if (strchr(filename, '/')) - return execve(filename, argv, envp); - - path = getenv("PATH"); - if (!path) - path = (char *)"/bin:/usr/bin"; - eacces_error = false; - p = path; - for(p = path; p != NULL; p = p_next) { - p1 = strchr(p, ':'); - if (!p1) { - p_next = NULL; - path_len = strlen(p); - } else { - p_next = p1 + 1; - path_len = p1 - p; - } - /* path too long */ - if ((path_len + 1 + filename_len + 1) > JS__PATH_MAX) - continue; - memcpy(buf, p, path_len); - buf[path_len] = '/'; - memcpy(buf + path_len + 1, filename, filename_len); - buf[path_len + 1 + filename_len] = '\0'; - - execve(buf, argv, envp); - - switch(errno) { - case EACCES: - eacces_error = true; - break; - case ENOENT: - case ENOTDIR: - break; - default: - return -1; - } - } - if (eacces_error) - errno = EACCES; - return -1; -} - -static void (*js_os_exec_closefrom)(int); - -#if !defined(EMSCRIPTEN) && !defined(__wasi__) - -static js_once_t js_os_exec_once = JS_ONCE_INIT; - -static void js_os_exec_once_init(void) -{ - *(void **) (&js_os_exec_closefrom) = dlsym(RTLD_DEFAULT, "closefrom"); -} - -#endif - -/* exec(args[, options]) -> exitcode */ -static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValueConst options, args = argv[0]; - JSValue val, ret_val; - const char **exec_argv, *file = NULL, *str, *cwd = NULL; - char **envp = environ; - uint32_t exec_argc, i; - int ret, pid, status; - bool block_flag = true, use_path = true; - static const char *std_name[3] = { "stdin", "stdout", "stderr" }; - int std_fds[3]; - uint32_t uid = -1, gid = -1; - int ngroups = -1; - gid_t groups[64]; - - val = JS_GetPropertyStr(ctx, args, "length"); - if (JS_IsException(val)) - return JS_EXCEPTION; - ret = JS_ToUint32(ctx, &exec_argc, val); - JS_FreeValue(ctx, val); - if (ret) - return JS_EXCEPTION; - /* arbitrary limit to avoid overflow */ - if (exec_argc < 1 || exec_argc > 65535) { - return JS_ThrowTypeError(ctx, "invalid number of arguments"); - } - exec_argv = js_mallocz(ctx, sizeof(exec_argv[0]) * (exec_argc + 1)); - if (!exec_argv) - return JS_EXCEPTION; - for(i = 0; i < exec_argc; i++) { - val = JS_GetPropertyUint32(ctx, args, i); - if (JS_IsException(val)) - goto exception; - str = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!str) - goto exception; - exec_argv[i] = str; - } - exec_argv[exec_argc] = NULL; - - for(i = 0; i < 3; i++) - std_fds[i] = i; - - /* get the options, if any */ - if (argc >= 2) { - options = argv[1]; - - if (get_bool_option(ctx, &block_flag, options, "block")) - goto exception; - if (get_bool_option(ctx, &use_path, options, "usePath")) - goto exception; - - val = JS_GetPropertyStr(ctx, options, "file"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - file = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!file) - goto exception; - } - - val = JS_GetPropertyStr(ctx, options, "cwd"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - cwd = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!cwd) - goto exception; - } - - /* stdin/stdout/stderr handles */ - for(i = 0; i < 3; i++) { - val = JS_GetPropertyStr(ctx, options, std_name[i]); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - int fd; - ret = JS_ToInt32(ctx, &fd, val); - JS_FreeValue(ctx, val); - if (ret) - goto exception; - std_fds[i] = fd; - } - } - - val = JS_GetPropertyStr(ctx, options, "env"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - envp = build_envp(ctx, val); - JS_FreeValue(ctx, val); - if (!envp) - goto exception; - } - - val = JS_GetPropertyStr(ctx, options, "uid"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - ret = JS_ToUint32(ctx, &uid, val); - JS_FreeValue(ctx, val); - if (ret) - goto exception; - } - - val = JS_GetPropertyStr(ctx, options, "gid"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - ret = JS_ToUint32(ctx, &gid, val); - JS_FreeValue(ctx, val); - if (ret) - goto exception; - } - - val = JS_GetPropertyStr(ctx, options, "groups"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - int64_t idx, len; - JSValue prop; - uint32_t id; - ngroups = 0; - if (JS_GetLength(ctx, val, &len)) { - JS_FreeValue(ctx, val); - goto exception; - } - for (idx = 0; idx < len; idx++) { - prop = JS_GetPropertyInt64(ctx, val, idx); - if (JS_IsException(prop)) - break; - if (JS_IsUndefined(prop)) - continue; - ret = JS_ToUint32(ctx, &id, prop); - JS_FreeValue(ctx, prop); - if (ret) - break; - if (ngroups == countof(groups)) { - JS_ThrowRangeError(ctx, "too many groups"); - break; - } - groups[ngroups++] = id; - } - JS_FreeValue(ctx, val); - if (idx < len) - goto exception; - } - - } - -#if !defined(EMSCRIPTEN) && !defined(__wasi__) - // should happen pre-fork because it calls dlsym() - // and that's not an async-signal-safe function - js_once(&js_os_exec_once, js_os_exec_once_init); -#endif - - pid = fork(); - if (pid < 0) { - JS_ThrowTypeError(ctx, "fork error"); - goto exception; - } - if (pid == 0) { - /* child */ - /* remap the stdin/stdout/stderr handles if necessary */ - for(i = 0; i < 3; i++) { - if (std_fds[i] != i) { - if (dup2(std_fds[i], i) < 0) - _exit(127); - } - } - - if (js_os_exec_closefrom) { - js_os_exec_closefrom(3); - } else { - int fd_max = sysconf(_SC_OPEN_MAX); - for(i = 3; i < fd_max; i++) - close(i); - } - - if (cwd) { - if (chdir(cwd) < 0) - _exit(127); - } - if (ngroups != -1) { - if (setgroups(ngroups, groups) < 0) - _exit(127); - } - if (uid != -1) { - if (setuid(uid) < 0) - _exit(127); - } - if (gid != -1) { - if (setgid(gid) < 0) - _exit(127); - } - - if (!file) - file = exec_argv[0]; - if (use_path) - ret = my_execvpe(file, (char **)exec_argv, envp); - else - ret = execve(file, (char **)exec_argv, envp); - _exit(127); - } - /* parent */ - if (block_flag) { - for(;;) { - ret = waitpid(pid, &status, 0); - if (ret == pid) { - if (WIFEXITED(status)) { - ret = WEXITSTATUS(status); - break; - } else if (WIFSIGNALED(status)) { - ret = -WTERMSIG(status); - break; - } - } - } - } else { - ret = pid; - } - ret_val = JS_NewInt32(ctx, ret); - done: - JS_FreeCString(ctx, file); - JS_FreeCString(ctx, cwd); - for(i = 0; i < exec_argc; i++) - JS_FreeCString(ctx, exec_argv[i]); - js_free(ctx, exec_argv); - if (envp != environ) { - char **p; - p = envp; - while (*p != NULL) { - js_free(ctx, *p); - p++; - } - js_free(ctx, envp); - } - return ret_val; - exception: - ret_val = JS_EXCEPTION; - goto done; -} - -/* getpid() -> pid */ -static JSValue js_os_getpid(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_NewInt32(ctx, getpid()); -} - -/* waitpid(pid, block) -> [pid, status] */ -static JSValue js_os_waitpid(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int pid, status, options, ret; - JSValue obj; - - if (JS_ToInt32(ctx, &pid, argv[0])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &options, argv[1])) - return JS_EXCEPTION; - - ret = waitpid(pid, &status, options); - if (ret < 0) { - ret = -errno; - status = 0; - } - - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return obj; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ret), - JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, status), - JS_PROP_C_W_E); - return obj; -} - -/* pipe() -> [read_fd, write_fd] or null if error */ -static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int pipe_fds[2], ret; - JSValue obj; - - ret = pipe(pipe_fds); - if (ret < 0) - return JS_NULL; - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return obj; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, pipe_fds[0]), - JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, pipe_fds[1]), - JS_PROP_C_W_E); - return obj; -} - -/* kill(pid, sig) */ -static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int pid, sig, ret; - - if (JS_ToInt32(ctx, &pid, argv[0])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &sig, argv[1])) - return JS_EXCEPTION; - ret = js_get_errno(kill(pid, sig)); - return JS_NewInt32(ctx, ret); -} - -/* dup(fd) */ -static JSValue js_os_dup(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd, ret; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - ret = js_get_errno(dup(fd)); - return JS_NewInt32(ctx, ret); -} - -/* dup2(fd) */ -static JSValue js_os_dup2(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd, fd2, ret; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &fd2, argv[1])) - return JS_EXCEPTION; - ret = js_get_errno(dup2(fd, fd2)); - return JS_NewInt32(ctx, ret); -} - -#endif /* !_WIN32 */ - -#ifdef USE_WORKER - -/* Worker */ - -typedef struct { - JSWorkerMessagePipe *recv_pipe; - JSWorkerMessagePipe *send_pipe; - JSWorkerMessageHandler *msg_handler; -} JSWorkerData; - -typedef struct { - char *filename; /* module filename */ - char *basename; /* module base name */ - JSWorkerMessagePipe *recv_pipe, *send_pipe; -} WorkerFuncArgs; - -typedef struct { - int ref_count; - uint64_t buf[]; -} JSSABHeader; - -static JSContext *(*js_worker_new_context_func)(JSRuntime *rt); - -static int atomic_add_int(int *ptr, int v) -{ - return atomic_fetch_add((_Atomic uint32_t*)ptr, v) + v; -} - -/* shared array buffer allocator */ -static void *js_sab_alloc(void *opaque, size_t size) -{ - JSSABHeader *sab; - sab = malloc(sizeof(JSSABHeader) + size); - if (!sab) - return NULL; - sab->ref_count = 1; - return sab->buf; -} - -static void js_sab_free(void *opaque, void *ptr) -{ - JSSABHeader *sab; - int ref_count; - sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); - ref_count = atomic_add_int(&sab->ref_count, -1); - assert(ref_count >= 0); - if (ref_count == 0) { - free(sab); - } -} - -static void js_sab_dup(void *opaque, void *ptr) -{ - JSSABHeader *sab; - sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); - atomic_add_int(&sab->ref_count, 1); -} - -static JSWorkerMessagePipe *js_new_message_pipe(void) -{ - JSWorkerMessagePipe *ps; - - ps = malloc(sizeof(*ps)); - if (!ps) - return NULL; - if (js_waker_init(&ps->waker)) { - free(ps); - return NULL; - } - ps->ref_count = 1; - init_list_head(&ps->msg_queue); - js_mutex_init(&ps->mutex); - return ps; -} - -static JSWorkerMessagePipe *js_dup_message_pipe(JSWorkerMessagePipe *ps) -{ - atomic_add_int(&ps->ref_count, 1); - return ps; -} - -static void js_free_message(JSWorkerMessage *msg) -{ - size_t i; - /* free the SAB */ - for(i = 0; i < msg->sab_tab_len; i++) { - js_sab_free(NULL, msg->sab_tab[i]); - } - free(msg->sab_tab); - free(msg->data); - free(msg); -} - -static void js_free_message_pipe(JSWorkerMessagePipe *ps) -{ - struct list_head *el, *el1; - JSWorkerMessage *msg; - int ref_count; - - if (!ps) - return; - - ref_count = atomic_add_int(&ps->ref_count, -1); - assert(ref_count >= 0); - if (ref_count == 0) { - list_for_each_safe(el, el1, &ps->msg_queue) { - msg = list_entry(el, JSWorkerMessage, link); - js_free_message(msg); - } - js_mutex_destroy(&ps->mutex); - js_waker_close(&ps->waker); - free(ps); - } -} - -static void js_free_port(JSRuntime *rt, JSWorkerMessageHandler *port) -{ - if (port) { - js_free_message_pipe(port->recv_pipe); - JS_FreeValueRT(rt, port->on_message_func); - list_del(&port->link); - js_free_rt(rt, port); - } -} - -static void js_worker_finalizer(JSRuntime *rt, JSValueConst val) -{ - JSThreadState *ts = js_get_thread_state(rt); - JSWorkerData *worker = JS_GetOpaque(val, ts->worker_class_id); - if (worker) { - js_free_message_pipe(worker->recv_pipe); - js_free_message_pipe(worker->send_pipe); - js_free_port(rt, worker->msg_handler); - js_free_rt(rt, worker); - } -} - -static JSClassDef js_worker_class = { - "Worker", - .finalizer = js_worker_finalizer, -}; - -static void worker_func(void *opaque) -{ - WorkerFuncArgs *args = opaque; - JSRuntime *rt; - JSThreadState *ts; - JSContext *ctx; - JSValue val; - - rt = JS_NewRuntime(); - if (rt == NULL) { - fprintf(stderr, "JS_NewRuntime failure"); - exit(1); - } - js_std_init_handlers(rt); - - JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); - - /* set the pipe to communicate with the parent */ - ts = js_get_thread_state(rt); - ts->recv_pipe = args->recv_pipe; - ts->send_pipe = args->send_pipe; - - /* function pointer to avoid linking the whole JS_NewContext() if - not needed */ - ctx = js_worker_new_context_func(rt); - if (ctx == NULL) { - fprintf(stderr, "JS_NewContext failure"); - } - - JS_SetCanBlock(rt, true); - - js_std_add_helpers(ctx, -1, NULL); - - val = JS_LoadModule(ctx, args->basename, args->filename); - free(args->filename); - free(args->basename); - free(args); - val = js_std_await(ctx, val); - if (JS_IsException(val)) - js_std_dump_error(ctx); - JS_FreeValue(ctx, val); - - js_std_loop(ctx); - - js_std_free_handlers(rt); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); -} - -static JSValue js_worker_ctor_internal(JSContext *ctx, JSValueConst new_target, - JSWorkerMessagePipe *recv_pipe, - JSWorkerMessagePipe *send_pipe) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSValue obj = JS_UNDEFINED, proto; - JSWorkerData *s; - - /* create the object */ - if (JS_IsUndefined(new_target)) { - proto = JS_GetClassProto(ctx, ts->worker_class_id); - } else { - proto = JS_GetPropertyStr(ctx, new_target, "prototype"); - if (JS_IsException(proto)) - goto fail; - } - obj = JS_NewObjectProtoClass(ctx, proto, ts->worker_class_id); - JS_FreeValue(ctx, proto); - if (JS_IsException(obj)) - goto fail; - s = js_mallocz(ctx, sizeof(*s)); - if (!s) - goto fail; - s->recv_pipe = js_dup_message_pipe(recv_pipe); - s->send_pipe = js_dup_message_pipe(send_pipe); - - JS_SetOpaque(obj, s); - return obj; - fail: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -static JSValue js_worker_ctor(JSContext *ctx, JSValueConst new_target, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - WorkerFuncArgs *args = NULL; - js_thread_t thr; - JSValue obj = JS_UNDEFINED; - int ret; - const char *filename = NULL, *basename; - JSAtom basename_atom; - - /* XXX: in order to avoid problems with resource liberation, we - don't support creating workers inside workers */ - if (!is_main_thread(rt)) - return JS_ThrowTypeError(ctx, "cannot create a worker inside a worker"); - - /* base name, assuming the calling function is a normal JS - function */ - basename_atom = JS_GetScriptOrModuleName(ctx, 1); - if (basename_atom == JS_ATOM_NULL) { - return JS_ThrowTypeError(ctx, "could not determine calling script or module name"); - } - basename = JS_AtomToCString(ctx, basename_atom); - JS_FreeAtom(ctx, basename_atom); - if (!basename) - goto fail; - - /* module name */ - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - goto fail; - - args = malloc(sizeof(*args)); - if (!args) - goto oom_fail; - memset(args, 0, sizeof(*args)); - args->filename = strdup(filename); - args->basename = strdup(basename); - - /* ports */ - args->recv_pipe = js_new_message_pipe(); - if (!args->recv_pipe) - goto oom_fail; - args->send_pipe = js_new_message_pipe(); - if (!args->send_pipe) - goto oom_fail; - - obj = js_worker_ctor_internal(ctx, new_target, - args->send_pipe, args->recv_pipe); - if (JS_IsException(obj)) - goto fail; - - ret = js_thread_create(&thr, worker_func, args, JS_THREAD_CREATE_DETACHED); - if (ret != 0) { - JS_ThrowTypeError(ctx, "could not create worker"); - goto fail; - } - JS_FreeCString(ctx, basename); - JS_FreeCString(ctx, filename); - return obj; - oom_fail: - JS_ThrowOutOfMemory(ctx); - fail: - JS_FreeCString(ctx, basename); - JS_FreeCString(ctx, filename); - if (args) { - free(args->filename); - free(args->basename); - js_free_message_pipe(args->recv_pipe); - js_free_message_pipe(args->send_pipe); - free(args); - } - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -static JSValue js_worker_postMessage(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, ts->worker_class_id); - JSWorkerMessagePipe *ps; - size_t data_len, i; - uint8_t *data; - JSWorkerMessage *msg; - JSSABTab sab_tab; - - if (!worker) - return JS_EXCEPTION; - - data = JS_WriteObject2(ctx, &data_len, argv[0], - JS_WRITE_OBJ_SAB | JS_WRITE_OBJ_REFERENCE, - &sab_tab); - if (!data) - return JS_EXCEPTION; - - msg = malloc(sizeof(*msg)); - if (!msg) - goto fail; - msg->data = NULL; - msg->sab_tab = NULL; - - /* must reallocate because the allocator may be different */ - msg->data = malloc(data_len); - if (!msg->data) - goto fail; - memcpy(msg->data, data, data_len); - msg->data_len = data_len; - - if (sab_tab.len > 0) { - msg->sab_tab = malloc(sizeof(msg->sab_tab[0]) * sab_tab.len); - if (!msg->sab_tab) - goto fail; - memcpy(msg->sab_tab, sab_tab.tab, sizeof(msg->sab_tab[0]) * sab_tab.len); - } - msg->sab_tab_len = sab_tab.len; - - js_free(ctx, data); - js_free(ctx, sab_tab.tab); - - /* increment the SAB reference counts */ - for(i = 0; i < msg->sab_tab_len; i++) { - js_sab_dup(NULL, msg->sab_tab[i]); - } - - ps = worker->send_pipe; - js_mutex_lock(&ps->mutex); - /* indicate that data is present */ - if (list_empty(&ps->msg_queue)) - js_waker_signal(&ps->waker); - list_add_tail(&msg->link, &ps->msg_queue); - js_mutex_unlock(&ps->mutex); - return JS_UNDEFINED; - fail: - if (msg) { - free(msg->data); - free(msg->sab_tab); - free(msg); - } - js_free(ctx, data); - js_free(ctx, sab_tab.tab); - return JS_EXCEPTION; - -} - -static JSValue js_worker_set_onmessage(JSContext *ctx, JSValueConst this_val, - JSValueConst func) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, ts->worker_class_id); - JSWorkerMessageHandler *port; - - if (!worker) - return JS_EXCEPTION; - - port = worker->msg_handler; - if (JS_IsNull(func)) { - if (port) { - js_free_port(rt, port); - worker->msg_handler = NULL; - } - } else { - if (!JS_IsFunction(ctx, func)) - return JS_ThrowTypeError(ctx, "not a function"); - if (!port) { - port = js_mallocz(ctx, sizeof(*port)); - if (!port) - return JS_EXCEPTION; - port->recv_pipe = js_dup_message_pipe(worker->recv_pipe); - port->on_message_func = JS_NULL; - list_add_tail(&port->link, &ts->port_list); - worker->msg_handler = port; - } - JS_FreeValue(ctx, port->on_message_func); - port->on_message_func = JS_DupValue(ctx, func); - } - return JS_UNDEFINED; -} - -static JSValue js_worker_get_onmessage(JSContext *ctx, JSValueConst this_val) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, ts->worker_class_id); - JSWorkerMessageHandler *port; - if (!worker) - return JS_EXCEPTION; - port = worker->msg_handler; - if (port) { - return JS_DupValue(ctx, port->on_message_func); - } else { - return JS_NULL; - } -} - -static const JSCFunctionListEntry js_worker_proto_funcs[] = { - JS_CFUNC_DEF("postMessage", 1, js_worker_postMessage ), - JS_CGETSET_DEF("onmessage", js_worker_get_onmessage, js_worker_set_onmessage ), -}; - -#endif /* USE_WORKER */ - -void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)) -{ -#ifdef USE_WORKER - js_worker_new_context_func = func; -#endif -} - -#if defined(_WIN32) -#define OS_PLATFORM "win32" -#elif defined(__APPLE__) -#define OS_PLATFORM "darwin" -#elif defined(EMSCRIPTEN) -#define OS_PLATFORM "js" -#elif defined(__CYGWIN__) -#define OS_PLATFORM "cygwin" -#elif defined(__linux__) -#define OS_PLATFORM "linux" -#elif defined(__OpenBSD__) -#define OS_PLATFORM "openbsd" -#elif defined(__NetBSD__) -#define OS_PLATFORM "netbsd" -#elif defined(__FreeBSD__) -#define OS_PLATFORM "freebsd" -#elif defined(__wasi__) -#define OS_PLATFORM "wasi" -#elif defined(__GNU__) -#define OS_PLATFORM "hurd" -#else -#define OS_PLATFORM "unknown" -#endif - -#define OS_FLAG(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) - -static const JSCFunctionListEntry js_os_funcs[] = { - JS_CFUNC_DEF("open", 2, js_os_open ), - OS_FLAG(O_RDONLY), - OS_FLAG(O_WRONLY), - OS_FLAG(O_RDWR), - OS_FLAG(O_APPEND), - OS_FLAG(O_CREAT), - OS_FLAG(O_EXCL), - OS_FLAG(O_TRUNC), -#if defined(_WIN32) - OS_FLAG(O_BINARY), - OS_FLAG(O_TEXT), -#endif - JS_CFUNC_DEF("close", 1, js_os_close ), - JS_CFUNC_DEF("seek", 3, js_os_seek ), - JS_CFUNC_MAGIC_DEF("read", 4, js_os_read_write, 0 ), - JS_CFUNC_MAGIC_DEF("write", 4, js_os_read_write, 1 ), - JS_CFUNC_DEF("isatty", 1, js_os_isatty ), -#if !defined(__wasi__) - JS_CFUNC_DEF("ttyGetWinSize", 1, js_os_ttyGetWinSize ), - JS_CFUNC_DEF("ttySetRaw", 1, js_os_ttySetRaw ), -#endif - JS_CFUNC_DEF("remove", 1, js_os_remove ), - JS_CFUNC_DEF("rename", 2, js_os_rename ), - JS_CFUNC_MAGIC_DEF("setReadHandler", 2, js_os_setReadHandler, 0 ), - JS_CFUNC_MAGIC_DEF("setWriteHandler", 2, js_os_setReadHandler, 1 ), - JS_CFUNC_DEF("signal", 2, js_os_signal ), - OS_FLAG(SIGINT), - OS_FLAG(SIGABRT), - OS_FLAG(SIGFPE), - OS_FLAG(SIGILL), - OS_FLAG(SIGSEGV), - OS_FLAG(SIGTERM), -#if !defined(_WIN32) && !defined(__wasi__) - OS_FLAG(SIGQUIT), - OS_FLAG(SIGPIPE), - OS_FLAG(SIGALRM), - OS_FLAG(SIGUSR1), - OS_FLAG(SIGUSR2), - OS_FLAG(SIGCHLD), - OS_FLAG(SIGCONT), - OS_FLAG(SIGSTOP), - OS_FLAG(SIGTSTP), - OS_FLAG(SIGTTIN), - OS_FLAG(SIGTTOU), - JS_CFUNC_DEF("cputime", 0, js_os_cputime ), -#endif - JS_CFUNC_DEF("exePath", 0, js_os_exepath ), - JS_CFUNC_DEF("now", 0, js_os_now ), - JS_CFUNC_MAGIC_DEF("setTimeout", 2, js_os_setTimeout, 0 ), - JS_CFUNC_MAGIC_DEF("setInterval", 2, js_os_setTimeout, 1 ), - // per spec: both functions can cancel timeouts and intervals - JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ), - JS_CFUNC_DEF("clearInterval", 1, js_os_clearTimeout ), - JS_CFUNC_DEF("sleepAsync", 1, js_os_sleepAsync ), - JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ), - JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ), - JS_CFUNC_DEF("chdir", 0, js_os_chdir ), - JS_CFUNC_DEF("mkdir", 1, js_os_mkdir ), - JS_CFUNC_DEF("readdir", 1, js_os_readdir ), - /* st_mode constants */ - OS_FLAG(S_IFMT), - OS_FLAG(S_IFIFO), - OS_FLAG(S_IFCHR), - OS_FLAG(S_IFDIR), - OS_FLAG(S_IFBLK), - OS_FLAG(S_IFREG), -#if !defined(_WIN32) - OS_FLAG(S_IFSOCK), - OS_FLAG(S_IFLNK), - OS_FLAG(S_ISGID), - OS_FLAG(S_ISUID), -#endif - JS_CFUNC_MAGIC_DEF("stat", 1, js_os_stat, 0 ), - JS_CFUNC_DEF("utimes", 3, js_os_utimes ), - JS_CFUNC_DEF("sleep", 1, js_os_sleep ), -#if !defined(__wasi__) - JS_CFUNC_DEF("realpath", 1, js_os_realpath ), -#endif -#if !defined(_WIN32) && !defined(__wasi__) && !(defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH)) - JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), - JS_CFUNC_DEF("symlink", 2, js_os_symlink ), - JS_CFUNC_DEF("readlink", 1, js_os_readlink ), - JS_CFUNC_DEF("exec", 1, js_os_exec ), - JS_CFUNC_DEF("getpid", 0, js_os_getpid ), - JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ), - OS_FLAG(WNOHANG), - JS_CFUNC_DEF("pipe", 0, js_os_pipe ), - JS_CFUNC_DEF("kill", 2, js_os_kill ), - JS_CFUNC_DEF("dup", 1, js_os_dup ), - JS_CFUNC_DEF("dup2", 2, js_os_dup2 ), -#endif -}; - -static int js_os_init(JSContext *ctx, JSModuleDef *m) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - - ts->can_js_os_poll = true; - -#ifdef USE_WORKER - { - JSValue proto, obj; - /* Worker class */ - JS_NewClassID(rt, &ts->worker_class_id); - JS_NewClass(rt, ts->worker_class_id, &js_worker_class); - proto = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, proto, js_worker_proto_funcs, countof(js_worker_proto_funcs)); - - obj = JS_NewCFunction2(ctx, js_worker_ctor, "Worker", 1, - JS_CFUNC_constructor, 0); - JS_SetConstructor(ctx, obj, proto); - - JS_SetClassProto(ctx, ts->worker_class_id, proto); - - /* set 'Worker.parent' if necessary */ - if (ts->recv_pipe && ts->send_pipe) { - JS_DefinePropertyValueStr(ctx, obj, "parent", - js_worker_ctor_internal(ctx, JS_UNDEFINED, ts->recv_pipe, ts->send_pipe), - JS_PROP_C_W_E); - } - - JS_SetModuleExport(ctx, m, "Worker", obj); - } -#endif /* USE_WORKER */ - - return JS_SetModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs)); -} - -JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name) -{ - JSModuleDef *m; - m = JS_NewCModule(ctx, module_name, js_os_init); - if (!m) - return NULL; - JS_AddModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs)); -#ifdef USE_WORKER - JS_AddModuleExport(ctx, m, "Worker"); -#endif - return m; -} - -/**********************************************************/ - -static JSValue js_print(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ -#ifdef _WIN32 - HANDLE handle; - DWORD mode; -#endif - const char *s; - JSValueConst v; - DynBuf b; - int i; - - dbuf_init(&b); - for(i = 0; i < argc; i++) { - v = argv[i]; - s = JS_ToCString(ctx, v); - if (!s && JS_IsObject(v)) { - JS_FreeValue(ctx, JS_GetException(ctx)); - JSValue t = JS_ToObjectString(ctx, v); - s = JS_ToCString(ctx, t); - JS_FreeValue(ctx, t); - } - if (s) { - dbuf_printf(&b, "%s%s", &" "[!i], s); - JS_FreeCString(ctx, s); - } else { - dbuf_printf(&b, "%s", &" "[!i]); - JS_FreeValue(ctx, JS_GetException(ctx)); - } - } - dbuf_putc(&b, '\n'); -#ifdef _WIN32 - // use WriteConsoleA with CP_UTF8 for better Unicode handling vis-a-vis - // the mangling that happens when going through msvcrt's stdio layer, - // *except* when stdout is redirected to something that is not a console - handle = (HANDLE)_get_osfhandle(/*STDOUT_FILENO*/1); // don't CloseHandle - if (GetFileType(handle) != FILE_TYPE_CHAR) - goto fallback; - if (!GetConsoleMode(handle, &mode)) - goto fallback; - handle = GetStdHandle(STD_OUTPUT_HANDLE); - if (handle == INVALID_HANDLE_VALUE) - goto fallback; - mode = GetConsoleOutputCP(); - SetConsoleOutputCP(CP_UTF8); - WriteConsoleA(handle, b.buf, b.size, NULL, NULL); - SetConsoleOutputCP(mode); - FlushFileBuffers(handle); - goto done; -fallback: -#endif - fwrite(b.buf, 1, b.size, stdout); - fflush(stdout); - goto done; // avoid unused label warning -done: - dbuf_free(&b); - return JS_UNDEFINED; -} - -void js_std_add_helpers(JSContext *ctx, int argc, char **argv) -{ - JSValue global_obj, console, args; - int i; - - /* XXX: should these global definitions be enumerable? */ - global_obj = JS_GetGlobalObject(ctx); - - console = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, console, "log", - JS_NewCFunction(ctx, js_print, "log", 1)); - JS_SetPropertyStr(ctx, global_obj, "console", console); - - /* same methods as the mozilla JS shell */ - if (argc >= 0) { - args = JS_NewArray(ctx); - for(i = 0; i < argc; i++) { - JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, argv[i])); - } - JS_SetPropertyStr(ctx, global_obj, "scriptArgs", args); - } - - JS_SetPropertyStr(ctx, global_obj, "print", - JS_NewCFunction(ctx, js_print, "print", 1)); - - JS_FreeValue(ctx, global_obj); -} - -static void js_std_finalize(JSRuntime *rt, void *arg) -{ - JSThreadState *ts = arg; - js_set_thread_state(rt, NULL); - js_free_rt(rt, ts); -} - -void js_std_init_handlers(JSRuntime *rt) -{ - JSThreadState *ts; - - ts = js_mallocz_rt(rt, sizeof(*ts)); - if (!ts) { - fprintf(stderr, "Could not allocate memory for the worker"); - exit(1); - } - init_list_head(&ts->os_rw_handlers); - init_list_head(&ts->os_signal_handlers); - init_list_head(&ts->os_timers); - init_list_head(&ts->port_list); - init_list_head(&ts->rejected_promise_list); - - ts->next_timer_id = 1; - - js_set_thread_state(rt, ts); - JS_AddRuntimeFinalizer(rt, js_std_finalize, ts); - -#ifdef USE_WORKER - /* set the SharedArrayBuffer memory handlers */ - { - JSSharedArrayBufferFunctions sf; - memset(&sf, 0, sizeof(sf)); - sf.sab_alloc = js_sab_alloc; - sf.sab_free = js_sab_free; - sf.sab_dup = js_sab_dup; - JS_SetSharedArrayBufferFunctions(rt, &sf); - } -#endif -} - -static void free_rp(JSRuntime *rt, JSRejectedPromiseEntry *rp) -{ - list_del(&rp->link); - JS_FreeValueRT(rt, rp->promise); - JS_FreeValueRT(rt, rp->reason); - js_free_rt(rt, rp); -} - -void js_std_free_handlers(JSRuntime *rt) -{ - JSThreadState *ts = js_get_thread_state(rt); - struct list_head *el, *el1; - - list_for_each_safe(el, el1, &ts->os_rw_handlers) { - JSOSRWHandler *rh = list_entry(el, JSOSRWHandler, link); - free_rw_handler(rt, rh); - } - - list_for_each_safe(el, el1, &ts->os_signal_handlers) { - JSOSSignalHandler *sh = list_entry(el, JSOSSignalHandler, link); - free_sh(rt, sh); - } - - list_for_each_safe(el, el1, &ts->os_timers) { - JSOSTimer *th = list_entry(el, JSOSTimer, link); - free_timer(rt, th); - } - - list_for_each_safe(el, el1, &ts->rejected_promise_list) { - JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); - free_rp(rt, rp); - } - -#ifdef USE_WORKER - /* XXX: free port_list ? */ - js_free_message_pipe(ts->recv_pipe); - js_free_message_pipe(ts->send_pipe); -#endif -} - -static void js_dump_obj(JSContext *ctx, FILE *f, JSValueConst val) -{ - const char *str; - - str = JS_ToCString(ctx, val); - if (str) { - fprintf(f, "%s\n", str); - JS_FreeCString(ctx, str); - } else { - fprintf(f, "[exception]\n"); - } -} - -static void js_std_dump_error1(JSContext *ctx, JSValueConst exception_val) -{ - JSValue val; - bool is_error; - - is_error = JS_IsError(exception_val); - js_dump_obj(ctx, stderr, exception_val); - if (is_error) { - val = JS_GetPropertyStr(ctx, exception_val, "stack"); - } else { - js_std_cmd(/*ErrorBackTrace*/2, ctx, &val); - } - if (!JS_IsUndefined(val)) { - js_dump_obj(ctx, stderr, val); - JS_FreeValue(ctx, val); - } -} - -void js_std_dump_error(JSContext *ctx) -{ - JSValue exception_val; - - exception_val = JS_GetException(ctx); - js_std_dump_error1(ctx, exception_val); - JS_FreeValue(ctx, exception_val); -} - -static JSRejectedPromiseEntry *find_rejected_promise(JSContext *ctx, JSThreadState *ts, - JSValueConst promise) -{ - struct list_head *el; - - list_for_each(el, &ts->rejected_promise_list) { - JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); - if (JS_IsSameValue(ctx, rp->promise, promise)) - return rp; - } - return NULL; -} - -void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, - JSValueConst reason, - bool is_handled, void *opaque) -{ - - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSRejectedPromiseEntry *rp = find_rejected_promise(ctx, ts, promise); - - if (is_handled) { - /* the rejection is handled, so the entry can be removed if present */ - if (rp) - free_rp(rt, rp); - } else { - /* add a new entry if needed */ - if (!rp) { - rp = js_malloc_rt(rt, sizeof(*rp)); - if (rp) { - rp->promise = JS_DupValue(ctx, promise); - rp->reason = JS_DupValue(ctx, reason); - list_add_tail(&rp->link, &ts->rejected_promise_list); - } - } - } -} - -/* check if there are pending promise rejections. It must be done - asynchrously in case a rejected promise is handled later. Currently - we do it once the application is about to sleep. It could be done - more often if needed. */ -static void js_std_promise_rejection_check(JSContext *ctx) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - struct list_head *el; - - if (unlikely(!list_empty(&ts->rejected_promise_list))) { - list_for_each(el, &ts->rejected_promise_list) { - JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); - fprintf(stderr, "Possibly unhandled promise rejection: "); - js_std_dump_error1(ctx, rp->reason); - fflush(stderr); - } - exit(1); - } -} - -/* main loop which calls the user JS callbacks */ -int js_std_loop(JSContext *ctx) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSContext *ctx1; - int err; - - for(;;) { - /* execute the pending jobs */ - for(;;) { - err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); - if (err <= 0) { - if (err < 0) - goto done; - break; - } - } - - js_std_promise_rejection_check(ctx); - - if (!ts->can_js_os_poll || js_os_poll(ctx)) - break; - } -done: - return JS_HasException(ctx); -} - -/* Wait for a promise and execute pending jobs while waiting for - it. Return the promise result or JS_EXCEPTION in case of promise - rejection. */ -JSValue js_std_await(JSContext *ctx, JSValue obj) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSValue ret; - int state; - - for(;;) { - state = JS_PromiseState(ctx, obj); - if (state == JS_PROMISE_FULFILLED) { - ret = JS_PromiseResult(ctx, obj); - JS_FreeValue(ctx, obj); - break; - } else if (state == JS_PROMISE_REJECTED) { - ret = JS_Throw(ctx, JS_PromiseResult(ctx, obj)); - JS_FreeValue(ctx, obj); - break; - } else if (state == JS_PROMISE_PENDING) { - JSContext *ctx1; - int err; - err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); - if (err < 0) { - js_std_dump_error(ctx1); - } - if (err == 0) - js_std_promise_rejection_check(ctx); - if (ts->can_js_os_poll) - js_os_poll(ctx); - } else { - /* not a promise */ - ret = obj; - break; - } - } - return ret; -} - -void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, - int load_only) -{ - JSValue obj, val; - obj = JS_ReadObject(ctx, buf, buf_len, JS_READ_OBJ_BYTECODE); - if (JS_IsException(obj)) - goto exception; - if (load_only) { - if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { - if (js_module_set_import_meta(ctx, obj, false, false) < 0) - goto exception; - } - JS_FreeValue(ctx, obj); - } else { - if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { - if (JS_ResolveModule(ctx, obj) < 0) { - JS_FreeValue(ctx, obj); - goto exception; - } - if (js_module_set_import_meta(ctx, obj, false, true) < 0) - goto exception; - val = JS_EvalFunction(ctx, obj); - val = js_std_await(ctx, val); - } else { - val = JS_EvalFunction(ctx, obj); - } - if (JS_IsException(val)) { - exception: - js_std_dump_error(ctx); - exit(1); - } - JS_FreeValue(ctx, val); - } -} - -static JSValue js_bjson_read(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - uint8_t *buf; - uint64_t pos, len; - JSValue obj; - size_t size; - int flags; - - if (JS_ToIndex(ctx, &pos, argv[1])) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &len, argv[2])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &flags, argv[3])) - return JS_EXCEPTION; - buf = JS_GetArrayBuffer(ctx, &size, argv[0]); - if (!buf) - return JS_EXCEPTION; - if (pos + len > size) - return JS_ThrowRangeError(ctx, "array buffer overflow"); - obj = JS_ReadObject(ctx, buf + pos, len, flags); - return obj; -} - -static JSValue js_bjson_write(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - size_t len; - uint8_t *buf; - JSValue array; - int flags; - - if (JS_ToInt32(ctx, &flags, argv[1])) - return JS_EXCEPTION; - buf = JS_WriteObject(ctx, &len, argv[0], flags); - if (!buf) - return JS_EXCEPTION; - array = JS_NewArrayBufferCopy(ctx, buf, len); - js_free(ctx, buf); - return array; -} - - -static const JSCFunctionListEntry js_bjson_funcs[] = { - JS_CFUNC_DEF("read", 4, js_bjson_read ), - JS_CFUNC_DEF("write", 2, js_bjson_write ), -#define DEF(x) JS_PROP_INT32_DEF(#x, JS_##x, JS_PROP_CONFIGURABLE) - DEF(READ_OBJ_BYTECODE), - DEF(READ_OBJ_REFERENCE), - DEF(READ_OBJ_SAB), - DEF(WRITE_OBJ_BYTECODE), - DEF(WRITE_OBJ_REFERENCE), - DEF(WRITE_OBJ_SAB), - DEF(WRITE_OBJ_STRIP_DEBUG), - DEF(WRITE_OBJ_STRIP_SOURCE), -#undef DEF -}; - -static int js_bjson_init(JSContext *ctx, JSModuleDef *m) -{ - return JS_SetModuleExportList(ctx, m, js_bjson_funcs, - countof(js_bjson_funcs)); -} - -JSModuleDef *js_init_module_bjson(JSContext *ctx, const char *module_name) -{ - JSModuleDef *m; - m = JS_NewCModule(ctx, module_name, js_bjson_init); - if (!m) - return NULL; - JS_AddModuleExportList(ctx, m, js_bjson_funcs, countof(js_bjson_funcs)); - return m; -} diff --git a/vendor/quickjs/source_ng/quickjs-libc.h b/vendor/quickjs/source_ng/quickjs-libc.h deleted file mode 100755 index 6af5a639d..000000000 --- a/vendor/quickjs/source_ng/quickjs-libc.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * QuickJS C library - * - * Copyright (c) 2017-2018 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef QUICKJS_LIBC_H -#define QUICKJS_LIBC_H - -#include -#include -#include - -#include "quickjs.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(__GNUC__) || defined(__clang__) -#define JS_EXTERN __attribute__((visibility("default"))) -#else -#define JS_EXTERN /* nothing */ -#endif - -JS_EXTERN JSModuleDef *js_init_module_std(JSContext *ctx, - const char *module_name); -JS_EXTERN JSModuleDef *js_init_module_os(JSContext *ctx, - const char *module_name); -JS_EXTERN JSModuleDef *js_init_module_bjson(JSContext *ctx, - const char *module_name); -JS_EXTERN void js_std_add_helpers(JSContext *ctx, int argc, char **argv); -JS_EXTERN int js_std_loop(JSContext *ctx); -JS_EXTERN JSValue js_std_await(JSContext *ctx, JSValue obj); -JS_EXTERN void js_std_init_handlers(JSRuntime *rt); -JS_EXTERN void js_std_free_handlers(JSRuntime *rt); -JS_EXTERN void js_std_dump_error(JSContext *ctx); -JS_EXTERN uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, - const char *filename); -JS_EXTERN int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, - bool use_realpath, bool is_main); -JS_EXTERN JSModuleDef *js_module_loader(JSContext *ctx, - const char *module_name, void *opaque); -JS_EXTERN void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, - size_t buf_len, int flags); -JS_EXTERN void js_std_promise_rejection_tracker(JSContext *ctx, - JSValueConst promise, - JSValueConst reason, - bool is_handled, - void *opaque); -JS_EXTERN void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)); - -#undef JS_EXTERN - -#ifdef __cplusplus -} /* extern "C" { */ -#endif - -#endif /* QUICKJS_LIBC_H */ diff --git a/vendor/quickjs/source_ng/quickjs-opcode.h b/vendor/quickjs/source_ng/quickjs-opcode.h deleted file mode 100755 index bd5be754e..000000000 --- a/vendor/quickjs/source_ng/quickjs-opcode.h +++ /dev/null @@ -1,371 +0,0 @@ -/* - * QuickJS opcode definitions - * - * Copyright (c) 2017-2018 Fabrice Bellard - * Copyright (c) 2017-2018 Charlie Gordon - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifdef FMT -FMT(none) -FMT(none_int) -FMT(none_loc) -FMT(none_arg) -FMT(none_var_ref) -FMT(u8) -FMT(i8) -FMT(loc8) -FMT(const8) -FMT(label8) -FMT(u16) -FMT(i16) -FMT(label16) -FMT(npop) -FMT(npopx) -FMT(npop_u16) -FMT(loc) -FMT(arg) -FMT(var_ref) -FMT(u32) -FMT(u32x2) -FMT(i32) -FMT(const) -FMT(label) -FMT(atom) -FMT(atom_u8) -FMT(atom_u16) -FMT(atom_label_u8) -FMT(atom_label_u16) -FMT(label_u16) -#undef FMT -#endif /* FMT */ - -#ifdef DEF - -#ifndef def -#define def(id, size, n_pop, n_push, f) DEF(id, size, n_pop, n_push, f) -#endif - -DEF(invalid, 1, 0, 0, none) /* never emitted */ - -/* push values */ -DEF( push_i32, 5, 0, 1, i32) -DEF( push_const, 5, 0, 1, const) -DEF( fclosure, 5, 0, 1, const) /* must follow push_const */ -DEF(push_atom_value, 5, 0, 1, atom) -DEF( private_symbol, 5, 0, 1, atom) -DEF( undefined, 1, 0, 1, none) -DEF( null, 1, 0, 1, none) -DEF( push_this, 1, 0, 1, none) /* only used at the start of a function */ -DEF( push_false, 1, 0, 1, none) -DEF( push_true, 1, 0, 1, none) -DEF( object, 1, 0, 1, none) -DEF( special_object, 2, 0, 1, u8) /* only used at the start of a function */ -DEF( rest, 3, 0, 1, u16) /* only used at the start of a function */ - -DEF( drop, 1, 1, 0, none) /* a -> */ -DEF( nip, 1, 2, 1, none) /* a b -> b */ -DEF( nip1, 1, 3, 2, none) /* a b c -> b c */ -DEF( dup, 1, 1, 2, none) /* a -> a a */ -DEF( dup1, 1, 2, 3, none) /* a b -> a a b */ -DEF( dup2, 1, 2, 4, none) /* a b -> a b a b */ -DEF( dup3, 1, 3, 6, none) /* a b c -> a b c a b c */ -DEF( insert2, 1, 2, 3, none) /* obj a -> a obj a (dup_x1) */ -DEF( insert3, 1, 3, 4, none) /* obj prop a -> a obj prop a (dup_x2) */ -DEF( insert4, 1, 4, 5, none) /* this obj prop a -> a this obj prop a */ -DEF( perm3, 1, 3, 3, none) /* obj a b -> a obj b */ -DEF( perm4, 1, 4, 4, none) /* obj prop a b -> a obj prop b */ -DEF( perm5, 1, 5, 5, none) /* this obj prop a b -> a this obj prop b */ -DEF( swap, 1, 2, 2, none) /* a b -> b a */ -DEF( swap2, 1, 4, 4, none) /* a b c d -> c d a b */ -DEF( rot3l, 1, 3, 3, none) /* x a b -> a b x */ -DEF( rot3r, 1, 3, 3, none) /* a b x -> x a b */ -DEF( rot4l, 1, 4, 4, none) /* x a b c -> a b c x */ -DEF( rot5l, 1, 5, 5, none) /* x a b c d -> a b c d x */ - -DEF(call_constructor, 3, 2, 1, npop) /* func new.target args -> ret. arguments are not counted in n_pop */ -DEF( call, 3, 1, 1, npop) /* arguments are not counted in n_pop */ -DEF( tail_call, 3, 1, 0, npop) /* arguments are not counted in n_pop */ -DEF( call_method, 3, 2, 1, npop) /* arguments are not counted in n_pop */ -DEF(tail_call_method, 3, 2, 0, npop) /* arguments are not counted in n_pop */ -DEF( array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */ -DEF( apply, 3, 3, 1, u16) -DEF( return, 1, 1, 0, none) -DEF( return_undef, 1, 0, 0, none) -DEF(check_ctor_return, 1, 1, 2, none) -DEF( check_ctor, 1, 0, 0, none) -DEF( init_ctor, 1, 0, 1, none) -DEF( check_brand, 1, 2, 2, none) /* this_obj func -> this_obj func */ -DEF( add_brand, 1, 2, 0, none) /* this_obj home_obj -> */ -DEF( return_async, 1, 1, 0, none) -DEF( throw, 1, 1, 0, none) -DEF( throw_error, 6, 0, 0, atom_u8) -DEF( eval, 5, 1, 1, npop_u16) /* func args... -> ret_val */ -DEF( apply_eval, 3, 2, 1, u16) /* func array -> ret_eval */ -DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a - bytecode string */ -DEF( get_super, 1, 1, 1, none) -DEF( import, 1, 1, 1, none) /* dynamic module import */ - -DEF( check_var, 5, 0, 1, atom) /* check if a variable exists */ -DEF( get_var_undef, 5, 0, 1, atom) /* push undefined if the variable does not exist */ -DEF( get_var, 5, 0, 1, atom) /* throw an exception if the variable does not exist */ -DEF( put_var, 5, 1, 0, atom) /* must come after get_var */ -DEF( put_var_init, 5, 1, 0, atom) /* must come after put_var. Used to initialize a global lexical variable */ -DEF( put_var_strict, 5, 2, 0, atom) /* for strict mode variable write */ - -DEF( get_ref_value, 1, 2, 3, none) -DEF( put_ref_value, 1, 3, 0, none) - -DEF( define_var, 6, 0, 0, atom_u8) -DEF(check_define_var, 6, 0, 0, atom_u8) -DEF( define_func, 6, 1, 0, atom_u8) - -// order matters, see IC counterparts -DEF( get_field, 5, 1, 1, atom) -DEF( get_field2, 5, 1, 2, atom) -DEF( put_field, 5, 2, 0, atom) - -DEF( get_private_field, 1, 2, 1, none) /* obj prop -> value */ -DEF( put_private_field, 1, 3, 0, none) /* obj value prop -> */ -DEF(define_private_field, 1, 3, 1, none) /* obj prop value -> obj */ -DEF( get_array_el, 1, 2, 1, none) -DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */ -DEF( put_array_el, 1, 3, 0, none) -DEF(get_super_value, 1, 3, 1, none) /* this obj prop -> value */ -DEF(put_super_value, 1, 4, 0, none) /* this obj prop value -> */ -DEF( define_field, 5, 2, 1, atom) -DEF( set_name, 5, 1, 1, atom) -DEF(set_name_computed, 1, 2, 2, none) -DEF( set_proto, 1, 2, 1, none) -DEF(set_home_object, 1, 2, 2, none) -DEF(define_array_el, 1, 3, 2, none) -DEF( append, 1, 3, 2, none) /* append enumerated object, update length */ -DEF(copy_data_properties, 2, 3, 3, u8) -DEF( define_method, 6, 2, 1, atom_u8) -DEF(define_method_computed, 2, 3, 1, u8) /* must come after define_method */ -DEF( define_class, 6, 2, 2, atom_u8) /* parent ctor -> ctor proto */ -DEF( define_class_computed, 6, 3, 3, atom_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */ - -DEF( get_loc, 3, 0, 1, loc) -DEF( put_loc, 3, 1, 0, loc) /* must come after get_loc */ -DEF( set_loc, 3, 1, 1, loc) /* must come after put_loc */ -DEF( get_arg, 3, 0, 1, arg) -DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */ -DEF( set_arg, 3, 1, 1, arg) /* must come after put_arg */ -DEF( get_var_ref, 3, 0, 1, var_ref) -DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */ -DEF( set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */ -DEF(set_loc_uninitialized, 3, 0, 0, loc) -DEF( get_loc_check, 3, 0, 1, loc) -DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */ -DEF( put_loc_check_init, 3, 1, 0, loc) -DEF(get_var_ref_check, 3, 0, 1, var_ref) -DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */ -DEF(put_var_ref_check_init, 3, 1, 0, var_ref) -DEF( close_loc, 3, 0, 0, loc) -DEF( if_false, 5, 1, 0, label) -DEF( if_true, 5, 1, 0, label) /* must come after if_false */ -DEF( goto, 5, 0, 0, label) /* must come after if_true */ -DEF( catch, 5, 0, 1, label) -DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */ -DEF( ret, 1, 1, 0, none) /* used to return from the finally block */ -DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */ - -DEF( to_object, 1, 1, 1, none) -//DEF( to_string, 1, 1, 1, none) -DEF( to_propkey, 1, 1, 1, none) -DEF( to_propkey2, 1, 2, 2, none) - -DEF( with_get_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ -DEF( with_put_var, 10, 2, 1, atom_label_u8) /* must be in the same order as scope_xxx */ -DEF(with_delete_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ -DEF( with_make_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ -DEF( with_get_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ -DEF(with_get_ref_undef, 10, 1, 0, atom_label_u8) - -DEF( make_loc_ref, 7, 0, 2, atom_u16) -DEF( make_arg_ref, 7, 0, 2, atom_u16) -DEF(make_var_ref_ref, 7, 0, 2, atom_u16) -DEF( make_var_ref, 5, 0, 2, atom) - -DEF( for_in_start, 1, 1, 1, none) -DEF( for_of_start, 1, 1, 3, none) -DEF(for_await_of_start, 1, 1, 3, none) -DEF( for_in_next, 1, 1, 3, none) -DEF( for_of_next, 2, 3, 5, u8) -DEF(iterator_check_object, 1, 1, 1, none) -DEF(iterator_get_value_done, 1, 1, 2, none) -DEF( iterator_close, 1, 3, 0, none) -DEF( iterator_next, 1, 4, 4, none) -DEF( iterator_call, 2, 4, 5, u8) -DEF( initial_yield, 1, 0, 0, none) -DEF( yield, 1, 1, 2, none) -DEF( yield_star, 1, 1, 2, none) -DEF(async_yield_star, 1, 1, 2, none) -DEF( await, 1, 1, 1, none) - -/* arithmetic/logic operations */ -DEF( neg, 1, 1, 1, none) -DEF( plus, 1, 1, 1, none) -DEF( dec, 1, 1, 1, none) -DEF( inc, 1, 1, 1, none) -DEF( post_dec, 1, 1, 2, none) -DEF( post_inc, 1, 1, 2, none) -DEF( dec_loc, 2, 0, 0, loc8) -DEF( inc_loc, 2, 0, 0, loc8) -DEF( add_loc, 2, 1, 0, loc8) -DEF( not, 1, 1, 1, none) -DEF( lnot, 1, 1, 1, none) -DEF( typeof, 1, 1, 1, none) -DEF( delete, 1, 2, 1, none) -DEF( delete_var, 5, 0, 1, atom) - -/* warning: order matters (see js_parse_assign_expr) */ -DEF( mul, 1, 2, 1, none) -DEF( div, 1, 2, 1, none) -DEF( mod, 1, 2, 1, none) -DEF( add, 1, 2, 1, none) -DEF( sub, 1, 2, 1, none) -DEF( shl, 1, 2, 1, none) -DEF( sar, 1, 2, 1, none) -DEF( shr, 1, 2, 1, none) -DEF( and, 1, 2, 1, none) -DEF( xor, 1, 2, 1, none) -DEF( or, 1, 2, 1, none) -DEF( pow, 1, 2, 1, none) - -DEF( lt, 1, 2, 1, none) -DEF( lte, 1, 2, 1, none) -DEF( gt, 1, 2, 1, none) -DEF( gte, 1, 2, 1, none) -DEF( instanceof, 1, 2, 1, none) -DEF( in, 1, 2, 1, none) -DEF( eq, 1, 2, 1, none) -DEF( neq, 1, 2, 1, none) -DEF( strict_eq, 1, 2, 1, none) -DEF( strict_neq, 1, 2, 1, none) -DEF(is_undefined_or_null, 1, 1, 1, none) -DEF( private_in, 1, 2, 1, none) -DEF(push_bigint_i32, 5, 0, 1, i32) -/* must be the last non short and non temporary opcode */ -DEF( nop, 1, 0, 0, none) - -/* temporary opcodes: never emitted in the final bytecode */ - -def( enter_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ -def( leave_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ - -def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */ - -def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ -def( scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ -def( scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */ -def(scope_delete_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ -def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */ -def( scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ -def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ -def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */ -def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */ -def(scope_put_private_field, 7, 2, 0, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */ -def(scope_in_private_field, 7, 1, 1, atom_u16) /* obj -> res emitted in phase 1, removed in phase 2 */ -def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */ -def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */ -def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */ - -def( source_loc, 9, 0, 0, u32x2) /* emitted in phase 1, removed in phase 3 */ - -DEF( push_minus1, 1, 0, 1, none_int) -DEF( push_0, 1, 0, 1, none_int) -DEF( push_1, 1, 0, 1, none_int) -DEF( push_2, 1, 0, 1, none_int) -DEF( push_3, 1, 0, 1, none_int) -DEF( push_4, 1, 0, 1, none_int) -DEF( push_5, 1, 0, 1, none_int) -DEF( push_6, 1, 0, 1, none_int) -DEF( push_7, 1, 0, 1, none_int) -DEF( push_i8, 2, 0, 1, i8) -DEF( push_i16, 3, 0, 1, i16) -DEF( push_const8, 2, 0, 1, const8) -DEF( fclosure8, 2, 0, 1, const8) /* must follow push_const8 */ -DEF(push_empty_string, 1, 0, 1, none) - -DEF( get_loc8, 2, 0, 1, loc8) -DEF( put_loc8, 2, 1, 0, loc8) -DEF( set_loc8, 2, 1, 1, loc8) - -DEF( get_loc0_loc1, 1, 0, 2, none_loc) -DEF( get_loc0, 1, 0, 1, none_loc) -DEF( get_loc1, 1, 0, 1, none_loc) -DEF( get_loc2, 1, 0, 1, none_loc) -DEF( get_loc3, 1, 0, 1, none_loc) -DEF( put_loc0, 1, 1, 0, none_loc) -DEF( put_loc1, 1, 1, 0, none_loc) -DEF( put_loc2, 1, 1, 0, none_loc) -DEF( put_loc3, 1, 1, 0, none_loc) -DEF( set_loc0, 1, 1, 1, none_loc) -DEF( set_loc1, 1, 1, 1, none_loc) -DEF( set_loc2, 1, 1, 1, none_loc) -DEF( set_loc3, 1, 1, 1, none_loc) -DEF( get_arg0, 1, 0, 1, none_arg) -DEF( get_arg1, 1, 0, 1, none_arg) -DEF( get_arg2, 1, 0, 1, none_arg) -DEF( get_arg3, 1, 0, 1, none_arg) -DEF( put_arg0, 1, 1, 0, none_arg) -DEF( put_arg1, 1, 1, 0, none_arg) -DEF( put_arg2, 1, 1, 0, none_arg) -DEF( put_arg3, 1, 1, 0, none_arg) -DEF( set_arg0, 1, 1, 1, none_arg) -DEF( set_arg1, 1, 1, 1, none_arg) -DEF( set_arg2, 1, 1, 1, none_arg) -DEF( set_arg3, 1, 1, 1, none_arg) -DEF( get_var_ref0, 1, 0, 1, none_var_ref) -DEF( get_var_ref1, 1, 0, 1, none_var_ref) -DEF( get_var_ref2, 1, 0, 1, none_var_ref) -DEF( get_var_ref3, 1, 0, 1, none_var_ref) -DEF( put_var_ref0, 1, 1, 0, none_var_ref) -DEF( put_var_ref1, 1, 1, 0, none_var_ref) -DEF( put_var_ref2, 1, 1, 0, none_var_ref) -DEF( put_var_ref3, 1, 1, 0, none_var_ref) -DEF( set_var_ref0, 1, 1, 1, none_var_ref) -DEF( set_var_ref1, 1, 1, 1, none_var_ref) -DEF( set_var_ref2, 1, 1, 1, none_var_ref) -DEF( set_var_ref3, 1, 1, 1, none_var_ref) - -DEF( get_length, 1, 1, 1, none) - -DEF( if_false8, 2, 1, 0, label8) -DEF( if_true8, 2, 1, 0, label8) /* must come after if_false8 */ -DEF( goto8, 2, 0, 0, label8) /* must come after if_true8 */ -DEF( goto16, 3, 0, 0, label16) - -DEF( call0, 1, 1, 1, npopx) -DEF( call1, 1, 1, 1, npopx) -DEF( call2, 1, 1, 1, npopx) -DEF( call3, 1, 1, 1, npopx) - -DEF( is_undefined, 1, 1, 1, none) -DEF( is_null, 1, 1, 1, none) -DEF(typeof_is_undefined, 1, 1, 1, none) -DEF( typeof_is_function, 1, 1, 1, none) - -#undef DEF -#undef def -#endif /* DEF */ diff --git a/vendor/quickjs/source_ng/quickjs.c b/vendor/quickjs/source_ng/quickjs.c deleted file mode 100755 index fd1be4407..000000000 --- a/vendor/quickjs/source_ng/quickjs.c +++ /dev/null @@ -1,58356 +0,0 @@ -/* - * QuickJS Javascript Engine - * - * Copyright (c) 2017-2025 Fabrice Bellard - * Copyright (c) 2017-2025 Charlie Gordon - * Copyright (c) 2023-2025 Ben Noordhuis - * Copyright (c) 2023-2025 Saúl Ibarra Corretgé - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include -#include -#include -#include -#include -#include -#if !defined(_MSC_VER) -#include -#if defined(_WIN32) -#include -#endif -#endif -#if defined(_WIN32) -#include -#endif -#include -#include -#include - -#include "cutils.h" -#include "list.h" -#include "quickjs.h" -#include "libregexp.h" -#include "dtoa.h" - -#if defined(EMSCRIPTEN) || defined(_MSC_VER) -#define DIRECT_DISPATCH 0 -#else -#define DIRECT_DISPATCH 1 -#endif - -#if defined(__APPLE__) -#define MALLOC_OVERHEAD 0 -#else -#define MALLOC_OVERHEAD 8 -#endif - -#if defined(__NEWLIB__) -#define NO_TM_GMTOFF -#endif - -// atomic_store etc. are completely busted in recent versions of tcc; -// somehow the compiler forgets to load |ptr| into %rdi when calling -// the __atomic_*() helpers in its lib/stdatomic.c and lib/atomic.S -#if !defined(__TINYC__) && !defined(EMSCRIPTEN) && !defined(__wasi__) && !__STDC_NO_ATOMICS__ -#include "quickjs-c-atomics.h" -#define CONFIG_ATOMICS -#endif - -#ifndef __GNUC__ -#define __extension__ -#endif - -#ifndef NDEBUG -#define ENABLE_DUMPS -#endif - -//#define FORCE_GC_AT_MALLOC /* test the GC by forcing it before each object allocation */ - -#define check_dump_flag(rt, flag) ((rt->dump_flags & (flag +0)) == (flag +0)) - -#define STRINGIFY_(x) #x -#define STRINGIFY(x) STRINGIFY_(x) - -#define QJS_VERSION_STRING \ - STRINGIFY(QJS_VERSION_MAJOR) "." STRINGIFY(QJS_VERSION_MINOR) "." STRINGIFY(QJS_VERSION_PATCH) QJS_VERSION_SUFFIX - -const char* JS_GetVersion(void) { - return QJS_VERSION_STRING; -} - -#undef STRINFIGY_ -#undef STRINGIFY - -static inline JSValueConst *vc(JSValue *vals) -{ - return (JSValueConst *)vals; -} - -static inline JSValue unsafe_unconst(JSValueConst v) -{ -#ifdef JS_CHECK_JSVALUE - return (JSValue)v; -#else - return v; -#endif -} - -static inline JSValueConst safe_const(JSValue v) -{ -#ifdef JS_CHECK_JSVALUE - return (JSValueConst)v; -#else - return v; -#endif -} - -enum { - /* classid tag */ /* union usage | properties */ - JS_CLASS_OBJECT = 1, /* must be first */ - JS_CLASS_ARRAY, /* u.array | length */ - JS_CLASS_ERROR, - JS_CLASS_NUMBER, /* u.object_data */ - JS_CLASS_STRING, /* u.object_data */ - JS_CLASS_BOOLEAN, /* u.object_data */ - JS_CLASS_SYMBOL, /* u.object_data */ - JS_CLASS_ARGUMENTS, /* u.array | length */ - JS_CLASS_MAPPED_ARGUMENTS, /* | length */ - JS_CLASS_DATE, /* u.object_data */ - JS_CLASS_MODULE_NS, - JS_CLASS_C_FUNCTION, /* u.cfunc */ - JS_CLASS_BYTECODE_FUNCTION, /* u.func */ - JS_CLASS_BOUND_FUNCTION, /* u.bound_function */ - JS_CLASS_C_FUNCTION_DATA, /* u.c_function_data_record */ - JS_CLASS_GENERATOR_FUNCTION, /* u.func */ - JS_CLASS_FOR_IN_ITERATOR, /* u.for_in_iterator */ - JS_CLASS_REGEXP, /* u.regexp */ - JS_CLASS_ARRAY_BUFFER, /* u.array_buffer */ - JS_CLASS_SHARED_ARRAY_BUFFER, /* u.array_buffer */ - JS_CLASS_UINT8C_ARRAY, /* u.array (typed_array) */ - JS_CLASS_INT8_ARRAY, /* u.array (typed_array) */ - JS_CLASS_UINT8_ARRAY, /* u.array (typed_array) */ - JS_CLASS_INT16_ARRAY, /* u.array (typed_array) */ - JS_CLASS_UINT16_ARRAY, /* u.array (typed_array) */ - JS_CLASS_INT32_ARRAY, /* u.array (typed_array) */ - JS_CLASS_UINT32_ARRAY, /* u.array (typed_array) */ - JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */ - JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */ - JS_CLASS_FLOAT16_ARRAY, /* u.array (typed_array) */ - JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */ - JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */ - JS_CLASS_DATAVIEW, /* u.typed_array */ - JS_CLASS_BIG_INT, /* u.object_data */ - JS_CLASS_MAP, /* u.map_state */ - JS_CLASS_SET, /* u.map_state */ - JS_CLASS_WEAKMAP, /* u.map_state */ - JS_CLASS_WEAKSET, /* u.map_state */ - JS_CLASS_ITERATOR, - JS_CLASS_ITERATOR_CONCAT, /* u.iterator_concat_data */ - JS_CLASS_ITERATOR_HELPER, /* u.iterator_helper_data */ - JS_CLASS_ITERATOR_WRAP, /* u.iterator_wrap_data */ - JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */ - JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */ - JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */ - JS_CLASS_STRING_ITERATOR, /* u.array_iterator_data */ - JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ - JS_CLASS_GENERATOR, /* u.generator_data */ - JS_CLASS_PROXY, /* u.proxy_data */ - JS_CLASS_PROMISE, /* u.promise_data */ - JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ - JS_CLASS_PROMISE_REJECT_FUNCTION, /* u.promise_function_data */ - JS_CLASS_ASYNC_FUNCTION, /* u.func */ - JS_CLASS_ASYNC_FUNCTION_RESOLVE, /* u.async_function_data */ - JS_CLASS_ASYNC_FUNCTION_REJECT, /* u.async_function_data */ - JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ - JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ - JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ - JS_CLASS_WEAK_REF, - JS_CLASS_FINALIZATION_REGISTRY, - JS_CLASS_DOM_EXCEPTION, - JS_CLASS_CALL_SITE, - - JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ -}; - -/* number of typed array types */ -#define JS_TYPED_ARRAY_COUNT (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1) -static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT]; -#define typed_array_size_log2(classid) (typed_array_size_log2[(classid)- JS_CLASS_UINT8C_ARRAY]) - -typedef enum JSErrorEnum { - JS_EVAL_ERROR, - JS_RANGE_ERROR, - JS_REFERENCE_ERROR, - JS_SYNTAX_ERROR, - JS_TYPE_ERROR, - JS_URI_ERROR, - JS_INTERNAL_ERROR, - JS_AGGREGATE_ERROR, - - JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ - JS_PLAIN_ERROR = JS_NATIVE_ERROR_COUNT -} JSErrorEnum; - -#define JS_MAX_LOCAL_VARS 65535 -#define JS_STACK_SIZE_MAX 65534 -#define JS_STRING_LEN_MAX ((1 << 30) - 1) -// 1,024 bytes is about the cutoff point where it starts getting -// more profitable to ref slice than to copy -#define JS_STRING_SLICE_LEN_MAX 1024 // in bytes - -#define __exception __attribute__((warn_unused_result)) - -typedef struct JSShape JSShape; -typedef struct JSString JSString; -typedef struct JSString JSAtomStruct; - -#define JS_VALUE_GET_STRING(v) ((JSString *)JS_VALUE_GET_PTR(v)) - -typedef enum { - JS_GC_PHASE_NONE, - JS_GC_PHASE_DECREF, - JS_GC_PHASE_REMOVE_CYCLES, -} JSGCPhaseEnum; - -typedef struct JSMallocState { - size_t malloc_count; - size_t malloc_size; - size_t malloc_limit; - void *opaque; /* user opaque */ -} JSMallocState; - -typedef struct JSRuntimeFinalizerState { - struct JSRuntimeFinalizerState *next; - JSRuntimeFinalizer *finalizer; - void *arg; -} JSRuntimeFinalizerState; - -typedef struct JSValueLink { - struct JSValueLink *next; - JSValueConst value; -} JSValueLink; - -struct JSRuntime { - JSMallocFunctions mf; - JSMallocState malloc_state; - const char *rt_info; - - int atom_hash_size; /* power of two */ - int atom_count; - int atom_size; - int atom_count_resize; /* resize hash table at this count */ - uint32_t *atom_hash; - JSAtomStruct **atom_array; - int atom_free_index; /* 0 = none */ - - JSClassID js_class_id_alloc; /* counter for user defined classes */ - int class_count; /* size of class_array */ - JSClass *class_array; - - struct list_head context_list; /* list of JSContext.link */ - /* list of JSGCObjectHeader.link. List of allocated GC objects (used - by the garbage collector) */ - struct list_head gc_obj_list; - /* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */ - struct list_head gc_zero_ref_count_list; - struct list_head tmp_obj_list; /* used during GC */ - JSGCPhaseEnum gc_phase : 8; - size_t malloc_gc_threshold; -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - struct list_head string_list; /* list of JSString.link */ -#endif - /* stack limitation */ - uintptr_t stack_size; /* in bytes, 0 if no limit */ - uintptr_t stack_top; - uintptr_t stack_limit; /* lower stack limit */ - - JSValue current_exception; - /* true if inside an out of memory error, to avoid recursing */ - bool in_out_of_memory; - /* true if inside build_backtrace, to avoid recursing */ - bool in_build_stack_trace; - /* true if inside JS_FreeRuntime */ - bool in_free; - - struct JSStackFrame *current_stack_frame; - - JSInterruptHandler *interrupt_handler; - void *interrupt_opaque; - - JSPromiseHook *promise_hook; - void *promise_hook_opaque; - // for smuggling the parent promise from js_promise_then - // to js_promise_constructor - JSValueLink *parent_promise; - - JSHostPromiseRejectionTracker *host_promise_rejection_tracker; - void *host_promise_rejection_tracker_opaque; - - struct list_head job_list; /* list of JSJobEntry.link */ - struct list_head weakref_keepalive_list; /* strong refs kept until the current host job completes */ - - JSModuleNormalizeFunc *module_normalize_func; - JSModuleLoaderFunc *module_loader_func; - void *module_loader_opaque; - /* timestamp for internal use in module evaluation */ - int64_t module_async_evaluation_next_timestamp; - - /* used to allocate, free and clone SharedArrayBuffers */ - JSSharedArrayBufferFunctions sab_funcs; - - bool can_block; /* true if Atomics.wait can block */ - uint32_t dump_flags : 24; - - /* Shape hash table */ - int shape_hash_bits; - int shape_hash_size; - int shape_hash_count; /* number of hashed shapes */ - JSShape **shape_hash; - void *user_opaque; - void *libc_opaque; - JSRuntimeFinalizerState *finalizers; -}; - -struct JSClass { - uint32_t class_id; /* 0 means free entry */ - JSAtom class_name; - JSClassFinalizer *finalizer; - JSClassGCMark *gc_mark; - JSClassCall *call; - /* pointers for exotic behavior, can be NULL if none are present */ - const JSClassExoticMethods *exotic; -}; - -typedef struct JSStackFrame { - struct JSStackFrame *prev_frame; /* NULL if first stack frame */ - JSValue cur_func; /* current function, JS_UNDEFINED if the frame is detached */ - JSValue *arg_buf; /* arguments */ - JSValue *var_buf; /* variables */ - struct list_head var_ref_list; /* list of JSVarRef.link */ - uint8_t *cur_pc; /* only used in bytecode functions : PC of the - instruction after the call */ - uint32_t arg_count : 31; - uint32_t is_strict_mode : 1; - /* only used in generators. Current stack pointer value. NULL if - the function is running. */ - JSValue *cur_sp; -} JSStackFrame; - -typedef struct JSWeakRefKeepAliveEntry { - struct list_head link; - JSValue value; -} JSWeakRefKeepAliveEntry; - -typedef enum { - JS_GC_OBJ_TYPE_JS_OBJECT, - JS_GC_OBJ_TYPE_FUNCTION_BYTECODE, - JS_GC_OBJ_TYPE_SHAPE, - JS_GC_OBJ_TYPE_VAR_REF, - JS_GC_OBJ_TYPE_ASYNC_FUNCTION, - JS_GC_OBJ_TYPE_JS_CONTEXT, -} JSGCObjectTypeEnum; - -/* header for GC objects. GC objects are C data structures with a - reference count that can reference other GC objects. JS Objects are - a particular type of GC object. */ -struct JSGCObjectHeader { - int ref_count; /* must come first, 32-bit */ - JSGCObjectTypeEnum gc_obj_type : 4; - uint8_t mark : 4; /* used by the GC */ - uint8_t dummy1; /* not used by the GC */ - uint16_t dummy2; /* not used by the GC */ - struct list_head link; -}; - -typedef struct JSVarRef { - union { - JSGCObjectHeader header; /* must come first */ - struct { - int __gc_ref_count; /* corresponds to header.ref_count */ - uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ - uint8_t is_detached; - }; - }; - JSValue *pvalue; /* pointer to the value, either on the stack or - to 'value' */ - JSValue value; /* used when the variable is no longer on the stack */ -} JSVarRef; - -typedef struct JSRefCountHeader { - int ref_count; -} JSRefCountHeader; - -/* bigint */ -typedef int32_t js_slimb_t; -typedef uint32_t js_limb_t; -typedef int64_t js_sdlimb_t; -typedef uint64_t js_dlimb_t; - -#define JS_LIMB_DIGITS 9 - -/* Must match the size of short_big_int in JSValueUnion */ -#define JS_LIMB_BITS 32 -#define JS_SHORT_BIG_INT_BITS JS_LIMB_BITS -#define JS_BIGINT_MAX_SIZE ((1024 * 1024) / JS_LIMB_BITS) /* in limbs */ -#define JS_SHORT_BIG_INT_MIN INT32_MIN -#define JS_SHORT_BIG_INT_MAX INT32_MAX - - -typedef struct JSBigInt { - JSRefCountHeader header; /* must come first, 32-bit */ - uint32_t len; /* number of limbs, >= 1 */ - js_limb_t tab[]; /* two's complement representation, always - normalized so that 'len' is the minimum - possible length >= 1 */ -} JSBigInt; - -/* this bigint structure can hold a 64 bit integer */ -typedef struct { - js_limb_t big_int_buf[sizeof(JSBigInt) / sizeof(js_limb_t)]; /* for JSBigInt */ - /* must come just after */ - js_limb_t tab[(64 + JS_LIMB_BITS - 1) / JS_LIMB_BITS]; -} JSBigIntBuf; - -typedef enum { - JS_AUTOINIT_ID_PROTOTYPE, - JS_AUTOINIT_ID_MODULE_NS, - JS_AUTOINIT_ID_PROP, - JS_AUTOINIT_ID_BYTECODE, -} JSAutoInitIDEnum; - -enum { - JS_BUILTIN_ARRAY_FROMASYNC = 1, -}; - -/* must be large enough to have a negligible runtime cost and small - enough to call the interrupt callback often. */ -#define JS_INTERRUPT_COUNTER_INIT 10000 - -struct JSContext { - JSGCObjectHeader header; /* must come first */ - JSRuntime *rt; - struct list_head link; - - uint16_t binary_object_count; - int binary_object_size; - - JSShape *array_shape; /* initial shape for Array objects */ - - JSValue *class_proto; - JSValue function_proto; - JSValue function_ctor; - JSValue array_ctor; - JSValue regexp_ctor; - JSValue promise_ctor; - JSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; - JSValue error_ctor; - JSValue error_back_trace; - JSValue error_prepare_stack; - JSValue error_stack_trace_limit; - JSValue iterator_ctor; - JSValue iterator_ctor_getset; - JSValue iterator_proto; - JSValue async_iterator_proto; - JSValue array_proto_values; - JSValue throw_type_error; - JSValue eval_obj; - - JSValue global_obj; /* global object */ - JSValue global_var_obj; /* contains the global let/const definitions */ - - double time_origin; - - uint64_t random_state; - - /* when the counter reaches zero, JSRutime.interrupt_handler is called */ - int interrupt_counter; - - struct list_head loaded_modules; /* list of JSModuleDef.link */ - - /* if NULL, RegExp compilation is not supported */ - JSValue (*compile_regexp)(JSContext *ctx, JSValueConst pattern, - JSValueConst flags); - /* if NULL, eval is not supported */ - JSValue (*eval_internal)(JSContext *ctx, JSValueConst this_obj, - const char *input, size_t input_len, - const char *filename, int line, int flags, int scope_idx); - void *user_opaque; -}; - -typedef union JSFloat64Union { - double d; - uint64_t u64; - uint32_t u32[2]; -} JSFloat64Union; - -typedef enum { - JS_WEAK_REF_KIND_MAP, - JS_WEAK_REF_KIND_WEAK_REF, - JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY, -} JSWeakRefKindEnum; - -typedef struct JSWeakRefRecord { - JSWeakRefKindEnum kind; - struct JSWeakRefRecord *next_weak_ref; - union { - struct JSMapRecord *map_record; - struct JSWeakRefData *weak_ref_data; - struct JSFinRecEntry *fin_rec_entry; - } u; -} JSWeakRefRecord; - -typedef struct JSMapRecord { - int ref_count; /* used during enumeration to avoid freeing the record */ - bool empty; /* true if the record is deleted */ - struct JSMapState *map; - struct list_head link; - struct list_head hash_link; - JSValue key; - JSValue value; -} JSMapRecord; - -typedef struct JSMapState { - bool is_weak; /* true if WeakSet/WeakMap */ - struct list_head records; /* list of JSMapRecord.link */ - uint32_t record_count; - struct list_head *hash_table; - uint32_t hash_size; /* must be a power of two */ - uint32_t record_count_threshold; /* count at which a hash table - resize is needed */ -} JSMapState; - -enum -{ - JS_TO_STRING_IS_PROPERTY_KEY = 1 << 0, - JS_TO_STRING_NO_SIDE_EFFECTS = 1 << 1, -}; - -enum { - JS_ATOM_TYPE_STRING = 1, - JS_ATOM_TYPE_GLOBAL_SYMBOL, - JS_ATOM_TYPE_SYMBOL, - JS_ATOM_TYPE_PRIVATE, -}; - -enum { - JS_ATOM_HASH_SYMBOL, - JS_ATOM_HASH_PRIVATE, -}; - -typedef enum { - JS_ATOM_KIND_STRING, - JS_ATOM_KIND_SYMBOL, - JS_ATOM_KIND_PRIVATE, -} JSAtomKindEnum; - -typedef enum { - JS_STRING_KIND_NORMAL, - JS_STRING_KIND_SLICE, -} JSStringKind; - -#define JS_ATOM_HASH_MASK ((1 << 29) - 1) - -struct JSString { - JSRefCountHeader header; /* must come first, 32-bit */ - uint32_t len : 31; - uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ - /* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3, - for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3 - XXX: could change encoding to have one more bit in hash */ - uint32_t hash : 29; - uint8_t kind : 1; - uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */ - uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */ - JSWeakRefRecord *first_weak_ref; -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - struct list_head link; /* string list */ -#endif -}; - -typedef struct JSStringSlice { - JSString *parent; - uint32_t start; // in bytes, not characters -} JSStringSlice; - -static inline void *strv(JSString *p) -{ - JSStringSlice *slice; - - switch (p->kind) { - case JS_STRING_KIND_NORMAL: - return (void *)&p[1]; - case JS_STRING_KIND_SLICE: - slice = (void *)&p[1]; - return (char *)&slice->parent[1] + slice->start; - } - abort(); - return NULL; -} - -static inline uint8_t *str8(JSString *p) -{ - return strv(p); -} - -static inline uint16_t *str16(JSString *p) -{ - return strv(p); -} - -typedef struct JSClosureVar { - uint8_t is_local : 1; - uint8_t is_arg : 1; - uint8_t is_const : 1; - uint8_t is_lexical : 1; - uint8_t var_kind : 4; /* see JSVarKindEnum */ - /* 8 bits available */ - uint16_t var_idx; /* is_local = true: index to a normal variable of the - parent function. otherwise: index to a closure - variable of the parent function */ - JSAtom var_name; -} JSClosureVar; - -#define ARG_SCOPE_INDEX 1 -#define ARG_SCOPE_END (-2) - -typedef struct JSVarScope { - int parent; /* index into fd->scopes of the enclosing scope */ - int first; /* index into fd->vars of the last variable in this scope */ -} JSVarScope; - -typedef enum { - /* XXX: add more variable kinds here instead of using bit fields */ - JS_VAR_NORMAL, - JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */ - JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator - function declaration */ - JS_VAR_CATCH, - JS_VAR_FUNCTION_NAME, /* function expression name */ - JS_VAR_PRIVATE_FIELD, - JS_VAR_PRIVATE_METHOD, - JS_VAR_PRIVATE_GETTER, - JS_VAR_PRIVATE_SETTER, /* must come after JS_VAR_PRIVATE_GETTER */ - JS_VAR_PRIVATE_GETTER_SETTER, /* must come after JS_VAR_PRIVATE_SETTER */ -} JSVarKindEnum; - -/* XXX: could use a different structure in bytecode functions to save - memory */ -typedef struct JSVarDef { - JSAtom var_name; - /* index into fd->scopes of this variable lexical scope */ - int scope_level; - /* during compilation: - - if scope_level = 0: scope in which the variable is defined - - if scope_level != 0: index into fd->vars of the next - variable in the same or enclosing lexical scope - in a bytecode function: - index into fd->vars of the next - variable in the same or enclosing lexical scope - */ - int scope_next; - uint8_t is_const : 1; - uint8_t is_lexical : 1; - uint8_t is_captured : 1; - uint8_t is_static_private : 1; /* only used during private class field parsing */ - uint8_t var_kind : 4; /* see JSVarKindEnum */ - /* only used during compilation: function pool index for lexical - variables with var_kind = - JS_VAR_FUNCTION_DECL/JS_VAR_NEW_FUNCTION_DECL or scope level of - the definition of the 'var' variables (they have scope_level = - 0) */ - int func_pool_idx : 24; /* only used during compilation : index in - the constant pool for hoisted function - definition */ -} JSVarDef; - -/* for the encoding of the pc2line table */ -#define PC2LINE_BASE (-1) -#define PC2LINE_RANGE 5 -#define PC2LINE_OP_FIRST 1 -#define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE) - -typedef enum JSFunctionKindEnum { - JS_FUNC_NORMAL = 0, - JS_FUNC_GENERATOR = (1 << 0), - JS_FUNC_ASYNC = (1 << 1), - JS_FUNC_ASYNC_GENERATOR = (JS_FUNC_GENERATOR | JS_FUNC_ASYNC), -} JSFunctionKindEnum; - -typedef struct JSFunctionBytecode { - JSGCObjectHeader header; /* must come first */ - uint8_t is_strict_mode : 1; - uint8_t has_prototype : 1; /* true if a prototype field is necessary */ - uint8_t has_simple_parameter_list : 1; - uint8_t is_derived_class_constructor : 1; - /* true if home_object needs to be initialized */ - uint8_t need_home_object : 1; - uint8_t func_kind : 2; - uint8_t new_target_allowed : 1; - uint8_t super_call_allowed : 1; - uint8_t super_allowed : 1; - uint8_t arguments_allowed : 1; - uint8_t backtrace_barrier : 1; /* stop backtrace on this function */ - /* XXX: 5 bits available */ - uint8_t *byte_code_buf; /* (self pointer) */ - int byte_code_len; - JSAtom func_name; - JSVarDef *vardefs; /* arguments + local variables (arg_count + var_count) (self pointer) */ - JSClosureVar *closure_var; /* list of variables in the closure (self pointer) */ - uint16_t arg_count; - uint16_t var_count; - uint16_t defined_arg_count; /* for length function property */ - uint16_t stack_size; /* maximum stack size */ - JSContext *realm; /* function realm */ - JSValue *cpool; /* constant pool (self pointer) */ - int cpool_count; - int closure_var_count; - JSAtom filename; - int line_num; - int col_num; - int source_len; - int pc2line_len; - uint8_t *pc2line_buf; - char *source; -} JSFunctionBytecode; - -typedef struct JSBoundFunction { - JSValue func_obj; - JSValue this_val; - int argc; - JSValue argv[]; -} JSBoundFunction; - -typedef enum JSIteratorKindEnum { - JS_ITERATOR_KIND_KEY, - JS_ITERATOR_KIND_VALUE, - JS_ITERATOR_KIND_KEY_AND_VALUE, -} JSIteratorKindEnum; - -typedef enum JSIteratorHelperKindEnum { - JS_ITERATOR_HELPER_KIND_DROP, - JS_ITERATOR_HELPER_KIND_EVERY, - JS_ITERATOR_HELPER_KIND_FILTER, - JS_ITERATOR_HELPER_KIND_FIND, - JS_ITERATOR_HELPER_KIND_FLAT_MAP, - JS_ITERATOR_HELPER_KIND_FOR_EACH, - JS_ITERATOR_HELPER_KIND_MAP, - JS_ITERATOR_HELPER_KIND_SOME, - JS_ITERATOR_HELPER_KIND_TAKE, -} JSIteratorHelperKindEnum; - -typedef struct JSForInIterator { - JSValue obj; - bool is_array; - uint32_t array_length; - uint32_t idx; -} JSForInIterator; - -typedef struct JSRegExp { - JSString *pattern; - JSString *bytecode; /* also contains the flags */ -} JSRegExp; - -typedef struct JSProxyData { - JSValue target; - JSValue handler; - uint8_t is_func; - uint8_t is_revoked; -} JSProxyData; - -typedef struct JSArrayBuffer { - int byte_length; /* 0 if detached */ - int max_byte_length; /* -1 if not resizable; >= byte_length otherwise */ - uint8_t detached; - uint8_t shared; /* if shared, the array buffer cannot be detached */ - uint8_t *data; /* NULL if detached */ - struct list_head array_list; - void *opaque; - JSFreeArrayBufferDataFunc *free_func; -} JSArrayBuffer; - -typedef struct JSTypedArray { - struct list_head link; /* link to arraybuffer */ - JSObject *obj; /* back pointer to the TypedArray/DataView object */ - JSObject *buffer; /* based array buffer */ - uint32_t offset; /* byte offset in the array buffer */ - uint32_t length; /* byte length in the array buffer */ - bool track_rab; /* auto-track length of backing array buffer */ -} JSTypedArray; - -typedef struct JSAsyncFunctionState { - JSValue this_val; /* 'this' generator argument */ - int argc; /* number of function arguments */ - bool throw_flag; /* used to throw an exception in JS_CallInternal() */ - JSStackFrame frame; -} JSAsyncFunctionState; - -/* XXX: could use an object instead to avoid the - JS_TAG_ASYNC_FUNCTION tag for the GC */ -typedef struct JSAsyncFunctionData { - JSGCObjectHeader header; /* must come first */ - JSValue resolving_funcs[2]; - bool is_active; /* true if the async function state is valid */ - JSAsyncFunctionState func_state; -} JSAsyncFunctionData; - -typedef struct JSReqModuleEntry { - JSAtom module_name; - JSModuleDef *module; /* used using resolution */ -} JSReqModuleEntry; - -typedef enum JSExportTypeEnum { - JS_EXPORT_TYPE_LOCAL, - JS_EXPORT_TYPE_INDIRECT, -} JSExportTypeEnum; - -typedef struct JSExportEntry { - union { - struct { - int var_idx; /* closure variable index */ - JSVarRef *var_ref; /* if != NULL, reference to the variable */ - } local; /* for local export */ - int req_module_idx; /* module for indirect export */ - } u; - JSExportTypeEnum export_type; - JSAtom local_name; /* '*' if export ns from. not used for local - export after compilation */ - JSAtom export_name; /* exported variable name */ -} JSExportEntry; - -typedef struct JSStarExportEntry { - int req_module_idx; /* in req_module_entries */ -} JSStarExportEntry; - -typedef struct JSImportEntry { - int var_idx; /* closure variable index */ - JSAtom import_name; - int req_module_idx; /* in req_module_entries */ -} JSImportEntry; - -typedef enum { - JS_MODULE_STATUS_UNLINKED, - JS_MODULE_STATUS_LINKING, - JS_MODULE_STATUS_LINKED, - JS_MODULE_STATUS_EVALUATING, - JS_MODULE_STATUS_EVALUATING_ASYNC, - JS_MODULE_STATUS_EVALUATED, -} JSModuleStatus; - -struct JSModuleDef { - JSRefCountHeader header; /* must come first, 32-bit */ - JSAtom module_name; - struct list_head link; - - JSReqModuleEntry *req_module_entries; - int req_module_entries_count; - int req_module_entries_size; - - JSExportEntry *export_entries; - int export_entries_count; - int export_entries_size; - - JSStarExportEntry *star_export_entries; - int star_export_entries_count; - int star_export_entries_size; - - JSImportEntry *import_entries; - int import_entries_count; - int import_entries_size; - - JSValue module_ns; - JSValue func_obj; /* only used for JS modules */ - JSModuleInitFunc *init_func; /* only used for C modules */ - bool has_tla; /* true if func_obj contains await */ - bool resolved; - bool func_created; - JSModuleStatus status : 8; - /* temp use during js_module_link() & js_module_evaluate() */ - int dfs_index, dfs_ancestor_index; - JSModuleDef *stack_prev; - /* temp use during js_module_evaluate() */ - JSModuleDef **async_parent_modules; - int async_parent_modules_count; - int async_parent_modules_size; - int pending_async_dependencies; - bool async_evaluation; - int64_t async_evaluation_timestamp; - JSModuleDef *cycle_root; - JSValue promise; /* corresponds to spec field: capability */ - JSValue resolving_funcs[2]; /* corresponds to spec field: capability */ - /* true if evaluation yielded an exception. It is saved in - eval_exception */ - bool eval_has_exception; - JSValue eval_exception; - JSValue meta_obj; /* for import.meta */ -}; - -typedef struct JSJobEntry { - struct list_head link; - JSContext *ctx; - JSJobFunc *job_func; - int argc; - JSValue argv[]; -} JSJobEntry; - -typedef struct JSProperty { - union { - JSValue value; /* JS_PROP_NORMAL */ - struct { /* JS_PROP_GETSET */ - JSObject *getter; /* NULL if undefined */ - JSObject *setter; /* NULL if undefined */ - } getset; - JSVarRef *var_ref; /* JS_PROP_VARREF */ - struct { /* JS_PROP_AUTOINIT */ - /* in order to use only 2 pointers, we compress the realm - and the init function pointer */ - uintptr_t realm_and_id; /* realm and init_id (JS_AUTOINIT_ID_x) - in the 2 low bits */ - void *opaque; - } init; - } u; -} JSProperty; - -#define JS_PROP_INITIAL_SIZE 2 -#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */ -#define JS_ARRAY_INITIAL_SIZE 2 - -typedef struct JSShapeProperty { - uint32_t hash_next : 26; /* 0 if last in list */ - uint32_t flags : 6; /* JS_PROP_XXX */ - JSAtom atom; /* JS_ATOM_NULL = free property entry */ -} JSShapeProperty; - -struct JSShape { - /* hash table of size hash_mask + 1 before the start of the - structure (see prop_hash_end()). */ - JSGCObjectHeader header; - /* true if the shape is inserted in the shape hash table. If not, - JSShape.hash is not valid */ - uint8_t is_hashed; - /* If true, the shape may have small array index properties 'n' with 0 - <= n <= 2^31-1. If false, the shape is guaranteed not to have - small array index properties */ - uint8_t has_small_array_index; - uint32_t hash; /* current hash value */ - uint32_t prop_hash_mask; - int prop_size; /* allocated properties */ - int prop_count; /* include deleted properties */ - int deleted_prop_count; - JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */ - JSObject *proto; - JSShapeProperty prop[]; /* prop_size elements */ -}; - -struct JSObject { - union { - JSGCObjectHeader header; - struct { - int __gc_ref_count; /* corresponds to header.ref_count */ - uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ - - uint8_t extensible : 1; - uint8_t free_mark : 1; /* only used when freeing objects with cycles */ - uint8_t is_exotic : 1; /* true if object has exotic property handlers */ - uint8_t fast_array : 1; /* true if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS and typed arrays) */ - uint8_t is_constructor : 1; /* true if object is a constructor function */ - uint8_t is_uncatchable_error : 1; /* if true, error is not catchable */ - uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ - uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ - uint16_t class_id; /* see JS_CLASS_x */ - }; - }; - /* byte offsets: 16/24 */ - JSShape *shape; /* prototype and property names + flag */ - JSProperty *prop; /* array of properties */ - /* byte offsets: 24/40 */ - JSWeakRefRecord *first_weak_ref; - /* byte offsets: 28/48 */ - union { - void *opaque; - struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */ - struct JSCFunctionDataRecord *c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */ - struct JSForInIterator *for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */ - struct JSArrayBuffer *array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */ - struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */ - struct JSMapState *map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ - struct JSMapIteratorData *map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ - struct JSArrayIteratorData *array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ - struct JSRegExpStringIteratorData *regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ - struct JSGeneratorData *generator_data; /* JS_CLASS_GENERATOR */ - struct JSIteratorConcatData *iterator_concat_data; /* JS_CLASS_ITERATOR_CONCAT */ - struct JSIteratorHelperData *iterator_helper_data; /* JS_CLASS_ITERATOR_HELPER */ - struct JSIteratorWrapData *iterator_wrap_data; /* JS_CLASS_ITERATOR_WRAP */ - struct JSProxyData *proxy_data; /* JS_CLASS_PROXY */ - struct JSPromiseData *promise_data; /* JS_CLASS_PROMISE */ - struct JSPromiseFunctionData *promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ - struct JSAsyncFunctionData *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ - struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ - struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */ - struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */ - /* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */ - struct JSFunctionBytecode *function_bytecode; - JSVarRef **var_refs; - JSObject *home_object; /* for 'super' access */ - } func; - struct { /* JS_CLASS_C_FUNCTION: 12/20 bytes */ - JSContext *realm; - JSCFunctionType c_function; - uint8_t length; - uint8_t cproto; - int16_t magic; - } cfunc; - /* array part for fast arrays and typed arrays */ - struct { /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ - union { - uint32_t size; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ - struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ - } u1; - union { - JSValue *values; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ - void *ptr; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ - int8_t *int8_ptr; /* JS_CLASS_INT8_ARRAY */ - uint8_t *uint8_ptr; /* JS_CLASS_UINT8_ARRAY, JS_CLASS_UINT8C_ARRAY */ - int16_t *int16_ptr; /* JS_CLASS_INT16_ARRAY */ - uint16_t *uint16_ptr; /* JS_CLASS_UINT16_ARRAY */ - int32_t *int32_ptr; /* JS_CLASS_INT32_ARRAY */ - uint32_t *uint32_ptr; /* JS_CLASS_UINT32_ARRAY */ - int64_t *int64_ptr; /* JS_CLASS_INT64_ARRAY */ - uint64_t *uint64_ptr; /* JS_CLASS_UINT64_ARRAY */ - uint16_t *fp16_ptr; /* JS_CLASS_FLOAT16_ARRAY */ - float *float_ptr; /* JS_CLASS_FLOAT32_ARRAY */ - double *double_ptr; /* JS_CLASS_FLOAT64_ARRAY */ - } u; - uint32_t count; /* <= 2^31-1. 0 for a detached typed array */ - } array; /* 12/20 bytes */ - JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */ - JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */ - } u; - /* byte sizes: 40/48/72 */ -}; - -typedef struct JSCallSiteData { - JSValue filename; - JSValue func; - JSValue func_name; - bool native; - int line_num; - int col_num; -} JSCallSiteData; - -enum { - __JS_ATOM_NULL = JS_ATOM_NULL, -#define DEF(name, str) JS_ATOM_ ## name, -#include "quickjs-atom.h" -#undef DEF - JS_ATOM_END, -}; -#define JS_ATOM_LAST_KEYWORD JS_ATOM_super -#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield - -static const char js_atom_init[] = -#define DEF(name, str) str "\0" -#include "quickjs-atom.h" -#undef DEF -; - -typedef enum OPCodeFormat { -#define FMT(f) OP_FMT_ ## f, -#define DEF(id, size, n_pop, n_push, f) -#include "quickjs-opcode.h" -#undef DEF -#undef FMT -} OPCodeFormat; - -typedef enum OPCodeEnum { -#define FMT(f) -#define DEF(id, size, n_pop, n_push, f) OP_ ## id, -#define def(id, size, n_pop, n_push, f) -#include "quickjs-opcode.h" -#undef def -#undef DEF -#undef FMT - OP_COUNT, /* excluding temporary opcodes */ - /* temporary opcodes : overlap with the short opcodes */ - OP_TEMP_START = OP_nop + 1, - OP___dummy = OP_TEMP_START - 1, -#define FMT(f) -#define DEF(id, size, n_pop, n_push, f) -#define def(id, size, n_pop, n_push, f) OP_ ## id, -#include "quickjs-opcode.h" -#undef def -#undef DEF -#undef FMT - OP_TEMP_END, -} OPCodeEnum; - -static int JS_InitAtoms(JSRuntime *rt); -static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, - int atom_type); -static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p); -static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b); -static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, - JSValueConst this_obj, - int argc, JSValueConst *argv, int flags); -static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj, - JSValueConst this_obj, - int argc, JSValueConst *argv, int flags); -static JSValue JS_CallInternal(JSContext *ctx, JSValueConst func_obj, - JSValueConst this_obj, JSValueConst new_target, - int argc, JSValueConst *argv, int flags); -static JSValue JS_CallConstructorInternal(JSContext *ctx, - JSValueConst func_obj, - JSValueConst new_target, - int argc, JSValueConst *argv, int flags); -static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj, - int argc, JSValueConst *argv); -static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, - int argc, JSValueConst *argv); -static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, - JSValue val, bool is_array_ctor); -static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj, - JSValueConst val, int flags, int scope_idx); -static __maybe_unused void JS_DumpString(JSRuntime *rt, JSString *p); -static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt); -static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p); -static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p); -static __maybe_unused void JS_DumpValue(JSRuntime *rt, JSValueConst val); -static __maybe_unused void JS_DumpAtoms(JSRuntime *rt); -static __maybe_unused void JS_DumpShapes(JSRuntime *rt); - -static JSValue js_function_apply(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic); -static void js_array_finalizer(JSRuntime *rt, JSValueConst val); -static void js_array_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_object_data_finalizer(JSRuntime *rt, JSValueConst val); -static void js_object_data_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_c_function_finalizer(JSRuntime *rt, JSValueConst val); -static void js_c_function_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_bytecode_function_finalizer(JSRuntime *rt, JSValueConst val); -static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_bound_function_finalizer(JSRuntime *rt, JSValueConst val); -static void js_bound_function_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValueConst val); -static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_regexp_finalizer(JSRuntime *rt, JSValueConst val); -static void js_array_buffer_finalizer(JSRuntime *rt, JSValueConst val); -static void js_typed_array_finalizer(JSRuntime *rt, JSValueConst val); -static void js_typed_array_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_proxy_finalizer(JSRuntime *rt, JSValueConst val); -static void js_proxy_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_map_finalizer(JSRuntime *rt, JSValueConst val); -static void js_map_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_map_iterator_finalizer(JSRuntime *rt, JSValueConst val); -static void js_map_iterator_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_array_iterator_finalizer(JSRuntime *rt, JSValueConst val); -static void js_array_iterator_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_iterator_concat_finalizer(JSRuntime *rt, JSValueConst val); -static void js_iterator_concat_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_iterator_helper_finalizer(JSRuntime *rt, JSValueConst val); -static void js_iterator_helper_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_iterator_wrap_finalizer(JSRuntime *rt, JSValueConst val); -static void js_iterator_wrap_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_regexp_string_iterator_finalizer(JSRuntime *rt, - JSValueConst val); -static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_generator_finalizer(JSRuntime *rt, JSValueConst val); -static void js_generator_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_promise_finalizer(JSRuntime *rt, JSValueConst val); -static void js_promise_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValueConst val); -static void js_promise_resolve_function_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); - -#define HINT_STRING 0 -#define HINT_NUMBER 1 -#define HINT_NONE 2 -#define HINT_FORCE_ORDINARY (1 << 4) // don't try Symbol.toPrimitive -static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint); -static JSValue JS_ToStringFree(JSContext *ctx, JSValue val); -static int JS_ToBoolFree(JSContext *ctx, JSValue val); -static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val); -static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val); -static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val); -static JSValue JS_ToPropertyKeyInternal(JSContext *ctx, JSValueConst val, - int flags); -static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len); -static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern, - JSValueConst flags); -static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor, - JSValue pattern, JSValue bc); -static void gc_decref(JSRuntime *rt); -static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, - const JSClassDef *class_def, JSAtom name); -static JSValue js_array_push(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int unshift); -static JSValue js_array_constructor(JSContext *ctx, JSValueConst new_target, - int argc, JSValueConst *argv); -static JSValue js_error_constructor(JSContext *ctx, JSValueConst new_target, - int argc, JSValueConst *argv, int magic); -static JSValue js_object_defineProperty(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic); - -typedef enum JSStrictEqModeEnum { - JS_EQ_STRICT, - JS_EQ_SAME_VALUE, - JS_EQ_SAME_VALUE_ZERO, -} JSStrictEqModeEnum; - -static bool js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, - JSStrictEqModeEnum eq_mode); -static bool js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2); -static bool js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2); -static bool js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2); -static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val); -static JSProperty *add_property(JSContext *ctx, - JSObject *p, JSAtom prop, int prop_flags); -static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val); -static JSValue JS_ThrowStackOverflow(JSContext *ctx); -static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx); -static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj); -static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj, - JSValueConst proto_val, bool throw_flag); -static int js_proxy_isExtensible(JSContext *ctx, JSValueConst obj); -static int js_proxy_preventExtensions(JSContext *ctx, JSValueConst obj); -static int js_proxy_isArray(JSContext *ctx, JSValueConst obj); -static int JS_CreateProperty(JSContext *ctx, JSObject *p, - JSAtom prop, JSValueConst val, - JSValueConst getter, JSValueConst setter, - int flags); -static int js_string_memcmp(JSString *p1, JSString *p2, int len); -static void reset_weak_ref(JSRuntime *rt, JSWeakRefRecord **first_weak_ref); -static bool is_valid_weakref_target(JSValueConst val); -static void insert_weakref_record(JSValueConst target, - struct JSWeakRefRecord *wr); -static JSValue js_array_buffer_constructor3(JSContext *ctx, - JSValueConst new_target, - uint64_t len, uint64_t *max_len, - JSClassID class_id, - uint8_t *buf, - JSFreeArrayBufferDataFunc *free_func, - void *opaque, bool alloc_flag); -static void js_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr); -static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValueConst obj); -static bool array_buffer_is_resizable(const JSArrayBuffer *abuf); -static JSValue js_typed_array_constructor(JSContext *ctx, - JSValueConst this_val, - int argc, JSValueConst *argv, - int classid); -static JSValue js_typed_array_constructor_ta(JSContext *ctx, - JSValueConst new_target, - JSValueConst src_obj, - int classid, uint32_t len); -static bool is_typed_array(JSClassID class_id); -static bool typed_array_is_oob(JSObject *p); -static uint32_t typed_array_length(JSObject *p); -static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx); -static JSValue JS_ThrowTypeErrorArrayBufferOOB(JSContext *ctx); -static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, - bool is_arg); -static JSValue js_call_generator_function(JSContext *ctx, JSValueConst func_obj, - JSValueConst this_obj, - int argc, JSValueConst *argv, - int flags); -static void js_async_function_resolve_finalizer(JSRuntime *rt, - JSValueConst val); -static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, - const char *input, size_t input_len, - const char *filename, int line, int flags, int scope_idx); -static void js_free_module_def(JSContext *ctx, JSModuleDef *m); -static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m, - JS_MarkFunc *mark_func); -static JSValue js_import_meta(JSContext *ctx); -static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier); -static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref); -static JSValue js_new_promise_capability(JSContext *ctx, - JSValue *resolving_funcs, - JSValueConst ctor); -static __exception int perform_promise_then(JSContext *ctx, - JSValueConst promise, - JSValueConst *resolve_reject, - JSValueConst *cap_resolving_funcs); -static JSValue js_promise_resolve(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic); -static JSValue js_promise_then(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv); -static JSValue js_promise_resolve_thenable_job(JSContext *ctx, - int argc, JSValueConst *argv); -static bool js_string_eq(JSString *p1, JSString *p2); -static int js_string_compare(JSString *p1, JSString *p2); -static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, - JSValue prop, JSValue val, int flags); -static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val); -static bool JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val); -static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val); -static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, - JSObject *p, JSAtom prop); -static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc); -static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, - JS_MarkFunc *mark_func); -static void JS_AddIntrinsicBasicObjects(JSContext *ctx); -static void js_free_shape(JSRuntime *rt, JSShape *sh); -static void js_free_shape_null(JSRuntime *rt, JSShape *sh); -static int js_shape_prepare_update(JSContext *ctx, JSObject *p, - JSShapeProperty **pprs); -static int init_shape_hash(JSRuntime *rt); -static __exception int js_get_length32(JSContext *ctx, uint32_t *pres, - JSValueConst obj); -static __exception int js_get_length64(JSContext *ctx, int64_t *pres, - JSValueConst obj); -static __exception int js_set_length64(JSContext *ctx, JSValueConst obj, - int64_t len); -static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len); -static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen, - JSValueConst array_arg); -static JSValue js_create_array(JSContext *ctx, int len, JSValueConst *tab); -static bool js_get_fast_array(JSContext *ctx, JSValue obj, - JSValue **arrpp, uint32_t *countp); -static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len); -static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx, - JSValue sync_iter); -static void js_c_function_data_finalizer(JSRuntime *rt, JSValueConst val); -static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func); -static JSValue js_call_c_function_data(JSContext *ctx, JSValueConst func_obj, - JSValueConst this_val, - int argc, JSValueConst *argv, int flags); -static JSAtom js_symbol_to_atom(JSContext *ctx, JSValueConst val); -static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, - JSGCObjectTypeEnum type); -static void remove_gc_object(JSGCObjectHeader *h); -static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s); -static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); -static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, - void *opaque); -static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, - JSAtom atom, void *opaque); - -static void js_set_uncatchable_error(JSContext *ctx, JSValueConst val, - bool flag); - -static JSValue js_new_callsite(JSContext *ctx, JSCallSiteData *csd); -static void js_new_callsite_data(JSContext *ctx, JSCallSiteData *csd, JSStackFrame *sf); -static void js_new_callsite_data2(JSContext *ctx, JSCallSiteData *csd, const char *filename, int line_num, int col_num); -static void _JS_AddIntrinsicCallSite(JSContext *ctx); - -static void JS_SetOpaqueInternal(JSValueConst obj, void *opaque); - -static const JSClassExoticMethods js_arguments_exotic_methods; -static const JSClassExoticMethods js_string_exotic_methods; -static const JSClassExoticMethods js_proxy_exotic_methods; -static const JSClassExoticMethods js_module_ns_exotic_methods; - -static inline bool double_is_int32(double d) -{ - uint64_t u, e; - JSFloat64Union t; - - t.d = d; - u = t.u64; - - e = ((u >> 52) & 0x7FF) - 1023; - if (e > 30) { - // accept 0, INT32_MIN, reject too large, too small, nan, inf, -0 - return !u || (u == 0xc1e0000000000000); - } else { - // shift out sign, exponent and whole part bits - // value is fractional if remaining low bits are non-zero - return !(u << 12 << e); - } -} - -static JSValue js_float64(double d) -{ - return __JS_NewFloat64(d); -} - -static int compare_u32(uint32_t a, uint32_t b) -{ - return -(a < b) + (b < a); // -1, 0 or 1 -} - -static JSValue js_int32(int32_t v) -{ - return JS_MKVAL(JS_TAG_INT, v); -} - -static JSValue js_uint32(uint32_t v) -{ - if (v <= INT32_MAX) - return js_int32(v); - else - return js_float64(v); -} - -static JSValue js_int64(int64_t v) -{ - if (v >= INT32_MIN && v <= INT32_MAX) - return js_int32(v); - else - return js_float64(v); -} - -static JSValue js_number(double d) -{ - if (double_is_int32(d)) - return js_int32((int32_t)d); - else - return js_float64(d); -} - -JSValue JS_NewNumber(JSContext *ctx, double d) -{ - return js_number(d); -} - -static JSValue js_bool(bool v) -{ - return JS_MKVAL(JS_TAG_BOOL, (v != 0)); -} - -static JSValue js_dup(JSValueConst v) -{ - if (JS_VALUE_HAS_REF_COUNT(v)) { - JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); - p->ref_count++; - } - return unsafe_unconst(v); -} - -JSValue JS_DupValue(JSContext *ctx, JSValueConst v) -{ - return js_dup(v); -} - -JSValue JS_DupValueRT(JSRuntime *rt, JSValueConst v) -{ - return js_dup(v); -} - -static void js_trigger_gc(JSRuntime *rt, size_t size) -{ - bool force_gc; -#ifdef FORCE_GC_AT_MALLOC - force_gc = true; -#else - force_gc = ((rt->malloc_state.malloc_size + size) > - rt->malloc_gc_threshold); -#endif - if (force_gc) { -#ifdef ENABLE_DUMPS // JS_DUMP_GC - if (check_dump_flag(rt, JS_DUMP_GC)) { - printf("GC: size=%zd\n", rt->malloc_state.malloc_size); - } -#endif - JS_RunGC(rt); - rt->malloc_gc_threshold = rt->malloc_state.malloc_size + - (rt->malloc_state.malloc_size >> 1); - } -} - -static size_t js_malloc_usable_size_unknown(const void *ptr) -{ - return 0; -} - -void *js_calloc_rt(JSRuntime *rt, size_t count, size_t size) -{ - void *ptr; - JSMallocState *s; - - /* Do not allocate zero bytes: behavior is platform dependent */ - assert(count != 0 && size != 0); - - if (size > 0) - if (unlikely(count != (count * size) / size)) - return NULL; - - s = &rt->malloc_state; - /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ - if (unlikely(s->malloc_size + (count * size) > s->malloc_limit - 1)) - return NULL; - - ptr = rt->mf.js_calloc(s->opaque, count, size); - if (!ptr) - return NULL; - - s->malloc_count++; - s->malloc_size += rt->mf.js_malloc_usable_size(ptr) + MALLOC_OVERHEAD; - return ptr; -} - -void *js_malloc_rt(JSRuntime *rt, size_t size) -{ - void *ptr; - JSMallocState *s; - - /* Do not allocate zero bytes: behavior is platform dependent */ - assert(size != 0); - - s = &rt->malloc_state; - /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ - if (unlikely(s->malloc_size + size > s->malloc_limit - 1)) - return NULL; - - ptr = rt->mf.js_malloc(s->opaque, size); - if (!ptr) - return NULL; - - s->malloc_count++; - s->malloc_size += rt->mf.js_malloc_usable_size(ptr) + MALLOC_OVERHEAD; - return ptr; -} - -void js_free_rt(JSRuntime *rt, void *ptr) -{ - JSMallocState *s; - - if (!ptr) - return; - - s = &rt->malloc_state; - s->malloc_count--; - s->malloc_size -= rt->mf.js_malloc_usable_size(ptr) + MALLOC_OVERHEAD; - rt->mf.js_free(s->opaque, ptr); -} - -void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size) -{ - size_t old_size; - JSMallocState *s; - - if (!ptr) { - if (size == 0) - return NULL; - return js_malloc_rt(rt, size); - } - if (unlikely(size == 0)) { - js_free_rt(rt, ptr); - return NULL; - } - old_size = rt->mf.js_malloc_usable_size(ptr); - s = &rt->malloc_state; - /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ - if (s->malloc_size + size - old_size > s->malloc_limit - 1) - return NULL; - - ptr = rt->mf.js_realloc(s->opaque, ptr, size); - if (!ptr) - return NULL; - - s->malloc_size += rt->mf.js_malloc_usable_size(ptr) - old_size; - return ptr; -} - -size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr) -{ - return rt->mf.js_malloc_usable_size(ptr); -} - -/** - * This used to be implemented as malloc + memset, but using calloc - * yields better performance in initial, bursty allocations, something useful - * for QuickJS. - * - * More information: https://github.com/quickjs-ng/quickjs/pull/519 - */ -void *js_mallocz_rt(JSRuntime *rt, size_t size) -{ - return js_calloc_rt(rt, 1, size); -} - -/* Throw out of memory in case of error */ -void *js_calloc(JSContext *ctx, size_t count, size_t size) -{ - void *ptr; - ptr = js_calloc_rt(ctx->rt, count, size); - if (unlikely(!ptr)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - return ptr; -} - -/* Throw out of memory in case of error */ -void *js_malloc(JSContext *ctx, size_t size) -{ - void *ptr; - ptr = js_malloc_rt(ctx->rt, size); - if (unlikely(!ptr)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - return ptr; -} - -/* Throw out of memory in case of error */ -void *js_mallocz(JSContext *ctx, size_t size) -{ - void *ptr; - ptr = js_mallocz_rt(ctx->rt, size); - if (unlikely(!ptr)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - return ptr; -} - -void js_free(JSContext *ctx, void *ptr) -{ - js_free_rt(ctx->rt, ptr); -} - -/* Throw out of memory in case of error */ -void *js_realloc(JSContext *ctx, void *ptr, size_t size) -{ - void *ret; - ret = js_realloc_rt(ctx->rt, ptr, size); - if (unlikely(!ret && size != 0)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - return ret; -} - -/* store extra allocated size in *pslack if successful */ -void *js_realloc2(JSContext *ctx, void *ptr, size_t size, size_t *pslack) -{ - void *ret; - ret = js_realloc_rt(ctx->rt, ptr, size); - if (unlikely(!ret && size != 0)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - if (pslack) { - size_t new_size = js_malloc_usable_size_rt(ctx->rt, ret); - *pslack = (new_size > size) ? new_size - size : 0; - } - return ret; -} - -size_t js_malloc_usable_size(JSContext *ctx, const void *ptr) -{ - return js_malloc_usable_size_rt(ctx->rt, ptr); -} - -/* Throw out of memory exception in case of error */ -char *js_strndup(JSContext *ctx, const char *s, size_t n) -{ - char *ptr; - ptr = js_malloc(ctx, n + 1); - if (ptr) { - memcpy(ptr, s, n); - ptr[n] = '\0'; - } - return ptr; -} - -char *js_strdup(JSContext *ctx, const char *str) -{ - return js_strndup(ctx, str, strlen(str)); -} - -static no_inline int js_realloc_array(JSContext *ctx, void **parray, - int elem_size, int *psize, int req_size) -{ - int new_size; - size_t slack; - void *new_array; - /* XXX: potential arithmetic overflow */ - new_size = max_int(req_size, *psize * 3 / 2); - new_array = js_realloc2(ctx, *parray, new_size * elem_size, &slack); - if (!new_array) - return -1; - new_size += slack / elem_size; - *psize = new_size; - *parray = new_array; - return 0; -} - -/* resize the array and update its size if req_size > *psize */ -static inline int js_resize_array(JSContext *ctx, void **parray, int elem_size, - int *psize, int req_size) -{ - if (unlikely(req_size > *psize)) - return js_realloc_array(ctx, parray, elem_size, psize, req_size); - else - return 0; -} - -static void *js_dbuf_realloc(void *ctx, void *ptr, size_t size) -{ - return js_realloc(ctx, ptr, size); -} - -static inline void js_dbuf_init(JSContext *ctx, DynBuf *s) -{ - dbuf_init2(s, ctx, js_dbuf_realloc); -} - -static inline int is_digit(int c) { - return c >= '0' && c <= '9'; -} - -static inline int string_get(JSString *p, int idx) { - return p->is_wide_char ? str16(p)[idx] : str8(p)[idx]; -} - -typedef struct JSClassShortDef { - JSAtom class_name; - JSClassFinalizer *finalizer; - JSClassGCMark *gc_mark; -} JSClassShortDef; - -static JSClassShortDef const js_std_class_def[] = { - { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_OBJECT */ - { JS_ATOM_Array, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARRAY */ - { JS_ATOM_Error, NULL, NULL }, /* JS_CLASS_ERROR */ - { JS_ATOM_Number, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_NUMBER */ - { JS_ATOM_String, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_STRING */ - { JS_ATOM_Boolean, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BOOLEAN */ - { JS_ATOM_Symbol, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_SYMBOL */ - { JS_ATOM_Arguments, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARGUMENTS */ - { JS_ATOM_Arguments, NULL, NULL }, /* JS_CLASS_MAPPED_ARGUMENTS */ - { JS_ATOM_Date, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_DATE */ - { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_MODULE_NS */ - { JS_ATOM_Function, js_c_function_finalizer, js_c_function_mark }, /* JS_CLASS_C_FUNCTION */ - { JS_ATOM_Function, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_BYTECODE_FUNCTION */ - { JS_ATOM_Function, js_bound_function_finalizer, js_bound_function_mark }, /* JS_CLASS_BOUND_FUNCTION */ - { JS_ATOM_Function, js_c_function_data_finalizer, js_c_function_data_mark }, /* JS_CLASS_C_FUNCTION_DATA */ - { JS_ATOM_GeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_GENERATOR_FUNCTION */ - { JS_ATOM_ForInIterator, js_for_in_iterator_finalizer, js_for_in_iterator_mark }, /* JS_CLASS_FOR_IN_ITERATOR */ - { JS_ATOM_RegExp, js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */ - { JS_ATOM_ArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_ARRAY_BUFFER */ - { JS_ATOM_SharedArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_SHARED_ARRAY_BUFFER */ - { JS_ATOM_Uint8ClampedArray, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8C_ARRAY */ - { JS_ATOM_Int8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT8_ARRAY */ - { JS_ATOM_Uint8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8_ARRAY */ - { JS_ATOM_Int16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT16_ARRAY */ - { JS_ATOM_Uint16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT16_ARRAY */ - { JS_ATOM_Int32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT32_ARRAY */ - { JS_ATOM_Uint32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT32_ARRAY */ - { JS_ATOM_BigInt64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_INT64_ARRAY */ - { JS_ATOM_BigUint64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_UINT64_ARRAY */ - { JS_ATOM_Float16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT16_ARRAY */ - { JS_ATOM_Float32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT32_ARRAY */ - { JS_ATOM_Float64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT64_ARRAY */ - { JS_ATOM_DataView, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_DATAVIEW */ - { JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_INT */ - { JS_ATOM_Map, js_map_finalizer, js_map_mark }, /* JS_CLASS_MAP */ - { JS_ATOM_Set, js_map_finalizer, js_map_mark }, /* JS_CLASS_SET */ - { JS_ATOM_WeakMap, js_map_finalizer, NULL }, /* JS_CLASS_WEAKMAP */ - { JS_ATOM_WeakSet, js_map_finalizer, NULL }, /* JS_CLASS_WEAKSET */ - { JS_ATOM_Iterator, NULL, NULL }, /* JS_CLASS_ITERATOR */ - { JS_ATOM_IteratorConcat, js_iterator_concat_finalizer, js_iterator_concat_mark }, /* JS_CLASS_ITERATOR_CONCAT */ - { JS_ATOM_IteratorHelper, js_iterator_helper_finalizer, js_iterator_helper_mark }, /* JS_CLASS_ITERATOR_HELPER */ - { JS_ATOM_IteratorWrap, js_iterator_wrap_finalizer, js_iterator_wrap_mark }, /* JS_CLASS_ITERATOR_WRAP */ - { JS_ATOM_Map_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_MAP_ITERATOR */ - { JS_ATOM_Set_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_SET_ITERATOR */ - { JS_ATOM_Array_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_ARRAY_ITERATOR */ - { JS_ATOM_String_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */ - { JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_REGEXP_STRING_ITERATOR */ - { JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */ -}; - -static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab, - int start, int count) -{ - JSClassDef cm_s, *cm = &cm_s; - int i, class_id; - - for(i = 0; i < count; i++) { - class_id = i + start; - memset(cm, 0, sizeof(*cm)); - cm->finalizer = tab[i].finalizer; - cm->gc_mark = tab[i].gc_mark; - if (JS_NewClass1(rt, class_id, cm, tab[i].class_name) < 0) - return -1; - } - return 0; -} - -/* Uses code from LLVM project. */ -static inline uintptr_t js_get_stack_pointer(void) -{ -#if defined(__clang__) || defined(__GNUC__) - return (uintptr_t)__builtin_frame_address(0); -#elif defined(_MSC_VER) - return (uintptr_t)_AddressOfReturnAddress(); -#else - char CharOnStack = 0; - // The volatile store here is intended to escape the local variable, to - // prevent the compiler from optimizing CharOnStack into anything other - // than a char on the stack. - // - // Tested on: MSVC 2015 - 2019, GCC 4.9 - 9, Clang 3.2 - 9, ICC 13 - 19. - char *volatile Ptr = &CharOnStack; - return (uintptr_t) Ptr; -#endif -} - -static inline bool js_check_stack_overflow(JSRuntime *rt, size_t alloca_size) -{ - uintptr_t sp; - sp = js_get_stack_pointer() - alloca_size; - return unlikely(sp < rt->stack_limit); -} - -JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) -{ - JSRuntime *rt; - JSMallocState ms; - - memset(&ms, 0, sizeof(ms)); - ms.opaque = opaque; - ms.malloc_limit = 0; - - rt = mf->js_calloc(opaque, 1, sizeof(JSRuntime)); - if (!rt) - return NULL; - rt->mf = *mf; - if (!rt->mf.js_malloc_usable_size) { - /* use dummy function if none provided */ - rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown; - } - /* Inline what js_malloc_rt does since we cannot use it here. */ - ms.malloc_count++; - ms.malloc_size += rt->mf.js_malloc_usable_size(rt) + MALLOC_OVERHEAD; - rt->malloc_state = ms; - rt->malloc_gc_threshold = 256 * 1024; - - init_list_head(&rt->context_list); - init_list_head(&rt->gc_obj_list); - init_list_head(&rt->gc_zero_ref_count_list); - rt->gc_phase = JS_GC_PHASE_NONE; - -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - init_list_head(&rt->string_list); -#endif - init_list_head(&rt->job_list); - init_list_head(&rt->weakref_keepalive_list); - - if (JS_InitAtoms(rt)) - goto fail; - - /* create the object, array and function classes */ - if (init_class_range(rt, js_std_class_def, JS_CLASS_OBJECT, - countof(js_std_class_def)) < 0) - goto fail; - rt->class_array[JS_CLASS_ARGUMENTS].exotic = &js_arguments_exotic_methods; - rt->class_array[JS_CLASS_STRING].exotic = &js_string_exotic_methods; - rt->class_array[JS_CLASS_MODULE_NS].exotic = &js_module_ns_exotic_methods; - - rt->class_array[JS_CLASS_C_FUNCTION].call = js_call_c_function; - rt->class_array[JS_CLASS_C_FUNCTION_DATA].call = js_call_c_function_data; - rt->class_array[JS_CLASS_BOUND_FUNCTION].call = js_call_bound_function; - rt->class_array[JS_CLASS_GENERATOR_FUNCTION].call = js_call_generator_function; - if (init_shape_hash(rt)) - goto fail; - - rt->js_class_id_alloc = JS_CLASS_INIT_COUNT; - - rt->stack_size = JS_DEFAULT_STACK_SIZE; -#ifdef __wasi__ - rt->stack_size = 0; -#endif - - JS_UpdateStackTop(rt); - - rt->current_exception = JS_UNINITIALIZED; - - return rt; - fail: - JS_FreeRuntime(rt); - return NULL; -} - -void *JS_GetRuntimeOpaque(JSRuntime *rt) -{ - return rt->user_opaque; -} - -void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque) -{ - rt->user_opaque = opaque; -} - -int JS_AddRuntimeFinalizer(JSRuntime *rt, JSRuntimeFinalizer *finalizer, - void *arg) -{ - JSRuntimeFinalizerState *fs = js_malloc_rt(rt, sizeof(*fs)); - if (!fs) - return -1; - fs->next = rt->finalizers; - fs->finalizer = finalizer; - fs->arg = arg; - rt->finalizers = fs; - return 0; -} - -static void *js_def_calloc(void *opaque, size_t count, size_t size) -{ - return calloc(count, size); -} - -static void *js_def_malloc(void *opaque, size_t size) -{ - return malloc(size); -} - -static void js_def_free(void *opaque, void *ptr) -{ - free(ptr); -} - -static void *js_def_realloc(void *opaque, void *ptr, size_t size) -{ - return realloc(ptr, size); -} - -static const JSMallocFunctions def_malloc_funcs = { - js_def_calloc, - js_def_malloc, - js_def_free, - js_def_realloc, - js__malloc_usable_size -}; - -JSRuntime *JS_NewRuntime(void) -{ - return JS_NewRuntime2(&def_malloc_funcs, NULL); -} - -void JS_SetMemoryLimit(JSRuntime *rt, size_t limit) -{ - rt->malloc_state.malloc_limit = limit; -} - -void JS_SetDumpFlags(JSRuntime *rt, uint64_t flags) -{ -#ifdef ENABLE_DUMPS - rt->dump_flags = flags; -#endif -} - -uint64_t JS_GetDumpFlags(JSRuntime *rt) -{ -#ifdef ENABLE_DUMPS - return rt->dump_flags; -#else - return 0; -#endif -} - -size_t JS_GetGCThreshold(JSRuntime *rt) { - return rt->malloc_gc_threshold; -} - -/* use -1 to disable automatic GC */ -void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold) -{ - rt->malloc_gc_threshold = gc_threshold; -} - -#define malloc(s) malloc_is_forbidden(s) -#define free(p) free_is_forbidden(p) -#define realloc(p,s) realloc_is_forbidden(p,s) - -void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque) -{ - rt->interrupt_handler = cb; - rt->interrupt_opaque = opaque; -} - -void JS_SetCanBlock(JSRuntime *rt, bool can_block) -{ - rt->can_block = can_block; -} - -void JS_SetSharedArrayBufferFunctions(JSRuntime *rt, - const JSSharedArrayBufferFunctions *sf) -{ - rt->sab_funcs = *sf; -} - -/* return 0 if OK, < 0 if exception */ -int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = ctx->rt; - JSJobEntry *e; - int i; - - assert(!rt->in_free); - - e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue)); - if (!e) - return -1; - e->ctx = ctx; - e->job_func = job_func; - e->argc = argc; - for(i = 0; i < argc; i++) { - e->argv[i] = js_dup(argv[i]); - } - list_add_tail(&e->link, &rt->job_list); - return 0; -} - -bool JS_IsJobPending(JSRuntime *rt) -{ - return !list_empty(&rt->job_list); -} - -/* return < 0 if exception, 0 if no job pending, 1 if a job was - executed successfully. the context of the job is stored in '*pctx' */ -int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx) -{ - JSContext *ctx; - JSJobEntry *e; - JSValue res; - int i, ret; - - if (list_empty(&rt->job_list)) { - *pctx = NULL; - return 0; - } - - /* get the first pending job and execute it */ - e = list_entry(rt->job_list.next, JSJobEntry, link); - list_del(&e->link); - ctx = e->ctx; - res = e->job_func(e->ctx, e->argc, vc(e->argv)); - for(i = 0; i < e->argc; i++) - JS_FreeValue(ctx, e->argv[i]); - if (JS_IsException(res)) - ret = -1; - else - ret = 1; - JS_FreeValue(ctx, res); - js_free(ctx, e); - *pctx = ctx; - return ret; -} - -static inline uint32_t atom_get_free(const JSAtomStruct *p) -{ - return (uintptr_t)p >> 1; -} - -static inline bool atom_is_free(const JSAtomStruct *p) -{ - return (uintptr_t)p & 1; -} - -static inline JSAtomStruct *atom_set_free(uint32_t v) -{ - return (JSAtomStruct *)(((uintptr_t)v << 1) | 1); -} - -/* Note: the string contents are uninitialized */ -static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char) -{ - JSString *str; - str = js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char); - if (unlikely(!str)) - return NULL; - str->header.ref_count = 1; - str->is_wide_char = is_wide_char; - str->len = max_len; - str->kind = JS_STRING_KIND_NORMAL; - str->atom_type = 0; - str->hash = 0; /* optional but costless */ - str->hash_next = 0; /* optional */ -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_add_tail(&str->link, &rt->string_list); -#endif - return str; -} - -static JSString *js_alloc_string(JSContext *ctx, int max_len, int is_wide_char) -{ - JSString *p; - p = js_alloc_string_rt(ctx->rt, max_len, is_wide_char); - if (unlikely(!p)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - return p; -} - -static inline void js_free_string0(JSRuntime *rt, JSString *str); - -/* same as JS_FreeValueRT() but faster */ -static inline void js_free_string(JSRuntime *rt, JSString *str) -{ - if (--str->header.ref_count <= 0) - js_free_string0(rt, str); -} - -static inline void js_free_string0(JSRuntime *rt, JSString *str) -{ - if (str->atom_type) { - JS_FreeAtomStruct(rt, str); - } else { -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_del(&str->link); -#endif - if (str->kind == JS_STRING_KIND_SLICE) { - JSStringSlice *slice = (void *)&str[1]; - js_free_string(rt, slice->parent); // safe, recurses only 1 level - } - js_free_rt(rt, str); - } -} - -void JS_SetRuntimeInfo(JSRuntime *rt, const char *s) -{ - if (rt) - rt->rt_info = s; -} - -void JS_FreeRuntime(JSRuntime *rt) -{ - struct list_head *el, *el1; - int i; - - rt->in_free = true; - JS_FreeValueRT(rt, rt->current_exception); - - list_for_each_safe(el, el1, &rt->job_list) { - JSJobEntry *e = list_entry(el, JSJobEntry, link); - for(i = 0; i < e->argc; i++) - JS_FreeValueRT(rt, e->argv[i]); - js_free_rt(rt, e); - } - init_list_head(&rt->job_list); - JS_ClearWeakRefKeepAlives(rt); - - JS_RunGC(rt); - -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - /* leaking objects */ - if (check_dump_flag(rt, JS_DUMP_LEAKS)) { - bool header_done; - JSGCObjectHeader *p; - int count; - - /* remove the internal refcounts to display only the object - referenced externally */ - list_for_each(el, &rt->gc_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - p->mark = 0; - } - gc_decref(rt); - - header_done = false; - list_for_each(el, &rt->gc_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - if (p->ref_count != 0) { - if (!header_done) { - printf("Object leaks:\n"); - JS_DumpObjectHeader(rt); - header_done = true; - } - JS_DumpGCObject(rt, p); - } - } - - count = 0; - list_for_each(el, &rt->gc_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - if (p->ref_count == 0) { - count++; - } - } - if (count != 0) - printf("Secondary object leaks: %d\n", count); - } -#endif - - assert(list_empty(&rt->gc_obj_list)); - - /* free the classes */ - for(i = 0; i < rt->class_count; i++) { - JSClass *cl = &rt->class_array[i]; - if (cl->class_id != 0) { - JS_FreeAtomRT(rt, cl->class_name); - } - } - js_free_rt(rt, rt->class_array); - -#ifdef ENABLE_DUMPS // JS_DUMP_ATOM_LEAKS - /* only the atoms defined in JS_InitAtoms() should be left */ - if (check_dump_flag(rt, JS_DUMP_ATOM_LEAKS)) { - bool header_done = false; - - for(i = 0; i < rt->atom_size; i++) { - JSAtomStruct *p = rt->atom_array[i]; - if (!atom_is_free(p) /* && p->str*/) { - if (i >= JS_ATOM_END || p->header.ref_count != 1) { - if (!header_done) { - header_done = true; - if (rt->rt_info) { - printf("%s:1: atom leakage:", rt->rt_info); - } else { - printf("Atom leaks:\n" - " %6s %6s %s\n", - "ID", "REFCNT", "NAME"); - } - } - if (rt->rt_info) { - printf(" "); - } else { - printf(" %6u %6u ", i, p->header.ref_count); - } - switch (p->atom_type) { - case JS_ATOM_TYPE_STRING: - JS_DumpString(rt, p); - break; - case JS_ATOM_TYPE_GLOBAL_SYMBOL: - printf("Symbol.for("); - JS_DumpString(rt, p); - printf(")"); - break; - case JS_ATOM_TYPE_SYMBOL: - if (p->hash == JS_ATOM_HASH_SYMBOL) { - printf("Symbol("); - JS_DumpString(rt, p); - printf(")"); - } else { - printf("Private("); - JS_DumpString(rt, p); - printf(")"); - } - break; - } - if (rt->rt_info) { - printf(":%u", p->header.ref_count); - } else { - printf("\n"); - } - } - } - } - if (rt->rt_info && header_done) - printf("\n"); - } -#endif - - /* free the atoms */ - for(i = 0; i < rt->atom_size; i++) { - JSAtomStruct *p = rt->atom_array[i]; - if (!atom_is_free(p)) { -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_del(&p->link); -#endif - js_free_rt(rt, p); - } - } - js_free_rt(rt, rt->atom_array); - js_free_rt(rt, rt->atom_hash); - js_free_rt(rt, rt->shape_hash); -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - if (check_dump_flag(rt, JS_DUMP_LEAKS) && !list_empty(&rt->string_list)) { - if (rt->rt_info) { - printf("%s:1: string leakage:", rt->rt_info); - } else { - printf("String leaks:\n" - " %6s %s\n", - "REFCNT", "VALUE"); - } - list_for_each_safe(el, el1, &rt->string_list) { - JSString *str = list_entry(el, JSString, link); - if (rt->rt_info) { - printf(" "); - } else { - printf(" %6u ", str->header.ref_count); - } - JS_DumpString(rt, str); - if (rt->rt_info) { - printf(":%u", str->header.ref_count); - } else { - printf("\n"); - } - list_del(&str->link); - js_free_rt(rt, str); - } - if (rt->rt_info) - printf("\n"); - } -#endif - - while (rt->finalizers) { - JSRuntimeFinalizerState *fs = rt->finalizers; - rt->finalizers = fs->next; - fs->finalizer(rt, fs->arg); - js_free_rt(rt, fs); - } - -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - if (check_dump_flag(rt, JS_DUMP_LEAKS)) { - JSMallocState *s = &rt->malloc_state; - if (s->malloc_count > 1) { - if (rt->rt_info) - printf("%s:1: ", rt->rt_info); - printf("Memory leak: %zd bytes lost in %zd block%s\n", - s->malloc_size - sizeof(JSRuntime), - s->malloc_count - 1, &"s"[s->malloc_count == 2]); - } - } -#endif - - { - JSMallocState *ms = &rt->malloc_state; - rt->mf.js_free(ms->opaque, rt); - } -} - -JSContext *JS_NewContextRaw(JSRuntime *rt) -{ - JSContext *ctx; - int i; - - ctx = js_mallocz_rt(rt, sizeof(JSContext)); - if (!ctx) - return NULL; - ctx->header.ref_count = 1; - add_gc_object(rt, &ctx->header, JS_GC_OBJ_TYPE_JS_CONTEXT); - - ctx->class_proto = js_malloc_rt(rt, sizeof(ctx->class_proto[0]) * - rt->class_count); - if (!ctx->class_proto) { - js_free_rt(rt, ctx); - return NULL; - } - ctx->rt = rt; - list_add_tail(&ctx->link, &rt->context_list); - for(i = 0; i < rt->class_count; i++) - ctx->class_proto[i] = JS_NULL; - ctx->array_ctor = JS_NULL; - ctx->iterator_ctor = JS_NULL; - ctx->iterator_ctor_getset = JS_NULL; - ctx->regexp_ctor = JS_NULL; - ctx->promise_ctor = JS_NULL; - ctx->error_ctor = JS_NULL; - ctx->error_back_trace = JS_UNDEFINED; - ctx->error_prepare_stack = JS_UNDEFINED; - ctx->error_stack_trace_limit = js_int32(10); - init_list_head(&ctx->loaded_modules); - - JS_AddIntrinsicBasicObjects(ctx); - return ctx; -} - -JSContext *JS_NewContext(JSRuntime *rt) -{ - JSContext *ctx; - - ctx = JS_NewContextRaw(rt); - if (!ctx) - return NULL; - - JS_AddIntrinsicBaseObjects(ctx); - JS_AddIntrinsicDate(ctx); - JS_AddIntrinsicEval(ctx); - JS_AddIntrinsicRegExp(ctx); - JS_AddIntrinsicJSON(ctx); - JS_AddIntrinsicProxy(ctx); - JS_AddIntrinsicMapSet(ctx); - JS_AddIntrinsicTypedArrays(ctx); - JS_AddIntrinsicPromise(ctx); - JS_AddIntrinsicBigInt(ctx); - JS_AddIntrinsicWeakRef(ctx); - JS_AddIntrinsicDOMException(ctx); - - JS_AddPerformance(ctx); - - return ctx; -} - -void *JS_GetContextOpaque(JSContext *ctx) -{ - return ctx->user_opaque; -} - -void JS_SetContextOpaque(JSContext *ctx, void *opaque) -{ - ctx->user_opaque = opaque; -} - -/* set the new value and free the old value after (freeing the value - can reallocate the object data) */ -static inline void set_value(JSContext *ctx, JSValue *pval, JSValue new_val) -{ - JSValue old_val; - old_val = *pval; - *pval = new_val; - JS_FreeValue(ctx, old_val); -} - -void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj) -{ - assert(class_id < ctx->rt->class_count); - set_value(ctx, &ctx->class_proto[class_id], obj); -} - -JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id) -{ - assert(class_id < ctx->rt->class_count); - return js_dup(ctx->class_proto[class_id]); -} - -JSValue JS_GetFunctionProto(JSContext *ctx) -{ - return js_dup(ctx->function_proto); -} - -typedef enum JSFreeModuleEnum { - JS_FREE_MODULE_ALL, - JS_FREE_MODULE_NOT_RESOLVED, -} JSFreeModuleEnum; - -/* XXX: would be more efficient with separate module lists */ -static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag) -{ - struct list_head *el, *el1; - list_for_each_safe(el, el1, &ctx->loaded_modules) { - JSModuleDef *m = list_entry(el, JSModuleDef, link); - if (flag == JS_FREE_MODULE_ALL || - (flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved)) { - js_free_module_def(ctx, m); - } - } -} - -JSContext *JS_DupContext(JSContext *ctx) -{ - ctx->header.ref_count++; - return ctx; -} - -/* used by the GC */ -static void JS_MarkContext(JSRuntime *rt, JSContext *ctx, - JS_MarkFunc *mark_func) -{ - int i; - struct list_head *el; - - /* modules are not seen by the GC, so we directly mark the objects - referenced by each module */ - list_for_each(el, &ctx->loaded_modules) { - JSModuleDef *m = list_entry(el, JSModuleDef, link); - js_mark_module_def(rt, m, mark_func); - } - - JS_MarkValue(rt, ctx->global_obj, mark_func); - JS_MarkValue(rt, ctx->global_var_obj, mark_func); - - JS_MarkValue(rt, ctx->throw_type_error, mark_func); - JS_MarkValue(rt, ctx->eval_obj, mark_func); - - JS_MarkValue(rt, ctx->array_proto_values, mark_func); - for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { - JS_MarkValue(rt, ctx->native_error_proto[i], mark_func); - } - JS_MarkValue(rt, ctx->error_ctor, mark_func); - JS_MarkValue(rt, ctx->error_back_trace, mark_func); - JS_MarkValue(rt, ctx->error_prepare_stack, mark_func); - JS_MarkValue(rt, ctx->error_stack_trace_limit, mark_func); - for(i = 0; i < rt->class_count; i++) { - JS_MarkValue(rt, ctx->class_proto[i], mark_func); - } - JS_MarkValue(rt, ctx->iterator_ctor, mark_func); - JS_MarkValue(rt, ctx->iterator_ctor_getset, mark_func); - JS_MarkValue(rt, ctx->async_iterator_proto, mark_func); - JS_MarkValue(rt, ctx->promise_ctor, mark_func); - JS_MarkValue(rt, ctx->array_ctor, mark_func); - JS_MarkValue(rt, ctx->regexp_ctor, mark_func); - JS_MarkValue(rt, ctx->function_ctor, mark_func); - JS_MarkValue(rt, ctx->function_proto, mark_func); - - if (ctx->array_shape) - mark_func(rt, &ctx->array_shape->header); -} - -void JS_FreeContext(JSContext *ctx) -{ - JSRuntime *rt = ctx->rt; - int i; - - if (--ctx->header.ref_count > 0) - return; - assert(ctx->header.ref_count == 0); - -#ifdef ENABLE_DUMPS // JS_DUMP_ATOMS - if (check_dump_flag(rt, JS_DUMP_ATOMS)) - JS_DumpAtoms(ctx->rt); -#endif -#ifdef ENABLE_DUMPS // JS_DUMP_SHAPES - if (check_dump_flag(rt, JS_DUMP_SHAPES)) - JS_DumpShapes(ctx->rt); -#endif -#ifdef ENABLE_DUMPS // JS_DUMP_OBJECTS - if (check_dump_flag(rt, JS_DUMP_OBJECTS)) { - struct list_head *el; - JSGCObjectHeader *p; - printf("JSObjects: {\n"); - JS_DumpObjectHeader(ctx->rt); - list_for_each(el, &rt->gc_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - JS_DumpGCObject(rt, p); - } - printf("}\n"); - } -#endif -#ifdef ENABLE_DUMPS // JS_DUMP_MEM - if (check_dump_flag(rt, JS_DUMP_MEM)) { - JSMemoryUsage stats; - JS_ComputeMemoryUsage(rt, &stats); - JS_DumpMemoryUsage(stdout, &stats, rt); - } -#endif - - js_free_modules(ctx, JS_FREE_MODULE_ALL); - - JS_FreeValue(ctx, ctx->global_obj); - JS_FreeValue(ctx, ctx->global_var_obj); - - JS_FreeValue(ctx, ctx->throw_type_error); - JS_FreeValue(ctx, ctx->eval_obj); - - JS_FreeValue(ctx, ctx->array_proto_values); - for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { - JS_FreeValue(ctx, ctx->native_error_proto[i]); - } - JS_FreeValue(ctx, ctx->error_ctor); - JS_FreeValue(ctx, ctx->error_back_trace); - JS_FreeValue(ctx, ctx->error_prepare_stack); - JS_FreeValue(ctx, ctx->error_stack_trace_limit); - for(i = 0; i < rt->class_count; i++) { - JS_FreeValue(ctx, ctx->class_proto[i]); - } - js_free_rt(rt, ctx->class_proto); - JS_FreeValue(ctx, ctx->iterator_ctor); - JS_FreeValue(ctx, ctx->iterator_ctor_getset); - JS_FreeValue(ctx, ctx->async_iterator_proto); - JS_FreeValue(ctx, ctx->promise_ctor); - JS_FreeValue(ctx, ctx->array_ctor); - JS_FreeValue(ctx, ctx->regexp_ctor); - JS_FreeValue(ctx, ctx->function_ctor); - JS_FreeValue(ctx, ctx->function_proto); - - js_free_shape_null(ctx->rt, ctx->array_shape); - - list_del(&ctx->link); - remove_gc_object(&ctx->header); - js_free_rt(ctx->rt, ctx); -} - -JSRuntime *JS_GetRuntime(JSContext *ctx) -{ - return ctx->rt; -} - -static void update_stack_limit(JSRuntime *rt) -{ -#if defined(__wasi__) - rt->stack_limit = 0; /* no limit */ -#else - if (rt->stack_size == 0) { - rt->stack_limit = 0; /* no limit */ - } else { - rt->stack_limit = rt->stack_top - rt->stack_size; - } -#endif -} - -void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size) -{ - rt->stack_size = stack_size; - update_stack_limit(rt); -} - -void JS_UpdateStackTop(JSRuntime *rt) -{ - rt->stack_top = js_get_stack_pointer(); - update_stack_limit(rt); -} - -static inline bool is_strict_mode(JSContext *ctx) -{ - JSStackFrame *sf = ctx->rt->current_stack_frame; - return sf && sf->is_strict_mode; -} - -/* JSAtom support */ - -#define JS_ATOM_TAG_INT (1U << 31) -#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1) -#define JS_ATOM_MAX ((1U << 30) - 1) - -/* return the max count from the hash size */ -#define JS_ATOM_COUNT_RESIZE(n) ((n) * 2) - -static inline bool __JS_AtomIsConst(JSAtom v) -{ - return (int32_t)v < JS_ATOM_END; -} - -static inline bool __JS_AtomIsTaggedInt(JSAtom v) -{ - return (v & JS_ATOM_TAG_INT) != 0; -} - -static inline JSAtom __JS_AtomFromUInt32(uint32_t v) -{ - return v | JS_ATOM_TAG_INT; -} - -static inline uint32_t __JS_AtomToUInt32(JSAtom atom) -{ - return atom & ~JS_ATOM_TAG_INT; -} - -static inline int is_num(int c) -{ - return c >= '0' && c <= '9'; -} - -/* return true if the string is a number n with 0 <= n <= 2^32-1 */ -static inline bool is_num_string(uint32_t *pval, JSString *p) -{ - uint32_t n; - uint64_t n64; - int c, i, len; - - len = p->len; - if (len == 0 || len > 10) - return false; - c = string_get(p, 0); - if (is_num(c)) { - if (c == '0') { - if (len != 1) - return false; - n = 0; - } else { - n = c - '0'; - for(i = 1; i < len; i++) { - c = string_get(p, i); - if (!is_num(c)) - return false; - n64 = (uint64_t)n * 10 + (c - '0'); - if ((n64 >> 32) != 0) - return false; - n = n64; - } - } - *pval = n; - return true; - } else { - return false; - } -} - -/* XXX: could use faster version ? */ -static inline uint32_t hash_string8(const uint8_t *str, size_t len, uint32_t h) -{ - size_t i; - - for(i = 0; i < len; i++) - h = h * 263 + str[i]; - return h; -} - -static inline uint32_t hash_string16(const uint16_t *str, - size_t len, uint32_t h) -{ - size_t i; - - for(i = 0; i < len; i++) - h = h * 263 + str[i]; - return h; -} - -static uint32_t hash_string(JSString *str, uint32_t h) -{ - if (str->is_wide_char) - h = hash_string16(str16(str), str->len, h); - else - h = hash_string8(str8(str), str->len, h); - return h; -} - -static __maybe_unused void JS_DumpString(JSRuntime *rt, JSString *p) -{ - int i, c, sep; - - if (p == NULL) { - printf(""); - return; - } - if (p->header.ref_count != 1) - printf("%d", p->header.ref_count); - if (p->is_wide_char) - putchar('L'); - sep = '\"'; - putchar(sep); - for(i = 0; i < p->len; i++) { - c = string_get(p, i); - if (c == sep || c == '\\') { - putchar('\\'); - putchar(c); - } else if (c >= ' ' && c <= 126) { - putchar(c); - } else if (c == '\n') { - putchar('\\'); - putchar('n'); - } else { - printf("\\u%04x", c); - } - } - putchar(sep); -} - -static __maybe_unused void JS_DumpAtoms(JSRuntime *rt) -{ - JSAtomStruct *p; - int h, i; - /* This only dumps hashed atoms, not JS_ATOM_TYPE_SYMBOL atoms */ - printf("JSAtom count=%d size=%d hash_size=%d:\n", - rt->atom_count, rt->atom_size, rt->atom_hash_size); - printf("JSAtom hash table: {\n"); - for(i = 0; i < rt->atom_hash_size; i++) { - h = rt->atom_hash[i]; - if (h) { - printf(" %d:", i); - while (h) { - p = rt->atom_array[h]; - printf(" "); - JS_DumpString(rt, p); - h = p->hash_next; - } - printf("\n"); - } - } - printf("}\n"); - printf("JSAtom table: {\n"); - for(i = 0; i < rt->atom_size; i++) { - p = rt->atom_array[i]; - if (!atom_is_free(p)) { - printf(" %d: { %d %08x ", i, p->atom_type, p->hash); - if (!(p->len == 0 && p->is_wide_char != 0)) - JS_DumpString(rt, p); - printf(" %d }\n", p->hash_next); - } - } - printf("}\n"); -} - -static int JS_ResizeAtomHash(JSRuntime *rt, int new_hash_size) -{ - JSAtomStruct *p; - uint32_t new_hash_mask, h, i, hash_next1, j, *new_hash; - - assert((new_hash_size & (new_hash_size - 1)) == 0); /* power of two */ - new_hash_mask = new_hash_size - 1; - new_hash = js_mallocz_rt(rt, sizeof(rt->atom_hash[0]) * new_hash_size); - if (!new_hash) - return -1; - for(i = 0; i < rt->atom_hash_size; i++) { - h = rt->atom_hash[i]; - while (h != 0) { - p = rt->atom_array[h]; - hash_next1 = p->hash_next; - /* add in new hash table */ - j = p->hash & new_hash_mask; - p->hash_next = new_hash[j]; - new_hash[j] = h; - h = hash_next1; - } - } - js_free_rt(rt, rt->atom_hash); - rt->atom_hash = new_hash; - rt->atom_hash_size = new_hash_size; - rt->atom_count_resize = JS_ATOM_COUNT_RESIZE(new_hash_size); - // JS_DumpAtoms(rt); - return 0; -} - -static int JS_InitAtoms(JSRuntime *rt) -{ - int i, len, atom_type; - const char *p; - - rt->atom_hash_size = 0; - rt->atom_hash = NULL; - rt->atom_count = 0; - rt->atom_size = 0; - rt->atom_free_index = 0; - if (JS_ResizeAtomHash(rt, 256)) /* there are at least 195 predefined atoms */ - return -1; - - p = js_atom_init; - for(i = 1; i < JS_ATOM_END; i++) { - if (i == JS_ATOM_Private_brand) - atom_type = JS_ATOM_TYPE_PRIVATE; - else if (i >= JS_ATOM_Symbol_toPrimitive) - atom_type = JS_ATOM_TYPE_SYMBOL; - else - atom_type = JS_ATOM_TYPE_STRING; - len = strlen(p); - if (__JS_NewAtomInit(rt, p, len, atom_type) == JS_ATOM_NULL) - return -1; - p = p + len + 1; - } - return 0; -} - -static JSAtom JS_DupAtomRT(JSRuntime *rt, JSAtom v) -{ - JSAtomStruct *p; - - if (!__JS_AtomIsConst(v)) { - p = rt->atom_array[v]; - p->header.ref_count++; - } - return v; -} - -JSAtom JS_DupAtom(JSContext *ctx, JSAtom v) -{ - JSRuntime *rt; - JSAtomStruct *p; - - if (!__JS_AtomIsConst(v)) { - rt = ctx->rt; - p = rt->atom_array[v]; - p->header.ref_count++; - } - return v; -} - -static JSAtomKindEnum JS_AtomGetKind(JSContext *ctx, JSAtom v) -{ - JSRuntime *rt; - JSAtomStruct *p; - - rt = ctx->rt; - if (__JS_AtomIsTaggedInt(v)) - return JS_ATOM_KIND_STRING; - p = rt->atom_array[v]; - switch(p->atom_type) { - case JS_ATOM_TYPE_STRING: - return JS_ATOM_KIND_STRING; - case JS_ATOM_TYPE_GLOBAL_SYMBOL: - return JS_ATOM_KIND_SYMBOL; - case JS_ATOM_TYPE_SYMBOL: - switch(p->hash) { - case JS_ATOM_HASH_SYMBOL: - return JS_ATOM_KIND_SYMBOL; - case JS_ATOM_HASH_PRIVATE: - return JS_ATOM_KIND_PRIVATE; - default: - abort(); - } - default: - abort(); - } - return (JSAtomKindEnum){-1}; // pacify compiler -} - -static JSAtom js_get_atom_index(JSRuntime *rt, JSAtomStruct *p) -{ - uint32_t i = p->hash_next; /* atom_index */ - if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { - JSAtomStruct *p1; - - i = rt->atom_hash[p->hash & (rt->atom_hash_size - 1)]; - p1 = rt->atom_array[i]; - while (p1 != p) { - assert(i != 0); - i = p1->hash_next; - p1 = rt->atom_array[i]; - } - } - return i; -} - -/* string case (internal). Return JS_ATOM_NULL if error. 'str' is - freed. */ -static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) -{ - uint32_t h, h1, i; - JSAtomStruct *p; - int len; - - if (atom_type < JS_ATOM_TYPE_SYMBOL) { - /* str is not NULL */ - if (str->atom_type == atom_type) { - /* str is the atom, return its index */ - i = js_get_atom_index(rt, str); - /* reduce string refcount and increase atom's unless constant */ - if (__JS_AtomIsConst(i)) - str->header.ref_count--; - return i; - } - /* try and locate an already registered atom */ - len = str->len; - h = hash_string(str, atom_type); - h &= JS_ATOM_HASH_MASK; - h1 = h & (rt->atom_hash_size - 1); - i = rt->atom_hash[h1]; - while (i != 0) { - p = rt->atom_array[i]; - if (p->hash == h && - p->atom_type == atom_type && - p->len == len && - js_string_memcmp(p, str, len) == 0) { - if (!__JS_AtomIsConst(i)) - p->header.ref_count++; - goto done; - } - i = p->hash_next; - } - } else { - h1 = 0; /* avoid warning */ - if (atom_type == JS_ATOM_TYPE_SYMBOL) { - h = JS_ATOM_HASH_SYMBOL; - } else { - h = JS_ATOM_HASH_PRIVATE; - atom_type = JS_ATOM_TYPE_SYMBOL; - } - } - - if (rt->atom_free_index == 0) { - /* allow new atom entries */ - uint32_t new_size, start; - JSAtomStruct **new_array; - - /* alloc new with size progression 3/2: - 4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092 - preallocating space for predefined atoms (at least 195). - */ - new_size = max_int(211, rt->atom_size * 3 / 2); - if (new_size > JS_ATOM_MAX) - goto fail; - /* XXX: should use realloc2 to use slack space */ - new_array = js_realloc_rt(rt, rt->atom_array, sizeof(*new_array) * new_size); - if (!new_array) - goto fail; - /* Note: the atom 0 is not used */ - start = rt->atom_size; - if (start == 0) { - /* JS_ATOM_NULL entry */ - p = js_mallocz_rt(rt, sizeof(JSAtomStruct)); - if (!p) { - js_free_rt(rt, new_array); - goto fail; - } - p->header.ref_count = 1; /* not refcounted */ - p->atom_type = JS_ATOM_TYPE_SYMBOL; -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_add_tail(&p->link, &rt->string_list); -#endif - new_array[0] = p; - rt->atom_count++; - start = 1; - } - rt->atom_size = new_size; - rt->atom_array = new_array; - rt->atom_free_index = start; - for(i = start; i < new_size; i++) { - uint32_t next; - if (i == (new_size - 1)) - next = 0; - else - next = i + 1; - rt->atom_array[i] = atom_set_free(next); - } - } - - if (str) { - if (str->atom_type == 0) { - p = str; - p->atom_type = atom_type; - } else { - p = js_malloc_rt(rt, sizeof(JSString) + - (str->len << str->is_wide_char) + - 1 - str->is_wide_char); - if (unlikely(!p)) - goto fail; - p->header.ref_count = 1; - p->is_wide_char = str->is_wide_char; - p->len = str->len; - p->kind = JS_STRING_KIND_NORMAL; -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_add_tail(&p->link, &rt->string_list); -#endif - memcpy(str8(p), str8(str), (str->len << str->is_wide_char) + - 1 - str->is_wide_char); - js_free_string(rt, str); - } - } else { - p = js_malloc_rt(rt, sizeof(JSAtomStruct)); /* empty wide string */ - if (!p) - return JS_ATOM_NULL; - p->header.ref_count = 1; - p->is_wide_char = 1; /* Hack to represent NULL as a JSString */ - p->len = 0; - p->kind = JS_STRING_KIND_NORMAL; -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_add_tail(&p->link, &rt->string_list); -#endif - } - - /* use an already free entry */ - i = rt->atom_free_index; - rt->atom_free_index = atom_get_free(rt->atom_array[i]); - rt->atom_array[i] = p; - - p->hash = h; - p->hash_next = i; /* atom_index */ - p->atom_type = atom_type; - p->first_weak_ref = NULL; - - rt->atom_count++; - - if (atom_type != JS_ATOM_TYPE_SYMBOL) { - p->hash_next = rt->atom_hash[h1]; - rt->atom_hash[h1] = i; - if (unlikely(rt->atom_count >= rt->atom_count_resize)) - JS_ResizeAtomHash(rt, rt->atom_hash_size * 2); - } - - // JS_DumpAtoms(rt); - return i; - - fail: - i = JS_ATOM_NULL; - done: - if (str) - js_free_string(rt, str); - return i; -} - -// XXX: `str` must be pure ASCII. No UTF-8 encoded strings -// XXX: `str` must not be the string representation of a small integer -static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, - int atom_type) -{ - JSString *p; - p = js_alloc_string_rt(rt, len, 0); - if (!p) - return JS_ATOM_NULL; - memcpy(str8(p), str, len); - str8(p)[len] = '\0'; - return __JS_NewAtom(rt, p, atom_type); -} - -// XXX: `str` must be raw 8-bit contents. No UTF-8 encoded strings -static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len, - int atom_type) -{ - uint32_t h, h1, i; - JSAtomStruct *p; - - h = hash_string8((const uint8_t *)str, len, JS_ATOM_TYPE_STRING); - h &= JS_ATOM_HASH_MASK; - h1 = h & (rt->atom_hash_size - 1); - i = rt->atom_hash[h1]; - while (i != 0) { - p = rt->atom_array[i]; - if (p->hash == h && - p->atom_type == JS_ATOM_TYPE_STRING && - p->len == len && - p->is_wide_char == 0 && - memcmp(str8(p), str, len) == 0) { - if (!__JS_AtomIsConst(i)) - p->header.ref_count++; - return i; - } - i = p->hash_next; - } - return JS_ATOM_NULL; -} - -static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p) -{ - uint32_t i = p->hash_next; /* atom_index */ - if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { - JSAtomStruct *p0, *p1; - uint32_t h0; - - h0 = p->hash & (rt->atom_hash_size - 1); - i = rt->atom_hash[h0]; - p1 = rt->atom_array[i]; - if (p1 == p) { - rt->atom_hash[h0] = p1->hash_next; - } else { - for(;;) { - assert(i != 0); - p0 = p1; - i = p1->hash_next; - p1 = rt->atom_array[i]; - if (p1 == p) { - p0->hash_next = p1->hash_next; - break; - } - } - } - } - /* insert in free atom list */ - rt->atom_array[i] = atom_set_free(rt->atom_free_index); - rt->atom_free_index = i; - if (unlikely(p->first_weak_ref)) { - reset_weak_ref(rt, &p->first_weak_ref); - } - /* free the string structure */ -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_del(&p->link); -#endif - js_free_rt(rt, p); - rt->atom_count--; - assert(rt->atom_count >= 0); -} - -static void __JS_FreeAtom(JSRuntime *rt, uint32_t i) -{ - JSAtomStruct *p; - - p = rt->atom_array[i]; - if (--p->header.ref_count > 0) - return; - JS_FreeAtomStruct(rt, p); -} - -/* Warning: 'p' is freed */ -static JSAtom JS_NewAtomStr(JSContext *ctx, JSString *p) -{ - JSRuntime *rt = ctx->rt; - uint32_t n; - if (is_num_string(&n, p)) { - if (n <= JS_ATOM_MAX_INT) { - js_free_string(rt, p); - return __JS_AtomFromUInt32(n); - } - } - /* XXX: should generate an exception */ - return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING); -} - -/* `str` may be pure ASCII or UTF-8 encoded */ -JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len) -{ - JSValue val; - - if (len == 0 || !is_digit(*str)) { - // TODO(chqrlie): this does not work if `str` has UTF-8 encoded contents - // bug example: `({ "\u00c3\u00a9": 1 }).\u00e9` evaluates to `1`. - JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING); - if (atom) - return atom; - } - val = JS_NewStringLen(ctx, str, len); - if (JS_IsException(val)) - return JS_ATOM_NULL; - return JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(val)); -} - -/* `str` may be pure ASCII or UTF-8 encoded */ -JSAtom JS_NewAtom(JSContext *ctx, const char *str) -{ - return JS_NewAtomLen(ctx, str, strlen(str)); -} - -JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n) -{ - if (n <= JS_ATOM_MAX_INT) { - return __JS_AtomFromUInt32(n); - } else { - char buf[16]; - size_t len = u32toa(buf, n); - JSValue val = js_new_string8_len(ctx, buf, len); - if (JS_IsException(val)) - return JS_ATOM_NULL; - return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), - JS_ATOM_TYPE_STRING); - } -} - -static JSAtom JS_NewAtomInt64(JSContext *ctx, int64_t n) -{ - if ((uint64_t)n <= JS_ATOM_MAX_INT) { - return __JS_AtomFromUInt32((uint32_t)n); - } else { - char buf[24]; - size_t len = i64toa(buf, n); - JSValue val = js_new_string8_len(ctx, buf, len); - if (JS_IsException(val)) - return JS_ATOM_NULL; - return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), - JS_ATOM_TYPE_STRING); - } -} - -/* 'p' is freed */ -static JSValue JS_NewSymbolInternal(JSContext *ctx, JSString *p, int atom_type) -{ - JSRuntime *rt = ctx->rt; - JSAtom atom; - atom = __JS_NewAtom(rt, p, atom_type); - if (atom == JS_ATOM_NULL) - return JS_ThrowOutOfMemory(ctx); - return JS_MKPTR(JS_TAG_SYMBOL, rt->atom_array[atom]); -} - -/* descr must be a non-numeric string atom */ -static JSValue JS_NewSymbolFromAtom(JSContext *ctx, JSAtom descr, - int atom_type) -{ - JSRuntime *rt = ctx->rt; - JSString *p; - - assert(!__JS_AtomIsTaggedInt(descr)); - assert(descr < rt->atom_size); - p = rt->atom_array[descr]; - js_dup(JS_MKPTR(JS_TAG_STRING, p)); - return JS_NewSymbolInternal(ctx, p, atom_type); -} - -/* `description` may be pure ASCII or UTF-8 encoded */ -JSValue JS_NewSymbol(JSContext *ctx, const char *description, bool is_global) -{ - JSAtom atom = JS_NewAtom(ctx, description); - if (atom == JS_ATOM_NULL) - return JS_EXCEPTION; - return JS_NewSymbolFromAtom(ctx, atom, is_global ? JS_ATOM_TYPE_GLOBAL_SYMBOL : JS_ATOM_TYPE_SYMBOL); -} - -#define ATOM_GET_STR_BUF_SIZE 64 - -static const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size, - JSAtom atom) -{ - if (__JS_AtomIsTaggedInt(atom)) { - snprintf(buf, buf_size, "%u", __JS_AtomToUInt32(atom)); - } else if (atom == JS_ATOM_NULL) { - snprintf(buf, buf_size, ""); - } else if (atom >= rt->atom_size) { - assert(atom < rt->atom_size); - snprintf(buf, buf_size, "", atom); - } else { - JSAtomStruct *p = rt->atom_array[atom]; - *buf = '\0'; - if (atom_is_free(p)) { - snprintf(buf, buf_size, "", atom); - } else if (p != NULL) { - JSString *str = p; - if (str->is_wide_char) { - /* encode surrogates correctly */ - utf8_encode_buf16(buf, buf_size, str16(str), str->len); - } else { - utf8_encode_buf8(buf, buf_size, str8(str), str->len); - } - } - } - return buf; -} - -static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom) -{ - return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom); -} - -static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, bool force_string) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - - if (__JS_AtomIsTaggedInt(atom)) { - size_t len = u32toa(buf, __JS_AtomToUInt32(atom)); - return js_new_string8_len(ctx, buf, len); - } else { - JSRuntime *rt = ctx->rt; - JSAtomStruct *p; - assert(atom < rt->atom_size); - p = rt->atom_array[atom]; - if (p->atom_type == JS_ATOM_TYPE_STRING) { - goto ret_string; - } else if (force_string) { - if (p->len == 0 && p->is_wide_char != 0) { - /* no description string */ - p = rt->atom_array[JS_ATOM_empty_string]; - } - ret_string: - return js_dup(JS_MKPTR(JS_TAG_STRING, p)); - } else { - return js_dup(JS_MKPTR(JS_TAG_SYMBOL, p)); - } - } -} - -JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom) -{ - return __JS_AtomToValue(ctx, atom, false); -} - -JSValue JS_AtomToString(JSContext *ctx, JSAtom atom) -{ - return __JS_AtomToValue(ctx, atom, true); -} - -/* return true if the atom is an array index (i.e. 0 <= index <= - 2^32-2 and return its value */ -static bool JS_AtomIsArrayIndex(JSContext *ctx, uint32_t *pval, JSAtom atom) -{ - if (__JS_AtomIsTaggedInt(atom)) { - *pval = __JS_AtomToUInt32(atom); - return true; - } else { - JSRuntime *rt = ctx->rt; - JSAtomStruct *p; - uint32_t val; - - assert(atom < rt->atom_size); - p = rt->atom_array[atom]; - if (p->atom_type == JS_ATOM_TYPE_STRING && - is_num_string(&val, p) && val != -1) { - *pval = val; - return true; - } else { - *pval = 0; - return false; - } - } -} - -/* This test must be fast if atom is not a numeric index (e.g. a - method name). Return JS_UNDEFINED if not a numeric - index. JS_EXCEPTION can also be returned. */ -static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom) -{ - JSRuntime *rt = ctx->rt; - JSAtomStruct *p1; - JSString *p; - int c, len, ret; - JSValue num, str; - - if (__JS_AtomIsTaggedInt(atom)) - return js_int32(__JS_AtomToUInt32(atom)); - assert(atom < rt->atom_size); - p1 = rt->atom_array[atom]; - if (p1->atom_type != JS_ATOM_TYPE_STRING) - return JS_UNDEFINED; - p = p1; - len = p->len; - if (p->is_wide_char) { - const uint16_t *r = str16(p), *r_end = str16(p) + len; - if (r >= r_end) - return JS_UNDEFINED; - c = *r; - if (c == '-') { - if (r >= r_end) - return JS_UNDEFINED; - r++; - c = *r; - /* -0 case is specific */ - if (c == '0' && len == 2) - goto minus_zero; - } - /* XXX: should test NaN, but the tests do not check it */ - if (!is_num(c)) { - /* XXX: String should be normalized, therefore 8-bit only */ - const uint16_t nfinity16[7] = { 'n', 'f', 'i', 'n', 'i', 't', 'y' }; - if (!(c =='I' && (r_end - r) == 8 && - !memcmp(r + 1, nfinity16, sizeof(nfinity16)))) - return JS_UNDEFINED; - } - } else { - const uint8_t *r = str8(p), *r_end = str8(p) + len; - if (r >= r_end) - return JS_UNDEFINED; - c = *r; - if (c == '-') { - if (r >= r_end) - return JS_UNDEFINED; - r++; - c = *r; - /* -0 case is specific */ - if (c == '0' && len == 2) { - minus_zero: - return js_float64(-0.0); - } - } - if (!is_num(c)) { - if (!(c =='I' && (r_end - r) == 8 && - !memcmp(r + 1, "nfinity", 7))) - return JS_UNDEFINED; - } - } - /* this is ECMA CanonicalNumericIndexString primitive */ - num = JS_ToNumber(ctx, JS_MKPTR(JS_TAG_STRING, p)); - if (JS_IsException(num)) - return num; - str = JS_ToString(ctx, num); - if (JS_IsException(str)) { - JS_FreeValue(ctx, num); - return str; - } - ret = js_string_eq(p, JS_VALUE_GET_STRING(str)); - JS_FreeValue(ctx, str); - if (ret) { - return num; - } else { - JS_FreeValue(ctx, num); - return JS_UNDEFINED; - } -} - -/* return -1 if exception or true/false */ -static int JS_AtomIsNumericIndex(JSContext *ctx, JSAtom atom) -{ - JSValue num; - num = JS_AtomIsNumericIndex1(ctx, atom); - if (likely(JS_IsUndefined(num))) - return false; - if (JS_IsException(num)) - return -1; - JS_FreeValue(ctx, num); - return true; -} - -void JS_FreeAtom(JSContext *ctx, JSAtom v) -{ - if (!__JS_AtomIsConst(v)) - __JS_FreeAtom(ctx->rt, v); -} - -void JS_FreeAtomRT(JSRuntime *rt, JSAtom v) -{ - if (!__JS_AtomIsConst(v)) - __JS_FreeAtom(rt, v); -} - -/* return true if 'v' is a symbol with a string description */ -static bool JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v) -{ - JSRuntime *rt; - JSAtomStruct *p; - - rt = ctx->rt; - if (__JS_AtomIsTaggedInt(v)) - return false; - p = rt->atom_array[v]; - return (((p->atom_type == JS_ATOM_TYPE_SYMBOL && - p->hash == JS_ATOM_HASH_SYMBOL) || - p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) && - !(p->len == 0 && p->is_wide_char != 0)); -} - -static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - const char *p; - int i; - - /* XXX: should handle embedded null characters */ - /* XXX: should move encoding code to JS_AtomGetStr */ - p = JS_AtomGetStr(ctx, buf, sizeof(buf), atom); - for (i = 0; p[i]; i++) { - int c = (unsigned char)p[i]; - if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || - (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) - break; - } - if (i > 0 && p[i] == '\0') { - printf("%s", p); - } else { - putchar('"'); - printf("%.*s", i, p); - for (; p[i]; i++) { - int c = (unsigned char)p[i]; - if (c == '\"' || c == '\\') { - putchar('\\'); - putchar(c); - } else if (c >= ' ' && c <= 126) { - putchar(c); - } else if (c == '\n') { - putchar('\\'); - putchar('n'); - } else { - printf("\\u%04x", c); - } - } - putchar('\"'); - } -} - -/* free with JS_FreeCString() */ -const char *JS_AtomToCStringLen(JSContext *ctx, size_t *plen, JSAtom atom) -{ - JSValue str; - const char *cstr; - - str = JS_AtomToString(ctx, atom); - if (JS_IsException(str)) { - if (plen) - *plen = 0; - return NULL; - } - cstr = JS_ToCStringLen(ctx, plen, str); - JS_FreeValue(ctx, str); - return cstr; -} - -#ifndef QJS_DISABLE_PARSER - -/* return a string atom containing name concatenated with str1 */ -/* `str1` may be pure ASCII or UTF-8 encoded */ -// TODO(chqrlie): use string concatenation instead of UTF-8 conversion -static JSAtom js_atom_concat_str(JSContext *ctx, JSAtom name, const char *str1) -{ - JSValue str; - JSAtom atom; - const char *cstr; - char *cstr2; - size_t len, len1; - - str = JS_AtomToString(ctx, name); - if (JS_IsException(str)) - return JS_ATOM_NULL; - cstr = JS_ToCStringLen(ctx, &len, str); - if (!cstr) - goto fail; - len1 = strlen(str1); - cstr2 = js_malloc(ctx, len + len1 + 1); - if (!cstr2) - goto fail; - memcpy(cstr2, cstr, len); - memcpy(cstr2 + len, str1, len1); - cstr2[len + len1] = '\0'; - atom = JS_NewAtomLen(ctx, cstr2, len + len1); - js_free(ctx, cstr2); - JS_FreeCString(ctx, cstr); - JS_FreeValue(ctx, str); - return atom; - fail: - JS_FreeCString(ctx, cstr); - JS_FreeValue(ctx, str); - return JS_ATOM_NULL; -} - -static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n) -{ - char buf[16]; - size_t len; - - len = u32toa(buf, n); - buf[len] = '\0'; - return js_atom_concat_str(ctx, name, buf); -} - -#endif // QJS_DISABLE_PARSER - -static inline bool JS_IsEmptyString(JSValueConst v) -{ - return JS_VALUE_GET_TAG(v) == JS_TAG_STRING && JS_VALUE_GET_STRING(v)->len == 0; -} - -/* JSClass support */ - -/* a new class ID is allocated if *pclass_id == 0, otherwise *pclass_id is left unchanged */ -JSClassID JS_NewClassID(JSRuntime *rt, JSClassID *pclass_id) -{ - JSClassID class_id = *pclass_id; - if (class_id == 0) { - class_id = rt->js_class_id_alloc++; - *pclass_id = class_id; - } - return class_id; -} - -JSClassID JS_GetClassID(JSValueConst v) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT) - return JS_INVALID_CLASS_ID; - p = JS_VALUE_GET_OBJ(v); - return p->class_id; -} - -bool JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id) -{ - return (class_id < rt->class_count && - rt->class_array[class_id].class_id != 0); -} - -JSAtom JS_GetClassName(JSRuntime *rt, JSClassID class_id) -{ - if (JS_IsRegisteredClass(rt, class_id)) { - return JS_DupAtomRT(rt, rt->class_array[class_id].class_id); - } else { - return JS_ATOM_NULL; - } -} - -/* create a new object internal class. Return -1 if error, 0 if - OK. The finalizer can be NULL if none is needed. */ -static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, - const JSClassDef *class_def, JSAtom name) -{ - int new_size, i; - JSClass *cl, *new_class_array; - struct list_head *el; - - if (class_id >= (1 << 16)) - return -1; - if (class_id < rt->class_count && - rt->class_array[class_id].class_id != 0) - return -1; - - if (class_id >= rt->class_count) { - new_size = max_int(JS_CLASS_INIT_COUNT, - max_int(class_id + 1, rt->class_count * 3 / 2)); - - /* reallocate the context class prototype array, if any */ - list_for_each(el, &rt->context_list) { - JSContext *ctx = list_entry(el, JSContext, link); - JSValue *new_tab; - new_tab = js_realloc_rt(rt, ctx->class_proto, - sizeof(ctx->class_proto[0]) * new_size); - if (!new_tab) - return -1; - for(i = rt->class_count; i < new_size; i++) - new_tab[i] = JS_NULL; - ctx->class_proto = new_tab; - } - /* reallocate the class array */ - new_class_array = js_realloc_rt(rt, rt->class_array, - sizeof(JSClass) * new_size); - if (!new_class_array) - return -1; - memset(new_class_array + rt->class_count, 0, - (new_size - rt->class_count) * sizeof(JSClass)); - rt->class_array = new_class_array; - rt->class_count = new_size; - } - cl = &rt->class_array[class_id]; - cl->class_id = class_id; - cl->class_name = JS_DupAtomRT(rt, name); - cl->finalizer = class_def->finalizer; - cl->gc_mark = class_def->gc_mark; - cl->call = class_def->call; - cl->exotic = class_def->exotic; - return 0; -} - -int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def) -{ - int ret, len; - JSAtom name; - - // XXX: class_def->class_name must be raw 8-bit contents. No UTF-8 encoded strings - len = strlen(class_def->class_name); - name = __JS_FindAtom(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); - if (name == JS_ATOM_NULL) { - name = __JS_NewAtomInit(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); - if (name == JS_ATOM_NULL) - return -1; - } - ret = JS_NewClass1(rt, class_id, class_def, name); - JS_FreeAtomRT(rt, name); - return ret; -} - -static inline JSValue js_empty_string(JSRuntime *rt) -{ - JSAtomStruct *p = rt->atom_array[JS_ATOM_empty_string]; - return js_dup(JS_MKPTR(JS_TAG_STRING, p)); -} - -// XXX: `buf` contains raw 8-bit data, no UTF-8 decoding is performed -// XXX: no special case for len == 0 -static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len) -{ - JSString *str; - str = js_alloc_string(ctx, len, 0); - if (!str) - return JS_EXCEPTION; - memcpy(str8(str), buf, len); - str8(str)[len] = '\0'; - return JS_MKPTR(JS_TAG_STRING, str); -} - -// XXX: `buf` contains raw 8-bit data, no UTF-8 decoding is performed -// XXX: no special case for the empty string -static inline JSValue js_new_string8(JSContext *ctx, const char *str) -{ - return js_new_string8_len(ctx, str, strlen(str)); -} - -static JSValue js_new_string16_len(JSContext *ctx, const uint16_t *buf, int len) -{ - JSString *str; - str = js_alloc_string(ctx, len, 1); - if (!str) - return JS_EXCEPTION; - memcpy(str16(str), buf, len * 2); - return JS_MKPTR(JS_TAG_STRING, str); -} - -static JSValue js_new_string_char(JSContext *ctx, uint16_t c) -{ - if (c < 0x100) { - char ch8 = c; - return js_new_string8_len(ctx, &ch8, 1); - } else { - uint16_t ch16 = c; - return js_new_string16_len(ctx, &ch16, 1); - } -} - -static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end) -{ - JSStringSlice *slice; - JSString *q; - int len; - - len = end - start; - if (start == 0 && end == p->len) { - return js_dup(JS_MKPTR(JS_TAG_STRING, p)); - } - if (len <= 0) { - return js_empty_string(ctx->rt); - } - if (len > (JS_STRING_SLICE_LEN_MAX >> p->is_wide_char)) { - if (p->kind == JS_STRING_KIND_SLICE) { - slice = (void *)&p[1]; - p = slice->parent; - start += slice->start >> p->is_wide_char; // bytes -> chars - } - // allocate as 16 bit wide string to avoid wastage; - // js_alloc_string allocates 1 byte extra for 8 bit strings; - q = js_alloc_string(ctx, sizeof(*slice)/2, /*is_wide_char*/true); - if (!q) - return JS_EXCEPTION; - q->is_wide_char = p->is_wide_char; - q->kind = JS_STRING_KIND_SLICE; - q->len = len; - slice = (void *)&q[1]; - slice->parent = p; - slice->start = start << p->is_wide_char; // chars -> bytes - p->header.ref_count++; - return JS_MKPTR(JS_TAG_STRING, q); - } - if (p->is_wide_char) { - JSString *str; - int i; - uint16_t c = 0; - for (i = start; i < end; i++) { - c |= str16(p)[i]; - } - if (c > 0xFF) - return js_new_string16_len(ctx, str16(p) + start, len); - - str = js_alloc_string(ctx, len, 0); - if (!str) - return JS_EXCEPTION; - for (i = 0; i < len; i++) { - str8(str)[i] = str16(p)[start + i]; - } - str8(str)[len] = '\0'; - return JS_MKPTR(JS_TAG_STRING, str); - } else { - return js_new_string8_len(ctx, (const char *)(str8(p) + start), len); - } -} - -typedef struct StringBuffer { - JSContext *ctx; - JSString *str; - int len; - int size; - int is_wide_char; - int error_status; -} StringBuffer; - -/* It is valid to call string_buffer_end() and all string_buffer functions even - if string_buffer_init() or another string_buffer function returns an error. - If the error_status is set, string_buffer_end() returns JS_EXCEPTION. - */ -static int string_buffer_init2(JSContext *ctx, StringBuffer *s, int size, - int is_wide) -{ - s->ctx = ctx; - s->size = size; - s->len = 0; - s->is_wide_char = is_wide; - s->error_status = 0; - s->str = js_alloc_string(ctx, size, is_wide); - if (unlikely(!s->str)) { - s->size = 0; - return s->error_status = -1; - } -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - /* the StringBuffer may reallocate the JSString, only link it at the end */ - list_del(&s->str->link); -#endif - return 0; -} - -static inline int string_buffer_init(JSContext *ctx, StringBuffer *s, int size) -{ - return string_buffer_init2(ctx, s, size, 0); -} - -static void string_buffer_free(StringBuffer *s) -{ - js_free(s->ctx, s->str); - s->str = NULL; -} - -static int string_buffer_set_error(StringBuffer *s) -{ - js_free(s->ctx, s->str); - s->str = NULL; - s->size = 0; - s->len = 0; - return s->error_status = -1; -} - -static no_inline int string_buffer_widen(StringBuffer *s, int size) -{ - JSString *str; - size_t slack; - int i; - - if (s->error_status) - return -1; - - str = js_realloc2(s->ctx, s->str, sizeof(JSString) + (size << 1), &slack); - if (!str) - return string_buffer_set_error(s); - size += slack >> 1; - for(i = s->len; i-- > 0;) { - str16(str)[i] = str8(str)[i]; - } - s->is_wide_char = 1; - s->size = size; - s->str = str; - return 0; -} - -static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c) -{ - JSString *new_str; - int new_size; - size_t new_size_bytes, slack; - - if (s->error_status) - return -1; - - if (new_len > JS_STRING_LEN_MAX) { - JS_ThrowRangeError(s->ctx, "invalid string length"); - return string_buffer_set_error(s); - } - new_size = min_int(max_int(new_len, s->size * 3 / 2), JS_STRING_LEN_MAX); - if (!s->is_wide_char && c >= 0x100) { - return string_buffer_widen(s, new_size); - } - new_size_bytes = sizeof(JSString) + (new_size << s->is_wide_char) + 1 - s->is_wide_char; - new_str = js_realloc2(s->ctx, s->str, new_size_bytes, &slack); - if (!new_str) - return string_buffer_set_error(s); - new_size = min_int(new_size + (slack >> s->is_wide_char), JS_STRING_LEN_MAX); - s->size = new_size; - s->str = new_str; - return 0; -} - -static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c) -{ - if (unlikely(s->len >= s->size)) { - if (string_buffer_realloc(s, s->len + 1, c)) - return -1; - } - if (s->is_wide_char) { - str16(s->str)[s->len++] = c; - } else if (c < 0x100) { - str8(s->str)[s->len++] = c; - } else { - if (string_buffer_widen(s, s->size)) - return -1; - str16(s->str)[s->len++] = c; - } - return 0; -} - -/* 0 <= c <= 0xff */ -static int string_buffer_putc8(StringBuffer *s, uint32_t c) -{ - if (unlikely(s->len >= s->size)) { - if (string_buffer_realloc(s, s->len + 1, c)) - return -1; - } - if (s->is_wide_char) { - str16(s->str)[s->len++] = c; - } else { - str8(s->str)[s->len++] = c; - } - return 0; -} - -/* 0 <= c <= 0xffff */ -static int string_buffer_putc16(StringBuffer *s, uint32_t c) -{ - if (likely(s->len < s->size)) { - if (s->is_wide_char) { - str16(s->str)[s->len++] = c; - return 0; - } else if (c < 0x100) { - str8(s->str)[s->len++] = c; - return 0; - } - } - return string_buffer_putc_slow(s, c); -} - -/* 0 <= c <= 0x10ffff */ -static int string_buffer_putc(StringBuffer *s, uint32_t c) -{ - if (unlikely(c >= 0x10000)) { - /* surrogate pair */ - if (string_buffer_putc16(s, get_hi_surrogate(c))) - return -1; - c = get_lo_surrogate(c); - } - return string_buffer_putc16(s, c); -} - -static int string_getc(JSString *p, int *pidx) -{ - int idx, c, c1; - idx = *pidx; - if (p->is_wide_char) { - c = str16(p)[idx++]; - if (is_hi_surrogate(c) && idx < p->len) { - c1 = str16(p)[idx]; - if (is_lo_surrogate(c1)) { - c = from_surrogate(c, c1); - idx++; - } - } - } else { - c = str8(p)[idx++]; - } - *pidx = idx; - return c; -} - -static int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len) -{ - int i; - - if (s->len + len > s->size) { - if (string_buffer_realloc(s, s->len + len, 0)) - return -1; - } - if (s->is_wide_char) { - for (i = 0; i < len; i++) { - str16(s->str)[s->len + i] = p[i]; - } - s->len += len; - } else { - memcpy(&str8(s->str)[s->len], p, len); - s->len += len; - } - return 0; -} - -static int string_buffer_write16(StringBuffer *s, const uint16_t *p, int len) -{ - int c = 0, i; - - for (i = 0; i < len; i++) { - c |= p[i]; - } - if (s->len + len > s->size) { - if (string_buffer_realloc(s, s->len + len, c)) - return -1; - } else if (!s->is_wide_char && c >= 0x100) { - if (string_buffer_widen(s, s->size)) - return -1; - } - if (s->is_wide_char) { - memcpy(&str16(s->str)[s->len], p, len << 1); - s->len += len; - } else { - for (i = 0; i < len; i++) { - str8(s->str)[s->len + i] = p[i]; - } - s->len += len; - } - return 0; -} - -/* appending an ASCII string */ -static int string_buffer_puts8(StringBuffer *s, const char *str) -{ - return string_buffer_write8(s, (const uint8_t *)str, strlen(str)); -} - -static int string_buffer_concat(StringBuffer *s, JSString *p, - uint32_t from, uint32_t to) -{ - if (to <= from) - return 0; - if (p->is_wide_char) - return string_buffer_write16(s, str16(p) + from, to - from); - else - return string_buffer_write8(s, str8(p) + from, to - from); -} - -static int string_buffer_concat_value(StringBuffer *s, JSValueConst v) -{ - JSString *p; - JSValue v1; - int res; - - if (s->error_status) { - /* prevent exception overload */ - return -1; - } - if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { - v1 = JS_ToString(s->ctx, v); - if (JS_IsException(v1)) - return string_buffer_set_error(s); - p = JS_VALUE_GET_STRING(v1); - res = string_buffer_concat(s, p, 0, p->len); - JS_FreeValue(s->ctx, v1); - return res; - } - p = JS_VALUE_GET_STRING(v); - return string_buffer_concat(s, p, 0, p->len); -} - -static int string_buffer_concat_value_free(StringBuffer *s, JSValue v) -{ - JSString *p; - int res; - - if (s->error_status) { - /* prevent exception overload */ - JS_FreeValue(s->ctx, v); - return -1; - } - if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { - v = JS_ToStringFree(s->ctx, v); - if (JS_IsException(v)) - return string_buffer_set_error(s); - } - p = JS_VALUE_GET_STRING(v); - res = string_buffer_concat(s, p, 0, p->len); - JS_FreeValue(s->ctx, v); - return res; -} - -static int string_buffer_fill(StringBuffer *s, int c, int count) -{ - /* XXX: optimize */ - if (s->len + count > s->size) { - if (string_buffer_realloc(s, s->len + count, c)) - return -1; - } - while (count-- > 0) { - if (string_buffer_putc16(s, c)) - return -1; - } - return 0; -} - -static JSValue string_buffer_end(StringBuffer *s) -{ - JSString *str; - str = s->str; - if (s->error_status) - return JS_EXCEPTION; - if (s->len == 0) { - js_free(s->ctx, str); - s->str = NULL; - return js_empty_string(s->ctx->rt); - } - if (s->len < s->size) { - /* smaller size so js_realloc should not fail, but OK if it does */ - /* XXX: should add some slack to avoid unnecessary calls */ - /* XXX: might need to use malloc+free to ensure smaller size */ - str = js_realloc_rt(s->ctx->rt, str, sizeof(JSString) + - (s->len << s->is_wide_char) + 1 - s->is_wide_char); - if (str == NULL) - str = s->str; - s->str = str; - } - if (!s->is_wide_char) - str8(str)[s->len] = 0; -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_add_tail(&str->link, &s->ctx->rt->string_list); -#endif - str->is_wide_char = s->is_wide_char; - str->len = s->len; - s->str = NULL; - return JS_MKPTR(JS_TAG_STRING, str); -} - -/* create a string from a UTF-8 buffer */ -JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t buf_len) -{ - JSString *str; - size_t len; - int kind; - - if (buf_len <= 0) { - return js_empty_string(ctx->rt); - } - /* Compute string kind and length: 7-bit, 8-bit, 16-bit, 16-bit UTF-16 */ - kind = utf8_scan(buf, buf_len, &len); - if (len > JS_STRING_LEN_MAX) - return JS_ThrowRangeError(ctx, "invalid string length"); - - switch (kind) { - case UTF8_PLAIN_ASCII: - str = js_alloc_string(ctx, len, 0); - if (!str) - return JS_EXCEPTION; - memcpy(str8(str), buf, len); - str8(str)[len] = '\0'; - break; - case UTF8_NON_ASCII: - /* buf contains non-ASCII code-points, but limited to 8-bit values */ - str = js_alloc_string(ctx, len, 0); - if (!str) - return JS_EXCEPTION; - utf8_decode_buf8(str8(str), len + 1, buf, buf_len); - break; - default: - // This causes a potential problem in JS_ThrowError if message is invalid - //if (kind & UTF8_HAS_ERRORS) - // return JS_ThrowRangeError(ctx, "invalid UTF-8 sequence"); - str = js_alloc_string(ctx, len, 1); - if (!str) - return JS_EXCEPTION; - utf8_decode_buf16(str16(str), len, buf, buf_len); - break; - } - return JS_MKPTR(JS_TAG_STRING, str); -} - -JSValue JS_NewTwoByteString(JSContext *ctx, const uint16_t *buf, size_t len) -{ - JSString *str; - - if (!len) - return js_empty_string(ctx->rt); - str = js_alloc_string(ctx, len, 1); - if (!str) - return JS_EXCEPTION; - memcpy(str16(str), buf, len * sizeof(*buf)); - return JS_MKPTR(JS_TAG_STRING, str); -} - -static JSValue JS_ConcatString3(JSContext *ctx, const char *str1, - JSValue str2, const char *str3) -{ - StringBuffer b_s, *b = &b_s; - int len1, len3; - JSString *p; - - if (unlikely(JS_VALUE_GET_TAG(str2) != JS_TAG_STRING)) { - str2 = JS_ToStringFree(ctx, str2); - if (JS_IsException(str2)) - goto fail; - } - p = JS_VALUE_GET_STRING(str2); - len1 = strlen(str1); - len3 = strlen(str3); - - if (string_buffer_init2(ctx, b, len1 + p->len + len3, p->is_wide_char)) - goto fail; - - string_buffer_write8(b, (const uint8_t *)str1, len1); - string_buffer_concat(b, p, 0, p->len); - string_buffer_write8(b, (const uint8_t *)str3, len3); - - JS_FreeValue(ctx, str2); - return string_buffer_end(b); - - fail: - JS_FreeValue(ctx, str2); - return JS_EXCEPTION; -} - -/* `str` may be pure ASCII or UTF-8 encoded */ -JSValue JS_NewAtomString(JSContext *ctx, const char *str) -{ - JSAtom atom = JS_NewAtom(ctx, str); - if (atom == JS_ATOM_NULL) - return JS_EXCEPTION; - JSValue val = JS_AtomToString(ctx, atom); - JS_FreeAtom(ctx, atom); - return val; -} - -/* return (NULL, 0) if exception. */ -/* return pointer into a JSString with a live ref_count */ -/* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 sequences */ -const char *JS_ToCStringLen2(JSContext *ctx, size_t *plen, JSValueConst val1, - bool cesu8) -{ - JSValue val; - JSString *str, *str_new; - int pos, len, c, c1; - JSObject *p; - uint8_t *q; - - if (JS_VALUE_GET_TAG(val1) == JS_TAG_STRING) { - val = js_dup(val1); - goto go; - } - - val = JS_ToString(ctx, val1); - if (!JS_IsException(val)) - goto go; - - // Stringification can fail when there is an exception pending, - // e.g. a stack overflow InternalError. Special-case exception - // objects to make debugging easier, look up the .message property - // and stringify that. - if (JS_VALUE_GET_TAG(val1) != JS_TAG_OBJECT) - goto fail; - - p = JS_VALUE_GET_OBJ(val1); - if (p->class_id != JS_CLASS_ERROR) - goto fail; - - val = JS_GetProperty(ctx, val1, JS_ATOM_message); - if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) { - JS_FreeValue(ctx, val); - goto fail; - } - -go: - str = JS_VALUE_GET_STRING(val); - len = str->len; - if (!str->is_wide_char) { - const uint8_t *src = str8(str); - int count; - - /* count the number of non-ASCII characters */ - /* Scanning the whole string is required for ASCII strings, - and computing the number of non-ASCII bytes is less expensive - than testing each byte, hence this method is faster for ASCII - strings, which is the most common case. - */ - count = 0; - for (pos = 0; pos < len; pos++) { - count += src[pos] >> 7; - } - if (count == 0 && str->kind == JS_STRING_KIND_NORMAL) { - if (plen) - *plen = len; - return (const char *)src; - } - str_new = js_alloc_string(ctx, len + count, 0); - if (!str_new) - goto fail; - q = str8(str_new); - for (pos = 0; pos < len; pos++) { - c = src[pos]; - if (c < 0x80) { - *q++ = c; - } else { - *q++ = (c >> 6) | 0xc0; - *q++ = (c & 0x3f) | 0x80; - } - } - } else { - const uint16_t *src = str16(str); - /* Allocate 3 bytes per 16 bit code point. Surrogate pairs may - produce 4 bytes but use 2 code points. - */ - str_new = js_alloc_string(ctx, len * 3, 0); - if (!str_new) - goto fail; - q = str8(str_new); - pos = 0; - while (pos < len) { - c = src[pos++]; - if (c < 0x80) { - *q++ = c; - } else { - if (is_hi_surrogate(c)) { - if (pos < len && !cesu8) { - c1 = src[pos]; - if (is_lo_surrogate(c1)) { - pos++; - c = from_surrogate(c, c1); - } else { - /* Keep unmatched surrogate code points */ - /* c = 0xfffd; */ /* error */ - } - } else { - /* Keep unmatched surrogate code points */ - /* c = 0xfffd; */ /* error */ - } - } - q += utf8_encode(q, c); - } - } - } - - *q = '\0'; - str_new->len = q - str8(str_new); - JS_FreeValue(ctx, val); - if (plen) - *plen = str_new->len; - return (const char *)str8(str_new); - fail: - if (plen) - *plen = 0; - return NULL; -} - -void JS_FreeCString(JSContext *ctx, const char *ptr) -{ - if (!ptr) - return; - /* purposely removing constness */ - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, (JSString *)ptr - 1)); -} - -static int memcmp16_8(const uint16_t *src1, const uint8_t *src2, int len) -{ - int c, i; - for(i = 0; i < len; i++) { - c = src1[i] - src2[i]; - if (c != 0) - return c; - } - return 0; -} - -static int memcmp16(const uint16_t *src1, const uint16_t *src2, int len) -{ - int c, i; - for(i = 0; i < len; i++) { - c = src1[i] - src2[i]; - if (c != 0) - return c; - } - return 0; -} - -static int js_string_memcmp(JSString *p1, JSString *p2, int len) -{ - int res; - - if (likely(!p1->is_wide_char)) { - if (likely(!p2->is_wide_char)) - res = memcmp(str8(p1), str8(p2), len); - else - res = -memcmp16_8(str16(p2), str8(p1), len); - } else { - if (!p2->is_wide_char) - res = memcmp16_8(str16(p1), str8(p2), len); - else - res = memcmp16(str16(p1), str16(p2), len); - } - return res; -} - -static bool js_string_eq(JSString *p1, JSString *p2) { - if (p1->len != p2->len) - return false; - return js_string_memcmp(p1, p2, p1->len) == 0; -} - -/* return < 0, 0 or > 0 */ -static int js_string_compare(JSString *p1, JSString *p2) -{ - int res, len; - len = min_int(p1->len, p2->len); - res = js_string_memcmp(p1, p2, len); - if (res == 0) - res = compare_u32(p1->len, p2->len); - return res; -} - -static void copy_str16(uint16_t *dst, JSString *p, int offset, int len) -{ - if (p->is_wide_char) { - memcpy(dst, str16(p) + offset, len * 2); - } else { - const uint8_t *src1 = str8(p) + offset; - int i; - - for(i = 0; i < len; i++) - dst[i] = src1[i]; - } -} - -static JSValue JS_ConcatString1(JSContext *ctx, JSString *p1, JSString *p2) -{ - JSString *p; - uint32_t len; - int is_wide_char; - - len = p1->len + p2->len; - if (len > JS_STRING_LEN_MAX) - return JS_ThrowRangeError(ctx, "invalid string length"); - is_wide_char = p1->is_wide_char | p2->is_wide_char; - p = js_alloc_string(ctx, len, is_wide_char); - if (!p) - return JS_EXCEPTION; - if (!is_wide_char) { - memcpy(str8(p), str8(p1), p1->len); - memcpy(str8(p) + p1->len, str8(p2), p2->len); - str8(p)[len] = '\0'; - } else { - copy_str16(str16(p), p1, 0, p1->len); - copy_str16(str16(p) + p1->len, p2, 0, p2->len); - } - return JS_MKPTR(JS_TAG_STRING, p); -} - -/* op1 and op2 are converted to strings. For convience, op1 or op2 = - JS_EXCEPTION are accepted and return JS_EXCEPTION. */ -static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2) -{ - JSValue ret; - JSString *p1, *p2; - - if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_STRING)) { - op1 = JS_ToStringFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - return JS_EXCEPTION; - } - } - if (unlikely(JS_VALUE_GET_TAG(op2) != JS_TAG_STRING)) { - op2 = JS_ToStringFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - return JS_EXCEPTION; - } - } - p1 = JS_VALUE_GET_STRING(op1); - p2 = JS_VALUE_GET_STRING(op2); - - /* XXX: could also check if p1 is empty */ - if (p2->len == 0) { - goto ret_op1; - } - if (p1->header.ref_count == 1 && p1->is_wide_char == p2->is_wide_char - && js_malloc_usable_size(ctx, p1) >= sizeof(*p1) + ((p1->len + p2->len) << p2->is_wide_char) + 1 - p1->is_wide_char) { - /* Concatenate in place in available space at the end of p1 */ - if (p1->is_wide_char) { - memcpy(str16(p1) + p1->len, str16(p2), p2->len << 1); - p1->len += p2->len; - } else { - memcpy(str8(p1) + p1->len, str8(p2), p2->len); - p1->len += p2->len; - str8(p1)[p1->len] = '\0'; - } - ret_op1: - JS_FreeValue(ctx, op2); - return op1; - } - ret = JS_ConcatString1(ctx, p1, p2); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - return ret; -} - -/* Shape support */ - -static inline size_t get_shape_size(size_t hash_size, size_t prop_size) -{ - return hash_size * sizeof(uint32_t) + sizeof(JSShape) + - prop_size * sizeof(JSShapeProperty); -} - -static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size) -{ - return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size); -} - -static inline uint32_t *prop_hash_end(JSShape *sh) -{ - return (uint32_t *)sh; -} - -static inline void *get_alloc_from_shape(JSShape *sh) -{ - return prop_hash_end(sh) - ((intptr_t)sh->prop_hash_mask + 1); -} - -static inline JSShapeProperty *get_shape_prop(JSShape *sh) -{ - return sh->prop; -} - -static int init_shape_hash(JSRuntime *rt) -{ - rt->shape_hash_bits = 6; /* 64 shapes */ - rt->shape_hash_size = 1 << rt->shape_hash_bits; - rt->shape_hash_count = 0; - rt->shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * - rt->shape_hash_size); - if (!rt->shape_hash) - return -1; - return 0; -} - -/* same magic hash multiplier as the Linux kernel */ -static uint32_t shape_hash(uint32_t h, uint32_t val) -{ - return (h + val) * 0x9e370001; -} - -/* truncate the shape hash to 'hash_bits' bits */ -static uint32_t get_shape_hash(uint32_t h, int hash_bits) -{ - return h >> (32 - hash_bits); -} - -static uint32_t shape_initial_hash(JSObject *proto) -{ - uint32_t h; - h = shape_hash(1, (uintptr_t)proto); - if (sizeof(proto) > 4) - h = shape_hash(h, (uint64_t)(uintptr_t)proto >> 32); - return h; -} - -static int resize_shape_hash(JSRuntime *rt, int new_shape_hash_bits) -{ - int new_shape_hash_size, i; - uint32_t h; - JSShape **new_shape_hash, *sh, *sh_next; - - new_shape_hash_size = 1 << new_shape_hash_bits; - new_shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * - new_shape_hash_size); - if (!new_shape_hash) - return -1; - for(i = 0; i < rt->shape_hash_size; i++) { - for(sh = rt->shape_hash[i]; sh != NULL; sh = sh_next) { - sh_next = sh->shape_hash_next; - h = get_shape_hash(sh->hash, new_shape_hash_bits); - sh->shape_hash_next = new_shape_hash[h]; - new_shape_hash[h] = sh; - } - } - js_free_rt(rt, rt->shape_hash); - rt->shape_hash_bits = new_shape_hash_bits; - rt->shape_hash_size = new_shape_hash_size; - rt->shape_hash = new_shape_hash; - return 0; -} - -static void js_shape_hash_link(JSRuntime *rt, JSShape *sh) -{ - uint32_t h; - h = get_shape_hash(sh->hash, rt->shape_hash_bits); - sh->shape_hash_next = rt->shape_hash[h]; - rt->shape_hash[h] = sh; - rt->shape_hash_count++; -} - -static void js_shape_hash_unlink(JSRuntime *rt, JSShape *sh) -{ - uint32_t h; - JSShape **psh; - - h = get_shape_hash(sh->hash, rt->shape_hash_bits); - psh = &rt->shape_hash[h]; - while (*psh != sh) - psh = &(*psh)->shape_hash_next; - *psh = sh->shape_hash_next; - rt->shape_hash_count--; -} - -/* create a new empty shape with prototype 'proto' */ -static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto, - int hash_size, int prop_size) -{ - JSRuntime *rt = ctx->rt; - void *sh_alloc; - JSShape *sh; - - /* resize the shape hash table if necessary */ - if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) { - resize_shape_hash(rt, rt->shape_hash_bits + 1); - } - - sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size)); - if (!sh_alloc) - return NULL; - sh = get_shape_from_alloc(sh_alloc, hash_size); - sh->header.ref_count = 1; - add_gc_object(rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); - if (proto) - js_dup(JS_MKPTR(JS_TAG_OBJECT, proto)); - sh->proto = proto; - memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) * - hash_size); - sh->prop_hash_mask = hash_size - 1; - sh->prop_size = prop_size; - sh->prop_count = 0; - sh->deleted_prop_count = 0; - - /* insert in the hash table */ - sh->hash = shape_initial_hash(proto); - sh->is_hashed = true; - sh->has_small_array_index = false; - js_shape_hash_link(ctx->rt, sh); - return sh; -} - -static JSShape *js_new_shape(JSContext *ctx, JSObject *proto) -{ - return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE, - JS_PROP_INITIAL_SIZE); -} - -/* The shape is cloned. The new shape is not inserted in the shape - hash table */ -static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1) -{ - JSShape *sh; - void *sh_alloc, *sh_alloc1; - size_t size; - JSShapeProperty *pr; - uint32_t i, hash_size; - - hash_size = sh1->prop_hash_mask + 1; - size = get_shape_size(hash_size, sh1->prop_size); - sh_alloc = js_malloc(ctx, size); - if (!sh_alloc) - return NULL; - sh_alloc1 = get_alloc_from_shape(sh1); - memcpy(sh_alloc, sh_alloc1, size); - sh = get_shape_from_alloc(sh_alloc, hash_size); - sh->header.ref_count = 1; - add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); - sh->is_hashed = false; - if (sh->proto) { - js_dup(JS_MKPTR(JS_TAG_OBJECT, sh->proto)); - } - for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { - JS_DupAtom(ctx, pr->atom); - } - return sh; -} - -static JSShape *js_dup_shape(JSShape *sh) -{ - sh->header.ref_count++; - return sh; -} - -static void js_free_shape0(JSRuntime *rt, JSShape *sh) -{ - uint32_t i; - JSShapeProperty *pr; - - assert(sh->header.ref_count == 0); - if (sh->is_hashed) - js_shape_hash_unlink(rt, sh); - if (sh->proto != NULL) { - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); - } - pr = get_shape_prop(sh); - for(i = 0; i < sh->prop_count; i++) { - JS_FreeAtomRT(rt, pr->atom); - pr++; - } - remove_gc_object(&sh->header); - js_free_rt(rt, get_alloc_from_shape(sh)); -} - -static void js_free_shape(JSRuntime *rt, JSShape *sh) -{ - if (unlikely(--sh->header.ref_count <= 0)) { - js_free_shape0(rt, sh); - } -} - -static void js_free_shape_null(JSRuntime *rt, JSShape *sh) -{ - if (sh) - js_free_shape(rt, sh); -} - -/* make space to hold at least 'count' properties */ -static no_inline int resize_properties(JSContext *ctx, JSShape **psh, - JSObject *p, uint32_t count) -{ - JSShape *sh; - uint32_t new_size, new_hash_size, new_hash_mask, i; - JSShapeProperty *pr; - void *sh_alloc; - intptr_t h; - - sh = *psh; - new_size = max_int(count, sh->prop_size * 3 / 2); - /* Reallocate prop array first to avoid crash or size inconsistency - in case of memory allocation failure */ - if (p) { - JSProperty *new_prop; - new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); - if (unlikely(!new_prop)) - return -1; - p->prop = new_prop; - } - new_hash_size = sh->prop_hash_mask + 1; - while (new_hash_size < new_size) - new_hash_size = 2 * new_hash_size; - if (new_hash_size != (sh->prop_hash_mask + 1)) { - JSShape *old_sh; - /* resize the hash table and the properties */ - old_sh = sh; - sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); - if (!sh_alloc) - return -1; - sh = get_shape_from_alloc(sh_alloc, new_hash_size); - list_del(&old_sh->header.link); - /* copy all the fields and the properties */ - memcpy(sh, old_sh, - sizeof(JSShape) + sizeof(sh->prop[0]) * old_sh->prop_count); - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); - new_hash_mask = new_hash_size - 1; - sh->prop_hash_mask = new_hash_mask; - memset(prop_hash_end(sh) - new_hash_size, 0, - sizeof(prop_hash_end(sh)[0]) * new_hash_size); - for(i = 0, pr = sh->prop; i < sh->prop_count; i++, pr++) { - if (pr->atom != JS_ATOM_NULL) { - h = ((uintptr_t)pr->atom & new_hash_mask); - pr->hash_next = prop_hash_end(sh)[-h - 1]; - prop_hash_end(sh)[-h - 1] = i + 1; - } - } - js_free(ctx, get_alloc_from_shape(old_sh)); - } else { - /* only resize the properties */ - list_del(&sh->header.link); - sh_alloc = js_realloc(ctx, get_alloc_from_shape(sh), - get_shape_size(new_hash_size, new_size)); - if (unlikely(!sh_alloc)) { - /* insert again in the GC list */ - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); - return -1; - } - sh = get_shape_from_alloc(sh_alloc, new_hash_size); - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); - } - *psh = sh; - sh->prop_size = new_size; - return 0; -} - -/* remove the deleted properties. */ -static int compact_properties(JSContext *ctx, JSObject *p) -{ - JSShape *sh, *old_sh; - void *sh_alloc; - intptr_t h; - uint32_t new_hash_size, i, j, new_hash_mask, new_size; - JSShapeProperty *old_pr, *pr; - JSProperty *prop, *new_prop; - - sh = p->shape; - assert(!sh->is_hashed); - - new_size = max_int(JS_PROP_INITIAL_SIZE, - sh->prop_count - sh->deleted_prop_count); - assert(new_size <= sh->prop_size); - - new_hash_size = sh->prop_hash_mask + 1; - while ((new_hash_size / 2) >= new_size) - new_hash_size = new_hash_size / 2; - new_hash_mask = new_hash_size - 1; - - /* resize the hash table and the properties */ - old_sh = sh; - sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); - if (!sh_alloc) - return -1; - sh = get_shape_from_alloc(sh_alloc, new_hash_size); - list_del(&old_sh->header.link); - memcpy(sh, old_sh, sizeof(JSShape)); - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); - - memset(prop_hash_end(sh) - new_hash_size, 0, - sizeof(prop_hash_end(sh)[0]) * new_hash_size); - - j = 0; - old_pr = old_sh->prop; - pr = sh->prop; - prop = p->prop; - for(i = 0; i < sh->prop_count; i++) { - if (old_pr->atom != JS_ATOM_NULL) { - pr->atom = old_pr->atom; - pr->flags = old_pr->flags; - h = ((uintptr_t)old_pr->atom & new_hash_mask); - pr->hash_next = prop_hash_end(sh)[-h - 1]; - prop_hash_end(sh)[-h - 1] = j + 1; - prop[j] = prop[i]; - j++; - pr++; - } - old_pr++; - } - assert(j == (sh->prop_count - sh->deleted_prop_count)); - sh->prop_hash_mask = new_hash_mask; - sh->prop_size = new_size; - sh->deleted_prop_count = 0; - sh->prop_count = j; - - p->shape = sh; - js_free(ctx, get_alloc_from_shape(old_sh)); - - /* reduce the size of the object properties */ - new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); - if (new_prop) - p->prop = new_prop; - return 0; -} - -static int add_shape_property(JSContext *ctx, JSShape **psh, - JSObject *p, JSAtom atom, int prop_flags) -{ - JSRuntime *rt = ctx->rt; - JSShape *sh = *psh; - JSShapeProperty *pr, *prop; - uint32_t hash_mask, new_shape_hash = 0; - intptr_t h; - - /* update the shape hash */ - if (sh->is_hashed) { - js_shape_hash_unlink(rt, sh); - new_shape_hash = shape_hash(shape_hash(sh->hash, atom), prop_flags); - } - - if (unlikely(sh->prop_count >= sh->prop_size)) { - if (resize_properties(ctx, psh, p, sh->prop_count + 1)) { - /* in case of error, reinsert in the hash table. - sh is still valid if resize_properties() failed */ - if (sh->is_hashed) - js_shape_hash_link(rt, sh); - return -1; - } - sh = *psh; - } - if (sh->is_hashed) { - sh->hash = new_shape_hash; - js_shape_hash_link(rt, sh); - } - /* Initialize the new shape property. - The object property at p->prop[sh->prop_count] is uninitialized */ - prop = get_shape_prop(sh); - pr = &prop[sh->prop_count++]; - pr->atom = JS_DupAtom(ctx, atom); - pr->flags = prop_flags; - sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom); - /* add in hash table */ - hash_mask = sh->prop_hash_mask; - h = atom & hash_mask; - pr->hash_next = prop_hash_end(sh)[-h - 1]; - prop_hash_end(sh)[-h - 1] = sh->prop_count; - return 0; -} - -/* find a hashed empty shape matching the prototype. Return NULL if - not found */ -static JSShape *find_hashed_shape_proto(JSRuntime *rt, JSObject *proto) -{ - JSShape *sh1; - uint32_t h, h1; - - h = shape_initial_hash(proto); - h1 = get_shape_hash(h, rt->shape_hash_bits); - for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { - if (sh1->hash == h && - sh1->proto == proto && - sh1->prop_count == 0) { - return sh1; - } - } - return NULL; -} - -/* find a hashed shape matching sh + (prop, prop_flags). Return NULL if - not found */ -static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh, - JSAtom atom, int prop_flags) -{ - JSShape *sh1; - uint32_t h, h1, i, n; - - h = sh->hash; - h = shape_hash(h, atom); - h = shape_hash(h, prop_flags); - h1 = get_shape_hash(h, rt->shape_hash_bits); - for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { - /* we test the hash first so that the rest is done only if the - shapes really match */ - if (sh1->hash == h && - sh1->proto == sh->proto && - sh1->prop_count == ((n = sh->prop_count) + 1)) { - for(i = 0; i < n; i++) { - if (unlikely(sh1->prop[i].atom != sh->prop[i].atom) || - unlikely(sh1->prop[i].flags != sh->prop[i].flags)) - goto next; - } - if (unlikely(sh1->prop[n].atom != atom) || - unlikely(sh1->prop[n].flags != prop_flags)) - goto next; - return sh1; - } - next: ; - } - return NULL; -} - -static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh) -{ - char atom_buf[ATOM_GET_STR_BUF_SIZE]; - int j; - - /* XXX: should output readable class prototype */ - printf("%5d %3d%c %14p %5d %5d", i, - sh->header.ref_count, " *"[sh->is_hashed], - (void *)sh->proto, sh->prop_size, sh->prop_count); - for(j = 0; j < sh->prop_count; j++) { - printf(" %s", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), - sh->prop[j].atom)); - } - printf("\n"); -} - -static __maybe_unused void JS_DumpShapes(JSRuntime *rt) -{ - int i; - JSShape *sh; - struct list_head *el; - JSObject *p; - JSGCObjectHeader *gp; - - printf("JSShapes: {\n"); - printf("%5s %4s %14s %5s %5s %s\n", "SLOT", "REFS", "PROTO", "SIZE", "COUNT", "PROPS"); - for(i = 0; i < rt->shape_hash_size; i++) { - for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { - JS_DumpShape(rt, i, sh); - assert(sh->is_hashed); - } - } - /* dump non-hashed shapes */ - list_for_each(el, &rt->gc_obj_list) { - gp = list_entry(el, JSGCObjectHeader, link); - if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { - p = (JSObject *)gp; - if (!p->shape->is_hashed) { - JS_DumpShape(rt, -1, p->shape); - } - } - } - printf("}\n"); -} - -static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID class_id) -{ - JSObject *p; - - js_trigger_gc(ctx->rt, sizeof(JSObject)); - p = js_malloc(ctx, sizeof(JSObject)); - if (unlikely(!p)) - goto fail; - p->class_id = class_id; - p->extensible = true; - p->free_mark = 0; - p->is_exotic = 0; - p->fast_array = 0; - p->is_constructor = 0; - p->is_uncatchable_error = 0; - p->tmp_mark = 0; - p->is_HTMLDDA = 0; - p->first_weak_ref = NULL; - p->u.opaque = NULL; - p->shape = sh; - p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size); - if (unlikely(!p->prop)) { - js_free(ctx, p); - fail: - js_free_shape(ctx->rt, sh); - return JS_EXCEPTION; - } - - switch(class_id) { - case JS_CLASS_OBJECT: - break; - case JS_CLASS_ARRAY: - { - JSProperty *pr; - p->is_exotic = 1; - p->fast_array = 1; - p->u.array.u.values = NULL; - p->u.array.count = 0; - p->u.array.u1.size = 0; - /* the length property is always the first one */ - if (likely(sh == ctx->array_shape)) { - pr = &p->prop[0]; - } else { - /* only used for the first array */ - /* cannot fail */ - pr = add_property(ctx, p, JS_ATOM_length, - JS_PROP_WRITABLE | JS_PROP_LENGTH); - } - pr->u.value = js_int32(0); - } - break; - case JS_CLASS_C_FUNCTION: - p->prop[0].u.value = JS_UNDEFINED; - break; - case JS_CLASS_ARGUMENTS: - case JS_CLASS_UINT8C_ARRAY: - case JS_CLASS_INT8_ARRAY: - case JS_CLASS_UINT8_ARRAY: - case JS_CLASS_INT16_ARRAY: - case JS_CLASS_UINT16_ARRAY: - case JS_CLASS_INT32_ARRAY: - case JS_CLASS_UINT32_ARRAY: - case JS_CLASS_BIG_INT64_ARRAY: - case JS_CLASS_BIG_UINT64_ARRAY: - case JS_CLASS_FLOAT16_ARRAY: - case JS_CLASS_FLOAT32_ARRAY: - case JS_CLASS_FLOAT64_ARRAY: - p->is_exotic = 1; - p->fast_array = 1; - p->u.array.u.ptr = NULL; - p->u.array.count = 0; - break; - case JS_CLASS_DATAVIEW: - p->u.array.u.ptr = NULL; - p->u.array.count = 0; - break; - case JS_CLASS_NUMBER: - case JS_CLASS_STRING: - case JS_CLASS_BOOLEAN: - case JS_CLASS_SYMBOL: - case JS_CLASS_DATE: - case JS_CLASS_BIG_INT: - p->u.object_data = JS_UNDEFINED; - goto set_exotic; - case JS_CLASS_REGEXP: - p->u.regexp.pattern = NULL; - p->u.regexp.bytecode = NULL; - goto set_exotic; - default: - set_exotic: - if (ctx->rt->class_array[class_id].exotic) { - p->is_exotic = 1; - } - break; - } - p->header.ref_count = 1; - add_gc_object(ctx->rt, &p->header, JS_GC_OBJ_TYPE_JS_OBJECT); - return JS_MKPTR(JS_TAG_OBJECT, p); -} - -static JSObject *get_proto_obj(JSValueConst proto_val) -{ - if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) - return NULL; - else - return JS_VALUE_GET_OBJ(proto_val); -} - -/* WARNING: proto must be an object or JS_NULL */ -JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValueConst proto_val, - JSClassID class_id) -{ - JSShape *sh; - JSObject *proto; - - proto = get_proto_obj(proto_val); - sh = find_hashed_shape_proto(ctx->rt, proto); - if (likely(sh)) { - sh = js_dup_shape(sh); - } else { - sh = js_new_shape(ctx, proto); - if (!sh) - return JS_EXCEPTION; - } - return JS_NewObjectFromShape(ctx, sh, class_id); -} - -static int JS_SetObjectData(JSContext *ctx, JSValueConst obj, JSValue val) -{ - JSObject *p; - - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - p = JS_VALUE_GET_OBJ(obj); - switch(p->class_id) { - case JS_CLASS_NUMBER: - case JS_CLASS_STRING: - case JS_CLASS_BOOLEAN: - case JS_CLASS_SYMBOL: - case JS_CLASS_DATE: - case JS_CLASS_BIG_INT: - JS_FreeValue(ctx, p->u.object_data); - p->u.object_data = val; - return 0; - } - } - JS_FreeValue(ctx, val); - if (!JS_IsException(obj)) - JS_ThrowTypeError(ctx, "invalid object type"); - return -1; -} - -JSValue JS_NewObjectClass(JSContext *ctx, JSClassID class_id) -{ - return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id); -} - -JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto) -{ - return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT); -} - -JSValue JS_NewObjectFrom(JSContext *ctx, int count, const JSAtom *props, - const JSValue *values) -{ - JSShapeProperty *pr; - uint32_t *hash; - JSRuntime *rt; - JSObject *p; - JSShape *sh; - JSValue obj; - JSAtom atom; - intptr_t h; - int i; - - rt = ctx->rt; - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) - return JS_EXCEPTION; - if (count > 0) { - p = JS_VALUE_GET_OBJ(obj); - sh = p->shape; - assert(sh->is_hashed); - assert(sh->header.ref_count == 1); - js_shape_hash_unlink(rt, sh); - if (resize_properties(ctx, &sh, p, count)) { - js_shape_hash_link(rt, sh); - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - p->shape = sh; - for (i = 0; i < count; i++) { - atom = props[i]; - pr = &sh->prop[i]; - sh->hash = shape_hash(shape_hash(sh->hash, atom), JS_PROP_C_W_E); - sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom); - h = atom & sh->prop_hash_mask; - hash = &prop_hash_end(sh)[-h - 1]; - pr->hash_next = *hash; - *hash = i + 1; - pr->atom = JS_DupAtom(ctx, atom); - pr->flags = JS_PROP_C_W_E; - p->prop[i].u.value = values[i]; - } - js_shape_hash_link(rt, sh); - sh->prop_count = count; - } - return obj; -} - -JSValue JS_NewObjectFromStr(JSContext *ctx, int count, const char **props, - const JSValue *values) -{ - JSAtom atoms_s[16], *atoms = atoms_s; - JSValue ret; - int i; - - i = 0; - ret = JS_EXCEPTION; - if (count < 1) - goto out; - if (count > (int)countof(atoms_s)) { - atoms = js_malloc(ctx, count * sizeof(*atoms)); - if (!atoms) - return JS_EXCEPTION; - } - for (i = 0; i < count; i++) { - atoms[i] = JS_NewAtom(ctx, props[i]); - if (atoms[i] == JS_ATOM_NULL) - goto out; - } - ret = JS_NewObjectFrom(ctx, count, atoms, values); -out: - while (i-- > 0) - JS_FreeAtom(ctx, atoms[i]); - if (atoms != atoms_s) - js_free(ctx, atoms); - return ret; -} - -JSValue JS_NewArray(JSContext *ctx) -{ - return JS_NewObjectFromShape(ctx, js_dup_shape(ctx->array_shape), - JS_CLASS_ARRAY); -} - -// note: takes ownership of |values|, unlike js_create_array -JSValue JS_NewArrayFrom(JSContext *ctx, int count, const JSValue *values) -{ - JSObject *p; - JSValue obj; - int i; - - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - goto exception; - if (count > 0) { - p = JS_VALUE_GET_OBJ(obj); - if (expand_fast_array(ctx, p, count)) { - JS_FreeValue(ctx, obj); - goto exception; - } - p->u.array.count = count; - p->prop[0].u.value = js_int32(count); - memcpy(p->u.array.u.values, values, count * sizeof(*values)); - } - return obj; -exception: - for (i = 0; i < count; i++) - JS_FreeValue(ctx, values[i]); - return JS_EXCEPTION; -} - -JSValue JS_NewObject(JSContext *ctx) -{ - /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */ - return JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT); -} - -static void js_function_set_properties(JSContext *ctx, JSValue func_obj, - JSAtom name, int len) -{ - /* ES6 feature non compatible with ES5.1: length is configurable */ - JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, js_int32(len), - JS_PROP_CONFIGURABLE); - JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, - JS_AtomToString(ctx, name), JS_PROP_CONFIGURABLE); -} - -static bool js_class_has_bytecode(JSClassID class_id) -{ - return (class_id == JS_CLASS_BYTECODE_FUNCTION || - class_id == JS_CLASS_GENERATOR_FUNCTION || - class_id == JS_CLASS_ASYNC_FUNCTION || - class_id == JS_CLASS_ASYNC_GENERATOR_FUNCTION); -} - -/* return NULL without exception if not a function or no bytecode */ -static JSFunctionBytecode *JS_GetFunctionBytecode(JSValueConst val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return NULL; - p = JS_VALUE_GET_OBJ(val); - if (!js_class_has_bytecode(p->class_id)) - return NULL; - return p->u.func.function_bytecode; -} - -static void js_method_set_home_object(JSContext *ctx, JSValue func_obj, - JSValue home_obj) -{ - JSObject *p, *p1; - JSFunctionBytecode *b; - - if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) - return; - p = JS_VALUE_GET_OBJ(func_obj); - if (!js_class_has_bytecode(p->class_id)) - return; - b = p->u.func.function_bytecode; - if (b->need_home_object) { - p1 = p->u.func.home_object; - if (p1) { - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); - } - if (JS_VALUE_GET_TAG(home_obj) == JS_TAG_OBJECT) - p1 = JS_VALUE_GET_OBJ(js_dup(home_obj)); - else - p1 = NULL; - p->u.func.home_object = p1; - } -} - -static JSValue js_get_function_name(JSContext *ctx, JSAtom name) -{ - JSValue name_str; - - name_str = JS_AtomToString(ctx, name); - if (JS_AtomSymbolHasDescription(ctx, name)) { - name_str = JS_ConcatString3(ctx, "[", name_str, "]"); - } - return name_str; -} - -/* Modify the name of a method according to the atom and - 'flags'. 'flags' is a bitmask of JS_PROP_HAS_GET and - JS_PROP_HAS_SET. Also set the home object of the method. - Return < 0 if exception. */ -static int js_method_set_properties(JSContext *ctx, JSValue func_obj, - JSAtom name, int flags, JSValue home_obj) -{ - JSValue name_str; - - name_str = js_get_function_name(ctx, name); - if (flags & JS_PROP_HAS_GET) { - name_str = JS_ConcatString3(ctx, "get ", name_str, ""); - } else if (flags & JS_PROP_HAS_SET) { - name_str = JS_ConcatString3(ctx, "set ", name_str, ""); - } - if (JS_IsException(name_str)) - return -1; - if (JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name_str, - JS_PROP_CONFIGURABLE) < 0) - return -1; - js_method_set_home_object(ctx, func_obj, home_obj); - return 0; -} - -/* Note: at least 'length' arguments will be readable in 'argv' */ -/* `name` may be NULL, pure ASCII or UTF-8 encoded */ -JSValue JS_NewCFunction3(JSContext *ctx, JSCFunction *func, - const char *name, - int length, JSCFunctionEnum cproto, int magic, - JSValueConst proto_val) -{ - JSValue func_obj; - JSObject *p; - JSAtom name_atom; - - func_obj = JS_NewObjectProtoClass(ctx, proto_val, JS_CLASS_C_FUNCTION); - if (JS_IsException(func_obj)) - return func_obj; - p = JS_VALUE_GET_OBJ(func_obj); - p->u.cfunc.realm = JS_DupContext(ctx); - p->u.cfunc.c_function.generic = func; - p->u.cfunc.length = length; - p->u.cfunc.cproto = cproto; - p->u.cfunc.magic = magic; - p->is_constructor = (cproto == JS_CFUNC_constructor || - cproto == JS_CFUNC_constructor_magic || - cproto == JS_CFUNC_constructor_or_func || - cproto == JS_CFUNC_constructor_or_func_magic); - name_atom = JS_ATOM_empty_string; - if (name && *name) { - name_atom = JS_NewAtom(ctx, name); - if (name_atom == JS_ATOM_NULL) { - JS_FreeValue(ctx, func_obj); - return JS_EXCEPTION; - } - } - js_function_set_properties(ctx, func_obj, name_atom, length); - JS_FreeAtom(ctx, name_atom); - return func_obj; -} - -/* Note: at least 'length' arguments will be readable in 'argv' */ -JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func, - const char *name, - int length, JSCFunctionEnum cproto, int magic) -{ - return JS_NewCFunction3(ctx, func, name, length, cproto, magic, - ctx->function_proto); -} - -typedef struct JSCFunctionDataRecord { - JSCFunctionData *func; - uint8_t length; - uint8_t data_len; - uint16_t magic; - JSValue data[]; -} JSCFunctionDataRecord; - -static void js_c_function_data_finalizer(JSRuntime *rt, JSValueConst val) -{ - JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); - int i; - - if (s) { - for(i = 0; i < s->data_len; i++) { - JS_FreeValueRT(rt, s->data[i]); - } - js_free_rt(rt, s); - } -} - -static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func) -{ - JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); - int i; - - if (s) { - for(i = 0; i < s->data_len; i++) { - JS_MarkValue(rt, s->data[i], mark_func); - } - } -} - -static JSValue js_call_c_function_data(JSContext *ctx, JSValueConst func_obj, - JSValueConst this_val, - int argc, JSValueConst *argv, int flags) -{ - JSRuntime *rt = ctx->rt; - JSStackFrame sf_s, *sf = &sf_s, *prev_sf; - JSCFunctionDataRecord *s; - JSValueConst *arg_buf; - JSValue ret; - size_t stack_size; - int arg_count; - int i; - - s = JS_GetOpaque(func_obj, JS_CLASS_C_FUNCTION_DATA); - if (!s) - return JS_EXCEPTION; // can't really happen - arg_buf = argv; - arg_count = s->length; - if (unlikely(argc < arg_count)) { - stack_size = arg_count * sizeof(arg_buf[0]); - if (js_check_stack_overflow(rt, stack_size)) - return JS_ThrowStackOverflow(ctx); - arg_buf = alloca(stack_size); - for(i = 0; i < argc; i++) - arg_buf[i] = argv[i]; - for(i = argc; i < arg_count; i++) - arg_buf[i] = JS_UNDEFINED; - } - prev_sf = rt->current_stack_frame; - sf->prev_frame = prev_sf; - rt->current_stack_frame = sf; - // TODO(bnoordhuis) switch realms like js_call_c_function does - sf->is_strict_mode = false; - sf->cur_func = unsafe_unconst(func_obj); - sf->arg_count = argc; - ret = s->func(ctx, this_val, argc, arg_buf, s->magic, vc(s->data)); - rt->current_stack_frame = sf->prev_frame; - return ret; -} - -JSValue JS_NewCFunctionData2(JSContext *ctx, JSCFunctionData *func, - const char *name, - int length, int magic, int data_len, - JSValueConst *data) -{ - JSCFunctionDataRecord *s; - JSAtom name_atom; - JSValue func_obj; - int i; - - func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, - JS_CLASS_C_FUNCTION_DATA); - if (JS_IsException(func_obj)) - return func_obj; - s = js_malloc(ctx, sizeof(*s) + data_len * sizeof(JSValue)); - if (!s) { - JS_FreeValue(ctx, func_obj); - return JS_EXCEPTION; - } - s->func = func; - s->length = length; - s->data_len = data_len; - s->magic = magic; - for(i = 0; i < data_len; i++) - s->data[i] = js_dup(data[i]); - JS_SetOpaqueInternal(func_obj, s); - name_atom = JS_ATOM_empty_string; - if (name && *name) { - name_atom = JS_NewAtom(ctx, name); - if (name_atom == JS_ATOM_NULL) { - JS_FreeValue(ctx, func_obj); - return JS_EXCEPTION; - } - } - js_function_set_properties(ctx, func_obj, name_atom, length); - JS_FreeAtom(ctx, name_atom); - return func_obj; -} - -JSValue JS_NewCFunctionData(JSContext *ctx, JSCFunctionData *func, - int length, int magic, int data_len, - JSValueConst *data) -{ - return JS_NewCFunctionData2(ctx, func, NULL, length, magic, data_len, data); -} - -static JSContext *js_autoinit_get_realm(JSProperty *pr) -{ - return (JSContext *)(pr->u.init.realm_and_id & ~3); -} - -static JSAutoInitIDEnum js_autoinit_get_id(JSProperty *pr) -{ - return pr->u.init.realm_and_id & 3; -} - -static void js_autoinit_free(JSRuntime *rt, JSProperty *pr) -{ - JS_FreeContext(js_autoinit_get_realm(pr)); -} - -static void js_autoinit_mark(JSRuntime *rt, JSProperty *pr, - JS_MarkFunc *mark_func) -{ - mark_func(rt, &js_autoinit_get_realm(pr)->header); -} - -static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags) -{ - if (unlikely(prop_flags & JS_PROP_TMASK)) { - if ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - if (pr->u.getset.getter) - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); - if (pr->u.getset.setter) - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); - } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - free_var_ref(rt, pr->u.var_ref); - } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - js_autoinit_free(rt, pr); - } - } else { - JS_FreeValueRT(rt, pr->u.value); - } -} - -static force_inline JSShapeProperty *find_own_property1(JSObject *p, - JSAtom atom) -{ - JSShape *sh; - JSShapeProperty *pr, *prop; - intptr_t h; - sh = p->shape; - h = (uintptr_t)atom & sh->prop_hash_mask; - h = prop_hash_end(sh)[-h - 1]; - prop = get_shape_prop(sh); - while (h) { - pr = &prop[h - 1]; - if (likely(pr->atom == atom)) { - return pr; - } - h = pr->hash_next; - } - return NULL; -} - -static force_inline JSShapeProperty *find_own_property(JSProperty **ppr, - JSObject *p, - JSAtom atom) -{ - JSShape *sh; - JSShapeProperty *pr, *prop; - intptr_t h; - sh = p->shape; - h = (uintptr_t)atom & sh->prop_hash_mask; - h = prop_hash_end(sh)[-h - 1]; - prop = get_shape_prop(sh); - while (h) { - pr = &prop[h - 1]; - if (likely(pr->atom == atom)) { - *ppr = &p->prop[h - 1]; - /* the compiler should be able to assume that pr != NULL here */ - return pr; - } - h = pr->hash_next; - } - *ppr = NULL; - return NULL; -} - -static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref) -{ - if (var_ref) { - assert(var_ref->header.ref_count > 0); - if (--var_ref->header.ref_count == 0) { - if (var_ref->is_detached) { - JS_FreeValueRT(rt, var_ref->value); - remove_gc_object(&var_ref->header); - } else { - list_del(&var_ref->header.link); /* still on the stack */ - } - js_free_rt(rt, var_ref); - } - } -} - -static void js_array_finalizer(JSRuntime *rt, JSValueConst val) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - int i; - - for(i = 0; i < p->u.array.count; i++) { - JS_FreeValueRT(rt, p->u.array.u.values[i]); - } - js_free_rt(rt, p->u.array.u.values); -} - -static void js_array_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - int i; - - for(i = 0; i < p->u.array.count; i++) { - JS_MarkValue(rt, p->u.array.u.values[i], mark_func); - } -} - -static void js_object_data_finalizer(JSRuntime *rt, JSValueConst val) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JS_FreeValueRT(rt, p->u.object_data); - p->u.object_data = JS_UNDEFINED; -} - -static void js_object_data_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JS_MarkValue(rt, p->u.object_data, mark_func); -} - -static void js_c_function_finalizer(JSRuntime *rt, JSValueConst val) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - - if (p->u.cfunc.realm) - JS_FreeContext(p->u.cfunc.realm); -} - -static void js_c_function_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - - if (p->u.cfunc.realm) - mark_func(rt, &p->u.cfunc.realm->header); -} - -static void js_bytecode_function_finalizer(JSRuntime *rt, JSValueConst val) -{ - JSObject *p1, *p = JS_VALUE_GET_OBJ(val); - JSFunctionBytecode *b; - JSVarRef **var_refs; - int i; - - p1 = p->u.func.home_object; - if (p1) { - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, p1)); - } - b = p->u.func.function_bytecode; - if (b) { - var_refs = p->u.func.var_refs; - if (var_refs) { - for(i = 0; i < b->closure_var_count; i++) - free_var_ref(rt, var_refs[i]); - js_free_rt(rt, var_refs); - } - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b)); - } -} - -static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSVarRef **var_refs = p->u.func.var_refs; - JSFunctionBytecode *b = p->u.func.function_bytecode; - int i; - - if (p->u.func.home_object) { - JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object), - mark_func); - } - if (b) { - if (var_refs) { - for(i = 0; i < b->closure_var_count; i++) { - JSVarRef *var_ref = var_refs[i]; - if (var_ref && var_ref->is_detached) { - mark_func(rt, &var_ref->header); - } - } - } - /* must mark the function bytecode because template objects may be - part of a cycle */ - JS_MarkValue(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b), mark_func); - } -} - -static void js_bound_function_finalizer(JSRuntime *rt, JSValueConst val) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSBoundFunction *bf = p->u.bound_function; - int i; - - JS_FreeValueRT(rt, bf->func_obj); - JS_FreeValueRT(rt, bf->this_val); - for(i = 0; i < bf->argc; i++) { - JS_FreeValueRT(rt, bf->argv[i]); - } - js_free_rt(rt, bf); -} - -static void js_bound_function_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSBoundFunction *bf = p->u.bound_function; - int i; - - JS_MarkValue(rt, bf->func_obj, mark_func); - JS_MarkValue(rt, bf->this_val, mark_func); - for(i = 0; i < bf->argc; i++) - JS_MarkValue(rt, bf->argv[i], mark_func); -} - -static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValueConst val) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSForInIterator *it = p->u.for_in_iterator; - JS_FreeValueRT(rt, it->obj); - js_free_rt(rt, it); -} - -static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSForInIterator *it = p->u.for_in_iterator; - JS_MarkValue(rt, it->obj, mark_func); -} - -static void free_object(JSRuntime *rt, JSObject *p) -{ - int i; - JSClassFinalizer *finalizer; - JSShape *sh; - JSShapeProperty *pr; - - p->free_mark = 1; /* used to tell the object is invalid when - freeing cycles */ - /* free all the fields */ - sh = p->shape; - pr = get_shape_prop(sh); - for(i = 0; i < sh->prop_count; i++) { - free_property(rt, &p->prop[i], pr->flags); - pr++; - } - js_free_rt(rt, p->prop); - /* as an optimization we destroy the shape immediately without - putting it in gc_zero_ref_count_list */ - js_free_shape(rt, sh); - - /* fail safe */ - p->shape = NULL; - p->prop = NULL; - - if (unlikely(p->first_weak_ref)) { - reset_weak_ref(rt, &p->first_weak_ref); - } - - finalizer = rt->class_array[p->class_id].finalizer; - if (finalizer) - (*finalizer)(rt, JS_MKPTR(JS_TAG_OBJECT, p)); - - /* fail safe */ - p->class_id = 0; - p->u.opaque = NULL; - p->u.func.var_refs = NULL; - p->u.func.home_object = NULL; - - remove_gc_object(&p->header); - if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && p->header.ref_count != 0) { - list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list); - } else { - js_free_rt(rt, p); - } -} - -static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp) -{ - switch(gp->gc_obj_type) { - case JS_GC_OBJ_TYPE_JS_OBJECT: - free_object(rt, (JSObject *)gp); - break; - case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: - free_function_bytecode(rt, (JSFunctionBytecode *)gp); - break; - default: - abort(); - } -} - -static void free_zero_refcount(JSRuntime *rt) -{ - struct list_head *el; - JSGCObjectHeader *p; - - rt->gc_phase = JS_GC_PHASE_DECREF; - for(;;) { - el = rt->gc_zero_ref_count_list.next; - if (el == &rt->gc_zero_ref_count_list) - break; - p = list_entry(el, JSGCObjectHeader, link); - assert(p->ref_count == 0); - free_gc_object(rt, p); - } - rt->gc_phase = JS_GC_PHASE_NONE; -} - -/* called with the ref_count of 'v' reaches zero. */ -static void js_free_value_rt(JSRuntime *rt, JSValue v) -{ - uint32_t tag = JS_VALUE_GET_TAG(v); - -#ifdef ENABLE_DUMPS // JS_DUMP_FREE - if (check_dump_flag(rt, JS_DUMP_FREE)) { - /* Prevent invalid object access during GC */ - if ((rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) - || (tag != JS_TAG_OBJECT && tag != JS_TAG_FUNCTION_BYTECODE)) { - printf("Freeing "); - if (tag == JS_TAG_OBJECT) { - JS_DumpObject(rt, JS_VALUE_GET_OBJ(v)); - } else { - JS_DumpValue(rt, v); - printf("\n"); - } - } - } -#endif - - switch(tag) { - case JS_TAG_STRING: - js_free_string0(rt, JS_VALUE_GET_STRING(v)); - break; - case JS_TAG_OBJECT: - case JS_TAG_FUNCTION_BYTECODE: - { - JSGCObjectHeader *p = JS_VALUE_GET_PTR(v); - if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { - list_del(&p->link); - list_add(&p->link, &rt->gc_zero_ref_count_list); - if (rt->gc_phase == JS_GC_PHASE_NONE) { - free_zero_refcount(rt); - } - } - } - break; - case JS_TAG_MODULE: - abort(); /* never freed here */ - break; - case JS_TAG_BIG_INT: - { - JSBigInt *p = JS_VALUE_GET_PTR(v); - js_free_rt(rt, p); - } - break; - case JS_TAG_SYMBOL: - { - JSAtomStruct *p = JS_VALUE_GET_PTR(v); - JS_FreeAtomStruct(rt, p); - } - break; - default: - printf("js_free_value_rt: unknown tag=%d\n", tag); - abort(); - } -} - -void JS_FreeValueRT(JSRuntime *rt, JSValue v) -{ - if (JS_VALUE_HAS_REF_COUNT(v)) { - JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); - if (--p->ref_count <= 0) { - js_free_value_rt(rt, v); - } - } -} - -void JS_FreeValue(JSContext *ctx, JSValue v) -{ - JS_FreeValueRT(ctx->rt, v); -} - -/* garbage collection */ - -static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, - JSGCObjectTypeEnum type) -{ - h->mark = 0; - h->gc_obj_type = type; - list_add_tail(&h->link, &rt->gc_obj_list); -} - -static void remove_gc_object(JSGCObjectHeader *h) -{ - list_del(&h->link); -} - -void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) -{ - if (JS_VALUE_HAS_REF_COUNT(val)) { - switch(JS_VALUE_GET_TAG(val)) { - case JS_TAG_OBJECT: - case JS_TAG_FUNCTION_BYTECODE: - mark_func(rt, JS_VALUE_GET_PTR(val)); - break; - default: - break; - } - } -} - -static void mark_weak_map_value(JSRuntime *rt, JSWeakRefRecord *first_weak_ref, JS_MarkFunc *mark_func) { - JSWeakRefRecord *wr; - JSMapRecord *mr; - JSMapState *s; - - for (wr = first_weak_ref; wr != NULL; wr = wr->next_weak_ref) { - if (wr->kind == JS_WEAK_REF_KIND_MAP) { - mr = wr->u.map_record; - s = mr->map; - assert(s->is_weak); - assert(!mr->empty); /* no iterator on WeakMap/WeakSet */ - JS_MarkValue(rt, mr->value, mark_func); - } - } -} - -static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, - JS_MarkFunc *mark_func) -{ - switch(gp->gc_obj_type) { - case JS_GC_OBJ_TYPE_JS_OBJECT: - { - JSObject *p = (JSObject *)gp; - JSShapeProperty *prs; - JSShape *sh; - int i; - sh = p->shape; - mark_func(rt, &sh->header); - /* mark all the fields */ - prs = get_shape_prop(sh); - for(i = 0; i < sh->prop_count; i++) { - JSProperty *pr = &p->prop[i]; - if (prs->atom != JS_ATOM_NULL) { - if (prs->flags & JS_PROP_TMASK) { - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - if (pr->u.getset.getter) - mark_func(rt, &pr->u.getset.getter->header); - if (pr->u.getset.setter) - mark_func(rt, &pr->u.getset.setter->header); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - if (pr->u.var_ref->is_detached) { - /* Note: the tag does not matter - provided it is a GC object */ - mark_func(rt, &pr->u.var_ref->header); - } - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - js_autoinit_mark(rt, pr, mark_func); - } - } else { - JS_MarkValue(rt, pr->u.value, mark_func); - } - } - prs++; - } - - if (unlikely(p->first_weak_ref)) { - mark_weak_map_value(rt, p->first_weak_ref, mark_func); - } - - if (p->class_id != JS_CLASS_OBJECT) { - JSClassGCMark *gc_mark; - gc_mark = rt->class_array[p->class_id].gc_mark; - if (gc_mark) - gc_mark(rt, JS_MKPTR(JS_TAG_OBJECT, p), mark_func); - } - } - break; - case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: - /* the template objects can be part of a cycle */ - { - JSFunctionBytecode *b = (JSFunctionBytecode *)gp; - int i; - for(i = 0; i < b->cpool_count; i++) { - JS_MarkValue(rt, b->cpool[i], mark_func); - } - if (b->realm) - mark_func(rt, &b->realm->header); - } - break; - case JS_GC_OBJ_TYPE_VAR_REF: - { - JSVarRef *var_ref = (JSVarRef *)gp; - /* only detached variable referenced are taken into account */ - assert(var_ref->is_detached); - JS_MarkValue(rt, *var_ref->pvalue, mark_func); - } - break; - case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: - { - JSAsyncFunctionData *s = (JSAsyncFunctionData *)gp; - if (s->is_active) - async_func_mark(rt, &s->func_state, mark_func); - JS_MarkValue(rt, s->resolving_funcs[0], mark_func); - JS_MarkValue(rt, s->resolving_funcs[1], mark_func); - } - break; - case JS_GC_OBJ_TYPE_SHAPE: - { - JSShape *sh = (JSShape *)gp; - if (sh->proto != NULL) { - mark_func(rt, &sh->proto->header); - } - } - break; - case JS_GC_OBJ_TYPE_JS_CONTEXT: - { - JSContext *ctx = (JSContext *)gp; - JS_MarkContext(rt, ctx, mark_func); - } - break; - default: - abort(); - } -} - -static void gc_decref_child(JSRuntime *rt, JSGCObjectHeader *p) -{ - assert(p->ref_count > 0); - p->ref_count--; - if (p->ref_count == 0 && p->mark == 1) { - list_del(&p->link); - list_add_tail(&p->link, &rt->tmp_obj_list); - } -} - -static void gc_decref(JSRuntime *rt) -{ - struct list_head *el, *el1; - JSGCObjectHeader *p; - - init_list_head(&rt->tmp_obj_list); - - /* decrement the refcount of all the children of all the GC - objects and move the GC objects with zero refcount to - tmp_obj_list */ - list_for_each_safe(el, el1, &rt->gc_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - assert(p->mark == 0); - mark_children(rt, p, gc_decref_child); - p->mark = 1; - if (p->ref_count == 0) { - list_del(&p->link); - list_add_tail(&p->link, &rt->tmp_obj_list); - } - } -} - -static void gc_scan_incref_child(JSRuntime *rt, JSGCObjectHeader *p) -{ - p->ref_count++; - if (p->ref_count == 1) { - /* ref_count was 0: remove from tmp_obj_list and add at the - end of gc_obj_list */ - list_del(&p->link); - list_add_tail(&p->link, &rt->gc_obj_list); - p->mark = 0; /* reset the mark for the next GC call */ - } -} - -static void gc_scan_incref_child2(JSRuntime *rt, JSGCObjectHeader *p) -{ - p->ref_count++; -} - -static void gc_scan(JSRuntime *rt) -{ - struct list_head *el; - JSGCObjectHeader *p; - - /* keep the objects with a refcount > 0 and their children. */ - list_for_each(el, &rt->gc_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - assert(p->ref_count > 0); - p->mark = 0; /* reset the mark for the next GC call */ - mark_children(rt, p, gc_scan_incref_child); - } - - /* restore the refcount of the objects to be deleted. */ - list_for_each(el, &rt->tmp_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - mark_children(rt, p, gc_scan_incref_child2); - } -} - -static void gc_free_cycles(JSRuntime *rt) -{ - struct list_head *el, *el1; - JSGCObjectHeader *p; -#ifdef ENABLE_DUMPS // JS_DUMP_GC_FREE - bool header_done = false; -#endif - - rt->gc_phase = JS_GC_PHASE_REMOVE_CYCLES; - - for(;;) { - el = rt->tmp_obj_list.next; - if (el == &rt->tmp_obj_list) - break; - p = list_entry(el, JSGCObjectHeader, link); - /* Only need to free the GC object associated with JS - values. The rest will be automatically removed because they - must be referenced by them. */ - switch(p->gc_obj_type) { - case JS_GC_OBJ_TYPE_JS_OBJECT: - case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: -#ifdef ENABLE_DUMPS // JS_DUMP_GC_FREE - if (check_dump_flag(rt, JS_DUMP_GC_FREE)) { - if (!header_done) { - printf("Freeing cycles:\n"); - JS_DumpObjectHeader(rt); - header_done = true; - } - JS_DumpGCObject(rt, p); - } -#endif - free_gc_object(rt, p); - break; - default: - list_del(&p->link); - list_add_tail(&p->link, &rt->gc_zero_ref_count_list); - break; - } - } - rt->gc_phase = JS_GC_PHASE_NONE; - - list_for_each_safe(el, el1, &rt->gc_zero_ref_count_list) { - p = list_entry(el, JSGCObjectHeader, link); - assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT || - p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); - js_free_rt(rt, p); - } - - init_list_head(&rt->gc_zero_ref_count_list); -} - -void JS_RunGC(JSRuntime *rt) -{ - /* decrement the reference of the children of each object. mark = - 1 after this pass. */ - gc_decref(rt); - - /* keep the GC objects with a non zero refcount and their childs */ - gc_scan(rt); - - /* free the GC objects in a cycle */ - gc_free_cycles(rt); -} - -/* Return false if not an object or if the object has already been - freed (zombie objects are visible in finalizers when freeing - cycles). */ -bool JS_IsLiveObject(JSRuntime *rt, JSValueConst obj) -{ - JSObject *p; - if (!JS_IsObject(obj)) - return false; - p = JS_VALUE_GET_OBJ(obj); - return !p->free_mark; -} - -/* Compute memory used by various object types */ -/* XXX: poor man's approach to handling multiply referenced objects */ -typedef struct JSMemoryUsage_helper { - double memory_used_count; - double str_count; - double str_size; - int64_t js_func_count; - double js_func_size; - int64_t js_func_code_size; - int64_t js_func_pc2line_count; - int64_t js_func_pc2line_size; -} JSMemoryUsage_helper; - -static void compute_value_size(JSValue val, JSMemoryUsage_helper *hp); - -static void compute_jsstring_size(JSString *str, JSMemoryUsage_helper *hp) -{ - if (!str->atom_type) { /* atoms are handled separately */ - double s_ref_count = str->header.ref_count; - hp->str_count += 1 / s_ref_count; - hp->str_size += ((sizeof(*str) + (str->len << str->is_wide_char) + - 1 - str->is_wide_char) / s_ref_count); - } -} - -static void compute_bytecode_size(JSFunctionBytecode *b, JSMemoryUsage_helper *hp) -{ - int memory_used_count, js_func_size, i; - - memory_used_count = 0; - js_func_size = sizeof(*b); - if (b->vardefs) { - js_func_size += (b->arg_count + b->var_count) * sizeof(*b->vardefs); - } - if (b->cpool) { - js_func_size += b->cpool_count * sizeof(*b->cpool); - for (i = 0; i < b->cpool_count; i++) { - JSValue val = b->cpool[i]; - compute_value_size(val, hp); - } - } - if (b->closure_var) { - js_func_size += b->closure_var_count * sizeof(*b->closure_var); - } - if (b->byte_code_buf) { - hp->js_func_code_size += b->byte_code_len; - } - memory_used_count++; - js_func_size += b->source_len + 1; - if (b->pc2line_len) { - memory_used_count++; - hp->js_func_pc2line_count += 1; - hp->js_func_pc2line_size += b->pc2line_len; - } - hp->js_func_size += js_func_size; - hp->js_func_count += 1; - hp->memory_used_count += memory_used_count; -} - -static void compute_value_size(JSValue val, JSMemoryUsage_helper *hp) -{ - switch(JS_VALUE_GET_TAG(val)) { - case JS_TAG_STRING: - compute_jsstring_size(JS_VALUE_GET_STRING(val), hp); - break; - case JS_TAG_BIG_INT: - /* should track JSBigInt usage */ - break; - } -} - -void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) -{ - struct list_head *el, *el1; - int i; - JSMemoryUsage_helper mem = { 0 }, *hp = &mem; - - memset(s, 0, sizeof(*s)); - s->malloc_count = rt->malloc_state.malloc_count; - s->malloc_size = rt->malloc_state.malloc_size; - s->malloc_limit = rt->malloc_state.malloc_limit; - - s->memory_used_count = 2; /* rt + rt->class_array */ - s->memory_used_size = sizeof(JSRuntime) + sizeof(JSClass) * rt->class_count; - - list_for_each(el, &rt->context_list) { - JSContext *ctx = list_entry(el, JSContext, link); - JSShape *sh = ctx->array_shape; - s->memory_used_count += 2; /* ctx + ctx->class_proto */ - s->memory_used_size += sizeof(JSContext) + - sizeof(JSValue) * rt->class_count; - s->binary_object_count += ctx->binary_object_count; - s->binary_object_size += ctx->binary_object_size; - - /* the hashed shapes are counted separately */ - if (sh && !sh->is_hashed) { - int hash_size = sh->prop_hash_mask + 1; - s->shape_count++; - s->shape_size += get_shape_size(hash_size, sh->prop_size); - } - list_for_each(el1, &ctx->loaded_modules) { - JSModuleDef *m = list_entry(el1, JSModuleDef, link); - s->memory_used_count += 1; - s->memory_used_size += sizeof(*m); - if (m->req_module_entries) { - s->memory_used_count += 1; - s->memory_used_size += m->req_module_entries_count * sizeof(*m->req_module_entries); - } - if (m->export_entries) { - s->memory_used_count += 1; - s->memory_used_size += m->export_entries_count * sizeof(*m->export_entries); - for (i = 0; i < m->export_entries_count; i++) { - JSExportEntry *me = &m->export_entries[i]; - if (me->export_type == JS_EXPORT_TYPE_LOCAL && me->u.local.var_ref) { - /* potential multiple count */ - s->memory_used_count += 1; - compute_value_size(me->u.local.var_ref->value, hp); - } - } - } - if (m->star_export_entries) { - s->memory_used_count += 1; - s->memory_used_size += m->star_export_entries_count * sizeof(*m->star_export_entries); - } - if (m->import_entries) { - s->memory_used_count += 1; - s->memory_used_size += m->import_entries_count * sizeof(*m->import_entries); - } - compute_value_size(m->module_ns, hp); - compute_value_size(m->func_obj, hp); - } - } - - list_for_each(el, &rt->gc_obj_list) { - JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); - JSObject *p; - JSShape *sh; - JSShapeProperty *prs; - - /* XXX: could count the other GC object types too */ - if (gp->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { - compute_bytecode_size((JSFunctionBytecode *)gp, hp); - continue; - } else if (gp->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) { - continue; - } - p = (JSObject *)gp; - sh = p->shape; - s->obj_count++; - if (p->prop) { - s->memory_used_count++; - s->prop_size += sh->prop_size * sizeof(*p->prop); - s->prop_count += sh->prop_count; - prs = get_shape_prop(sh); - for(i = 0; i < sh->prop_count; i++) { - JSProperty *pr = &p->prop[i]; - if (prs->atom != JS_ATOM_NULL && !(prs->flags & JS_PROP_TMASK)) { - compute_value_size(pr->u.value, hp); - } - prs++; - } - } - /* the hashed shapes are counted separately */ - if (!sh->is_hashed) { - int hash_size = sh->prop_hash_mask + 1; - s->shape_count++; - s->shape_size += get_shape_size(hash_size, sh->prop_size); - } - - switch(p->class_id) { - case JS_CLASS_ARRAY: /* u.array | length */ - case JS_CLASS_ARGUMENTS: /* u.array | length */ - s->array_count++; - if (p->fast_array) { - s->fast_array_count++; - if (p->u.array.u.values) { - s->memory_used_count++; - s->memory_used_size += p->u.array.count * - sizeof(*p->u.array.u.values); - s->fast_array_elements += p->u.array.count; - for (i = 0; i < p->u.array.count; i++) { - compute_value_size(p->u.array.u.values[i], hp); - } - } - } - break; - case JS_CLASS_NUMBER: /* u.object_data */ - case JS_CLASS_STRING: /* u.object_data */ - case JS_CLASS_BOOLEAN: /* u.object_data */ - case JS_CLASS_SYMBOL: /* u.object_data */ - case JS_CLASS_DATE: /* u.object_data */ - case JS_CLASS_BIG_INT: /* u.object_data */ - compute_value_size(p->u.object_data, hp); - break; - case JS_CLASS_C_FUNCTION: /* u.cfunc */ - s->c_func_count++; - break; - case JS_CLASS_BYTECODE_FUNCTION: /* u.func */ - { - JSFunctionBytecode *b = p->u.func.function_bytecode; - JSVarRef **var_refs = p->u.func.var_refs; - /* home_object: object will be accounted for in list scan */ - if (var_refs) { - s->memory_used_count++; - s->js_func_size += b->closure_var_count * sizeof(*var_refs); - for (i = 0; i < b->closure_var_count; i++) { - if (var_refs[i]) { - double ref_count = var_refs[i]->header.ref_count; - s->memory_used_count += 1 / ref_count; - s->js_func_size += sizeof(*var_refs[i]) / ref_count; - /* handle non object closed values */ - if (var_refs[i]->pvalue == &var_refs[i]->value) { - /* potential multiple count */ - compute_value_size(var_refs[i]->value, hp); - } - } - } - } - } - break; - case JS_CLASS_BOUND_FUNCTION: /* u.bound_function */ - { - JSBoundFunction *bf = p->u.bound_function; - /* func_obj and this_val are objects */ - for (i = 0; i < bf->argc; i++) { - compute_value_size(bf->argv[i], hp); - } - s->memory_used_count += 1; - s->memory_used_size += sizeof(*bf) + bf->argc * sizeof(*bf->argv); - } - break; - case JS_CLASS_C_FUNCTION_DATA: /* u.c_function_data_record */ - { - JSCFunctionDataRecord *fd = p->u.c_function_data_record; - if (fd) { - for (i = 0; i < fd->data_len; i++) { - compute_value_size(fd->data[i], hp); - } - s->memory_used_count += 1; - s->memory_used_size += sizeof(*fd) + fd->data_len * sizeof(*fd->data); - } - } - break; - case JS_CLASS_REGEXP: /* u.regexp */ - compute_jsstring_size(p->u.regexp.pattern, hp); - compute_jsstring_size(p->u.regexp.bytecode, hp); - break; - - case JS_CLASS_FOR_IN_ITERATOR: /* u.for_in_iterator */ - { - JSForInIterator *it = p->u.for_in_iterator; - if (it) { - compute_value_size(it->obj, hp); - s->memory_used_count += 1; - s->memory_used_size += sizeof(*it); - } - } - break; - case JS_CLASS_ARRAY_BUFFER: /* u.array_buffer */ - case JS_CLASS_SHARED_ARRAY_BUFFER: /* u.array_buffer */ - { - JSArrayBuffer *abuf = p->u.array_buffer; - if (abuf) { - s->memory_used_count += 1; - s->memory_used_size += sizeof(*abuf); - if (abuf->data) { - s->memory_used_count += 1; - s->memory_used_size += abuf->byte_length; - } - } - } - break; - case JS_CLASS_GENERATOR: /* u.generator_data */ - case JS_CLASS_UINT8C_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_INT8_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_UINT8_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_INT16_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_UINT16_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_INT32_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_UINT32_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_BIG_INT64_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_BIG_UINT64_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_FLOAT16_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_FLOAT32_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_FLOAT64_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_DATAVIEW: /* u.typed_array */ - case JS_CLASS_MAP: /* u.map_state */ - case JS_CLASS_SET: /* u.map_state */ - case JS_CLASS_WEAKMAP: /* u.map_state */ - case JS_CLASS_WEAKSET: /* u.map_state */ - case JS_CLASS_MAP_ITERATOR: /* u.map_iterator_data */ - case JS_CLASS_SET_ITERATOR: /* u.map_iterator_data */ - case JS_CLASS_ARRAY_ITERATOR: /* u.array_iterator_data */ - case JS_CLASS_STRING_ITERATOR: /* u.array_iterator_data */ - case JS_CLASS_PROXY: /* u.proxy_data */ - case JS_CLASS_PROMISE: /* u.promise_data */ - case JS_CLASS_PROMISE_RESOLVE_FUNCTION: /* u.promise_function_data */ - case JS_CLASS_PROMISE_REJECT_FUNCTION: /* u.promise_function_data */ - case JS_CLASS_ASYNC_FUNCTION_RESOLVE: /* u.async_function_data */ - case JS_CLASS_ASYNC_FUNCTION_REJECT: /* u.async_function_data */ - case JS_CLASS_ASYNC_FROM_SYNC_ITERATOR: /* u.async_from_sync_iterator_data */ - case JS_CLASS_ASYNC_GENERATOR: /* u.async_generator_data */ - /* TODO */ - default: - /* XXX: class definition should have an opaque block size */ - if (p->u.opaque) { - s->memory_used_count += 1; - } - break; - } - } - s->obj_size += s->obj_count * sizeof(JSObject); - - /* hashed shapes */ - s->memory_used_count++; /* rt->shape_hash */ - s->memory_used_size += sizeof(rt->shape_hash[0]) * rt->shape_hash_size; - for(i = 0; i < rt->shape_hash_size; i++) { - JSShape *sh; - for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { - int hash_size = sh->prop_hash_mask + 1; - s->shape_count++; - s->shape_size += get_shape_size(hash_size, sh->prop_size); - } - } - - /* atoms */ - s->memory_used_count += 2; /* rt->atom_array, rt->atom_hash */ - s->atom_count = rt->atom_count; - s->atom_size = sizeof(rt->atom_array[0]) * rt->atom_size + - sizeof(rt->atom_hash[0]) * rt->atom_hash_size; - for(i = 0; i < rt->atom_size; i++) { - JSAtomStruct *p = rt->atom_array[i]; - if (!atom_is_free(p)) { - s->atom_size += (sizeof(*p) + (p->len << p->is_wide_char) + - 1 - p->is_wide_char); - } - } - s->str_count = round(mem.str_count); - s->str_size = round(mem.str_size); - s->js_func_count = mem.js_func_count; - s->js_func_size = round(mem.js_func_size); - s->js_func_code_size = mem.js_func_code_size; - s->js_func_pc2line_count = mem.js_func_pc2line_count; - s->js_func_pc2line_size = mem.js_func_pc2line_size; - s->memory_used_count += round(mem.memory_used_count) + - s->atom_count + s->str_count + - s->obj_count + s->shape_count + - s->js_func_count + s->js_func_pc2line_count; - s->memory_used_size += s->atom_size + s->str_size + - s->obj_size + s->prop_size + s->shape_size + - s->js_func_size + s->js_func_code_size + s->js_func_pc2line_size; -} - -void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) -{ - fprintf(fp, "QuickJS-ng memory usage -- %s version, %d-bit, %s Endian, malloc limit: %"PRId64"\n\n", - JS_GetVersion(), (int)sizeof(void *) * 8, is_be() ? "Big" : "Little", s->malloc_limit); - if (rt) { - static const struct { - const char *name; - size_t size; - } object_types[] = { - { "JSRuntime", sizeof(JSRuntime) }, - { "JSContext", sizeof(JSContext) }, - { "JSObject", sizeof(JSObject) }, - { "JSString", sizeof(JSString) }, - { "JSFunctionBytecode", sizeof(JSFunctionBytecode) }, - }; - int i, usage_size_ok = 0; - for(i = 0; i < countof(object_types); i++) { - unsigned int size = object_types[i].size; - void *p = js_malloc_rt(rt, size); - if (p) { - unsigned int size1 = js_malloc_usable_size_rt(rt, p); - if (size1 >= size) { - usage_size_ok = 1; - fprintf(fp, " %3u + %-2u %s\n", - size, size1 - size, object_types[i].name); - } - js_free_rt(rt, p); - } - } - if (!usage_size_ok) { - fprintf(fp, " malloc_usable_size unavailable\n"); - } - { - int obj_classes[JS_CLASS_INIT_COUNT + 1] = { 0 }; - int class_id; - struct list_head *el; - list_for_each(el, &rt->gc_obj_list) { - JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); - JSObject *p; - if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { - p = (JSObject *)gp; - obj_classes[min_uint32(p->class_id, JS_CLASS_INIT_COUNT)]++; - } - } - fprintf(fp, "\n" "JSObject classes\n"); - if (obj_classes[0]) - fprintf(fp, " %5d %2.0d %s\n", obj_classes[0], 0, "none"); - for (class_id = 1; class_id < JS_CLASS_INIT_COUNT; class_id++) { - if (obj_classes[class_id] && class_id < rt->class_count) { - char buf[ATOM_GET_STR_BUF_SIZE]; - fprintf(fp, " %5d %2.0d %s\n", obj_classes[class_id], class_id, - JS_AtomGetStrRT(rt, buf, sizeof(buf), rt->class_array[class_id].class_name)); - } - } - if (obj_classes[JS_CLASS_INIT_COUNT]) - fprintf(fp, " %5d %2.0d %s\n", obj_classes[JS_CLASS_INIT_COUNT], 0, "other"); - } - fprintf(fp, "\n"); - } - fprintf(fp, "%-20s %8s %8s\n", "NAME", "COUNT", "SIZE"); - - if (s->malloc_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per block)\n", - "memory allocated", s->malloc_count, s->malloc_size, - (double)s->malloc_size / s->malloc_count); - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%d overhead, %0.1f average slack)\n", - "memory used", s->memory_used_count, s->memory_used_size, - MALLOC_OVERHEAD, ((double)(s->malloc_size - s->memory_used_size) / - s->memory_used_count)); - } - if (s->atom_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per atom)\n", - "atoms", s->atom_count, s->atom_size, - (double)s->atom_size / s->atom_count); - } - if (s->str_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per string)\n", - "strings", s->str_count, s->str_size, - (double)s->str_size / s->str_count); - } - if (s->obj_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", - "objects", s->obj_count, s->obj_size, - (double)s->obj_size / s->obj_count); - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", - " properties", s->prop_count, s->prop_size, - (double)s->prop_count / s->obj_count); - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per shape)\n", - " shapes", s->shape_count, s->shape_size, - (double)s->shape_size / s->shape_count); - } - if (s->js_func_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", - "bytecode functions", s->js_func_count, s->js_func_size); - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", - " bytecode", s->js_func_count, s->js_func_code_size, - (double)s->js_func_code_size / s->js_func_count); - if (s->js_func_pc2line_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", - " pc2line", s->js_func_pc2line_count, - s->js_func_pc2line_size, - (double)s->js_func_pc2line_size / s->js_func_pc2line_count); - } - } - if (s->c_func_count) { - fprintf(fp, "%-20s %8"PRId64"\n", "C functions", s->c_func_count); - } - if (s->array_count) { - fprintf(fp, "%-20s %8"PRId64"\n", "arrays", s->array_count); - if (s->fast_array_count) { - fprintf(fp, "%-20s %8"PRId64"\n", " fast arrays", s->fast_array_count); - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per fast array)\n", - " elements", s->fast_array_elements, - s->fast_array_elements * (int)sizeof(JSValue), - (double)s->fast_array_elements / s->fast_array_count); - } - } - if (s->binary_object_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", - "binary objects", s->binary_object_count, s->binary_object_size); - } -} - -JSValue JS_GetGlobalObject(JSContext *ctx) -{ - return js_dup(ctx->global_obj); -} - -/* WARNING: obj is freed */ -JSValue JS_Throw(JSContext *ctx, JSValue obj) -{ - JSRuntime *rt = ctx->rt; - JS_FreeValue(ctx, rt->current_exception); - rt->current_exception = obj; - return JS_EXCEPTION; -} - -/* return the pending exception (cannot be called twice). */ -JSValue JS_GetException(JSContext *ctx) -{ - JSValue val; - JSRuntime *rt = ctx->rt; - val = rt->current_exception; - rt->current_exception = JS_UNINITIALIZED; - return val; -} - -bool JS_HasException(JSContext *ctx) -{ - return !JS_IsUninitialized(ctx->rt->current_exception); -} - -static void dbuf_put_leb128(DynBuf *s, uint32_t v) -{ - uint32_t a; - for(;;) { - a = v & 0x7f; - v >>= 7; - if (v != 0) { - dbuf_putc(s, a | 0x80); - } else { - dbuf_putc(s, a); - break; - } - } -} - -static void dbuf_put_sleb128(DynBuf *s, int32_t v1) -{ - uint32_t v = v1; - dbuf_put_leb128(s, (2 * v) ^ -(v >> 31)); -} - -static int get_leb128(uint32_t *pval, const uint8_t *buf, - const uint8_t *buf_end) -{ - const uint8_t *ptr = buf; - uint32_t v, a, i; - v = 0; - for(i = 0; i < 5; i++) { - if (unlikely(ptr >= buf_end)) - break; - a = *ptr++; - v |= (a & 0x7f) << (i * 7); - if (!(a & 0x80)) { - *pval = v; - return ptr - buf; - } - } - *pval = 0; - return -1; -} - -static int get_sleb128(int32_t *pval, const uint8_t *buf, - const uint8_t *buf_end) -{ - int ret; - uint32_t val; - ret = get_leb128(&val, buf, buf_end); - if (ret < 0) { - *pval = 0; - return -1; - } - *pval = (val >> 1) ^ -(val & 1); - return ret; -} - -static int find_line_num(JSContext *ctx, JSFunctionBytecode *b, - uint32_t pc_value, int *col) -{ - const uint8_t *p_end, *p; - int new_line_num, new_col_num, line_num, col_num, pc, v, ret; - unsigned int op; - - *col = 1; - p = b->pc2line_buf; - if (!p) - goto fail; - p_end = p + b->pc2line_len; - pc = 0; - line_num = b->line_num; - col_num = b->col_num; - while (p < p_end) { - op = *p++; - if (op == 0) { - uint32_t val; - ret = get_leb128(&val, p, p_end); - if (ret < 0) - goto fail; - pc += val; - p += ret; - ret = get_sleb128(&v, p, p_end); - if (ret < 0) - goto fail; - p += ret; - new_line_num = line_num + v; - } else { - op -= PC2LINE_OP_FIRST; - pc += (op / PC2LINE_RANGE); - new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; - } - ret = get_sleb128(&v, p, p_end); - if (ret < 0) - goto fail; - p += ret; - new_col_num = col_num + v; - if (pc_value < pc) - break; - line_num = new_line_num; - col_num = new_col_num; - } - *col = col_num; - return line_num; -fail: - /* should never happen */ - return b->line_num; -} - -/* in order to avoid executing arbitrary code during the stack trace - generation, we only look at simple 'name' properties containing a - string. */ -static const char *get_func_name(JSContext *ctx, JSValueConst func) -{ - JSProperty *pr; - JSShapeProperty *prs; - JSValue val; - - if (JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT) - return NULL; - prs = find_own_property(&pr, JS_VALUE_GET_OBJ(func), JS_ATOM_name); - if (!prs) - return NULL; - if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) - return NULL; - val = pr->u.value; - if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) - return NULL; - return JS_ToCString(ctx, val); -} - -/* Note: it is important that no exception is returned by this function */ -static bool can_add_backtrace(JSValueConst obj) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(obj); - if (p->class_id != JS_CLASS_ERROR && p->class_id != JS_CLASS_DOM_EXCEPTION) - return false; - if (find_own_property1(p, JS_ATOM_stack)) - return false; - return true; -} - -#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) -/* only taken into account if filename is provided */ -#define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1) -#define JS_BACKTRACE_FLAG_FILTER_FUNC (1 << 2) - -/* if filename != NULL, an additional level is added with the filename - and line number information (used for parse error). */ -static void build_backtrace(JSContext *ctx, JSValueConst error_val, - JSValueConst filter_func, const char *filename, - int line_num, int col_num, int backtrace_flags) -{ - JSStackFrame *sf, *sf_start; - JSValue stack, prepare, saved_exception; - DynBuf dbuf; - const char *func_name_str; - const char *str1; - JSObject *p; - JSFunctionBytecode *b; - bool backtrace_barrier, has_prepare, has_filter_func; - JSRuntime *rt; - JSCallSiteData csd[64]; - uint32_t i; - double d; - int stack_trace_limit; - - rt = ctx->rt; - if (rt->in_build_stack_trace) - return; - rt->in_build_stack_trace = true; - - // Save exception because conversion to double may fail. - saved_exception = JS_GetException(ctx); - - // Extract stack trace limit. - // Ignore error since it sets d to NAN anyway. - // coverity[check_return] - JS_ToFloat64(ctx, &d, ctx->error_stack_trace_limit); - if (isnan(d) || d < 0.0) - stack_trace_limit = 0; - else if (d > INT32_MAX) - stack_trace_limit = INT32_MAX; - else - stack_trace_limit = fabs(d); - - // Restore current exception. - JS_Throw(ctx, saved_exception); - saved_exception = JS_UNINITIALIZED; - - stack_trace_limit = min_int(stack_trace_limit, countof(csd)); - stack_trace_limit = max_int(stack_trace_limit, 0); - has_prepare = false; - has_filter_func = backtrace_flags & JS_BACKTRACE_FLAG_FILTER_FUNC; - i = 0; - - if (!JS_IsNull(ctx->error_ctor)) { - prepare = js_dup(ctx->error_prepare_stack); - has_prepare = JS_IsFunction(ctx, prepare); - } - - if (has_prepare) { - saved_exception = JS_GetException(ctx); - if (stack_trace_limit == 0) - goto done; - if (filename) - js_new_callsite_data2(ctx, &csd[i++], filename, line_num, col_num); - } else { - js_dbuf_init(ctx, &dbuf); - if (stack_trace_limit == 0) - goto done; - if (filename) { - i++; - dbuf_printf(&dbuf, " at %s", filename); - if (line_num != -1) - dbuf_printf(&dbuf, ":%d:%d", line_num, col_num); - dbuf_putc(&dbuf, '\n'); - } - } - - if (filename && (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL)) - goto done; - - sf_start = rt->current_stack_frame; - - /* Find the frame we want to start from. Note that when a filter is used the filter - function will be the first, but we also specify we want to skip the first one. */ - if (has_filter_func) { - for (sf = sf_start; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) { - if (js_same_value(ctx, sf->cur_func, filter_func)) { - sf_start = sf; - break; - } - } - } - - for (sf = sf_start; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) { - if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { - backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; - continue; - } - - p = JS_VALUE_GET_OBJ(sf->cur_func); - b = NULL; - backtrace_barrier = false; - - if (js_class_has_bytecode(p->class_id)) { - b = p->u.func.function_bytecode; - backtrace_barrier = b->backtrace_barrier; - } - - if (has_prepare) { - js_new_callsite_data(ctx, &csd[i], sf); - } else { - /* func_name_str is UTF-8 encoded if needed */ - func_name_str = get_func_name(ctx, sf->cur_func); - if (!func_name_str || func_name_str[0] == '\0') - str1 = ""; - else - str1 = func_name_str; - dbuf_printf(&dbuf, " at %s", str1); - JS_FreeCString(ctx, func_name_str); - - if (b && sf->cur_pc) { - const char *atom_str; - int line_num1, col_num1; - uint32_t pc; - - pc = sf->cur_pc - b->byte_code_buf - 1; - line_num1 = find_line_num(ctx, b, pc, &col_num1); - atom_str = b->filename ? JS_AtomToCString(ctx, b->filename) : NULL; - dbuf_printf(&dbuf, " (%s", atom_str ? atom_str : ""); - JS_FreeCString(ctx, atom_str); - if (line_num1 != -1) - dbuf_printf(&dbuf, ":%d:%d", line_num1, col_num1); - dbuf_putc(&dbuf, ')'); - } else if (b) { - // FIXME(bnoordhuis) Missing `sf->cur_pc = pc` in bytecode - // handler in JS_CallInternal. Almost never user observable - // except with intercepting JS proxies that throw exceptions. - dbuf_printf(&dbuf, " (missing)"); - } else { - dbuf_printf(&dbuf, " (native)"); - } - dbuf_putc(&dbuf, '\n'); - } - i++; - - /* stop backtrace if JS_EVAL_FLAG_BACKTRACE_BARRIER was used */ - if (backtrace_barrier) - break; - } - done: - if (has_prepare) { - int j = 0, k; - stack = JS_NewArray(ctx); - if (JS_IsException(stack)) { - stack = JS_NULL; - } else { - for (; j < i; j++) { - JSValue v = js_new_callsite(ctx, &csd[j]); - if (JS_IsException(v)) - break; - if (JS_DefinePropertyValueUint32(ctx, stack, j, v, JS_PROP_C_W_E) < 0) { - JS_FreeValue(ctx, v); - break; - } - } - } - // Clear the csd's we didn't use in case of error. - for (k = j; k < i; k++) { - JS_FreeValue(ctx, csd[k].filename); - JS_FreeValue(ctx, csd[k].func); - JS_FreeValue(ctx, csd[k].func_name); - } - JSValueConst args[] = { - error_val, - stack, - }; - JSValue stack2 = JS_Call(ctx, prepare, ctx->error_ctor, countof(args), args); - JS_FreeValue(ctx, stack); - if (JS_IsException(stack2)) - stack = JS_NULL; - else - stack = stack2; - JS_FreeValue(ctx, prepare); - JS_Throw(ctx, saved_exception); - } else { - if (dbuf_error(&dbuf)) - stack = JS_NULL; - else - stack = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size); - dbuf_free(&dbuf); - } - - if (JS_IsUndefined(ctx->error_back_trace)) - ctx->error_back_trace = js_dup(stack); - if (has_filter_func || can_add_backtrace(error_val)) { - JS_DefinePropertyValue(ctx, error_val, JS_ATOM_stack, stack, - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - } else { - JS_FreeValue(ctx, stack); - } - - rt->in_build_stack_trace = false; -} - -JSValue JS_NewError(JSContext *ctx) -{ - JSValue obj = JS_NewObjectClass(ctx, JS_CLASS_ERROR); - if (JS_IsException(obj)) - return JS_EXCEPTION; - build_backtrace(ctx, obj, JS_UNDEFINED, NULL, 0, 0, 0); - return obj; -} - -static JSValue JS_MakeError2(JSContext *ctx, JSErrorEnum error_num, - bool add_backtrace, const char *message) -{ - JSValue obj, msg; - - if (error_num == JS_PLAIN_ERROR) { - obj = JS_NewObjectClass(ctx, JS_CLASS_ERROR); - } else { - obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num], - JS_CLASS_ERROR); - } - if (JS_IsException(obj)) - return JS_EXCEPTION; - msg = JS_NewString(ctx, message); - if (JS_IsException(msg)) - msg = JS_NewString(ctx, "Invalid error message"); - if (!JS_IsException(msg)) { - JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg, - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - } - if (add_backtrace) - build_backtrace(ctx, obj, JS_UNDEFINED, NULL, 0, 0, 0); - return obj; -} - -static JSValue JS_PRINTF_FORMAT_ATTR(4, 0) -JS_MakeError(JSContext *ctx, JSErrorEnum error_num, bool add_backtrace, - JS_PRINTF_FORMAT const char *fmt, va_list ap) -{ - char buf[256]; - - vsnprintf(buf, sizeof(buf), fmt, ap); - return JS_MakeError2(ctx, error_num, add_backtrace, buf); -} - -/* fmt and arguments may be pure ASCII or UTF-8 encoded contents */ -static JSValue JS_PRINTF_FORMAT_ATTR(4, 0) -JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num, bool add_backtrace, - JS_PRINTF_FORMAT const char *fmt, va_list ap) -{ - JSValue obj; - - obj = JS_MakeError(ctx, error_num, add_backtrace, fmt, ap); - if (unlikely(JS_IsException(obj))) { - /* out of memory: throw JS_NULL to avoid recursing */ - obj = JS_NULL; - } - return JS_Throw(ctx, obj); -} - -static JSValue JS_PRINTF_FORMAT_ATTR(3, 0) -JS_ThrowError(JSContext *ctx, JSErrorEnum error_num, - JS_PRINTF_FORMAT const char *fmt, va_list ap) -{ - JSRuntime *rt = ctx->rt; - JSStackFrame *sf; - bool add_backtrace; - - /* the backtrace is added later if called from a bytecode function */ - sf = rt->current_stack_frame; - add_backtrace = !rt->in_out_of_memory && - (!sf || (JS_GetFunctionBytecode(sf->cur_func) == NULL)); - return JS_ThrowError2(ctx, error_num, add_backtrace, fmt, ap); -} - -#define JS_ERROR_MAP(X) \ - X(Internal, INTERNAL) \ - X(Plain, PLAIN) \ - X(Range, RANGE) \ - X(Reference, REFERENCE) \ - X(Syntax, SYNTAX) \ - X(Type, TYPE) \ - -#define X(lc, uc) \ - JSValue JS_PRINTF_FORMAT_ATTR(2, 3) \ - JS_New##lc##Error(JSContext *ctx, \ - JS_PRINTF_FORMAT const char *fmt, ...) \ - { \ - JSValue val; \ - va_list ap; \ - \ - va_start(ap, fmt); \ - val = JS_MakeError(ctx, JS_##uc##_ERROR, \ - /*add_backtrace*/true, fmt, ap); \ - va_end(ap); \ - return val; \ - } \ - JSValue JS_PRINTF_FORMAT_ATTR(2, 3) \ - JS_Throw##lc##Error(JSContext *ctx, \ - JS_PRINTF_FORMAT const char *fmt, ...) \ - { \ - JSValue val; \ - va_list ap; \ - \ - va_start(ap, fmt); \ - val = JS_ThrowError(ctx, JS_##uc##_ERROR, fmt, ap); \ - va_end(ap); \ - return val; \ - } \ - -JS_ERROR_MAP(X) - -#undef X -#undef JS_ERROR_MAP - -static int JS_PRINTF_FORMAT_ATTR(3, 4) JS_ThrowTypeErrorOrFalse(JSContext *ctx, int flags, JS_PRINTF_FORMAT const char *fmt, ...) -{ - va_list ap; - - if ((flags & JS_PROP_THROW) || - ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { - va_start(ap, fmt); - JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); - va_end(ap); - return -1; - } else { - return false; - } -} - -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" -#endif // __GNUC__ -static JSValue JS_ThrowTypeErrorAtom(JSContext *ctx, const char *fmt, JSAtom atom) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - JS_AtomGetStr(ctx, buf, sizeof(buf), atom); - return JS_ThrowTypeError(ctx, fmt, buf); -} - -static JSValue JS_ThrowSyntaxErrorAtom(JSContext *ctx, const char *fmt, JSAtom atom) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - JS_AtomGetStr(ctx, buf, sizeof(buf), atom); - return JS_ThrowSyntaxError(ctx, fmt, buf); -} -#ifdef __GNUC__ -#pragma GCC diagnostic pop // ignored "-Wformat-nonliteral" -#endif // __GNUC__ - -static int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom) -{ - if ((flags & JS_PROP_THROW) || - ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { - JS_ThrowTypeErrorAtom(ctx, "'%s' is read-only", atom); - return -1; - } else { - return false; - } -} - -JSValue JS_ThrowOutOfMemory(JSContext *ctx) -{ - JSRuntime *rt = ctx->rt; - if (!rt->in_out_of_memory) { - rt->in_out_of_memory = true; - JS_ThrowInternalError(ctx, "out of memory"); - rt->in_out_of_memory = false; - } - return JS_EXCEPTION; -} - -static JSValue JS_ThrowStackOverflow(JSContext *ctx) -{ - return JS_ThrowRangeError(ctx, "Maximum call stack size exceeded"); -} - -static JSValue JS_ThrowTypeErrorNotAConstructor(JSContext *ctx, - JSValueConst func_obj) -{ - JSObject *p; - JSAtom name; - - if (JS_TAG_OBJECT != JS_VALUE_GET_TAG(func_obj)) - goto fini; - p = JS_VALUE_GET_OBJ(func_obj); - if (!js_class_has_bytecode(p->class_id)) - goto fini; - name = p->u.func.function_bytecode->func_name; - if (name == JS_ATOM_NULL) - goto fini; - return JS_ThrowTypeErrorAtom(ctx, "%s is not a constructor", name); -fini: - return JS_ThrowTypeError(ctx, "not a constructor"); -} - -static JSValue JS_ThrowTypeErrorNotAFunction(JSContext *ctx) -{ - return JS_ThrowTypeError(ctx, "not a function"); -} - -static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx) -{ - return JS_ThrowTypeError(ctx, "not an object"); -} - -static JSValue JS_ThrowTypeErrorNotASymbol(JSContext *ctx) -{ - return JS_ThrowTypeError(ctx, "not a symbol"); -} - -static JSValue JS_ThrowReferenceErrorNotDefined(JSContext *ctx, JSAtom name) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - return JS_ThrowReferenceError(ctx, "%s is not defined", - JS_AtomGetStr(ctx, buf, sizeof(buf), name)); -} - -static JSValue JS_ThrowReferenceErrorUninitialized(JSContext *ctx, JSAtom name) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - return JS_ThrowReferenceError(ctx, "%s is not initialized", - name == JS_ATOM_NULL ? "lexical variable" : - JS_AtomGetStr(ctx, buf, sizeof(buf), name)); -} - -static JSValue JS_ThrowReferenceErrorUninitialized2(JSContext *ctx, - JSFunctionBytecode *b, - int idx, bool is_ref) -{ - JSAtom atom = JS_ATOM_NULL; - if (is_ref) { - atom = b->closure_var[idx].var_name; - } else { - /* not present if the function is stripped and contains no eval() */ - if (b->vardefs) - atom = b->vardefs[b->arg_count + idx].var_name; - } - return JS_ThrowReferenceErrorUninitialized(ctx, atom); -} - -static JSValue JS_ThrowTypeErrorInvalidClass(JSContext *ctx, int class_id) -{ - JSRuntime *rt = ctx->rt; - JSAtom name; - name = rt->class_array[class_id].class_name; - return JS_ThrowTypeErrorAtom(ctx, "%s object expected", name); -} - -static void JS_ThrowInterrupted(JSContext *ctx) -{ - JS_ThrowInternalError(ctx, "interrupted"); - JS_SetUncatchableError(ctx, ctx->rt->current_exception); -} - -static no_inline __exception int __js_poll_interrupts(JSContext *ctx) -{ - JSRuntime *rt = ctx->rt; - ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; - if (rt->interrupt_handler) { - if (rt->interrupt_handler(rt, rt->interrupt_opaque)) { - JS_ThrowInterrupted(ctx); - return -1; - } - } - return 0; -} - -static inline __exception int js_poll_interrupts(JSContext *ctx) -{ - if (unlikely(--ctx->interrupt_counter <= 0)) { - return __js_poll_interrupts(ctx); - } else { - return 0; - } -} - -/* return -1 (exception) or true/false */ -static int JS_SetPrototypeInternal(JSContext *ctx, JSValueConst obj, - JSValueConst proto_val, bool throw_flag) -{ - JSObject *proto, *p, *p1; - JSShape *sh; - - if (throw_flag) { - if (JS_VALUE_GET_TAG(obj) == JS_TAG_NULL || - JS_VALUE_GET_TAG(obj) == JS_TAG_UNDEFINED) - goto not_obj; - } else { - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - goto not_obj; - } - p = JS_VALUE_GET_OBJ(obj); - if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) { - if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_NULL) { - not_obj: - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } - proto = NULL; - } else { - proto = JS_VALUE_GET_OBJ(proto_val); - } - - if (throw_flag && JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - return true; - - if (unlikely(p->class_id == JS_CLASS_PROXY)) - return js_proxy_setPrototypeOf(ctx, obj, proto_val, throw_flag); - sh = p->shape; - if (sh->proto == proto) - return true; - if (p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_OBJECT])) { - if (throw_flag) { - JS_ThrowTypeError(ctx, "'Immutable prototype object \'Object.prototype\' cannot have their prototype set'"); - return -1; - } - return false; - } - if (!p->extensible) { - if (throw_flag) { - JS_ThrowTypeError(ctx, "object is not extensible"); - return -1; - } else { - return false; - } - } - if (proto) { - /* check if there is a cycle */ - p1 = proto; - do { - if (p1 == p) { - if (throw_flag) { - JS_ThrowTypeError(ctx, "circular prototype chain"); - return -1; - } else { - return false; - } - } - /* Note: for Proxy objects, proto is NULL */ - p1 = p1->shape->proto; - } while (p1 != NULL); - js_dup(proto_val); - } - - if (js_shape_prepare_update(ctx, p, NULL)) - return -1; - sh = p->shape; - if (sh->proto) - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); - sh->proto = proto; - return true; -} - -/* return -1 (exception) or true/false */ -int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValueConst proto_val) -{ - return JS_SetPrototypeInternal(ctx, obj, proto_val, true); -} - -/* Only works for primitive types, otherwise return JS_NULL. */ -static JSValueConst JS_GetPrototypePrimitive(JSContext *ctx, JSValueConst val) -{ - JSValue ret; - switch(JS_VALUE_GET_NORM_TAG(val)) { - case JS_TAG_SHORT_BIG_INT: - case JS_TAG_BIG_INT: - ret = ctx->class_proto[JS_CLASS_BIG_INT]; - break; - case JS_TAG_INT: - case JS_TAG_FLOAT64: - ret = ctx->class_proto[JS_CLASS_NUMBER]; - break; - case JS_TAG_BOOL: - ret = ctx->class_proto[JS_CLASS_BOOLEAN]; - break; - case JS_TAG_STRING: - ret = ctx->class_proto[JS_CLASS_STRING]; - break; - case JS_TAG_SYMBOL: - ret = ctx->class_proto[JS_CLASS_SYMBOL]; - break; - case JS_TAG_OBJECT: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - default: - ret = JS_NULL; - break; - } - return ret; -} - -/* Return an Object, JS_NULL or JS_EXCEPTION in case of Proxy object. */ -JSValue JS_GetPrototype(JSContext *ctx, JSValueConst obj) -{ - JSValue val; - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - JSObject *p; - p = JS_VALUE_GET_OBJ(obj); - if (unlikely(p->class_id == JS_CLASS_PROXY)) { - val = js_proxy_getPrototypeOf(ctx, obj); - } else { - p = p->shape->proto; - if (!p) - val = JS_NULL; - else - val = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); - } - } else { - val = js_dup(JS_GetPrototypePrimitive(ctx, obj)); - } - return val; -} - -static JSValue JS_GetPrototypeFree(JSContext *ctx, JSValue obj) -{ - JSValue obj1; - obj1 = JS_GetPrototype(ctx, obj); - JS_FreeValue(ctx, obj); - return obj1; -} - -int JS_GetLength(JSContext *ctx, JSValueConst obj, int64_t *pres) { - return js_get_length64(ctx, pres, obj); -} - -int JS_SetLength(JSContext *ctx, JSValueConst obj, int64_t len) { - return js_set_length64(ctx, obj, len); -} - -/* return true, false or (-1) in case of exception */ -static int JS_OrdinaryIsInstanceOf(JSContext *ctx, JSValueConst val, - JSValueConst obj) -{ - JSValue obj_proto; - JSObject *proto; - const JSObject *p, *proto1; - int ret; - - if (!JS_IsFunction(ctx, obj)) - return false; - p = JS_VALUE_GET_OBJ(obj); - if (p->class_id == JS_CLASS_BOUND_FUNCTION) { - JSBoundFunction *s = p->u.bound_function; - return JS_IsInstanceOf(ctx, val, s->func_obj); - } - - /* Only explicitly boxed values are instances of constructors */ - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return false; - obj_proto = JS_GetProperty(ctx, obj, JS_ATOM_prototype); - if (JS_VALUE_GET_TAG(obj_proto) != JS_TAG_OBJECT) { - if (!JS_IsException(obj_proto)) - JS_ThrowTypeError(ctx, "operand 'prototype' property is not an object"); - ret = -1; - goto done; - } - proto = JS_VALUE_GET_OBJ(obj_proto); - p = JS_VALUE_GET_OBJ(val); - for(;;) { - proto1 = p->shape->proto; - if (!proto1) { - /* slow case if proxy in the prototype chain */ - if (unlikely(p->class_id == JS_CLASS_PROXY)) { - JSValue obj1; - obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, (JSObject *)p)); - for(;;) { - obj1 = JS_GetPrototypeFree(ctx, obj1); - if (JS_IsException(obj1)) { - ret = -1; - break; - } - if (JS_IsNull(obj1)) { - ret = false; - break; - } - if (proto == JS_VALUE_GET_OBJ(obj1)) { - JS_FreeValue(ctx, obj1); - ret = true; - break; - } - /* must check for timeout to avoid infinite loop */ - if (js_poll_interrupts(ctx)) { - JS_FreeValue(ctx, obj1); - ret = -1; - break; - } - } - } else { - ret = false; - } - break; - } - p = proto1; - if (proto == p) { - ret = true; - break; - } - } -done: - JS_FreeValue(ctx, obj_proto); - return ret; -} - -/* return true, false or (-1) in case of exception */ -int JS_IsInstanceOf(JSContext *ctx, JSValueConst val, JSValueConst obj) -{ - JSValue method; - - if (!JS_IsObject(obj)) - goto fail; - method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_hasInstance); - if (JS_IsException(method)) - return -1; - if (!JS_IsNull(method) && !JS_IsUndefined(method)) { - JSValue ret; - ret = JS_CallFree(ctx, method, obj, 1, &val); - return JS_ToBoolFree(ctx, ret); - } - - /* legacy case */ - if (!JS_IsFunction(ctx, obj)) { - fail: - JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand"); - return -1; - } - return JS_OrdinaryIsInstanceOf(ctx, val, obj); -} - -#include "builtin-array-fromasync.h" - -static JSValue js_bytecode_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, - void *opaque) -{ - switch ((uintptr_t)opaque) { - default: - abort(); - case JS_BUILTIN_ARRAY_FROMASYNC: - { - JSValue obj = JS_ReadObject(ctx, qjsc_builtin_array_fromasync, - sizeof(qjsc_builtin_array_fromasync), - JS_READ_OBJ_BYTECODE); - if (JS_IsException(obj)) - return JS_EXCEPTION; - JSValue fun = JS_EvalFunction(ctx, obj); - if (JS_IsException(fun)) - return JS_EXCEPTION; - assert(JS_IsFunction(ctx, fun)); - JSValue args[] = { - JS_NewCFunction(ctx, js_array_constructor, "Array", 0), - JS_NewCFunctionMagic(ctx, js_error_constructor, "TypeError", 1, - JS_CFUNC_constructor_or_func_magic, - JS_TYPE_ERROR), - JS_AtomToValue(ctx, JS_ATOM_Symbol_asyncIterator), - JS_NewCFunctionMagic(ctx, js_object_defineProperty, - "Object.defineProperty", 3, - JS_CFUNC_generic_magic, 0), - JS_AtomToValue(ctx, JS_ATOM_Symbol_iterator), - }; - JSValue result = JS_Call(ctx, fun, JS_UNDEFINED, - countof(args), vc(args)); - for (size_t i = 0; i < countof(args); i++) - JS_FreeValue(ctx, args[i]); - JS_FreeValue(ctx, fun); - if (JS_SetPrototypeInternal(ctx, result, ctx->function_proto, - /*throw_flag*/true) < 0) { - JS_FreeValue(ctx, result); - return JS_EXCEPTION; - } - return result; - } - } - return JS_UNDEFINED; -} - -/* return the value associated to the autoinit property or an exception */ -typedef JSValue JSAutoInitFunc(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); - -static JSAutoInitFunc *const js_autoinit_func_table[] = { - js_instantiate_prototype, /* JS_AUTOINIT_ID_PROTOTYPE */ - js_module_ns_autoinit, /* JS_AUTOINIT_ID_MODULE_NS */ - JS_InstantiateFunctionListItem2, /* JS_AUTOINIT_ID_PROP */ - js_bytecode_autoinit, /* JS_AUTOINIT_ID_BYTECODE */ -}; - -/* warning: 'prs' is reallocated after it */ -static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, - JSProperty *pr, JSShapeProperty *prs) -{ - JSValue val; - JSContext *realm; - JSAutoInitFunc *func; - - if (js_shape_prepare_update(ctx, p, &prs)) - return -1; - - realm = js_autoinit_get_realm(pr); - func = js_autoinit_func_table[js_autoinit_get_id(pr)]; - /* 'func' shall not modify the object properties 'pr' */ - val = func(realm, p, prop, pr->u.init.opaque); - js_autoinit_free(ctx->rt, pr); - prs->flags &= ~JS_PROP_TMASK; - pr->u.value = JS_UNDEFINED; - if (JS_IsException(val)) - return -1; - pr->u.value = val; - return 0; -} - -static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, - JSAtom prop, JSValueConst this_obj, - bool throw_ref_error) -{ - JSObject *p; - JSProperty *pr; - JSShapeProperty *prs; - uint32_t tag; - - tag = JS_VALUE_GET_TAG(obj); - if (unlikely(tag != JS_TAG_OBJECT)) { - switch(tag) { - case JS_TAG_NULL: - return JS_ThrowTypeErrorAtom(ctx, "Cannot read properties of null (reading '%s')", prop); - case JS_TAG_UNDEFINED: - return JS_ThrowTypeErrorAtom(ctx, "Cannot read properties of undefined (reading '%s')", prop); - case JS_TAG_EXCEPTION: - return JS_EXCEPTION; - case JS_TAG_STRING: - { - JSString *p1 = JS_VALUE_GET_STRING(obj); - if (__JS_AtomIsTaggedInt(prop)) { - uint32_t idx, ch; - idx = __JS_AtomToUInt32(prop); - if (idx < p1->len) { - ch = string_get(p1, idx); - return js_new_string_char(ctx, ch); - } - } else if (prop == JS_ATOM_length) { - return js_int32(p1->len); - } - } - break; - default: - break; - } - /* cannot raise an exception */ - p = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, obj)); - if (!p) - return JS_UNDEFINED; - } else { - p = JS_VALUE_GET_OBJ(obj); - } - - for(;;) { - prs = find_own_property(&pr, p, prop); - if (prs) { - /* found */ - if (unlikely(prs->flags & JS_PROP_TMASK)) { - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - if (unlikely(!pr->u.getset.getter)) { - return JS_UNDEFINED; - } else { - JSValue func = JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter); - /* Note: the field could be removed in the getter */ - func = js_dup(func); - return JS_CallFree(ctx, func, this_obj, 0, NULL); - } - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - JSValue val = *pr->u.var_ref->pvalue; - if (unlikely(JS_IsUninitialized(val))) - return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return js_dup(val); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - /* Instantiate property and retry */ - if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) - return JS_EXCEPTION; - continue; - } - } else { - return js_dup(pr->u.value); - } - } - if (unlikely(p->is_exotic)) { - /* exotic behaviors */ - if (p->fast_array) { - if (__JS_AtomIsTaggedInt(prop)) { - uint32_t idx = __JS_AtomToUInt32(prop); - if (idx < p->u.array.count) { - /* we avoid duplicating the code */ - return JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); - } else if (is_typed_array(p->class_id)) { - return JS_UNDEFINED; - } - } else if (is_typed_array(p->class_id)) { - int ret; - ret = JS_AtomIsNumericIndex(ctx, prop); - if (ret != 0) { - if (ret < 0) - return JS_EXCEPTION; - return JS_UNDEFINED; - } - } - } else { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em) { - if (em->get_property) { - JSValue obj1, retval; - /* XXX: should pass throw_ref_error */ - /* Note: if 'p' is a prototype, it can be - freed in the called function */ - obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); - retval = em->get_property(ctx, obj1, prop, this_obj); - JS_FreeValue(ctx, obj1); - return retval; - } - if (em->get_own_property) { - JSPropertyDescriptor desc; - int ret; - JSValue obj1; - - /* Note: if 'p' is a prototype, it can be - freed in the called function */ - obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); - ret = em->get_own_property(ctx, &desc, obj1, prop); - JS_FreeValue(ctx, obj1); - if (ret < 0) - return JS_EXCEPTION; - if (ret) { - if (desc.flags & JS_PROP_GETSET) { - JS_FreeValue(ctx, desc.setter); - return JS_CallFree(ctx, desc.getter, this_obj, 0, NULL); - } else { - return desc.value; - } - } - } - } - } - } - p = p->shape->proto; - if (!p) - break; - } - if (unlikely(throw_ref_error)) { - return JS_ThrowReferenceErrorNotDefined(ctx, prop); - } else { - return JS_UNDEFINED; - } -} - -JSValue JS_GetProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop) -{ - return JS_GetPropertyInternal(ctx, this_obj, prop, this_obj, false); -} - -static JSValue JS_ThrowTypeErrorPrivateNotFound(JSContext *ctx, JSAtom atom) -{ - return JS_ThrowTypeErrorAtom(ctx, "private class field '%s' does not exist", - atom); -} - -/* Private fields can be added even on non extensible objects or - Proxies */ -static int JS_DefinePrivateField(JSContext *ctx, JSValueConst obj, - JSValue name, JSValue val) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - JSAtom prop; - - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { - JS_ThrowTypeErrorNotAnObject(ctx); - goto fail; - } - /* safety check */ - if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { - JS_ThrowTypeErrorNotASymbol(ctx); - goto fail; - } - prop = js_symbol_to_atom(ctx, name); - p = JS_VALUE_GET_OBJ(obj); - prs = find_own_property(&pr, p, prop); - if (prs) { - JS_ThrowTypeErrorAtom(ctx, "private class field '%s' already exists", - prop); - goto fail; - } - pr = add_property(ctx, p, prop, JS_PROP_C_W_E); - if (unlikely(!pr)) { - fail: - JS_FreeValue(ctx, val); - return -1; - } - pr->u.value = val; - return 0; -} - -static JSValue JS_GetPrivateField(JSContext *ctx, JSValueConst obj, - JSValueConst name) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - JSAtom prop; - - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) - return JS_ThrowTypeErrorNotAnObject(ctx); - /* safety check */ - if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) - return JS_ThrowTypeErrorNotASymbol(ctx); - prop = js_symbol_to_atom(ctx, name); - p = JS_VALUE_GET_OBJ(obj); - prs = find_own_property(&pr, p, prop); - if (!prs) { - JS_ThrowTypeErrorPrivateNotFound(ctx, prop); - return JS_EXCEPTION; - } - return js_dup(pr->u.value); -} - -static int JS_SetPrivateField(JSContext *ctx, JSValueConst obj, - JSValueConst name, JSValue val) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - JSAtom prop; - - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { - JS_ThrowTypeErrorNotAnObject(ctx); - goto fail; - } - /* safety check */ - if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { - JS_ThrowTypeErrorNotASymbol(ctx); - goto fail; - } - prop = js_symbol_to_atom(ctx, name); - p = JS_VALUE_GET_OBJ(obj); - prs = find_own_property(&pr, p, prop); - if (!prs) { - JS_ThrowTypeErrorPrivateNotFound(ctx, prop); - fail: - JS_FreeValue(ctx, val); - return -1; - } - set_value(ctx, &pr->u.value, val); - return 0; -} - -/* add a private brand field to 'home_obj' if not already present and - if obj is != null add a private brand to it */ -static int JS_AddBrand(JSContext *ctx, JSValueConst obj, JSValueConst home_obj) -{ - JSObject *p, *p1; - JSShapeProperty *prs; - JSProperty *pr; - JSValue brand; - JSAtom brand_atom; - - if (unlikely(JS_VALUE_GET_TAG(home_obj) != JS_TAG_OBJECT)) { - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } - p = JS_VALUE_GET_OBJ(home_obj); - prs = find_own_property(&pr, p, JS_ATOM_Private_brand); - if (!prs) { - /* if the brand is not present, add it */ - brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE); - if (JS_IsException(brand)) - return -1; - pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E); - if (!pr) { - JS_FreeValue(ctx, brand); - return -1; - } - pr->u.value = js_dup(brand); - } else { - brand = js_dup(pr->u.value); - } - brand_atom = js_symbol_to_atom(ctx, brand); - - if (JS_IsObject(obj)) { - p1 = JS_VALUE_GET_OBJ(obj); - prs = find_own_property(&pr, p1, brand_atom); - if (unlikely(prs)) { - JS_FreeAtom(ctx, brand_atom); - JS_ThrowTypeError(ctx, "private method is already present"); - return -1; - } - pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E); - JS_FreeAtom(ctx, brand_atom); - if (!pr) - return -1; - pr->u.value = JS_UNDEFINED; - } else { - JS_FreeAtom(ctx, brand_atom); - } - - return 0; -} - -/* return a boolean telling if the brand of the home object of 'func' - is present on 'obj' or -1 in case of exception */ -static int JS_CheckBrand(JSContext *ctx, JSValue obj, JSValue func) -{ - JSObject *p, *p1, *home_obj; - JSShapeProperty *prs; - JSProperty *pr; - JSValue brand; - - /* get the home object of 'func' */ - if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) - goto not_obj; - p1 = JS_VALUE_GET_OBJ(func); - if (!js_class_has_bytecode(p1->class_id)) - goto not_obj; - home_obj = p1->u.func.home_object; - if (!home_obj) - goto not_obj; - prs = find_own_property(&pr, home_obj, JS_ATOM_Private_brand); - if (!prs) { - JS_ThrowTypeError(ctx, "expecting private field"); - return -1; - } - brand = pr->u.value; - /* safety check */ - if (unlikely(JS_VALUE_GET_TAG(brand) != JS_TAG_SYMBOL)) - goto not_obj; - - /* get the brand array of 'obj' */ - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { - not_obj: - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } - p = JS_VALUE_GET_OBJ(obj); - prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, brand)); - return (prs != NULL); -} - -static uint32_t js_string_obj_get_length(JSContext *ctx, JSValueConst obj) -{ - JSObject *p; - JSString *p1; - uint32_t len = 0; - - /* This is a class exotic method: obj class_id is JS_CLASS_STRING */ - p = JS_VALUE_GET_OBJ(obj); - if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) { - p1 = JS_VALUE_GET_STRING(p->u.object_data); - len = p1->len; - } - return len; -} - -static int num_keys_cmp(const void *p1, const void *p2, void *opaque) -{ - JSContext *ctx = opaque; - JSAtom atom1 = ((const JSPropertyEnum *)p1)->atom; - JSAtom atom2 = ((const JSPropertyEnum *)p2)->atom; - uint32_t v1, v2; - bool atom1_is_integer, atom2_is_integer; - - atom1_is_integer = JS_AtomIsArrayIndex(ctx, &v1, atom1); - atom2_is_integer = JS_AtomIsArrayIndex(ctx, &v2, atom2); - assert(atom1_is_integer && atom2_is_integer); - if (v1 < v2) - return -1; - else if (v1 == v2) - return 0; - else - return 1; -} - -static void js_free_prop_enum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len) -{ - uint32_t i; - if (tab) { - for(i = 0; i < len; i++) - JS_FreeAtom(ctx, tab[i].atom); - js_free(ctx, tab); - } -} - -/* return < 0 in case if exception, 0 if OK. ptab and its atoms must - be freed by the user. */ -static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx, - JSPropertyEnum **ptab, - uint32_t *plen, - JSObject *p, int flags) -{ - int i, j; - JSShape *sh; - JSShapeProperty *prs; - JSPropertyEnum *tab_atom, *tab_exotic; - JSAtom atom; - uint32_t num_keys_count, str_keys_count, sym_keys_count, atom_count; - uint32_t num_index, str_index, sym_index, exotic_count, exotic_keys_count; - bool is_enumerable, num_sorted; - uint32_t num_key; - JSAtomKindEnum kind; - - /* clear pointer for consistency in case of failure */ - *ptab = NULL; - *plen = 0; - - /* compute the number of returned properties */ - num_keys_count = 0; - str_keys_count = 0; - sym_keys_count = 0; - exotic_keys_count = 0; - exotic_count = 0; - tab_exotic = NULL; - sh = p->shape; - for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { - atom = prs->atom; - if (atom != JS_ATOM_NULL) { - is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); - kind = JS_AtomGetKind(ctx, atom); - if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && - ((flags >> kind) & 1) != 0) { - /* need to raise an exception in case of the module - name space (implicit GetOwnProperty) */ - if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) && - (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY))) { - JSVarRef *var_ref = p->prop[i].u.var_ref; - if (unlikely(JS_IsUninitialized(*var_ref->pvalue))) { - JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return -1; - } - } - if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { - num_keys_count++; - } else if (kind == JS_ATOM_KIND_STRING) { - str_keys_count++; - } else { - sym_keys_count++; - } - } - } - } - - if (p->is_exotic) { - if (p->fast_array) { - if (flags & JS_GPN_STRING_MASK) { - num_keys_count += p->u.array.count; - } - } else if (p->class_id == JS_CLASS_STRING) { - if (flags & JS_GPN_STRING_MASK) { - num_keys_count += js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); - } - } else { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em && em->get_own_property_names) { - if (em->get_own_property_names(ctx, &tab_exotic, &exotic_count, - JS_MKPTR(JS_TAG_OBJECT, p))) - return -1; - for(i = 0; i < exotic_count; i++) { - atom = tab_exotic[i].atom; - kind = JS_AtomGetKind(ctx, atom); - if (((flags >> kind) & 1) != 0) { - is_enumerable = false; - if (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY)) { - JSPropertyDescriptor desc; - int res; - /* set the "is_enumerable" field if necessary */ - res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom); - if (res < 0) { - js_free_prop_enum(ctx, tab_exotic, exotic_count); - return -1; - } - if (res) { - is_enumerable = - ((desc.flags & JS_PROP_ENUMERABLE) != 0); - js_free_desc(ctx, &desc); - } - tab_exotic[i].is_enumerable = is_enumerable; - } - if (!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) { - exotic_keys_count++; - } - } - } - } - } - } - - /* fill them */ - - atom_count = num_keys_count + str_keys_count + sym_keys_count + exotic_keys_count; - /* avoid allocating 0 bytes */ - tab_atom = js_malloc(ctx, sizeof(tab_atom[0]) * max_int(atom_count, 1)); - if (!tab_atom) { - js_free_prop_enum(ctx, tab_exotic, exotic_count); - return -1; - } - - num_index = 0; - str_index = num_keys_count; - sym_index = str_index + str_keys_count; - - num_sorted = true; - sh = p->shape; - for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { - atom = prs->atom; - if (atom != JS_ATOM_NULL) { - is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); - kind = JS_AtomGetKind(ctx, atom); - if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && - ((flags >> kind) & 1) != 0) { - if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { - j = num_index++; - num_sorted = false; - } else if (kind == JS_ATOM_KIND_STRING) { - j = str_index++; - } else { - j = sym_index++; - } - tab_atom[j].atom = JS_DupAtom(ctx, atom); - tab_atom[j].is_enumerable = is_enumerable; - } - } - } - - if (p->is_exotic) { - int len; - if (p->fast_array) { - if (flags & JS_GPN_STRING_MASK) { - len = p->u.array.count; - goto add_array_keys; - } - } else if (p->class_id == JS_CLASS_STRING) { - if (flags & JS_GPN_STRING_MASK) { - len = js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); - add_array_keys: - for(i = 0; i < len; i++) { - tab_atom[num_index].atom = __JS_AtomFromUInt32(i); - if (tab_atom[num_index].atom == JS_ATOM_NULL) { - js_free_prop_enum(ctx, tab_atom, num_index); - return -1; - } - tab_atom[num_index].is_enumerable = true; - num_index++; - } - } - } else { - /* Note: exotic keys are not reordered and comes after the object own properties. */ - for(i = 0; i < exotic_count; i++) { - atom = tab_exotic[i].atom; - is_enumerable = tab_exotic[i].is_enumerable; - kind = JS_AtomGetKind(ctx, atom); - if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && - ((flags >> kind) & 1) != 0) { - tab_atom[sym_index].atom = atom; - tab_atom[sym_index].is_enumerable = is_enumerable; - sym_index++; - } else { - JS_FreeAtom(ctx, atom); - } - } - js_free(ctx, tab_exotic); - } - } - - assert(num_index == num_keys_count); - assert(str_index == num_keys_count + str_keys_count); - assert(sym_index == atom_count); - - if (num_keys_count != 0 && !num_sorted) { - rqsort(tab_atom, num_keys_count, sizeof(tab_atom[0]), num_keys_cmp, - ctx); - } - *ptab = tab_atom; - *plen = atom_count; - return 0; -} - -int JS_GetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, - uint32_t *plen, JSValueConst obj, int flags) -{ - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } - return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen, - JS_VALUE_GET_OBJ(obj), flags); -} - -/* Return -1 if exception, - false if the property does not exist, true if it exists. If true is - returned, the property descriptor 'desc' is filled present. */ -static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, - JSObject *p, JSAtom prop) -{ - JSShapeProperty *prs; - JSProperty *pr; - -retry: - prs = find_own_property(&pr, p, prop); - if (prs) { - if (desc) { - desc->flags = prs->flags & JS_PROP_C_W_E; - desc->getter = JS_UNDEFINED; - desc->setter = JS_UNDEFINED; - desc->value = JS_UNDEFINED; - if (unlikely(prs->flags & JS_PROP_TMASK)) { - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - desc->flags |= JS_PROP_GETSET; - if (pr->u.getset.getter) - desc->getter = js_dup(JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); - if (pr->u.getset.setter) - desc->setter = js_dup(JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - JSValue val = *pr->u.var_ref->pvalue; - if (unlikely(JS_IsUninitialized(val))) { - JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return -1; - } - desc->value = js_dup(val); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - /* Instantiate property and retry */ - if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) - return -1; - goto retry; - } - } else { - desc->value = js_dup(pr->u.value); - } - } else { - /* for consistency, send the exception even if desc is NULL */ - if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF)) { - if (unlikely(JS_IsUninitialized(*pr->u.var_ref->pvalue))) { - JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return -1; - } - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - /* nothing to do: delay instantiation until actual value and/or attributes are read */ - } - } - return true; - } - if (p->is_exotic) { - if (p->fast_array) { - /* specific case for fast arrays */ - if (__JS_AtomIsTaggedInt(prop)) { - uint32_t idx; - idx = __JS_AtomToUInt32(prop); - if (idx < p->u.array.count) { - if (desc) { - desc->flags = JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | - JS_PROP_CONFIGURABLE; - desc->getter = JS_UNDEFINED; - desc->setter = JS_UNDEFINED; - desc->value = JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); - } - return true; - } - } - } else { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em && em->get_own_property) { - return em->get_own_property(ctx, desc, - JS_MKPTR(JS_TAG_OBJECT, p), prop); - } - } - } - return false; -} - -int JS_GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, - JSValueConst obj, JSAtom prop) -{ - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } - return JS_GetOwnPropertyInternal(ctx, desc, JS_VALUE_GET_OBJ(obj), prop); -} - -void JS_FreePropertyEnum(JSContext *ctx, JSPropertyEnum *tab, - uint32_t len) -{ - js_free_prop_enum(ctx, tab, len); -} - -/* return -1 if exception (Proxy object only) or true/false */ -int JS_IsExtensible(JSContext *ctx, JSValueConst obj) -{ - JSObject *p; - - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) - return false; - p = JS_VALUE_GET_OBJ(obj); - if (unlikely(p->class_id == JS_CLASS_PROXY)) - return js_proxy_isExtensible(ctx, obj); - else - return p->extensible; -} - -/* return -1 if exception (Proxy object only) or true/false */ -int JS_PreventExtensions(JSContext *ctx, JSValueConst obj) -{ - JSObject *p; - - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) - return false; - p = JS_VALUE_GET_OBJ(obj); - if (unlikely(p->class_id == JS_CLASS_PROXY)) - return js_proxy_preventExtensions(ctx, obj); - p->extensible = false; - return true; -} - -/* return -1 if exception otherwise true or false */ -int JS_HasProperty(JSContext *ctx, JSValueConst obj, JSAtom prop) -{ - JSObject *p; - int ret; - JSValue obj1; - - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) - return false; - p = JS_VALUE_GET_OBJ(obj); - for(;;) { - if (p->is_exotic) { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em && em->has_property) { - /* has_property can free the prototype */ - obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); - ret = em->has_property(ctx, obj1, prop); - JS_FreeValue(ctx, obj1); - return ret; - } - } - /* JS_GetOwnPropertyInternal can free the prototype */ - js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); - ret = JS_GetOwnPropertyInternal(ctx, NULL, p, prop); - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); - if (ret != 0) - return ret; - if (is_typed_array(p->class_id)) { - ret = JS_AtomIsNumericIndex(ctx, prop); - if (ret != 0) { - if (ret < 0) - return -1; - return false; - } - } - p = p->shape->proto; - if (!p) - break; - } - return false; -} - -/* val must be a symbol */ -static JSAtom js_symbol_to_atom(JSContext *ctx, JSValueConst val) -{ - JSAtomStruct *p = JS_VALUE_GET_PTR(val); - return js_get_atom_index(ctx->rt, p); -} - -/* return JS_ATOM_NULL in case of exception */ -static JSAtom JS_ValueToAtomInternal(JSContext *ctx, JSValueConst val, - int flags) -{ - JSAtom atom; - uint32_t tag; - tag = JS_VALUE_GET_TAG(val); - if (tag == JS_TAG_INT && - (uint32_t)JS_VALUE_GET_INT(val) <= JS_ATOM_MAX_INT) { - /* fast path for integer values */ - atom = __JS_AtomFromUInt32(JS_VALUE_GET_INT(val)); - } else if (tag == JS_TAG_SYMBOL) { - JSAtomStruct *p = JS_VALUE_GET_PTR(val); - atom = JS_DupAtom(ctx, js_get_atom_index(ctx->rt, p)); - } else { - JSValue str; - str = JS_ToPropertyKeyInternal(ctx, val, flags); - if (JS_IsException(str)) - return JS_ATOM_NULL; - if (JS_VALUE_GET_TAG(str) == JS_TAG_SYMBOL) { - atom = js_symbol_to_atom(ctx, str); - } else { - atom = JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(str)); - } - } - return atom; -} - -JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val) -{ - return JS_ValueToAtomInternal(ctx, val, /*flags*/0); -} - -static bool js_get_fast_array_element(JSContext *ctx, JSObject *p, - uint32_t idx, JSValue *pval) -{ - switch(p->class_id) { - case JS_CLASS_ARRAY: - case JS_CLASS_ARGUMENTS: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_dup(p->u.array.u.values[idx]); - return true; - case JS_CLASS_INT8_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_int32(p->u.array.u.int8_ptr[idx]); - return true; - case JS_CLASS_UINT8C_ARRAY: - case JS_CLASS_UINT8_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_int32(p->u.array.u.uint8_ptr[idx]); - return true; - case JS_CLASS_INT16_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_int32(p->u.array.u.int16_ptr[idx]); - return true; - case JS_CLASS_UINT16_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_int32(p->u.array.u.uint16_ptr[idx]); - return true; - case JS_CLASS_INT32_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_int32(p->u.array.u.int32_ptr[idx]); - return true; - case JS_CLASS_UINT32_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_uint32(p->u.array.u.uint32_ptr[idx]); - return true; - case JS_CLASS_BIG_INT64_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = JS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]); - return true; - case JS_CLASS_BIG_UINT64_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = JS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]); - return true; - case JS_CLASS_FLOAT16_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_float64(fromfp16(p->u.array.u.fp16_ptr[idx])); - return true; - case JS_CLASS_FLOAT32_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_float64(p->u.array.u.float_ptr[idx]); - return true; - case JS_CLASS_FLOAT64_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_float64(p->u.array.u.double_ptr[idx]); - return true; - default: - return false; - } -} - -static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj, - JSValue prop) -{ - JSAtom atom; - JSValue ret; - uint32_t tag; - - tag = JS_VALUE_GET_TAG(this_obj); - if (likely(tag == JS_TAG_OBJECT)) { - if (JS_VALUE_GET_TAG(prop) == JS_TAG_INT) { - JSObject *p = JS_VALUE_GET_OBJ(this_obj); - uint32_t idx = JS_VALUE_GET_INT(prop); - JSValue val; - /* fast path for array and typed array access */ - if (js_get_fast_array_element(ctx, p, idx, &val)) - return val; - } - } else if (unlikely(tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED)) { - // per spec: not allowed to call ToPropertyKey before ToObject - // so we must ensure to not invoke JS anything that's observable - // from JS code - atom = JS_ValueToAtomInternal(ctx, prop, JS_TO_STRING_NO_SIDE_EFFECTS); - JS_FreeValue(ctx, prop); - if (unlikely(atom == JS_ATOM_NULL)) - return JS_EXCEPTION; - if (tag == JS_TAG_NULL) { - JS_ThrowTypeErrorAtom(ctx, "Cannot read properties of null (reading '%s')", atom); - } else { - JS_ThrowTypeErrorAtom(ctx, "Cannot read properties of undefined (reading '%s')", atom); - } - JS_FreeAtom(ctx, atom); - return JS_EXCEPTION; - } - atom = JS_ValueToAtom(ctx, prop); - JS_FreeValue(ctx, prop); - if (unlikely(atom == JS_ATOM_NULL)) - return JS_EXCEPTION; - ret = JS_GetProperty(ctx, this_obj, atom); - JS_FreeAtom(ctx, atom); - return ret; -} - -JSValue JS_GetPropertyUint32(JSContext *ctx, JSValueConst this_obj, - uint32_t idx) -{ - return JS_GetPropertyInt64(ctx, this_obj, idx); -} - -/* Check if an object has a generalized numeric property. Return value: - -1 for exception, *pval set to JS_EXCEPTION - true if property exists, stored into *pval, - false if property does not exist. *pval set to JS_UNDEFINED. - */ -static int JS_TryGetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, JSValue *pval) -{ - JSValue val; - JSAtom prop; - int present; - - if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT && - (uint64_t)idx <= INT32_MAX)) { - /* fast path for array and typed array access */ - JSObject *p = JS_VALUE_GET_OBJ(obj); - if (js_get_fast_array_element(ctx, p, idx, pval)) - return true; - } - val = JS_EXCEPTION; - present = -1; - prop = JS_NewAtomInt64(ctx, idx); - if (likely(prop != JS_ATOM_NULL)) { - present = JS_HasProperty(ctx, obj, prop); - if (present > 0) { - val = JS_GetProperty(ctx, obj, prop); - if (unlikely(JS_IsException(val))) - present = -1; - } else if (present == false) { - val = JS_UNDEFINED; - } - JS_FreeAtom(ctx, prop); - } - *pval = val; - return present; -} - -JSValue JS_GetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx) -{ - JSAtom prop; - JSValue val; - - if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT && - (uint64_t)idx <= INT32_MAX)) { - /* fast path for array and typed array access */ - JSObject *p = JS_VALUE_GET_OBJ(obj); - if (js_get_fast_array_element(ctx, p, idx, &val)) - return val; - } - prop = JS_NewAtomInt64(ctx, idx); - if (prop == JS_ATOM_NULL) - return JS_EXCEPTION; - - val = JS_GetProperty(ctx, obj, prop); - JS_FreeAtom(ctx, prop); - return val; -} - -/* `prop` may be pure ASCII or UTF-8 encoded */ -JSValue JS_GetPropertyStr(JSContext *ctx, JSValueConst this_obj, - const char *prop) -{ - JSAtom atom; - JSValue ret; - atom = JS_NewAtom(ctx, prop); - if (atom == JS_ATOM_NULL) - return JS_EXCEPTION; - ret = JS_GetProperty(ctx, this_obj, atom); - JS_FreeAtom(ctx, atom); - return ret; -} - -/* Note: the property value is not initialized. Return NULL if memory - error. */ -static JSProperty *add_property(JSContext *ctx, - JSObject *p, JSAtom prop, int prop_flags) -{ - JSShape *sh, *new_sh; - - sh = p->shape; - if (sh->is_hashed) { - /* try to find an existing shape */ - new_sh = find_hashed_shape_prop(ctx->rt, sh, prop, prop_flags); - if (new_sh) { - /* matching shape found: use it */ - /* the property array may need to be resized */ - if (new_sh->prop_size != sh->prop_size) { - JSProperty *new_prop; - new_prop = js_realloc(ctx, p->prop, sizeof(p->prop[0]) * - new_sh->prop_size); - if (!new_prop) - return NULL; - p->prop = new_prop; - } - p->shape = js_dup_shape(new_sh); - js_free_shape(ctx->rt, sh); - return &p->prop[new_sh->prop_count - 1]; - } else if (sh->header.ref_count != 1) { - /* if the shape is shared, clone it */ - new_sh = js_clone_shape(ctx, sh); - if (!new_sh) - return NULL; - /* hash the cloned shape */ - new_sh->is_hashed = true; - js_shape_hash_link(ctx->rt, new_sh); - js_free_shape(ctx->rt, p->shape); - p->shape = new_sh; - } - } - assert(p->shape->header.ref_count == 1); - if (add_shape_property(ctx, &p->shape, p, prop, prop_flags)) - return NULL; - return &p->prop[p->shape->prop_count - 1]; -} - -/* can be called on Array or Arguments objects. return < 0 if - memory alloc error. */ -static no_inline __exception int convert_fast_array_to_array(JSContext *ctx, - JSObject *p) -{ - JSProperty *pr; - JSShape *sh; - JSValue *tab; - uint32_t i, len, new_count; - - if (js_shape_prepare_update(ctx, p, NULL)) - return -1; - len = p->u.array.count; - /* resize the properties once to simplify the error handling */ - sh = p->shape; - new_count = sh->prop_count + len; - if (new_count > sh->prop_size) { - if (resize_properties(ctx, &p->shape, p, new_count)) - return -1; - } - - tab = p->u.array.u.values; - for(i = 0; i < len; i++) { - /* add_property cannot fail here but - __JS_AtomFromUInt32(i) fails for i > INT32_MAX */ - pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E); - pr->u.value = *tab++; - } - js_free(ctx, p->u.array.u.values); - p->u.array.count = 0; - p->u.array.u.values = NULL; /* fail safe */ - p->u.array.u1.size = 0; - p->fast_array = 0; - return 0; -} - -static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) -{ - JSShape *sh; - JSShapeProperty *pr, *lpr, *prop; - JSProperty *pr1; - uint32_t lpr_idx; - intptr_t h, h1; - - redo: - sh = p->shape; - h1 = atom & sh->prop_hash_mask; - h = prop_hash_end(sh)[-h1 - 1]; - prop = get_shape_prop(sh); - lpr = NULL; - lpr_idx = 0; /* prevent warning */ - while (h != 0) { - pr = &prop[h - 1]; - if (likely(pr->atom == atom)) { - /* found ! */ - if (!(pr->flags & JS_PROP_CONFIGURABLE)) - return false; - /* realloc the shape if needed */ - if (lpr) - lpr_idx = lpr - get_shape_prop(sh); - if (js_shape_prepare_update(ctx, p, &pr)) - return -1; - sh = p->shape; - /* remove property */ - if (lpr) { - lpr = get_shape_prop(sh) + lpr_idx; - lpr->hash_next = pr->hash_next; - } else { - prop_hash_end(sh)[-h1 - 1] = pr->hash_next; - } - sh->deleted_prop_count++; - /* free the entry */ - pr1 = &p->prop[h - 1]; - free_property(ctx->rt, pr1, pr->flags); - JS_FreeAtom(ctx, pr->atom); - /* put default values */ - pr->flags = 0; - pr->atom = JS_ATOM_NULL; - pr1->u.value = JS_UNDEFINED; - - /* compact the properties if too many deleted properties */ - if (sh->deleted_prop_count >= 8 && - sh->deleted_prop_count >= ((unsigned)sh->prop_count / 2)) { - compact_properties(ctx, p); - } - return true; - } - lpr = pr; - h = pr->hash_next; - } - - if (p->is_exotic) { - if (p->fast_array) { - uint32_t idx; - if (JS_AtomIsArrayIndex(ctx, &idx, atom) && - idx < p->u.array.count) { - if (p->class_id == JS_CLASS_ARRAY || - p->class_id == JS_CLASS_ARGUMENTS) { - /* Special case deleting the last element of a fast Array */ - if (idx == p->u.array.count - 1) { - JS_FreeValue(ctx, p->u.array.u.values[idx]); - p->u.array.count = idx; - return true; - } - if (convert_fast_array_to_array(ctx, p)) - return -1; - goto redo; - } else { - return false; - } - } - } else { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em && em->delete_property) { - return em->delete_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), atom); - } - } - } - /* not found */ - return true; -} - -static int call_setter(JSContext *ctx, JSObject *setter, - JSValueConst this_obj, JSValue val, int flags) -{ - JSValue ret, func; - if (likely(setter)) { - func = JS_MKPTR(JS_TAG_OBJECT, setter); - /* Note: the field could be removed in the setter */ - func = js_dup(func); - ret = JS_CallFree(ctx, func, this_obj, 1, vc(&val)); - JS_FreeValue(ctx, val); - if (JS_IsException(ret)) - return -1; - JS_FreeValue(ctx, ret); - return true; - } else { - JS_FreeValue(ctx, val); - if ((flags & JS_PROP_THROW) || - ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { - JS_ThrowTypeError(ctx, "no setter for property"); - return -1; - } - return false; - } -} - -/* set the array length and remove the array elements if necessary. */ -static int set_array_length(JSContext *ctx, JSObject *p, JSValue val, - int flags) -{ - uint32_t len, idx, cur_len; - int i, ret; - - /* Note: this call can reallocate the properties of 'p' */ - ret = JS_ToArrayLengthFree(ctx, &len, val, false); - if (ret) - return -1; - /* JS_ToArrayLengthFree() must be done before the read-only test */ - if (unlikely(!(p->shape->prop[0].flags & JS_PROP_WRITABLE))) - return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); - - if (likely(p->fast_array)) { - uint32_t old_len = p->u.array.count; - if (len < old_len) { - for(i = len; i < old_len; i++) { - JS_FreeValue(ctx, p->u.array.u.values[i]); - } - p->u.array.count = len; - } - p->prop[0].u.value = js_uint32(len); - } else { - /* Note: length is always a uint32 because the object is an - array */ - JS_ToUint32(ctx, &cur_len, p->prop[0].u.value); - if (len < cur_len) { - uint32_t d; - JSShape *sh; - JSShapeProperty *pr; - - d = cur_len - len; - sh = p->shape; - if (d <= sh->prop_count) { - JSAtom atom; - - /* faster to iterate */ - while (cur_len > len) { - atom = JS_NewAtomUInt32(ctx, cur_len - 1); - ret = delete_property(ctx, p, atom); - JS_FreeAtom(ctx, atom); - if (unlikely(!ret)) { - /* unlikely case: property is not - configurable */ - break; - } - cur_len--; - } - } else { - /* faster to iterate thru all the properties. Need two - passes in case one of the property is not - configurable */ - cur_len = len; - for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; - i++, pr++) { - if (pr->atom != JS_ATOM_NULL && - JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { - if (idx >= cur_len && - !(pr->flags & JS_PROP_CONFIGURABLE)) { - cur_len = idx + 1; - } - } - } - - for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; - i++, pr++) { - if (pr->atom != JS_ATOM_NULL && - JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { - if (idx >= cur_len) { - /* remove the property */ - delete_property(ctx, p, pr->atom); - /* WARNING: the shape may have been modified */ - sh = p->shape; - pr = get_shape_prop(sh) + i; - } - } - } - } - } else { - cur_len = len; - } - set_value(ctx, &p->prop[0].u.value, js_uint32(cur_len)); - if (unlikely(cur_len > len)) { - return JS_ThrowTypeErrorOrFalse(ctx, flags, "not configurable"); - } - } - return true; -} - -/* return -1 if exception */ -static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len) -{ - uint32_t new_size; - size_t slack; - JSValue *new_array_prop; - /* XXX: potential arithmetic overflow */ - new_size = max_int(new_len, p->u.array.u1.size * 3 / 2); - new_array_prop = js_realloc2(ctx, p->u.array.u.values, sizeof(JSValue) * new_size, &slack); - if (!new_array_prop) - return -1; - new_size += slack / sizeof(*new_array_prop); - p->u.array.u.values = new_array_prop; - p->u.array.u1.size = new_size; - return 0; -} - -/* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array = - true and p->extensible = true */ -static int add_fast_array_element(JSContext *ctx, JSObject *p, - JSValue val, int flags) -{ - uint32_t new_len, array_len; - /* extend the array by one */ - /* XXX: convert to slow array if new_len > 2^31-1 elements */ - new_len = p->u.array.count + 1; - /* update the length if necessary. We assume that if the length is - not an integer, then if it >= 2^31. */ - if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) == JS_TAG_INT)) { - array_len = JS_VALUE_GET_INT(p->prop[0].u.value); - if (new_len > array_len) { - if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) { - JS_FreeValue(ctx, val); - return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); - } - p->prop[0].u.value = js_int32(new_len); - } - } - if (unlikely(new_len > p->u.array.u1.size)) { - if (expand_fast_array(ctx, p, new_len)) { - JS_FreeValue(ctx, val); - return -1; - } - } - p->u.array.u.values[new_len - 1] = val; - p->u.array.count = new_len; - return true; -} - -static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc) -{ - JS_FreeValue(ctx, desc->getter); - JS_FreeValue(ctx, desc->setter); - JS_FreeValue(ctx, desc->value); -} - -/* return -1 in case of exception or true or false. Warning: 'val' is - freed by the function. 'flags' is a bitmask of JS_PROP_NO_ADD, - JS_PROP_THROW or JS_PROP_THROW_STRICT. If JS_PROP_NO_ADD is set, - the new property is not added and an error is raised. - 'obj' must be an object when obj != this_obj. - */ -static int JS_SetPropertyInternal2(JSContext *ctx, JSValueConst obj, JSAtom prop, - JSValue val, JSValueConst this_obj, int flags) -{ - JSObject *p, *p1; - JSShapeProperty *prs; - JSProperty *pr; - JSPropertyDescriptor desc; - int ret; - - switch(JS_VALUE_GET_TAG(this_obj)) { - case JS_TAG_NULL: - JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop); - goto fail; - case JS_TAG_UNDEFINED: - JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of undefined", prop); - goto fail; - case JS_TAG_OBJECT: - p = JS_VALUE_GET_OBJ(this_obj); - p1 = JS_VALUE_GET_OBJ(obj); - if (p == p1) - break; - goto retry2; - default: - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - obj = JS_GetPrototypePrimitive(ctx, obj); - p = NULL; - p1 = JS_VALUE_GET_OBJ(obj); - goto prototype_lookup; - } - -retry: - prs = find_own_property(&pr, p1, prop); - if (prs) { - if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | - JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { - /* fast case */ - set_value(ctx, &pr->u.value, val); - return true; - } else if (prs->flags & JS_PROP_LENGTH) { - assert(p->class_id == JS_CLASS_ARRAY); - assert(prop == JS_ATOM_length); - return set_array_length(ctx, p, val, flags); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - /* JS_PROP_WRITABLE is always true for variable - references, but they are write protected in module name - spaces. */ - if (p->class_id == JS_CLASS_MODULE_NS) - goto read_only_prop; - set_value(ctx, pr->u.var_ref->pvalue, val); - return true; - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - /* Instantiate property and retry (potentially useless) */ - if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) - goto fail; - goto retry; - } else { - goto read_only_prop; - } - } - - for(;;) { - if (p1->is_exotic) { - if (p1->fast_array) { - if (__JS_AtomIsTaggedInt(prop)) { - uint32_t idx = __JS_AtomToUInt32(prop); - if (idx < p1->u.array.count) { - if (unlikely(p == p1)) - return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), val, flags); - else - break; - } else if (is_typed_array(p1->class_id)) { - goto typed_array_oob; - } - } else if (is_typed_array(p1->class_id)) { - ret = JS_AtomIsNumericIndex(ctx, prop); - if (ret != 0) { - if (ret < 0) - goto fail; - typed_array_oob: - // per spec: evaluate value for side effects - if (p1->class_id == JS_CLASS_BIG_INT64_ARRAY || - p1->class_id == JS_CLASS_BIG_UINT64_ARRAY) { - int64_t v; - if (JS_ToBigInt64Free(ctx, &v, val)) - return -1; - } else { - val = JS_ToNumberFree(ctx, val); - JS_FreeValue(ctx, val); - if (JS_IsException(val)) - return -1; - } - return true; - } - } - } else { - const JSClassExoticMethods *em = ctx->rt->class_array[p1->class_id].exotic; - if (em) { - JSValue obj1; - if (em->set_property) { - /* set_property can free the prototype */ - obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); - ret = em->set_property(ctx, obj1, prop, - val, this_obj, flags); - JS_FreeValue(ctx, obj1); - JS_FreeValue(ctx, val); - return ret; - } - if (em->get_own_property) { - /* get_own_property can free the prototype */ - obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); - ret = em->get_own_property(ctx, &desc, - obj1, prop); - JS_FreeValue(ctx, obj1); - if (ret < 0) - goto fail; - if (ret) { - if (desc.flags & JS_PROP_GETSET) { - JSObject *setter; - if (JS_IsUndefined(desc.setter)) - setter = NULL; - else - setter = JS_VALUE_GET_OBJ(desc.setter); - ret = call_setter(ctx, setter, this_obj, val, flags); - JS_FreeValue(ctx, desc.getter); - JS_FreeValue(ctx, desc.setter); - return ret; - } else { - JS_FreeValue(ctx, desc.value); - if (!(desc.flags & JS_PROP_WRITABLE)) - goto read_only_prop; - if (likely(p == p1)) { - ret = JS_DefineProperty(ctx, this_obj, prop, val, - JS_UNDEFINED, JS_UNDEFINED, - JS_PROP_HAS_VALUE); - JS_FreeValue(ctx, val); - return ret; - } else { - break; - } - } - } - } - } - } - } - p1 = p1->shape->proto; - prototype_lookup: - if (!p1) - break; - - retry2: - prs = find_own_property(&pr, p1, prop); - if (prs) { - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - /* Instantiate property and retry (potentially useless) */ - if (JS_AutoInitProperty(ctx, p1, prop, pr, prs)) - return -1; - goto retry2; - } else if (!(prs->flags & JS_PROP_WRITABLE)) { - goto read_only_prop; - } else { - break; - } - } - } - - if (unlikely(flags & JS_PROP_NO_ADD)) { - JS_ThrowReferenceErrorNotDefined(ctx, prop); - goto fail; - } - - if (unlikely(!p)) { - ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "not an object"); - goto done; - } - - if (unlikely(!p->extensible)) { - ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); - goto done; - } - - if (p == JS_VALUE_GET_OBJ(obj)) { - if (p->is_exotic) { - if (p->class_id == JS_CLASS_ARRAY && p->fast_array && - __JS_AtomIsTaggedInt(prop)) { - uint32_t idx = __JS_AtomToUInt32(prop); - if (idx == p->u.array.count) { - /* fast case */ - return add_fast_array_element(ctx, p, val, flags); - } - } - goto generic_create_prop; - } else { - pr = add_property(ctx, p, prop, JS_PROP_C_W_E); - if (!pr) - goto fail; - pr->u.value = val; - return true; - } - } - - // TODO(bnoordhuis) return JSProperty slot and update in place - // when plain property (not is_exotic/setter/etc.) to avoid - // calling find_own_property() thrice? - ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); - if (ret < 0) - goto fail; - - if (ret) { - JS_FreeValue(ctx, desc.value); - if (desc.flags & JS_PROP_GETSET) { - JS_FreeValue(ctx, desc.getter); - JS_FreeValue(ctx, desc.setter); - ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "setter is forbidden"); - goto done; - } else if (!(desc.flags & JS_PROP_WRITABLE) || - p->class_id == JS_CLASS_MODULE_NS) { - read_only_prop: - ret = JS_ThrowTypeErrorReadOnly(ctx, flags, prop); - goto done; - } - ret = JS_DefineProperty(ctx, this_obj, prop, val, - JS_UNDEFINED, JS_UNDEFINED, - JS_PROP_HAS_VALUE); - } else { - generic_create_prop: - ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED, - flags | - JS_PROP_HAS_VALUE | - JS_PROP_HAS_ENUMERABLE | - JS_PROP_HAS_WRITABLE | - JS_PROP_HAS_CONFIGURABLE | - JS_PROP_C_W_E); - } - -done: - JS_FreeValue(ctx, val); - return ret; -fail: - JS_FreeValue(ctx, val); - return -1; -} - -static int JS_SetPropertyInternal(JSContext *ctx, JSValueConst obj, JSAtom prop, - JSValue val, int flags) -{ - return JS_SetPropertyInternal2(ctx, obj, prop, val, obj, flags); -} - -int JS_SetProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop, JSValue val) -{ - return JS_SetPropertyInternal(ctx, this_obj, prop, val, JS_PROP_THROW); -} - -/* flags can be JS_PROP_THROW or JS_PROP_THROW_STRICT */ -static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, - JSValue prop, JSValue val, int flags) -{ - if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && - JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { - JSObject *p; - uint32_t idx; - double d; - int32_t v; - - /* fast path for array access */ - p = JS_VALUE_GET_OBJ(this_obj); - idx = JS_VALUE_GET_INT(prop); - switch(p->class_id) { - case JS_CLASS_ARRAY: - if (unlikely(idx >= (uint32_t)p->u.array.count)) { - JSObject *p1; - JSShape *sh1; - - /* fast path to add an element to the array */ - if (idx != (uint32_t)p->u.array.count || - !p->fast_array || !p->extensible) - goto slow_path; - /* check if prototype chain has a numeric property */ - p1 = p->shape->proto; - while (p1 != NULL) { - sh1 = p1->shape; - if (p1->class_id == JS_CLASS_ARRAY) { - if (unlikely(!p1->fast_array)) - goto slow_path; - } else if (p1->class_id == JS_CLASS_OBJECT) { - if (unlikely(sh1->has_small_array_index)) - goto slow_path; - } else { - goto slow_path; - } - p1 = sh1->proto; - } - /* add element */ - return add_fast_array_element(ctx, p, val, flags); - } - set_value(ctx, &p->u.array.u.values[idx], val); - break; - case JS_CLASS_ARGUMENTS: - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto slow_path; - set_value(ctx, &p->u.array.u.values[idx], val); - break; - case JS_CLASS_UINT8C_ARRAY: - if (JS_ToUint8ClampFree(ctx, &v, val)) - goto ta_cvt_fail; - /* Note: the conversion can detach the typed array, so the - array bound check must be done after */ - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.uint8_ptr[idx] = v; - break; - case JS_CLASS_INT8_ARRAY: - case JS_CLASS_UINT8_ARRAY: - if (JS_ToInt32Free(ctx, &v, val)) - goto ta_cvt_fail; - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.uint8_ptr[idx] = v; - break; - case JS_CLASS_INT16_ARRAY: - case JS_CLASS_UINT16_ARRAY: - if (JS_ToInt32Free(ctx, &v, val)) - goto ta_cvt_fail; - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.uint16_ptr[idx] = v; - break; - case JS_CLASS_INT32_ARRAY: - case JS_CLASS_UINT32_ARRAY: - if (JS_ToInt32Free(ctx, &v, val)) - goto ta_cvt_fail; - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.uint32_ptr[idx] = v; - break; - case JS_CLASS_BIG_INT64_ARRAY: - case JS_CLASS_BIG_UINT64_ARRAY: - /* XXX: need specific conversion function */ - { - int64_t v; - if (JS_ToBigInt64Free(ctx, &v, val)) - goto ta_cvt_fail; - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.uint64_ptr[idx] = v; - } - break; - case JS_CLASS_FLOAT16_ARRAY: - if (JS_ToFloat64Free(ctx, &d, val)) - goto ta_cvt_fail; - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.fp16_ptr[idx] = tofp16(d); - break; - case JS_CLASS_FLOAT32_ARRAY: - if (JS_ToFloat64Free(ctx, &d, val)) - goto ta_cvt_fail; - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.float_ptr[idx] = d; - break; - case JS_CLASS_FLOAT64_ARRAY: - if (JS_ToFloat64Free(ctx, &d, val)) { - ta_cvt_fail: - if (flags & JS_PROP_REFLECT_DEFINE_PROPERTY) { - JS_FreeValue(ctx, JS_GetException(ctx)); - return false; - } - return -1; - } - if (unlikely(idx >= (uint32_t)p->u.array.count)) { - ta_out_of_bound: - if (typed_array_is_oob(p)) - if (flags & JS_PROP_DEFINE_PROPERTY) - return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound numeric index"); - return true; // per spec: no OOB exception - } - p->u.array.u.double_ptr[idx] = d; - break; - default: - goto slow_path; - } - return true; - } else { - JSAtom atom; - int ret; - slow_path: - atom = JS_ValueToAtom(ctx, prop); - JS_FreeValue(ctx, prop); - if (unlikely(atom == JS_ATOM_NULL)) { - JS_FreeValue(ctx, val); - return -1; - } - ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, flags); - JS_FreeAtom(ctx, atom); - return ret; - } -} - -int JS_SetPropertyUint32(JSContext *ctx, JSValueConst this_obj, - uint32_t idx, JSValue val) -{ - return JS_SetPropertyValue(ctx, this_obj, js_uint32(idx), val, - JS_PROP_THROW); -} - -int JS_SetPropertyInt64(JSContext *ctx, JSValueConst this_obj, - int64_t idx, JSValue val) -{ - JSAtom prop; - int res; - - if ((uint64_t)idx <= INT32_MAX) { - /* fast path for fast arrays */ - return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), val, - JS_PROP_THROW); - } - prop = JS_NewAtomInt64(ctx, idx); - if (prop == JS_ATOM_NULL) { - JS_FreeValue(ctx, val); - return -1; - } - res = JS_SetProperty(ctx, this_obj, prop, val); - JS_FreeAtom(ctx, prop); - return res; -} - -/* `prop` may be pure ASCII or UTF-8 encoded */ -int JS_SetPropertyStr(JSContext *ctx, JSValueConst this_obj, - const char *prop, JSValue val) -{ - JSAtom atom; - int ret; - atom = JS_NewAtom(ctx, prop); - if (atom == JS_ATOM_NULL) { - JS_FreeValue(ctx, val); - return -1; - } - ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, JS_PROP_THROW); - JS_FreeAtom(ctx, atom); - return ret; -} - -/* compute the property flags. For each flag: (JS_PROP_HAS_x forces - it, otherwise def_flags is used) - Note: makes assumption about the bit pattern of the flags -*/ -static int get_prop_flags(int flags, int def_flags) -{ - int mask; - mask = (flags >> JS_PROP_HAS_SHIFT) & JS_PROP_C_W_E; - return (flags & mask) | (def_flags & ~mask); -} - -static int JS_CreateProperty(JSContext *ctx, JSObject *p, - JSAtom prop, JSValueConst val, - JSValueConst getter, JSValueConst setter, - int flags) -{ - JSProperty *pr; - int ret, prop_flags; - - /* add a new property or modify an existing exotic one */ - if (p->is_exotic) { - if (p->class_id == JS_CLASS_ARRAY) { - uint32_t idx, len; - - if (p->fast_array) { - if (__JS_AtomIsTaggedInt(prop)) { - idx = __JS_AtomToUInt32(prop); - if (idx == p->u.array.count) { - if (!p->extensible) - goto not_extensible; - if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) - goto convert_to_array; - prop_flags = get_prop_flags(flags, 0); - if (prop_flags != JS_PROP_C_W_E) - goto convert_to_array; - return add_fast_array_element(ctx, p, - js_dup(val), flags); - } else { - goto convert_to_array; - } - } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { - /* convert the fast array to normal array */ - convert_to_array: - if (convert_fast_array_to_array(ctx, p)) - return -1; - goto generic_array; - } - } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { - JSProperty *plen; - JSShapeProperty *pslen; - generic_array: - /* update the length field */ - plen = &p->prop[0]; - JS_ToUint32(ctx, &len, plen->u.value); - if ((idx + 1) > len) { - pslen = get_shape_prop(p->shape); - if (unlikely(!(pslen->flags & JS_PROP_WRITABLE))) - return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); - /* XXX: should update the length after defining - the property */ - len = idx + 1; - set_value(ctx, &plen->u.value, js_uint32(len)); - } - } - } else if (is_typed_array(p->class_id)) { - ret = JS_AtomIsNumericIndex(ctx, prop); - if (ret != 0) { - if (ret < 0) - return -1; - return JS_ThrowTypeErrorOrFalse(ctx, flags, "cannot create numeric index in typed array"); - } - } else if (!(flags & JS_PROP_NO_EXOTIC)) { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em) { - if (em->define_own_property) { - return em->define_own_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), - prop, val, getter, setter, flags); - } - ret = JS_IsExtensible(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); - if (ret < 0) - return -1; - if (!ret) - goto not_extensible; - } - } - } - - if (!p->extensible) { - not_extensible: - return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); - } - - if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - prop_flags = (flags & (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | - JS_PROP_GETSET; - } else { - prop_flags = flags & JS_PROP_C_W_E; - } - pr = add_property(ctx, p, prop, prop_flags); - if (unlikely(!pr)) - return -1; - if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - pr->u.getset.getter = NULL; - if ((flags & JS_PROP_HAS_GET) && JS_IsFunction(ctx, getter)) { - pr->u.getset.getter = - JS_VALUE_GET_OBJ(js_dup(getter)); - } - pr->u.getset.setter = NULL; - if ((flags & JS_PROP_HAS_SET) && JS_IsFunction(ctx, setter)) { - pr->u.getset.setter = - JS_VALUE_GET_OBJ(js_dup(setter)); - } - } else { - if (flags & JS_PROP_HAS_VALUE) { - pr->u.value = js_dup(val); - } else { - pr->u.value = JS_UNDEFINED; - } - } - return true; -} - -/* return false if not OK */ -static bool check_define_prop_flags(int prop_flags, int flags) -{ - bool has_accessor, is_getset; - - if (!(prop_flags & JS_PROP_CONFIGURABLE)) { - if ((flags & (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) == - (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) { - return false; - } - if ((flags & JS_PROP_HAS_ENUMERABLE) && - (flags & JS_PROP_ENUMERABLE) != (prop_flags & JS_PROP_ENUMERABLE)) - return false; - } - if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | - JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - if (!(prop_flags & JS_PROP_CONFIGURABLE)) { - has_accessor = ((flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) != 0); - is_getset = ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET); - if (has_accessor != is_getset) - return false; - if (!has_accessor && !is_getset && !(prop_flags & JS_PROP_WRITABLE)) { - /* not writable: cannot set the writable bit */ - if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == - (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) - return false; - } - } - } - return true; -} - -/* ensure that the shape can be safely modified */ -static int js_shape_prepare_update(JSContext *ctx, JSObject *p, - JSShapeProperty **pprs) -{ - JSShape *sh; - uint32_t idx = 0; /* prevent warning */ - - sh = p->shape; - if (sh->is_hashed) { - if (sh->header.ref_count != 1) { - if (pprs) - idx = *pprs - get_shape_prop(sh); - /* clone the shape (the resulting one is no longer hashed) */ - sh = js_clone_shape(ctx, sh); - if (!sh) - return -1; - js_free_shape(ctx->rt, p->shape); - p->shape = sh; - if (pprs) - *pprs = get_shape_prop(sh) + idx; - } else { - js_shape_hash_unlink(ctx->rt, sh); - sh->is_hashed = false; - } - } - return 0; -} - -static int js_update_property_flags(JSContext *ctx, JSObject *p, - JSShapeProperty **pprs, int flags) -{ - if (flags != (*pprs)->flags) { - if (js_shape_prepare_update(ctx, p, pprs)) - return -1; - (*pprs)->flags = flags; - } - return 0; -} - -/* allowed flags: - JS_PROP_CONFIGURABLE, JS_PROP_WRITABLE, JS_PROP_ENUMERABLE - JS_PROP_HAS_GET, JS_PROP_HAS_SET, JS_PROP_HAS_VALUE, - JS_PROP_HAS_CONFIGURABLE, JS_PROP_HAS_WRITABLE, JS_PROP_HAS_ENUMERABLE, - JS_PROP_THROW, JS_PROP_NO_EXOTIC. - If JS_PROP_THROW is set, return an exception instead of false. - if JS_PROP_NO_EXOTIC is set, do not call the exotic - define_own_property callback. - return -1 (exception), false or true. -*/ -int JS_DefineProperty(JSContext *ctx, JSValueConst this_obj, - JSAtom prop, JSValueConst val, - JSValueConst getter, JSValueConst setter, int flags) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - int mask, res; - - if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) { - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } - p = JS_VALUE_GET_OBJ(this_obj); - - redo_prop_update: - prs = find_own_property(&pr, p, prop); - if (prs) { - /* the range of the Array length property is always tested before */ - if ((prs->flags & JS_PROP_LENGTH) && (flags & JS_PROP_HAS_VALUE)) { - uint32_t array_length; - if (JS_ToArrayLengthFree(ctx, &array_length, - js_dup(val), false)) { - return -1; - } - /* this code relies on the fact that Uint32 are never allocated */ - val = js_uint32(array_length); - /* prs may have been modified */ - prs = find_own_property(&pr, p, prop); - assert(prs != NULL); - } - /* property already exists */ - if (!check_define_prop_flags(prs->flags, flags)) { - not_configurable: - return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable"); - } - - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - /* Instantiate property and retry */ - if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) - return -1; - goto redo_prop_update; - } - - if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | - JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - JSObject *new_getter, *new_setter; - - if (JS_IsFunction(ctx, getter)) { - new_getter = JS_VALUE_GET_OBJ(getter); - } else { - new_getter = NULL; - } - if (JS_IsFunction(ctx, setter)) { - new_setter = JS_VALUE_GET_OBJ(setter); - } else { - new_setter = NULL; - } - - if ((prs->flags & JS_PROP_TMASK) != JS_PROP_GETSET) { - if (js_shape_prepare_update(ctx, p, &prs)) - return -1; - /* convert to getset */ - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - free_var_ref(ctx->rt, pr->u.var_ref); - } else { - JS_FreeValue(ctx, pr->u.value); - } - prs->flags = (prs->flags & - (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | - JS_PROP_GETSET; - pr->u.getset.getter = NULL; - pr->u.getset.setter = NULL; - } else { - if (!(prs->flags & JS_PROP_CONFIGURABLE)) { - if ((flags & JS_PROP_HAS_GET) && - new_getter != pr->u.getset.getter) { - goto not_configurable; - } - if ((flags & JS_PROP_HAS_SET) && - new_setter != pr->u.getset.setter) { - goto not_configurable; - } - } - } - if (flags & JS_PROP_HAS_GET) { - if (pr->u.getset.getter) - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); - if (new_getter) - js_dup(getter); - pr->u.getset.getter = new_getter; - } - if (flags & JS_PROP_HAS_SET) { - if (pr->u.getset.setter) - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); - if (new_setter) - js_dup(setter); - pr->u.getset.setter = new_setter; - } - } else { - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - /* convert to data descriptor */ - if (js_shape_prepare_update(ctx, p, &prs)) - return -1; - if (pr->u.getset.getter) - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); - if (pr->u.getset.setter) - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); - prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); - pr->u.value = JS_UNDEFINED; - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - /* Note: JS_PROP_VARREF is always writable */ - } else { - if ((prs->flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 && - (flags & JS_PROP_HAS_VALUE)) { - if (!js_same_value(ctx, val, pr->u.value)) { - goto not_configurable; - } else { - return true; - } - } - } - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - if (flags & JS_PROP_HAS_VALUE) { - if (p->class_id == JS_CLASS_MODULE_NS) { - /* JS_PROP_WRITABLE is always true for variable - references, but they are write protected in module name - spaces. */ - if (!js_same_value(ctx, val, *pr->u.var_ref->pvalue)) - goto not_configurable; - } - /* update the reference */ - set_value(ctx, pr->u.var_ref->pvalue, js_dup(val)); - } - /* if writable is set to false, no longer a - reference (for mapped arguments) */ - if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == JS_PROP_HAS_WRITABLE) { - JSValue val1; - if (js_shape_prepare_update(ctx, p, &prs)) - return -1; - val1 = js_dup(*pr->u.var_ref->pvalue); - free_var_ref(ctx->rt, pr->u.var_ref); - pr->u.value = val1; - prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); - } - } else if (prs->flags & JS_PROP_LENGTH) { - if (flags & JS_PROP_HAS_VALUE) { - /* Note: no JS code is executable because - 'val' is guaranted to be a Uint32 */ - res = set_array_length(ctx, p, js_dup(val), flags); - } else { - res = true; - } - /* still need to reset the writable flag if - needed. The JS_PROP_LENGTH is kept because the - Uint32 test is still done if the length - property is read-only. */ - if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == - JS_PROP_HAS_WRITABLE) { - prs = get_shape_prop(p->shape); - if (js_update_property_flags(ctx, p, &prs, - prs->flags & ~JS_PROP_WRITABLE)) - return -1; - } - return res; - } else { - if (flags & JS_PROP_HAS_VALUE) { - JS_FreeValue(ctx, pr->u.value); - pr->u.value = js_dup(val); - } - if (flags & JS_PROP_HAS_WRITABLE) { - if (js_update_property_flags(ctx, p, &prs, - (prs->flags & ~JS_PROP_WRITABLE) | - (flags & JS_PROP_WRITABLE))) - return -1; - } - } - } - } - mask = 0; - if (flags & JS_PROP_HAS_CONFIGURABLE) - mask |= JS_PROP_CONFIGURABLE; - if (flags & JS_PROP_HAS_ENUMERABLE) - mask |= JS_PROP_ENUMERABLE; - if (js_update_property_flags(ctx, p, &prs, - (prs->flags & ~mask) | (flags & mask))) - return -1; - return true; - } - - /* handle modification of fast array elements */ - if (p->fast_array) { - uint32_t idx; - uint32_t prop_flags; - if (p->class_id == JS_CLASS_ARRAY) { - if (__JS_AtomIsTaggedInt(prop)) { - idx = __JS_AtomToUInt32(prop); - if (idx < p->u.array.count) { - prop_flags = get_prop_flags(flags, JS_PROP_C_W_E); - if (prop_flags != JS_PROP_C_W_E) - goto convert_to_slow_array; - if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - convert_to_slow_array: - if (convert_fast_array_to_array(ctx, p)) - return -1; - else - goto redo_prop_update; - } - if (flags & JS_PROP_HAS_VALUE) { - set_value(ctx, &p->u.array.u.values[idx], js_dup(val)); - } - return true; - } - } - } else if (is_typed_array(p->class_id)) { - JSValue num; - int ret; - - if (!__JS_AtomIsTaggedInt(prop)) { - /* slow path with to handle all numeric indexes */ - num = JS_AtomIsNumericIndex1(ctx, prop); - if (JS_IsUndefined(num)) - goto typed_array_done; - if (JS_IsException(num)) - return -1; - ret = JS_NumberIsInteger(ctx, num); - if (ret < 0) { - JS_FreeValue(ctx, num); - return -1; - } - if (!ret) { - JS_FreeValue(ctx, num); - return JS_ThrowTypeErrorOrFalse(ctx, flags, "non integer index in typed array"); - } - ret = JS_NumberIsNegativeOrMinusZero(ctx, num); - JS_FreeValue(ctx, num); - if (ret) { - return JS_ThrowTypeErrorOrFalse(ctx, flags, "negative index in typed array"); - } - if (!__JS_AtomIsTaggedInt(prop)) - goto typed_array_oob; - } - idx = __JS_AtomToUInt32(prop); - /* if the typed array is detached, p->u.array.count = 0 */ - if (idx >= p->u.array.count) { - typed_array_oob: - return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound index in typed array"); - } - prop_flags = get_prop_flags(flags, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET) || - prop_flags != (JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE)) { - return JS_ThrowTypeErrorOrFalse(ctx, flags, "invalid descriptor flags"); - } - if (flags & JS_PROP_HAS_VALUE) { - return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), js_dup(val), flags); - } - return true; - typed_array_done: ; - } - } - - return JS_CreateProperty(ctx, p, prop, val, getter, setter, flags); -} - -static int JS_DefineAutoInitProperty(JSContext *ctx, JSValueConst this_obj, - JSAtom prop, JSAutoInitIDEnum id, - void *opaque, int flags) -{ - JSObject *p; - JSProperty *pr; - - if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) - return false; - - p = JS_VALUE_GET_OBJ(this_obj); - - if (find_own_property(&pr, p, prop)) { - /* property already exists */ - abort(); - return false; - } - - /* Specialized CreateProperty */ - pr = add_property(ctx, p, prop, (flags & JS_PROP_C_W_E) | JS_PROP_AUTOINIT); - if (unlikely(!pr)) - return -1; - pr->u.init.realm_and_id = (uintptr_t)JS_DupContext(ctx); - assert((pr->u.init.realm_and_id & 3) == 0); - assert(id <= 3); - pr->u.init.realm_and_id |= id; - pr->u.init.opaque = opaque; - return true; -} - -/* shortcut to add or redefine a new property value */ -int JS_DefinePropertyValue(JSContext *ctx, JSValueConst this_obj, - JSAtom prop, JSValue val, int flags) -{ - int ret; - ret = JS_DefineProperty(ctx, this_obj, prop, val, JS_UNDEFINED, JS_UNDEFINED, - flags | JS_PROP_HAS_VALUE | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE); - JS_FreeValue(ctx, val); - return ret; -} - -int JS_DefinePropertyValueValue(JSContext *ctx, JSValueConst this_obj, - JSValue prop, JSValue val, int flags) -{ - JSAtom atom; - int ret; - atom = JS_ValueToAtom(ctx, prop); - JS_FreeValue(ctx, prop); - if (unlikely(atom == JS_ATOM_NULL)) { - JS_FreeValue(ctx, val); - return -1; - } - ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); - JS_FreeAtom(ctx, atom); - return ret; -} - -int JS_DefinePropertyValueUint32(JSContext *ctx, JSValueConst this_obj, - uint32_t idx, JSValue val, int flags) -{ - return JS_DefinePropertyValueValue(ctx, this_obj, js_uint32(idx), - val, flags); -} - -int JS_DefinePropertyValueInt64(JSContext *ctx, JSValueConst this_obj, - int64_t idx, JSValue val, int flags) -{ - return JS_DefinePropertyValueValue(ctx, this_obj, js_int64(idx), - val, flags); -} - -/* `prop` may be pure ASCII or UTF-8 encoded */ -int JS_DefinePropertyValueStr(JSContext *ctx, JSValueConst this_obj, - const char *prop, JSValue val, int flags) -{ - JSAtom atom; - int ret; - atom = JS_NewAtom(ctx, prop); - if (atom == JS_ATOM_NULL) { - JS_FreeValue(ctx, val); - return -1; - } - ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); - JS_FreeAtom(ctx, atom); - return ret; -} - -/* shortcut to add getter & setter */ -int JS_DefinePropertyGetSet(JSContext *ctx, JSValueConst this_obj, - JSAtom prop, JSValue getter, JSValue setter, - int flags) -{ - int ret; - ret = JS_DefineProperty(ctx, this_obj, prop, JS_UNDEFINED, getter, setter, - flags | JS_PROP_HAS_GET | JS_PROP_HAS_SET | - JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE); - JS_FreeValue(ctx, getter); - JS_FreeValue(ctx, setter); - return ret; -} - -static int JS_CreateDataPropertyUint32(JSContext *ctx, JSValueConst this_obj, - int64_t idx, JSValue val, int flags) -{ - return JS_DefinePropertyValueValue(ctx, this_obj, js_int64(idx), - val, flags | JS_PROP_CONFIGURABLE | - JS_PROP_ENUMERABLE | JS_PROP_WRITABLE); -} - - -/* return true if 'obj' has a non empty 'name' string */ -static bool js_object_has_name(JSContext *ctx, JSValue obj) -{ - JSProperty *pr; - JSShapeProperty *prs; - JSValue val; - JSString *p; - - prs = find_own_property(&pr, JS_VALUE_GET_OBJ(obj), JS_ATOM_name); - if (!prs) - return false; - if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) - return true; - val = pr->u.value; - if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) - return true; - p = JS_VALUE_GET_STRING(val); - return (p->len != 0); -} - -static int JS_DefineObjectName(JSContext *ctx, JSValue obj, - JSAtom name, int flags) -{ - if (name != JS_ATOM_NULL - && JS_IsObject(obj) - && !js_object_has_name(ctx, obj) - && JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, JS_AtomToString(ctx, name), flags) < 0) { - return -1; - } - return 0; -} - -static int JS_DefineObjectNameComputed(JSContext *ctx, JSValue obj, - JSValue str, int flags) -{ - if (JS_IsObject(obj) && - !js_object_has_name(ctx, obj)) { - JSAtom prop; - JSValue name_str; - prop = JS_ValueToAtom(ctx, str); - if (prop == JS_ATOM_NULL) - return -1; - name_str = js_get_function_name(ctx, prop); - JS_FreeAtom(ctx, prop); - if (JS_IsException(name_str)) - return -1; - if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, name_str, flags) < 0) - return -1; - } - return 0; -} - -#define DEFINE_GLOBAL_LEX_VAR (1 << 7) -#define DEFINE_GLOBAL_FUNC_VAR (1 << 6) - -static JSValue JS_ThrowSyntaxErrorVarRedeclaration(JSContext *ctx, JSAtom prop) -{ - return JS_ThrowSyntaxErrorAtom(ctx, "redeclaration of '%s'", prop); -} - -/* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */ -/* XXX: could support exotic global object. */ -static int JS_CheckDefineGlobalVar(JSContext *ctx, JSAtom prop, int flags) -{ - JSObject *p; - JSShapeProperty *prs; - - p = JS_VALUE_GET_OBJ(ctx->global_obj); - prs = find_own_property1(p, prop); - /* XXX: should handle JS_PROP_AUTOINIT */ - if (flags & DEFINE_GLOBAL_LEX_VAR) { - if (prs && !(prs->flags & JS_PROP_CONFIGURABLE)) - goto fail_redeclaration; - } else { - if (!prs && !p->extensible) - goto define_error; - if (flags & DEFINE_GLOBAL_FUNC_VAR) { - if (prs) { - if (!(prs->flags & JS_PROP_CONFIGURABLE) && - ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET || - ((prs->flags & (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)) != - (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)))) { - define_error: - JS_ThrowTypeErrorAtom(ctx, "cannot define variable '%s'", - prop); - return -1; - } - } - } - } - /* check if there already is a lexical declaration */ - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - prs = find_own_property1(p, prop); - if (prs) { - fail_redeclaration: - JS_ThrowSyntaxErrorVarRedeclaration(ctx, prop); - return -1; - } - return 0; -} - -/* def_flags is (0, DEFINE_GLOBAL_LEX_VAR) | - JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE */ -/* XXX: could support exotic global object. */ -static int JS_DefineGlobalVar(JSContext *ctx, JSAtom prop, int def_flags) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - JSValue val; - int flags; - - if (def_flags & DEFINE_GLOBAL_LEX_VAR) { - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - flags = JS_PROP_ENUMERABLE | (def_flags & JS_PROP_WRITABLE) | - JS_PROP_CONFIGURABLE; - val = JS_UNINITIALIZED; - } else { - p = JS_VALUE_GET_OBJ(ctx->global_obj); - flags = JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | - (def_flags & JS_PROP_CONFIGURABLE); - val = JS_UNDEFINED; - } - prs = find_own_property1(p, prop); - if (prs) - return 0; - if (!p->extensible) - return 0; - pr = add_property(ctx, p, prop, flags); - if (unlikely(!pr)) - return -1; - pr->u.value = val; - return 0; -} - -/* 'def_flags' is 0 or JS_PROP_CONFIGURABLE. */ -/* XXX: could support exotic global object. */ -static int JS_DefineGlobalFunction(JSContext *ctx, JSAtom prop, - JSValue func, int def_flags) -{ - - JSObject *p; - JSShapeProperty *prs; - int flags; - - p = JS_VALUE_GET_OBJ(ctx->global_obj); - prs = find_own_property1(p, prop); - flags = JS_PROP_HAS_VALUE | JS_PROP_THROW; - if (!prs || (prs->flags & JS_PROP_CONFIGURABLE)) { - flags |= JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | def_flags | - JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE; - } - if (JS_DefineProperty(ctx, ctx->global_obj, prop, func, - JS_UNDEFINED, JS_UNDEFINED, flags) < 0) - return -1; - return 0; -} - -static JSValue JS_GetGlobalVar(JSContext *ctx, JSAtom prop, - bool throw_ref_error) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - - /* no exotic behavior is possible in global_var_obj */ - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - prs = find_own_property(&pr, p, prop); - if (prs) { - /* XXX: should handle JS_PROP_TMASK properties */ - if (unlikely(JS_IsUninitialized(pr->u.value))) - return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return js_dup(pr->u.value); - } - return JS_GetPropertyInternal(ctx, ctx->global_obj, prop, - ctx->global_obj, throw_ref_error); -} - -/* construct a reference to a global variable */ -static int JS_GetGlobalVarRef(JSContext *ctx, JSAtom prop, JSValue *sp) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - - /* no exotic behavior is possible in global_var_obj */ - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - prs = find_own_property(&pr, p, prop); - if (prs) { - /* XXX: should handle JS_PROP_AUTOINIT properties? */ - /* XXX: conformance: do these tests in - OP_put_var_ref/OP_get_var_ref ? */ - if (unlikely(JS_IsUninitialized(pr->u.value))) { - JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return -1; - } - if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { - return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); - } - sp[0] = js_dup(ctx->global_var_obj); - } else { - int ret; - ret = JS_HasProperty(ctx, ctx->global_obj, prop); - if (ret < 0) - return -1; - if (ret) { - sp[0] = js_dup(ctx->global_obj); - } else { - sp[0] = JS_UNDEFINED; - } - } - sp[1] = JS_AtomToValue(ctx, prop); - return 0; -} - -/* use for strict variable access: test if the variable exists */ -static int JS_CheckGlobalVar(JSContext *ctx, JSAtom prop) -{ - JSObject *p; - JSShapeProperty *prs; - int ret; - - /* no exotic behavior is possible in global_var_obj */ - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - prs = find_own_property1(p, prop); - if (prs) { - ret = true; - } else { - ret = JS_HasProperty(ctx, ctx->global_obj, prop); - if (ret < 0) - return -1; - } - return ret; -} - -/* flag = 0: normal variable write - flag = 1: initialize lexical variable - flag = 2: normal variable write, strict check was done before -*/ -static int JS_SetGlobalVar(JSContext *ctx, JSAtom prop, JSValue val, - int flag) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - int flags; - - /* no exotic behavior is possible in global_var_obj */ - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - prs = find_own_property(&pr, p, prop); - if (prs) { - /* XXX: should handle JS_PROP_AUTOINIT properties? */ - if (flag != 1) { - if (unlikely(JS_IsUninitialized(pr->u.value))) { - JS_FreeValue(ctx, val); - JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return -1; - } - if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { - JS_FreeValue(ctx, val); - return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); - } - } - set_value(ctx, &pr->u.value, val); - return 0; - } - flags = JS_PROP_THROW_STRICT; - if (is_strict_mode(ctx)) - flags |= JS_PROP_NO_ADD; - return JS_SetPropertyInternal(ctx, ctx->global_obj, prop, val, flags); -} - -/* return -1, false or true */ -static int JS_DeleteGlobalVar(JSContext *ctx, JSAtom prop) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - int ret; - - /* 9.1.1.4.7 DeleteBinding ( N ) */ - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - prs = find_own_property(&pr, p, prop); - if (prs) - return false; /* lexical variables cannot be deleted */ - ret = JS_HasProperty(ctx, ctx->global_obj, prop); - if (ret < 0) - return -1; - if (ret) { - return JS_DeleteProperty(ctx, ctx->global_obj, prop, 0); - } else { - return true; - } -} - -/* return -1, false or true. return false if not configurable or - invalid object. return -1 in case of exception. - flags can be 0, JS_PROP_THROW or JS_PROP_THROW_STRICT */ -int JS_DeleteProperty(JSContext *ctx, JSValueConst obj, JSAtom prop, int flags) -{ - JSValue obj1; - JSObject *p; - int res; - - obj1 = JS_ToObject(ctx, obj); - if (JS_IsException(obj1)) - return -1; - p = JS_VALUE_GET_OBJ(obj1); - res = delete_property(ctx, p, prop); - JS_FreeValue(ctx, obj1); - if (res != false) - return res; - if ((flags & JS_PROP_THROW) || - ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { - JS_ThrowTypeError(ctx, "could not delete property"); - return -1; - } - return false; -} - -int JS_DeletePropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, int flags) -{ - JSAtom prop; - int res; - - if ((uint64_t)idx <= JS_ATOM_MAX_INT) { - /* fast path for fast arrays */ - return JS_DeleteProperty(ctx, obj, __JS_AtomFromUInt32(idx), flags); - } - prop = JS_NewAtomInt64(ctx, idx); - if (prop == JS_ATOM_NULL) - return -1; - res = JS_DeleteProperty(ctx, obj, prop, flags); - JS_FreeAtom(ctx, prop); - return res; -} - -bool JS_IsFunction(JSContext *ctx, JSValueConst val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(val); - switch(p->class_id) { - case JS_CLASS_BYTECODE_FUNCTION: - return true; - case JS_CLASS_PROXY: - return p->u.proxy_data->is_func; - default: - return (ctx->rt->class_array[p->class_id].call != NULL); - } -} - -static bool JS_IsCFunction(JSContext *ctx, JSValueConst val, JSCFunction *func, - int magic) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(val); - if (p->class_id == JS_CLASS_C_FUNCTION) - return (p->u.cfunc.c_function.generic == func && p->u.cfunc.magic == magic); - else - return false; -} - -bool JS_IsConstructor(JSContext *ctx, JSValueConst val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(val); - return p->is_constructor; -} - -bool JS_SetConstructorBit(JSContext *ctx, JSValueConst func_obj, bool val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(func_obj); - p->is_constructor = val; - return true; -} - -bool JS_IsRegExp(JSValueConst val) -{ - return JS_CLASS_REGEXP == JS_GetClassID(val); -} - -bool JS_IsMap(JSValueConst val) -{ - return JS_CLASS_MAP == JS_GetClassID(val); -} - -bool JS_IsSet(JSValueConst val) -{ - return JS_CLASS_SET == JS_GetClassID(val); -} - -bool JS_IsWeakRef(JSValueConst val) -{ - return JS_CLASS_WEAK_REF == JS_GetClassID(val); -} - -bool JS_IsWeakSet(JSValueConst val) -{ - return JS_CLASS_WEAKSET == JS_GetClassID(val); -} - -bool JS_IsWeakMap(JSValueConst val) -{ - return JS_CLASS_WEAKMAP == JS_GetClassID(val); -} - -bool JS_IsDataView(JSValueConst val) -{ - return JS_CLASS_DATAVIEW == JS_GetClassID(val); -} - -bool JS_IsError(JSValueConst val) -{ - return JS_CLASS_ERROR == JS_GetClassID(val); -} - -/* used to avoid catching interrupt exceptions */ -bool JS_IsUncatchableError(JSValueConst val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(val); - return p->class_id == JS_CLASS_ERROR && p->is_uncatchable_error; -} - -static void js_set_uncatchable_error(JSContext *ctx, JSValueConst val, bool flag) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return; - p = JS_VALUE_GET_OBJ(val); - if (p->class_id == JS_CLASS_ERROR) - p->is_uncatchable_error = flag; -} - -void JS_SetUncatchableError(JSContext *ctx, JSValueConst val) -{ - js_set_uncatchable_error(ctx, val, true); -} - -void JS_ClearUncatchableError(JSContext *ctx, JSValueConst val) -{ - js_set_uncatchable_error(ctx, val, false); -} - -void JS_ResetUncatchableError(JSContext *ctx) -{ - js_set_uncatchable_error(ctx, ctx->rt->current_exception, false); -} - -int JS_SetOpaque(JSValueConst obj, void *opaque) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - p = JS_VALUE_GET_OBJ(obj); - // User code can't set the opaque of internal objects. - if (p->class_id >= JS_CLASS_INIT_COUNT) { - p->u.opaque = opaque; - return 0; - } - } - - return -1; -} - -/* |obj| must be a JSObject of an internal class. */ -static void JS_SetOpaqueInternal(JSValueConst obj, void *opaque) -{ - JSObject *p; - assert(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT); - p = JS_VALUE_GET_OBJ(obj); - assert(p->class_id < JS_CLASS_INIT_COUNT); - p->u.opaque = opaque; -} - -/* return NULL if not an object of class class_id */ -void *JS_GetOpaque(JSValueConst obj, JSClassID class_id) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - return NULL; - p = JS_VALUE_GET_OBJ(obj); - if (p->class_id != class_id) - return NULL; - return p->u.opaque; -} - -void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id) -{ - void *p = JS_GetOpaque(obj, class_id); - if (unlikely(!p)) { - JS_ThrowTypeErrorInvalidClass(ctx, class_id); - } - return p; -} - -void *JS_GetAnyOpaque(JSValueConst obj, JSClassID *class_id) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { - *class_id = 0; - return NULL; - } - p = JS_VALUE_GET_OBJ(obj); - *class_id = p->class_id; - return p->u.opaque; -} - -static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint) -{ - int i; - bool force_ordinary; - - JSAtom method_name; - JSValue method, ret; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return val; - force_ordinary = hint & HINT_FORCE_ORDINARY; - hint &= ~HINT_FORCE_ORDINARY; - if (!force_ordinary) { - method = JS_GetProperty(ctx, val, JS_ATOM_Symbol_toPrimitive); - if (JS_IsException(method)) - goto exception; - /* ECMA says *If exoticToPrim is not undefined* but tests in - test262 use null as a non callable converter */ - if (!JS_IsUndefined(method) && !JS_IsNull(method)) { - JSAtom atom; - JSValue arg; - switch(hint) { - case HINT_STRING: - atom = JS_ATOM_string; - break; - case HINT_NUMBER: - atom = JS_ATOM_number; - break; - default: - case HINT_NONE: - atom = JS_ATOM_default; - break; - } - arg = JS_AtomToString(ctx, atom); - ret = JS_CallFree(ctx, method, val, 1, vc(&arg)); - JS_FreeValue(ctx, arg); - if (JS_IsException(ret)) - goto exception; - JS_FreeValue(ctx, val); - if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) - return ret; - JS_FreeValue(ctx, ret); - return JS_ThrowTypeError(ctx, "toPrimitive"); - } - } - if (hint != HINT_STRING) - hint = HINT_NUMBER; - for(i = 0; i < 2; i++) { - if ((i ^ hint) == 0) { - method_name = JS_ATOM_toString; - } else { - method_name = JS_ATOM_valueOf; - } - method = JS_GetProperty(ctx, val, method_name); - if (JS_IsException(method)) - goto exception; - if (JS_IsFunction(ctx, method)) { - ret = JS_CallFree(ctx, method, val, 0, NULL); - if (JS_IsException(ret)) - goto exception; - if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { - JS_FreeValue(ctx, val); - return ret; - } - JS_FreeValue(ctx, ret); - } else { - JS_FreeValue(ctx, method); - } - } - JS_ThrowTypeError(ctx, "toPrimitive"); -exception: - JS_FreeValue(ctx, val); - return JS_EXCEPTION; -} - -static JSValue JS_ToPrimitive(JSContext *ctx, JSValueConst val, int hint) -{ - return JS_ToPrimitiveFree(ctx, js_dup(val), hint); -} - -void JS_SetIsHTMLDDA(JSContext *ctx, JSValueConst obj) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - return; - p = JS_VALUE_GET_OBJ(obj); - p->is_HTMLDDA = true; -} - -static inline bool JS_IsHTMLDDA(JSContext *ctx, JSValueConst obj) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(obj); - return p->is_HTMLDDA; -} - -static int JS_ToBoolFree(JSContext *ctx, JSValue val) -{ - uint32_t tag = JS_VALUE_GET_TAG(val); - switch(tag) { - case JS_TAG_INT: - return JS_VALUE_GET_INT(val) != 0; - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - return JS_VALUE_GET_INT(val); - case JS_TAG_EXCEPTION: - return -1; - case JS_TAG_STRING: - { - bool ret = JS_VALUE_GET_STRING(val)->len != 0; - JS_FreeValue(ctx, val); - return ret; - } - case JS_TAG_SHORT_BIG_INT: - return JS_VALUE_GET_SHORT_BIG_INT(val) != 0; - case JS_TAG_BIG_INT: - { - JSBigInt *p = JS_VALUE_GET_PTR(val); - bool ret; - int i; - - /* fail safe: we assume it is not necessarily - normalized. Beginning from the MSB ensures that the - test is fast. */ - ret = false; - for(i = p->len - 1; i >= 0; i--) { - if (p->tab[i] != 0) { - ret = true; - break; - } - } - JS_FreeValue(ctx, val); - return ret; - } - case JS_TAG_OBJECT: - { - JSObject *p = JS_VALUE_GET_OBJ(val); - bool ret = !p->is_HTMLDDA; - JS_FreeValue(ctx, val); - return ret; - } - break; - default: - if (JS_TAG_IS_FLOAT64(tag)) { - double d = JS_VALUE_GET_FLOAT64(val); - return !isnan(d) && d != 0; - } else { - JS_FreeValue(ctx, val); - return true; - } - } -} - -int JS_ToBool(JSContext *ctx, JSValueConst val) -{ - return JS_ToBoolFree(ctx, js_dup(val)); -} - -/* pc points to pure ASCII or UTF-8, null terminated contents */ -static int skip_spaces(const char *pc) -{ - const uint8_t *p, *p_next, *p_start; - uint32_t c; - - p = p_start = (const uint8_t *)pc; - for (;;) { - c = *p++; - if (c < 0x80) { - if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20))) - break; - } else { - c = utf8_decode(p - 1, &p_next); - /* no need to test for invalid UTF-8, 0xFFFD is not a space */ - if (!lre_is_space(c)) - break; - p = p_next; - } - } - return p - 1 - p_start; -} - -static inline int js_to_digit(int c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'A' && c <= 'Z') - return c - 'A' + 10; - else if (c >= 'a' && c <= 'z') - return c - 'a' + 10; - else - return 36; -} - -/* bigint support */ - -#define ADDC(res, carry_out, op1, op2, carry_in) \ -do { \ - js_limb_t __v, __a, __k, __k1; \ - __v = (op1); \ - __a = __v + (op2); \ - __k1 = __a < __v; \ - __k = (carry_in); \ - __a = __a + __k; \ - carry_out = (__a < __k) | __k1; \ - res = __a; \ -} while (0) - -/* a != 0 */ -static inline js_limb_t js_limb_clz(js_limb_t a) -{ - if (!a) - return JS_LIMB_BITS; - return clz32(a); -} - -static js_limb_t js_mp_add(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, - js_limb_t n, js_limb_t carry) -{ - int i; - for(i = 0;i < n; i++) { - ADDC(res[i], carry, op1[i], op2[i], carry); - } - return carry; -} - -static js_limb_t js_mp_sub(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, - int n, js_limb_t carry) -{ - int i; - js_limb_t k, a, v, k1; - - k = carry; - for(i=0;i v; - v = a - k; - k = (v > a) | k1; - res[i] = v; - } - return k; -} - -/* compute 0 - op2. carry = 0 or 1. */ -static js_limb_t js_mp_neg(js_limb_t *res, const js_limb_t *op2, int n) -{ - int i; - js_limb_t v, carry; - - carry = 1; - for(i=0;i> JS_LIMB_BITS; - } - return l; -} - -static js_limb_t js_mp_div1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, - js_limb_t b, js_limb_t r) -{ - js_slimb_t i; - js_dlimb_t a1; - for(i = n - 1; i >= 0; i--) { - a1 = ((js_dlimb_t)r << JS_LIMB_BITS) | taba[i]; - tabr[i] = a1 / b; - r = a1 % b; - } - return r; -} - -/* tabr[] += taba[] * b, return the high word. */ -static js_limb_t js_mp_add_mul1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, - js_limb_t b) -{ - js_limb_t i, l; - js_dlimb_t t; - - l = 0; - for(i = 0; i < n; i++) { - t = (js_dlimb_t)taba[i] * (js_dlimb_t)b + l + tabr[i]; - tabr[i] = t; - l = t >> JS_LIMB_BITS; - } - return l; -} - -/* size of the result : op1_size + op2_size. */ -static void js_mp_mul_basecase(js_limb_t *result, - const js_limb_t *op1, js_limb_t op1_size, - const js_limb_t *op2, js_limb_t op2_size) -{ - int i; - js_limb_t r; - - result[op1_size] = js_mp_mul1(result, op1, op1_size, op2[0], 0); - for(i=1;i> JS_LIMB_BITS); - } - return l; -} - -/* WARNING: d must be >= 2^(JS_LIMB_BITS-1) */ -static inline js_limb_t js_udiv1norm_init(js_limb_t d) -{ - js_limb_t a0, a1; - a1 = -d - 1; - a0 = -1; - return (((js_dlimb_t)a1 << JS_LIMB_BITS) | a0) / d; -} - -/* return the quotient and the remainder in '*pr'of 'a1*2^JS_LIMB_BITS+a0 - / d' with 0 <= a1 < d. */ -static inline js_limb_t js_udiv1norm(js_limb_t *pr, js_limb_t a1, js_limb_t a0, - js_limb_t d, js_limb_t d_inv) -{ - js_limb_t n1m, n_adj, q, r, ah; - js_dlimb_t a; - n1m = ((js_slimb_t)a0 >> (JS_LIMB_BITS - 1)); - n_adj = a0 + (n1m & d); - a = (js_dlimb_t)d_inv * (a1 - n1m) + n_adj; - q = (a >> JS_LIMB_BITS) + a1; - /* compute a - q * r and update q so that the remainder is\ - between 0 and d - 1 */ - a = ((js_dlimb_t)a1 << JS_LIMB_BITS) | a0; - a = a - (js_dlimb_t)q * d - d; - ah = a >> JS_LIMB_BITS; - q += 1 + ah; - r = (js_limb_t)a + (ah & d); - *pr = r; - return q; -} - -#define UDIV1NORM_THRESHOLD 3 - -/* b must be >= 1 << (JS_LIMB_BITS - 1) */ -static js_limb_t js_mp_div1norm(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, - js_limb_t b, js_limb_t r) -{ - js_slimb_t i; - - if (n >= UDIV1NORM_THRESHOLD) { - js_limb_t b_inv; - b_inv = js_udiv1norm_init(b); - for(i = n - 1; i >= 0; i--) { - tabr[i] = js_udiv1norm(&r, r, taba[i], b, b_inv); - } - } else { - js_dlimb_t a1; - for(i = n - 1; i >= 0; i--) { - a1 = ((js_dlimb_t)r << JS_LIMB_BITS) | taba[i]; - tabr[i] = a1 / b; - r = a1 % b; - } - } - return r; -} - -/* base case division: divides taba[0..na-1] by tabb[0..nb-1]. tabb[nb - - 1] must be >= 1 << (JS_LIMB_BITS - 1). na - nb must be >= 0. 'taba' - is modified and contains the remainder (nb limbs). tabq[0..na-nb] - contains the quotient with tabq[na - nb] <= 1. */ -static void js_mp_divnorm(js_limb_t *tabq, js_limb_t *taba, js_limb_t na, - const js_limb_t *tabb, js_limb_t nb) -{ - js_limb_t r, a, c, q, v, b1, b1_inv, n, dummy_r; - int i, j; - - b1 = tabb[nb - 1]; - if (nb == 1) { - taba[0] = js_mp_div1norm(tabq, taba, na, b1, 0); - return; - } - n = na - nb; - - if (n >= UDIV1NORM_THRESHOLD) - b1_inv = js_udiv1norm_init(b1); - else - b1_inv = 0; - - /* first iteration: the quotient is only 0 or 1 */ - q = 1; - for(j = nb - 1; j >= 0; j--) { - if (taba[n + j] != tabb[j]) { - if (taba[n + j] < tabb[j]) - q = 0; - break; - } - } - tabq[n] = q; - if (q) { - js_mp_sub(taba + n, taba + n, tabb, nb, 0); - } - - for(i = n - 1; i >= 0; i--) { - if (unlikely(taba[i + nb] >= b1)) { - q = -1; - } else if (b1_inv) { - q = js_udiv1norm(&dummy_r, taba[i + nb], taba[i + nb - 1], b1, b1_inv); - } else { - js_dlimb_t al; - al = ((js_dlimb_t)taba[i + nb] << JS_LIMB_BITS) | taba[i + nb - 1]; - q = al / b1; - r = al % b1; - } - r = js_mp_sub_mul1(taba + i, tabb, nb, q); - - v = taba[i + nb]; - a = v - r; - c = (a > v); - taba[i + nb] = a; - - if (c != 0) { - /* negative result */ - for(;;) { - q--; - c = js_mp_add(taba + i, taba + i, tabb, nb, 0); - /* propagate carry and test if positive result */ - if (c != 0) { - if (++taba[i + nb] == 0) { - break; - } - } - } - } - tabq[i] = q; - } -} - -/* 1 <= shift <= JS_LIMB_BITS - 1 */ -static js_limb_t js_mp_shl(js_limb_t *tabr, const js_limb_t *taba, int n, - int shift) -{ - int i; - js_limb_t l, v; - l = 0; - for(i = 0; i < n; i++) { - v = taba[i]; - tabr[i] = (v << shift) | l; - l = v >> (JS_LIMB_BITS - shift); - } - return l; -} - -/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). - 1 <= shift <= LIMB_BITS - 1 */ -static js_limb_t js_mp_shr(js_limb_t *tab_r, const js_limb_t *tab, int n, - int shift, js_limb_t high) -{ - int i; - js_limb_t l, a; - - l = high; - for(i = n - 1; i >= 0; i--) { - a = tab[i]; - tab_r[i] = (a >> shift) | (l << (JS_LIMB_BITS - shift)); - l = a; - } - return l & (((js_limb_t)1 << shift) - 1); -} - -static JSBigInt *js_bigint_new(JSContext *ctx, int len) -{ - JSBigInt *r; - if (len > JS_BIGINT_MAX_SIZE) { - JS_ThrowRangeError(ctx, "BigInt is too large to allocate"); - return NULL; - } - r = js_malloc(ctx, sizeof(JSBigInt) + len * sizeof(js_limb_t)); - if (!r) - return NULL; - r->header.ref_count = 1; - r->len = len; - return r; -} - -static JSBigInt *js_bigint_set_si(JSBigIntBuf *buf, js_slimb_t a) -{ - JSBigInt *r = (JSBigInt *)buf->big_int_buf; - r->header.ref_count = 0; /* fail safe */ - r->len = 1; - r->tab[0] = a; - return r; -} - -static JSBigInt *js_bigint_set_si64(JSBigIntBuf *buf, int64_t a) -{ - JSBigInt *r = (JSBigInt *)buf->big_int_buf; - r->header.ref_count = 0; /* fail safe */ - if (a >= INT32_MIN && a <= INT32_MAX) { - r->len = 1; - r->tab[0] = a; - } else { - r->len = 2; - r->tab[0] = a; - r->tab[1] = a >> JS_LIMB_BITS; - } - return r; -} - -/* val must be a short big int */ -static JSBigInt *js_bigint_set_short(JSBigIntBuf *buf, JSValueConst val) -{ - return js_bigint_set_si(buf, JS_VALUE_GET_SHORT_BIG_INT(val)); -} - -static __maybe_unused void js_bigint_dump1(JSContext *ctx, const char *str, - const js_limb_t *tab, int len) -{ - int i; - printf("%s: ", str); - for(i = len - 1; i >= 0; i--) { - printf(" %08x", tab[i]); - } - printf("\n"); -} - -static __maybe_unused void js_bigint_dump(JSContext *ctx, const char *str, - const JSBigInt *p) -{ - js_bigint_dump1(ctx, str, p->tab, p->len); -} - -static JSBigInt *js_bigint_new_si(JSContext *ctx, js_slimb_t a) -{ - JSBigInt *r; - r = js_bigint_new(ctx, 1); - if (!r) - return NULL; - r->tab[0] = a; - return r; -} - -static JSBigInt *js_bigint_new_si64(JSContext *ctx, int64_t a) -{ - if (a >= INT32_MIN && a <= INT32_MAX) { - return js_bigint_new_si(ctx, a); - } else { - JSBigInt *r; - r = js_bigint_new(ctx, 2); - if (!r) - return NULL; - r->tab[0] = a; - r->tab[1] = a >> 32; - return r; - } -} - -static JSBigInt *js_bigint_new_ui64(JSContext *ctx, uint64_t a) -{ - if (a <= INT64_MAX) { - return js_bigint_new_si64(ctx, a); - } else { - JSBigInt *r; - r = js_bigint_new(ctx, (65 + JS_LIMB_BITS - 1) / JS_LIMB_BITS); - if (!r) - return NULL; - r->tab[0] = a; - r->tab[1] = a >> 32; - r->tab[2] = 0; - return r; - } -} - -static JSBigInt *js_bigint_new_di(JSContext *ctx, js_sdlimb_t a) -{ - JSBigInt *r; - if (a == (js_slimb_t)a) { - r = js_bigint_new(ctx, 1); - if (!r) - return NULL; - r->tab[0] = a; - } else { - r = js_bigint_new(ctx, 2); - if (!r) - return NULL; - r->tab[0] = a; - r->tab[1] = a >> JS_LIMB_BITS; - } - return r; -} - -/* Remove redundant high order limbs. Warning: 'a' may be - reallocated. Can never fail. -*/ -static JSBigInt *js_bigint_normalize1(JSContext *ctx, JSBigInt *a, int l) -{ - js_limb_t v; - - assert(a->header.ref_count == 1); - while (l > 1) { - v = a->tab[l - 1]; - if ((v != 0 && v != -1) || - (v & 1) != (a->tab[l - 2] >> (JS_LIMB_BITS - 1))) { - break; - } - l--; - } - if (l != a->len) { - JSBigInt *a1; - /* realloc to reduce the size */ - a->len = l; - a1 = js_realloc(ctx, a, sizeof(JSBigInt) + l * sizeof(js_limb_t)); - if (a1) - a = a1; - } - return a; -} - -static JSBigInt *js_bigint_normalize(JSContext *ctx, JSBigInt *a) -{ - return js_bigint_normalize1(ctx, a, a->len); -} - -/* return 0 or 1 depending on the sign */ -static inline int js_bigint_sign(const JSBigInt *a) -{ - return a->tab[a->len - 1] >> (JS_LIMB_BITS - 1); -} - -static js_slimb_t js_bigint_get_si_sat(const JSBigInt *a) -{ - if (a->len == 1) { - return a->tab[0]; - } else { - if (js_bigint_sign(a)) - return INT32_MIN; - else - return INT32_MAX; - } -} - -/* add the op1 limb */ -static JSBigInt *js_bigint_extend(JSContext *ctx, JSBigInt *r, - js_limb_t op1) -{ - int n2 = r->len; - if ((op1 != 0 && op1 != -1) || - (op1 & 1) != r->tab[n2 - 1] >> (JS_LIMB_BITS - 1)) { - JSBigInt *r1; - r1 = js_realloc(ctx, r, - sizeof(JSBigInt) + (n2 + 1) * sizeof(js_limb_t)); - if (!r1) { - js_free(ctx, r); - return NULL; - } - r = r1; - r->len = n2 + 1; - r->tab[n2] = op1; - } else { - /* otherwise still need to normalize the result */ - r = js_bigint_normalize(ctx, r); - } - return r; -} - -/* return NULL in case of error. Compute a + b (b_neg = 0) or a - b - (b_neg = 1) */ -/* XXX: optimize */ -static JSBigInt *js_bigint_add(JSContext *ctx, const JSBigInt *a, - const JSBigInt *b, int b_neg) -{ - JSBigInt *r; - int n1, n2, i; - js_limb_t carry, op1, op2, a_sign, b_sign; - - n2 = max_int(a->len, b->len); - n1 = min_int(a->len, b->len); - r = js_bigint_new(ctx, n2); - if (!r) - return NULL; - /* XXX: optimize */ - /* common part */ - carry = b_neg; - for(i = 0; i < n1; i++) { - op1 = a->tab[i]; - op2 = b->tab[i] ^ (-b_neg); - ADDC(r->tab[i], carry, op1, op2, carry); - } - a_sign = -js_bigint_sign(a); - b_sign = (-js_bigint_sign(b)) ^ (-b_neg); - /* part with sign extension of one operand */ - if (a->len > b->len) { - for(i = n1; i < n2; i++) { - op1 = a->tab[i]; - ADDC(r->tab[i], carry, op1, b_sign, carry); - } - } else if (a->len < b->len) { - for(i = n1; i < n2; i++) { - op2 = b->tab[i] ^ (-b_neg); - ADDC(r->tab[i], carry, a_sign, op2, carry); - } - } - - /* part with sign extension for both operands. Extend the result - if necessary */ - return js_bigint_extend(ctx, r, a_sign + b_sign + carry); -} - -/* XXX: optimize */ -static JSBigInt *js_bigint_neg(JSContext *ctx, const JSBigInt *a) -{ - JSBigIntBuf buf; - JSBigInt *b; - b = js_bigint_set_si(&buf, 0); - return js_bigint_add(ctx, b, a, 1); -} - -static JSBigInt *js_bigint_mul(JSContext *ctx, const JSBigInt *a, - const JSBigInt *b) -{ - JSBigInt *r; - - r = js_bigint_new(ctx, a->len + b->len); - if (!r) - return NULL; - js_mp_mul_basecase(r->tab, a->tab, a->len, b->tab, b->len); - /* correct the result if negative operands (no overflow is - possible) */ - if (js_bigint_sign(a)) - js_mp_sub(r->tab + a->len, r->tab + a->len, b->tab, b->len, 0); - if (js_bigint_sign(b)) - js_mp_sub(r->tab + b->len, r->tab + b->len, a->tab, a->len, 0); - return js_bigint_normalize(ctx, r); -} - -/* return the division or the remainder. 'b' must be != 0. return NULL - in case of exception (division by zero or memory error) */ -static JSBigInt *js_bigint_divrem(JSContext *ctx, const JSBigInt *a, - const JSBigInt *b, bool is_rem) -{ - JSBigInt *r, *q; - js_limb_t *tabb, h; - int na, nb, a_sign, b_sign, shift; - - if (b->len == 1 && b->tab[0] == 0) { - JS_ThrowRangeError(ctx, "BigInt division by zero"); - return NULL; - } - - a_sign = js_bigint_sign(a); - b_sign = js_bigint_sign(b); - na = a->len; - nb = b->len; - - r = js_bigint_new(ctx, na + 2); - if (!r) - return NULL; - if (a_sign) { - js_mp_neg(r->tab, a->tab, na); - } else { - memcpy(r->tab, a->tab, na * sizeof(a->tab[0])); - } - /* normalize */ - while (na > 1 && r->tab[na - 1] == 0) - na--; - - tabb = js_malloc(ctx, nb * sizeof(tabb[0])); - if (!tabb) { - js_free(ctx, r); - return NULL; - } - if (b_sign) { - js_mp_neg(tabb, b->tab, nb); - } else { - memcpy(tabb, b->tab, nb * sizeof(tabb[0])); - } - /* normalize */ - while (nb > 1 && tabb[nb - 1] == 0) - nb--; - - /* trivial case if 'a' is small */ - if (na < nb) { - js_free(ctx, r); - js_free(ctx, tabb); - if (is_rem) { - /* r = a */ - r = js_bigint_new(ctx, a->len); - if (!r) - return NULL; - memcpy(r->tab, a->tab, a->len * sizeof(a->tab[0])); - return r; - } else { - /* q = 0 */ - return js_bigint_new_si(ctx, 0); - } - } - - /* normalize 'b' */ - shift = js_limb_clz(tabb[nb - 1]); - if (shift != 0) { - js_mp_shl(tabb, tabb, nb, shift); - h = js_mp_shl(r->tab, r->tab, na, shift); - if (h != 0) - r->tab[na++] = h; - } - - q = js_bigint_new(ctx, na - nb + 2); /* one more limb for the sign */ - if (!q) { - js_free(ctx, r); - js_free(ctx, tabb); - return NULL; - } - - // js_bigint_dump1(ctx, "a", r->tab, na); - // js_bigint_dump1(ctx, "b", tabb, nb); - js_mp_divnorm(q->tab, r->tab, na, tabb, nb); - js_free(ctx, tabb); - - if (is_rem) { - js_free(ctx, q); - if (shift != 0) - js_mp_shr(r->tab, r->tab, nb, shift, 0); - r->tab[nb++] = 0; - if (a_sign) - js_mp_neg(r->tab, r->tab, nb); - r = js_bigint_normalize1(ctx, r, nb); - return r; - } else { - js_free(ctx, r); - q->tab[na - nb + 1] = 0; - if (a_sign ^ b_sign) { - js_mp_neg(q->tab, q->tab, q->len); - } - q = js_bigint_normalize(ctx, q); - return q; - } -} - -/* and, or, xor */ -static JSBigInt *js_bigint_logic(JSContext *ctx, const JSBigInt *a, - const JSBigInt *b, OPCodeEnum op) -{ - JSBigInt *r; - js_limb_t b_sign; - int a_len, b_len, i; - - if (a->len < b->len) { - const JSBigInt *tmp; - tmp = a; - a = b; - b = tmp; - } - /* a_len >= b_len */ - a_len = a->len; - b_len = b->len; - b_sign = -js_bigint_sign(b); - - r = js_bigint_new(ctx, a_len); - if (!r) - return NULL; - switch(op) { - case OP_or: - for(i = 0; i < b_len; i++) { - r->tab[i] = a->tab[i] | b->tab[i]; - } - for(i = b_len; i < a_len; i++) { - r->tab[i] = a->tab[i] | b_sign; - } - break; - case OP_and: - for(i = 0; i < b_len; i++) { - r->tab[i] = a->tab[i] & b->tab[i]; - } - for(i = b_len; i < a_len; i++) { - r->tab[i] = a->tab[i] & b_sign; - } - break; - case OP_xor: - for(i = 0; i < b_len; i++) { - r->tab[i] = a->tab[i] ^ b->tab[i]; - } - for(i = b_len; i < a_len; i++) { - r->tab[i] = a->tab[i] ^ b_sign; - } - break; - default: - abort(); - } - return js_bigint_normalize(ctx, r); -} - -static JSBigInt *js_bigint_not(JSContext *ctx, const JSBigInt *a) -{ - JSBigInt *r; - int i; - - r = js_bigint_new(ctx, a->len); - if (!r) - return NULL; - for(i = 0; i < a->len; i++) { - r->tab[i] = ~a->tab[i]; - } - /* no normalization is needed */ - return r; -} - -static JSBigInt *js_bigint_shl(JSContext *ctx, const JSBigInt *a, - unsigned int shift1) -{ - int d, i, shift; - JSBigInt *r; - js_limb_t l; - - if (a->len == 1 && a->tab[0] == 0) - return js_bigint_new_si(ctx, 0); /* zero case */ - d = shift1 / JS_LIMB_BITS; - shift = shift1 % JS_LIMB_BITS; - r = js_bigint_new(ctx, a->len + d); - if (!r) - return NULL; - for(i = 0; i < d; i++) - r->tab[i] = 0; - if (shift == 0) { - for(i = 0; i < a->len; i++) { - r->tab[i + d] = a->tab[i]; - } - } else { - l = js_mp_shl(r->tab + d, a->tab, a->len, shift); - if (js_bigint_sign(a)) - l |= (js_limb_t)(-1) << shift; - r = js_bigint_extend(ctx, r, l); - } - return r; -} - -static JSBigInt *js_bigint_shr(JSContext *ctx, const JSBigInt *a, - unsigned int shift1) -{ - int d, i, shift, a_sign, n1; - JSBigInt *r; - - d = shift1 / JS_LIMB_BITS; - shift = shift1 % JS_LIMB_BITS; - a_sign = js_bigint_sign(a); - if (d >= a->len) - return js_bigint_new_si(ctx, -a_sign); - n1 = a->len - d; - r = js_bigint_new(ctx, n1); - if (!r) - return NULL; - if (shift == 0) { - for(i = 0; i < n1; i++) { - r->tab[i] = a->tab[i + d]; - } - /* no normalization is needed */ - } else { - js_mp_shr(r->tab, a->tab + d, n1, shift, -a_sign); - r = js_bigint_normalize(ctx, r); - } - return r; -} - -static JSBigInt *js_bigint_pow(JSContext *ctx, const JSBigInt *a, JSBigInt *b) -{ - uint32_t e; - int n_bits, i; - JSBigInt *r, *r1; - - /* b must be >= 0 */ - if (js_bigint_sign(b)) { - JS_ThrowRangeError(ctx, "BigInt negative exponent"); - return NULL; - } - if (b->len == 1 && b->tab[0] == 0) { - /* a^0 = 1 */ - return js_bigint_new_si(ctx, 1); - } else if (a->len == 1) { - js_limb_t v; - bool is_neg; - - v = a->tab[0]; - if (v <= 1) - return js_bigint_new_si(ctx, v); - else if (v == -1) - return js_bigint_new_si(ctx, 1 - 2 * (b->tab[0] & 1)); - is_neg = (js_slimb_t)v < 0; - if (is_neg) - v = -v; - if ((v & (v - 1)) == 0) { - uint64_t e1; - int n; - /* v = 2^n */ - n = JS_LIMB_BITS - 1 - js_limb_clz(v); - if (b->len > 1) - goto overflow; - if (b->tab[0] > INT32_MAX) - goto overflow; - e = b->tab[0]; - e1 = (uint64_t)e * n; - if (e1 > JS_BIGINT_MAX_SIZE * JS_LIMB_BITS) - goto overflow; - e = e1; - if (is_neg) - is_neg = b->tab[0] & 1; - r = js_bigint_new(ctx, - (e + JS_LIMB_BITS + 1 - is_neg) / JS_LIMB_BITS); - if (!r) - return NULL; - memset(r->tab, 0, sizeof(r->tab[0]) * r->len); - r->tab[e / JS_LIMB_BITS] = - (js_limb_t)(1 - 2 * is_neg) << (e % JS_LIMB_BITS); - return r; - } - } - if (b->len > 1) - goto overflow; - if (b->tab[0] > INT32_MAX) - goto overflow; - e = b->tab[0]; - n_bits = 32 - clz32(e); - - r = js_bigint_new(ctx, a->len); - if (!r) - return NULL; - memcpy(r->tab, a->tab, a->len * sizeof(a->tab[0])); - for(i = n_bits - 2; i >= 0; i--) { - r1 = js_bigint_mul(ctx, r, r); - if (!r1) - return NULL; - js_free(ctx, r); - r = r1; - if ((e >> i) & 1) { - r1 = js_bigint_mul(ctx, r, a); - if (!r1) - return NULL; - js_free(ctx, r); - r = r1; - } - } - return r; - overflow: - JS_ThrowRangeError(ctx, "BigInt is too large"); - return NULL; -} - -/* return (mant, exp) so that abs(a) ~ mant*2^(exp - (limb_bits - - 1). a must be != 0. */ -static uint64_t js_bigint_get_mant_exp(JSContext *ctx, - int *pexp, const JSBigInt *a) -{ - js_limb_t t[4 - JS_LIMB_BITS / 32], carry, v, low_bits; - int n1, n2, sgn, shift, i, j, e; - uint64_t a1, a0; - - n2 = 4 - JS_LIMB_BITS / 32; - n1 = a->len - n2; - sgn = js_bigint_sign(a); - - /* low_bits != 0 if there are a non zero low bit in abs(a) */ - low_bits = 0; - carry = sgn; - for(i = 0; i < n1; i++) { - v = (a->tab[i] ^ (-sgn)) + carry; - carry = v < carry; - low_bits |= v; - } - /* get the n2 high limbs of abs(a) */ - for(j = 0; j < n2; j++) { - i = j + n1; - if (i < 0) { - v = 0; - } else { - v = (a->tab[i] ^ (-sgn)) + carry; - carry = v < carry; - } - t[j] = v; - } - - a1 = ((uint64_t)t[2] << 32) | t[1]; - a0 = (uint64_t)t[0] << 32; - a0 |= (low_bits != 0); - /* normalize */ - { - shift = clz64(a1); - if (shift != 0) { - a1 = (a1 << shift) | (a0 >> (64 - shift)); - a0 <<= shift; - } - } - a1 |= (a0 != 0); /* keep the bits for the final rounding */ - /* compute the exponent */ - e = a->len * JS_LIMB_BITS - shift - 1; - *pexp = e; - return a1; -} - -/* shift left with round to nearest, ties to even. n >= 1 */ -static uint64_t shr_rndn(uint64_t a, int n) -{ - uint64_t addend = ((a >> n) & 1) + ((1 << (n - 1)) - 1); - return (a + addend) >> n; -} - -/* convert to float64 with round to nearest, ties to even. Return - +/-infinity if too large. */ -static double js_bigint_to_float64(JSContext *ctx, const JSBigInt *a) -{ - int sgn, e; - uint64_t mant; - - if (a->len == 1) { - /* fast case, including zero */ - return (double)(js_slimb_t)a->tab[0]; - } - - sgn = js_bigint_sign(a); - mant = js_bigint_get_mant_exp(ctx, &e, a); - if (e > 1023) { - /* overflow: return infinity */ - mant = 0; - e = 1024; - } else { - mant = (mant >> 1) | (mant & 1); /* avoid overflow in rounding */ - mant = shr_rndn(mant, 10); - /* rounding can cause an overflow */ - if (mant >= ((uint64_t)1 << 53)) { - mant >>= 1; - e++; - } - mant &= (((uint64_t)1 << 52) - 1); - } - return uint64_as_float64(((uint64_t)sgn << 63) | - ((uint64_t)(e + 1023) << 52) | - mant); -} - -/* return (1, NULL) if not an integer, (2, NULL) if NaN or Infinity, - (0, n) if an integer, (0, NULL) in case of memory error */ -static JSBigInt *js_bigint_from_float64(JSContext *ctx, int *pres, double a1) -{ - uint64_t a = float64_as_uint64(a1); - int sgn, e, shift; - uint64_t mant; - JSBigIntBuf buf; - JSBigInt *r; - - sgn = a >> 63; - e = (a >> 52) & ((1 << 11) - 1); - mant = a & (((uint64_t)1 << 52) - 1); - if (e == 2047) { - /* NaN, Infinity */ - *pres = 2; - return NULL; - } - if (e == 0 && mant == 0) { - /* zero */ - *pres = 0; - return js_bigint_new_si(ctx, 0); - } - e -= 1023; - /* 0 < a < 1 : not an integer */ - if (e < 0) - goto not_an_integer; - mant |= (uint64_t)1 << 52; - if (e < 52) { - shift = 52 - e; - /* check that there is no fractional part */ - if (mant & (((uint64_t)1 << shift) - 1)) { - not_an_integer: - *pres = 1; - return NULL; - } - mant >>= shift; - e = 0; - } else { - e -= 52; - } - if (sgn) - mant = -mant; - /* the integer is mant*2^e */ - r = js_bigint_set_si64(&buf, (int64_t)mant); - *pres = 0; - return js_bigint_shl(ctx, r, e); -} - -/* return -1, 0, 1 or (2) (unordered) */ -static int js_bigint_float64_cmp(JSContext *ctx, const JSBigInt *a, - double b) -{ - int b_sign, a_sign, e, f; - uint64_t mant, b1, a_mant; - - b1 = float64_as_uint64(b); - b_sign = b1 >> 63; - e = (b1 >> 52) & ((1 << 11) - 1); - mant = b1 & (((uint64_t)1 << 52) - 1); - a_sign = js_bigint_sign(a); - if (e == 2047) { - if (mant != 0) { - /* NaN */ - return 2; - } else { - /* +/- infinity */ - return 2 * b_sign - 1; - } - } else if (e == 0 && mant == 0) { - /* b = +/-0 */ - if (a->len == 1 && a->tab[0] == 0) - return 0; - else - return 1 - 2 * a_sign; - } else if (a->len == 1 && a->tab[0] == 0) { - /* a = 0, b != 0 */ - return 2 * b_sign - 1; - } else if (a_sign != b_sign) { - return 1 - 2 * a_sign; - } else { - e -= 1023; - /* Note: handling denormals is not necessary because we - compare to integers hence f >= 0 */ - /* compute f so that 2^f <= abs(a) < 2^(f+1) */ - a_mant = js_bigint_get_mant_exp(ctx, &f, a); - if (f != e) { - if (f < e) - return -1; - else - return 1; - } else { - mant = (mant | ((uint64_t)1 << 52)) << 11; /* align to a_mant */ - if (a_mant < mant) - return 2 * a_sign - 1; - else if (a_mant > mant) - return 1 - 2 * a_sign; - else - return 0; - } - } -} - -/* return -1, 0 or 1 */ -static int js_bigint_cmp(JSContext *ctx, const JSBigInt *a, - const JSBigInt *b) -{ - int a_sign, b_sign, res, i; - a_sign = js_bigint_sign(a); - b_sign = js_bigint_sign(b); - if (a_sign != b_sign) { - res = 1 - 2 * a_sign; - } else { - /* we assume the numbers are normalized */ - if (a->len != b->len) { - if (a->len < b->len) - res = 2 * a_sign - 1; - else - res = 1 - 2 * a_sign; - } else { - res = 0; - for(i = a->len -1; i >= 0; i--) { - if (a->tab[i] != b->tab[i]) { - if (a->tab[i] < b->tab[i]) - res = -1; - else - res = 1; - break; - } - } - } - } - return res; -} - -/* contains 10^i */ -static const js_limb_t js_pow_dec[JS_LIMB_DIGITS + 1] = { - 1U, - 10U, - 100U, - 1000U, - 10000U, - 100000U, - 1000000U, - 10000000U, - 100000000U, - 1000000000U, -}; - -/* syntax: [-]digits in base radix. Return NULL if memory error. radix - = 10, 2, 8 or 16. */ -static JSBigInt *js_bigint_from_string(JSContext *ctx, - const char *str, int radix) -{ - const char *p = str; - size_t n_digits1; - int is_neg, n_digits, n_limbs, len, log2_radix, n_bits, i; - JSBigInt *r; - js_limb_t v, c, h; - - is_neg = 0; - if (*p == '-') { - is_neg = 1; - p++; - } - while (*p == '0') - p++; - n_digits1 = strlen(p); - /* the real check for overflox is done js_bigint_new(). Here - we just avoid integer overflow */ - if (n_digits1 > JS_BIGINT_MAX_SIZE * JS_LIMB_BITS) { - JS_ThrowRangeError(ctx, "BigInt is too large to allocate"); - return NULL; - } - n_digits = n_digits1; - log2_radix = 32 - clz32(radix - 1); /* ceil(log2(radix)) */ - /* compute the maximum number of limbs */ - if (radix == 10) { - n_bits = (n_digits * 27 + 7) / 8; /* >= ceil(n_digits * log2(10)) */ - } else { - n_bits = n_digits * log2_radix; - } - /* we add one extra bit for the sign */ - n_limbs = max_int(1, n_bits / JS_LIMB_BITS + 1); - r = js_bigint_new(ctx, n_limbs); - if (!r) - return NULL; - if (radix == 10) { - int digits_per_limb = JS_LIMB_DIGITS; - len = 1; - r->tab[0] = 0; - for(;;) { - /* XXX: slow */ - v = 0; - for(i = 0; i < digits_per_limb; i++) { - c = js_to_digit(*p); - if (c >= radix) - break; - p++; - v = v * 10 + c; - } - if (i == 0) - break; - if (len == 1 && r->tab[0] == 0) { - r->tab[0] = v; - } else { - h = js_mp_mul1(r->tab, r->tab, len, js_pow_dec[i], v); - if (h != 0) { - r->tab[len++] = h; - } - } - } - /* add one extra limb to have the correct sign*/ - if ((r->tab[len - 1] >> (JS_LIMB_BITS - 1)) != 0) - r->tab[len++] = 0; - r->len = len; - } else { - unsigned int bit_pos, shift, pos; - - /* power of two base: no multiplication is needed */ - r->len = n_limbs; - memset(r->tab, 0, sizeof(r->tab[0]) * n_limbs); - for(i = 0; i < n_digits; i++) { - c = js_to_digit(p[n_digits - 1 - i]); - assert(c < radix); - bit_pos = i * log2_radix; - shift = bit_pos & (JS_LIMB_BITS - 1); - pos = bit_pos / JS_LIMB_BITS; - r->tab[pos] |= c << shift; - /* if log2_radix does not divide JS_LIMB_BITS, needed an - additional op */ - if (shift + log2_radix > JS_LIMB_BITS) { - r->tab[pos + 1] |= c >> (JS_LIMB_BITS - shift); - } - } - } - r = js_bigint_normalize(ctx, r); - /* XXX: could do it in place */ - if (is_neg) { - JSBigInt *r1; - r1 = js_bigint_neg(ctx, r); - js_free(ctx, r); - r = r1; - } - return r; -} - -/* 2 <= base <= 36 */ -static char const digits[36] = { - '0','1','2','3','4','5','6','7','8','9', - 'a','b','c','d','e','f','g','h','i','j', - 'k','l','m','n','o','p','q','r','s','t', - 'u','v','w','x','y','z' -}; - -/* special version going backwards */ -/* XXX: use dtoa.c */ -static char *js_u64toa(char *q, int64_t n, unsigned int base) -{ - int digit; - if (base == 10) { - /* division by known base uses multiplication */ - do { - digit = (uint64_t)n % 10; - n = (uint64_t)n / 10; - *--q = '0' + digit; - } while (n != 0); - } else { - do { - digit = (uint64_t)n % base; - n = (uint64_t)n / base; - *--q = digits[digit]; - } while (n != 0); - } - return q; -} - -/* len >= 1. 2 <= radix <= 36 */ -static char *js_limb_to_a(char *q, js_limb_t n, unsigned int radix, int len) -{ - int digit, i; - - if (radix == 10) { - /* specific case with constant divisor */ - /* XXX: optimize */ - for(i = 0; i < len; i++) { - digit = (js_limb_t)n % 10; - n = (js_limb_t)n / 10; - *--q = digit + '0'; - } - } else { - for(i = 0; i < len; i++) { - digit = (js_limb_t)n % radix; - n = (js_limb_t)n / radix; - *--q = digits[digit]; - } - } - return q; -} - -#define JS_RADIX_MAX 36 - -static const uint8_t js_digits_per_limb_table[JS_RADIX_MAX - 1] = { -32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, -}; - -static const js_limb_t js_radix_base_table[JS_RADIX_MAX - 1] = { - 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395, - 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91, - 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021, - 0x57f6c100, 0x98c29b81, 0x00000000, 0x18754571, - 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d, - 0x94ace180, 0xcaf18367, 0x0b640000, 0x0e8d4a51, - 0x1269ae40, 0x17179149, 0x1cb91000, 0x23744899, - 0x2b73a840, 0x34e63b41, 0x40000000, 0x4cfa3cc1, - 0x5c13d840, 0x6d91b519, 0x81bf1000, -}; - -static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix) -{ - if (JS_VALUE_GET_TAG(val) == JS_TAG_SHORT_BIG_INT) { - char buf[66]; - int len; - len = i64toa_radix(buf, JS_VALUE_GET_SHORT_BIG_INT(val), radix); - return js_new_string8_len(ctx, buf, len); - } else { - JSBigInt *r, *tmp = NULL; - char *buf, *q, *buf_end; - int is_neg, n_bits, log2_radix, n_digits; - bool is_binary_radix; - JSValue res; - - assert(JS_VALUE_GET_TAG(val) == JS_TAG_BIG_INT); - r = JS_VALUE_GET_PTR(val); - if (r->len == 1 && r->tab[0] == 0) { - /* '0' case */ - return js_new_string8_len(ctx, "0", 1); - } - is_binary_radix = ((radix & (radix - 1)) == 0); - is_neg = js_bigint_sign(r); - if (is_neg) { - tmp = js_bigint_neg(ctx, r); - if (!tmp) - return JS_EXCEPTION; - r = tmp; - } else if (!is_binary_radix) { - /* need to modify 'r' */ - tmp = js_bigint_new(ctx, r->len); - if (!tmp) - return JS_EXCEPTION; - memcpy(tmp->tab, r->tab, r->len * sizeof(r->tab[0])); - r = tmp; - } - log2_radix = 31 - clz32(radix); /* floor(log2(radix)) */ - n_bits = r->len * JS_LIMB_BITS - js_limb_clz(r->tab[r->len - 1]); - /* n_digits is exact only if radix is a power of - two. Otherwise it is >= the exact number of digits */ - n_digits = (n_bits + log2_radix - 1) / log2_radix; - /* XXX: could directly build the JSString */ - buf = js_malloc(ctx, n_digits + is_neg + 1); - if (!buf) { - js_free(ctx, tmp); - return JS_EXCEPTION; - } - q = buf + n_digits + is_neg + 1; - *--q = '\0'; - buf_end = q; - if (!is_binary_radix) { - int len; - js_limb_t radix_base, v; - radix_base = js_radix_base_table[radix - 2]; - len = r->len; - for(;;) { - /* remove leading zero limbs */ - while (len > 1 && r->tab[len - 1] == 0) - len--; - if (len == 1 && r->tab[0] < radix_base) { - v = r->tab[0]; - if (v != 0) { - q = js_u64toa(q, v, radix); - } - break; - } else { - v = js_mp_div1(r->tab, r->tab, len, radix_base, 0); - q = js_limb_to_a(q, v, radix, js_digits_per_limb_table[radix - 2]); - } - } - } else { - int i, shift; - unsigned int bit_pos, pos, c; - - /* radix is a power of two */ - for(i = 0; i < n_digits; i++) { - bit_pos = i * log2_radix; - pos = bit_pos / JS_LIMB_BITS; - shift = bit_pos % JS_LIMB_BITS; - c = r->tab[pos] >> shift; - if ((shift + log2_radix) > JS_LIMB_BITS && - (pos + 1) < r->len) { - c |= r->tab[pos + 1] << (JS_LIMB_BITS - shift); - } - c &= (radix - 1); - *--q = digits[c]; - } - } - if (is_neg) - *--q = '-'; - js_free(ctx, tmp); - res = js_new_string8_len(ctx, q, buf_end - q); - js_free(ctx, buf); - return res; - } -} - -/* if possible transform a BigInt to short big and free it, otherwise - return a normal bigint */ -static JSValue JS_CompactBigInt(JSContext *ctx, JSBigInt *p) -{ - JSValue res; - if (p->len == 1) { - res = __JS_NewShortBigInt(ctx, (js_slimb_t)p->tab[0]); - js_free(ctx, p); - return res; - } else { - return JS_MKPTR(JS_TAG_BIG_INT, p); - } -} - -#define ATOD_INT_ONLY (1 << 0) -/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ -#define ATOD_ACCEPT_BIN_OCT (1 << 2) -/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ -#define ATOD_ACCEPT_LEGACY_OCTAL (1 << 4) -/* accept _ between digits as a digit separator */ -#define ATOD_ACCEPT_UNDERSCORES (1 << 5) -/* allow a suffix to override the type */ -#define ATOD_ACCEPT_SUFFIX (1 << 6) -/* default type */ -#define ATOD_TYPE_MASK (3 << 7) -#define ATOD_TYPE_FLOAT64 (0 << 7) -#define ATOD_TYPE_BIG_INT (1 << 7) -/* accept -0x1 */ -#define ATOD_ACCEPT_PREFIX_AFTER_SIGN (1 << 10) - -/* return an exception in case of memory error. Return JS_NAN if - invalid syntax */ -/* XXX: directly use js_atod() */ -static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, - int radix, int flags) -{ - const char *p, *p_start; - int sep, is_neg; - bool is_float, has_legacy_octal; - int atod_type = flags & ATOD_TYPE_MASK; - char buf1[64], *buf; - int i, j, len; - bool buf_allocated = false; - JSValue val; - JSATODTempMem atod_mem; - - /* optional separator between digits */ - sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; - has_legacy_octal = false; - - p = str; - p_start = p; - is_neg = 0; - if (p[0] == '+') { - p++; - p_start++; - if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) - goto no_radix_prefix; - } else if (p[0] == '-') { - p++; - p_start++; - is_neg = 1; - if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) - goto no_radix_prefix; - } - if (p[0] == '0') { - if ((p[1] == 'x' || p[1] == 'X') && - (radix == 0 || radix == 16)) { - p += 2; - radix = 16; - } else if ((p[1] == 'o' || p[1] == 'O') && - radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { - p += 2; - radix = 8; - } else if ((p[1] == 'b' || p[1] == 'B') && - radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { - p += 2; - radix = 2; - } else if ((p[1] >= '0' && p[1] <= '9') && - radix == 0 && (flags & ATOD_ACCEPT_LEGACY_OCTAL)) { - int i; - has_legacy_octal = true; - sep = 256; - for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) - continue; - if (p[i] == '8' || p[i] == '9') - goto no_prefix; - p += 1; - radix = 8; - } else { - goto no_prefix; - } - /* there must be a digit after the prefix */ - if (js_to_digit((uint8_t)*p) >= radix) - goto fail; - no_prefix: ; - } else { - no_radix_prefix: - if (!(flags & ATOD_INT_ONLY) && - (atod_type == ATOD_TYPE_FLOAT64) && - js__strstart(p, "Infinity", &p)) { - double d = INF; - if (is_neg) - d = -d; - val = js_float64(d); - goto done; - } - } - if (radix == 0) - radix = 10; - is_float = false; - p_start = p; - while (js_to_digit((uint8_t)*p) < radix - || (*p == sep && (radix != 10 || - p != p_start + 1 || p[-1] != '0') && - js_to_digit((uint8_t)p[1]) < radix)) { - p++; - } - if (!(flags & ATOD_INT_ONLY) && radix == 10) { - if (*p == '.' && (p > p_start || js_to_digit((uint8_t)p[1]) < radix)) { - is_float = true; - p++; - if (*p == sep) - goto fail; - while (js_to_digit((uint8_t)*p) < radix || - (*p == sep && js_to_digit((uint8_t)p[1]) < radix)) - p++; - } - if (p > p_start && (*p == 'e' || *p == 'E')) { - const char *p1 = p + 1; - is_float = true; - if (*p1 == '+') { - p1++; - } else if (*p1 == '-') { - p1++; - } - if (is_digit((uint8_t)*p1)) { - p = p1 + 1; - while (is_digit((uint8_t)*p) || (*p == sep && is_digit((uint8_t)p[1]))) - p++; - } - } - } - if (p == p_start) - goto fail; - - buf = buf1; - buf_allocated = false; - len = p - p_start; - if (unlikely((len + 2) > sizeof(buf1))) { - buf = js_malloc_rt(ctx->rt, len + 2); /* no exception raised */ - if (!buf) - goto mem_error; - buf_allocated = true; - } - /* remove the separators and the radix prefixes */ - j = 0; - if (is_neg) - buf[j++] = '-'; - for (i = 0; i < len; i++) { - if (p_start[i] != '_') - buf[j++] = p_start[i]; - } - buf[j] = '\0'; - - if (flags & ATOD_ACCEPT_SUFFIX) { - if (*p == 'n') { - p++; - atod_type = ATOD_TYPE_BIG_INT; - } - } - - switch(atod_type) { - case ATOD_TYPE_FLOAT64: - { - double d; - d = js_atod(buf, NULL, radix, is_float ? 0 : JS_ATOD_INT_ONLY, - &atod_mem); - /* return int or float64 */ - val = js_number(d); - } - break; - case ATOD_TYPE_BIG_INT: - { - JSBigInt *r; - if (has_legacy_octal || is_float) - goto fail; - r = js_bigint_from_string(ctx, buf, radix); - if (!r) { - val = JS_EXCEPTION; - goto done; - } - val = JS_CompactBigInt(ctx, r); - } - break; - default: - abort(); - } - -done: - if (buf_allocated) - js_free_rt(ctx->rt, buf); - if (pp) - *pp = p; - return val; - fail: - val = JS_NAN; - goto done; - mem_error: - val = JS_ThrowOutOfMemory(ctx); - goto done; -} - -typedef enum JSToNumberHintEnum { - TON_FLAG_NUMBER, - TON_FLAG_NUMERIC, -} JSToNumberHintEnum; - -static JSValue JS_ToNumberHintFree(JSContext *ctx, JSValue val, - JSToNumberHintEnum flag) -{ - uint32_t tag; - JSValue ret; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_BIG_INT: - case JS_TAG_SHORT_BIG_INT: - if (flag != TON_FLAG_NUMERIC) { - JS_FreeValue(ctx, val); - return JS_ThrowTypeError(ctx, "cannot convert BigInt to number"); - } - ret = val; - break; - case JS_TAG_FLOAT64: - case JS_TAG_INT: - case JS_TAG_EXCEPTION: - ret = val; - break; - case JS_TAG_BOOL: - case JS_TAG_NULL: - ret = js_int32(JS_VALUE_GET_INT(val)); - break; - case JS_TAG_UNDEFINED: - ret = JS_NAN; - break; - case JS_TAG_OBJECT: - val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); - if (JS_IsException(val)) - return JS_EXCEPTION; - goto redo; - case JS_TAG_STRING: - { - const char *str; - const char *p; - size_t len; - - str = JS_ToCStringLen(ctx, &len, val); - JS_FreeValue(ctx, val); - if (!str) - return JS_EXCEPTION; - p = str; - p += skip_spaces(p); - if ((p - str) == len) { - ret = JS_NewInt32(ctx, 0); - } else { - int flags = ATOD_ACCEPT_BIN_OCT; - ret = js_atof(ctx, p, &p, 0, flags); - if (!JS_IsException(ret)) { - p += skip_spaces(p); - if ((p - str) != len) { - JS_FreeValue(ctx, ret); - ret = JS_NAN; - } - } - } - JS_FreeCString(ctx, str); - } - break; - case JS_TAG_SYMBOL: - JS_FreeValue(ctx, val); - return JS_ThrowTypeError(ctx, "cannot convert symbol to number"); - default: - JS_FreeValue(ctx, val); - ret = JS_NAN; - break; - } - return ret; -} - -static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val) -{ - return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMBER); -} - -static JSValue JS_ToNumericFree(JSContext *ctx, JSValue val) -{ - return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMERIC); -} - -static JSValue JS_ToNumeric(JSContext *ctx, JSValueConst val) -{ - return JS_ToNumericFree(ctx, js_dup(val)); -} - -static __exception int __JS_ToFloat64Free(JSContext *ctx, double *pres, - JSValue val) -{ - double d; - uint32_t tag; - - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) - goto fail; - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - d = JS_VALUE_GET_INT(val); - break; - case JS_TAG_FLOAT64: - d = JS_VALUE_GET_FLOAT64(val); - break; - default: - abort(); - } - *pres = d; - return 0; -fail: - *pres = NAN; - return -1; -} - -static inline int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val) -{ - uint32_t tag; - - tag = JS_VALUE_GET_TAG(val); - if (tag <= JS_TAG_NULL) { - *pres = JS_VALUE_GET_INT(val); - return 0; - } else if (JS_TAG_IS_FLOAT64(tag)) { - *pres = JS_VALUE_GET_FLOAT64(val); - return 0; - } else { - return __JS_ToFloat64Free(ctx, pres, val); - } -} - -int JS_ToFloat64(JSContext *ctx, double *pres, JSValueConst val) -{ - return JS_ToFloat64Free(ctx, pres, js_dup(val)); -} - -JSValue JS_ToNumber(JSContext *ctx, JSValueConst val) -{ - return JS_ToNumberFree(ctx, js_dup(val)); -} - -/* same as JS_ToNumber() but return 0 in case of NaN/Undefined */ -static __maybe_unused JSValue JS_ToIntegerFree(JSContext *ctx, JSValue val) -{ - uint32_t tag; - JSValue ret; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - ret = js_int32(JS_VALUE_GET_INT(val)); - break; - case JS_TAG_FLOAT64: - { - double d = JS_VALUE_GET_FLOAT64(val); - if (isnan(d)) { - ret = js_int32(0); - } else { - /* convert -0 to +0 */ - d = trunc(d) + 0.0; - ret = js_number(d); - } - } - break; - default: - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) - return val; - goto redo; - } - return ret; -} - -/* Note: the integer value is satured to 32 bits */ -static int JS_ToInt32SatFree(JSContext *ctx, int *pres, JSValue val) -{ - uint32_t tag; - int ret; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - ret = JS_VALUE_GET_INT(val); - break; - case JS_TAG_EXCEPTION: - *pres = 0; - return -1; - case JS_TAG_FLOAT64: - { - double d = JS_VALUE_GET_FLOAT64(val); - if (isnan(d)) { - ret = 0; - } else { - if (d < INT32_MIN) - ret = INT32_MIN; - else if (d > INT32_MAX) - ret = INT32_MAX; - else - ret = (int)d; - } - } - break; - default: - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) { - *pres = 0; - return -1; - } - goto redo; - } - *pres = ret; - return 0; -} - -static int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValueConst val) -{ - return JS_ToInt32SatFree(ctx, pres, js_dup(val)); -} - -static int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValueConst val, - int min, int max, int min_offset) -{ - int res = JS_ToInt32SatFree(ctx, pres, js_dup(val)); - if (res == 0) { - if (*pres < min) { - *pres += min_offset; - if (*pres < min) - *pres = min; - } else { - if (*pres > max) - *pres = max; - } - } - return res; -} - -static int JS_ToInt64SatFree(JSContext *ctx, int64_t *pres, JSValue val) -{ - uint32_t tag; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - *pres = JS_VALUE_GET_INT(val); - return 0; - case JS_TAG_EXCEPTION: - *pres = 0; - return -1; - case JS_TAG_FLOAT64: - { - double d = JS_VALUE_GET_FLOAT64(val); - if (isnan(d)) { - *pres = 0; - } else { - if (d < INT64_MIN) - *pres = INT64_MIN; - else if (d >= 0x1p63) - *pres = INT64_MAX; - else - *pres = (int64_t)d; - } - } - return 0; - default: - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) { - *pres = 0; - return -1; - } - goto redo; - } -} - -int JS_ToInt64Sat(JSContext *ctx, int64_t *pres, JSValueConst val) -{ - return JS_ToInt64SatFree(ctx, pres, js_dup(val)); -} - -int JS_ToInt64Clamp(JSContext *ctx, int64_t *pres, JSValueConst val, - int64_t min, int64_t max, int64_t neg_offset) -{ - int res = JS_ToInt64SatFree(ctx, pres, js_dup(val)); - if (res == 0) { - if (*pres < 0) - *pres += neg_offset; - if (*pres < min) - *pres = min; - else if (*pres > max) - *pres = max; - } - return res; -} - -/* Same as JS_ToInt32Free() but with a 64 bit result. Return (<0, 0) - in case of exception */ -static int JS_ToInt64Free(JSContext *ctx, int64_t *pres, JSValue val) -{ - uint32_t tag; - int64_t ret; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - ret = JS_VALUE_GET_INT(val); - break; - case JS_TAG_FLOAT64: - { - JSFloat64Union u; - double d; - int e; - d = JS_VALUE_GET_FLOAT64(val); - u.d = d; - /* we avoid doing fmod(x, 2^64) */ - e = (u.u64 >> 52) & 0x7ff; - if (likely(e <= (1023 + 62))) { - /* fast case */ - ret = (int64_t)d; - } else if (e <= (1023 + 62 + 53)) { - uint64_t v; - /* remainder modulo 2^64 */ - v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); - ret = v << ((e - 1023) - 52); - /* take the sign into account */ - if (u.u64 >> 63) - if (ret != INT64_MIN) - ret = -ret; - } else { - ret = 0; /* also handles NaN and +inf */ - } - } - break; - default: - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) { - *pres = 0; - return -1; - } - goto redo; - } - *pres = ret; - return 0; -} - -int JS_ToInt64(JSContext *ctx, int64_t *pres, JSValueConst val) -{ - return JS_ToInt64Free(ctx, pres, js_dup(val)); -} - -int JS_ToInt64Ext(JSContext *ctx, int64_t *pres, JSValueConst val) -{ - if (JS_IsBigInt(val)) - return JS_ToBigInt64(ctx, pres, val); - else - return JS_ToInt64(ctx, pres, val); -} - -/* return (<0, 0) in case of exception */ -static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val) -{ - uint32_t tag; - int32_t ret; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - ret = JS_VALUE_GET_INT(val); - break; - case JS_TAG_FLOAT64: - { - JSFloat64Union u; - double d; - int e; - d = JS_VALUE_GET_FLOAT64(val); - u.d = d; - /* we avoid doing fmod(x, 2^32) */ - e = (u.u64 >> 52) & 0x7ff; - if (likely(e <= (1023 + 30))) { - /* fast case */ - ret = (int32_t)d; - } else if (e <= (1023 + 30 + 53)) { - uint64_t v; - /* remainder modulo 2^32 */ - v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); - v = v << ((e - 1023) - 52 + 32); - ret = v >> 32; - /* take the sign into account */ - if (u.u64 >> 63) - if (ret != INT32_MIN) - ret = -ret; - } else { - ret = 0; /* also handles NaN and +inf */ - } - } - break; - default: - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) { - *pres = 0; - return -1; - } - goto redo; - } - *pres = ret; - return 0; -} - -int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValueConst val) -{ - return JS_ToInt32Free(ctx, pres, js_dup(val)); -} - -static inline int JS_ToUint32Free(JSContext *ctx, uint32_t *pres, JSValue val) -{ - return JS_ToInt32Free(ctx, (int32_t *)pres, val); -} - -static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val) -{ - uint32_t tag; - int res; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - res = JS_VALUE_GET_INT(val); - res = max_int(0, min_int(255, res)); - break; - case JS_TAG_FLOAT64: - { - double d = JS_VALUE_GET_FLOAT64(val); - if (isnan(d)) { - res = 0; - } else { - if (d < 0) - res = 0; - else if (d > 255) - res = 255; - else - res = lrint(d); - } - } - break; - default: - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) { - *pres = 0; - return -1; - } - goto redo; - } - *pres = res; - return 0; -} - -static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, - JSValue val, bool is_array_ctor) -{ - uint32_t tag, len; - - tag = JS_VALUE_GET_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - { - int v; - v = JS_VALUE_GET_INT(val); - if (v < 0) - goto fail; - len = v; - } - break; - default: - if (JS_TAG_IS_FLOAT64(tag)) { - double d; - d = JS_VALUE_GET_FLOAT64(val); - if (!(d >= 0 && d <= UINT32_MAX)) - goto fail; - len = (uint32_t)d; - if (len != d) - goto fail; - } else { - uint32_t len1; - - if (is_array_ctor) { - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) - return -1; - /* cannot recurse because val is a number */ - if (JS_ToArrayLengthFree(ctx, &len, val, true)) - return -1; - } else { - /* legacy behavior: must do the conversion twice and compare */ - if (JS_ToUint32(ctx, &len, val)) { - JS_FreeValue(ctx, val); - return -1; - } - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) - return -1; - /* cannot recurse because val is a number */ - if (JS_ToArrayLengthFree(ctx, &len1, val, false)) - return -1; - if (len1 != len) { - fail: - JS_ThrowRangeError(ctx, "invalid array length"); - return -1; - } - } - } - break; - } - *plen = len; - return 0; -} - -#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1) - -static bool is_safe_integer(double d) -{ - return isfinite(d) && floor(d) == d && - fabs(d) <= (double)MAX_SAFE_INTEGER; -} - -int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValueConst val) -{ - int64_t v; - if (JS_ToInt64Sat(ctx, &v, val)) - return -1; - if (v < 0 || v > MAX_SAFE_INTEGER) { - JS_ThrowRangeError(ctx, "invalid array index"); - *plen = 0; - return -1; - } - *plen = v; - return 0; -} - -/* convert a value to a length between 0 and MAX_SAFE_INTEGER. - return -1 for exception */ -static __exception int JS_ToLengthFree(JSContext *ctx, int64_t *plen, - JSValue val) -{ - int res = JS_ToInt64Clamp(ctx, plen, val, 0, MAX_SAFE_INTEGER, 0); - JS_FreeValue(ctx, val); - return res; -} - -/* Note: can return an exception */ -static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val) -{ - double d; - if (!JS_IsNumber(val)) - return false; - if (unlikely(JS_ToFloat64(ctx, &d, val))) - return -1; - return isfinite(d) && floor(d) == d; -} - -static bool JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val) -{ - uint32_t tag; - - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - { - int v; - v = JS_VALUE_GET_INT(val); - return (v < 0); - } - case JS_TAG_FLOAT64: - { - JSFloat64Union u; - u.d = JS_VALUE_GET_FLOAT64(val); - return (u.u64 >> 63); - } - case JS_TAG_SHORT_BIG_INT: - return (JS_VALUE_GET_SHORT_BIG_INT(val) < 0); - case JS_TAG_BIG_INT: - { - JSBigInt *p = JS_VALUE_GET_PTR(val); - return js_bigint_sign(p); - } - default: - return false; - } -} - -static JSValue js_bigint_to_string(JSContext *ctx, JSValueConst val) -{ - return js_bigint_to_string1(ctx, val, 10); -} - -/*---- floating point number to string conversions ----*/ - -static JSValue js_dtoa2(JSContext *ctx, - double d, int radix, int n_digits, int flags) -{ - char static_buf[128], *buf, *tmp_buf; - int len, len_max; - JSValue res; - JSDTOATempMem dtoa_mem; - len_max = js_dtoa_max_len(d, radix, n_digits, flags); - - /* longer buffer may be used if radix != 10 */ - if (len_max > sizeof(static_buf) - 1) { - tmp_buf = js_malloc(ctx, len_max + 1); - if (!tmp_buf) - return JS_EXCEPTION; - buf = tmp_buf; - } else { - tmp_buf = NULL; - buf = static_buf; - } - len = js_dtoa(buf, d, radix, n_digits, flags, &dtoa_mem); - res = js_new_string8_len(ctx, buf, len); - js_free(ctx, tmp_buf); - return res; -} - -static JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, - int flags) -{ - uint32_t tag; - char buf[32]; - size_t len; - - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_STRING: - return js_dup(val); - case JS_TAG_INT: - len = i32toa(buf, JS_VALUE_GET_INT(val)); - return js_new_string8_len(ctx, buf, len); - case JS_TAG_BOOL: - return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ? - JS_ATOM_true : JS_ATOM_false); - case JS_TAG_NULL: - return JS_AtomToString(ctx, JS_ATOM_null); - case JS_TAG_UNDEFINED: - return JS_AtomToString(ctx, JS_ATOM_undefined); - case JS_TAG_EXCEPTION: - return JS_EXCEPTION; - case JS_TAG_OBJECT: - if (flags & JS_TO_STRING_NO_SIDE_EFFECTS) { - return js_new_string8(ctx, "{}"); - } else { - JSValue val1, ret; - val1 = JS_ToPrimitive(ctx, val, HINT_STRING); - if (JS_IsException(val1)) - return val1; - ret = JS_ToStringInternal(ctx, val1, flags); - JS_FreeValue(ctx, val1); - return ret; - } - break; - case JS_TAG_FUNCTION_BYTECODE: - return js_new_string8(ctx, "[function bytecode]"); - case JS_TAG_SYMBOL: - if (flags & JS_TO_STRING_IS_PROPERTY_KEY) { - return js_dup(val); - } else { - return JS_ThrowTypeError(ctx, "cannot convert symbol to string"); - } - case JS_TAG_FLOAT64: - return js_dtoa2(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0, - JS_DTOA_FORMAT_FREE); - case JS_TAG_SHORT_BIG_INT: - case JS_TAG_BIG_INT: - return js_bigint_to_string(ctx, val); - case JS_TAG_UNINITIALIZED: - return js_new_string8(ctx, "[uninitialized]"); - default: - return js_new_string8(ctx, "[unsupported type]"); - } -} - -JSValue JS_ToString(JSContext *ctx, JSValueConst val) -{ - return JS_ToStringInternal(ctx, val, /*flags*/0); -} - -static JSValue JS_ToStringFree(JSContext *ctx, JSValue val) -{ - JSValue ret; - ret = JS_ToString(ctx, val); - JS_FreeValue(ctx, val); - return ret; -} - -static JSValue JS_ToLocaleStringFree(JSContext *ctx, JSValue val) -{ - if (JS_IsUndefined(val) || JS_IsNull(val)) - return JS_ToStringFree(ctx, val); - return JS_InvokeFree(ctx, val, JS_ATOM_toLocaleString, 0, NULL); -} - -static JSValue JS_ToPropertyKeyInternal(JSContext *ctx, JSValueConst val, - int flags) -{ - return JS_ToStringInternal(ctx, val, flags | JS_TO_STRING_IS_PROPERTY_KEY); -} - -JSValue JS_ToPropertyKey(JSContext *ctx, JSValueConst val) -{ - return JS_ToPropertyKeyInternal(ctx, val, /*flags*/0); -} - -static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValueConst val) -{ - uint32_t tag = JS_VALUE_GET_TAG(val); - if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) - return JS_ThrowTypeError(ctx, "null or undefined are forbidden"); - return JS_ToString(ctx, val); -} - -static JSValue JS_ToQuotedString(JSContext *ctx, JSValueConst val1) -{ - JSValue val; - JSString *p; - int i; - uint32_t c; - StringBuffer b_s, *b = &b_s; - char buf[16]; - - val = JS_ToStringCheckObject(ctx, val1); - if (JS_IsException(val)) - return val; - p = JS_VALUE_GET_STRING(val); - - if (string_buffer_init(ctx, b, p->len + 2)) - goto fail; - - if (string_buffer_putc8(b, '\"')) - goto fail; - for(i = 0; i < p->len; ) { - c = string_getc(p, &i); - switch(c) { - case '\t': - c = 't'; - goto quote; - case '\r': - c = 'r'; - goto quote; - case '\n': - c = 'n'; - goto quote; - case '\b': - c = 'b'; - goto quote; - case '\f': - c = 'f'; - goto quote; - case '\"': - case '\\': - quote: - if (string_buffer_putc8(b, '\\')) - goto fail; - if (string_buffer_putc8(b, c)) - goto fail; - break; - default: - if (c < 32 || is_surrogate(c)) { - snprintf(buf, sizeof(buf), "\\u%04x", c); - if (string_buffer_write8(b, (uint8_t*)buf, 6)) - goto fail; - } else { - if (string_buffer_putc(b, c)) - goto fail; - } - break; - } - } - if (string_buffer_putc8(b, '\"')) - goto fail; - JS_FreeValue(ctx, val); - return string_buffer_end(b); - fail: - JS_FreeValue(ctx, val); - string_buffer_free(b); - return JS_EXCEPTION; -} - -static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt) -{ - printf("%14s %4s %4s %14s %10s %s\n", - "ADDRESS", "REFS", "SHRF", "PROTO", "CLASS", "PROPS"); -} - -/* for debug only: dump an object without side effect */ -static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) -{ - uint32_t i; - char atom_buf[ATOM_GET_STR_BUF_SIZE]; - JSShape *sh; - JSShapeProperty *prs; - JSProperty *pr; - bool is_first = true; - - /* XXX: should encode atoms with special characters */ - sh = p->shape; /* the shape can be NULL while freeing an object */ - printf("%14p %4d ", - (void *)p, - p->header.ref_count); - if (sh) { - printf("%3d%c %14p ", - sh->header.ref_count, - " *"[sh->is_hashed], - (void *)sh->proto); - } else { - printf("%3s %14s ", "-", "-"); - } - printf("%10s ", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), rt->class_array[p->class_id].class_name)); - if (p->is_exotic && p->fast_array) { - printf("[ "); - for(i = 0; i < p->u.array.count; i++) { - if (i != 0) - printf(", "); - switch (p->class_id) { - case JS_CLASS_ARRAY: - case JS_CLASS_ARGUMENTS: - JS_DumpValue(rt, p->u.array.u.values[i]); - break; - case JS_CLASS_UINT8C_ARRAY: - case JS_CLASS_INT8_ARRAY: - case JS_CLASS_UINT8_ARRAY: - case JS_CLASS_INT16_ARRAY: - case JS_CLASS_UINT16_ARRAY: - case JS_CLASS_INT32_ARRAY: - case JS_CLASS_UINT32_ARRAY: - case JS_CLASS_BIG_INT64_ARRAY: - case JS_CLASS_BIG_UINT64_ARRAY: - case JS_CLASS_FLOAT16_ARRAY: - case JS_CLASS_FLOAT32_ARRAY: - case JS_CLASS_FLOAT64_ARRAY: - { - int size = 1 << typed_array_size_log2(p->class_id); - const uint8_t *b = p->u.array.u.uint8_ptr + i * size; - while (size-- > 0) - printf("%02X", *b++); - } - break; - } - } - printf(" ] "); - } - - if (sh) { - printf("{ "); - for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { - if (prs->atom != JS_ATOM_NULL) { - pr = &p->prop[i]; - if (!is_first) - printf(", "); - printf("%s: ", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), prs->atom)); - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - printf("[getset %p %p]", (void *)pr->u.getset.getter, - (void *)pr->u.getset.setter); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - printf("[varref %p]", (void *)pr->u.var_ref); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - printf("[autoinit %p %d %p]", - (void *)js_autoinit_get_realm(pr), - js_autoinit_get_id(pr), - (void *)pr->u.init.opaque); - } else { - JS_DumpValue(rt, pr->u.value); - } - is_first = false; - } - } - printf(" }"); - } - - if (js_class_has_bytecode(p->class_id)) { - JSFunctionBytecode *b = p->u.func.function_bytecode; - JSVarRef **var_refs; - if (b->closure_var_count) { - var_refs = p->u.func.var_refs; - printf(" Closure:"); - for(i = 0; i < b->closure_var_count; i++) { - printf(" "); - JS_DumpValue(rt, var_refs[i]->value); - } - if (p->u.func.home_object) { - printf(" HomeObject: "); - JS_DumpValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object)); - } - } - } - printf("\n"); -} - -static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) -{ - if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { - JS_DumpObject(rt, (JSObject *)p); - } else { - printf("%14p %4d ", - (void *)p, - p->ref_count); - switch(p->gc_obj_type) { - case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: - printf("[function bytecode]"); - break; - case JS_GC_OBJ_TYPE_SHAPE: - printf("[shape]"); - break; - case JS_GC_OBJ_TYPE_VAR_REF: - printf("[var_ref]"); - break; - case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: - printf("[async_function]"); - break; - case JS_GC_OBJ_TYPE_JS_CONTEXT: - printf("[js_context]"); - break; - default: - printf("[unknown %d]", p->gc_obj_type); - break; - } - printf("\n"); - } -} - -static __maybe_unused void JS_DumpValue(JSRuntime *rt, JSValueConst val) -{ - uint32_t tag = JS_VALUE_GET_NORM_TAG(val); - const char *str; - - switch(tag) { - case JS_TAG_INT: - printf("%d", JS_VALUE_GET_INT(val)); - break; - case JS_TAG_BOOL: - if (JS_VALUE_GET_BOOL(val)) - str = "true"; - else - str = "false"; - goto print_str; - case JS_TAG_NULL: - str = "null"; - goto print_str; - case JS_TAG_EXCEPTION: - str = "exception"; - goto print_str; - case JS_TAG_UNINITIALIZED: - str = "uninitialized"; - goto print_str; - case JS_TAG_UNDEFINED: - str = "undefined"; - print_str: - printf("%s", str); - break; - case JS_TAG_FLOAT64: - printf("%.14g", JS_VALUE_GET_FLOAT64(val)); - break; - case JS_TAG_SHORT_BIG_INT: - printf("%" PRId64 "n", (int64_t)JS_VALUE_GET_SHORT_BIG_INT(val)); - break; - case JS_TAG_BIG_INT: - { - JSBigInt *p = JS_VALUE_GET_PTR(val); - int sgn, i; - /* In order to avoid allocations we just dump the limbs */ - sgn = js_bigint_sign(p); - if (sgn) - printf("BigInt.asIntN(%d,", p->len * JS_LIMB_BITS); - printf("0x"); - for(i = p->len - 1; i >= 0; i--) { - if (i != p->len - 1) - printf("_"); - printf("%08x", p->tab[i]); - } - printf("n"); - if (sgn) - printf(")"); - } - break; - case JS_TAG_STRING: - { - JSString *p; - p = JS_VALUE_GET_STRING(val); - JS_DumpString(rt, p); - } - break; - case JS_TAG_FUNCTION_BYTECODE: - { - JSFunctionBytecode *b = JS_VALUE_GET_PTR(val); - char buf[ATOM_GET_STR_BUF_SIZE]; - if (b->func_name) { - printf("[bytecode %s]", JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name)); - } else { - printf("[bytecode (anonymous)]"); - } - } - break; - case JS_TAG_OBJECT: - { - JSObject *p = JS_VALUE_GET_OBJ(val); - JSAtom atom = rt->class_array[p->class_id].class_name; - char atom_buf[ATOM_GET_STR_BUF_SIZE]; - printf("[%s %p]", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), atom), (void *)p); - } - break; - case JS_TAG_SYMBOL: - { - JSAtomStruct *p = JS_VALUE_GET_PTR(val); - char atom_buf[ATOM_GET_STR_BUF_SIZE]; - printf("Symbol(%s)", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), js_get_atom_index(rt, p))); - } - break; - case JS_TAG_MODULE: - printf("[module]"); - break; - default: - printf("[unknown tag %d]", tag); - break; - } -} - -bool JS_IsArray(JSValueConst val) -{ - if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { - JSObject *p = JS_VALUE_GET_OBJ(val); - return p->class_id == JS_CLASS_ARRAY; - } - return false; -} - -/* return -1 if exception (proxy case) or true/false */ -static int js_is_array(JSContext *ctx, JSValueConst val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { - p = JS_VALUE_GET_OBJ(val); - if (unlikely(p->class_id == JS_CLASS_PROXY)) - return js_proxy_isArray(ctx, val); - else - return p->class_id == JS_CLASS_ARRAY; - } else { - return false; - } -} - -static double js_math_pow(double a, double b) -{ - double d; - - if (unlikely(!isfinite(b)) && fabs(a) == 1) { - /* not compatible with IEEE 754 */ - d = NAN; - } else { - JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); - d = pow(a, b); - JS_X87_FPCW_RESTORE(fpcw); - } - return d; -} - -JSValue JS_NewBigInt64(JSContext *ctx, int64_t v) -{ - if (v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX) { - return __JS_NewShortBigInt(ctx, v); - } else { - JSBigInt *p; - p = js_bigint_new_si64(ctx, v); - if (!p) - return JS_EXCEPTION; - return JS_MKPTR(JS_TAG_BIG_INT, p); - } -} - -JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v) -{ - if (v <= JS_SHORT_BIG_INT_MAX) { - return __JS_NewShortBigInt(ctx, v); - } else { - JSBigInt *p; - p = js_bigint_new_ui64(ctx, v); - if (!p) - return JS_EXCEPTION; - return JS_MKPTR(JS_TAG_BIG_INT, p); - } -} - -/* return NaN if bad bigint literal */ -static JSValue JS_StringToBigInt(JSContext *ctx, JSValue val) -{ - const char *str, *p; - size_t len; - int flags; - - str = JS_ToCStringLen(ctx, &len, val); - JS_FreeValue(ctx, val); - if (!str) - return JS_EXCEPTION; - p = str; - p += skip_spaces(p); - if ((p - str) == len) { - val = JS_NewBigInt64(ctx, 0); - } else { - flags = ATOD_INT_ONLY | ATOD_ACCEPT_BIN_OCT | ATOD_TYPE_BIG_INT; - val = js_atof(ctx, p, &p, 0, flags); - p += skip_spaces(p); - if (!JS_IsException(val)) { - if ((p - str) != len) { - JS_FreeValue(ctx, val); - val = JS_NAN; - } - } - } - JS_FreeCString(ctx, str); - return val; -} - -static JSValue JS_StringToBigIntErr(JSContext *ctx, JSValue val) -{ - val = JS_StringToBigInt(ctx, val); - if (JS_VALUE_IS_NAN(val)) - return JS_ThrowSyntaxError(ctx, "invalid BigInt literal"); - return val; -} - -/* JS Numbers are not allowed */ -static JSValue JS_ToBigIntFree(JSContext *ctx, JSValue val) -{ - uint32_t tag; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_SHORT_BIG_INT: - case JS_TAG_BIG_INT: - break; - case JS_TAG_INT: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - case JS_TAG_FLOAT64: - goto fail; - case JS_TAG_BOOL: - val = __JS_NewShortBigInt(ctx, JS_VALUE_GET_INT(val)); - break; - case JS_TAG_STRING: - val = JS_StringToBigIntErr(ctx, val); - if (JS_IsException(val)) - return val; - goto redo; - case JS_TAG_OBJECT: - val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); - if (JS_IsException(val)) - return val; - goto redo; - default: - fail: - JS_FreeValue(ctx, val); - return JS_ThrowTypeError(ctx, "cannot convert to bigint"); - } - return val; -} - -static JSValue JS_ToBigInt(JSContext *ctx, JSValueConst val) -{ - return JS_ToBigIntFree(ctx, js_dup(val)); -} - -/* XXX: merge with JS_ToInt64Free with a specific flag */ -static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val) -{ - uint64_t res; - - val = JS_ToBigIntFree(ctx, val); - if (JS_IsException(val)) { - *pres = 0; - return -1; - } - if (JS_VALUE_GET_TAG(val) == JS_TAG_SHORT_BIG_INT) { - res = JS_VALUE_GET_SHORT_BIG_INT(val); - } else { - JSBigInt *p = JS_VALUE_GET_PTR(val); - /* return the value mod 2^64 */ - res = p->tab[0]; - if (p->len >= 2) - res |= (uint64_t)p->tab[1] << 32; - JS_FreeValue(ctx, val); - } - *pres = res; - return 0; -} - -int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValueConst val) -{ - return JS_ToBigInt64Free(ctx, pres, js_dup(val)); -} - -int JS_ToBigUint64(JSContext *ctx, uint64_t *pres, JSValueConst val) -{ - return JS_ToBigInt64Free(ctx, (int64_t *)pres, js_dup(val)); -} - -static no_inline __exception int js_unary_arith_slow(JSContext *ctx, - JSValue *sp, - OPCodeEnum op) -{ - JSValue op1; - int v; - uint32_t tag; - JSBigIntBuf buf1; - JSBigInt *p1; - - op1 = sp[-1]; - /* fast path for float64 */ - if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) - goto handle_float64; - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) - goto exception; - tag = JS_VALUE_GET_TAG(op1); - switch(tag) { - case JS_TAG_INT: - { - int64_t v64; - v64 = JS_VALUE_GET_INT(op1); - switch(op) { - case OP_inc: - case OP_dec: - v = 2 * (op - OP_dec) - 1; - v64 += v; - break; - case OP_plus: - break; - case OP_neg: - if (v64 == 0) { - sp[-1] = js_float64(-0.0); - return 0; - } else { - v64 = -v64; - } - break; - default: - abort(); - } - sp[-1] = js_int64(v64); - } - break; - case JS_TAG_SHORT_BIG_INT: - { - int64_t v; - v = JS_VALUE_GET_SHORT_BIG_INT(op1); - switch(op) { - case OP_plus: - JS_ThrowTypeError(ctx, "bigint argument with unary +"); - goto exception; - case OP_inc: - if (v == JS_SHORT_BIG_INT_MAX) - goto bigint_slow_case; - sp[-1] = __JS_NewShortBigInt(ctx, v + 1); - break; - case OP_dec: - if (v == JS_SHORT_BIG_INT_MIN) - goto bigint_slow_case; - sp[-1] = __JS_NewShortBigInt(ctx, v - 1); - break; - case OP_neg: - v = JS_VALUE_GET_SHORT_BIG_INT(op1); - if (v == JS_SHORT_BIG_INT_MIN) { - bigint_slow_case: - p1 = js_bigint_set_short(&buf1, op1); - goto bigint_slow_case1; - } - sp[-1] = __JS_NewShortBigInt(ctx, -v); - break; - default: - abort(); - } - } - break; - case JS_TAG_BIG_INT: - { - JSBigInt *r; - p1 = JS_VALUE_GET_PTR(op1); - bigint_slow_case1: - switch(op) { - case OP_plus: - JS_ThrowTypeError(ctx, "bigint argument with unary +"); - JS_FreeValue(ctx, op1); - goto exception; - case OP_inc: - case OP_dec: - { - JSBigIntBuf buf2; - JSBigInt *p2; - p2 = js_bigint_set_si(&buf2, 2 * (op - OP_dec) - 1); - r = js_bigint_add(ctx, p1, p2, 0); - } - break; - case OP_neg: - r = js_bigint_neg(ctx, p1); - break; - case OP_not: - r = js_bigint_not(ctx, p1); - break; - default: - abort(); - } - JS_FreeValue(ctx, op1); - if (!r) - goto exception; - sp[-1] = JS_CompactBigInt(ctx, r); - } - break; - default: - handle_float64: - { - double d; - d = JS_VALUE_GET_FLOAT64(op1); - switch(op) { - case OP_inc: - case OP_dec: - v = 2 * (op - OP_dec) - 1; - d += v; - break; - case OP_plus: - break; - case OP_neg: - d = -d; - break; - default: - abort(); - } - sp[-1] = js_float64(d); - } - break; - } - return 0; - exception: - sp[-1] = JS_UNDEFINED; - return -1; -} - -static __exception int js_post_inc_slow(JSContext *ctx, - JSValue *sp, OPCodeEnum op) -{ - JSValue op1; - - /* XXX: allow custom operators */ - op1 = sp[-1]; - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - sp[-1] = JS_UNDEFINED; - return -1; - } - sp[-1] = op1; - sp[0] = js_dup(op1); - return js_unary_arith_slow(ctx, sp + 1, op - OP_post_dec + OP_dec); -} - -static no_inline int js_not_slow(JSContext *ctx, JSValue *sp) -{ - JSValue op1; - - op1 = sp[-1]; - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) - goto exception; - if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) { - sp[-1] = __JS_NewShortBigInt(ctx, ~JS_VALUE_GET_SHORT_BIG_INT(op1)); - } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT) { - JSBigInt *r; - r = js_bigint_not(ctx, JS_VALUE_GET_PTR(op1)); - JS_FreeValue(ctx, op1); - if (!r) - goto exception; - sp[-1] = JS_CompactBigInt(ctx, r); - } else { - int32_t v1; - if (unlikely(JS_ToInt32Free(ctx, &v1, op1))) - goto exception; - sp[-1] = js_int32(~v1); - } - return 0; - exception: - sp[-1] = JS_UNDEFINED; - return -1; -} - -static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp, - OPCodeEnum op) -{ - JSValue op1, op2; - uint32_t tag1, tag2; - double d1, d2; - - op1 = sp[-2]; - op2 = sp[-1]; - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - /* fast path for float operations */ - if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { - d1 = JS_VALUE_GET_FLOAT64(op1); - d2 = JS_VALUE_GET_FLOAT64(op2); - goto handle_float64; - } - /* fast path for short big int operations */ - if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { - js_slimb_t v1, v2; - js_sdlimb_t v; - v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); - v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); - switch(op) { - case OP_sub: - v = (js_sdlimb_t)v1 - (js_sdlimb_t)v2; - break; - case OP_mul: - v = (js_sdlimb_t)v1 * (js_sdlimb_t)v2; - break; - case OP_div: - if (v2 == 0 || - ((js_limb_t)v1 == (js_limb_t)1 << (JS_LIMB_BITS - 1) && - v2 == -1)) { - goto slow_big_int; - } - sp[-2] = __JS_NewShortBigInt(ctx, v1 / v2); - return 0; - case OP_mod: - if (v2 == 0 || - ((js_limb_t)v1 == (js_limb_t)1 << (JS_LIMB_BITS - 1) && - v2 == -1)) { - goto slow_big_int; - } - sp[-2] = __JS_NewShortBigInt(ctx, v1 % v2); - return 0; - case OP_pow: - goto slow_big_int; - default: - abort(); - } - if (likely(v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX)) { - sp[-2] = __JS_NewShortBigInt(ctx, v); - } else { - JSBigInt *r = js_bigint_new_di(ctx, v); - if (!r) - goto exception; - sp[-2] = JS_MKPTR(JS_TAG_BIG_INT, r); - } - return 0; - } - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToNumericFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - - if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { - int32_t v1, v2; - int64_t v; - v1 = JS_VALUE_GET_INT(op1); - v2 = JS_VALUE_GET_INT(op2); - switch(op) { - case OP_sub: - v = (int64_t)v1 - (int64_t)v2; - break; - case OP_mul: - v = (int64_t)v1 * (int64_t)v2; - if (v == 0 && (v1 | v2) < 0) { - sp[-2] = js_float64(-0.0); - return 0; - } - break; - case OP_div: - JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); - sp[-2] = js_number((double)v1 / (double)v2); - JS_X87_FPCW_RESTORE(fpcw); - return 0; - case OP_mod: - if (v1 < 0 || v2 <= 0) { - JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); - sp[-2] = js_number(fmod(v1, v2)); - JS_X87_FPCW_RESTORE(fpcw); - return 0; - } else { - v = (int64_t)v1 % (int64_t)v2; - } - break; - case OP_pow: - sp[-2] = js_number(js_math_pow(v1, v2)); - return 0; - default: - abort(); - } - sp[-2] = js_int64(v); - } else if ((tag1 == JS_TAG_SHORT_BIG_INT || tag1 == JS_TAG_BIG_INT) && - (tag2 == JS_TAG_SHORT_BIG_INT || tag2 == JS_TAG_BIG_INT)) { - JSBigInt *p1, *p2, *r; - JSBigIntBuf buf1, buf2; - slow_big_int: - /* bigint result */ - if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) - p1 = js_bigint_set_short(&buf1, op1); - else - p1 = JS_VALUE_GET_PTR(op1); - if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) - p2 = js_bigint_set_short(&buf2, op2); - else - p2 = JS_VALUE_GET_PTR(op2); - switch(op) { - case OP_add: - r = js_bigint_add(ctx, p1, p2, 0); - break; - case OP_sub: - r = js_bigint_add(ctx, p1, p2, 1); - break; - case OP_mul: - r = js_bigint_mul(ctx, p1, p2); - break; - case OP_div: - r = js_bigint_divrem(ctx, p1, p2, false); - break; - case OP_mod: - r = js_bigint_divrem(ctx, p1, p2, true); - break; - case OP_pow: - r = js_bigint_pow(ctx, p1, p2); - break; - default: - abort(); - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (!r) - goto exception; - sp[-2] = JS_CompactBigInt(ctx, r); - } else { - double dr; - /* float64 result */ - if (JS_ToFloat64Free(ctx, &d1, op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - if (JS_ToFloat64Free(ctx, &d2, op2)) - goto exception; - handle_float64: - JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); - switch(op) { - case OP_sub: - dr = d1 - d2; - break; - case OP_mul: - dr = d1 * d2; - break; - case OP_div: - dr = d1 / d2; - break; - case OP_mod: - dr = fmod(d1, d2); - break; - case OP_pow: - dr = js_math_pow(d1, d2); - break; - default: - abort(); - } - JS_X87_FPCW_RESTORE(fpcw); - sp[-2] = js_float64(dr); - } - return 0; - exception: - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - uint32_t tag1, tag2; - - op1 = sp[-2]; - op2 = sp[-1]; - - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - /* fast path for float64 */ - if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { - double d1, d2; - d1 = JS_VALUE_GET_FLOAT64(op1); - d2 = JS_VALUE_GET_FLOAT64(op2); - sp[-2] = js_float64(d1 + d2); - return 0; - } - /* fast path for short bigint */ - if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { - js_slimb_t v1, v2; - js_sdlimb_t v; - v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); - v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); - v = (js_sdlimb_t)v1 + (js_sdlimb_t)v2; - if (likely(v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX)) { - sp[-2] = __JS_NewShortBigInt(ctx, v); - } else { - JSBigInt *r = js_bigint_new_di(ctx, v); - if (!r) - goto exception; - sp[-2] = JS_MKPTR(JS_TAG_BIG_INT, r); - } - return 0; - } - - if (tag1 == JS_TAG_OBJECT || tag2 == JS_TAG_OBJECT) { - op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - - op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - } - - if (tag1 == JS_TAG_STRING || tag2 == JS_TAG_STRING) { - sp[-2] = JS_ConcatString(ctx, op1, op2); - if (JS_IsException(sp[-2])) - goto exception; - return 0; - } - - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToNumericFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - - if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { - int32_t v1, v2; - int64_t v; - v1 = JS_VALUE_GET_INT(op1); - v2 = JS_VALUE_GET_INT(op2); - v = (int64_t)v1 + (int64_t)v2; - sp[-2] = js_int64(v); - } else if ((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && - (tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT)) { - JSBigInt *p1, *p2, *r; - JSBigIntBuf buf1, buf2; - /* bigint result */ - if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) - p1 = js_bigint_set_short(&buf1, op1); - else - p1 = JS_VALUE_GET_PTR(op1); - if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) - p2 = js_bigint_set_short(&buf2, op2); - else - p2 = JS_VALUE_GET_PTR(op2); - r = js_bigint_add(ctx, p1, p2, 0); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (!r) - goto exception; - sp[-2] = JS_CompactBigInt(ctx, r); - } else { - double d1, d2; - /* float64 result */ - if (JS_ToFloat64Free(ctx, &d1, op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - if (JS_ToFloat64Free(ctx, &d2, op2)) - goto exception; - JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); - sp[-2] = js_float64(d1 + d2); - JS_X87_FPCW_RESTORE(fpcw); - } - return 0; - exception: - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -static no_inline __exception int js_binary_logic_slow(JSContext *ctx, - JSValue *sp, - OPCodeEnum op) -{ - JSValue op1, op2; - uint32_t tag1, tag2; - uint32_t v1, v2, r; - - op1 = sp[-2]; - op2 = sp[-1]; - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - - if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { - js_slimb_t v1, v2, v; - js_sdlimb_t vd; - v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); - v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); - /* bigint fast path */ - switch(op) { - case OP_and: - v = v1 & v2; - break; - case OP_or: - v = v1 | v2; - break; - case OP_xor: - v = v1 ^ v2; - break; - case OP_sar: - if (v2 > (JS_LIMB_BITS - 1)) { - goto slow_big_int; - } else if (v2 < 0) { - if (v2 < -(JS_LIMB_BITS - 1)) - goto slow_big_int; - v2 = -v2; - goto bigint_shl; - } - bigint_sar: - v = v1 >> v2; - break; - case OP_shl: - if (v2 > (JS_LIMB_BITS - 1)) { - goto slow_big_int; - } else if (v2 < 0) { - if (v2 < -(JS_LIMB_BITS - 1)) - goto slow_big_int; - v2 = -v2; - goto bigint_sar; - } - bigint_shl: - vd = (js_dlimb_t)v1 << v2; - if (likely(vd >= JS_SHORT_BIG_INT_MIN && - vd <= JS_SHORT_BIG_INT_MAX)) { - v = vd; - } else { - JSBigInt *r = js_bigint_new_di(ctx, vd); - if (!r) - goto exception; - sp[-2] = JS_MKPTR(JS_TAG_BIG_INT, r); - return 0; - } - break; - default: - abort(); - } - sp[-2] = __JS_NewShortBigInt(ctx, v); - return 0; - } - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToNumericFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - - tag1 = JS_VALUE_GET_TAG(op1); - tag2 = JS_VALUE_GET_TAG(op2); - if ((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && - (tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT)) { - JSBigInt *p1, *p2, *r; - JSBigIntBuf buf1, buf2; - slow_big_int: - if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) - p1 = js_bigint_set_short(&buf1, op1); - else - p1 = JS_VALUE_GET_PTR(op1); - if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) - p2 = js_bigint_set_short(&buf2, op2); - else - p2 = JS_VALUE_GET_PTR(op2); - switch(op) { - case OP_and: - case OP_or: - case OP_xor: - r = js_bigint_logic(ctx, p1, p2, op); - break; - case OP_shl: - case OP_sar: - { - js_slimb_t shift; - shift = js_bigint_get_si_sat(p2); - if (shift > INT32_MAX) - shift = INT32_MAX; - else if (shift < -INT32_MAX) - shift = -INT32_MAX; - if (op == OP_sar) - shift = -shift; - if (shift >= 0) - r = js_bigint_shl(ctx, p1, shift); - else - r = js_bigint_shr(ctx, p1, -shift); - } - break; - default: - abort(); - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - if (!r) - goto exception; - sp[-2] = JS_CompactBigInt(ctx, r); - } else { - if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) { - JS_FreeValue(ctx, op2); - goto exception; - } - if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v2, op2))) - goto exception; - switch(op) { - case OP_shl: - r = v1 << (v2 & 0x1f); - break; - case OP_sar: - r = (int)v1 >> (v2 & 0x1f); - break; - case OP_and: - r = v1 & v2; - break; - case OP_or: - r = v1 | v2; - break; - case OP_xor: - r = v1 ^ v2; - break; - default: - abort(); - } - sp[-2] = js_int32(r); - } - return 0; - exception: - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -/* op1 must be a bigint or int. */ -static JSBigInt *JS_ToBigIntBuf(JSContext *ctx, JSBigIntBuf *buf1, - JSValue op1) -{ - JSBigInt *p1; - - switch(JS_VALUE_GET_TAG(op1)) { - case JS_TAG_INT: - p1 = js_bigint_set_si(buf1, JS_VALUE_GET_INT(op1)); - break; - case JS_TAG_SHORT_BIG_INT: - p1 = js_bigint_set_short(buf1, op1); - break; - case JS_TAG_BIG_INT: - p1 = JS_VALUE_GET_PTR(op1); - break; - default: - abort(); - } - return p1; -} - -/* op1 and op2 must be numeric types and at least one must be a - bigint. No exception is generated. */ -static int js_compare_bigint(JSContext *ctx, OPCodeEnum op, - JSValue op1, JSValue op2) -{ - int res, val, tag1, tag2; - JSBigIntBuf buf1, buf2; - JSBigInt *p1, *p2; - - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - if ((tag1 == JS_TAG_SHORT_BIG_INT || tag1 == JS_TAG_INT) && - (tag2 == JS_TAG_SHORT_BIG_INT || tag2 == JS_TAG_INT)) { - /* fast path */ - js_slimb_t v1, v2; - if (tag1 == JS_TAG_INT) - v1 = JS_VALUE_GET_INT(op1); - else - v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); - if (tag2 == JS_TAG_INT) - v2 = JS_VALUE_GET_INT(op2); - else - v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); - val = (v1 > v2) - (v1 < v2); - } else { - if (tag1 == JS_TAG_FLOAT64) { - p2 = JS_ToBigIntBuf(ctx, &buf2, op2); - val = js_bigint_float64_cmp(ctx, p2, JS_VALUE_GET_FLOAT64(op1)); - if (val == 2) - goto unordered; - val = -val; - } else if (tag2 == JS_TAG_FLOAT64) { - p1 = JS_ToBigIntBuf(ctx, &buf1, op1); - val = js_bigint_float64_cmp(ctx, p1, JS_VALUE_GET_FLOAT64(op2)); - if (val == 2) { - unordered: - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - return false; - } - } else { - p1 = JS_ToBigIntBuf(ctx, &buf1, op1); - p2 = JS_ToBigIntBuf(ctx, &buf2, op2); - val = js_bigint_cmp(ctx, p1, p2); - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - } - - switch(op) { - case OP_lt: - res = val < 0; - break; - case OP_lte: - res = val <= 0; - break; - case OP_gt: - res = val > 0; - break; - case OP_gte: - res = val >= 0; - break; - case OP_eq: - res = val == 0; - break; - default: - abort(); - } - return res; -} - -static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, - OPCodeEnum op) -{ - JSValue op1, op2; - int res; - uint32_t tag1, tag2; - - op1 = sp[-2]; - op2 = sp[-1]; - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - - op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NUMBER); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - - if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { - JSString *p1, *p2; - p1 = JS_VALUE_GET_STRING(op1); - p2 = JS_VALUE_GET_STRING(op2); - res = js_string_compare(p1, p2); - switch(op) { - case OP_lt: - res = (res < 0); - break; - case OP_lte: - res = (res <= 0); - break; - case OP_gt: - res = (res > 0); - break; - default: - case OP_gte: - res = (res >= 0); - break; - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - } else if ((tag1 <= JS_TAG_NULL || tag1 == JS_TAG_FLOAT64) && - (tag2 <= JS_TAG_NULL || tag2 == JS_TAG_FLOAT64)) { - /* fast path for float64/int */ - goto float64_compare; - } else { - if ((((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && - tag2 == JS_TAG_STRING) || - ((tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) && - tag1 == JS_TAG_STRING))) { - if (tag1 == JS_TAG_STRING) { - op1 = JS_StringToBigInt(ctx, op1); - if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT && - JS_VALUE_GET_TAG(op1) != JS_TAG_SHORT_BIG_INT) - goto invalid_bigint_string; - } - if (tag2 == JS_TAG_STRING) { - op2 = JS_StringToBigInt(ctx, op2); - if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT && - JS_VALUE_GET_TAG(op2) != JS_TAG_SHORT_BIG_INT) { - invalid_bigint_string: - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - res = false; - goto done; - } - } - } else { - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToNumericFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - } - - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - - if (tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT || - tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) { - res = js_compare_bigint(ctx, op, op1, op2); - } else { - double d1, d2; - - float64_compare: - /* can use floating point comparison */ - if (tag1 == JS_TAG_FLOAT64) { - d1 = JS_VALUE_GET_FLOAT64(op1); - } else { - d1 = JS_VALUE_GET_INT(op1); - } - if (tag2 == JS_TAG_FLOAT64) { - d2 = JS_VALUE_GET_FLOAT64(op2); - } else { - d2 = JS_VALUE_GET_INT(op2); - } - switch(op) { - case OP_lt: - res = (d1 < d2); /* if NaN return false */ - break; - case OP_lte: - res = (d1 <= d2); /* if NaN return false */ - break; - case OP_gt: - res = (d1 > d2); /* if NaN return false */ - break; - default: - case OP_gte: - res = (d1 >= d2); /* if NaN return false */ - break; - } - } - } - done: - sp[-2] = js_bool(res); - return 0; - exception: - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -static bool tag_is_number(uint32_t tag) -{ - return (tag == JS_TAG_INT || - tag == JS_TAG_FLOAT64 || - tag == JS_TAG_BIG_INT || tag == JS_TAG_SHORT_BIG_INT); -} - -static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, - bool is_neq) -{ - JSValue op1, op2; - int res; - uint32_t tag1, tag2; - - op1 = sp[-2]; - op2 = sp[-1]; - redo: - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - if (tag_is_number(tag1) && tag_is_number(tag2)) { - if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { - res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); - } else if ((tag1 == JS_TAG_FLOAT64 && - (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) || - (tag2 == JS_TAG_FLOAT64 && - (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64))) { - double d1, d2; - if (tag1 == JS_TAG_FLOAT64) { - d1 = JS_VALUE_GET_FLOAT64(op1); - } else { - d1 = JS_VALUE_GET_INT(op1); - } - if (tag2 == JS_TAG_FLOAT64) { - d2 = JS_VALUE_GET_FLOAT64(op2); - } else { - d2 = JS_VALUE_GET_INT(op2); - } - res = (d1 == d2); - } else { - res = js_compare_bigint(ctx, OP_eq, op1, op2); - if (res < 0) - goto exception; - } - } else if (tag1 == tag2) { - res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); - } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || - (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { - res = true; - } else if ((tag1 == JS_TAG_STRING && tag_is_number(tag2)) || - (tag2 == JS_TAG_STRING && tag_is_number(tag1))) { - - if (tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT || - tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) { - if (tag1 == JS_TAG_STRING) { - op1 = JS_StringToBigInt(ctx, op1); - if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT && - JS_VALUE_GET_TAG(op1) != JS_TAG_SHORT_BIG_INT) - goto invalid_bigint_string; - } - if (tag2 == JS_TAG_STRING) { - op2 = JS_StringToBigInt(ctx, op2); - if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT && - JS_VALUE_GET_TAG(op2) != JS_TAG_SHORT_BIG_INT ) { - invalid_bigint_string: - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - res = false; - goto done; - } - } - } else { - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToNumericFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - } - res = js_strict_eq(ctx, op1, op2); - } else if (tag1 == JS_TAG_BOOL) { - op1 = js_int32(JS_VALUE_GET_INT(op1)); - goto redo; - } else if (tag2 == JS_TAG_BOOL) { - op2 = js_int32(JS_VALUE_GET_INT(op2)); - goto redo; - } else if ((tag1 == JS_TAG_OBJECT && - (tag_is_number(tag2) || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) || - (tag2 == JS_TAG_OBJECT && - (tag_is_number(tag1) || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL))) { - op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - goto redo; - } else { - /* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */ - if ((JS_IsHTMLDDA(ctx, op1) && - (tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) || - (JS_IsHTMLDDA(ctx, op2) && - (tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) { - res = true; - } else { - res = false; - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - } - done: - sp[-2] = js_bool(res ^ is_neq); - return 0; - exception: - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - uint32_t v1, v2, r; - - op1 = sp[-2]; - op2 = sp[-1]; - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToNumericFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - - if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT || - JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT || - JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_INT || - JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) { - JS_ThrowTypeError(ctx, "BigInt operands are forbidden for >>>"); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - goto exception; - } - /* cannot give an exception */ - JS_ToUint32Free(ctx, &v1, op1); - JS_ToUint32Free(ctx, &v2, op2); - r = v1 >> (v2 & 0x1f); - sp[-2] = js_uint32(r); - return 0; - exception: - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -static bool js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, - JSStrictEqModeEnum eq_mode) -{ - bool res; - int tag1, tag2; - double d1, d2; - - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - switch(tag1) { - case JS_TAG_BOOL: - if (tag1 != tag2) { - res = false; - } else { - res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); - goto done_no_free; - } - break; - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - res = (tag1 == tag2); - break; - case JS_TAG_STRING: - { - JSString *p1, *p2; - if (tag1 != tag2) { - res = false; - } else { - p1 = JS_VALUE_GET_STRING(op1); - p2 = JS_VALUE_GET_STRING(op2); - res = js_string_eq(p1, p2); - } - } - break; - case JS_TAG_SYMBOL: - { - JSAtomStruct *p1, *p2; - if (tag1 != tag2) { - res = false; - } else { - p1 = JS_VALUE_GET_PTR(op1); - p2 = JS_VALUE_GET_PTR(op2); - res = (p1 == p2); - } - } - break; - case JS_TAG_OBJECT: - if (tag1 != tag2) - res = false; - else - res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2); - break; - case JS_TAG_INT: - d1 = JS_VALUE_GET_INT(op1); - if (tag2 == JS_TAG_INT) { - d2 = JS_VALUE_GET_INT(op2); - goto number_test; - } else if (tag2 == JS_TAG_FLOAT64) { - d2 = JS_VALUE_GET_FLOAT64(op2); - goto number_test; - } else { - res = false; - } - break; - case JS_TAG_FLOAT64: - d1 = JS_VALUE_GET_FLOAT64(op1); - if (tag2 == JS_TAG_FLOAT64) { - d2 = JS_VALUE_GET_FLOAT64(op2); - } else if (tag2 == JS_TAG_INT) { - d2 = JS_VALUE_GET_INT(op2); - } else { - res = false; - break; - } - number_test: - if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) { - JSFloat64Union u1, u2; - /* NaN is not always normalized, so this test is necessary */ - if (isnan(d1) || isnan(d2)) { - res = isnan(d1) == isnan(d2); - } else if (eq_mode == JS_EQ_SAME_VALUE_ZERO) { - res = (d1 == d2); /* +0 == -0 */ - } else { - u1.d = d1; - u2.d = d2; - res = (u1.u64 == u2.u64); /* +0 != -0 */ - } - } else { - res = (d1 == d2); /* if NaN return false and +0 == -0 */ - } - goto done_no_free; - case JS_TAG_SHORT_BIG_INT: - case JS_TAG_BIG_INT: - { - JSBigIntBuf buf1, buf2; - JSBigInt *p1, *p2; - - if (tag2 != JS_TAG_SHORT_BIG_INT && - tag2 != JS_TAG_BIG_INT) { - res = false; - break; - } - - if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) - p1 = js_bigint_set_short(&buf1, op1); - else - p1 = JS_VALUE_GET_PTR(op1); - if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) - p2 = js_bigint_set_short(&buf2, op2); - else - p2 = JS_VALUE_GET_PTR(op2); - res = (js_bigint_cmp(ctx, p1, p2) == 0); - } - break; - default: - res = false; - break; - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - done_no_free: - return res; -} - -static bool js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2) -{ - return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); -} - -static bool js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2) -{ - return js_strict_eq2(ctx, js_dup(op1), js_dup(op2), JS_EQ_SAME_VALUE); -} - -static bool js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2) -{ - return js_strict_eq2(ctx, js_dup(op1), js_dup(op2), JS_EQ_SAME_VALUE_ZERO); -} - -static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp, - bool is_neq) -{ - bool res; - res = js_strict_eq(ctx, sp[-2], sp[-1]); - sp[-2] = js_bool(res ^ is_neq); - return 0; -} - -static __exception int js_operator_in(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - JSAtom atom; - int ret; - - op1 = sp[-2]; - op2 = sp[-1]; - - if (JS_VALUE_GET_TAG(op2) != JS_TAG_OBJECT) { - JS_ThrowTypeError(ctx, "invalid 'in' operand"); - return -1; - } - atom = JS_ValueToAtom(ctx, op1); - if (unlikely(atom == JS_ATOM_NULL)) - return -1; - ret = JS_HasProperty(ctx, op2, atom); - JS_FreeAtom(ctx, atom); - if (ret < 0) - return -1; - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - sp[-2] = js_bool(ret); - return 0; -} - -static __exception int js_operator_private_in(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - int ret; - op1 = sp[-2]; /* object */ - op2 = sp[-1]; /* field name or method function */ - if (JS_VALUE_GET_TAG(op1) != JS_TAG_OBJECT) { - JS_ThrowTypeError(ctx, "invalid 'in' operand"); - return -1; - } - if (JS_IsObject(op2)) { - /* method: use the brand */ - ret = JS_CheckBrand(ctx, op1, op2); - if (ret < 0) - return -1; - } else { - JSAtom atom; - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - /* field */ - atom = JS_ValueToAtom(ctx, op2); - if (unlikely(atom == JS_ATOM_NULL)) - return -1; - p = JS_VALUE_GET_OBJ(op1); - prs = find_own_property(&pr, p, atom); - JS_FreeAtom(ctx, atom); - ret = (prs != NULL); - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - sp[-2] = js_bool(ret); - return 0; -} - -static __exception int js_has_unscopable(JSContext *ctx, JSValue obj, - JSAtom atom) -{ - JSValue arr, val; - int ret; - - arr = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_unscopables); - if (JS_IsException(arr)) - return -1; - ret = 0; - if (JS_IsObject(arr)) { - val = JS_GetProperty(ctx, arr, atom); - ret = JS_ToBoolFree(ctx, val); - } - JS_FreeValue(ctx, arr); - return ret; -} - -static __exception int js_operator_instanceof(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - int ret; - - op1 = sp[-2]; - op2 = sp[-1]; - ret = JS_IsInstanceOf(ctx, op1, op2); - if (ret < 0) - return ret; - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - sp[-2] = js_bool(ret); - return 0; -} - -static __exception int js_operator_typeof(JSContext *ctx, JSValue op1) -{ - JSAtom atom; - uint32_t tag; - - tag = JS_VALUE_GET_NORM_TAG(op1); - switch(tag) { - case JS_TAG_SHORT_BIG_INT: - case JS_TAG_BIG_INT: - atom = JS_ATOM_bigint; - break; - case JS_TAG_INT: - case JS_TAG_FLOAT64: - atom = JS_ATOM_number; - break; - case JS_TAG_UNDEFINED: - atom = JS_ATOM_undefined; - break; - case JS_TAG_BOOL: - atom = JS_ATOM_boolean; - break; - case JS_TAG_STRING: - atom = JS_ATOM_string; - break; - case JS_TAG_OBJECT: - { - JSObject *p; - p = JS_VALUE_GET_OBJ(op1); - if (unlikely(p->is_HTMLDDA)) - atom = JS_ATOM_undefined; - else if (JS_IsFunction(ctx, op1)) - atom = JS_ATOM_function; - else - goto obj_type; - } - break; - case JS_TAG_NULL: - obj_type: - atom = JS_ATOM_object; - break; - case JS_TAG_SYMBOL: - atom = JS_ATOM_symbol; - break; - default: - atom = JS_ATOM_unknown; - break; - } - return atom; -} - -static __exception int js_operator_delete(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - JSAtom atom; - int ret; - - op1 = sp[-2]; - op2 = sp[-1]; - atom = JS_ValueToAtom(ctx, op2); - if (unlikely(atom == JS_ATOM_NULL)) - return -1; - ret = JS_DeleteProperty(ctx, op1, atom, JS_PROP_THROW_STRICT); - JS_FreeAtom(ctx, atom); - if (unlikely(ret < 0)) - return -1; - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - sp[-2] = js_bool(ret); - return 0; -} - -static JSValue js_throw_type_error(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); - if (!b || b->is_strict_mode || !b->has_prototype) { - return JS_ThrowTypeError(ctx, "invalid property access"); - } - return JS_UNDEFINED; -} - -static JSValue js_function_proto_fileName(JSContext *ctx, - JSValueConst this_val) -{ - JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); - if (b) { - return JS_AtomToString(ctx, b->filename); - } - return JS_UNDEFINED; -} - -static JSValue js_function_proto_int32(JSContext *ctx, - JSValueConst this_val, - int magic) -{ - JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); - if (b) { - int *field = (int *) ((char *)b + magic); - return js_int32(*field); - } - return JS_UNDEFINED; -} - -static int js_arguments_define_own_property(JSContext *ctx, - JSValueConst this_obj, - JSAtom prop, JSValueConst val, - JSValueConst getter, - JSValueConst setter, int flags) -{ - JSObject *p; - uint32_t idx; - p = JS_VALUE_GET_OBJ(this_obj); - /* convert to normal array when redefining an existing numeric field */ - if (p->fast_array && JS_AtomIsArrayIndex(ctx, &idx, prop) && - idx < p->u.array.count) { - if (convert_fast_array_to_array(ctx, p)) - return -1; - } - /* run the default define own property */ - return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter, - flags | JS_PROP_NO_EXOTIC); -} - -static const JSClassExoticMethods js_arguments_exotic_methods = { - .define_own_property = js_arguments_define_own_property, -}; - -static JSValue js_build_arguments(JSContext *ctx, int argc, JSValueConst *argv) -{ - JSValue val, *tab; - JSProperty *pr; - JSObject *p; - int i; - - val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], - JS_CLASS_ARGUMENTS); - if (JS_IsException(val)) - return val; - p = JS_VALUE_GET_OBJ(val); - - /* add the length field (cannot fail) */ - pr = add_property(ctx, p, JS_ATOM_length, - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - if (!pr) { - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } - pr->u.value = js_int32(argc); - - /* initialize the fast array part */ - tab = NULL; - if (argc > 0) { - tab = js_malloc(ctx, sizeof(tab[0]) * argc); - if (!tab) { - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } - for(i = 0; i < argc; i++) { - tab[i] = js_dup(argv[i]); - } - } - p->u.array.u.values = tab; - p->u.array.count = argc; - - JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator, - js_dup(ctx->array_proto_values), - JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); - /* add callee property to throw a TypeError in strict mode */ - JS_DefineProperty(ctx, val, JS_ATOM_callee, JS_UNDEFINED, - ctx->throw_type_error, ctx->throw_type_error, - JS_PROP_HAS_GET | JS_PROP_HAS_SET); - return val; -} - -#define GLOBAL_VAR_OFFSET 0x40000000 -#define ARGUMENT_VAR_OFFSET 0x20000000 - -/* legacy arguments object: add references to the function arguments */ -static JSValue js_build_mapped_arguments(JSContext *ctx, int argc, - JSValueConst *argv, - JSStackFrame *sf, int arg_count) -{ - JSValue val; - JSProperty *pr; - JSObject *p; - int i; - - val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], - JS_CLASS_MAPPED_ARGUMENTS); - if (JS_IsException(val)) - return val; - p = JS_VALUE_GET_OBJ(val); - - /* add the length field (cannot fail) */ - pr = add_property(ctx, p, JS_ATOM_length, - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - if (!pr) - goto fail; - pr->u.value = js_int32(argc); - - for(i = 0; i < arg_count; i++) { - JSVarRef *var_ref; - var_ref = get_var_ref(ctx, sf, i, true); - if (!var_ref) - goto fail; - pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E | JS_PROP_VARREF); - if (!pr) { - free_var_ref(ctx->rt, var_ref); - goto fail; - } - pr->u.var_ref = var_ref; - } - - /* the arguments not mapped to the arguments of the function can - be normal properties */ - for(i = arg_count; i < argc; i++) { - if (JS_DefinePropertyValueUint32(ctx, val, i, - js_dup(argv[i]), - JS_PROP_C_W_E) < 0) - goto fail; - } - - JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator, - js_dup(ctx->array_proto_values), - JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); - /* callee returns this function in non strict mode */ - JS_DefinePropertyValue(ctx, val, JS_ATOM_callee, - js_dup(ctx->rt->current_stack_frame->cur_func), - JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); - return val; - fail: - JS_FreeValue(ctx, val); - return JS_EXCEPTION; -} - -static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj) -{ - JSObject *p; - JSPropertyEnum *tab_atom; - int i; - JSValue enum_obj, obj1; - JSForInIterator *it; - uint32_t tag, tab_atom_count; - - tag = JS_VALUE_GET_TAG(obj); - if (tag != JS_TAG_OBJECT && tag != JS_TAG_NULL && tag != JS_TAG_UNDEFINED) { - obj = JS_ToObjectFree(ctx, obj); - } - - it = js_malloc(ctx, sizeof(*it)); - if (!it) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - enum_obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_FOR_IN_ITERATOR); - if (JS_IsException(enum_obj)) { - js_free(ctx, it); - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - it->is_array = false; - it->obj = obj; - it->idx = 0; - p = JS_VALUE_GET_OBJ(enum_obj); - p->u.for_in_iterator = it; - - if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) - return enum_obj; - - /* fast path: assume no enumerable properties in the prototype chain */ - obj1 = js_dup(obj); - for(;;) { - obj1 = JS_GetPrototypeFree(ctx, obj1); - if (JS_IsNull(obj1)) - break; - if (JS_IsException(obj1)) - goto fail; - if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, - JS_VALUE_GET_OBJ(obj1), - JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) { - JS_FreeValue(ctx, obj1); - goto fail; - } - js_free_prop_enum(ctx, tab_atom, tab_atom_count); - if (tab_atom_count != 0) { - JS_FreeValue(ctx, obj1); - goto slow_path; - } - /* must check for timeout to avoid infinite loop */ - if (js_poll_interrupts(ctx)) { - JS_FreeValue(ctx, obj1); - goto fail; - } - } - - p = JS_VALUE_GET_OBJ(obj); - - if (p->fast_array) { - JSShape *sh; - JSShapeProperty *prs; - /* check that there are no enumerable normal fields */ - sh = p->shape; - for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { - if (prs->flags & JS_PROP_ENUMERABLE) - goto normal_case; - } - /* for fast arrays, we only store the number of elements */ - it->is_array = true; - it->array_length = p->u.array.count; - } else { - normal_case: - if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, - JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) - goto fail; - for(i = 0; i < tab_atom_count; i++) { - JS_SetPropertyInternal(ctx, enum_obj, tab_atom[i].atom, JS_NULL, 0); - } - js_free_prop_enum(ctx, tab_atom, tab_atom_count); - } - return enum_obj; - - slow_path: - /* non enumerable properties hide the enumerables ones in the - prototype chain */ - obj1 = js_dup(obj); - for(;;) { - if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, - JS_VALUE_GET_OBJ(obj1), - JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { - JS_FreeValue(ctx, obj1); - goto fail; - } - for(i = 0; i < tab_atom_count; i++) { - JS_DefinePropertyValue(ctx, enum_obj, tab_atom[i].atom, JS_NULL, - (tab_atom[i].is_enumerable ? - JS_PROP_ENUMERABLE : 0)); - } - js_free_prop_enum(ctx, tab_atom, tab_atom_count); - obj1 = JS_GetPrototypeFree(ctx, obj1); - if (JS_IsNull(obj1)) - break; - if (JS_IsException(obj1)) - goto fail; - /* must check for timeout to avoid infinite loop */ - if (js_poll_interrupts(ctx)) { - JS_FreeValue(ctx, obj1); - goto fail; - } - } - return enum_obj; - - fail: - JS_FreeValue(ctx, enum_obj); - return JS_EXCEPTION; -} - -/* obj -> enum_obj */ -static __exception int js_for_in_start(JSContext *ctx, JSValue *sp) -{ - sp[-1] = build_for_in_iterator(ctx, sp[-1]); - if (JS_IsException(sp[-1])) - return -1; - return 0; -} - -/* enum_obj -> enum_obj value done */ -static __exception int js_for_in_next(JSContext *ctx, JSValue *sp) -{ - JSValue enum_obj; - JSObject *p; - JSAtom prop; - JSForInIterator *it; - int ret; - - enum_obj = sp[-1]; - /* fail safe */ - if (JS_VALUE_GET_TAG(enum_obj) != JS_TAG_OBJECT) - goto done; - p = JS_VALUE_GET_OBJ(enum_obj); - if (p->class_id != JS_CLASS_FOR_IN_ITERATOR) - goto done; - it = p->u.for_in_iterator; - - for(;;) { - if (it->is_array) { - if (it->idx >= it->array_length) - goto done; - prop = __JS_AtomFromUInt32(it->idx); - it->idx++; - } else { - JSShape *sh = p->shape; - JSShapeProperty *prs; - if (it->idx >= sh->prop_count) - goto done; - prs = get_shape_prop(sh) + it->idx; - prop = prs->atom; - it->idx++; - if (prop == JS_ATOM_NULL || !(prs->flags & JS_PROP_ENUMERABLE)) - continue; - } - // check if the property was deleted unless we're dealing with a proxy - JSValue obj = it->obj; - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - JSObject *p = JS_VALUE_GET_OBJ(obj); - if (p->class_id == JS_CLASS_PROXY) - break; - } - ret = JS_HasProperty(ctx, obj, prop); - if (ret < 0) - return ret; - if (ret) - break; - } - /* return the property */ - sp[0] = JS_AtomToValue(ctx, prop); - sp[1] = JS_FALSE; - return 0; - done: - /* return the end */ - sp[0] = JS_UNDEFINED; - sp[1] = JS_TRUE; - return 0; -} - -static JSValue JS_GetIterator2(JSContext *ctx, JSValueConst obj, - JSValueConst method) -{ - JSValue enum_obj; - - enum_obj = JS_Call(ctx, method, obj, 0, NULL); - if (JS_IsException(enum_obj)) - return enum_obj; - if (!JS_IsObject(enum_obj)) { - JS_FreeValue(ctx, enum_obj); - return JS_ThrowTypeErrorNotAnObject(ctx); - } - return enum_obj; -} - -static JSValue JS_GetIterator(JSContext *ctx, JSValueConst obj, bool is_async) -{ - JSValue method, ret, sync_iter; - - if (is_async) { - method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_asyncIterator); - if (JS_IsException(method)) - return method; - if (JS_IsUndefined(method) || JS_IsNull(method)) { - method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); - if (JS_IsException(method)) - return method; - sync_iter = JS_GetIterator2(ctx, obj, method); - JS_FreeValue(ctx, method); - if (JS_IsException(sync_iter)) - return sync_iter; - ret = JS_CreateAsyncFromSyncIterator(ctx, sync_iter); - JS_FreeValue(ctx, sync_iter); - return ret; - } - } else { - method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); - if (JS_IsException(method)) - return method; - } - if (!JS_IsFunction(ctx, method)) { - JS_FreeValue(ctx, method); - return JS_ThrowTypeError(ctx, "value is not iterable"); - } - ret = JS_GetIterator2(ctx, obj, method); - JS_FreeValue(ctx, method); - return ret; -} - -/* return *pdone = 2 if the iterator object is not parsed */ -static JSValue JS_IteratorNext2(JSContext *ctx, JSValueConst enum_obj, - JSValueConst method, - int argc, JSValueConst *argv, int *pdone) -{ - JSValue obj; - - /* fast path for the built-in iterators (avoid creating the - intermediate result object) */ - if (JS_IsObject(method)) { - JSObject *p = JS_VALUE_GET_OBJ(method); - if (p->class_id == JS_CLASS_C_FUNCTION && - p->u.cfunc.cproto == JS_CFUNC_iterator_next) { - JSCFunctionType func; - JSValueConst args[1]; - - /* in case the function expects one argument */ - if (argc == 0) { - args[0] = JS_UNDEFINED; - argv = args; - } - func = p->u.cfunc.c_function; - return func.iterator_next(ctx, enum_obj, argc, argv, - pdone, p->u.cfunc.magic); - } - } - obj = JS_Call(ctx, method, enum_obj, argc, argv); - if (JS_IsException(obj)) - goto fail; - if (!JS_IsObject(obj)) { - JS_FreeValue(ctx, obj); - JS_ThrowTypeError(ctx, "iterator must return an object"); - goto fail; - } - *pdone = 2; - return obj; - fail: - *pdone = false; - return JS_EXCEPTION; -} - -static JSValue JS_IteratorNext(JSContext *ctx, JSValueConst enum_obj, - JSValueConst method, - int argc, JSValueConst *argv, int *pdone) -{ - JSValue obj, value, done_val; - int done; - - obj = JS_IteratorNext2(ctx, enum_obj, method, argc, argv, &done); - if (JS_IsException(obj)) - goto fail; - if (likely(done == 0)) { - *pdone = false; - return obj; - } else if (done != 2) { - JS_FreeValue(ctx, obj); - *pdone = true; - return JS_UNDEFINED; - } else { - done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); - if (JS_IsException(done_val)) - goto fail; - *pdone = JS_ToBoolFree(ctx, done_val); - value = JS_UNDEFINED; - if (!*pdone) { - value = JS_GetProperty(ctx, obj, JS_ATOM_value); - } - JS_FreeValue(ctx, obj); - return value; - } - fail: - JS_FreeValue(ctx, obj); - *pdone = false; - return JS_EXCEPTION; -} - -/* return < 0 in case of exception */ -static int JS_IteratorClose(JSContext *ctx, JSValueConst enum_obj, - bool is_exception_pending) -{ - JSValue method, ret, ex_obj; - int res; - - if (is_exception_pending) { - ex_obj = ctx->rt->current_exception; - ctx->rt->current_exception = JS_UNINITIALIZED; - res = -1; - } else { - ex_obj = JS_UNDEFINED; - res = 0; - } - method = JS_GetProperty(ctx, enum_obj, JS_ATOM_return); - if (JS_IsException(method)) { - res = -1; - goto done; - } - if (JS_IsUndefined(method) || JS_IsNull(method)) { - goto done; - } - ret = JS_CallFree(ctx, method, enum_obj, 0, NULL); - if (!is_exception_pending) { - if (JS_IsException(ret)) { - res = -1; - } else if (!JS_IsObject(ret)) { - JS_ThrowTypeErrorNotAnObject(ctx); - res = -1; - } - } - JS_FreeValue(ctx, ret); - done: - if (is_exception_pending) { - JS_Throw(ctx, ex_obj); - } - return res; -} - -/* obj -> enum_rec (3 slots) */ -static __exception int js_for_of_start(JSContext *ctx, JSValue *sp, - bool is_async) -{ - JSValue op1, obj, method; - op1 = sp[-1]; - obj = JS_GetIterator(ctx, op1, is_async); - if (JS_IsException(obj)) - return -1; - JS_FreeValue(ctx, op1); - sp[-1] = obj; - method = JS_GetProperty(ctx, obj, JS_ATOM_next); - if (JS_IsException(method)) - return -1; - sp[0] = method; - return 0; -} - -/* enum_rec [objs] -> enum_rec [objs] value done. There are 'offset' - objs. If 'done' is true or in case of exception, 'enum_rec' is set - to undefined. If 'done' is true, 'value' is always set to - undefined. */ -static __exception int js_for_of_next(JSContext *ctx, JSValue *sp, int offset) -{ - JSValue value = JS_UNDEFINED; - int done = 1; - - if (likely(!JS_IsUndefined(sp[offset]))) { - value = JS_IteratorNext(ctx, sp[offset], sp[offset + 1], 0, NULL, &done); - if (JS_IsException(value)) - done = -1; - if (done) { - /* value is JS_UNDEFINED or JS_EXCEPTION */ - /* replace the iteration object with undefined */ - JS_FreeValue(ctx, sp[offset]); - sp[offset] = JS_UNDEFINED; - if (done < 0) { - return -1; - } else { - JS_FreeValue(ctx, value); - value = JS_UNDEFINED; - } - } - } - sp[0] = value; - sp[1] = js_bool(done); - return 0; -} - -static JSValue JS_IteratorGetCompleteValue(JSContext *ctx, JSValue obj, - int *pdone) -{ - JSValue done_val, value; - int done; - done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); - if (JS_IsException(done_val)) - goto fail; - done = JS_ToBoolFree(ctx, done_val); - value = JS_GetProperty(ctx, obj, JS_ATOM_value); - if (JS_IsException(value)) - goto fail; - *pdone = done; - return value; - fail: - *pdone = false; - return JS_EXCEPTION; -} - -static __exception int js_iterator_get_value_done(JSContext *ctx, JSValue *sp) -{ - JSValue obj, value; - int done; - obj = sp[-1]; - if (!JS_IsObject(obj)) { - JS_ThrowTypeError(ctx, "iterator must return an object"); - return -1; - } - value = JS_IteratorGetCompleteValue(ctx, obj, &done); - if (JS_IsException(value)) - return -1; - JS_FreeValue(ctx, obj); - sp[-1] = value; - sp[0] = js_bool(done); - return 0; -} - -static JSValue js_create_iterator_result(JSContext *ctx, - JSValue val, - bool done) -{ - JSValue obj; - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) { - JS_FreeValue(ctx, val); - return obj; - } - if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_value, - val, JS_PROP_C_W_E) < 0) { - goto fail; - } - if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_done, - js_bool(done), JS_PROP_C_W_E) < 0) { - fail: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - return obj; -} - -static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, - int *pdone, int magic); - -static JSValue js_create_array_iterator(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic); - -static bool js_is_fast_array(JSContext *ctx, JSValue obj) -{ - /* Try and handle fast arrays explicitly */ - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - JSObject *p = JS_VALUE_GET_OBJ(obj); - if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { - return true; - } - } - return false; -} - -/* Access an Array's internal JSValue array if available */ -static bool js_get_fast_array(JSContext *ctx, JSValue obj, - JSValue **arrpp, uint32_t *countp) -{ - /* Try and handle fast arrays explicitly */ - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - JSObject *p = JS_VALUE_GET_OBJ(obj); - if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { - *countp = p->u.array.count; - *arrpp = p->u.array.u.values; - return true; - } - } - return false; -} - -static __exception int js_append_enumerate(JSContext *ctx, JSValue *sp) -{ - JSValue iterator, enumobj, method, value; - int is_array_iterator; - JSValue *arrp; - uint32_t i, count32, pos; - - if (JS_VALUE_GET_TAG(sp[-2]) != JS_TAG_INT) { - JS_ThrowInternalError(ctx, "invalid index for append"); - return -1; - } - - pos = JS_VALUE_GET_INT(sp[-2]); - - /* XXX: further optimisations: - - use ctx->array_proto_values? - - check if array_iterator_prototype next method is built-in and - avoid constructing actual iterator object? - - build this into js_for_of_start and use in all `for (x of o)` loops - */ - iterator = JS_GetProperty(ctx, sp[-1], JS_ATOM_Symbol_iterator); - if (JS_IsException(iterator)) - return -1; - /* Used to squelch a -Wcast-function-type warning. */ - JSCFunctionType ft = { .generic_magic = js_create_array_iterator }; - is_array_iterator = JS_IsCFunction(ctx, iterator, - ft.generic, - JS_ITERATOR_KIND_VALUE); - JS_FreeValue(ctx, iterator); - - enumobj = JS_GetIterator(ctx, sp[-1], false); - if (JS_IsException(enumobj)) - return -1; - method = JS_GetProperty(ctx, enumobj, JS_ATOM_next); - if (JS_IsException(method)) { - JS_FreeValue(ctx, enumobj); - return -1; - } - /* Used to squelch a -Wcast-function-type warning. */ - JSCFunctionType ft2 = { .iterator_next = js_array_iterator_next }; - if (is_array_iterator - && JS_IsCFunction(ctx, method, ft2.generic, 0) - && js_get_fast_array(ctx, sp[-1], &arrp, &count32)) { - uint32_t len; - if (js_get_length32(ctx, &len, sp[-1])) - goto exception; - /* if len > count32, the elements >= count32 might be read in - the prototypes and might have side effects */ - if (len != count32) - goto general_case; - /* Handle fast arrays explicitly */ - for (i = 0; i < count32; i++) { - if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, - js_dup(arrp[i]), JS_PROP_C_W_E) < 0) - goto exception; - } - } else { - general_case: - for (;;) { - int done; - value = JS_IteratorNext(ctx, enumobj, method, 0, NULL, &done); - if (JS_IsException(value)) - goto exception; - if (done) { - /* value is JS_UNDEFINED */ - break; - } - if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, value, JS_PROP_C_W_E) < 0) - goto exception; - } - } - /* Note: could raise an error if too many elements */ - sp[-2] = js_int32(pos); - JS_FreeValue(ctx, enumobj); - JS_FreeValue(ctx, method); - return 0; - -exception: - JS_IteratorClose(ctx, enumobj, true); - JS_FreeValue(ctx, enumobj); - JS_FreeValue(ctx, method); - return -1; -} - -static __exception int JS_CopyDataProperties(JSContext *ctx, - JSValue target, - JSValue source, - JSValue excluded, - bool setprop) -{ - JSPropertyEnum *tab_atom; - JSValue val; - uint32_t i, tab_atom_count; - JSObject *p; - JSObject *pexcl = NULL; - int ret, gpn_flags; - JSPropertyDescriptor desc; - bool is_enumerable; - - if (JS_VALUE_GET_TAG(source) != JS_TAG_OBJECT) - return 0; - - if (JS_VALUE_GET_TAG(excluded) == JS_TAG_OBJECT) - pexcl = JS_VALUE_GET_OBJ(excluded); - - p = JS_VALUE_GET_OBJ(source); - - gpn_flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY; - if (p->is_exotic) { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - /* cannot use JS_GPN_ENUM_ONLY with e.g. proxies because it - introduces a visible change */ - if (em && em->get_own_property_names) { - gpn_flags &= ~JS_GPN_ENUM_ONLY; - } - } - if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, - gpn_flags)) - return -1; - - for (i = 0; i < tab_atom_count; i++) { - if (pexcl) { - ret = JS_GetOwnPropertyInternal(ctx, NULL, pexcl, tab_atom[i].atom); - if (ret) { - if (ret < 0) - goto exception; - continue; - } - } - if (!(gpn_flags & JS_GPN_ENUM_ONLY)) { - /* test if the property is enumerable */ - ret = JS_GetOwnPropertyInternal(ctx, &desc, p, tab_atom[i].atom); - if (ret < 0) - goto exception; - if (!ret) - continue; - is_enumerable = (desc.flags & JS_PROP_ENUMERABLE) != 0; - js_free_desc(ctx, &desc); - if (!is_enumerable) - continue; - } - val = JS_GetProperty(ctx, source, tab_atom[i].atom); - if (JS_IsException(val)) - goto exception; - if (setprop) - ret = JS_SetProperty(ctx, target, tab_atom[i].atom, val); - else - ret = JS_DefinePropertyValue(ctx, target, tab_atom[i].atom, val, - JS_PROP_C_W_E); - if (ret < 0) - goto exception; - } - js_free_prop_enum(ctx, tab_atom, tab_atom_count); - return 0; - exception: - js_free_prop_enum(ctx, tab_atom, tab_atom_count); - return -1; -} - -/* only valid inside C functions */ -static JSValueConst JS_GetActiveFunction(JSContext *ctx) -{ - return ctx->rt->current_stack_frame->cur_func; -} - -static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, - int var_idx, bool is_arg) -{ - JSVarRef *var_ref; - struct list_head *el; - JSValue *pvalue; - - if (is_arg) - pvalue = &sf->arg_buf[var_idx]; - else - pvalue = &sf->var_buf[var_idx]; - - list_for_each(el, &sf->var_ref_list) { - var_ref = list_entry(el, JSVarRef, header.link); - if (var_ref->pvalue == pvalue) { - var_ref->header.ref_count++; - return var_ref; - } - } - /* create a new one */ - var_ref = js_malloc(ctx, sizeof(JSVarRef)); - if (!var_ref) - return NULL; - var_ref->header.ref_count = 1; - var_ref->is_detached = false; - list_add_tail(&var_ref->header.link, &sf->var_ref_list); - var_ref->pvalue = pvalue; - var_ref->value = JS_UNDEFINED; - return var_ref; -} - -static JSValue js_closure2(JSContext *ctx, JSValue func_obj, - JSFunctionBytecode *b, - JSVarRef **cur_var_refs, - JSStackFrame *sf) -{ - JSObject *p; - JSVarRef **var_refs; - int i; - - p = JS_VALUE_GET_OBJ(func_obj); - p->u.func.function_bytecode = b; - p->u.func.home_object = NULL; - p->u.func.var_refs = NULL; - if (b->closure_var_count) { - var_refs = js_mallocz(ctx, sizeof(var_refs[0]) * b->closure_var_count); - if (!var_refs) - goto fail; - p->u.func.var_refs = var_refs; - for(i = 0; i < b->closure_var_count; i++) { - JSClosureVar *cv = &b->closure_var[i]; - JSVarRef *var_ref; - if (cv->is_local) { - /* reuse the existing variable reference if it already exists */ - var_ref = get_var_ref(ctx, sf, cv->var_idx, cv->is_arg); - if (!var_ref) - goto fail; - } else { - var_ref = cur_var_refs[cv->var_idx]; - var_ref->header.ref_count++; - } - var_refs[i] = var_ref; - } - } - return func_obj; - fail: - /* bfunc is freed when func_obj is freed */ - JS_FreeValue(ctx, func_obj); - return JS_EXCEPTION; -} - -static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque) -{ - JSValue obj, this_val; - int ret; - - this_val = JS_MKPTR(JS_TAG_OBJECT, p); - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) - return JS_EXCEPTION; - ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_constructor, - js_dup(this_val), - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - if (ret < 0) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - return obj; -} - -static const uint16_t func_kind_to_class_id[] = { - [JS_FUNC_NORMAL] = JS_CLASS_BYTECODE_FUNCTION, - [JS_FUNC_GENERATOR] = JS_CLASS_GENERATOR_FUNCTION, - [JS_FUNC_ASYNC] = JS_CLASS_ASYNC_FUNCTION, - [JS_FUNC_ASYNC_GENERATOR] = JS_CLASS_ASYNC_GENERATOR_FUNCTION, -}; - -static JSValue js_closure(JSContext *ctx, JSValue bfunc, - JSVarRef **cur_var_refs, - JSStackFrame *sf) -{ - JSFunctionBytecode *b; - JSValue func_obj; - JSAtom name_atom; - - b = JS_VALUE_GET_PTR(bfunc); - func_obj = JS_NewObjectClass(ctx, func_kind_to_class_id[b->func_kind]); - if (JS_IsException(func_obj)) { - JS_FreeValue(ctx, bfunc); - return JS_EXCEPTION; - } - func_obj = js_closure2(ctx, func_obj, b, cur_var_refs, sf); - if (JS_IsException(func_obj)) { - /* bfunc has been freed */ - goto fail; - } - name_atom = b->func_name; - if (name_atom == JS_ATOM_NULL) - name_atom = JS_ATOM_empty_string; - js_function_set_properties(ctx, func_obj, name_atom, - b->defined_arg_count); - - if (b->func_kind & JS_FUNC_GENERATOR) { - JSValue proto; - int proto_class_id; - /* generators have a prototype field which is used as - prototype for the generator object */ - if (b->func_kind == JS_FUNC_ASYNC_GENERATOR) - proto_class_id = JS_CLASS_ASYNC_GENERATOR; - else - proto_class_id = JS_CLASS_GENERATOR; - proto = JS_NewObjectProto(ctx, ctx->class_proto[proto_class_id]); - if (JS_IsException(proto)) - goto fail; - JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, proto, - JS_PROP_WRITABLE); - } else if (b->has_prototype) { - /* add the 'prototype' property: delay instantiation to avoid - creating cycles for every javascript function. The prototype - object is created on the fly when first accessed */ - JS_SetConstructorBit(ctx, func_obj, true); - JS_DefineAutoInitProperty(ctx, func_obj, JS_ATOM_prototype, - JS_AUTOINIT_ID_PROTOTYPE, NULL, - JS_PROP_WRITABLE); - } - return func_obj; - fail: - /* bfunc is freed when func_obj is freed */ - JS_FreeValue(ctx, func_obj); - return JS_EXCEPTION; -} - -#define JS_DEFINE_CLASS_HAS_HERITAGE (1 << 0) - -static int js_op_define_class(JSContext *ctx, JSValue *sp, - JSAtom class_name, int class_flags, - JSVarRef **cur_var_refs, - JSStackFrame *sf, bool is_computed_name) -{ - JSValue bfunc, parent_class, proto = JS_UNDEFINED; - JSValue ctor = JS_UNDEFINED, parent_proto = JS_UNDEFINED; - JSFunctionBytecode *b; - - parent_class = sp[-2]; - bfunc = sp[-1]; - - if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE) { - if (JS_IsNull(parent_class)) { - parent_proto = JS_NULL; - parent_class = js_dup(ctx->function_proto); - } else { - if (!JS_IsConstructor(ctx, parent_class)) { - JS_ThrowTypeError(ctx, "parent class must be constructor"); - goto fail; - } - parent_proto = JS_GetProperty(ctx, parent_class, JS_ATOM_prototype); - if (JS_IsException(parent_proto)) - goto fail; - if (!JS_IsNull(parent_proto) && !JS_IsObject(parent_proto)) { - JS_ThrowTypeError(ctx, "parent prototype must be an object or null"); - goto fail; - } - } - } else { - /* parent_class is JS_UNDEFINED in this case */ - parent_proto = js_dup(ctx->class_proto[JS_CLASS_OBJECT]); - parent_class = js_dup(ctx->function_proto); - } - proto = JS_NewObjectProto(ctx, parent_proto); - if (JS_IsException(proto)) - goto fail; - - b = JS_VALUE_GET_PTR(bfunc); - assert(b->func_kind == JS_FUNC_NORMAL); - ctor = JS_NewObjectProtoClass(ctx, parent_class, - JS_CLASS_BYTECODE_FUNCTION); - if (JS_IsException(ctor)) - goto fail; - ctor = js_closure2(ctx, ctor, b, cur_var_refs, sf); - bfunc = JS_UNDEFINED; - if (JS_IsException(ctor)) - goto fail; - js_method_set_home_object(ctx, ctor, proto); - JS_SetConstructorBit(ctx, ctor, true); - - JS_DefinePropertyValue(ctx, ctor, JS_ATOM_length, - js_int32(b->defined_arg_count), - JS_PROP_CONFIGURABLE); - - if (is_computed_name) { - if (JS_DefineObjectNameComputed(ctx, ctor, sp[-3], - JS_PROP_CONFIGURABLE) < 0) - goto fail; - } else { - if (JS_DefineObjectName(ctx, ctor, class_name, JS_PROP_CONFIGURABLE) < 0) - goto fail; - } - - /* the constructor property must be first. It can be overriden by - computed property names */ - if (JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor, - js_dup(ctor), - JS_PROP_CONFIGURABLE | - JS_PROP_WRITABLE | JS_PROP_THROW) < 0) - goto fail; - /* set the prototype property */ - if (JS_DefinePropertyValue(ctx, ctor, JS_ATOM_prototype, - js_dup(proto), JS_PROP_THROW) < 0) - goto fail; - - JS_FreeValue(ctx, parent_proto); - JS_FreeValue(ctx, parent_class); - - sp[-2] = ctor; - sp[-1] = proto; - return 0; - fail: - JS_FreeValue(ctx, parent_class); - JS_FreeValue(ctx, parent_proto); - JS_FreeValue(ctx, bfunc); - JS_FreeValue(ctx, proto); - JS_FreeValue(ctx, ctor); - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -static void close_var_refs(JSRuntime *rt, JSStackFrame *sf) -{ - struct list_head *el, *el1; - JSVarRef *var_ref; - - list_for_each_safe(el, el1, &sf->var_ref_list) { - var_ref = list_entry(el, JSVarRef, header.link); - var_ref->value = js_dup(*var_ref->pvalue); - var_ref->pvalue = &var_ref->value; - /* the reference is no longer to a local variable */ - var_ref->is_detached = true; - add_gc_object(rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); - } -} - -static void close_lexical_var(JSContext *ctx, JSStackFrame *sf, int var_idx) -{ - JSValue *pvalue; - struct list_head *el, *el1; - JSVarRef *var_ref; - - pvalue = &sf->var_buf[var_idx]; - list_for_each_safe(el, el1, &sf->var_ref_list) { - var_ref = list_entry(el, JSVarRef, header.link); - if (var_ref->pvalue == pvalue) { - var_ref->value = js_dup(*var_ref->pvalue); - var_ref->pvalue = &var_ref->value; - list_del(&var_ref->header.link); - /* the reference is no longer to a local variable */ - var_ref->is_detached = true; - add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); - } - } -} - -#define JS_CALL_FLAG_COPY_ARGV (1 << 1) -#define JS_CALL_FLAG_GENERATOR (1 << 2) - -static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, - JSValueConst this_obj, - int argc, JSValueConst *argv, int flags) -{ - JSRuntime *rt = ctx->rt; - JSCFunctionType func; - JSObject *p; - JSStackFrame sf_s, *sf = &sf_s, *prev_sf; - JSValue ret_val; - JSValueConst *arg_buf; - int arg_count, i; - JSCFunctionEnum cproto; - - p = JS_VALUE_GET_OBJ(func_obj); - cproto = p->u.cfunc.cproto; - arg_count = p->u.cfunc.length; - - /* better to always check stack overflow */ - if (js_check_stack_overflow(rt, sizeof(arg_buf[0]) * arg_count)) - return JS_ThrowStackOverflow(ctx); - - prev_sf = rt->current_stack_frame; - sf->prev_frame = prev_sf; - rt->current_stack_frame = sf; - ctx = p->u.cfunc.realm; /* change the current realm */ - - sf->is_strict_mode = false; - sf->cur_func = unsafe_unconst(func_obj); - sf->arg_count = argc; - arg_buf = argv; - - if (unlikely(argc < arg_count)) { - /* ensure that at least argc_count arguments are readable */ - arg_buf = alloca(sizeof(arg_buf[0]) * arg_count); - for(i = 0; i < argc; i++) - arg_buf[i] = argv[i]; - for(i = argc; i < arg_count; i++) - arg_buf[i] = JS_UNDEFINED; - sf->arg_count = arg_count; - } - sf->arg_buf = (JSValue *)arg_buf; - - func = p->u.cfunc.c_function; - switch(cproto) { - case JS_CFUNC_constructor: - case JS_CFUNC_constructor_or_func: - if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { - if (cproto == JS_CFUNC_constructor) { - not_a_constructor: - ret_val = JS_ThrowTypeError(ctx, "must be called with new"); - break; - } else { - this_obj = JS_UNDEFINED; - } - } - /* here this_obj is new_target */ - /* fall thru */ - case JS_CFUNC_generic: - ret_val = func.generic(ctx, this_obj, argc, arg_buf); - break; - case JS_CFUNC_constructor_magic: - case JS_CFUNC_constructor_or_func_magic: - if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { - if (cproto == JS_CFUNC_constructor_magic) { - goto not_a_constructor; - } else { - this_obj = JS_UNDEFINED; - } - } - /* fall thru */ - case JS_CFUNC_generic_magic: - ret_val = func.generic_magic(ctx, this_obj, argc, arg_buf, - p->u.cfunc.magic); - break; - case JS_CFUNC_getter: - ret_val = func.getter(ctx, this_obj); - break; - case JS_CFUNC_setter: - ret_val = func.setter(ctx, this_obj, arg_buf[0]); - break; - case JS_CFUNC_getter_magic: - ret_val = func.getter_magic(ctx, this_obj, p->u.cfunc.magic); - break; - case JS_CFUNC_setter_magic: - ret_val = func.setter_magic(ctx, this_obj, arg_buf[0], p->u.cfunc.magic); - break; - case JS_CFUNC_f_f: - { - double d1; - - if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { - ret_val = JS_EXCEPTION; - break; - } - ret_val = js_number(func.f_f(d1)); - } - break; - case JS_CFUNC_f_f_f: - { - double d1, d2; - - if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { - ret_val = JS_EXCEPTION; - break; - } - if (unlikely(JS_ToFloat64(ctx, &d2, arg_buf[1]))) { - ret_val = JS_EXCEPTION; - break; - } - ret_val = js_number(func.f_f_f(d1, d2)); - } - break; - case JS_CFUNC_iterator_next: - { - int done; - ret_val = func.iterator_next(ctx, this_obj, argc, arg_buf, - &done, p->u.cfunc.magic); - if (!JS_IsException(ret_val) && done != 2) { - ret_val = js_create_iterator_result(ctx, ret_val, done); - } - } - break; - default: - abort(); - } - - rt->current_stack_frame = sf->prev_frame; - return ret_val; -} - -static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj, - JSValueConst this_obj, - int argc, JSValueConst *argv, int flags) -{ - JSObject *p; - JSBoundFunction *bf; - JSValueConst *arg_buf, new_target; - int arg_count, i; - - p = JS_VALUE_GET_OBJ(func_obj); - bf = p->u.bound_function; - arg_count = bf->argc + argc; - if (js_check_stack_overflow(ctx->rt, sizeof(JSValue) * arg_count)) - return JS_ThrowStackOverflow(ctx); - arg_buf = alloca(sizeof(JSValue) * arg_count); - for(i = 0; i < bf->argc; i++) { - arg_buf[i] = bf->argv[i]; - } - for(i = 0; i < argc; i++) { - arg_buf[bf->argc + i] = argv[i]; - } - if (flags & JS_CALL_FLAG_CONSTRUCTOR) { - new_target = this_obj; - if (js_same_value(ctx, func_obj, new_target)) - new_target = bf->func_obj; - return JS_CallConstructor2(ctx, bf->func_obj, new_target, - arg_count, arg_buf); - } else { - return JS_Call(ctx, bf->func_obj, bf->this_val, - arg_count, arg_buf); - } -} - -/* argument of OP_special_object */ -typedef enum { - OP_SPECIAL_OBJECT_ARGUMENTS, - OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS, - OP_SPECIAL_OBJECT_THIS_FUNC, - OP_SPECIAL_OBJECT_NEW_TARGET, - OP_SPECIAL_OBJECT_HOME_OBJECT, - OP_SPECIAL_OBJECT_VAR_OBJECT, - OP_SPECIAL_OBJECT_IMPORT_META, - OP_SPECIAL_OBJECT_NULL_PROTO, -} OPSpecialObjectEnum; - -#define FUNC_RET_AWAIT 0 -#define FUNC_RET_YIELD 1 -#define FUNC_RET_YIELD_STAR 2 - -#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_* -static void dump_single_byte_code(JSContext *ctx, const uint8_t *pc, - JSFunctionBytecode *b, int start_pos); -static void print_func_name(JSFunctionBytecode *b); -#endif - -static bool needs_backtrace(JSValue exc) -{ - JSObject *p; - - if (JS_VALUE_GET_TAG(exc) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(exc); - if (p->class_id != JS_CLASS_ERROR) - return false; - return !find_own_property1(p, JS_ATOM_stack); -} - -/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ -static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, - JSValueConst this_obj, JSValueConst new_target, - int argc, JSValueConst *argv, int flags) -{ - JSRuntime *rt = caller_ctx->rt; - JSContext *ctx; - JSObject *p; - JSFunctionBytecode *b; - JSStackFrame sf_s, *sf = &sf_s; - uint8_t *pc; - int opcode, arg_allocated_size, i; - JSValue *local_buf, *stack_buf, *var_buf, *arg_buf, *sp, ret_val, *pval; - JSVarRef **var_refs; - size_t alloca_size; - -#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_STEP -#define DUMP_BYTECODE_OR_DONT(pc) \ - if (check_dump_flag(ctx->rt, JS_DUMP_BYTECODE_STEP)) dump_single_byte_code(ctx, pc, b, 0); -#else -#define DUMP_BYTECODE_OR_DONT(pc) -#endif - -#if !DIRECT_DISPATCH -#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) switch (opcode = *pc++) -#define CASE(op) case op -#define DEFAULT default -#define BREAK break -#else - __extension__ static const void * const dispatch_table[256] = { -#define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id, -#define def(id, size, n_pop, n_push, f) -#include "quickjs-opcode.h" - [ OP_COUNT ... 255 ] = &&case_default - }; -#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) __extension__ ({ goto *dispatch_table[opcode = *pc++]; }); -#define CASE(op) case_ ## op -#define DEFAULT case_default -#define BREAK SWITCH(pc) -#endif - - if (js_poll_interrupts(caller_ctx)) - return JS_EXCEPTION; - if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) { - if (flags & JS_CALL_FLAG_GENERATOR) { - JSAsyncFunctionState *s = JS_VALUE_GET_PTR(func_obj); - /* func_obj get contains a pointer to JSFuncAsyncState */ - /* the stack frame is already allocated */ - sf = &s->frame; - p = JS_VALUE_GET_OBJ(sf->cur_func); - b = p->u.func.function_bytecode; - ctx = b->realm; - var_refs = p->u.func.var_refs; - local_buf = arg_buf = sf->arg_buf; - var_buf = sf->var_buf; - stack_buf = sf->var_buf + b->var_count; - sp = sf->cur_sp; - sf->cur_sp = NULL; /* cur_sp is NULL if the function is running */ - pc = sf->cur_pc; - sf->prev_frame = rt->current_stack_frame; - rt->current_stack_frame = sf; - if (s->throw_flag) - goto exception; - else - goto restart; - } else { - goto not_a_function; - } - } - p = JS_VALUE_GET_OBJ(func_obj); - if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { - JSClassCall *call_func; - call_func = rt->class_array[p->class_id].call; - if (!call_func) { - not_a_function: - return JS_ThrowTypeErrorNotAFunction(caller_ctx); - } - return call_func(caller_ctx, func_obj, this_obj, argc, - argv, flags); - } - b = p->u.func.function_bytecode; - - if (unlikely(argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) { - arg_allocated_size = b->arg_count; - } else { - arg_allocated_size = 0; - } - - alloca_size = sizeof(JSValue) * (arg_allocated_size + b->var_count + - b->stack_size); - if (js_check_stack_overflow(rt, alloca_size)) - return JS_ThrowStackOverflow(caller_ctx); - - sf->is_strict_mode = b->is_strict_mode; - arg_buf = (JSValue *)argv; - sf->arg_count = argc; - sf->cur_func = unsafe_unconst(func_obj); - init_list_head(&sf->var_ref_list); - var_refs = p->u.func.var_refs; - - local_buf = alloca(alloca_size); - if (unlikely(arg_allocated_size)) { - int n = min_int(argc, b->arg_count); - arg_buf = local_buf; - for(i = 0; i < n; i++) - arg_buf[i] = js_dup(argv[i]); - for(; i < b->arg_count; i++) - arg_buf[i] = JS_UNDEFINED; - sf->arg_count = b->arg_count; - } - var_buf = local_buf + arg_allocated_size; - sf->var_buf = var_buf; - sf->arg_buf = arg_buf; - - for(i = 0; i < b->var_count; i++) - var_buf[i] = JS_UNDEFINED; - - stack_buf = var_buf + b->var_count; - sp = stack_buf; - pc = b->byte_code_buf; - /* sf->cur_pc must we set to pc before any recursive calls to JS_CallInternal. */ - sf->cur_pc = NULL; - sf->prev_frame = rt->current_stack_frame; - rt->current_stack_frame = sf; - ctx = b->realm; /* set the current realm */ - -#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_STEP - if (check_dump_flag(ctx->rt, JS_DUMP_BYTECODE_STEP)) - print_func_name(b); -#endif - - restart: - for(;;) { - int call_argc; - JSValue *call_argv; - - SWITCH(pc) { - CASE(OP_push_i32): - *sp++ = js_int32(get_u32(pc)); - pc += 4; - BREAK; - CASE(OP_push_bigint_i32): - *sp++ = __JS_NewShortBigInt(ctx, (int)get_u32(pc)); - pc += 4; - BREAK; - CASE(OP_push_const): - *sp++ = js_dup(b->cpool[get_u32(pc)]); - pc += 4; - BREAK; - CASE(OP_push_minus1): - CASE(OP_push_0): - CASE(OP_push_1): - CASE(OP_push_2): - CASE(OP_push_3): - CASE(OP_push_4): - CASE(OP_push_5): - CASE(OP_push_6): - CASE(OP_push_7): - *sp++ = js_int32(opcode - OP_push_0); - BREAK; - CASE(OP_push_i8): - *sp++ = js_int32(get_i8(pc)); - pc += 1; - BREAK; - CASE(OP_push_i16): - *sp++ = js_int32(get_i16(pc)); - pc += 2; - BREAK; - CASE(OP_push_const8): - *sp++ = js_dup(b->cpool[*pc++]); - BREAK; - CASE(OP_fclosure8): - *sp++ = js_closure(ctx, js_dup(b->cpool[*pc++]), var_refs, sf); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - BREAK; - CASE(OP_push_empty_string): - *sp++ = js_empty_string(rt); - BREAK; - CASE(OP_get_length): - { - JSValue val; - - sf->cur_pc = pc; - val = JS_GetProperty(ctx, sp[-1], JS_ATOM_length); - if (unlikely(JS_IsException(val))) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = val; - } - BREAK; - CASE(OP_push_atom_value): - *sp++ = JS_AtomToValue(ctx, get_u32(pc)); - pc += 4; - BREAK; - CASE(OP_undefined): - *sp++ = JS_UNDEFINED; - BREAK; - CASE(OP_null): - *sp++ = JS_NULL; - BREAK; - CASE(OP_push_this): - /* OP_push_this is only called at the start of a function */ - { - JSValue val; - if (!b->is_strict_mode) { - uint32_t tag = JS_VALUE_GET_TAG(this_obj); - if (likely(tag == JS_TAG_OBJECT)) - goto normal_this; - if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) { - val = js_dup(ctx->global_obj); - } else { - val = JS_ToObject(ctx, this_obj); - if (JS_IsException(val)) - goto exception; - } - } else { - normal_this: - val = js_dup(this_obj); - } - *sp++ = val; - } - BREAK; - CASE(OP_push_false): - *sp++ = JS_FALSE; - BREAK; - CASE(OP_push_true): - *sp++ = JS_TRUE; - BREAK; - CASE(OP_object): - *sp++ = JS_NewObject(ctx); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - BREAK; - CASE(OP_special_object): - { - int arg = *pc++; - switch(arg) { - case OP_SPECIAL_OBJECT_ARGUMENTS: - *sp++ = js_build_arguments(ctx, argc, argv); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - break; - case OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS: - *sp++ = js_build_mapped_arguments(ctx, argc, argv, - sf, min_int(argc, b->arg_count)); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - break; - case OP_SPECIAL_OBJECT_THIS_FUNC: - *sp++ = js_dup(sf->cur_func); - break; - case OP_SPECIAL_OBJECT_NEW_TARGET: - *sp++ = js_dup(new_target); - break; - case OP_SPECIAL_OBJECT_HOME_OBJECT: - { - JSObject *p1; - p1 = p->u.func.home_object; - if (unlikely(!p1)) - *sp++ = JS_UNDEFINED; - else - *sp++ = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); - } - break; - case OP_SPECIAL_OBJECT_VAR_OBJECT: - *sp++ = JS_NewObjectProto(ctx, JS_NULL); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - break; - case OP_SPECIAL_OBJECT_IMPORT_META: - *sp++ = js_import_meta(ctx); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - break; - case OP_SPECIAL_OBJECT_NULL_PROTO: - *sp++ = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_OBJECT); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - break; - default: - abort(); - } - } - BREAK; - CASE(OP_rest): - { - int i, n, first = get_u16(pc); - pc += 2; - i = min_int(first, argc); - n = argc - i; - *sp++ = js_create_array(ctx, n, &argv[i]); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - } - BREAK; - - CASE(OP_drop): - JS_FreeValue(ctx, sp[-1]); - sp--; - BREAK; - CASE(OP_nip): - JS_FreeValue(ctx, sp[-2]); - sp[-2] = sp[-1]; - sp--; - BREAK; - CASE(OP_nip1): /* a b c -> b c */ - JS_FreeValue(ctx, sp[-3]); - sp[-3] = sp[-2]; - sp[-2] = sp[-1]; - sp--; - BREAK; - CASE(OP_dup): - sp[0] = js_dup(sp[-1]); - sp++; - BREAK; - CASE(OP_dup2): /* a b -> a b a b */ - sp[0] = js_dup(sp[-2]); - sp[1] = js_dup(sp[-1]); - sp += 2; - BREAK; - CASE(OP_dup3): /* a b c -> a b c a b c */ - sp[0] = js_dup(sp[-3]); - sp[1] = js_dup(sp[-2]); - sp[2] = js_dup(sp[-1]); - sp += 3; - BREAK; - CASE(OP_dup1): /* a b -> a a b */ - sp[0] = sp[-1]; - sp[-1] = js_dup(sp[-2]); - sp++; - BREAK; - CASE(OP_insert2): /* obj a -> a obj a (dup_x1) */ - sp[0] = sp[-1]; - sp[-1] = sp[-2]; - sp[-2] = js_dup(sp[0]); - sp++; - BREAK; - CASE(OP_insert3): /* obj prop a -> a obj prop a (dup_x2) */ - sp[0] = sp[-1]; - sp[-1] = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = js_dup(sp[0]); - sp++; - BREAK; - CASE(OP_insert4): /* this obj prop a -> a this obj prop a */ - sp[0] = sp[-1]; - sp[-1] = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = sp[-4]; - sp[-4] = js_dup(sp[0]); - sp++; - BREAK; - CASE(OP_perm3): /* obj a b -> a obj b (213) */ - { - JSValue tmp; - tmp = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = tmp; - } - BREAK; - CASE(OP_rot3l): /* x a b -> a b x (231) */ - { - JSValue tmp; - tmp = sp[-3]; - sp[-3] = sp[-2]; - sp[-2] = sp[-1]; - sp[-1] = tmp; - } - BREAK; - CASE(OP_rot4l): /* x a b c -> a b c x */ - { - JSValue tmp; - tmp = sp[-4]; - sp[-4] = sp[-3]; - sp[-3] = sp[-2]; - sp[-2] = sp[-1]; - sp[-1] = tmp; - } - BREAK; - CASE(OP_rot5l): /* x a b c d -> a b c d x */ - { - JSValue tmp; - tmp = sp[-5]; - sp[-5] = sp[-4]; - sp[-4] = sp[-3]; - sp[-3] = sp[-2]; - sp[-2] = sp[-1]; - sp[-1] = tmp; - } - BREAK; - CASE(OP_rot3r): /* a b x -> x a b (312) */ - { - JSValue tmp; - tmp = sp[-1]; - sp[-1] = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = tmp; - } - BREAK; - CASE(OP_perm4): /* obj prop a b -> a obj prop b */ - { - JSValue tmp; - tmp = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = sp[-4]; - sp[-4] = tmp; - } - BREAK; - CASE(OP_perm5): /* this obj prop a b -> a this obj prop b */ - { - JSValue tmp; - tmp = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = sp[-4]; - sp[-4] = sp[-5]; - sp[-5] = tmp; - } - BREAK; - CASE(OP_swap): /* a b -> b a */ - { - JSValue tmp; - tmp = sp[-2]; - sp[-2] = sp[-1]; - sp[-1] = tmp; - } - BREAK; - CASE(OP_swap2): /* a b c d -> c d a b */ - { - JSValue tmp1, tmp2; - tmp1 = sp[-4]; - tmp2 = sp[-3]; - sp[-4] = sp[-2]; - sp[-3] = sp[-1]; - sp[-2] = tmp1; - sp[-1] = tmp2; - } - BREAK; - - CASE(OP_fclosure): - { - JSValue bfunc = js_dup(b->cpool[get_u32(pc)]); - pc += 4; - *sp++ = js_closure(ctx, bfunc, var_refs, sf); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - } - BREAK; - CASE(OP_call0): - CASE(OP_call1): - CASE(OP_call2): - CASE(OP_call3): - call_argc = opcode - OP_call0; - goto has_call_argc; - CASE(OP_call): - CASE(OP_tail_call): - { - call_argc = get_u16(pc); - pc += 2; - goto has_call_argc; - has_call_argc: - call_argv = sp - call_argc; - sf->cur_pc = pc; - ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, - JS_UNDEFINED, call_argc, - vc(call_argv), 0); - if (unlikely(JS_IsException(ret_val))) - goto exception; - if (opcode == OP_tail_call) - goto done; - for(i = -1; i < call_argc; i++) - JS_FreeValue(ctx, call_argv[i]); - sp -= call_argc + 1; - *sp++ = ret_val; - } - BREAK; - CASE(OP_call_constructor): - { - call_argc = get_u16(pc); - pc += 2; - call_argv = sp - call_argc; - sf->cur_pc = pc; - ret_val = JS_CallConstructorInternal(ctx, call_argv[-2], - call_argv[-1], call_argc, - vc(call_argv), 0); - if (unlikely(JS_IsException(ret_val))) - goto exception; - for(i = -2; i < call_argc; i++) - JS_FreeValue(ctx, call_argv[i]); - sp -= call_argc + 2; - *sp++ = ret_val; - } - BREAK; - CASE(OP_call_method): - CASE(OP_tail_call_method): - { - call_argc = get_u16(pc); - pc += 2; - call_argv = sp - call_argc; - sf->cur_pc = pc; - ret_val = JS_CallInternal(ctx, call_argv[-1], call_argv[-2], - JS_UNDEFINED, call_argc, - vc(call_argv), 0); - if (unlikely(JS_IsException(ret_val))) - goto exception; - if (opcode == OP_tail_call_method) - goto done; - for(i = -2; i < call_argc; i++) - JS_FreeValue(ctx, call_argv[i]); - sp -= call_argc + 2; - *sp++ = ret_val; - } - BREAK; - CASE(OP_array_from): - { - call_argc = get_u16(pc); - pc += 2; - call_argv = sp - call_argc; - ret_val = JS_NewArrayFrom(ctx, call_argc, call_argv); - sp -= call_argc; - if (unlikely(JS_IsException(ret_val))) - goto exception; - *sp++ = ret_val; - } - BREAK; - - CASE(OP_apply): - { - int magic; - magic = get_u16(pc); - pc += 2; - sf->cur_pc = pc; - - ret_val = js_function_apply(ctx, sp[-3], 2, vc(&sp[-2]), magic); - if (unlikely(JS_IsException(ret_val))) - goto exception; - JS_FreeValue(ctx, sp[-3]); - JS_FreeValue(ctx, sp[-2]); - JS_FreeValue(ctx, sp[-1]); - sp -= 3; - *sp++ = ret_val; - } - BREAK; - CASE(OP_return): - ret_val = *--sp; - goto done; - CASE(OP_return_undef): - ret_val = JS_UNDEFINED; - goto done; - - CASE(OP_check_ctor_return): - /* return true if 'this' should be returned */ - if (!JS_IsObject(sp[-1])) { - if (!JS_IsUndefined(sp[-1])) { - JS_ThrowTypeError(caller_ctx, "derived class constructor must return an object or undefined"); - goto exception; - } - sp[0] = JS_TRUE; - } else { - sp[0] = JS_FALSE; - } - sp++; - BREAK; - CASE(OP_check_ctor): - if (JS_IsUndefined(new_target)) { - non_ctor_call: - JS_ThrowTypeError(ctx, "class constructors must be invoked with 'new'"); - goto exception; - } - BREAK; - CASE(OP_init_ctor): - { - JSValue super, ret; - sf->cur_pc = pc; - if (JS_IsUndefined(new_target)) - goto non_ctor_call; - super = JS_GetPrototype(ctx, func_obj); - if (JS_IsException(super)) - goto exception; - ret = JS_CallConstructor2(ctx, super, new_target, argc, argv); - JS_FreeValue(ctx, super); - if (JS_IsException(ret)) - goto exception; - *sp++ = ret; - } - BREAK; - CASE(OP_check_brand): - { - int ret = JS_CheckBrand(ctx, sp[-2], sp[-1]); - if (ret < 0) - goto exception; - if (!ret) { - JS_ThrowTypeError(ctx, "invalid brand on object"); - goto exception; - } - } - BREAK; - CASE(OP_add_brand): - if (JS_AddBrand(ctx, sp[-2], sp[-1]) < 0) - goto exception; - JS_FreeValue(ctx, sp[-2]); - JS_FreeValue(ctx, sp[-1]); - sp -= 2; - BREAK; - - CASE(OP_throw): - JS_Throw(ctx, *--sp); - goto exception; - - CASE(OP_throw_error): -#define JS_THROW_VAR_RO 0 -#define JS_THROW_VAR_REDECL 1 -#define JS_THROW_VAR_UNINITIALIZED 2 -#define JS_THROW_ERROR_DELETE_SUPER 3 -#define JS_THROW_ERROR_ITERATOR_THROW 4 - { - JSAtom atom; - int type; - atom = get_u32(pc); - type = pc[4]; - pc += 5; - if (type == JS_THROW_VAR_RO) - JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, atom); - else - if (type == JS_THROW_VAR_REDECL) - JS_ThrowSyntaxErrorVarRedeclaration(ctx, atom); - else - if (type == JS_THROW_VAR_UNINITIALIZED) - JS_ThrowReferenceErrorUninitialized(ctx, atom); - else - if (type == JS_THROW_ERROR_DELETE_SUPER) - JS_ThrowReferenceError(ctx, "unsupported reference to 'super'"); - else - if (type == JS_THROW_ERROR_ITERATOR_THROW) - JS_ThrowTypeError(ctx, "iterator does not have a throw method"); - else - JS_ThrowInternalError(ctx, "invalid throw var type %d", type); - } - goto exception; - - CASE(OP_eval): - { - JSValue obj; - int scope_idx; - call_argc = get_u16(pc); - scope_idx = get_u16(pc + 2) - 1; - pc += 4; - call_argv = sp - call_argc; - sf->cur_pc = pc; - if (js_same_value(ctx, call_argv[-1], ctx->eval_obj)) { - if (call_argc >= 1) - obj = call_argv[0]; - else - obj = JS_UNDEFINED; - ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, - JS_EVAL_TYPE_DIRECT, scope_idx); - } else { - ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, - JS_UNDEFINED, call_argc, - vc(call_argv), 0); - } - if (unlikely(JS_IsException(ret_val))) - goto exception; - for(i = -1; i < call_argc; i++) - JS_FreeValue(ctx, call_argv[i]); - sp -= call_argc + 1; - *sp++ = ret_val; - } - BREAK; - /* could merge with OP_apply */ - CASE(OP_apply_eval): - { - int scope_idx; - uint32_t len; - JSValue *tab; - JSValue obj; - - scope_idx = get_u16(pc) - 1; - pc += 2; - sf->cur_pc = pc; - tab = build_arg_list(ctx, &len, sp[-1]); - if (!tab) - goto exception; - if (js_same_value(ctx, sp[-2], ctx->eval_obj)) { - if (len >= 1) - obj = tab[0]; - else - obj = JS_UNDEFINED; - ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, - JS_EVAL_TYPE_DIRECT, scope_idx); - } else { - ret_val = JS_Call(ctx, sp[-2], JS_UNDEFINED, len, vc(tab)); - } - free_arg_list(ctx, tab, len); - if (unlikely(JS_IsException(ret_val))) - goto exception; - JS_FreeValue(ctx, sp[-2]); - JS_FreeValue(ctx, sp[-1]); - sp -= 2; - *sp++ = ret_val; - } - BREAK; - - CASE(OP_regexp): - { - sp[-2] = js_regexp_constructor_internal(ctx, JS_UNDEFINED, - sp[-2], sp[-1]); - sp--; - } - BREAK; - - CASE(OP_get_super): - { - JSValue proto; - proto = JS_GetPrototype(ctx, sp[-1]); - if (JS_IsException(proto)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = proto; - } - BREAK; - - CASE(OP_import): - { - JSValue val; - sf->cur_pc = pc; - val = js_dynamic_import(ctx, sp[-1]); - if (JS_IsException(val)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = val; - } - BREAK; - - CASE(OP_check_var): - { - int ret; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - - ret = JS_CheckGlobalVar(ctx, atom); - if (ret < 0) - goto exception; - *sp++ = js_bool(ret); - } - BREAK; - - CASE(OP_get_var_undef): - CASE(OP_get_var): - { - JSValue val; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - sf->cur_pc = pc; - - val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef); - if (unlikely(JS_IsException(val))) - goto exception; - *sp++ = val; - } - BREAK; - - CASE(OP_put_var): - CASE(OP_put_var_init): - { - int ret; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - sf->cur_pc = pc; - - ret = JS_SetGlobalVar(ctx, atom, sp[-1], opcode - OP_put_var); - sp--; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_put_var_strict): - { - int ret; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - sf->cur_pc = pc; - - /* sp[-2] is JS_TRUE or JS_FALSE */ - if (unlikely(!JS_VALUE_GET_INT(sp[-2]))) { - JS_ThrowReferenceErrorNotDefined(ctx, atom); - goto exception; - } - ret = JS_SetGlobalVar(ctx, atom, sp[-1], 2); - sp -= 2; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_check_define_var): - { - JSAtom atom; - int flags; - atom = get_u32(pc); - flags = pc[4]; - pc += 5; - if (JS_CheckDefineGlobalVar(ctx, atom, flags)) - goto exception; - } - BREAK; - CASE(OP_define_var): - { - JSAtom atom; - int flags; - atom = get_u32(pc); - flags = pc[4]; - pc += 5; - if (JS_DefineGlobalVar(ctx, atom, flags)) - goto exception; - } - BREAK; - CASE(OP_define_func): - { - JSAtom atom; - int flags; - atom = get_u32(pc); - flags = pc[4]; - pc += 5; - if (JS_DefineGlobalFunction(ctx, atom, sp[-1], flags)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp--; - } - BREAK; - - CASE(OP_get_loc): - { - int idx; - idx = get_u16(pc); - pc += 2; - sp[0] = js_dup(var_buf[idx]); - sp++; - } - BREAK; - CASE(OP_put_loc): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, &var_buf[idx], sp[-1]); - sp--; - } - BREAK; - CASE(OP_set_loc): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, &var_buf[idx], js_dup(sp[-1])); - } - BREAK; - CASE(OP_get_arg): - { - int idx; - idx = get_u16(pc); - pc += 2; - sp[0] = js_dup(arg_buf[idx]); - sp++; - } - BREAK; - CASE(OP_put_arg): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, &arg_buf[idx], sp[-1]); - sp--; - } - BREAK; - CASE(OP_set_arg): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, &arg_buf[idx], js_dup(sp[-1])); - } - BREAK; - - CASE(OP_get_loc8): *sp++ = js_dup(var_buf[*pc++]); BREAK; - CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK; - CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], js_dup(sp[-1])); BREAK; - - // Observation: get_loc0 and get_loc1 are individually very - // frequent opcodes _and_ they are very often paired together, - // making them ideal candidates for opcode fusion. - CASE(OP_get_loc0_loc1): - *sp++ = js_dup(var_buf[0]); - *sp++ = js_dup(var_buf[1]); - BREAK; - - CASE(OP_get_loc0): *sp++ = js_dup(var_buf[0]); BREAK; - CASE(OP_get_loc1): *sp++ = js_dup(var_buf[1]); BREAK; - CASE(OP_get_loc2): *sp++ = js_dup(var_buf[2]); BREAK; - CASE(OP_get_loc3): *sp++ = js_dup(var_buf[3]); BREAK; - CASE(OP_put_loc0): set_value(ctx, &var_buf[0], *--sp); BREAK; - CASE(OP_put_loc1): set_value(ctx, &var_buf[1], *--sp); BREAK; - CASE(OP_put_loc2): set_value(ctx, &var_buf[2], *--sp); BREAK; - CASE(OP_put_loc3): set_value(ctx, &var_buf[3], *--sp); BREAK; - CASE(OP_set_loc0): set_value(ctx, &var_buf[0], js_dup(sp[-1])); BREAK; - CASE(OP_set_loc1): set_value(ctx, &var_buf[1], js_dup(sp[-1])); BREAK; - CASE(OP_set_loc2): set_value(ctx, &var_buf[2], js_dup(sp[-1])); BREAK; - CASE(OP_set_loc3): set_value(ctx, &var_buf[3], js_dup(sp[-1])); BREAK; - CASE(OP_get_arg0): *sp++ = js_dup(arg_buf[0]); BREAK; - CASE(OP_get_arg1): *sp++ = js_dup(arg_buf[1]); BREAK; - CASE(OP_get_arg2): *sp++ = js_dup(arg_buf[2]); BREAK; - CASE(OP_get_arg3): *sp++ = js_dup(arg_buf[3]); BREAK; - CASE(OP_put_arg0): set_value(ctx, &arg_buf[0], *--sp); BREAK; - CASE(OP_put_arg1): set_value(ctx, &arg_buf[1], *--sp); BREAK; - CASE(OP_put_arg2): set_value(ctx, &arg_buf[2], *--sp); BREAK; - CASE(OP_put_arg3): set_value(ctx, &arg_buf[3], *--sp); BREAK; - CASE(OP_set_arg0): set_value(ctx, &arg_buf[0], js_dup(sp[-1])); BREAK; - CASE(OP_set_arg1): set_value(ctx, &arg_buf[1], js_dup(sp[-1])); BREAK; - CASE(OP_set_arg2): set_value(ctx, &arg_buf[2], js_dup(sp[-1])); BREAK; - CASE(OP_set_arg3): set_value(ctx, &arg_buf[3], js_dup(sp[-1])); BREAK; - CASE(OP_get_var_ref0): *sp++ = js_dup(*var_refs[0]->pvalue); BREAK; - CASE(OP_get_var_ref1): *sp++ = js_dup(*var_refs[1]->pvalue); BREAK; - CASE(OP_get_var_ref2): *sp++ = js_dup(*var_refs[2]->pvalue); BREAK; - CASE(OP_get_var_ref3): *sp++ = js_dup(*var_refs[3]->pvalue); BREAK; - CASE(OP_put_var_ref0): set_value(ctx, var_refs[0]->pvalue, *--sp); BREAK; - CASE(OP_put_var_ref1): set_value(ctx, var_refs[1]->pvalue, *--sp); BREAK; - CASE(OP_put_var_ref2): set_value(ctx, var_refs[2]->pvalue, *--sp); BREAK; - CASE(OP_put_var_ref3): set_value(ctx, var_refs[3]->pvalue, *--sp); BREAK; - CASE(OP_set_var_ref0): set_value(ctx, var_refs[0]->pvalue, js_dup(sp[-1])); BREAK; - CASE(OP_set_var_ref1): set_value(ctx, var_refs[1]->pvalue, js_dup(sp[-1])); BREAK; - CASE(OP_set_var_ref2): set_value(ctx, var_refs[2]->pvalue, js_dup(sp[-1])); BREAK; - CASE(OP_set_var_ref3): set_value(ctx, var_refs[3]->pvalue, js_dup(sp[-1])); BREAK; - - CASE(OP_get_var_ref): - { - int idx; - JSValue val; - idx = get_u16(pc); - pc += 2; - val = *var_refs[idx]->pvalue; - sp[0] = js_dup(val); - sp++; - } - BREAK; - CASE(OP_put_var_ref): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, var_refs[idx]->pvalue, sp[-1]); - sp--; - } - BREAK; - CASE(OP_set_var_ref): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, var_refs[idx]->pvalue, js_dup(sp[-1])); - } - BREAK; - CASE(OP_get_var_ref_check): - { - int idx; - JSValue val; - idx = get_u16(pc); - pc += 2; - val = *var_refs[idx]->pvalue; - if (unlikely(JS_IsUninitialized(val))) { - JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, true); - goto exception; - } - sp[0] = js_dup(val); - sp++; - } - BREAK; - CASE(OP_put_var_ref_check): - { - int idx; - idx = get_u16(pc); - pc += 2; - if (unlikely(JS_IsUninitialized(*var_refs[idx]->pvalue))) { - JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, true); - goto exception; - } - set_value(ctx, var_refs[idx]->pvalue, sp[-1]); - sp--; - } - BREAK; - CASE(OP_put_var_ref_check_init): - { - int idx; - idx = get_u16(pc); - pc += 2; - if (unlikely(!JS_IsUninitialized(*var_refs[idx]->pvalue))) { - JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, true); - goto exception; - } - set_value(ctx, var_refs[idx]->pvalue, sp[-1]); - sp--; - } - BREAK; - CASE(OP_set_loc_uninitialized): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, &var_buf[idx], JS_UNINITIALIZED); - } - BREAK; - CASE(OP_get_loc_check): - { - int idx; - idx = get_u16(pc); - pc += 2; - if (unlikely(JS_IsUninitialized(var_buf[idx]))) { - JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, - false); - goto exception; - } - sp[0] = js_dup(var_buf[idx]); - sp++; - } - BREAK; - CASE(OP_put_loc_check): - { - int idx; - idx = get_u16(pc); - pc += 2; - if (unlikely(JS_IsUninitialized(var_buf[idx]))) { - JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, - false); - goto exception; - } - set_value(ctx, &var_buf[idx], sp[-1]); - sp--; - } - BREAK; - CASE(OP_put_loc_check_init): - { - int idx; - idx = get_u16(pc); - pc += 2; - if (unlikely(!JS_IsUninitialized(var_buf[idx]))) { - JS_ThrowReferenceError(caller_ctx, - "'this' can be initialized only once"); - goto exception; - } - set_value(ctx, &var_buf[idx], sp[-1]); - sp--; - } - BREAK; - CASE(OP_close_loc): - { - int idx; - idx = get_u16(pc); - pc += 2; - close_lexical_var(ctx, sf, idx); - } - BREAK; - - CASE(OP_make_loc_ref): - CASE(OP_make_arg_ref): - CASE(OP_make_var_ref_ref): - { - JSVarRef *var_ref; - JSProperty *pr; - JSAtom atom; - int idx; - atom = get_u32(pc); - idx = get_u16(pc + 4); - pc += 6; - *sp++ = JS_NewObjectProto(ctx, JS_NULL); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - if (opcode == OP_make_var_ref_ref) { - var_ref = var_refs[idx]; - var_ref->header.ref_count++; - } else { - var_ref = get_var_ref(ctx, sf, idx, opcode == OP_make_arg_ref); - if (!var_ref) - goto exception; - } - pr = add_property(ctx, JS_VALUE_GET_OBJ(sp[-1]), atom, - JS_PROP_WRITABLE | JS_PROP_VARREF); - if (!pr) { - free_var_ref(rt, var_ref); - goto exception; - } - pr->u.var_ref = var_ref; - *sp++ = JS_AtomToValue(ctx, atom); - } - BREAK; - CASE(OP_make_var_ref): - { - JSAtom atom; - atom = get_u32(pc); - pc += 4; - - if (JS_GetGlobalVarRef(ctx, atom, sp)) - goto exception; - sp += 2; - } - BREAK; - - CASE(OP_goto): - pc += (int32_t)get_u32(pc); - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - BREAK; - CASE(OP_goto16): - pc += (int16_t)get_u16(pc); - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - BREAK; - CASE(OP_goto8): - pc += (int8_t)pc[0]; - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - BREAK; - CASE(OP_if_true): - { - int res; - JSValue op1; - - op1 = sp[-1]; - pc += 4; - if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { - res = JS_VALUE_GET_INT(op1); - } else { - res = JS_ToBoolFree(ctx, op1); - } - sp--; - if (res) { - pc += (int32_t)get_u32(pc - 4) - 4; - } - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - } - BREAK; - CASE(OP_if_false): - { - int res; - JSValue op1; - - op1 = sp[-1]; - pc += 4; - if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { - res = JS_VALUE_GET_INT(op1); - } else { - res = JS_ToBoolFree(ctx, op1); - } - sp--; - if (!res) { - pc += (int32_t)get_u32(pc - 4) - 4; - } - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - } - BREAK; - CASE(OP_if_true8): - { - int res; - JSValue op1; - - op1 = sp[-1]; - pc += 1; - if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { - res = JS_VALUE_GET_INT(op1); - } else { - res = JS_ToBoolFree(ctx, op1); - } - sp--; - if (res) { - pc += (int8_t)pc[-1] - 1; - } - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - } - BREAK; - CASE(OP_if_false8): - { - int res; - JSValue op1; - - op1 = sp[-1]; - pc += 1; - if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { - res = JS_VALUE_GET_INT(op1); - } else { - res = JS_ToBoolFree(ctx, op1); - } - sp--; - if (!res) { - pc += (int8_t)pc[-1] - 1; - } - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - } - BREAK; - CASE(OP_catch): - { - int32_t diff; - diff = get_u32(pc); - sp[0] = JS_NewCatchOffset(ctx, pc + diff - b->byte_code_buf); - sp++; - pc += 4; - } - BREAK; - CASE(OP_gosub): - { - int32_t diff; - diff = get_u32(pc); - /* XXX: should have a different tag to avoid security flaw */ - sp[0] = js_int32(pc + 4 - b->byte_code_buf); - sp++; - pc += diff; - } - BREAK; - CASE(OP_ret): - { - JSValue op1; - uint32_t pos; - op1 = sp[-1]; - if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_INT)) - goto ret_fail; - pos = JS_VALUE_GET_INT(op1); - if (unlikely(pos >= b->byte_code_len)) { - ret_fail: - JS_ThrowInternalError(ctx, "invalid ret value"); - goto exception; - } - sp--; - pc = b->byte_code_buf + pos; - } - BREAK; - - CASE(OP_for_in_start): - sf->cur_pc = pc; - if (js_for_in_start(ctx, sp)) - goto exception; - BREAK; - CASE(OP_for_in_next): - sf->cur_pc = pc; - if (js_for_in_next(ctx, sp)) - goto exception; - sp += 2; - BREAK; - CASE(OP_for_of_start): - sf->cur_pc = pc; - if (js_for_of_start(ctx, sp, false)) - goto exception; - sp += 1; - *sp++ = JS_NewCatchOffset(ctx, 0); - BREAK; - CASE(OP_for_of_next): - { - int offset = -3 - pc[0]; - pc += 1; - sf->cur_pc = pc; - if (js_for_of_next(ctx, sp, offset)) - goto exception; - sp += 2; - } - BREAK; - CASE(OP_for_await_of_start): - sf->cur_pc = pc; - if (js_for_of_start(ctx, sp, true)) - goto exception; - sp += 1; - *sp++ = JS_NewCatchOffset(ctx, 0); - BREAK; - CASE(OP_iterator_get_value_done): - sf->cur_pc = pc; - if (js_iterator_get_value_done(ctx, sp)) - goto exception; - sp += 1; - BREAK; - CASE(OP_iterator_check_object): - if (unlikely(!JS_IsObject(sp[-1]))) { - JS_ThrowTypeError(ctx, "iterator must return an object"); - goto exception; - } - BREAK; - - CASE(OP_iterator_close): - /* iter_obj next catch_offset -> */ - sp--; /* drop the catch offset to avoid getting caught by exception */ - JS_FreeValue(ctx, sp[-1]); /* drop the next method */ - sp--; - if (!JS_IsUndefined(sp[-1])) { - sf->cur_pc = pc; - if (JS_IteratorClose(ctx, sp[-1], false)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - } - sp--; - BREAK; - CASE(OP_nip_catch): - { - JSValue ret_val; - /* catch_offset ... ret_val -> ret_eval */ - ret_val = *--sp; - while (sp > stack_buf && - JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) { - JS_FreeValue(ctx, *--sp); - } - if (unlikely(sp == stack_buf)) { - JS_ThrowInternalError(ctx, "nip_catch"); - JS_FreeValue(ctx, ret_val); - goto exception; - } - sp[-1] = ret_val; - } - BREAK; - - CASE(OP_iterator_next): - /* stack: iter_obj next catch_offset val */ - { - JSValue ret; - sf->cur_pc = pc; - ret = JS_Call(ctx, sp[-3], sp[-4], 1, vc(sp - 1)); - if (JS_IsException(ret)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = ret; - } - BREAK; - - CASE(OP_iterator_call): - /* stack: iter_obj next catch_offset val */ - { - JSValue method, ret; - bool ret_flag; - int flags; - flags = *pc++; - sf->cur_pc = pc; - method = JS_GetProperty(ctx, sp[-4], (flags & 1) ? - JS_ATOM_throw : JS_ATOM_return); - if (JS_IsException(method)) - goto exception; - if (JS_IsUndefined(method) || JS_IsNull(method)) { - ret_flag = true; - } else { - if (flags & 2) { - /* no argument */ - ret = JS_CallFree(ctx, method, sp[-4], - 0, NULL); - } else { - ret = JS_CallFree(ctx, method, sp[-4], - 1, vc(sp - 1)); - } - if (JS_IsException(ret)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = ret; - ret_flag = false; - } - sp[0] = js_bool(ret_flag); - sp += 1; - } - BREAK; - - CASE(OP_lnot): - { - int res; - JSValue op1; - - op1 = sp[-1]; - if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { - res = JS_VALUE_GET_INT(op1) != 0; - } else { - res = JS_ToBoolFree(ctx, op1); - } - sp[-1] = js_bool(!res); - } - BREAK; - - CASE(OP_get_field): - { - JSValue val; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - sf->cur_pc = pc; - val = JS_GetPropertyInternal(ctx, sp[-1], atom, sp[-1], false); - if (unlikely(JS_IsException(val))) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = val; - } - BREAK; - - CASE(OP_get_field2): - { - JSValue val; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - sf->cur_pc = pc; - val = JS_GetPropertyInternal(ctx, sp[-1], atom, sp[-1], false); - if (unlikely(JS_IsException(val))) - goto exception; - *sp++ = val; - } - BREAK; - - CASE(OP_put_field): - { - int ret; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - sf->cur_pc = pc; - ret = JS_SetPropertyInternal2(ctx, - sp[-2], atom, - sp[-1], sp[-2], - JS_PROP_THROW_STRICT); - JS_FreeValue(ctx, sp[-2]); - sp -= 2; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_private_symbol): - { - JSAtom atom; - JSValue val; - - atom = get_u32(pc); - pc += 4; - val = JS_NewSymbolFromAtom(ctx, atom, JS_ATOM_TYPE_PRIVATE); - if (JS_IsException(val)) - goto exception; - *sp++ = val; - } - BREAK; - - CASE(OP_get_private_field): - { - JSValue val; - sf->cur_pc = pc; - val = JS_GetPrivateField(ctx, sp[-2], sp[-1]); - JS_FreeValue(ctx, sp[-1]); - JS_FreeValue(ctx, sp[-2]); - sp[-2] = val; - sp--; - if (unlikely(JS_IsException(val))) - goto exception; - } - BREAK; - - CASE(OP_put_private_field): - { - int ret; - sf->cur_pc = pc; - ret = JS_SetPrivateField(ctx, sp[-3], sp[-1], sp[-2]); - JS_FreeValue(ctx, sp[-3]); - JS_FreeValue(ctx, sp[-1]); - sp -= 3; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_define_private_field): - { - int ret; - ret = JS_DefinePrivateField(ctx, sp[-3], sp[-2], sp[-1]); - JS_FreeValue(ctx, sp[-2]); - sp -= 2; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_define_field): - { - int ret; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - - ret = JS_DefinePropertyValue(ctx, sp[-2], atom, sp[-1], - JS_PROP_C_W_E | JS_PROP_THROW); - sp--; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_set_name): - { - int ret; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - - ret = JS_DefineObjectName(ctx, sp[-1], atom, JS_PROP_CONFIGURABLE); - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - CASE(OP_set_name_computed): - { - int ret; - ret = JS_DefineObjectNameComputed(ctx, sp[-1], sp[-2], JS_PROP_CONFIGURABLE); - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - CASE(OP_set_proto): - { - JSValue proto; - proto = sp[-1]; - if (JS_IsObject(proto) || JS_IsNull(proto)) { - if (JS_SetPrototypeInternal(ctx, sp[-2], proto, true) < 0) - goto exception; - } - JS_FreeValue(ctx, proto); - sp--; - } - BREAK; - CASE(OP_set_home_object): - js_method_set_home_object(ctx, sp[-1], sp[-2]); - BREAK; - CASE(OP_define_method): - CASE(OP_define_method_computed): - { - JSValue getter, setter, value; - JSValue obj; - JSAtom atom; - int flags, ret, op_flags; - bool is_computed; -#define OP_DEFINE_METHOD_METHOD 0 -#define OP_DEFINE_METHOD_GETTER 1 -#define OP_DEFINE_METHOD_SETTER 2 -#define OP_DEFINE_METHOD_ENUMERABLE 4 - - is_computed = (opcode == OP_define_method_computed); - if (is_computed) { - atom = JS_ValueToAtom(ctx, sp[-2]); - if (unlikely(atom == JS_ATOM_NULL)) - goto exception; - opcode += OP_define_method - OP_define_method_computed; - } else { - atom = get_u32(pc); - pc += 4; - } - op_flags = *pc++; - - obj = sp[-2 - is_computed]; - flags = JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE | - JS_PROP_HAS_ENUMERABLE | JS_PROP_THROW; - if (op_flags & OP_DEFINE_METHOD_ENUMERABLE) - flags |= JS_PROP_ENUMERABLE; - op_flags &= 3; - value = JS_UNDEFINED; - getter = JS_UNDEFINED; - setter = JS_UNDEFINED; - if (op_flags == OP_DEFINE_METHOD_METHOD) { - value = sp[-1]; - flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE; - } else if (op_flags == OP_DEFINE_METHOD_GETTER) { - getter = sp[-1]; - flags |= JS_PROP_HAS_GET; - } else { - setter = sp[-1]; - flags |= JS_PROP_HAS_SET; - } - ret = js_method_set_properties(ctx, sp[-1], atom, flags, obj); - if (ret >= 0) { - ret = JS_DefineProperty(ctx, obj, atom, value, - getter, setter, flags); - } - JS_FreeValue(ctx, sp[-1]); - if (is_computed) { - JS_FreeAtom(ctx, atom); - JS_FreeValue(ctx, sp[-2]); - } - sp -= 1 + is_computed; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_define_class): - CASE(OP_define_class_computed): - { - int class_flags; - JSAtom atom; - - atom = get_u32(pc); - class_flags = pc[4]; - pc += 5; - if (js_op_define_class(ctx, sp, atom, class_flags, - var_refs, sf, - (opcode == OP_define_class_computed)) < 0) - goto exception; - } - BREAK; - - CASE(OP_get_array_el): - { - JSValue val; - - sf->cur_pc = pc; - val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); - JS_FreeValue(ctx, sp[-2]); - sp[-2] = val; - sp--; - if (unlikely(JS_IsException(val))) - goto exception; - } - BREAK; - - CASE(OP_get_array_el2): - { - JSValue val; - - sf->cur_pc = pc; - val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); - sp[-1] = val; - if (unlikely(JS_IsException(val))) - goto exception; - } - BREAK; - - CASE(OP_get_ref_value): - { - JSValue val; - sf->cur_pc = pc; - if (unlikely(JS_IsUndefined(sp[-2]))) { - JSAtom atom = JS_ValueToAtom(ctx, sp[-1]); - if (atom != JS_ATOM_NULL) { - JS_ThrowReferenceErrorNotDefined(ctx, atom); - JS_FreeAtom(ctx, atom); - } - goto exception; - } - val = JS_GetPropertyValue(ctx, sp[-2], - js_dup(sp[-1])); - if (unlikely(JS_IsException(val))) - goto exception; - sp[0] = val; - sp++; - } - BREAK; - - CASE(OP_get_super_value): - { - JSValue val; - JSAtom atom; - sf->cur_pc = pc; - atom = JS_ValueToAtom(ctx, sp[-1]); - if (unlikely(atom == JS_ATOM_NULL)) - goto exception; - val = JS_GetPropertyInternal(ctx, sp[-2], atom, sp[-3], false); - JS_FreeAtom(ctx, atom); - if (unlikely(JS_IsException(val))) - goto exception; - JS_FreeValue(ctx, sp[-1]); - JS_FreeValue(ctx, sp[-2]); - JS_FreeValue(ctx, sp[-3]); - sp[-3] = val; - sp -= 2; - } - BREAK; - - CASE(OP_put_array_el): - { - int ret; - sf->cur_pc = pc; - ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], JS_PROP_THROW_STRICT); - JS_FreeValue(ctx, sp[-3]); - sp -= 3; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_put_ref_value): - { - int ret, flags; - sf->cur_pc = pc; - flags = JS_PROP_THROW_STRICT; - if (unlikely(JS_IsUndefined(sp[-3]))) { - if (is_strict_mode(ctx)) { - JSAtom atom = JS_ValueToAtom(ctx, sp[-2]); - if (atom != JS_ATOM_NULL) { - JS_ThrowReferenceErrorNotDefined(ctx, atom); - JS_FreeAtom(ctx, atom); - } - goto exception; - } else { - sp[-3] = js_dup(ctx->global_obj); - } - } else { - if (is_strict_mode(ctx)) - flags |= JS_PROP_NO_ADD; - } - ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], flags); - JS_FreeValue(ctx, sp[-3]); - sp -= 3; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_put_super_value): - { - int ret; - JSAtom atom; - sf->cur_pc = pc; - if (JS_VALUE_GET_TAG(sp[-3]) != JS_TAG_OBJECT) { - JS_ThrowTypeErrorNotAnObject(ctx); - goto exception; - } - atom = JS_ValueToAtom(ctx, sp[-2]); - if (unlikely(atom == JS_ATOM_NULL)) - goto exception; - ret = JS_SetPropertyInternal2(ctx, - sp[-3], atom, - sp[-1], sp[-4], - JS_PROP_THROW_STRICT); - JS_FreeAtom(ctx, atom); - JS_FreeValue(ctx, sp[-4]); - JS_FreeValue(ctx, sp[-3]); - JS_FreeValue(ctx, sp[-2]); - sp -= 4; - if (ret < 0) - goto exception; - } - BREAK; - - CASE(OP_define_array_el): - { - int ret; - ret = JS_DefinePropertyValueValue(ctx, sp[-3], js_dup(sp[-2]), sp[-1], - JS_PROP_C_W_E | JS_PROP_THROW); - sp -= 1; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_append): /* array pos enumobj -- array pos */ - { - sf->cur_pc = pc; - if (js_append_enumerate(ctx, sp)) - goto exception; - JS_FreeValue(ctx, *--sp); - } - BREAK; - - CASE(OP_copy_data_properties): /* target source excludeList */ - { - /* stack offsets (-1 based): - 2 bits for target, - 3 bits for source, - 2 bits for exclusionList */ - int mask; - - mask = *pc++; - sf->cur_pc = pc; - if (JS_CopyDataProperties(ctx, sp[-1 - (mask & 3)], - sp[-1 - ((mask >> 2) & 7)], - sp[-1 - ((mask >> 5) & 7)], 0)) - goto exception; - } - BREAK; - - CASE(OP_add): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - int64_t r; - r = (int64_t)JS_VALUE_GET_INT(op1) + JS_VALUE_GET_INT(op2); - if (unlikely((int)r != r)) - goto add_slow; - sp[-2] = js_int32(r); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); - sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) + - JS_VALUE_GET_FLOAT64(op2)); - JS_X87_FPCW_RESTORE(fpcw); - sp--; - } else { - add_slow: - sf->cur_pc = pc; - if (js_add_slow(ctx, sp)) - goto exception; - sp--; - } - } - BREAK; - CASE(OP_add_loc): - { - JSValue *pv; - int idx; - idx = *pc; - pc += 1; - - pv = &var_buf[idx]; - if (likely(JS_VALUE_IS_BOTH_INT(*pv, sp[-1]))) { - int64_t r; - r = (int64_t)JS_VALUE_GET_INT(*pv) + - JS_VALUE_GET_INT(sp[-1]); - if (unlikely((int)r != r)) - goto add_loc_slow; - *pv = js_int32(r); - sp--; - } else if (JS_VALUE_GET_TAG(*pv) == JS_TAG_STRING) { - JSValue op1; - op1 = sp[-1]; - sp--; - sf->cur_pc = pc; - op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); - if (JS_IsException(op1)) - goto exception; - op1 = JS_ConcatString(ctx, js_dup(*pv), op1); - if (JS_IsException(op1)) - goto exception; - set_value(ctx, pv, op1); - } else { - JSValue ops[2]; - add_loc_slow: - /* In case of exception, js_add_slow frees ops[0] - and ops[1], so we must duplicate *pv */ - sf->cur_pc = pc; - ops[0] = js_dup(*pv); - ops[1] = sp[-1]; - sp--; - if (js_add_slow(ctx, ops + 2)) - goto exception; - set_value(ctx, pv, ops[0]); - } - } - BREAK; - CASE(OP_sub): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - int64_t r; - r = (int64_t)JS_VALUE_GET_INT(op1) - JS_VALUE_GET_INT(op2); - if (unlikely((int)r != r)) - goto binary_arith_slow; - sp[-2] = js_int32(r); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); - sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) - - JS_VALUE_GET_FLOAT64(op2)); - JS_X87_FPCW_RESTORE(fpcw); - sp--; - } else { - goto binary_arith_slow; - } - } - BREAK; - CASE(OP_mul): - { - JSValue op1, op2; - double d; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - int32_t v1, v2; - int64_t r; - v1 = JS_VALUE_GET_INT(op1); - v2 = JS_VALUE_GET_INT(op2); - r = (int64_t)v1 * v2; - if (unlikely((int)r != r)) { - d = (double)r; - goto mul_fp_res; - } - /* need to test zero case for -0 result */ - if (unlikely(r == 0 && (v1 | v2) < 0)) { - d = -0.0; - goto mul_fp_res; - } - sp[-2] = js_int32(r); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); - d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2); - JS_X87_FPCW_RESTORE(fpcw); - mul_fp_res: - sp[-2] = js_float64(d); - sp--; - } else { - goto binary_arith_slow; - } - } - BREAK; - CASE(OP_div): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - int v1, v2; - v1 = JS_VALUE_GET_INT(op1); - v2 = JS_VALUE_GET_INT(op2); - sp[-2] = js_number((double)v1 / (double)v2); - sp--; - } else { - goto binary_arith_slow; - } - } - BREAK; - CASE(OP_mod): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - int v1, v2, r; - v1 = JS_VALUE_GET_INT(op1); - v2 = JS_VALUE_GET_INT(op2); - /* We must avoid v2 = 0, v1 = INT32_MIN and v2 = - -1 and the cases where the result is -0. */ - if (unlikely(v1 < 0 || v2 <= 0)) - goto binary_arith_slow; - r = v1 % v2; - sp[-2] = js_int32(r); - sp--; - } else { - goto binary_arith_slow; - } - } - BREAK; - CASE(OP_pow): - binary_arith_slow: - sf->cur_pc = pc; - if (js_binary_arith_slow(ctx, sp, opcode)) - goto exception; - sp--; - BREAK; - - CASE(OP_plus): - { - JSValue op1; - uint32_t tag; - op1 = sp[-1]; - tag = JS_VALUE_GET_TAG(op1); - if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag)) { - } else { - sf->cur_pc = pc; - if (js_unary_arith_slow(ctx, sp, opcode)) - goto exception; - } - } - BREAK; - CASE(OP_neg): - { - JSValue op1; - uint32_t tag; - int val; - double d; - op1 = sp[-1]; - tag = JS_VALUE_GET_TAG(op1); - if (tag == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - /* Note: -0 cannot be expressed as integer */ - if (unlikely(val == 0)) { - d = -0.0; - goto neg_fp_res; - } - if (unlikely(val == INT32_MIN)) { - d = -(double)val; - goto neg_fp_res; - } - sp[-1] = js_int32(-val); - } else if (JS_TAG_IS_FLOAT64(tag)) { - d = -JS_VALUE_GET_FLOAT64(op1); - neg_fp_res: - sp[-1] = js_float64(d); - } else { - sf->cur_pc = pc; - if (js_unary_arith_slow(ctx, sp, opcode)) - goto exception; - } - } - BREAK; - CASE(OP_inc): - { - JSValue op1; - int val; - op1 = sp[-1]; - if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - if (unlikely(val == INT32_MAX)) - goto inc_slow; - sp[-1] = js_int32(val + 1); - } else { - inc_slow: - sf->cur_pc = pc; - if (js_unary_arith_slow(ctx, sp, opcode)) - goto exception; - } - } - BREAK; - CASE(OP_dec): - { - JSValue op1; - int val; - op1 = sp[-1]; - if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - if (unlikely(val == INT32_MIN)) - goto dec_slow; - sp[-1] = js_int32(val - 1); - } else { - dec_slow: - sf->cur_pc = pc; - if (js_unary_arith_slow(ctx, sp, opcode)) - goto exception; - } - } - BREAK; - CASE(OP_post_inc): - CASE(OP_post_dec): - sf->cur_pc = pc; - if (js_post_inc_slow(ctx, sp, opcode)) - goto exception; - sp++; - BREAK; - CASE(OP_inc_loc): - { - JSValue op1; - int val; - int idx; - idx = *pc; - pc += 1; - - op1 = var_buf[idx]; - if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - if (unlikely(val == INT32_MAX)) - goto inc_loc_slow; - var_buf[idx] = js_int32(val + 1); - } else { - inc_loc_slow: - sf->cur_pc = pc; - /* must duplicate otherwise the variable value may - be destroyed before JS code accesses it */ - op1 = js_dup(op1); - if (js_unary_arith_slow(ctx, &op1 + 1, OP_inc)) - goto exception; - set_value(ctx, &var_buf[idx], op1); - } - } - BREAK; - CASE(OP_dec_loc): - { - JSValue op1; - int val; - int idx; - idx = *pc; - pc += 1; - - op1 = var_buf[idx]; - if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - if (unlikely(val == INT32_MIN)) - goto dec_loc_slow; - var_buf[idx] = js_int32(val - 1); - } else { - dec_loc_slow: - sf->cur_pc = pc; - /* must duplicate otherwise the variable value may - be destroyed before JS code accesses it */ - op1 = js_dup(op1); - if (js_unary_arith_slow(ctx, &op1 + 1, OP_dec)) - goto exception; - set_value(ctx, &var_buf[idx], op1); - } - } - BREAK; - CASE(OP_not): - { - JSValue op1; - op1 = sp[-1]; - if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - sp[-1] = js_int32(~JS_VALUE_GET_INT(op1)); - } else { - sf->cur_pc = pc; - if (js_not_slow(ctx, sp)) - goto exception; - } - } - BREAK; - - CASE(OP_shl): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - uint32_t v1, v2; - v1 = JS_VALUE_GET_INT(op1); - v2 = JS_VALUE_GET_INT(op2) & 0x1f; - sp[-2] = js_int32(v1 << v2); - sp--; - } else { - sf->cur_pc = pc; - if (js_binary_logic_slow(ctx, sp, opcode)) - goto exception; - sp--; - } - } - BREAK; - CASE(OP_shr): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - uint32_t v2; - v2 = JS_VALUE_GET_INT(op2); - v2 &= 0x1f; - sp[-2] = js_uint32((uint32_t)JS_VALUE_GET_INT(op1) >> v2); - sp--; - } else { - sf->cur_pc = pc; - if (js_shr_slow(ctx, sp)) - goto exception; - sp--; - } - } - BREAK; - CASE(OP_sar): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - uint32_t v2; - v2 = JS_VALUE_GET_INT(op2); - if (unlikely(v2 > 0x1f)) { - v2 &= 0x1f; - } - sp[-2] = js_int32((int)JS_VALUE_GET_INT(op1) >> v2); - sp--; - } else { - sf->cur_pc = pc; - if (js_binary_logic_slow(ctx, sp, opcode)) - goto exception; - sp--; - } - } - BREAK; - CASE(OP_and): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - sp[-2] = js_int32(JS_VALUE_GET_INT(op1) & JS_VALUE_GET_INT(op2)); - sp--; - } else { - sf->cur_pc = pc; - if (js_binary_logic_slow(ctx, sp, opcode)) - goto exception; - sp--; - } - } - BREAK; - CASE(OP_or): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - sp[-2] = js_int32(JS_VALUE_GET_INT(op1) | JS_VALUE_GET_INT(op2)); - sp--; - } else { - sf->cur_pc = pc; - if (js_binary_logic_slow(ctx, sp, opcode)) - goto exception; - sp--; - } - } - BREAK; - CASE(OP_xor): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - sp[-2] = js_int32(JS_VALUE_GET_INT(op1) ^ JS_VALUE_GET_INT(op2)); - sp--; - } else { - sf->cur_pc = pc; - if (js_binary_logic_slow(ctx, sp, opcode)) - goto exception; - sp--; - } - } - BREAK; - - -#define OP_CMP(opcode, binary_op, slow_call) \ - CASE(opcode): \ - { \ - JSValue op1, op2; \ - op1 = sp[-2]; \ - op2 = sp[-1]; \ - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \ - sp[-2] = js_bool(JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \ - sp--; \ - } else { \ - sf->cur_pc = pc; \ - if (slow_call) \ - goto exception; \ - sp--; \ - } \ - } \ - BREAK - - OP_CMP(OP_lt, <, js_relational_slow(ctx, sp, opcode)); - OP_CMP(OP_lte, <=, js_relational_slow(ctx, sp, opcode)); - OP_CMP(OP_gt, >, js_relational_slow(ctx, sp, opcode)); - OP_CMP(OP_gte, >=, js_relational_slow(ctx, sp, opcode)); - OP_CMP(OP_eq, ==, js_eq_slow(ctx, sp, 0)); - OP_CMP(OP_neq, !=, js_eq_slow(ctx, sp, 1)); - OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, sp, 0)); - OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, sp, 1)); - - CASE(OP_in): - sf->cur_pc = pc; - if (js_operator_in(ctx, sp)) - goto exception; - sp--; - BREAK; - CASE(OP_private_in): - if (js_operator_private_in(ctx, sp)) - goto exception; - sp--; - BREAK; - CASE(OP_instanceof): - sf->cur_pc = pc; - if (js_operator_instanceof(ctx, sp)) - goto exception; - sp--; - BREAK; - CASE(OP_typeof): - { - JSValue op1; - JSAtom atom; - - op1 = sp[-1]; - atom = js_operator_typeof(ctx, op1); - JS_FreeValue(ctx, op1); - sp[-1] = JS_AtomToString(ctx, atom); - } - BREAK; - CASE(OP_delete): - sf->cur_pc = pc; - if (js_operator_delete(ctx, sp)) - goto exception; - sp--; - BREAK; - CASE(OP_delete_var): - { - JSAtom atom; - int ret; - - atom = get_u32(pc); - pc += 4; - - sf->cur_pc = pc; - ret = JS_DeleteGlobalVar(ctx, atom); - if (unlikely(ret < 0)) - goto exception; - *sp++ = js_bool(ret); - } - BREAK; - - CASE(OP_to_object): - if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_OBJECT) { - sf->cur_pc = pc; - ret_val = JS_ToObject(ctx, sp[-1]); - if (JS_IsException(ret_val)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = ret_val; - } - BREAK; - - CASE(OP_to_propkey): - switch (JS_VALUE_GET_TAG(sp[-1])) { - case JS_TAG_INT: - case JS_TAG_STRING: - case JS_TAG_SYMBOL: - break; - default: - sf->cur_pc = pc; - ret_val = JS_ToPropertyKey(ctx, sp[-1]); - if (JS_IsException(ret_val)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = ret_val; - break; - } - BREAK; - - CASE(OP_to_propkey2): - /* must be tested first */ - if (unlikely(JS_IsUndefined(sp[-2]) || JS_IsNull(sp[-2]))) { - JS_ThrowTypeError(ctx, "value has no property"); - goto exception; - } - switch (JS_VALUE_GET_TAG(sp[-1])) { - case JS_TAG_INT: - case JS_TAG_STRING: - case JS_TAG_SYMBOL: - break; - default: - sf->cur_pc = pc; - ret_val = JS_ToPropertyKey(ctx, sp[-1]); - if (JS_IsException(ret_val)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = ret_val; - break; - } - BREAK; - CASE(OP_with_get_var): - CASE(OP_with_put_var): - CASE(OP_with_delete_var): - CASE(OP_with_make_ref): - CASE(OP_with_get_ref): - CASE(OP_with_get_ref_undef): - { - JSAtom atom; - int32_t diff; - JSValue obj, val; - int ret, is_with; - atom = get_u32(pc); - diff = get_u32(pc + 4); - is_with = pc[8]; - pc += 9; - sf->cur_pc = pc; - - obj = sp[-1]; - ret = JS_HasProperty(ctx, obj, atom); - if (unlikely(ret < 0)) - goto exception; - if (ret) { - if (is_with) { - ret = js_has_unscopable(ctx, obj, atom); - if (unlikely(ret < 0)) - goto exception; - if (ret) - goto no_with; - } - switch (opcode) { - case OP_with_get_var: - val = JS_GetProperty(ctx, obj, atom); - if (unlikely(JS_IsException(val))) - goto exception; - set_value(ctx, &sp[-1], val); - break; - case OP_with_put_var: - /* XXX: check if strict mode */ - ret = JS_SetPropertyInternal(ctx, obj, atom, sp[-2], - JS_PROP_THROW_STRICT); - JS_FreeValue(ctx, sp[-1]); - sp -= 2; - if (unlikely(ret < 0)) - goto exception; - break; - case OP_with_delete_var: - ret = JS_DeleteProperty(ctx, obj, atom, 0); - if (unlikely(ret < 0)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = js_bool(ret); - break; - case OP_with_make_ref: - /* produce a pair object/propname on the stack */ - *sp++ = JS_AtomToValue(ctx, atom); - break; - case OP_with_get_ref: - /* produce a pair object/method on the stack */ - val = JS_GetProperty(ctx, obj, atom); - if (unlikely(JS_IsException(val))) - goto exception; - *sp++ = val; - break; - case OP_with_get_ref_undef: - /* produce a pair undefined/function on the stack */ - val = JS_GetProperty(ctx, obj, atom); - if (unlikely(JS_IsException(val))) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = JS_UNDEFINED; - *sp++ = val; - break; - } - pc += diff - 5; - } else { - no_with: - /* if not jumping, drop the object argument */ - JS_FreeValue(ctx, sp[-1]); - sp--; - } - } - BREAK; - - CASE(OP_await): - ret_val = js_int32(FUNC_RET_AWAIT); - goto done_generator; - CASE(OP_yield): - ret_val = js_int32(FUNC_RET_YIELD); - goto done_generator; - CASE(OP_yield_star): - CASE(OP_async_yield_star): - ret_val = js_int32(FUNC_RET_YIELD_STAR); - goto done_generator; - CASE(OP_return_async): - CASE(OP_initial_yield): - ret_val = JS_UNDEFINED; - goto done_generator; - - CASE(OP_nop): - BREAK; - CASE(OP_is_undefined_or_null): - if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED || - JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { - goto set_true; - } else { - goto free_and_set_false; - } - CASE(OP_is_undefined): - if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED) { - goto set_true; - } else { - goto free_and_set_false; - } - CASE(OP_is_null): - if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { - goto set_true; - } else { - goto free_and_set_false; - } - /* XXX: could merge to a single opcode */ - CASE(OP_typeof_is_undefined): - /* different from OP_is_undefined because of isHTMLDDA */ - if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_undefined) { - goto free_and_set_true; - } else { - goto free_and_set_false; - } - CASE(OP_typeof_is_function): - if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_function) { - goto free_and_set_true; - } else { - goto free_and_set_false; - } - free_and_set_true: - JS_FreeValue(ctx, sp[-1]); - set_true: - sp[-1] = JS_TRUE; - BREAK; - free_and_set_false: - JS_FreeValue(ctx, sp[-1]); - sp[-1] = JS_FALSE; - BREAK; - CASE(OP_invalid): - DEFAULT: - JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x", - (int)(pc - b->byte_code_buf - 1), opcode); - goto exception; - } - } - exception: - if (needs_backtrace(rt->current_exception) - || JS_IsUndefined(ctx->error_back_trace)) { - sf->cur_pc = pc; - build_backtrace(ctx, rt->current_exception, JS_UNDEFINED, - NULL, 0, 0, 0); - } - if (!JS_IsUncatchableError(rt->current_exception)) { - while (sp > stack_buf) { - JSValue val = *--sp; - JS_FreeValue(ctx, val); - if (JS_VALUE_GET_TAG(val) == JS_TAG_CATCH_OFFSET) { - int pos = JS_VALUE_GET_INT(val); - if (pos == 0) { - /* enumerator: close it with a throw */ - JS_FreeValue(ctx, sp[-1]); /* drop the next method */ - sp--; - JS_IteratorClose(ctx, sp[-1], true); - } else { - *sp++ = rt->current_exception; - rt->current_exception = JS_UNINITIALIZED; - JS_FreeValueRT(rt, ctx->error_back_trace); - ctx->error_back_trace = JS_UNDEFINED; - pc = b->byte_code_buf + pos; - goto restart; - } - } - } - } - ret_val = JS_EXCEPTION; - /* the local variables are freed by the caller in the generator - case. Hence the label 'done' should never be reached in a - generator function. */ - if (b->func_kind != JS_FUNC_NORMAL) { - done_generator: - sf->cur_pc = pc; - sf->cur_sp = sp; - } else { - done: - if (unlikely(!list_empty(&sf->var_ref_list))) { - /* variable references reference the stack: must close them */ - close_var_refs(rt, sf); - } - /* free the local variables and stack */ - for(pval = local_buf; pval < sp; pval++) { - JS_FreeValue(ctx, *pval); - } - } - rt->current_stack_frame = sf->prev_frame; - return ret_val; -} - -JSValue JS_Call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, - int argc, JSValueConst *argv) -{ - return JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, - argc, argv, JS_CALL_FLAG_COPY_ARGV); -} - -static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj, - int argc, JSValueConst *argv) -{ - JSValue res = JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, - argc, argv, JS_CALL_FLAG_COPY_ARGV); - JS_FreeValue(ctx, func_obj); - return res; -} - -/* warning: the refcount of the context is not incremented. Return - NULL in case of exception (case of revoked proxy only) */ -static JSContext *JS_GetFunctionRealm(JSContext *ctx, JSValueConst func_obj) -{ - JSObject *p; - JSContext *realm; - - if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) - return ctx; - p = JS_VALUE_GET_OBJ(func_obj); - switch(p->class_id) { - case JS_CLASS_C_FUNCTION: - realm = p->u.cfunc.realm; - break; - case JS_CLASS_BYTECODE_FUNCTION: - case JS_CLASS_GENERATOR_FUNCTION: - case JS_CLASS_ASYNC_FUNCTION: - case JS_CLASS_ASYNC_GENERATOR_FUNCTION: - { - JSFunctionBytecode *b; - b = p->u.func.function_bytecode; - realm = b->realm; - } - break; - case JS_CLASS_PROXY: - { - JSProxyData *s = p->u.opaque; - if (!s) - return ctx; - if (s->is_revoked) { - JS_ThrowTypeErrorRevokedProxy(ctx); - return NULL; - } else { - realm = JS_GetFunctionRealm(ctx, s->target); - } - } - break; - case JS_CLASS_BOUND_FUNCTION: - { - JSBoundFunction *bf = p->u.bound_function; - realm = JS_GetFunctionRealm(ctx, bf->func_obj); - } - break; - default: - realm = ctx; - break; - } - return realm; -} - -static JSValue js_create_from_ctor(JSContext *ctx, JSValueConst ctor, - int class_id) -{ - JSValue proto, obj; - JSContext *realm; - - if (JS_IsUndefined(ctor)) { - proto = js_dup(ctx->class_proto[class_id]); - } else { - proto = JS_GetProperty(ctx, ctor, JS_ATOM_prototype); - if (JS_IsException(proto)) - return proto; - if (!JS_IsObject(proto)) { - JS_FreeValue(ctx, proto); - realm = JS_GetFunctionRealm(ctx, ctor); - if (!realm) - return JS_EXCEPTION; - proto = js_dup(realm->class_proto[class_id]); - } - } - obj = JS_NewObjectProtoClass(ctx, proto, class_id); - JS_FreeValue(ctx, proto); - return obj; -} - -/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ -static JSValue JS_CallConstructorInternal(JSContext *ctx, - JSValueConst func_obj, - JSValueConst new_target, - int argc, JSValueConst *argv, - int flags) -{ - JSObject *p; - JSFunctionBytecode *b; - - if (js_poll_interrupts(ctx)) - return JS_EXCEPTION; - flags |= JS_CALL_FLAG_CONSTRUCTOR; - if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) - goto not_a_function; - p = JS_VALUE_GET_OBJ(func_obj); - if (unlikely(!p->is_constructor)) - return JS_ThrowTypeErrorNotAConstructor(ctx, func_obj); - if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { - JSClassCall *call_func; - call_func = ctx->rt->class_array[p->class_id].call; - if (!call_func) { - not_a_function: - return JS_ThrowTypeErrorNotAFunction(ctx); - } - return call_func(ctx, func_obj, new_target, argc, - argv, flags); - } - - b = p->u.func.function_bytecode; - if (b->is_derived_class_constructor) { - return JS_CallInternal(ctx, func_obj, JS_UNDEFINED, new_target, argc, argv, flags); - } else { - JSValue obj, ret; - /* legacy constructor behavior */ - obj = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT); - if (JS_IsException(obj)) - return JS_EXCEPTION; - ret = JS_CallInternal(ctx, func_obj, obj, new_target, argc, argv, flags); - if (JS_VALUE_GET_TAG(ret) == JS_TAG_OBJECT || - JS_IsException(ret)) { - JS_FreeValue(ctx, obj); - return ret; - } else { - JS_FreeValue(ctx, ret); - return obj; - } - } -} - -JSValue JS_CallConstructor2(JSContext *ctx, JSValueConst func_obj, - JSValueConst new_target, - int argc, JSValueConst *argv) -{ - return JS_CallConstructorInternal(ctx, func_obj, new_target, - argc, argv, - JS_CALL_FLAG_COPY_ARGV); -} - -JSValue JS_CallConstructor(JSContext *ctx, JSValueConst func_obj, - int argc, JSValueConst *argv) -{ - return JS_CallConstructorInternal(ctx, func_obj, func_obj, - argc, argv, - JS_CALL_FLAG_COPY_ARGV); -} - -JSValue JS_Invoke(JSContext *ctx, JSValueConst this_val, JSAtom atom, - int argc, JSValueConst *argv) -{ - JSValue func_obj; - func_obj = JS_GetProperty(ctx, this_val, atom); - if (JS_IsException(func_obj)) - return func_obj; - return JS_CallFree(ctx, func_obj, this_val, argc, argv); -} - -static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, - int argc, JSValueConst *argv) -{ - JSValue res = JS_Invoke(ctx, this_val, atom, argc, argv); - JS_FreeValue(ctx, this_val); - return res; -} - -/* JSAsyncFunctionState (used by generator and async functions) */ -static __exception int async_func_init(JSContext *ctx, JSAsyncFunctionState *s, - JSValueConst func_obj, - JSValueConst this_obj, - int argc, JSValueConst *argv) -{ - JSObject *p; - JSFunctionBytecode *b; - JSStackFrame *sf; - int local_count, i, arg_buf_len, n; - - sf = &s->frame; - init_list_head(&sf->var_ref_list); - p = JS_VALUE_GET_OBJ(func_obj); - b = p->u.func.function_bytecode; - sf->is_strict_mode = b->is_strict_mode; - sf->cur_pc = b->byte_code_buf; - arg_buf_len = max_int(b->arg_count, argc); - local_count = arg_buf_len + b->var_count + b->stack_size; - sf->arg_buf = js_malloc(ctx, sizeof(JSValue) * max_int(local_count, 1)); - if (!sf->arg_buf) - return -1; - sf->cur_func = js_dup(func_obj); - s->this_val = js_dup(this_obj); - s->argc = argc; - sf->arg_count = arg_buf_len; - sf->var_buf = sf->arg_buf + arg_buf_len; - sf->cur_sp = sf->var_buf + b->var_count; - for(i = 0; i < argc; i++) - sf->arg_buf[i] = js_dup(argv[i]); - n = arg_buf_len + b->var_count; - for(i = argc; i < n; i++) - sf->arg_buf[i] = JS_UNDEFINED; - return 0; -} - -static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, - JS_MarkFunc *mark_func) -{ - JSStackFrame *sf; - JSValue *sp; - - sf = &s->frame; - JS_MarkValue(rt, sf->cur_func, mark_func); - JS_MarkValue(rt, s->this_val, mark_func); - if (sf->cur_sp) { - /* if the function is running, cur_sp is not known so we - cannot mark the stack. Marking the variables is not needed - because a running function cannot be part of a removable - cycle */ - for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) - JS_MarkValue(rt, *sp, mark_func); - } -} - -static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) -{ - JSStackFrame *sf; - JSValue *sp; - - sf = &s->frame; - - /* close the closure variables. */ - close_var_refs(rt, sf); - - if (sf->arg_buf) { - /* cannot free the function if it is running */ - assert(sf->cur_sp != NULL); - for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) { - JS_FreeValueRT(rt, *sp); - } - js_free_rt(rt, sf->arg_buf); - } - JS_FreeValueRT(rt, sf->cur_func); - JS_FreeValueRT(rt, s->this_val); -} - -static JSValue async_func_resume(JSContext *ctx, JSAsyncFunctionState *s) -{ - JSValue func_obj; - - if (js_check_stack_overflow(ctx->rt, 0)) - return JS_ThrowStackOverflow(ctx); - - /* the tag does not matter provided it is not an object */ - func_obj = JS_MKPTR(JS_TAG_INT, s); - return JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED, - s->argc, vc(s->frame.arg_buf), - JS_CALL_FLAG_GENERATOR); -} - - -/* Generators */ - -typedef enum JSGeneratorStateEnum { - JS_GENERATOR_STATE_SUSPENDED_START, - JS_GENERATOR_STATE_SUSPENDED_YIELD, - JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR, - JS_GENERATOR_STATE_EXECUTING, - JS_GENERATOR_STATE_COMPLETED, -} JSGeneratorStateEnum; - -typedef struct JSGeneratorData { - JSGeneratorStateEnum state; - JSAsyncFunctionState func_state; -} JSGeneratorData; - -static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s) -{ - if (s->state == JS_GENERATOR_STATE_COMPLETED) - return; - async_func_free(rt, &s->func_state); - s->state = JS_GENERATOR_STATE_COMPLETED; -} - -static void js_generator_finalizer(JSRuntime *rt, JSValueConst obj) -{ - JSGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_GENERATOR); - - if (s) { - free_generator_stack_rt(rt, s); - js_free_rt(rt, s); - } -} - -static void free_generator_stack(JSContext *ctx, JSGeneratorData *s) -{ - free_generator_stack_rt(ctx->rt, s); -} - -static void js_generator_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSGeneratorData *s = p->u.generator_data; - - if (!s || s->state == JS_GENERATOR_STATE_COMPLETED) - return; - async_func_mark(rt, &s->func_state, mark_func); -} - -/* XXX: use enum */ -#define GEN_MAGIC_NEXT 0 -#define GEN_MAGIC_RETURN 1 -#define GEN_MAGIC_THROW 2 - -static JSValue js_generator_next(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, - int *pdone, int magic) -{ - JSGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_GENERATOR); - JSStackFrame *sf; - JSValue ret, func_ret; - - *pdone = true; - if (!s) - return JS_ThrowTypeError(ctx, "not a generator"); - sf = &s->func_state.frame; - switch(s->state) { - default: - case JS_GENERATOR_STATE_SUSPENDED_START: - if (magic == GEN_MAGIC_NEXT) { - goto exec_no_arg; - } else { - free_generator_stack(ctx, s); - goto done; - } - break; - case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR: - case JS_GENERATOR_STATE_SUSPENDED_YIELD: - /* cur_sp[-1] was set to JS_UNDEFINED in the previous call */ - ret = js_dup(argv[0]); - if (magic == GEN_MAGIC_THROW && - s->state == JS_GENERATOR_STATE_SUSPENDED_YIELD) { - JS_Throw(ctx, ret); - s->func_state.throw_flag = true; - } else { - sf->cur_sp[-1] = ret; - sf->cur_sp[0] = js_int32(magic); - sf->cur_sp++; - exec_no_arg: - s->func_state.throw_flag = false; - } - s->state = JS_GENERATOR_STATE_EXECUTING; - func_ret = async_func_resume(ctx, &s->func_state); - s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD; - if (JS_IsException(func_ret)) { - /* finalize the execution in case of exception */ - free_generator_stack(ctx, s); - return func_ret; - } - if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { - /* get the returned yield value at the top of the stack */ - ret = sf->cur_sp[-1]; - sf->cur_sp[-1] = JS_UNDEFINED; - if (JS_VALUE_GET_INT(func_ret) == FUNC_RET_YIELD_STAR) { - s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR; - /* return (value, done) object */ - *pdone = 2; - } else { - *pdone = false; - } - } else { - /* end of iterator */ - ret = sf->cur_sp[-1]; - sf->cur_sp[-1] = JS_UNDEFINED; - JS_FreeValue(ctx, func_ret); - free_generator_stack(ctx, s); - } - break; - case JS_GENERATOR_STATE_COMPLETED: - done: - /* execution is finished */ - switch(magic) { - default: - case GEN_MAGIC_NEXT: - ret = JS_UNDEFINED; - break; - case GEN_MAGIC_RETURN: - ret = js_dup(argv[0]); - break; - case GEN_MAGIC_THROW: - ret = JS_Throw(ctx, js_dup(argv[0])); - break; - } - break; - case JS_GENERATOR_STATE_EXECUTING: - ret = JS_ThrowTypeError(ctx, "cannot invoke a running generator"); - break; - } - return ret; -} - -static JSValue js_call_generator_function(JSContext *ctx, JSValueConst func_obj, - JSValueConst this_obj, - int argc, JSValueConst *argv, - int flags) -{ - JSValue obj, func_ret; - JSGeneratorData *s; - - s = js_mallocz(ctx, sizeof(*s)); - if (!s) - return JS_EXCEPTION; - s->state = JS_GENERATOR_STATE_SUSPENDED_START; - if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { - s->state = JS_GENERATOR_STATE_COMPLETED; - goto fail; - } - - /* execute the function up to 'OP_initial_yield' */ - func_ret = async_func_resume(ctx, &s->func_state); - if (JS_IsException(func_ret)) - goto fail; - JS_FreeValue(ctx, func_ret); - - obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_GENERATOR); - if (JS_IsException(obj)) - goto fail; - JS_SetOpaqueInternal(obj, s); - return obj; - fail: - free_generator_stack_rt(ctx->rt, s); - js_free(ctx, s); - return JS_EXCEPTION; -} - -/* AsyncFunction */ - -static void js_async_function_terminate(JSRuntime *rt, JSAsyncFunctionData *s) -{ - if (s->is_active) { - async_func_free(rt, &s->func_state); - s->is_active = false; - } -} - -static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s) -{ - js_async_function_terminate(rt, s); - JS_FreeValueRT(rt, s->resolving_funcs[0]); - JS_FreeValueRT(rt, s->resolving_funcs[1]); - remove_gc_object(&s->header); - js_free_rt(rt, s); -} - -static void js_async_function_free(JSRuntime *rt, JSAsyncFunctionData *s) -{ - if (--s->header.ref_count == 0) { - js_async_function_free0(rt, s); - } -} - -static void js_async_function_resolve_finalizer(JSRuntime *rt, - JSValueConst val) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSAsyncFunctionData *s = p->u.async_function_data; - if (s) { - js_async_function_free(rt, s); - } -} - -static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSAsyncFunctionData *s = p->u.async_function_data; - if (s) { - mark_func(rt, &s->header); - } -} - -static int js_async_function_resolve_create(JSContext *ctx, - JSAsyncFunctionData *s, - JSValue *resolving_funcs) -{ - int i; - JSObject *p; - - for(i = 0; i < 2; i++) { - resolving_funcs[i] = - JS_NewObjectProtoClass(ctx, ctx->function_proto, - JS_CLASS_ASYNC_FUNCTION_RESOLVE + i); - if (JS_IsException(resolving_funcs[i])) { - if (i == 1) - JS_FreeValue(ctx, resolving_funcs[0]); - return -1; - } - p = JS_VALUE_GET_OBJ(resolving_funcs[i]); - s->header.ref_count++; - p->u.async_function_data = s; - } - return 0; -} - -static bool js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s) -{ - bool is_success = true; - JSValue func_ret, ret2; - - func_ret = async_func_resume(ctx, &s->func_state); - if (JS_IsException(func_ret)) { - fail: - if (unlikely(JS_IsUncatchableError(ctx->rt->current_exception))) { - is_success = false; - } else { - JSValue error = JS_GetException(ctx); - ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED, - 1, vc(&error)); - JS_FreeValue(ctx, error); - resolved: - if (unlikely(JS_IsException(ret2))) { - if (JS_IsUncatchableError(ctx->rt->current_exception)) { - is_success = false; - } else { - abort(); /* BUG */ - } - } - JS_FreeValue(ctx, ret2); - } - js_async_function_terminate(ctx->rt, s); - } else { - JSValue value; - value = s->func_state.frame.cur_sp[-1]; - s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; - if (JS_IsUndefined(func_ret)) { - /* function returned */ - ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED, - 1, vc(&value)); - JS_FreeValue(ctx, value); - goto resolved; - } else { - JSValue promise, resolving_funcs[2], resolving_funcs1[2]; - int i, res; - - /* await */ - JS_FreeValue(ctx, func_ret); /* not used */ - promise = js_promise_resolve(ctx, ctx->promise_ctor, - 1, vc(&value), 0); - JS_FreeValue(ctx, value); - if (JS_IsException(promise)) - goto fail; - if (js_async_function_resolve_create(ctx, s, resolving_funcs)) { - JS_FreeValue(ctx, promise); - goto fail; - } - - /* Note: no need to create 'thrownawayCapability' as in - the spec */ - for(i = 0; i < 2; i++) - resolving_funcs1[i] = JS_UNDEFINED; - res = perform_promise_then(ctx, promise, - vc(resolving_funcs), - vc(resolving_funcs1)); - JS_FreeValue(ctx, promise); - for(i = 0; i < 2; i++) - JS_FreeValue(ctx, resolving_funcs[i]); - if (res) - goto fail; - } - } - return is_success; -} - -static JSValue js_async_function_resolve_call(JSContext *ctx, - JSValueConst func_obj, - JSValueConst this_obj, - int argc, JSValueConst *argv, - int flags) -{ - JSObject *p = JS_VALUE_GET_OBJ(func_obj); - JSAsyncFunctionData *s = p->u.async_function_data; - bool is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE; - JSValueConst arg; - - if (argc > 0) - arg = argv[0]; - else - arg = JS_UNDEFINED; - s->func_state.throw_flag = is_reject; - if (is_reject) { - JS_Throw(ctx, js_dup(arg)); - } else { - /* return value of await */ - s->func_state.frame.cur_sp[-1] = js_dup(arg); - } - if (!js_async_function_resume(ctx, s)) - return JS_EXCEPTION; - return JS_UNDEFINED; -} - -static JSValue js_async_function_call(JSContext *ctx, JSValueConst func_obj, - JSValueConst this_obj, - int argc, JSValueConst *argv, int flags) -{ - JSValue promise; - JSAsyncFunctionData *s; - - s = js_mallocz(ctx, sizeof(*s)); - if (!s) - return JS_EXCEPTION; - s->header.ref_count = 1; - add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION); - s->is_active = false; - s->resolving_funcs[0] = JS_UNDEFINED; - s->resolving_funcs[1] = JS_UNDEFINED; - - promise = JS_NewPromiseCapability(ctx, s->resolving_funcs); - if (JS_IsException(promise)) - goto fail; - - if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { - fail: - JS_FreeValue(ctx, promise); - js_async_function_free(ctx->rt, s); - return JS_EXCEPTION; - } - s->is_active = true; - - if (!js_async_function_resume(ctx, s)) - goto fail; - - js_async_function_free(ctx->rt, s); - - return promise; -} - -/* AsyncGenerator */ - -typedef enum JSAsyncGeneratorStateEnum { - JS_ASYNC_GENERATOR_STATE_SUSPENDED_START, - JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD, - JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR, - JS_ASYNC_GENERATOR_STATE_EXECUTING, - JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN, - JS_ASYNC_GENERATOR_STATE_COMPLETED, -} JSAsyncGeneratorStateEnum; - -typedef struct JSAsyncGeneratorRequest { - struct list_head link; - /* completion */ - int completion_type; /* GEN_MAGIC_x */ - JSValue result; - /* promise capability */ - JSValue promise; - JSValue resolving_funcs[2]; -} JSAsyncGeneratorRequest; - -typedef struct JSAsyncGeneratorData { - JSObject *generator; /* back pointer to the object (const) */ - JSAsyncGeneratorStateEnum state; - JSAsyncFunctionState func_state; - struct list_head queue; /* list of JSAsyncGeneratorRequest.link */ -} JSAsyncGeneratorData; - -static void js_async_generator_free(JSRuntime *rt, - JSAsyncGeneratorData *s) -{ - struct list_head *el, *el1; - JSAsyncGeneratorRequest *req; - - list_for_each_safe(el, el1, &s->queue) { - req = list_entry(el, JSAsyncGeneratorRequest, link); - JS_FreeValueRT(rt, req->result); - JS_FreeValueRT(rt, req->promise); - JS_FreeValueRT(rt, req->resolving_funcs[0]); - JS_FreeValueRT(rt, req->resolving_funcs[1]); - js_free_rt(rt, req); - } - if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && - s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { - async_func_free(rt, &s->func_state); - } - js_free_rt(rt, s); -} - -static void js_async_generator_finalizer(JSRuntime *rt, JSValueConst obj) -{ - JSAsyncGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_ASYNC_GENERATOR); - - if (s) { - js_async_generator_free(rt, s); - } -} - -static void js_async_generator_mark(JSRuntime *rt, JSValueConst val, - JS_MarkFunc *mark_func) -{ - JSAsyncGeneratorData *s = JS_GetOpaque(val, JS_CLASS_ASYNC_GENERATOR); - struct list_head *el; - JSAsyncGeneratorRequest *req; - if (s) { - list_for_each(el, &s->queue) { - req = list_entry(el, JSAsyncGeneratorRequest, link); - JS_MarkValue(rt, req->result, mark_func); - JS_MarkValue(rt, req->promise, mark_func); - JS_MarkValue(rt, req->resolving_funcs[0], mark_func); - JS_MarkValue(rt, req->resolving_funcs[1], mark_func); - } - if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && - s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { - async_func_mark(rt, &s->func_state, mark_func); - } - } -} - -static JSValue js_async_generator_resolve_function(JSContext *ctx, - JSValueConst this_obj, - int argc, JSValueConst *argv, - int magic, JSValueConst *func_data); - -static int js_async_generator_resolve_function_create(JSContext *ctx, - JSValue generator, - JSValue *resolving_funcs, - bool is_resume_next) -{ - int i; - JSValue func; - - for(i = 0; i < 2; i++) { - func = JS_NewCFunctionData(ctx, js_async_generator_resolve_function, 1, - i + is_resume_next * 2, 1, vc(&generator)); - if (JS_IsException(func)) { - if (i == 1) - JS_FreeValue(ctx, resolving_funcs[0]); - return -1; - } - resolving_funcs[i] = func; - } - return 0; -} - -static int js_async_generator_await(JSContext *ctx, - JSAsyncGeneratorData *s, - JSValue value) -{ - JSValue promise, resolving_funcs[2], resolving_funcs1[2]; - int i, res; - - promise = js_promise_resolve(ctx, ctx->promise_ctor, - 1, vc(&value), 0); - if (JS_IsException(promise)) - goto fail; - - if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator), - resolving_funcs, false)) { - JS_FreeValue(ctx, promise); - goto fail; - } - - /* Note: no need to create 'thrownawayCapability' as in - the spec */ - for(i = 0; i < 2; i++) - resolving_funcs1[i] = JS_UNDEFINED; - res = perform_promise_then(ctx, promise, - vc(resolving_funcs), - vc(resolving_funcs1)); - JS_FreeValue(ctx, promise); - for(i = 0; i < 2; i++) - JS_FreeValue(ctx, resolving_funcs[i]); - if (res) - goto fail; - return 0; - fail: - return -1; -} - -static void js_async_generator_resolve_or_reject(JSContext *ctx, - JSAsyncGeneratorData *s, - JSValueConst result, - int is_reject) -{ - JSAsyncGeneratorRequest *next; - JSValue ret; - - next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); - list_del(&next->link); - ret = JS_Call(ctx, next->resolving_funcs[is_reject], JS_UNDEFINED, 1, - &result); - JS_FreeValue(ctx, ret); - JS_FreeValue(ctx, next->result); - JS_FreeValue(ctx, next->promise); - JS_FreeValue(ctx, next->resolving_funcs[0]); - JS_FreeValue(ctx, next->resolving_funcs[1]); - js_free(ctx, next); -} - -static void js_async_generator_resolve(JSContext *ctx, - JSAsyncGeneratorData *s, - JSValueConst value, - bool done) -{ - JSValue result; - result = js_create_iterator_result(ctx, js_dup(value), done); - /* XXX: better exception handling ? */ - js_async_generator_resolve_or_reject(ctx, s, result, 0); - JS_FreeValue(ctx, result); - } - -static void js_async_generator_reject(JSContext *ctx, - JSAsyncGeneratorData *s, - JSValueConst exception) -{ - js_async_generator_resolve_or_reject(ctx, s, exception, 1); -} - -static void js_async_generator_complete(JSContext *ctx, - JSAsyncGeneratorData *s) -{ - if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) { - s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; - async_func_free(ctx->rt, &s->func_state); - } -} - -static int js_async_generator_completed_return(JSContext *ctx, - JSAsyncGeneratorData *s, - JSValue value) -{ - JSValue promise, resolving_funcs[2], resolving_funcs1[2]; - int res; - - // Can fail looking up JS_ATOM_constructor when is_reject==0. - promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, vc(&value), - /*is_reject*/0); - // A poisoned .constructor property is observable and the resulting - // exception should be delivered to the catch handler. - if (JS_IsException(promise)) { - JSValue err = JS_GetException(ctx); - promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, vc(&err), - /*is_reject*/1); - JS_FreeValue(ctx, err); - if (JS_IsException(promise)) - return -1; - } - if (js_async_generator_resolve_function_create(ctx, - JS_MKPTR(JS_TAG_OBJECT, s->generator), - resolving_funcs1, - true)) { - JS_FreeValue(ctx, promise); - return -1; - } - resolving_funcs[0] = JS_UNDEFINED; - resolving_funcs[1] = JS_UNDEFINED; - res = perform_promise_then(ctx, promise, - vc(resolving_funcs1), - vc(resolving_funcs)); - JS_FreeValue(ctx, resolving_funcs1[0]); - JS_FreeValue(ctx, resolving_funcs1[1]); - JS_FreeValue(ctx, promise); - return res; -} - -static void js_async_generator_resume_next(JSContext *ctx, - JSAsyncGeneratorData *s) -{ - JSAsyncGeneratorRequest *next; - JSValue func_ret, value; - - for(;;) { - if (list_empty(&s->queue)) - break; - next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); - switch(s->state) { - case JS_ASYNC_GENERATOR_STATE_EXECUTING: - /* only happens when restarting execution after await() */ - goto resume_exec; - case JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN: - goto done; - case JS_ASYNC_GENERATOR_STATE_SUSPENDED_START: - if (next->completion_type == GEN_MAGIC_NEXT) { - goto exec_no_arg; - } else { - js_async_generator_complete(ctx, s); - } - break; - case JS_ASYNC_GENERATOR_STATE_COMPLETED: - if (next->completion_type == GEN_MAGIC_NEXT) { - js_async_generator_resolve(ctx, s, JS_UNDEFINED, true); - } else if (next->completion_type == GEN_MAGIC_RETURN) { - s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN; - js_async_generator_completed_return(ctx, s, next->result); - } else { - js_async_generator_reject(ctx, s, next->result); - } - goto done; - case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD: - case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR: - value = js_dup(next->result); - if (next->completion_type == GEN_MAGIC_THROW && - s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) { - JS_Throw(ctx, value); - s->func_state.throw_flag = true; - } else { - /* 'yield' returns a value. 'yield *' also returns a value - in case the 'throw' method is called */ - s->func_state.frame.cur_sp[-1] = value; - s->func_state.frame.cur_sp[0] = - js_int32(next->completion_type); - s->func_state.frame.cur_sp++; - exec_no_arg: - s->func_state.throw_flag = false; - } - s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING; - resume_exec: - func_ret = async_func_resume(ctx, &s->func_state); - if (JS_IsException(func_ret)) { - value = JS_GetException(ctx); - js_async_generator_complete(ctx, s); - js_async_generator_reject(ctx, s, value); - JS_FreeValue(ctx, value); - } else if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { - int func_ret_code, ret; - value = s->func_state.frame.cur_sp[-1]; - s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; - func_ret_code = JS_VALUE_GET_INT(func_ret); - switch(func_ret_code) { - case FUNC_RET_YIELD: - case FUNC_RET_YIELD_STAR: - if (func_ret_code == FUNC_RET_YIELD_STAR) - s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR; - else - s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD; - js_async_generator_resolve(ctx, s, value, false); - JS_FreeValue(ctx, value); - break; - case FUNC_RET_AWAIT: - ret = js_async_generator_await(ctx, s, value); - JS_FreeValue(ctx, value); - if (ret < 0) { - /* exception: throw it */ - s->func_state.throw_flag = true; - goto resume_exec; - } - goto done; - default: - abort(); - } - } else { - assert(JS_IsUndefined(func_ret)); - /* end of function */ - value = s->func_state.frame.cur_sp[-1]; - s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; - js_async_generator_complete(ctx, s); - js_async_generator_resolve(ctx, s, value, true); - JS_FreeValue(ctx, value); - } - break; - default: - abort(); - } - } - done: ; -} - -static JSValue js_async_generator_resolve_function(JSContext *ctx, - JSValueConst this_obj, - int argc, JSValueConst *argv, - int magic, JSValueConst *func_data) -{ - bool is_reject = magic & 1; - JSAsyncGeneratorData *s = JS_GetOpaque(func_data[0], JS_CLASS_ASYNC_GENERATOR); - JSValueConst arg = argv[0]; - - /* XXX: what if s == NULL */ - - if (magic >= 2) { - /* resume next case in AWAITING_RETURN state */ - assert(s->state == JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN || - s->state == JS_ASYNC_GENERATOR_STATE_COMPLETED); - s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; - if (is_reject) { - js_async_generator_reject(ctx, s, arg); - } else { - js_async_generator_resolve(ctx, s, arg, true); - } - } else { - /* restart function execution after await() */ - assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING); - s->func_state.throw_flag = is_reject; - if (is_reject) { - JS_Throw(ctx, js_dup(arg)); - } else { - /* return value of await */ - s->func_state.frame.cur_sp[-1] = js_dup(arg); - } - js_async_generator_resume_next(ctx, s); - } - return JS_UNDEFINED; -} - -/* magic = GEN_MAGIC_x */ -static JSValue js_async_generator_next(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, - int magic) -{ - JSAsyncGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_GENERATOR); - JSValue promise, resolving_funcs[2]; - JSAsyncGeneratorRequest *req; - - promise = JS_NewPromiseCapability(ctx, resolving_funcs); - if (JS_IsException(promise)) - return JS_EXCEPTION; - if (!s) { - JSValue err, res2; - JS_ThrowTypeError(ctx, "not an AsyncGenerator object"); - err = JS_GetException(ctx); - res2 = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, - 1, vc(&err)); - JS_FreeValue(ctx, err); - JS_FreeValue(ctx, res2); - JS_FreeValue(ctx, resolving_funcs[0]); - JS_FreeValue(ctx, resolving_funcs[1]); - return promise; - } - req = js_mallocz(ctx, sizeof(*req)); - if (!req) - goto fail; - req->completion_type = magic; - req->result = js_dup(argv[0]); - req->promise = js_dup(promise); - req->resolving_funcs[0] = resolving_funcs[0]; - req->resolving_funcs[1] = resolving_funcs[1]; - list_add_tail(&req->link, &s->queue); - if (s->state != JS_ASYNC_GENERATOR_STATE_EXECUTING) { - js_async_generator_resume_next(ctx, s); - } - return promise; - fail: - JS_FreeValue(ctx, resolving_funcs[0]); - JS_FreeValue(ctx, resolving_funcs[1]); - JS_FreeValue(ctx, promise); - return JS_EXCEPTION; -} - -static JSValue js_async_generator_function_call(JSContext *ctx, - JSValueConst func_obj, - JSValueConst this_obj, - int argc, JSValueConst *argv, - int flags) -{ - JSValue obj, func_ret; - JSAsyncGeneratorData *s; - - s = js_mallocz(ctx, sizeof(*s)); - if (!s) - return JS_EXCEPTION; - s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START; - init_list_head(&s->queue); - if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { - s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; - goto fail; - } - - /* execute the function up to 'OP_initial_yield' (no yield nor - await are possible) */ - func_ret = async_func_resume(ctx, &s->func_state); - if (JS_IsException(func_ret)) - goto fail; - JS_FreeValue(ctx, func_ret); - - obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_ASYNC_GENERATOR); - if (JS_IsException(obj)) - goto fail; - s->generator = JS_VALUE_GET_OBJ(obj); - JS_SetOpaqueInternal(obj, s); - return obj; - fail: - js_async_generator_free(ctx->rt, s); - return JS_EXCEPTION; -} - -/* JS parser */ - -enum { - TOK_NUMBER = -128, - TOK_STRING, - TOK_TEMPLATE, - TOK_IDENT, - TOK_REGEXP, - /* warning: order matters (see js_parse_assign_expr) */ - TOK_MUL_ASSIGN, - TOK_DIV_ASSIGN, - TOK_MOD_ASSIGN, - TOK_PLUS_ASSIGN, - TOK_MINUS_ASSIGN, - TOK_SHL_ASSIGN, - TOK_SAR_ASSIGN, - TOK_SHR_ASSIGN, - TOK_AND_ASSIGN, - TOK_XOR_ASSIGN, - TOK_OR_ASSIGN, - TOK_POW_ASSIGN, - TOK_LAND_ASSIGN, - TOK_LOR_ASSIGN, - TOK_DOUBLE_QUESTION_MARK_ASSIGN, - TOK_DEC, - TOK_INC, - TOK_SHL, - TOK_SAR, - TOK_SHR, - TOK_LT, - TOK_LTE, - TOK_GT, - TOK_GTE, - TOK_EQ, - TOK_STRICT_EQ, - TOK_NEQ, - TOK_STRICT_NEQ, - TOK_LAND, - TOK_LOR, - TOK_POW, - TOK_ARROW, - TOK_ELLIPSIS, - TOK_DOUBLE_QUESTION_MARK, - TOK_QUESTION_MARK_DOT, - TOK_ERROR, - TOK_PRIVATE_NAME, - TOK_EOF, - /* keywords: WARNING: same order as atoms */ - TOK_NULL, /* must be first */ - TOK_FALSE, - TOK_TRUE, - TOK_IF, - TOK_ELSE, - TOK_RETURN, - TOK_VAR, - TOK_THIS, - TOK_DELETE, - TOK_VOID, - TOK_TYPEOF, - TOK_NEW, - TOK_IN, - TOK_INSTANCEOF, - TOK_DO, - TOK_WHILE, - TOK_FOR, - TOK_BREAK, - TOK_CONTINUE, - TOK_SWITCH, - TOK_CASE, - TOK_DEFAULT, - TOK_THROW, - TOK_TRY, - TOK_CATCH, - TOK_FINALLY, - TOK_FUNCTION, - TOK_DEBUGGER, - TOK_WITH, - /* FutureReservedWord */ - TOK_CLASS, - TOK_CONST, - TOK_ENUM, - TOK_EXPORT, - TOK_EXTENDS, - TOK_IMPORT, - TOK_SUPER, - /* FutureReservedWords when parsing strict mode code */ - TOK_IMPLEMENTS, - TOK_INTERFACE, - TOK_LET, - TOK_PACKAGE, - TOK_PRIVATE, - TOK_PROTECTED, - TOK_PUBLIC, - TOK_STATIC, - TOK_YIELD, - TOK_AWAIT, /* must be last */ - TOK_OF, /* only used for js_parse_skip_parens_token() */ -}; - -#define TOK_FIRST_KEYWORD TOK_NULL -#define TOK_LAST_KEYWORD TOK_AWAIT - -/* unicode code points */ -#define CP_NBSP 0x00a0 -#define CP_BOM 0xfeff - -#define CP_LS 0x2028 -#define CP_PS 0x2029 - -typedef struct BlockEnv { - struct BlockEnv *prev; - JSAtom label_name; /* JS_ATOM_NULL if none */ - int label_break; /* -1 if none */ - int label_cont; /* -1 if none */ - int drop_count; /* number of stack elements to drop */ - int label_finally; /* -1 if none */ - int scope_level; - uint8_t has_iterator : 1; - uint8_t is_regular_stmt : 1; // i.e. not a loop statement -} BlockEnv; - -typedef struct JSGlobalVar { - int cpool_idx; /* if >= 0, index in the constant pool for hoisted - function defintion*/ - uint8_t force_init : 1; /* force initialization to undefined */ - uint8_t is_lexical : 1; /* global let/const definition */ - uint8_t is_const : 1; /* const definition */ - int scope_level; /* scope of definition */ - JSAtom var_name; /* variable name */ -} JSGlobalVar; - -typedef struct RelocEntry { - struct RelocEntry *next; - uint32_t addr; /* address to patch */ - int size; /* address size: 1, 2 or 4 bytes */ -} RelocEntry; - -typedef struct JumpSlot { - int op; - int size; - int pos; - int label; -} JumpSlot; - -typedef struct LabelSlot { - int ref_count; - int pos; /* phase 1 address, -1 means not resolved yet */ - int pos2; /* phase 2 address, -1 means not resolved yet */ - int addr; /* phase 3 address, -1 means not resolved yet */ - RelocEntry *first_reloc; -} LabelSlot; - -typedef struct SourceLocSlot { - uint32_t pc; - int line_num; - int col_num; -} SourceLocSlot; - -typedef enum JSParseFunctionEnum { - JS_PARSE_FUNC_STATEMENT, - JS_PARSE_FUNC_VAR, - JS_PARSE_FUNC_EXPR, - JS_PARSE_FUNC_ARROW, - JS_PARSE_FUNC_GETTER, - JS_PARSE_FUNC_SETTER, - JS_PARSE_FUNC_METHOD, - JS_PARSE_FUNC_CLASS_STATIC_INIT, - JS_PARSE_FUNC_CLASS_CONSTRUCTOR, - JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR, -} JSParseFunctionEnum; - -typedef enum JSParseExportEnum { - JS_PARSE_EXPORT_NONE, - JS_PARSE_EXPORT_NAMED, - JS_PARSE_EXPORT_DEFAULT, -} JSParseExportEnum; - -typedef struct JSFunctionDef { - JSContext *ctx; - struct JSFunctionDef *parent; - int parent_cpool_idx; /* index in the constant pool of the parent - or -1 if none */ - int parent_scope_level; /* scope level in parent at point of definition */ - struct list_head child_list; /* list of JSFunctionDef.link */ - struct list_head link; - - int eval_type; /* only valid if is_eval = true */ - - /* Pack all boolean flags together as 1-bit fields to reduce struct size - while avoiding padding and compiler deoptimization. */ - bool is_eval : 1; /* true if eval code */ - bool is_global_var : 1; /* true if variables are not defined locally: - eval global, eval module or non strict eval */ - bool is_func_expr : 1; /* true if function expression */ - bool has_home_object : 1; /* true if the home object is available */ - bool has_prototype : 1; /* true if a prototype field is necessary */ - bool has_simple_parameter_list : 1; - bool has_parameter_expressions : 1; /* if true, an argument scope is created */ - bool has_use_strict : 1; /* to reject directive in special cases */ - bool has_eval_call : 1; /* true if the function contains a call to eval() */ - bool has_arguments_binding : 1; /* true if the 'arguments' binding is - available in the function */ - bool has_this_binding : 1; /* true if the 'this' and new.target binding are - available in the function */ - bool new_target_allowed : 1; /* true if the 'new.target' does not - throw a syntax error */ - bool super_call_allowed : 1; /* true if super() is allowed */ - bool super_allowed : 1; /* true if super. or super[] is allowed */ - bool arguments_allowed : 1; /* true if the 'arguments' identifier is allowed */ - bool is_derived_class_constructor : 1; - bool in_function_body : 1; - bool backtrace_barrier : 1; - bool need_home_object : 1; - bool use_short_opcodes : 1; /* true if short opcodes are used in byte_code */ - bool has_await : 1; /* true if await is used (used in module eval) */ - - JSFunctionKindEnum func_kind : 8; - JSParseFunctionEnum func_type : 7; - uint8_t is_strict_mode : 1; - JSAtom func_name; /* JS_ATOM_NULL if no name */ - - JSVarDef *vars; - uint32_t *vars_htab; // indexes into vars[] - int var_size; /* allocated size for vars[] */ - int var_count; - JSVarDef *args; - int arg_size; /* allocated size for args[] */ - int arg_count; /* number of arguments */ - int defined_arg_count; - int var_object_idx; /* -1 if none */ - int arg_var_object_idx; /* -1 if none (var object for the argument scope) */ - int arguments_var_idx; /* -1 if none */ - int arguments_arg_idx; /* argument variable definition in argument scope, - -1 if none */ - int func_var_idx; /* variable containing the current function (-1 - if none, only used if is_func_expr is true) */ - int eval_ret_idx; /* variable containing the return value of the eval, -1 if none */ - int this_var_idx; /* variable containg the 'this' value, -1 if none */ - int new_target_var_idx; /* variable containg the 'new.target' value, -1 if none */ - int this_active_func_var_idx; /* variable containg the 'this.active_func' value, -1 if none */ - int home_object_var_idx; - - int scope_level; /* index into fd->scopes if the current lexical scope */ - int scope_first; /* index into vd->vars of first lexically scoped variable */ - int scope_size; /* allocated size of fd->scopes array */ - int scope_count; /* number of entries used in the fd->scopes array */ - JSVarScope *scopes; - JSVarScope def_scope_array[4]; - int body_scope; /* scope of the body of the function or eval */ - - int global_var_count; - int global_var_size; - JSGlobalVar *global_vars; - - DynBuf byte_code; - int last_opcode_pos; /* -1 if no last opcode */ - - LabelSlot *label_slots; - int label_size; /* allocated size for label_slots[] */ - int label_count; - BlockEnv *top_break; /* break/continue label stack */ - - /* constant pool (strings, functions, numbers) */ - JSValue *cpool; - int cpool_count; - int cpool_size; - - /* list of variables in the closure */ - int closure_var_count; - int closure_var_size; - JSClosureVar *closure_var; - - JumpSlot *jump_slots; - int jump_size; - int jump_count; - - SourceLocSlot *source_loc_slots; - int source_loc_size; - int source_loc_count; - int line_number_last; - int line_number_last_pc; - int col_number_last; - - /* pc2line table */ - JSAtom filename; - int line_num; - int col_num; - DynBuf pc2line; - - char *source; /* raw source, utf-8 encoded */ - int source_len; - - JSModuleDef *module; /* != NULL when parsing a module */ -} JSFunctionDef; - -typedef struct JSToken { - int val; - int line_num; /* line number of token start */ - int col_num; /* column number of token start */ - const uint8_t *ptr; - union { - struct { - JSValue str; - int sep; - } str; - struct { - JSValue val; - } num; - struct { - JSAtom atom; - bool has_escape; - bool is_reserved; - } ident; - struct { - JSValue body; - JSValue flags; - } regexp; - } u; -} JSToken; - -typedef struct JSParseState { - JSContext *ctx; - int last_line_num; /* line number of last token */ - int last_col_num; /* column number of last token */ - int line_num; /* line number of current offset */ - int col_num; /* column number of current offset */ - const char *filename; - JSToken token; - bool got_lf; /* true if got line feed before the current token */ - const uint8_t *last_ptr; - const uint8_t *buf_start; - const uint8_t *buf_ptr; - const uint8_t *buf_end; - const uint8_t *eol; // most recently seen end-of-line character - const uint8_t *mark; // first token character, invariant: eol < mark - - /* current function code */ - JSFunctionDef *cur_func; - bool is_module; /* parsing a module */ - bool allow_html_comments; -} JSParseState; - -typedef struct JSOpCode { -#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_* - const char *name; -#endif - uint8_t size; /* in bytes */ - /* the opcodes remove n_pop items from the top of the stack, then - pushes n_push items */ - uint8_t n_pop; - uint8_t n_push; - uint8_t fmt; -} JSOpCode; - -static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = { -#define FMT(f) -#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_* -#define DEF(id, size, n_pop, n_push, f) { #id, size, n_pop, n_push, OP_FMT_ ## f }, -#else -#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_ ## f }, -#endif -#include "quickjs-opcode.h" -#undef DEF -#undef FMT -}; - -/* After the final compilation pass, short opcodes are used. Their - opcodes overlap with the temporary opcodes which cannot appear in - the final bytecode. Their description is after the temporary - opcodes in opcode_info[]. */ -#define short_opcode_info(op) \ - opcode_info[(op) >= OP_TEMP_START ? \ - (op) + (OP_TEMP_END - OP_TEMP_START) : (op)] - -static void free_token(JSParseState *s, JSToken *token) -{ - switch(token->val) { - case TOK_NUMBER: - JS_FreeValue(s->ctx, token->u.num.val); - break; - case TOK_STRING: - case TOK_TEMPLATE: - JS_FreeValue(s->ctx, token->u.str.str); - break; - case TOK_REGEXP: - JS_FreeValue(s->ctx, token->u.regexp.body); - JS_FreeValue(s->ctx, token->u.regexp.flags); - break; - case TOK_IDENT: - case TOK_PRIVATE_NAME: - JS_FreeAtom(s->ctx, token->u.ident.atom); - break; - default: - if (token->val >= TOK_FIRST_KEYWORD && - token->val <= TOK_LAST_KEYWORD) { - JS_FreeAtom(s->ctx, token->u.ident.atom); - } - break; - } -} - -static void __attribute((unused)) dump_token(JSParseState *s, - const JSToken *token) -{ - printf("%d:%d ", token->line_num, token->col_num); - switch(token->val) { - case TOK_NUMBER: - { - double d; - JS_ToFloat64(s->ctx, &d, token->u.num.val); /* no exception possible */ - printf("number: %.14g\n", d); - } - break; - case TOK_IDENT: - dump_atom: - { - char buf[ATOM_GET_STR_BUF_SIZE]; - printf("ident: '%s'\n", - JS_AtomGetStr(s->ctx, buf, sizeof(buf), token->u.ident.atom)); - } - break; - case TOK_STRING: - { - const char *str; - /* XXX: quote the string */ - str = JS_ToCString(s->ctx, token->u.str.str); - printf("string: '%s'\n", str); - JS_FreeCString(s->ctx, str); - } - break; - case TOK_TEMPLATE: - { - const char *str; - str = JS_ToCString(s->ctx, token->u.str.str); - printf("template: `%s`\n", str); - JS_FreeCString(s->ctx, str); - } - break; - case TOK_REGEXP: - { - const char *str, *str2; - str = JS_ToCString(s->ctx, token->u.regexp.body); - str2 = JS_ToCString(s->ctx, token->u.regexp.flags); - printf("regexp: '%s' '%s'\n", str, str2); - JS_FreeCString(s->ctx, str); - JS_FreeCString(s->ctx, str2); - } - break; - case TOK_EOF: - printf("eof\n"); - break; - default: - if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) { - goto dump_atom; - } else if (s->token.val >= 256) { - printf("token: %d\n", token->val); - } else { - printf("token: '%c'\n", token->val); - } - break; - } -} - -int JS_PRINTF_FORMAT_ATTR(2, 3) js_parse_error(JSParseState *s, JS_PRINTF_FORMAT const char *fmt, ...) -{ - JSContext *ctx = s->ctx; - va_list ap; - int backtrace_flags; - - va_start(ap, fmt); - JS_ThrowError2(ctx, JS_SYNTAX_ERROR, false, fmt, ap); - va_end(ap); - backtrace_flags = 0; - if (s->cur_func && s->cur_func->backtrace_barrier) - backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; - build_backtrace(ctx, ctx->rt->current_exception, JS_UNDEFINED, s->filename, - s->line_num, s->col_num, backtrace_flags); - return -1; -} - -#ifndef QJS_DISABLE_PARSER - -static __exception int next_token(JSParseState *s); - -static int js_parse_expect(JSParseState *s, int tok) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - - if (s->token.val == tok) - return next_token(s); - - switch(s->token.val) { - case TOK_EOF: - return js_parse_error(s, "Unexpected end of input"); - case TOK_NUMBER: - return js_parse_error(s, "Unexpected number"); - case TOK_STRING: - return js_parse_error(s, "Unexpected string"); - case TOK_TEMPLATE: - return js_parse_error(s, "Unexpected string template"); - case TOK_REGEXP: - return js_parse_error(s, "Unexpected regexp"); - case TOK_IDENT: - return js_parse_error(s, "Unexpected identifier '%s'", - JS_AtomGetStr(s->ctx, buf, sizeof(buf), - s->token.u.ident.atom)); - case TOK_ERROR: - return js_parse_error(s, "Invalid or unexpected token"); - default: - return js_parse_error(s, "Unexpected token '%.*s'", - (int)(s->buf_ptr - s->token.ptr), - (const char *)s->token.ptr); - } -} - -static int js_parse_expect_semi(JSParseState *s) -{ - if (s->token.val != ';') { - /* automatic insertion of ';' */ - if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) { - return 0; - } - return js_parse_error(s, "expecting '%c'", ';'); - } - return next_token(s); -} - -static int js_parse_error_reserved_identifier(JSParseState *s) -{ - char buf1[ATOM_GET_STR_BUF_SIZE]; - return js_parse_error(s, "'%s' is a reserved identifier", - JS_AtomGetStr(s->ctx, buf1, sizeof(buf1), - s->token.u.ident.atom)); -} - -static __exception int js_parse_template_part(JSParseState *s, - const uint8_t *p) -{ - const uint8_t *p_next; - uint32_t c; - StringBuffer b_s, *b = &b_s; - JSValue str; - - /* p points to the first byte of the template part */ - if (string_buffer_init(s->ctx, b, 32)) - goto fail; - for(;;) { - if (p >= s->buf_end) - goto unexpected_eof; - c = *p++; - if (c == '`') { - /* template end part */ - break; - } - if (c == '$' && *p == '{') { - /* template start or middle part */ - p++; - break; - } - if (c == '\\') { - if (string_buffer_putc8(b, c)) - goto fail; - if (p >= s->buf_end) - goto unexpected_eof; - c = *p++; - } - /* newline sequences are normalized as single '\n' bytes */ - if (c == '\r') { - if (*p == '\n') - p++; - c = '\n'; - } - if (c == '\n') { - s->line_num++; - s->eol = &p[-1]; - s->mark = p; - } else if (c >= 0x80) { - c = utf8_decode(p - 1, &p_next); - if (p_next == p) { - js_parse_error(s, "invalid UTF-8 sequence"); - goto fail; - } - p = p_next; - } - if (string_buffer_putc(b, c)) - goto fail; - } - str = string_buffer_end(b); - if (JS_IsException(str)) - return -1; - s->token.val = TOK_TEMPLATE; - s->token.u.str.sep = c; - s->token.u.str.str = str; - s->buf_ptr = p; - return 0; - - unexpected_eof: - js_parse_error(s, "unexpected end of string"); - fail: - string_buffer_free(b); - return -1; -} - -static __exception int js_parse_string(JSParseState *s, int sep, - bool do_throw, const uint8_t *p, - JSToken *token, const uint8_t **pp) -{ - const uint8_t *p_next; - int ret; - uint32_t c; - StringBuffer b_s, *b = &b_s; - JSValue str; - - /* string */ - if (string_buffer_init(s->ctx, b, 32)) - goto fail; - for(;;) { - if (p >= s->buf_end) - goto invalid_char; - c = *p; - if (c < 0x20) { - if (sep == '`') { - if (c == '\r') { - if (p[1] == '\n') - p++; - c = '\n'; - } - /* do not update s->line_num */ - } else if (c == '\n' || c == '\r') - goto invalid_char; - } - p++; - if (c == sep) - break; - if (c == '$' && *p == '{' && sep == '`') { - /* template start or middle part */ - p++; - break; - } - if (c == '\\') { - c = *p; - switch(c) { - case '\0': - if (p >= s->buf_end) { - if (sep != '`') - goto invalid_char; - if (do_throw) - js_parse_error(s, "Unexpected end of input"); - goto fail; - } - p++; - break; - case '\'': - case '\"': - case '\\': - p++; - break; - case '\r': /* accept DOS and MAC newline sequences */ - if (p[1] == '\n') { - p++; - } - /* fall thru */ - case '\n': - /* ignore escaped newline sequence */ - p++; - if (sep != '`') { - s->line_num++; - s->eol = &p[-1]; - s->mark = p; - } - continue; - default: - if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) { - /* accept isolated \0 */ - p++; - c = '\0'; - } else - if ((c >= '0' && c <= '9') - && (s->cur_func->is_strict_mode || sep == '`')) { - if (do_throw) { - js_parse_error(s, "%s are not allowed in %s", - (c >= '8') ? "\\8 and \\9" : "Octal escape sequences", - (sep == '`') ? "template strings" : "strict mode"); - } - goto fail; - } else if (c >= 0x80) { - c = utf8_decode(p, &p_next); - if (p_next == p + 1) { - goto invalid_utf8; - } - p = p_next; - /* LS or PS are skipped */ - if (c == CP_LS || c == CP_PS) - continue; - } else { - ret = lre_parse_escape(&p, true); - if (ret == -1) { - if (do_throw) { - js_parse_error(s, "Invalid %s escape sequence", - c == 'u' ? "Unicode" : "hexadecimal"); - } - goto fail; - } else if (ret < 0) { - /* ignore the '\' (could output a warning) */ - p++; - } else { - c = ret; - } - } - break; - } - } else if (c >= 0x80) { - c = utf8_decode(p - 1, &p_next); - if (p_next == p) - goto invalid_utf8; - p = p_next; - } - if (string_buffer_putc(b, c)) - goto fail; - } - str = string_buffer_end(b); - if (JS_IsException(str)) - return -1; - token->val = TOK_STRING; - token->u.str.sep = c; - token->u.str.str = str; - *pp = p; - return 0; - - invalid_utf8: - if (do_throw) - js_parse_error(s, "invalid UTF-8 sequence"); - goto fail; - invalid_char: - if (do_throw) - js_parse_error(s, "unexpected end of string"); - fail: - string_buffer_free(b); - return -1; -} - -static inline bool token_is_pseudo_keyword(JSParseState *s, JSAtom atom) { - return s->token.val == TOK_IDENT && s->token.u.ident.atom == atom && - !s->token.u.ident.has_escape; -} - -static __exception int js_parse_regexp(JSParseState *s) -{ - const uint8_t *p, *p_next; - bool in_class; - StringBuffer b_s, *b = &b_s; - StringBuffer b2_s, *b2 = &b2_s; - uint32_t c; - JSValue body_str, flags_str; - - p = s->buf_ptr; - p++; - in_class = false; - if (string_buffer_init(s->ctx, b, 32)) - return -1; - if (string_buffer_init(s->ctx, b2, 1)) - goto fail; - for(;;) { - if (p >= s->buf_end) { - eof_error: - js_parse_error(s, "unexpected end of regexp"); - goto fail; - } - c = *p++; - if (c == '\n' || c == '\r') { - goto eol_error; - } else if (c == '/') { - if (!in_class) - break; - } else if (c == '[') { - in_class = true; - } else if (c == ']') { - /* XXX: incorrect as the first character in a class */ - in_class = false; - } else if (c == '\\') { - if (string_buffer_putc8(b, c)) - goto fail; - c = *p++; - if (c == '\n' || c == '\r') - goto eol_error; - else if (c == '\0' && p >= s->buf_end) - goto eof_error; - else if (c >= 0x80) { - c = utf8_decode(p - 1, &p_next); - if (p_next == p) { - goto invalid_utf8; - } - p = p_next; - if (c == CP_LS || c == CP_PS) - goto eol_error; - } - } else if (c >= 0x80) { - c = utf8_decode(p - 1, &p_next); - if (p_next == p) { - invalid_utf8: - js_parse_error(s, "invalid UTF-8 sequence"); - goto fail; - } - p = p_next; - /* LS or PS are considered as line terminator */ - if (c == CP_LS || c == CP_PS) { - eol_error: - js_parse_error(s, "unexpected line terminator in regexp"); - goto fail; - } - } - if (string_buffer_putc(b, c)) - goto fail; - } - - /* flags */ - for(;;) { - c = utf8_decode(p, &p_next); - /* no need to test for invalid UTF-8, 0xFFFD is not ident_next */ - if (!lre_js_is_ident_next(c)) - break; - if (string_buffer_putc(b2, c)) - goto fail; - p = p_next; - } - - body_str = string_buffer_end(b); - flags_str = string_buffer_end(b2); - if (JS_IsException(body_str) || - JS_IsException(flags_str)) { - JS_FreeValue(s->ctx, body_str); - JS_FreeValue(s->ctx, flags_str); - return -1; - } - s->token.val = TOK_REGEXP; - s->token.u.regexp.body = body_str; - s->token.u.regexp.flags = flags_str; - s->buf_ptr = p; - return 0; - fail: - string_buffer_free(b); - string_buffer_free(b2); - return -1; -} - -#endif // QJS_DISABLE_PARSER - -static __exception int ident_realloc(JSContext *ctx, char **pbuf, size_t *psize, - char *static_buf) -{ - char *buf, *new_buf; - size_t size, new_size; - - buf = *pbuf; - size = *psize; - if (size >= (SIZE_MAX / 3) * 2) - new_size = SIZE_MAX; - else - new_size = size + (size >> 1); - if (buf == static_buf) { - new_buf = js_malloc(ctx, new_size); - if (!new_buf) - return -1; - memcpy(new_buf, buf, size); - } else { - new_buf = js_realloc(ctx, buf, new_size); - if (!new_buf) - return -1; - } - *pbuf = new_buf; - *psize = new_size; - return 0; -} - -#ifndef QJS_DISABLE_PARSER - -/* convert a TOK_IDENT to a keyword when needed */ -static void update_token_ident(JSParseState *s) -{ - if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD || - (s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD && - s->cur_func->is_strict_mode) || - (s->token.u.ident.atom == JS_ATOM_yield && - ((s->cur_func->func_kind & JS_FUNC_GENERATOR) || - (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && - !s->cur_func->in_function_body && s->cur_func->parent && - (s->cur_func->parent->func_kind & JS_FUNC_GENERATOR)))) || - (s->token.u.ident.atom == JS_ATOM_await && - (s->is_module || - (s->cur_func->func_kind & JS_FUNC_ASYNC) || - s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT || - (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && - !s->cur_func->in_function_body && s->cur_func->parent && - ((s->cur_func->parent->func_kind & JS_FUNC_ASYNC) || - s->cur_func->parent->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT))))) { - if (s->token.u.ident.has_escape) { - s->token.u.ident.is_reserved = true; - s->token.val = TOK_IDENT; - } else { - /* The keywords atoms are pre allocated */ - s->token.val = s->token.u.ident.atom - 1 + TOK_FIRST_KEYWORD; - } - } -} - -/* if the current token is an identifier or keyword, reparse it - according to the current function type */ -static void reparse_ident_token(JSParseState *s) -{ - if (s->token.val == TOK_IDENT || - (s->token.val >= TOK_FIRST_KEYWORD && - s->token.val <= TOK_LAST_KEYWORD)) { - s->token.val = TOK_IDENT; - s->token.u.ident.is_reserved = false; - update_token_ident(s); - } -} - -/* 'c' is the first character. Return JS_ATOM_NULL in case of error */ -static JSAtom parse_ident(JSParseState *s, const uint8_t **pp, - bool *pident_has_escape, int c, bool is_private) -{ - const uint8_t *p, *p_next; - char ident_buf[128], *buf; - size_t ident_size, ident_pos; - JSAtom atom = JS_ATOM_NULL; - - p = *pp; - buf = ident_buf; - ident_size = sizeof(ident_buf); - ident_pos = 0; - if (is_private) - buf[ident_pos++] = '#'; - for(;;) { - if (c < 0x80) { - buf[ident_pos++] = c; - } else { - ident_pos += utf8_encode((uint8_t*)buf + ident_pos, c); - } - c = *p; - p_next = p + 1; - if (c == '\\' && *p_next == 'u') { - c = lre_parse_escape(&p_next, true); - *pident_has_escape = true; - } else if (c >= 0x80) { - c = utf8_decode(p, &p_next); - /* no need to test for invalid UTF-8, 0xFFFD is not ident_next */ - } - if (!lre_js_is_ident_next(c)) - break; - p = p_next; - if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { - if (ident_realloc(s->ctx, &buf, &ident_size, ident_buf)) - goto done; - } - } - /* buf is pure ASCII or UTF-8 encoded */ - atom = JS_NewAtomLen(s->ctx, buf, ident_pos); - done: - if (unlikely(buf != ident_buf)) - js_free(s->ctx, buf); - *pp = p; - return atom; -} - - -static __exception int next_token(JSParseState *s) -{ - const uint8_t *p, *p_next; - int c; - bool ident_has_escape; - JSAtom atom; - - if (js_check_stack_overflow(s->ctx->rt, 1000)) { - JS_ThrowStackOverflow(s->ctx); - return -1; - } - - free_token(s, &s->token); - - p = s->last_ptr = s->buf_ptr; - s->got_lf = false; - s->last_line_num = s->token.line_num; - s->last_col_num = s->token.col_num; - redo: - s->token.line_num = s->line_num; - s->token.col_num = s->col_num; - s->token.ptr = p; - c = *p; - switch(c) { - case 0: - if (p >= s->buf_end) { - s->token.val = TOK_EOF; - } else { - goto def_token; - } - break; - case '`': - if (js_parse_template_part(s, p + 1)) - goto fail; - p = s->buf_ptr; - break; - case '\'': - case '\"': - if (js_parse_string(s, c, true, p + 1, &s->token, &p)) - goto fail; - break; - case '\r': /* accept DOS and MAC newline sequences */ - if (p[1] == '\n') { - p++; - } - /* fall thru */ - case '\n': - p++; - line_terminator: - s->eol = &p[-1]; - s->mark = p; - s->got_lf = true; - s->line_num++; - goto redo; - case '\f': - case '\v': - case ' ': - case '\t': - s->mark = ++p; - goto redo; - case '/': - if (p[1] == '*') { - /* comment */ - p += 2; - for(;;) { - if (*p == '\0' && p >= s->buf_end) { - js_parse_error(s, "unexpected end of comment"); - goto fail; - } - if (p[0] == '*' && p[1] == '/') { - p += 2; - break; - } - if (*p == '\n') { - s->line_num++; - s->got_lf = true; /* considered as LF for ASI */ - s->eol = p++; - s->mark = p; - } else if (*p == '\r') { - s->got_lf = true; /* considered as LF for ASI */ - p++; - } else if (*p >= 0x80) { - c = utf8_decode(p, &p); - /* ignore invalid UTF-8 in comments */ - if (c == CP_LS || c == CP_PS) { - s->got_lf = true; /* considered as LF for ASI */ - } - } else { - p++; - } - } - s->mark = p; - goto redo; - } else if (p[1] == '/') { - /* line comment */ - p += 2; - skip_line_comment: - for(;;) { - if (*p == '\0' && p >= s->buf_end) - break; - if (*p == '\r' || *p == '\n') - break; - if (*p >= 0x80) { - c = utf8_decode(p, &p); - /* ignore invalid UTF-8 in comments */ - /* LS or PS are considered as line terminator */ - if (c == CP_LS || c == CP_PS) { - break; - } - } else { - p++; - } - } - s->mark = p; - goto redo; - } else if (p[1] == '=') { - p += 2; - s->token.val = TOK_DIV_ASSIGN; - } else { - p++; - s->token.val = c; - } - break; - case '\\': - if (p[1] == 'u') { - const uint8_t *p1 = p + 1; - int c1 = lre_parse_escape(&p1, true); - if (c1 >= 0 && lre_js_is_ident_first(c1)) { - c = c1; - p = p1; - ident_has_escape = true; - goto has_ident; - } else { - /* XXX: syntax error? */ - } - } - goto def_token; - case 'a': case 'b': case 'c': case 'd': - case 'e': case 'f': case 'g': case 'h': - case 'i': case 'j': case 'k': case 'l': - case 'm': case 'n': case 'o': case 'p': - case 'q': case 'r': case 's': case 't': - case 'u': case 'v': case 'w': case 'x': - case 'y': case 'z': - case 'A': case 'B': case 'C': case 'D': - case 'E': case 'F': case 'G': case 'H': - case 'I': case 'J': case 'K': case 'L': - case 'M': case 'N': case 'O': case 'P': - case 'Q': case 'R': case 'S': case 'T': - case 'U': case 'V': case 'W': case 'X': - case 'Y': case 'Z': - case '_': - case '$': - /* identifier */ - s->mark = p; - p++; - ident_has_escape = false; - has_ident: - atom = parse_ident(s, &p, &ident_has_escape, c, false); - if (atom == JS_ATOM_NULL) - goto fail; - s->token.u.ident.atom = atom; - s->token.u.ident.has_escape = ident_has_escape; - s->token.u.ident.is_reserved = false; - s->token.val = TOK_IDENT; - update_token_ident(s); - break; - case '#': - /* private name */ - { - p++; - c = *p; - p_next = p + 1; - if (c == '\\' && *p_next == 'u') { - c = lre_parse_escape(&p_next, true); - } else if (c >= 0x80) { - c = utf8_decode(p, &p_next); - if (p_next == p + 1) - goto invalid_utf8; - } - if (!lre_js_is_ident_first(c)) { - js_parse_error(s, "invalid first character of private name"); - goto fail; - } - p = p_next; - ident_has_escape = false; /* not used */ - atom = parse_ident(s, &p, &ident_has_escape, c, true); - if (atom == JS_ATOM_NULL) - goto fail; - s->token.u.ident.atom = atom; - s->token.val = TOK_PRIVATE_NAME; - } - break; - case '.': - if (p[1] == '.' && p[2] == '.') { - p += 3; - s->token.val = TOK_ELLIPSIS; - break; - } - if (p[1] >= '0' && p[1] <= '9') { - goto parse_number; - } else { - goto def_token; - } - break; - case '0': - /* in strict mode, octal literals are not accepted */ - if (is_digit(p[1]) && (s->cur_func->is_strict_mode)) { - js_parse_error(s, "Octal literals are not allowed in strict mode"); - goto fail; - } - goto parse_number; - case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': - case '9': - /* number */ - parse_number: - { - JSValue ret; - const uint8_t *p1; - int flags; - flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL | - ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; - ret = js_atof(s->ctx, (const char *)p, (const char **)&p, 0, - flags); - if (JS_IsException(ret)) - goto fail; - /* reject `10instanceof Number` */ - if (JS_VALUE_IS_NAN(ret) || - lre_js_is_ident_next(utf8_decode(p, &p1))) { - JS_FreeValue(s->ctx, ret); - js_parse_error(s, "invalid number literal"); - goto fail; - } - s->token.val = TOK_NUMBER; - s->token.u.num.val = ret; - } - break; - case '*': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_MUL_ASSIGN; - } else if (p[1] == '*') { - if (p[2] == '=') { - p += 3; - s->token.val = TOK_POW_ASSIGN; - } else { - p += 2; - s->token.val = TOK_POW; - } - } else { - goto def_token; - } - break; - case '%': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_MOD_ASSIGN; - } else { - goto def_token; - } - break; - case '+': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_PLUS_ASSIGN; - } else if (p[1] == '+') { - p += 2; - s->token.val = TOK_INC; - } else { - goto def_token; - } - break; - case '-': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_MINUS_ASSIGN; - } else if (p[1] == '-') { - if (s->allow_html_comments && p[2] == '>' && - (s->got_lf || s->last_ptr == s->buf_start)) { - /* Annex B: `-->` at beginning of line is an html comment end. - It extends to the end of the line. - */ - goto skip_line_comment; - } - p += 2; - s->token.val = TOK_DEC; - } else { - goto def_token; - } - break; - case '<': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_LTE; - } else if (p[1] == '<') { - if (p[2] == '=') { - p += 3; - s->token.val = TOK_SHL_ASSIGN; - } else { - p += 2; - s->token.val = TOK_SHL; - } - } else if (s->allow_html_comments && - p[1] == '!' && p[2] == '-' && p[3] == '-') { - /* Annex B: handle `