diff --git a/.github/scripts/codeql_buildscript.sh b/.github/scripts/codeql_buildscript.sh index 99060af8f4..eae69851c8 100755 --- a/.github/scripts/codeql_buildscript.sh +++ b/.github/scripts/codeql_buildscript.sh @@ -19,9 +19,20 @@ sudo wget --progress=dot:giga -O clang+llvm-x86_64-linux-gnu.tar.xz https://gith popd # libtinfo.so.5 for /opt/llvm-18.1.8/lib/libomptarget.rtl.amdgpu.so.18.1 +# Install the libtinfo5 compat package at the same ncurses version as the +# runner's already-installed libtinfo6; fall back to the newest one in the +# pool. A hard-coded URL 404s once the ncurses point release is bumped, which +# then breaks the wamrc link against libomptarget (NCURSES_TINFO_5 symbols). sudo apt -qq update -wget http://security.ubuntu.com/ubuntu/pool/universe/n/ncurses/libtinfo5_6.3-2ubuntu0.1_amd64.deb -sudo apt install -y -qq ./libtinfo5_6.3-2ubuntu0.1_amd64.deb +nc_pool="http://security.ubuntu.com/ubuntu/pool/universe/n/ncurses" +nc_ver="$(dpkg-query -W -f='${Version}' libtinfo6)" +libtinfo5_deb="libtinfo5_${nc_ver}_amd64.deb" +if ! wget -q "${nc_pool}/${libtinfo5_deb}"; then + libtinfo5_deb="$(wget -qO- "${nc_pool}/" \ + | grep -oE 'libtinfo5_[0-9][^"]*_amd64\.deb' | sort -Vu | tail -1)" + wget -q "${nc_pool}/${libtinfo5_deb}" +fi +sudo apt install -y -qq "./${libtinfo5_deb}" # Start the build process WAMR_DIR=${PWD} @@ -58,6 +69,7 @@ build_iwasm() { -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ -DLLVM_DIR=${LLVM_DIR} \ + -DWAMR_BUILD_COMPONENT_MODEL=0 \ $options cmake --build build --target iwasm --parallel if [[ $? != 0 ]]; then @@ -75,8 +87,11 @@ wamrc_options_list=( # List of compilation options for iwasm iwasm_options_list=( - #default - "" + # Default plus Preview 2. The remaining entries deliberately start from + # the core runtime so each feature permutation is independent; several + # legacy-libc, 32-bit, GC and thread combinations are not component-model + # configurations. + "-DWAMR_BUILD_COMPONENT_MODEL=1" # +classic interp "-DWAMR_BUILD_FAST_INTERP=0 -DWAMR_BUILD_SIMD=0" # fast jit @@ -84,6 +99,8 @@ iwasm_options_list=( # +llvm jit "-DWAMR_BUILD_JIT=1" # + # The Preview 2 native-call bridge is 64-bit-only; this still covers the + # core X86_32 runtime. "-DWAMR_BUILD_TARGET=X86_32" # # libraries diff --git a/.github/scripts/codeql_fail_on_error.py b/.github/scripts/codeql_fail_on_error.py index f150c38a25..1a6bcdba56 100755 --- a/.github/scripts/codeql_fail_on_error.py +++ b/.github/scripts/codeql_fail_on_error.py @@ -61,9 +61,15 @@ def codeql_sarif_contain_error(filename, dismissed_alerts): s = json.load(f) for run in s.get("runs", []): - rules_metadata = run["tool"]["driver"]["rules"] + # Rule metadata isn't always in one place: CodeQL keeps it on the tool + # driver, but rules contributed by a query-pack extension live on that + # extension instead, and the driver's list is empty when nothing fired. + # So read the driver rules, then fall back to gathering them from the + # extensions rather than assuming a fixed location. + rules_metadata = run["tool"]["driver"].get("rules") or [] if not rules_metadata: - rules_metadata = run["tool"]["extensions"][0]["rules"] + for ext in run["tool"].get("extensions", []): + rules_metadata += ext.get("rules", []) for res in run.get("results", []): if "ruleIndex" in res: diff --git a/.github/workflows/apple_app_store_interpreter.yml b/.github/workflows/apple_app_store_interpreter.yml new file mode 100644 index 0000000000..47ec47d2a9 --- /dev/null +++ b/.github/workflows/apple_app_store_interpreter.yml @@ -0,0 +1,198 @@ +# Copyright (C) 2026 Matt Hargett. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +name: Apple App Store interpreter + +on: + pull_request: + types: + - opened + - synchronize + paths: + - ".github/workflows/apple_app_store_interpreter.yml" + - "build-scripts/**" + - "core/**" + - "!core/deps/**" + - "product-mini/platforms/darwin/**" + - "tests/apple-app-store-interpreter/**" + - "tests/unit/component-execution/wasm-apps/add.wasm" + push: + branches: + - main + - "dev/**" + paths: + - ".github/workflows/apple_app_store_interpreter.yml" + - "build-scripts/**" + - "core/**" + - "!core/deps/**" + - "product-mini/platforms/darwin/**" + - "tests/apple-app-store-interpreter/**" + - "tests/unit/component-execution/wasm-apps/add.wasm" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build_static_interpreter: + name: ${{ matrix.platform }} static wasm32-wasip2 fast interpreter + runs-on: macos-15 + strategy: + fail-fast: false + matrix: + include: + - platform: macOS + system_name: Darwin + sdk: macosx + architecture: arm64 + - platform: iOS + system_name: iOS + sdk: iphoneos + architecture: arm64 + - platform: tvOS + system_name: tvOS + sdk: appletvos + architecture: arm64 + - platform: watchOS + system_name: watchOS + sdk: watchos + architecture: arm64_32 + - platform: visionOS + system_name: visionOS + sdk: xros + architecture: arm64 + + steps: + - name: checkout + uses: actions/checkout@v7.0.0 + + - name: Install component parser build dependency + env: + HOMEBREW_NO_AUTO_UPDATE: "1" + run: brew list bison >/dev/null 2>&1 || brew install bison + + - name: Build static interpreter archive + run: | + cmake \ + -C tests/apple-app-store-interpreter/apple_wasip2_fast_interp.cmake \ + -S product-mini/platforms/darwin \ + -B "build/apple-${{ matrix.sdk }}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DBISON_EXECUTABLE="$(brew --prefix bison)/bin/bison" \ + -DCMAKE_SYSTEM_NAME="${{ matrix.system_name }}" \ + -DCMAKE_OSX_SYSROOT="${{ matrix.sdk }}" \ + -DCMAKE_OSX_ARCHITECTURES="${{ matrix.architecture }}" \ + -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \ + -DWAMR_BUILD_TARGET=AARCH64 + cmake --build "build/apple-${{ matrix.sdk }}" \ + --target vmlib --config Release --parallel 4 + + - name: Verify archive shape + run: | + library="build/apple-${{ matrix.sdk }}/libiwasm.a" + tests/apple-app-store-interpreter/verify_archive.sh \ + "build/apple-${{ matrix.sdk }}" \ + "$library" \ + "${{ matrix.architecture }}" + + - name: Run hardened-runtime embedding smoke + if: matrix.platform == 'macOS' + run: | + common_flags=( + -std=c11 -O3 -DNDEBUG -Wall -Wextra -Werror + -flto=full -arch "${{ matrix.architecture }}" + -I core/iwasm/include + ) + link_flags=( + "build/apple-${{ matrix.sdk }}/libiwasm.a" + -flto=full "-Wl,-dead_strip" -lpthread -lm + ) + xcrun --sdk macosx clang "${common_flags[@]}" \ + -I core/iwasm/include \ + tests/apple-app-store-interpreter/smoke.c \ + "${link_flags[@]}" \ + -o "build/apple-${{ matrix.sdk }}/embed-smoke" + xcrun --sdk macosx clang "${common_flags[@]}" \ + -DWASM_ENABLE_COMPONENT_MODEL=1 \ + -DWASM_ENABLE_INTERP=1 \ + -DWASM_ENABLE_FAST_INTERP=1 \ + -DWASM_ENABLE_THREAD_MGR=1 \ + -I core/iwasm/common \ + -I core/iwasm/common/component-model \ + -I core/iwasm/interpreter \ + -I core/shared/mem-alloc \ + -I core/shared/utils \ + -I core/shared/platform/include \ + -I core/shared/platform/darwin \ + tests/apple-app-store-interpreter/component_smoke.c \ + "${link_flags[@]}" \ + -o "build/apple-${{ matrix.sdk }}/component-smoke" + xcrun --sdk macosx clang "${common_flags[@]}" \ + -DWASM_ENABLE_COMPONENT_MODEL=1 \ + -DWASM_ENABLE_INTERP=1 \ + -DWASM_ENABLE_FAST_INTERP=1 \ + -DWASM_ENABLE_THREAD_MGR=1 \ + -I core/iwasm/common \ + -I core/iwasm/common/component-model \ + -I core/iwasm/interpreter \ + -I core/shared/mem-alloc \ + -I core/shared/utils \ + -I core/shared/platform/include \ + -I core/shared/platform/darwin \ + tests/apple-app-store-interpreter/component_termination_smoke.c \ + "${link_flags[@]}" \ + -o "build/apple-${{ matrix.sdk }}/component-termination-smoke" + xcrun --sdk macosx clang "${common_flags[@]}" \ + -DWASM_ENABLE_COMPONENT_MODEL=1 \ + -DWASM_ENABLE_INTERP=1 \ + -DWASM_ENABLE_FAST_INTERP=1 \ + -DWASM_ENABLE_THREAD_MGR=1 \ + -I core/iwasm/common \ + -I core/iwasm/common/component-model \ + -I core/iwasm/interpreter \ + -I core/shared/mem-alloc \ + -I core/shared/utils \ + -I core/shared/platform/include \ + -I core/shared/platform/darwin \ + tests/apple-app-store-interpreter/component_host_import_smoke.c \ + "${link_flags[@]}" \ + -o "build/apple-${{ matrix.sdk }}/component-host-import-smoke" + xcrun --sdk macosx clang "${common_flags[@]}" \ + -DWASM_ENABLE_COMPONENT_MODEL=1 \ + -DWASM_ENABLE_INTERP=1 \ + -DWASM_ENABLE_FAST_INTERP=1 \ + -DWASM_ENABLE_THREAD_MGR=1 \ + -I core/iwasm/common \ + -I core/iwasm/common/component-model \ + -I core/iwasm/interpreter \ + -I core/shared/mem-alloc \ + -I core/shared/utils \ + -I core/shared/platform/include \ + -I core/shared/platform/darwin \ + tests/apple-app-store-interpreter/component_wasip2_command_smoke.c \ + "${link_flags[@]}" \ + -o "build/apple-${{ matrix.sdk }}/component-wasip2-command-smoke" + tests/apple-app-store-interpreter/verify_link.sh \ + "build/apple-${{ matrix.sdk }}/component-smoke" + codesign --force --sign - --options runtime \ + "build/apple-${{ matrix.sdk }}/embed-smoke" + codesign --force --sign - --options runtime \ + "build/apple-${{ matrix.sdk }}/component-smoke" + codesign --force --sign - --options runtime \ + "build/apple-${{ matrix.sdk }}/component-termination-smoke" + codesign --force --sign - --options runtime \ + "build/apple-${{ matrix.sdk }}/component-host-import-smoke" + codesign --force --sign - --options runtime \ + "build/apple-${{ matrix.sdk }}/component-wasip2-command-smoke" + "build/apple-${{ matrix.sdk }}/embed-smoke" + "build/apple-${{ matrix.sdk }}/component-smoke" \ + tests/unit/component-execution/wasm-apps/add.wasm + "build/apple-${{ matrix.sdk }}/component-termination-smoke" + "build/apple-${{ matrix.sdk }}/component-host-import-smoke" \ + tests/apple-app-store-interpreter/static_host_component.wasm + "build/apple-${{ matrix.sdk }}/component-wasip2-command-smoke" \ + tests/apple-app-store-interpreter/wasip2_command.wasm diff --git a/.github/workflows/build_docker_images.yml b/.github/workflows/build_docker_images.yml index 9c4371b4e7..2b4a6bba3f 100644 --- a/.github/workflows/build_docker_images.yml +++ b/.github/workflows/build_docker_images.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: Build and save Docker image(wasm-debug-server:${{ inputs.ver_num }}) to tar file run: | diff --git a/.github/workflows/build_iwasm_release.yml b/.github/workflows/build_iwasm_release.yml index ab84c4f43f..314164763a 100644 --- a/.github/workflows/build_iwasm_release.yml +++ b/.github/workflows/build_iwasm_release.yml @@ -104,11 +104,11 @@ jobs: contents: write # for uploading release artifacts steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 - name: get cached LLVM libraries id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin diff --git a/.github/workflows/build_llvm_libraries.yml b/.github/workflows/build_llvm_libraries.yml index 65c787d668..eedfeef579 100644 --- a/.github/workflows/build_llvm_libraries.yml +++ b/.github/workflows/build_llvm_libraries.yml @@ -45,7 +45,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: install dependencies for non macos if: ${{ !startsWith(inputs.os, 'macos') }} @@ -79,7 +79,7 @@ jobs: - name: Cache LLVM libraries id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin diff --git a/.github/workflows/build_wamr_lldb.yml b/.github/workflows/build_wamr_lldb.yml index 98a15e653b..5387e98d3a 100644 --- a/.github/workflows/build_wamr_lldb.yml +++ b/.github/workflows/build_wamr_lldb.yml @@ -55,7 +55,7 @@ jobs: contents: write # for uploading release artifacts steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 - name: download and install wasi-sdk run: | @@ -68,7 +68,7 @@ jobs: - name: Cache build id: lldb_build_cache - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm-project/build/bin diff --git a/.github/workflows/build_wamr_sdk.yml b/.github/workflows/build_wamr_sdk.yml index df1f26c748..2b83e4928c 100644 --- a/.github/workflows/build_wamr_sdk.yml +++ b/.github/workflows/build_wamr_sdk.yml @@ -45,7 +45,7 @@ jobs: contents: write # for uploading release artifacts steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 - name: download wamr-app-framework run: | diff --git a/.github/workflows/build_wamr_vscode_ext.yml b/.github/workflows/build_wamr_vscode_ext.yml index 7410fd0955..a4838eb277 100644 --- a/.github/workflows/build_wamr_vscode_ext.yml +++ b/.github/workflows/build_wamr_vscode_ext.yml @@ -24,7 +24,7 @@ jobs: contents: write # for uploading release artifacts steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 - name: Use Node.js 18.x uses: actions/setup-node@v6 diff --git a/.github/workflows/build_wamr_wasi_extensions.yml b/.github/workflows/build_wamr_wasi_extensions.yml index 21a07a1cc1..163e0d029d 100644 --- a/.github/workflows/build_wamr_wasi_extensions.yml +++ b/.github/workflows/build_wamr_wasi_extensions.yml @@ -28,7 +28,7 @@ jobs: os: [ubuntu-22.04] steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: install-wasi-sdk-wabt uses: ./.github/actions/install-wasi-sdk-wabt diff --git a/.github/workflows/build_wamrc.yml b/.github/workflows/build_wamrc.yml index 9fbe38c0a0..c22e419a80 100644 --- a/.github/workflows/build_wamrc.yml +++ b/.github/workflows/build_wamrc.yml @@ -41,11 +41,11 @@ jobs: contents: write # for uploading release artifacts steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 - name: get cached LLVM libraries id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin diff --git a/.github/workflows/check_version_h.yml b/.github/workflows/check_version_h.yml index 4ea4c1105e..1a4a172dd3 100644 --- a/.github/workflows/check_version_h.yml +++ b/.github/workflows/check_version_h.yml @@ -14,7 +14,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: cmake execute to generate version.h run: cmake -B build_version -S . diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ecc30954b8..968bb8f5d8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -9,17 +9,35 @@ on: push: branches: - dev/** + # scan pull requests so findings surface on the PR instead of only post-merge + pull_request: # midnight UTC on the latest commit on the main branch schedule: - cron: "0 0 * * *" # allow to be triggered manually workflow_dispatch: +# Serialize CodeQL runs per PR / branch. Only a pull_request cancels its own +# superseded run - once a PR head is replaced the old analysis is throwaway. +# Pushes to dev/** and the nightly cron are left to finish, so branch and +# scheduled scans are never dropped. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: analyze: - # only run this job if the repository is not a fork - # if want to run this job on a fork, please remove the if condition - if: github.repository == 'bytecodealliance/wasm-micro-runtime' + # Pull requests: only scan when the head branch lives in this same + # repository. A pull request from a fork runs with a read-only GITHUB_TOKEN + # (no `security-events: write`), so uploading results and reading + # code-scanning alerts is not permitted and the job would fail; skip it + # cleanly instead. Other events (push to dev/**, the nightly cron, manual + # runs) keep the original behavior of running only on the upstream repo. + if: >- + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository) + || (github.event_name != 'pull_request' && + github.repository == 'bytecodealliance/wasm-micro-runtime') name: Analyze # Runner size impacts CodeQL analysis time. To learn more, please see: # - https://gh.io/recommended-hardware-resources-for-running-codeql @@ -43,13 +61,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 with: submodules: recursive # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v4.36.0 + uses: github/codeql-action/init@v4.36.3 with: languages: ${{ matrix.language }} # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs @@ -61,7 +79,7 @@ jobs: ./.github/scripts/codeql_buildscript.sh || exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4.36.0 + uses: github/codeql-action/analyze@v4.36.3 with: category: "/language:${{matrix.language}}" upload: false @@ -114,7 +132,7 @@ jobs: output: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif - name: Upload CodeQL results to code scanning - uses: github/codeql-action/upload-sarif@v4.36.0 + uses: github/codeql-action/upload-sarif@v4.36.3 with: sarif_file: ${{ steps.step1.outputs.sarif-output }} category: "/language:${{matrix.language}}" diff --git a/.github/workflows/coding_guidelines.yml b/.github/workflows/coding_guidelines.yml index 402c455d86..2b5cc2b06f 100644 --- a/.github/workflows/coding_guidelines.yml +++ b/.github/workflows/coding_guidelines.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 with: fetch-depth: 0 diff --git a/.github/workflows/compilation_on_android_ubuntu.yml b/.github/workflows/compilation_on_android_ubuntu.yml index 15de026046..5206d3dca8 100644 --- a/.github/workflows/compilation_on_android_ubuntu.yml +++ b/.github/workflows/compilation_on_android_ubuntu.yml @@ -100,13 +100,13 @@ jobs: llvm_cache_key: ${{ needs.build_llvm_libraries_on_ubuntu_2204.outputs.cache_key }} steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 # since jobs.id can't contain the dot character # it is hard to use `format` to assemble the cache key - name: Get LLVM libraries id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -271,13 +271,13 @@ jobs: extra_options: "-DWAMR_BUILD_SIMD=0" steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 # only download llvm cache when needed - name: Get LLVM libraries id: retrieve_llvm_libs if: endsWith(matrix.make_options_run_mode, '_JIT_BUILD_OPTIONS') - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -322,13 +322,13 @@ jobs: steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 with: submodules: recursive - name: Get LLVM libraries id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -381,11 +381,11 @@ jobs: steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: Get LLVM libraries id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -405,6 +405,10 @@ jobs: working-directory: tests/regression/ba-issues - name: Run regression tests + env: + # Avoid host-CPU AOT output changing as hosted runners move between + # x86 generations; this matches the x86-64 spec-suite baseline. + WAMRC_TARGET_OPTIONS: "--target=x86_64 --cpu=skylake" run: | python run.py working-directory: tests/regression/ba-issues @@ -446,12 +450,12 @@ jobs: steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: Get LLVM libraries id: retrieve_llvm_libs if: (!endsWith(matrix.make_options, '_INTERP_BUILD_OPTIONS')) - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -503,11 +507,11 @@ jobs: steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: Get LLVM libraries id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -677,7 +681,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: Set-up OCaml uses: ocaml/setup-ocaml@v3 @@ -707,11 +711,21 @@ jobs: && matrix.running_mode != 'fast-jit' && matrix.running_mode != 'jit' && matrix.running_mode != 'multi-tier-jit') run: echo "TEST_ON_X86_32=true" >> $GITHUB_ENV + - name: Free up disk space + if: env.USE_LLVM == 'true' + run: | + # Restoring the prebuilt LLVM libraries cache overflows the runner's + # small default free space and fails with "no space left on device". + # Drop preinstalled toolchains this job never uses (~20 GB freed). + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \ + /opt/hostedtoolcache/CodeQL /usr/local/share/boost + df -h / + #only download llvm libraries in jit and aot mode - name: Get LLVM libraries if: env.USE_LLVM == 'true' id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin diff --git a/.github/workflows/compilation_on_macos.yml b/.github/workflows/compilation_on_macos.yml index 30c9b0565b..174ef7c2d0 100644 --- a/.github/workflows/compilation_on_macos.yml +++ b/.github/workflows/compilation_on_macos.yml @@ -89,11 +89,11 @@ jobs: llvm_cache_key: ${{ needs.build_llvm_libraries_on_arm_macos.outputs.cache_key }} steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: Get LLVM libraries id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -191,13 +191,13 @@ jobs: extra_options: "-DWAMR_BUILD_SIMD=0" steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 # only download llvm cache when needed - name: Get LLVM libraries id: retrieve_llvm_libs if: endsWith(matrix.make_options_run_mode, '_JIT_BUILD_OPTIONS') - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -253,12 +253,12 @@ jobs: steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: Get LLVM libraries id: retrieve_llvm_libs if: (!endsWith(matrix.make_options, '_INTERP_BUILD_OPTIONS')) - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -285,6 +285,14 @@ jobs: cmake --build . --config Release --parallel 4 working-directory: wamr-compiler + # The macOS runner ships Apple/Xcode GNU bison 2.3, which cannot parse the + # component-model wave-parser grammar (it uses Bison 2.4+ %code blocks). + # Put Homebrew bison (3.x) ahead of /usr/bin/bison on PATH. + - name: Setup modern bison + run: | + brew install bison + echo "$(brew --prefix bison)/bin" >> "$GITHUB_PATH" + - name: Build Sample [wasm-c-api] run: | VERBOSE=1 @@ -311,7 +319,7 @@ jobs: llvm_cache_key: ${{ needs.build_llvm_libraries_on_arm_macos.outputs.cache_key }} steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: install-wasi-sdk-wabt uses: ./.github/actions/install-wasi-sdk-wabt @@ -366,7 +374,7 @@ jobs: - name: Get LLVM libraries id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin diff --git a/.github/workflows/compilation_on_nuttx.yml b/.github/workflows/compilation_on_nuttx.yml index 1ad28f3bb8..6901d16e00 100644 --- a/.github/workflows/compilation_on_nuttx.yml +++ b/.github/workflows/compilation_on_nuttx.yml @@ -56,6 +56,11 @@ jobs: image: ghcr.io/apache/nuttx/apache-nuttx-ci-linux@sha256:d9261eacf6c6ebe656c571757751c803e8f04c3ae9b820320a5ea5dd57b7205a strategy: + # A single hosted-runner loss during checkout should not discard the + # other board/config results, and limiting the checkout burst avoids + # launching all 56 container jobs at once. + fail-fast: false + max-parallel: 16 matrix: nuttx_board_config: [ # x64 @@ -83,25 +88,24 @@ jobs: - "CONFIG_INTERPRETERS_WAMR_AOT CONFIG_INTERPRETERS_WAMR_FAST CONFIG_INTERPRETERS_WAMR_LIBC_BUILTIN" - "CONFIG_INTERPRETERS_WAMR_AOT CONFIG_INTERPRETERS_WAMR_CLASSIC" - "CONFIG_INTERPRETERS_WAMR_AOT CONFIG_INTERPRETERS_WAMR_CLASSIC CONFIG_INTERPRETERS_WAMR_LIBC_WASI" - - "CONFIG_INTERPRETERS_WAMR_AOT CONFIG_INTERPRETERS_WAMR_CLASSIC CONFIG_INTERPRETERS_WAMR_LIBC_WASI" steps: - name: Checkout NuttX - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 with: repository: apache/nuttx ref: 09a71ec7c16c43398d5acbdcbeee7b08736c3170 path: nuttx - name: Checkout NuttX Apps - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 with: repository: apache/nuttx-apps ref: 6bd593459c4af3cef325c3d22bccd5537a8ed755 path: apps - name: Checkout WAMR - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 with: repository: ${{ github.repository }} path: apps/interpreters/wamr/wamr @@ -123,10 +127,10 @@ jobs: - name: Build working-directory: nuttx - run: make -j$(nproc) EXTRAFLAGS=-Werror + run: make -j"$(nproc)" EXTRAFLAGS=-Werror - name: Checkout Bloaty - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 with: repository: google/bloaty submodules: recursive diff --git a/.github/workflows/compilation_on_sgx.yml b/.github/workflows/compilation_on_sgx.yml index 6791d31aaf..ddddf02570 100644 --- a/.github/workflows/compilation_on_sgx.yml +++ b/.github/workflows/compilation_on_sgx.yml @@ -123,7 +123,7 @@ jobs: make_options_feature: "-DWAMR_BUILD_SIMD=0" steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: install SGX SDK and necessary libraries uses: ./.github/actions/install-linux-sgx @@ -166,7 +166,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: install-wasi-sdk-wabt uses: ./.github/actions/install-wasi-sdk-wabt @@ -190,7 +190,7 @@ jobs: - name: Get LLVM libraries if: matrix.iwasm_make_options_run_mode == '$AOT_BUILD_OPTIONS' id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -227,7 +227,10 @@ jobs: if: matrix.iwasm_make_options_run_mode == '$AOT_BUILD_OPTIONS' run: | source /opt/intel/sgxsdk/environment - ./wamrc -sgx -o ./file.aot ./file.wasm + # A hosted Zen 4 runner can select AVX-512 instructions that the SGX + # execution environment does not expose. Use the same non-AVX-512 + # SIMD baseline as the x86-64 spec suite instead of the host CPU. + ./wamrc -sgx --target=x86_64 --cpu=skylake -o ./file.aot ./file.wasm ./iwasm --dir=. ./file.aot working-directory: product-mini/platforms/${{ matrix.platform }}/enclave-sample @@ -242,18 +245,14 @@ jobs: [$DEFAULT_TEST_OPTIONS, $SIMD_TEST_OPTIONS, $XIP_TEST_OPTIONS] os: [ubuntu-22.04] exclude: - # classic-interp, fast-interp and fast-jit don't support simd + # classic-interp and fast-jit don't support simd - running_mode: "classic-interp" test_option: $SIMD_TEST_OPTIONS - - running_mode: "fast-interp" - test_option: $SIMD_TEST_OPTIONS - running_mode: "fast-jit" test_option: $SIMD_TEST_OPTIONS - # classic-interp, fast-interp and fast jit don't support XIP + # classic-interp and fast-jit don't support XIP - running_mode: "classic-interp" test_option: $XIP_TEST_OPTIONS - - running_mode: "fast-interp" - test_option: $XIP_TEST_OPTIONS - running_mode: "fast-jit" test_option: $XIP_TEST_OPTIONS include: @@ -262,12 +261,12 @@ jobs: steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: Get LLVM libraries if: matrix.running_mode == 'aot' id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin diff --git a/.github/workflows/compilation_on_windows.yml b/.github/workflows/compilation_on_windows.yml index f52517aabf..2b36b9875c 100644 --- a/.github/workflows/compilation_on_windows.yml +++ b/.github/workflows/compilation_on_windows.yml @@ -84,7 +84,7 @@ jobs: "-DWAMR_BUILD_LIBC_UVWASI=0 -DWAMR_BUILD_LIBC_WASI=1", ] steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 - name: clone uvwasi library if: ${{ !contains(matrix.build_options, '-DWAMR_BUILD_LIBC_UVWASI=0') }} @@ -108,13 +108,13 @@ jobs: llvm_cache_key: ${{ needs.build_llvm_libraries_on_windows.outputs.cache_key }} steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 # since jobs.id can't contain the dot character # it is hard to use `format` to assemble the cache key - name: Get LLVM libraries id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -150,7 +150,7 @@ jobs: ] steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: download and install wasi-sdk if: matrix.test_option == '$WASI_TEST_OPTIONS' diff --git a/.github/workflows/compilation_on_zephyr.yml b/.github/workflows/compilation_on_zephyr.yml index 26abb90106..a01ef33bd9 100644 --- a/.github/workflows/compilation_on_zephyr.yml +++ b/.github/workflows/compilation_on_zephyr.yml @@ -106,7 +106,7 @@ jobs: # └─── application/ --> DUMMY. keep west_lite.yml here - name: Checkout code - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 with: path: modules/wasm-micro-runtime diff --git a/.github/workflows/create_tag.yml b/.github/workflows/create_tag.yml index 2ca4e8283a..5091616fa1 100644 --- a/.github/workflows/create_tag.yml +++ b/.github/workflows/create_tag.yml @@ -29,7 +29,7 @@ jobs: contents: write # create and push tags steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 # Full git history is needed to get a proper list of commits and tags with: fetch-depth: 0 diff --git a/.github/workflows/hadolint_dockerfiles.yml b/.github/workflows/hadolint_dockerfiles.yml index 8f4051f86b..c36ebace41 100644 --- a/.github/workflows/hadolint_dockerfiles.yml +++ b/.github/workflows/hadolint_dockerfiles.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 # on default, hadolint will fail on warnings and errors - name: Run hadolint on dockerfiles diff --git a/.github/workflows/nightly_run.yml b/.github/workflows/nightly_run.yml index 15b58f7d5d..7c24c792ec 100644 --- a/.github/workflows/nightly_run.yml +++ b/.github/workflows/nightly_run.yml @@ -67,13 +67,13 @@ jobs: llvm_cache_key: ${{ needs.build_llvm_libraries_on_ubuntu.outputs.cache_key }} steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 # since jobs.id can't contain the dot character # it is hard to use `format` to assemble the cache key - name: Get LLVM libraries id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -235,13 +235,13 @@ jobs: steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 # only download llvm cache when needed - name: Get LLVM libraries id: retrieve_llvm_libs if: endsWith(matrix.make_options_run_mode, '_JIT_BUILD_OPTIONS') - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -286,13 +286,13 @@ jobs: steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 with: submodules: recursive - name: Get LLVM libraries id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -470,12 +470,12 @@ jobs: steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: Get LLVM libraries id: retrieve_llvm_libs if: (!endsWith(matrix.make_options, '_INTERP_BUILD_OPTIONS')) - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -523,7 +523,7 @@ jobs: llvm_cache_key: ${{ needs.build_llvm_libraries_on_ubuntu.outputs.cache_key }} steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: install-wasi-sdk-wabt uses: ./.github/actions/install-wasi-sdk-wabt @@ -532,7 +532,7 @@ jobs: - name: Get LLVM libraries id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin @@ -725,7 +725,7 @@ jobs: sanitizer: ubsan steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: install-wasi-sdk-wabt if: matrix.test_option == '$WASI_TEST_OPTIONS' @@ -754,7 +754,7 @@ jobs: - name: Get LLVM libraries if: env.USE_LLVM == 'true' id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin diff --git a/.github/workflows/release_process.yml b/.github/workflows/release_process.yml index b7eebd1c52..a30bb83a7b 100644 --- a/.github/workflows/release_process.yml +++ b/.github/workflows/release_process.yml @@ -55,7 +55,7 @@ jobs: outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 - name: prepare the release note run: | diff --git a/.github/workflows/reuse_latest_release_binaries.yml b/.github/workflows/reuse_latest_release_binaries.yml index 3bbd4ac9bf..32cc73bffb 100644 --- a/.github/workflows/reuse_latest_release_binaries.yml +++ b/.github/workflows/reuse_latest_release_binaries.yml @@ -34,7 +34,7 @@ jobs: contents: write # for creating realease and uploading release artifacts steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 # Full git history is needed to get a proper list of commits and tags with: fetch-depth: 0 diff --git a/.github/workflows/spec_test_on_nuttx.yml b/.github/workflows/spec_test_on_nuttx.yml index baf4fb3e11..25d3d9c5c0 100644 --- a/.github/workflows/spec_test_on_nuttx.yml +++ b/.github/workflows/spec_test_on_nuttx.yml @@ -143,21 +143,21 @@ jobs: # Note: we use an unreleased version nuttx for xtensa because # 12.4 doesn't contain necessary esp32s3 changes. - name: Checkout NuttX - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 with: repository: apache/nuttx ref: ${{ matrix.target_config.target == 'xtensa' && '985d395b025cf2012b22f6bb4461959fa6d87645' || '09a71ec7c16c43398d5acbdcbeee7b08736c3170' }} path: nuttx - name: Checkout NuttX Apps - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 with: repository: apache/nuttx-apps ref: ${{ matrix.target_config.target == 'xtensa' && '2ef3eb25c0cec944b13792185f7e5d5a05990d5f' || '6bd593459c4af3cef325c3d22bccd5537a8ed755' }} path: apps - name: Checkout WAMR - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 with: repository: ${{ github.repository }} path: apps/interpreters/wamr/wamr @@ -165,7 +165,7 @@ jobs: - name: Get LLVM libraries if: contains(matrix.wamr_test_option.mode, 'aot') id: retrieve_llvm_libs - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | ./core/deps/llvm/build/bin diff --git a/.github/workflows/supply_chain.yml b/.github/workflows/supply_chain.yml index 5efdc62c3d..efa24f26ba 100644 --- a/.github/workflows/supply_chain.yml +++ b/.github/workflows/supply_chain.yml @@ -34,7 +34,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v3.1.0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v3.1.0 with: persist-credentials: false @@ -60,6 +60,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@0e150e40762c1253b364a04b0fc9f2cc14effff2 + uses: github/codeql-action/upload-sarif@3cf0a529d8434171b6af190714e8d5b7abb83927 with: sarif_file: results.sarif diff --git a/.github/workflows/wamr_wasi_extensions.yml b/.github/workflows/wamr_wasi_extensions.yml index 0ef0b5b123..b16bf0399c 100644 --- a/.github/workflows/wamr_wasi_extensions.yml +++ b/.github/workflows/wamr_wasi_extensions.yml @@ -30,7 +30,7 @@ jobs: os: [ubuntu-22.04, macos-15-intel, macos-15] steps: - name: checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v7.0.0 - name: install-wasi-sdk-wabt uses: ./.github/actions/install-wasi-sdk-wabt diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..e130590084 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,137 @@ +# AGENTS.md — WAMR fork: WASIp2 / component-model + Apple integration + +Dev guide for the `rebeckerspecialties/wasm-micro-runtime` fork's WASI-Preview-2 +work. This lineage is SEPARATE from the fork's `feat/legacy-eh-*` and +`feat/relaxed-simd-*` branches (those rebase on upstream `cd390ea0`). The +parent benchmark repo's `AGENTS.md` (`../AGENTS.md` when this is a submodule) +has the cross-runtime / Apple-app context. + +## Branch & PR map + +All branch from the airbus fork's `dev/cm_wasip2_complete @ 2815b698` (pulled in +as the base; it adds component-model + WASIp2 + the WAVE text parser). Airbus +contact: Mihai Dimoiu (component-model author). + +- **#10 `feat/wasip2-apple-port`** — ports the Linux-only WASIp2 host layer + (`core/iwasm/libraries/libc-wasi-p2/`) to Apple platforms, all `#if + defined(__APPLE__)`: kqueue+EVFILT_TIMER (vs timerfd), getentropy (vs + getrandom), openat+O_NOFOLLOW_ANY (vs openat2/RESOLVE_BENEATH), st_*timespec, + fsync, posix_fadvise no-op, SO_NOSIGPIPE, socket+fcntl (vs SOCK_CLOEXEC/ + accept4/pipe2), TCP_KEEPALIVE, local recvmsg/sendmsg loops (vs recvmmsg), + IOV_MAX, SO_SNDLOWAT permit. PLUS the base-CI fixes below. +- **#9 `integration/cm-wasip2-all`** — the integration PR: #10 + #8 + #7 + + relaxed-SIMD + legacy-EH + fuzz, all merged. Keep this branch carrying every + fix (sync after each change to #10). +- **#8 `feat/component-conformance-traps`** — canonical-ABI trap coverage + + ptr+len overflow fix (`wasm_component_canonical.c`, 3 sites widen operands + before add) + `tests/unit/canonical-abi/test_canonical_abi_traps.cc`. +- **#7 `feat/component-parser-fuzz`** — `tests/fuzz/component-fuzz/` libFuzzer + target + 4 memory-safety fixes. + +## Base-CI regressions FIXED vs upstream (don't re-investigate) + +The airbus base is RED on its own CI; these were all green on +`bytecodealliance` upstream but red on the base. Each verified by reading the CI +job log + comparing the same-named job on upstream `main`. **Method that worked: +`gh run view --job --log`, then diff the suspect function vs +`gh api .../contents/ | base64 -d`.** + +1. **`product-mini/.../main.c` `error_buf` truncation** — `execute_wasm_module` + took `char *error_buf` and passed `sizeof(error_buf)` (=8) to + `wasm_runtime_load`/`instantiate`; trap messages truncated to "WASM mo", so + the spec-test harness never matched expected trap text → data/elem/start + failed in ALL interp/jit modes + ba-issues regression. Fixed by threading a + real `error_buf_size` param. THE spec-test killer. +2. **main.c AoT CLI dispatch gap** — the base's new `wasm_decode_header` + + `is_wasm_module`/`is_wasm_component` dispatch had NO AoT branch → `iwasm + foo.aot` → "Unknown WASM file type" exit 255. Fixed: `|| get_package_type(...) + == Wasm_Module_AoT`. Cleared wasi-threads/debug-tools samples + ba-issues AoT. +3. **main.c unconditional `#include "wasm.h"`** — broke all AOT-only (INTERP=0) + builds with "wasm.h file not found". Fixed: move inside the COMPONENT_MODEL + guard (it's only needed there). +4. **main.c instantiation print** — used `LOG_ERROR` (bh_log "[time-tid]:" + banner pollutes spec-test stdout); changed to `printf` to match upstream. +5. **`core/iwasm/common/wave-parser/wave_parser.cmake`** — passed bison + `--feature=caret` (Bison ≥2.6) unconditionally; gated on `BISON_VERSION`. + ALSO `wave_parser.y` uses `%code requires` (Bison 2.4+) in the GRAMMAR source + — macOS runner ships Bison 2.3, so `compilation_on_macos.yml`'s + `build_samples_wasm_c_api` job gets a `brew install bison` + PATH step. +6. **`tests/unit/{linear-memory-aot,runtime-common}/build_aot.sh`** — used + `command -v wat2wasm`; CI installs WABT to `/opt/wabt/bin` (not on PATH). + Fixed: prefer `/opt/wabt/bin/wat2wasm`, fall back to PATH (matches upstream). + +## CI state & gates + +- **June 18 head: 454 passed, 4 failed, 1 cancelled.** Three failing AOT jobs + share the `align.wast` "stack size does not match block type" failure. The + X86_32 unit job is missing generated WASIp2 component fixtures, and the + X86_64 unit job was cancelled after the canonical UDP server test ran for six + hours. macOS, Windows, SGX, Zephyr, NuttX, and coding-guideline jobs passed. + Treat these as branch CI debt, not as infrastructure failures. +- **Gates**: `compilation` workflows (`-Werror`), `Coding Guidelines` + (`ci/coding_guidelines_check.py` runs `git-clang-format-14 --diff` — only + CHANGED lines, so pre-existing violations don't count; format with + `/opt/homebrew/opt/llvm@14/bin/clang-format -i --style=file`). `pull_request` + fires CI; matrices use `fail-fast` (one failure cancels siblings → fix the + earliest real failure first). +- **Gate decision (user, 2026-06-14)**: interpreter-only green is enough; ignore + pre-existing AOT-path base failures the interpreter app doesn't exercise. + +## Building on macOS + +```sh +# wasip2 + Apple host port (component model + WASIp2): +export PATH="/opt/homebrew/opt/bison/bin:/opt/homebrew/opt/flex/bin:$PATH" +CC=/opt/homebrew/opt/llvm/bin/clang CXX=/opt/homebrew/opt/llvm/bin/clang++ \ + cmake -S product-mini/platforms/darwin -B build \ + -DWAMR_BUILD_INTERP=1 -DWAMR_BUILD_FAST_INTERP=1 -DWAMR_BUILD_AOT=0 \ + -DWAMR_BUILD_COMPONENT_MODEL=1 -DWAMR_BUILD_LIBC_WASI=1 -DWAMR_BUILD_LIBC_BUILTIN=1 +cmake --build build -j8 # -> build/iwasm (2.4.3), build/libiwasm.a +``` + +Gotchas: the `darwin` platform CMakeLists does NOT default COMPONENT_MODEL on +(unlike top-level/linux); `WASM_ENABLE_COMPONENT_MODEL` defaults 0 in +`core/config.h`. `SIMD + CLASSIC_INTERP` and `MEMORY64 + FAST_INTERP` are +rejected by `build-scripts/unsupported_combination.cmake` — use FAST_INTERP +with SIMD, or disable SIMD for classic. The component model requires an +interpreter (calls `wasm_instantiate`); pure AOT-only + component can't link. + +## App Store core-wasm embedding profile + +CapsHost is core wasm with custom preview2-style imports. It is not component +execution, so its first WAMR build keeps `WAMR_BUILD_COMPONENT_MODEL`, +`WAMR_BUILD_LIBC_WASI`, and `WAMR_BUILD_LIBC_BUILTIN` off. The production shape +is a static fast-interpreter archive with multi-module, SIMD/relaxed-SIMD, +legacy-EH parsing, custom names, and call stacks enabled; all AOT/JIT/compiler +paths stay off. Enable `WAMR_BUILD_THREAD_MGR=1` solely so asynchronous +termination is observed inside pure-Wasm loops; keep guest pthreads, WASI +threads, and shared memory disabled. + +Set `WAMR_DISABLE_HW_BOUND_CHECK=1` on every Apple target. This keeps software +linear-memory checks while avoiding WAMR's process-global SIGSEGV/SIGBUS +handlers; `sigaltstack` is unavailable on tvOS/watchOS. Non-executable macOS +mappings must not carry `MAP_JIT`, so this profile does not need the hardened +runtime JIT entitlement. Also set `WAMR_DISABLE_WAKEUP_BLOCKING_OP=1`; CapsHost +owns cancellation/wakeup for its native asynchronous imports. + +`.github/workflows/apple_app_store_interpreter.yml` cross-builds that exact +archive for macOS arm64, iOS arm64, tvOS arm64, watchOS arm64_32, and visionOS +arm64 under `-Werror`. It rejects AOT/JIT/compiler objects and runs a hardened +macOS embedding smoke that covers runtime initialization, a native import with +an attachment, module load/instantiate, an exported function call, and +asynchronous termination of an infinite guest loop. + +WAMR's multi-module reader and module registry are process-global and keyed only +by logical module name. Until WAMR gains scoped resolver contexts and graph +teardown, use one serialized WAMR session per active guest root: retain every +root/worklet wasm buffer, load all worklets from the same immutable namespace, +join/deinstantiate them before `wasm_runtime_destroy()`, and reject a logical +dependency name that maps to different bytes. Put host state on explicit exec +environment user data; submodule instance custom data is not inherited. + +## Constraints + +- App-Store target is **interpreter-only**: no JIT / AOT / MAP_JIT anywhere. +- Patch-stack discipline: tidy, well-named commits for upstreaming. +- **Do NOT mention Claude or Anthropic** in commit messages, PR text, or branch + names. diff --git a/build-scripts/config_common.cmake b/build-scripts/config_common.cmake index 2bbe3950f3..53d83e36d1 100644 --- a/build-scripts/config_common.cmake +++ b/build-scripts/config_common.cmake @@ -243,6 +243,15 @@ if (NOT DEFINED WAMR_BUILD_EXCE_HANDLING) set (WAMR_BUILD_EXCE_HANDLING 0) endif () +if (NOT DEFINED WAMR_BUILD_RELAXED_SIMD) + # Relaxed-SIMD (wasm 2.0 extension) — off by default, mirrors the + # dormant `WASM_FEATURE_RELAXED_SIMD` bit at `aot_runtime.h:32`. + # Enable via `-DWAMR_BUILD_RELAXED_SIMD=1` at cmake time; the + # cmake block in this file then defines `WASM_ENABLE_RELAXED_SIMD` + # for the C compiler. + set (WAMR_BUILD_RELAXED_SIMD 0) +endif () + if (NOT DEFINED WAMR_BUILD_GC) set (WAMR_BUILD_GC 0) endif () @@ -369,6 +378,11 @@ elseif (WAMR_BUILD_LIBC_WASI EQUAL 1) else () message (" Libc WASI disabled") endif () +if (WAMR_BUILD_LIBC_WASI_P2 EQUAL 1) + message (" Libc WASI Preview 2 enabled") +else () + message (" Libc WASI Preview 2 disabled") +endif () if (WAMR_BUILD_THREAD_MGR EQUAL 1) message (" Thread manager enabled") endif () @@ -470,6 +484,49 @@ if (WAMR_BUILD_SIMD EQUAL 1) endif () add_definitions(-DWASM_ENABLE_SIMD=${SIMD_ENABLED}) endif () +if (WAMR_BUILD_RELAXED_SIMD EQUAL 1) + # Relaxed-SIMD is a strict superset of SIMD — fail fast if the + # caller forgot to also turn on the base feature, otherwise the + # interpreter sees a relaxed sub-opcode it can dispatch but the + # surrounding SIMD machinery (frame_lp v128 cells, simde + # intrinsics) is compiled out and we'd link against undefined + # symbols. + if (NOT WAMR_BUILD_SIMD EQUAL 1) + message (FATAL_ERROR + "WAMR_BUILD_RELAXED_SIMD=1 requires WAMR_BUILD_SIMD=1") + endif () + # Scope is fast-interp only for now. The shared loader + # `prepare_bytecode` accepts the new opcodes when this flag is + # set, but the AOT / JIT / wamrc compilation paths in + # `core/iwasm/compilation/aot_compiler.c:1494, 2463, 2639, 2799` + # all truncate the SIMD sub-opcode to `uint8` (`opcode = + # (uint8)opcode1`). Sub-opcodes 0x100..0x113 would silently + # alias into `SIMD_v128_load` / `SIMD_v128_load8x8_s` / ... + # causing garbage memarg reads at codegen time. Reject the + # combination at configure time rather than silently + # mis-compile. + if (NOT WAMR_BUILD_FAST_INTERP EQUAL 1) + message (FATAL_ERROR + "WAMR_BUILD_RELAXED_SIMD=1 requires WAMR_BUILD_FAST_INTERP=1 " + "(the relaxed-SIMD dispatch + SIMDe glue lives only in the " + "fast-interp path; classic-interp doesn't ship a SIMD switch)") + endif () + if (WAMR_BUILD_AOT EQUAL 1 OR WAMR_BUILD_JIT EQUAL 1 + OR WAMR_BUILD_WAMR_COMPILER EQUAL 1 + OR WAMR_BUILD_FAST_JIT EQUAL 1) + message (FATAL_ERROR + "WAMR_BUILD_RELAXED_SIMD=1 cannot be combined with " + "WAMR_BUILD_AOT / WAMR_BUILD_JIT / WAMR_BUILD_FAST_JIT / " + "WAMR_BUILD_WAMR_COMPILER today — those pipelines truncate " + "the SIMD sub-opcode to uint8 (see aot_compiler.c) and " + "would silently mis-compile relaxed-SIMD opcodes " + "0x100..0x113 as legacy v128_load/store variants. Build " + "fast-interp-only to use relaxed-SIMD until the AOT/JIT " + "pipelines learn the wider sub-opcode range.") + endif () + add_definitions (-DWASM_ENABLE_RELAXED_SIMD=1) + message (" Relaxed SIMD enabled") +endif () if (WAMR_BUILD_AOT_STACK_FRAME EQUAL 1) add_definitions (-DWASM_ENABLE_AOT_STACK_FRAME=1) message (" AOT stack frame enabled") @@ -816,6 +873,7 @@ message ( " \"Multiple Memories\" via WAMR_BUILD_MULTI_MEMORY: ${WAMR_BUILD_MULTI_MEMORY}\n" " \"Reference Types\" via WAMR_BUILD_REF_TYPES: ${WAMR_BUILD_REF_TYPES}\n" " \"Reference-Typed Strings\" via WAMR_BUILD_STRINGREF: ${WAMR_BUILD_STRINGREF}\n" +" \"Relaxed SIMD\" via WAMR_BUILD_RELAXED_SIMD: ${WAMR_BUILD_RELAXED_SIMD}\n" " \"Tail Call\" via WAMR_BUILD_TAIL_CALL: ${WAMR_BUILD_TAIL_CALL}\n" " \"Threads\" via WAMR_BUILD_SHARED_MEMORY: ${WAMR_BUILD_SHARED_MEMORY}\n" " \"Typed Function References\" via WAMR_BUILD_GC: ${WAMR_BUILD_GC}\n" diff --git a/build-scripts/runtime_lib.cmake b/build-scripts/runtime_lib.cmake index 4eba5ba2dc..1c36ac42c0 100644 --- a/build-scripts/runtime_lib.cmake +++ b/build-scripts/runtime_lib.cmake @@ -19,6 +19,31 @@ if (NOT DEFINED SHARED_PLATFORM_CONFIG) set (SHARED_PLATFORM_CONFIG ${SHARED_DIR}/platform/${WAMR_BUILD_PLATFORM}/shared_platform.cmake) endif () +# Preview 2 used to be pulled in implicitly with the Preview 1 libc-wasi +# implementation. Keep that behavior for component-model builds unless the +# embedding application makes an explicit choice, while allowing Preview 2 to +# be enabled without exposing Preview 1 imports. +if (NOT DEFINED WAMR_BUILD_LIBC_WASI_P2) + if (WAMR_BUILD_COMPONENT_MODEL EQUAL 1 + AND WAMR_BUILD_LIBC_WASI EQUAL 1) + set (WAMR_BUILD_LIBC_WASI_P2 1) + else () + set (WAMR_BUILD_LIBC_WASI_P2 0) + endif () +endif () + +if (WAMR_BUILD_LIBC_WASI_P2 EQUAL 1 + AND NOT WAMR_BUILD_COMPONENT_MODEL EQUAL 1) + message (FATAL_ERROR + "WAMR_BUILD_LIBC_WASI_P2 requires WAMR_BUILD_COMPONENT_MODEL") +endif () + +if (WAMR_BUILD_LIBC_WASI_P2 EQUAL 1 + AND WAMR_BUILD_LIBC_UVWASI EQUAL 1) + message (FATAL_ERROR + "WAMR_BUILD_LIBC_WASI_P2 does not support the uvwasi backend") +endif () + if (DEFINED EXTRA_SDK_INCLUDE_PATH) message(STATUS, "EXTRA_SDK_INCLUDE_PATH = ${EXTRA_SDK_INCLUDE_PATH} ") include_directories ( @@ -100,9 +125,11 @@ if (WAMR_BUILD_LIBC_UVWASI EQUAL 1) set (WAMR_BUILD_MODULE_INST_CONTEXT 1) elseif (WAMR_BUILD_LIBC_WASI EQUAL 1) include (${IWASM_DIR}/libraries/libc-wasi/libc_wasi.cmake) - if (WAMR_BUILD_COMPONENT_MODEL EQUAL 1) - include (${IWASM_DIR}/libraries/libc-wasi-p2/libc_wasi_p2.cmake) - endif () + set (WAMR_BUILD_MODULE_INST_CONTEXT 1) +endif () + +if (WAMR_BUILD_LIBC_WASI_P2 EQUAL 1) + include (${IWASM_DIR}/libraries/libc-wasi-p2/libc_wasi_p2.cmake) set (WAMR_BUILD_MODULE_INST_CONTEXT 1) endif () @@ -169,7 +196,12 @@ endif () ####################### Common sources ####################### if (NOT MSVC) - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -ffunction-sections -fdata-sections") + if (WAMR_BUILD_COMPONENT_MODEL EQUAL 1) + set (WAMR_C_LANGUAGE_STANDARD "gnu11") + else () + set (WAMR_C_LANGUAGE_STANDARD "gnu99") + endif () + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=${WAMR_C_LANGUAGE_STANDARD} -ffunction-sections -fdata-sections") endif () # include the build config template file diff --git a/build-scripts/unsupported_combination.cmake b/build-scripts/unsupported_combination.cmake index 4284be32bf..1789c71b52 100644 --- a/build-scripts/unsupported_combination.cmake +++ b/build-scripts/unsupported_combination.cmake @@ -64,7 +64,17 @@ endfunction() if(WAMR_BUILD_EXCE_HANDLING EQUAL 1) check_aot_mode_error("Unsupported build configuration: EXCE_HANDLING + AOT") - check_fast_interp_error("Unsupported build configuration: EXCE_HANDLING + FAST_INTERP") + # FAST_INTERP + EXCE_HANDLING is supported for *throw-only* shapes: + # WASM modules that declare tags and execute throw / rethrow without + # ever entering a same-function try / catch handler. The throw + # propagates to the caller via the existing got_exception bailout + # path, exactly like any other trap. This covers Porffor (its + # JS-to-wasm compiler emits 0 try/catch handlers; every JS throw + # escapes to the host). Modules that contain WASM_OP_TRY / CATCH / + # CATCH_ALL / DELEGATE still load, but those handlers report + # "unsupported opcode" at runtime — see the WASM_OP_TRY handler in + # core/iwasm/interpreter/wasm_interp_fast.c. Full same-function + # try / catch lowering is the natural follow-up. check_fast_jit_error("Unsupported build configuration: EXCE_HANDLING + FAST_JIT") check_llvm_jit_error("Unsupported build configuration: EXCE_HANDLING + JIT") endif() diff --git a/core/config.h b/core/config.h index a19095c31e..81770360bc 100644 --- a/core/config.h +++ b/core/config.h @@ -149,6 +149,10 @@ #define WASM_ENABLE_LIBC_WASI 0 #endif +#ifndef WASM_ENABLE_LIBC_WASI_P2 +#define WASM_ENABLE_LIBC_WASI_P2 0 +#endif + #ifndef WASM_ENABLE_UVWASI #define WASM_ENABLE_UVWASI 0 #endif @@ -336,6 +340,17 @@ unless used elsewhere */ #define WASM_ENABLE_SIMDE 0 #endif +/* Disable relaxed-SIMD (wasm 2.0 extension — 20 new opcodes at + * 0x100..0x113 under the existing 0xfd prefix) unless manually + * enabled. The fast-interp path under `WAMR_BUILD_RELAXED_SIMD=1` + * widens the SIMD sub-opcode IR encoding from 1 byte to 2 bytes + * and wires SIMDe relaxed intrinsics into the SIMD-prefix switch; + * AOT/JIT codegen does NOT yet recognize the wider range, so the + * cmake gate forbids enabling this flag with AOT/JIT/WAMR_COMPILER. */ +#ifndef WASM_ENABLE_RELAXED_SIMD +#define WASM_ENABLE_RELAXED_SIMD 0 +#endif + /* GC performance profiling */ #ifndef WASM_ENABLE_GC_PERF_PROFILING #define WASM_ENABLE_GC_PERF_PROFILING 0 @@ -696,9 +711,9 @@ unless used elsewhere */ #define WASM_ENABLE_MEMORY64 0 #endif -/* Enable multi-memory by default */ +/* Enable multi-memory by default for component-model builds only */ #ifndef WASM_ENABLE_MULTI_MEMORY -#define WASM_ENABLE_MULTI_MEMORY 1 +#define WASM_ENABLE_MULTI_MEMORY WASM_ENABLE_COMPONENT_MODEL #endif #ifndef WASM_TABLE_MAX_SIZE diff --git a/core/iwasm/aot/aot_loader.c b/core/iwasm/aot/aot_loader.c index 121708d669..7d30c5e58f 100644 --- a/core/iwasm/aot/aot_loader.c +++ b/core/iwasm/aot/aot_loader.c @@ -4226,9 +4226,9 @@ create_module(char *name, char *error_buf, uint32 error_buf_size) } #endif -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 wasi_args_set_defaults(&module->wasi_args); -#endif /* WASM_ENABLE_LIBC_WASI != 0 */ +#endif /* WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 */ return module; #if WASM_ENABLE_GC != 0 diff --git a/core/iwasm/aot/aot_runtime.c b/core/iwasm/aot/aot_runtime.c index 4fbaa1e90d..5afa0ee6cf 100644 --- a/core/iwasm/aot/aot_runtime.c +++ b/core/iwasm/aot/aot_runtime.c @@ -53,14 +53,15 @@ bh_static_assert(offsetof(AOTModuleInstance, c_api_func_imports) #if WASM_ENABLE_COMPONENT_MODEL != 0 bh_static_assert(offsetof(AOTModuleInstance, global_table_data) == 13 * sizeof(uint64) + 128 + 14 * sizeof(uint64) - + sizeof(void *) + sizeof(uint64)); + + 3 * sizeof(uint64)); +bh_static_assert(sizeof(AOTMemoryInstance) == 128); +bh_static_assert(offsetof(AOTTableInstance, elems) == 40); #else bh_static_assert(offsetof(AOTModuleInstance, global_table_data) == 13 * sizeof(uint64) + 128 + 14 * sizeof(uint64)); -#endif - bh_static_assert(sizeof(AOTMemoryInstance) == 120); bh_static_assert(offsetof(AOTTableInstance, elems) == 24); +#endif bh_static_assert(offsetof(AOTModuleInstanceExtra, stack_sizes) == 0); bh_static_assert(offsetof(AOTModuleInstanceExtra, shared_heap_base_addr_adj) diff --git a/core/iwasm/aot/aot_runtime.h b/core/iwasm/aot/aot_runtime.h index 687c75e142..82ed039609 100644 --- a/core/iwasm/aot/aot_runtime.h +++ b/core/iwasm/aot/aot_runtime.h @@ -300,7 +300,7 @@ typedef struct AOTModule { /* is indirect mode or not */ bool is_indirect_mode; -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 WASIArguments wasi_args; bool import_wasi_api; #endif diff --git a/core/iwasm/common/component-model/wasm_component.c b/core/iwasm/common/component-model/wasm_component.c index 15cec31892..db35076b43 100644 --- a/core/iwasm/common/component-model/wasm_component.c +++ b/core/iwasm/common/component-model/wasm_component.c @@ -11,539 +11,272 @@ #include "wasm_loader_common.h" #include "wasm_runtime_common.h" #include "wasm_export.h" +#include "wasm_component_validate.h" #include -// Parse all sections in a WASM component binary -// Each section is dispatched to its own parser and stored in the output -// structure +/* + * Install each section wrapper in the component before parsing its payload. + * Section parsers fill zero-initialized structures incrementally, so this + * makes the normal component destructor the single owner of partial results. + */ +#define ALLOC_SECTION_WRAPPER(section, member, type, label) \ + do { \ + type *wrapper = wasm_runtime_malloc(sizeof(type)); \ + if (!wrapper) { \ + set_error_buf_ex(error_buf, error_buf_size, \ + "Failed to allocate %s section", label); \ + goto fail; \ + } \ + memset(wrapper, 0, sizeof(type)); \ + (section)->parsed.member = wrapper; \ + } while (0) + bool -wasm_component_parse_sections(const uint8_t *buf, uint32_t size, - WASMComponent *out_component, LoadArgs *args, - unsigned int depth) +wasm_component_parse_sections_ex(const uint8_t *buf, uint32_t size, + WASMComponent *out_component, LoadArgs *args, + unsigned int depth, char *error_buf, + uint32_t error_buf_size) { + const uint8_t *p, *end; + WASMComponentSection *sections; + uint32_t section_capacity = 8; + if (!buf || size < 8 || !out_component) { + set_error_buf_ex(error_buf, error_buf_size, + "Invalid component buffer or output pointer"); return false; } - - // Decode the header first - if (!wasm_decode_header(buf, size, &out_component->header)) { + if (out_component->sections || out_component->section_count) { + set_error_buf_ex(error_buf, error_buf_size, + "Component output is already initialized"); + return false; + } + if (!wasm_decode_header(buf, size, &out_component->header) + || !is_wasm_component(out_component->header)) { + set_error_buf_ex(error_buf, error_buf_size, + "Invalid WebAssembly component header"); return false; } - // const uint8_t *begin = buf; - const uint8_t *p = buf + 8; - const uint8_t *end = buf + size; - uint32_t section_capacity = 8; - - WASMComponentSection *sections = - wasm_runtime_malloc(section_capacity * sizeof(WASMComponentSection)); + p = buf + 8; + end = buf + size; + sections = wasm_runtime_malloc(section_capacity * sizeof(*sections)); if (!sections) { + set_error_buf_ex(error_buf, error_buf_size, + "Failed to allocate component section table"); return false; } + memset(sections, 0, section_capacity * sizeof(*sections)); + out_component->sections = sections; - uint32_t section_count = 0; while (p < end) { - // Read section id (1 byte) and payload length (LEB128) - uint8_t section_id = *p++; - char error_buf[128] = { 0 }; - - // Read payload length + WASMComponentSection *section; + const uint8_t *payload_start; uint64 payload_len = 0; - if (!read_leb((uint8_t **)&p, end, 32, false, &payload_len, error_buf, - sizeof(error_buf))) { - wasm_runtime_free(sections); - return false; // Error handling + uint32_t consumed_len = 0; + uint8_t section_id = *p++; + bool parse_success = false; + char section_error[256] = { 0 }; + + if (!read_leb((uint8_t **)&p, end, 32, false, &payload_len, + section_error, sizeof(section_error))) { + set_error_buf_ex(error_buf, error_buf_size, + "Failed to read component section %u size: %s", + section_id, section_error); + goto fail; } - - if ((uint32_t)(end - p) < payload_len) { - wasm_runtime_free(sections); - return false; + if (payload_len > (uint64)(end - p)) { + set_error_buf_ex(error_buf, error_buf_size, + "Component section %u payload exceeds input", + section_id); + goto fail; } - // Store section info - if (section_count == section_capacity) { + if (out_component->section_count == section_capacity) { + WASMComponentSection *new_sections; + uint32_t old_capacity = section_capacity; + uint64_t byte_count; + + if (section_capacity > UINT32_MAX / 2) { + set_error_buf_ex(error_buf, error_buf_size, + "Component has too many sections"); + goto fail; + } section_capacity *= 2; - WASMComponentSection *new_sections = wasm_runtime_realloc( - sections, section_capacity * sizeof(WASMComponentSection)); + byte_count = (uint64_t)section_capacity * sizeof(*sections); + if (byte_count > UINT32_MAX) { + set_error_buf_ex(error_buf, error_buf_size, + "Component section table is too large"); + goto fail; + } + new_sections = wasm_runtime_realloc(sections, (uint32_t)byte_count); if (!new_sections) { - wasm_runtime_free(sections); - return false; + set_error_buf_ex(error_buf, error_buf_size, + "Failed to grow component section table"); + goto fail; } sections = new_sections; + memset(sections + old_capacity, 0, + (section_capacity - old_capacity) * sizeof(*sections)); + out_component->sections = sections; } - const uint8_t *payload_start = p; - uint32_t current_section_index = section_count; - sections[current_section_index].payload = payload_start; - sections[current_section_index].payload_len = (uint32_t)payload_len; - sections[current_section_index].id = section_id; - sections[current_section_index].parsed.any = - NULL; // Initialize parsed union to NULL - section_count++; - - uint32_t consumed_len = 0; - bool parse_success = false; + payload_start = p; + section = §ions[out_component->section_count++]; + memset(section, 0, sizeof(*section)); + section->id = section_id; + section->payload = payload_start; + section->payload_len = (uint32_t)payload_len; - // LOG_DEBUG("Parsing section: %d | Section size: %d | payload_start: - // %d\n", section_id, payload_len, payload_start-begin); switch (section_id) { - // Section 0: custom section case WASM_COMP_SECTION_CORE_CUSTOM: - { - // Parse custom section (name + data) - WASMComponentCoreCustomSection *custom = - wasm_runtime_malloc(sizeof(WASMComponentCoreCustomSection)); - if (custom) - memset(custom, 0, sizeof(WASMComponentCoreCustomSection)); - + ALLOC_SECTION_WRAPPER(section, core_custom, + WASMComponentCoreCustomSection, "custom"); parse_success = wasm_component_parse_core_custom_section( - &p, (uint32_t)payload_len, custom, error_buf, - sizeof(error_buf), &consumed_len); - if (!parse_success || consumed_len != payload_len) { - if (custom) { - wasm_runtime_free(custom); - } - if (error_buf[0]) { - LOG_DEBUG("Error parsing custom section: %s\n", - error_buf); - } - if (consumed_len != payload_len) { - LOG_DEBUG("FATAL ERROR: Custom section consumed %u " - "bytes but expected %lu\n", - consumed_len, payload_len); - wasm_runtime_free(sections); - return false; - } - } - else { - sections[current_section_index].parsed.core_custom = custom; - } + &payload_start, (uint32_t)payload_len, + section->parsed.core_custom, section_error, + sizeof(section_error), &consumed_len); break; - } - // Section 1: module section case WASM_COMP_SECTION_CORE_MODULE: - { - // Parse and load the embedded core wasm module - WASMComponentCoreModuleWrapper *module = - wasm_runtime_malloc(sizeof(WASMComponentCoreModuleWrapper)); - if (module) - memset(module, 0, sizeof(WASMComponentCoreModuleWrapper)); + ALLOC_SECTION_WRAPPER(section, core_module, + WASMComponentCoreModuleWrapper, + "core module"); parse_success = wasm_component_parse_core_module_section( - &payload_start, (uint32_t)payload_len, module, args, - error_buf, sizeof(error_buf), &consumed_len); - if (!parse_success || consumed_len != payload_len) { - if (module) { - wasm_runtime_free(module); - } - if (error_buf[0]) { - LOG_DEBUG("Error parsing module section: %s\n", - error_buf); - } - if (consumed_len != payload_len) { - LOG_DEBUG("FATAL ERROR: Module section consumed %u " - "bytes but expected %lu\n", - consumed_len, payload_len); - wasm_runtime_free(sections); - return false; - } - } - else { - sections[current_section_index].parsed.core_module = module; - } + &payload_start, (uint32_t)payload_len, + section->parsed.core_module, args, section_error, + sizeof(section_error), &consumed_len); break; - } - // Section 2: instance section case WASM_COMP_SECTION_CORE_INSTANCE: - { - WASMComponentCoreInstSection *core_instance_section = - wasm_runtime_malloc(sizeof(WASMComponentCoreInstSection)); - if (core_instance_section) - memset(core_instance_section, 0, - sizeof(WASMComponentCoreInstSection)); + ALLOC_SECTION_WRAPPER(section, core_instance_section, + WASMComponentCoreInstSection, + "core instance"); parse_success = wasm_component_parse_core_instance_section( &payload_start, (uint32_t)payload_len, - core_instance_section, error_buf, sizeof(error_buf), - &consumed_len); - if (parse_success && consumed_len == payload_len) { - sections[current_section_index] - .parsed.core_instance_section = core_instance_section; - } - else { - if (core_instance_section) { - wasm_runtime_free(core_instance_section); - } - if (error_buf[0]) { - LOG_DEBUG("Error parsing core instances section: %s\n", - error_buf); - } - if (consumed_len != payload_len) { - LOG_DEBUG("FATAL ERROR: Core Instances section " - "consumed %u bytes but expected %lu\n", - consumed_len, payload_len); - wasm_runtime_free(sections); - return false; - } - } + section->parsed.core_instance_section, section_error, + sizeof(section_error), &consumed_len); break; - } - // Section 3: core types section case WASM_COMP_SECTION_CORE_TYPE: - { - WASMComponentCoreTypeSection *core_type_section = - wasm_runtime_malloc(sizeof(WASMComponentCoreTypeSection)); - if (core_type_section) - memset(core_type_section, 0, - sizeof(WASMComponentCoreTypeSection)); + ALLOC_SECTION_WRAPPER(section, core_type_section, + WASMComponentCoreTypeSection, + "core type"); parse_success = wasm_component_parse_core_type_section( - &payload_start, (uint32_t)payload_len, core_type_section, - error_buf, sizeof(error_buf), &consumed_len); - if (parse_success && consumed_len == payload_len) { - sections[current_section_index].parsed.core_type_section = - core_type_section; - } - else { - if (core_type_section) { - wasm_runtime_free(core_type_section); - } - if (error_buf[0]) { - LOG_DEBUG("Error parsing core types section: %s\n", - error_buf); - } - if (consumed_len != payload_len) { - LOG_DEBUG("FATAL ERROR: Core types section consumed %u " - "bytes but expected %lu\n", - consumed_len, payload_len); - wasm_runtime_free(sections); - return false; - } - } + &payload_start, (uint32_t)payload_len, + section->parsed.core_type_section, section_error, + sizeof(section_error), &consumed_len); break; - } - // Section 4: component section case WASM_COMP_SECTION_COMPONENT: - { - // Parse and load the embedded component - WASMComponent *component = - wasm_runtime_malloc(sizeof(WASMComponent)); - if (!component) { - LOG_DEBUG( - "Failed to allocate memory for nested component\n"); - wasm_runtime_free(sections); - return false; - } - // Initialize the component structure to avoid garbage data - memset(component, 0, sizeof(WASMComponent)); - - // Parse the nested component sections directly from the payload - parse_success = wasm_component_parse_sections( - payload_start, (uint32_t)payload_len, component, args, - depth + 1); - consumed_len = payload_len; // The entire payload is consumed - - if (!parse_success) { - LOG_DEBUG(" Failed to parse nested component, freeing " - "component at %p\n", - component); - wasm_runtime_free(component); - LOG_DEBUG("Error parsing sub component section\n"); - wasm_runtime_free(sections); - return false; - } - else { - LOG_DEBUG( - " Successfully parsed nested component at %p\n", - component); - sections[current_section_index].parsed.component = - component; - } + ALLOC_SECTION_WRAPPER(section, component, WASMComponent, + "nested component"); + parse_success = wasm_component_parse_component_section( + &payload_start, (uint32_t)payload_len, + section->parsed.component, section_error, + sizeof(section_error), args, depth, &consumed_len); break; - } - // Section 5: instances section case WASM_COMP_SECTION_INSTANCES: - { - WASMComponentInstSection *instance_section = - wasm_runtime_malloc(sizeof(WASMComponentInstSection)); - if (instance_section) - memset(instance_section, 0, - sizeof(WASMComponentInstSection)); + ALLOC_SECTION_WRAPPER(section, instance_section, + WASMComponentInstSection, "instance"); parse_success = wasm_component_parse_instances_section( - &payload_start, (uint32_t)payload_len, instance_section, - error_buf, sizeof(error_buf), &consumed_len); - if (parse_success && consumed_len == payload_len) { - sections[current_section_index].parsed.instance_section = - instance_section; - } - else { - if (instance_section) { - wasm_runtime_free(instance_section); - } - if (error_buf[0]) { - LOG_DEBUG("Error parsing instances section: %s\n", - error_buf); - } - if (consumed_len != payload_len) { - LOG_DEBUG("FATAL ERROR: Instances section consumed %u " - "bytes but expected %lu\n", - consumed_len, payload_len); - wasm_runtime_free(sections); - return false; - } - } + &payload_start, (uint32_t)payload_len, + section->parsed.instance_section, section_error, + sizeof(section_error), &consumed_len); break; - } - // Section 6: aliases section for imports/exports case WASM_COMP_SECTION_ALIASES: - { - // Parse alias definitions for imports/exports - WASMComponentAliasSection *alias_section = - wasm_runtime_malloc(sizeof(WASMComponentAliasSection)); - if (alias_section) - memset(alias_section, 0, sizeof(WASMComponentAliasSection)); + ALLOC_SECTION_WRAPPER(section, alias_section, + WASMComponentAliasSection, "alias"); parse_success = wasm_component_parse_alias_section( - &payload_start, (uint32_t)payload_len, alias_section, - error_buf, sizeof(error_buf), &consumed_len); - if (parse_success && consumed_len == payload_len) { - sections[current_section_index].parsed.alias_section = - alias_section; - } - else { - if (alias_section) { - wasm_runtime_free(alias_section); - } - if (error_buf[0]) { - LOG_DEBUG("Error parsing alias section: %s\n", - error_buf); - } - if (consumed_len != payload_len) { - LOG_DEBUG("FATAL ERROR: Alias section consumed %u " - "bytes but expected %lu\n", - consumed_len, payload_len); - wasm_runtime_free(sections); - return false; - } - } + &payload_start, (uint32_t)payload_len, + section->parsed.alias_section, section_error, + sizeof(section_error), &consumed_len); break; - } - // Section 7: types section case WASM_COMP_SECTION_TYPE: - { - WASMComponentTypeSection *type_section = - wasm_runtime_malloc(sizeof(WASMComponentTypeSection)); - if (type_section) - memset(type_section, 0, sizeof(WASMComponentTypeSection)); + ALLOC_SECTION_WRAPPER(section, type_section, + WASMComponentTypeSection, "type"); parse_success = wasm_component_parse_types_section( - &payload_start, (uint32_t)payload_len, type_section, - error_buf, sizeof(error_buf), &consumed_len); - if (parse_success && consumed_len == payload_len) { - sections[current_section_index].parsed.type_section = - type_section; - } - else { - if (type_section) { - wasm_runtime_free(type_section); - } - if (error_buf[0]) { - LOG_DEBUG("Error parsing types section: %s\n", - error_buf); - } - if (consumed_len != payload_len) { - LOG_DEBUG("FATAL ERROR: Types section consumed %u " - "bytes but expected %lu\n", - consumed_len, payload_len); - wasm_runtime_free(sections); - return false; - } - } + &payload_start, (uint32_t)payload_len, + section->parsed.type_section, section_error, + sizeof(section_error), &consumed_len); break; - } - // Section 8: canons section case WASM_COMP_SECTION_CANONS: - { - WASMComponentCanonSection *canon_section = - wasm_runtime_malloc(sizeof(WASMComponentCanonSection)); - if (canon_section) - memset(canon_section, 0, sizeof(WASMComponentCanonSection)); + ALLOC_SECTION_WRAPPER(section, canon_section, + WASMComponentCanonSection, "canon"); parse_success = wasm_component_parse_canons_section( - &payload_start, (uint32_t)payload_len, canon_section, - error_buf, sizeof(error_buf), &consumed_len); - if (parse_success && consumed_len == payload_len) { - sections[current_section_index].parsed.canon_section = - canon_section; - } - else { - if (canon_section) { - wasm_runtime_free(canon_section); - } - if (error_buf[0]) { - LOG_DEBUG("Error parsing canons section: %s\n", - error_buf); - } - if (consumed_len != payload_len) { - LOG_DEBUG("FATAL ERROR: Canons section consumed %u " - "bytes but expected %lu\n", - consumed_len, payload_len); - wasm_runtime_free(sections); - return false; - } - } + &payload_start, (uint32_t)payload_len, + section->parsed.canon_section, section_error, + sizeof(section_error), &consumed_len); break; - } - // Section 9: start section case WASM_COMP_SECTION_START: - { - WASMComponentStartSection *start_section = - wasm_runtime_malloc(sizeof(WASMComponentStartSection)); - if (start_section) - memset(start_section, 0, sizeof(WASMComponentStartSection)); + ALLOC_SECTION_WRAPPER(section, start_section, + WASMComponentStartSection, "start"); parse_success = wasm_component_parse_start_section( - &payload_start, (uint32_t)payload_len, start_section, - error_buf, sizeof(error_buf), &consumed_len); - if (parse_success && consumed_len == payload_len) { - sections[current_section_index].parsed.start_section = - start_section; - } - else { - if (start_section) { - wasm_runtime_free(start_section); - } - if (error_buf[0]) { - LOG_DEBUG("Error parsing start section: %s\n", - error_buf); - } - if (consumed_len != payload_len) { - LOG_DEBUG("FATAL ERROR: Start section consumed %u " - "bytes but expected %lu\n", - consumed_len, payload_len); - wasm_runtime_free(sections); - return false; - } - } + &payload_start, (uint32_t)payload_len, + section->parsed.start_section, section_error, + sizeof(section_error), &consumed_len); break; - } - // Section 10: imports section (component model imports) case WASM_COMP_SECTION_IMPORTS: - { - // Parse all imports: name (simple/versioned) and externdesc - // (all 6 types) - WASMComponentImportSection *import_section = - wasm_runtime_malloc(sizeof(WASMComponentImportSection)); - if (import_section) - memset(import_section, 0, - sizeof(WASMComponentImportSection)); + ALLOC_SECTION_WRAPPER(section, import_section, + WASMComponentImportSection, "import"); parse_success = wasm_component_parse_imports_section( - &payload_start, (uint32_t)payload_len, import_section, - error_buf, sizeof(error_buf), &consumed_len); - if (parse_success && consumed_len == payload_len) { - sections[current_section_index].parsed.import_section = - import_section; - } - else { - if (import_section) { - wasm_runtime_free(import_section); - } - if (error_buf[0]) { - LOG_DEBUG("Error parsing imports section: %s\n", - error_buf); - } - if (consumed_len != payload_len) { - LOG_DEBUG("FATAL ERROR: Imports section consumed %u " - "bytes but expected %lu\n", - consumed_len, payload_len); - wasm_runtime_free(sections); - return false; - } - } + &payload_start, (uint32_t)payload_len, + section->parsed.import_section, section_error, + sizeof(section_error), &consumed_len); break; - } - // Section 11: exports section case WASM_COMP_SECTION_EXPORTS: - { - WASMComponentExportSection *export_section = - wasm_runtime_malloc(sizeof(WASMComponentExportSection)); - if (export_section) - memset(export_section, 0, - sizeof(WASMComponentExportSection)); + ALLOC_SECTION_WRAPPER(section, export_section, + WASMComponentExportSection, "export"); parse_success = wasm_component_parse_exports_section( - &payload_start, (uint32_t)payload_len, export_section, - error_buf, sizeof(error_buf), &consumed_len); - if (parse_success && consumed_len == payload_len) { - sections[current_section_index].parsed.export_section = - export_section; - } - else { - if (export_section) { - wasm_runtime_free(export_section); - } - if (error_buf[0]) { - LOG_DEBUG("Error parsing export section: %s\n", - error_buf); - } - if (consumed_len != payload_len) { - LOG_DEBUG("FATAL ERROR: Exports section consumed %u " - "bytes but expected %lu\n", - consumed_len, payload_len); - wasm_runtime_free(sections); - return false; - } - } + &payload_start, (uint32_t)payload_len, + section->parsed.export_section, section_error, + sizeof(section_error), &consumed_len); break; - } - // Section 12: values section case WASM_COMP_SECTION_VALUES: - { - WASMComponentValueSection *value_section = - wasm_runtime_malloc(sizeof(WASMComponentValueSection)); - if (value_section) - memset(value_section, 0, sizeof(WASMComponentValueSection)); + ALLOC_SECTION_WRAPPER(section, value_section, + WASMComponentValueSection, "value"); parse_success = wasm_component_parse_values_section( - &payload_start, (uint32_t)payload_len, value_section, - error_buf, sizeof(error_buf), &consumed_len); - if (parse_success && consumed_len == payload_len) { - sections[current_section_index].parsed.value_section = - value_section; - } - else { - if (value_section) { - wasm_runtime_free(value_section); - } - if (error_buf[0]) { - LOG_DEBUG("Error parsing values section: %s\n", - error_buf); - } - if (consumed_len != payload_len) { - LOG_DEBUG("FATAL ERROR: Values section consumed %u " - "bytes but expected %lu\n", - consumed_len, payload_len); - wasm_runtime_free(sections); - return false; - } - } + &payload_start, (uint32_t)payload_len, + section->parsed.value_section, section_error, + sizeof(section_error), &consumed_len); break; - } default: - // Unknown/unsupported section id - LOG_DEBUG("FATAL ERROR: Unknown/unsupported section (id=%u, " - "size=%lu)\n", - section_id, payload_len); - wasm_runtime_free(sections); - return false; + set_error_buf_ex(error_buf, error_buf_size, + "Unsupported component section id %u", + section_id); + goto fail; } - // Advance the main parser by the consumed amount - p = payload_start + consumed_len; - - // Safety check to ensure we don't go past the end - if (p > end) { - wasm_runtime_free(sections); - return false; + if (!parse_success) { + set_error_buf_ex( + error_buf, error_buf_size, + "Failed to parse component section %u: %s", section_id, + section_error[0] ? section_error : "unspecified parser error"); + goto fail; + } + if (consumed_len != payload_len) { + set_error_buf_ex(error_buf, error_buf_size, + "Component section %u consumed %u of %u bytes", + section_id, consumed_len, (uint32_t)payload_len); + goto fail; } + p += payload_len; } - out_component->sections = sections; - out_component->section_count = section_count; return true; + +fail: + wasm_component_free(out_component); + return false; +} + +bool +wasm_component_parse_sections(const uint8_t *buf, uint32_t size, + WASMComponent *out_component, LoadArgs *args, + unsigned int depth) +{ + return wasm_component_parse_sections_ex(buf, size, out_component, args, + depth, NULL, 0); } +#undef ALLOC_SECTION_WRAPPER + // Check if Header is Component bool is_wasm_component(WASMHeader header) @@ -557,81 +290,256 @@ is_wasm_component(WASMHeader header) return true; } +WASMComponent * +wasm_component_load(uint8_t *buf, uint32_t size, const LoadArgs *load_args, + char *error_buf, uint32_t error_buf_size) +{ + WASMComponent *component = NULL; + LoadArgs args = { 0 }; + static char default_name[] = "Component"; + + if (!buf || size == 0) { + set_error_buf_ex(error_buf, error_buf_size, "Invalid component buffer"); + return NULL; + } + + if (load_args) { + args = *load_args; + } + if (!args.name) { + args.name = default_name; + } + args.is_component = true; + + component = wasm_runtime_malloc(sizeof(*component)); + if (!component) { + set_error_buf_ex(error_buf, error_buf_size, + "Failed to allocate component"); + return NULL; + } + memset(component, 0, sizeof(*component)); +#if WASM_ENABLE_LIBC_WASI_P2 != 0 + wasi_args_set_defaults(&component->wasi_args); +#endif + + if (!wasm_component_parse_sections_ex(buf, size, component, &args, 0, + error_buf, error_buf_size)) { + wasm_component_free(component); + wasm_runtime_free(component); + return NULL; + } + if (!wasm_component_validate(component, NULL, error_buf, error_buf_size)) { + wasm_component_free(component); + wasm_runtime_free(component); + return NULL; + } + + return component; +} + +static char * +component_string_dup(const char *value) +{ + size_t length; + char *copy; + + if (!value || (length = strlen(value)) >= UINT32_MAX) { + return NULL; + } + copy = wasm_runtime_malloc((uint32_t)length + 1); + if (copy) { + memcpy(copy, value, length + 1); + } + return copy; +} + +bool +wasm_component_register_host_resource_drop_callback( + WASMComponent *component, const char *interface_name, + const char *resource_name, + wasm_component_host_resource_drop_callback_t callback) +{ + WASMComponentHostResourceDropRegistration *registration; + char *interface_copy = NULL, *resource_copy = NULL; + + if (!component || !interface_name || !interface_name[0] || !resource_name + || !resource_name[0] || !callback) { + return false; + } + + for (uint32_t idx = 0; idx < component->host_resource_drop_count; idx++) { + registration = &component->host_resource_drops[idx]; + if (strcmp(registration->interface_name, interface_name) == 0 + && strcmp(registration->resource_name, resource_name) == 0) { + registration->callback = callback; + return true; + } + } + + interface_copy = component_string_dup(interface_name); + resource_copy = component_string_dup(resource_name); + if (!interface_copy || !resource_copy) { + goto fail; + } + + if (component->host_resource_drop_count + == component->host_resource_drop_capacity) { + uint32_t new_capacity = component->host_resource_drop_capacity + ? component->host_resource_drop_capacity * 2 + : 4; + uint64_t byte_count = + (uint64_t)new_capacity * sizeof(*component->host_resource_drops); + WASMComponentHostResourceDropRegistration *new_registrations; + + if (new_capacity < component->host_resource_drop_capacity + || byte_count > UINT32_MAX) { + goto fail; + } + new_registrations = wasm_runtime_realloc(component->host_resource_drops, + (uint32_t)byte_count); + if (!new_registrations) { + goto fail; + } + component->host_resource_drops = new_registrations; + component->host_resource_drop_capacity = new_capacity; + } + + registration = + &component->host_resource_drops[component->host_resource_drop_count++]; + registration->interface_name = interface_copy; + registration->resource_name = resource_copy; + registration->callback = callback; + return true; + +fail: + if (interface_copy) { + wasm_runtime_free(interface_copy); + } + if (resource_copy) { + wasm_runtime_free(resource_copy); + } + return false; +} + +bool +wasm_component_find_host_resource_drop_callback( + const WASMComponent *component, const char *interface_name, + const char *resource_name, + wasm_component_host_resource_drop_callback_t *callback) +{ + if (!component || !interface_name || !resource_name || !callback) { + return false; + } + + for (uint32_t idx = 0; idx < component->host_resource_drop_count; idx++) { + const WASMComponentHostResourceDropRegistration *registration = + &component->host_resource_drops[idx]; + if (strcmp(registration->interface_name, interface_name) == 0 + && strcmp(registration->resource_name, resource_name) == 0) { + *callback = registration->callback; + return true; + } + } + return false; +} + +void +wasm_component_unload(WASMComponent *component) +{ + if (component) { + wasm_component_free(component); + wasm_runtime_free(component); + } +} + // Main component free function void wasm_component_free(WASMComponent *component) { - if (!component || !component->sections) + if (!component) return; - for (uint32_t i = 0; i < component->section_count; ++i) { - WASMComponentSection *sec = &component->sections[i]; + if (component->sections) { + for (uint32_t i = 0; i < component->section_count; ++i) { + WASMComponentSection *sec = &component->sections[i]; - switch (sec->id) { - case WASM_COMP_SECTION_CORE_CUSTOM: // Section 0 - wasm_component_free_core_custom_section(sec); - break; + switch (sec->id) { + case WASM_COMP_SECTION_CORE_CUSTOM: // Section 0 + wasm_component_free_core_custom_section(sec); + break; - case WASM_COMP_SECTION_CORE_MODULE: // Section 1 - wasm_component_free_core_module_section(sec); - break; + case WASM_COMP_SECTION_CORE_MODULE: // Section 1 + wasm_component_free_core_module_section(sec); + break; - case WASM_COMP_SECTION_CORE_INSTANCE: // Section 2 - wasm_component_free_core_instance_section(sec); - break; + case WASM_COMP_SECTION_CORE_INSTANCE: // Section 2 + wasm_component_free_core_instance_section(sec); + break; - case WASM_COMP_SECTION_CORE_TYPE: // Section 3 - wasm_component_free_core_type_section(sec); - break; + case WASM_COMP_SECTION_CORE_TYPE: // Section 3 + wasm_component_free_core_type_section(sec); + break; - case WASM_COMP_SECTION_COMPONENT: // Section 4 - wasm_component_free_component_section(sec); - break; + case WASM_COMP_SECTION_COMPONENT: // Section 4 + wasm_component_free_component_section(sec); + break; - case WASM_COMP_SECTION_INSTANCES: // Section 5 - wasm_component_free_instances_section(sec); - break; + case WASM_COMP_SECTION_INSTANCES: // Section 5 + wasm_component_free_instances_section(sec); + break; - case WASM_COMP_SECTION_ALIASES: // Section 6 - wasm_component_free_alias_section(sec); - break; + case WASM_COMP_SECTION_ALIASES: // Section 6 + wasm_component_free_alias_section(sec); + break; - case WASM_COMP_SECTION_TYPE: // Section 7 - wasm_component_free_types_section(sec); - break; + case WASM_COMP_SECTION_TYPE: // Section 7 + wasm_component_free_types_section(sec); + break; - case WASM_COMP_SECTION_CANONS: // Section 8 - wasm_component_free_canons_section(sec); - break; + case WASM_COMP_SECTION_CANONS: // Section 8 + wasm_component_free_canons_section(sec); + break; - case WASM_COMP_SECTION_START: // Section 9 - wasm_component_free_start_section(sec); - break; + case WASM_COMP_SECTION_START: // Section 9 + wasm_component_free_start_section(sec); + break; - case WASM_COMP_SECTION_IMPORTS: // Section 10 - wasm_component_free_imports_section(sec); - break; + case WASM_COMP_SECTION_IMPORTS: // Section 10 + wasm_component_free_imports_section(sec); + break; - case WASM_COMP_SECTION_EXPORTS: // Section 11 - wasm_component_free_exports_section(sec); - break; + case WASM_COMP_SECTION_EXPORTS: // Section 11 + wasm_component_free_exports_section(sec); + break; - case WASM_COMP_SECTION_VALUES: // Section 12 - wasm_component_free_values_section(sec); - break; + case WASM_COMP_SECTION_VALUES: // Section 12 + wasm_component_free_values_section(sec); + break; - default: - // For other sections that don't have parsed data or are stubs - // Just free the any pointer if it exists - if (sec->parsed.any) { - wasm_runtime_free(sec->parsed.any); - sec->parsed.any = NULL; - } - break; + default: + // For other sections that don't have parsed data or are + // stubs Just free the any pointer if it exists + if (sec->parsed.any) { + wasm_runtime_free(sec->parsed.any); + sec->parsed.any = NULL; + } + break; + } } + + wasm_runtime_free(component->sections); + component->sections = NULL; + component->section_count = 0; } - wasm_runtime_free(component->sections); - component->sections = NULL; - component->section_count = 0; -} \ No newline at end of file + for (uint32_t idx = 0; idx < component->host_resource_drop_count; idx++) { + wasm_runtime_free(component->host_resource_drops[idx].interface_name); + wasm_runtime_free(component->host_resource_drops[idx].resource_name); + } + if (component->host_resource_drops) { + wasm_runtime_free(component->host_resource_drops); + } + component->host_resource_drops = NULL; + component->host_resource_drop_count = 0; + component->host_resource_drop_capacity = 0; +} diff --git a/core/iwasm/common/component-model/wasm_component.h b/core/iwasm/common/component-model/wasm_component.h index 0a14824837..052aa5e8a4 100644 --- a/core/iwasm/common/component-model/wasm_component.h +++ b/core/iwasm/common/component-model/wasm_component.h @@ -1553,12 +1553,21 @@ typedef struct WASMComponentSection { } parsed; } WASMComponentSection; +typedef struct WASMComponentHostResourceDropRegistration { + char *interface_name; + char *resource_name; + wasm_component_host_resource_drop_callback_t callback; +} WASMComponentHostResourceDropRegistration; + // Main Component Structure - Complete component typedef struct WASMComponent { WASMHeader header; WASMComponentSection *sections; uint32_t section_count; -#if WASM_ENABLE_LIBC_WASI != 0 + WASMComponentHostResourceDropRegistration *host_resource_drops; + uint32_t host_resource_drop_count; + uint32_t host_resource_drop_capacity; +#if WASM_ENABLE_LIBC_WASI_P2 != 0 WASIArguments wasi_args; bool import_wasi_api; #endif @@ -1574,6 +1583,17 @@ void set_error_buf_ex(char *error_buf, uint32_t error_buf_size, const char *format, ...); +/* Allocate and zero a parser-owned array after checking both the runtime's + * uint32 allocation limit and the minimum bytes still required in the input + * for each encoded element. The payload bounds may be NULL only for arrays + * whose element count is not read from the current input buffer. */ +void * +wasm_component_checked_calloc(uint32_t count, uint32_t element_size, + const uint8_t *payload, const uint8_t *end, + uint32_t min_element_size, + const char *description, char *error_buf, + uint32_t error_buf_size); + bool parse_valtype(const uint8_t **payload, const uint8_t *end, WASMComponentValueType *out, char *error_buf, @@ -1689,11 +1709,26 @@ parse_core_valtype(const uint8_t **payload, const uint8_t *end, bool is_wasm_component(WASMHeader header); +WASMComponent * +wasm_component_load(uint8_t *buf, uint32_t size, const LoadArgs *load_args, + char *error_buf, uint32_t error_buf_size); +void +wasm_component_unload(WASMComponent *component); +bool +wasm_component_find_host_resource_drop_callback( + const WASMComponent *component, const char *interface_name, + const char *resource_name, + wasm_component_host_resource_drop_callback_t *callback); bool wasm_component_parse_sections(const uint8_t *buf, uint32_t size, WASMComponent *out_component, LoadArgs *args, unsigned int depth); bool +wasm_component_parse_sections_ex(const uint8_t *buf, uint32_t size, + WASMComponent *out_component, LoadArgs *args, + unsigned int depth, char *error_buf, + uint32_t error_buf_size); +bool wasm_component_parse_core_custom_section(const uint8_t **payload, uint32_t payload_len, WASMComponentCoreCustomSection *out, diff --git a/core/iwasm/common/component-model/wasm_component_alias_section.c b/core/iwasm/common/component-model/wasm_component_alias_section.c index 4a3ebb3a8f..c6d716ddcd 100644 --- a/core/iwasm/common/component-model/wasm_component_alias_section.c +++ b/core/iwasm/common/component-model/wasm_component_alias_section.c @@ -33,6 +33,10 @@ parse_single_alias(const uint8_t **payload, const uint8_t *end, } // Read tag + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, "Missing alias target tag"); + return false; + } uint8_t tag = *p++; // Parse alias target using switch @@ -143,16 +147,14 @@ wasm_component_parse_alias_section(const uint8_t **payload, out->count = alias_count; if (alias_count > 0) { - out->aliases = wasm_runtime_malloc(sizeof(WASMComponentAliasDefinition) - * alias_count); + out->aliases = wasm_component_checked_calloc( + alias_count, sizeof(WASMComponentAliasDefinition), p, end, 1, + "component alias", error_buf, error_buf_size); if (!out->aliases) { if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - // Zero-initialize the aliases array - memset(out->aliases, 0, - sizeof(WASMComponentAliasDefinition) * alias_count); for (uint32_t i = 0; i < alias_count; ++i) { // Allocate memory for the sort field diff --git a/core/iwasm/common/component-model/wasm_component_application.c b/core/iwasm/common/component-model/wasm_component_application.c index a364e0dc1b..84acac7af7 100644 --- a/core/iwasm/common/component-model/wasm_component_application.c +++ b/core/iwasm/common/component-model/wasm_component_application.c @@ -62,6 +62,452 @@ wasm_component_set_exception(WASMComponentInstance *comp_inst, } } +/* + * A prepared call owns all scratch storage required to bridge wasm_val_t to + * the interpreter's cell ABI. This is deliberately opaque to embedders: a + * generated binding resolves it once and can then call without WAVE parsing, + * WIT-value allocation, or per-call runtime allocation. + */ +struct WASMComponentPreparedCall { + WASMComponentInstance *component_inst; + WASMFunctionInstance *core_func; + WASMExecEnv *exec_env; + WASMFunctionInstance *post_return_func; + WASMExecEnv *post_return_exec_env; + uint32 param_count; + uint32 result_count; + uint32 param_cell_count; + uint32 result_cell_count; + wasm_valkind_t param_kinds[MAX_FLAT_TYPES]; + wasm_valkind_t result_kinds[MAX_FLAT_TYPES]; + uint32 argv_cells[MAX_FLAT_TYPES * 2]; + bool post_return_pending; +}; + +static void +set_prepared_call_error(WASMComponentInstance *component_inst, char *error_buf, + uint32 error_buf_size, const char *message) +{ + if (component_inst) { + wasm_component_set_exception(component_inst, message); + } + if (error_buf && error_buf_size > 0) { + snprintf(error_buf, error_buf_size, "%s", message); + } +} + +static bool +prepared_call_kind(uint8 value_type, wasm_valkind_t *kind, uint32 *cell_count) +{ + switch (value_type) { + case VALUE_TYPE_I32: + *kind = WASM_I32; + *cell_count = 1; + return true; + case VALUE_TYPE_I64: + *kind = WASM_I64; + *cell_count = 2; + return true; + case VALUE_TYPE_F32: + *kind = WASM_F32; + *cell_count = 1; + return true; + case VALUE_TYPE_F64: + *kind = WASM_F64; + *cell_count = 2; + return true; + default: + return false; + } +} + +static bool +prepare_flat_signature(WASMFuncType *type, wasm_valkind_t param_kinds[], + wasm_valkind_t result_kinds[], uint32 *param_cells, + uint32 *result_cells) +{ + uint32 i, cells; + + if (!type || type->param_count > MAX_FLAT_TYPES + || type->result_count > MAX_FLAT_TYPES) { + return false; + } + + *param_cells = 0; + for (i = 0; i < type->param_count; i++) { + if (!prepared_call_kind(type->types[i], ¶m_kinds[i], &cells)) { + return false; + } + *param_cells += cells; + } + + *result_cells = 0; + for (i = 0; i < type->result_count; i++) { + if (!prepared_call_kind(type->types[type->param_count + i], + &result_kinds[i], &cells)) { + return false; + } + *result_cells += cells; + } + + return *param_cells == type->param_cell_num + && *result_cells == type->ret_cell_num + && *param_cells <= MAX_FLAT_TYPES * 2 + && *result_cells <= MAX_FLAT_TYPES * 2; +} + +static WASMComponentPreparedCall * +prepare_export_call(WASMComponentInstance *component_inst, + const char *interface_name, const char *export_name, + char *error_buf, uint32 error_buf_size) +{ + WASMComponentFunctionInstance *target_func; + WASMComponentPreparedCall *prepared_call = NULL; + WASMFuncType *type, *post_return_type; + uint32 module_type, post_param_cells = 0, post_result_cells = 0; + wasm_valkind_t post_param_kinds[MAX_FLAT_TYPES]; + wasm_valkind_t post_result_kinds[MAX_FLAT_TYPES]; + + if (!component_inst || !export_name) { + set_prepared_call_error(component_inst, error_buf, error_buf_size, + "component: invalid prepared call arguments"); + return NULL; + } + + target_func = + interface_name + ? wasm_component_lookup_function_qualified( + component_inst, interface_name, export_name) + : wasm_component_lookup_function(component_inst, export_name); + if (!target_func || !target_func->core_func + || !target_func->core_func->module_instance) { + set_prepared_call_error( + component_inst, error_buf, error_buf_size, + interface_name + ? "component: qualified prepared export lookup failed" + : "component: prepared export lookup failed"); + return NULL; + } + + if (target_func->canon_options && target_func->canon_options->async) { + set_prepared_call_error( + component_inst, error_buf, error_buf_size, + "component: prepared calls require a synchronous export"); + return NULL; + } + + prepared_call = wasm_runtime_malloc(sizeof(*prepared_call)); + if (!prepared_call) { + set_prepared_call_error(component_inst, error_buf, error_buf_size, + "component: failed to allocate prepared call"); + return NULL; + } + memset(prepared_call, 0, sizeof(*prepared_call)); + + prepared_call->component_inst = component_inst; + prepared_call->core_func = target_func->core_func; + module_type = target_func->core_func->module_instance->module_type; + type = wasm_runtime_get_function_type(target_func->core_func, module_type); + if (!prepare_flat_signature(type, prepared_call->param_kinds, + prepared_call->result_kinds, + &prepared_call->param_cell_count, + &prepared_call->result_cell_count)) { + set_prepared_call_error( + component_inst, error_buf, error_buf_size, + "component: export has a non-flat or unsupported core signature"); + goto fail; + } + prepared_call->param_count = type->param_count; + prepared_call->result_count = type->result_count; + + prepared_call->exec_env = wasm_runtime_get_exec_env_singleton( + (WASMModuleInstanceCommon *)target_func->core_func->module_instance); + if (!prepared_call->exec_env) { + set_prepared_call_error( + component_inst, error_buf, error_buf_size, + "component: failed to create prepared execution environment"); + goto fail; + } + + if (target_func->canon_options + && target_func->canon_options->post_return_func) { + prepared_call->post_return_func = + target_func->canon_options->post_return_func; + module_type = + prepared_call->post_return_func->module_instance->module_type; + post_return_type = wasm_runtime_get_function_type( + prepared_call->post_return_func, module_type); + if (!prepare_flat_signature(post_return_type, post_param_kinds, + post_result_kinds, &post_param_cells, + &post_result_cells) + || post_return_type->param_count != prepared_call->result_count + || post_return_type->result_count != 0 + || post_param_cells != prepared_call->result_cell_count + || post_result_cells != 0 + || memcmp(post_param_kinds, prepared_call->result_kinds, + prepared_call->result_count + * sizeof(prepared_call->result_kinds[0])) + != 0) { + set_prepared_call_error(component_inst, error_buf, error_buf_size, + "component: post-return signature does not " + "match export results"); + goto fail; + } + prepared_call->post_return_exec_env = + wasm_runtime_get_exec_env_singleton( + (WASMModuleInstanceCommon *) + prepared_call->post_return_func->module_instance); + if (!prepared_call->post_return_exec_env) { + set_prepared_call_error(component_inst, error_buf, error_buf_size, + "component: failed to create post-return " + "execution environment"); + goto fail; + } + } + + wasm_component_set_exception(component_inst, NULL); + if (error_buf && error_buf_size > 0) { + error_buf[0] = '\0'; + } + return prepared_call; + +fail: + wasm_runtime_free(prepared_call); + return NULL; +} + +WASMComponentPreparedCall * +wasm_component_prepare_export_call(WASMComponentInstance *component_inst, + const char *export_name, char *error_buf, + uint32 error_buf_size) +{ + return prepare_export_call(component_inst, NULL, export_name, error_buf, + error_buf_size); +} + +WASMComponentPreparedCall * +wasm_component_prepare_export_call_qualified( + WASMComponentInstance *component_inst, const char *interface_name, + const char *export_name, char *error_buf, uint32 error_buf_size) +{ + if (!interface_name) { + set_prepared_call_error( + component_inst, error_buf, error_buf_size, + "component: invalid qualified prepared call arguments"); + return NULL; + } + return prepare_export_call(component_inst, interface_name, export_name, + error_buf, error_buf_size); +} + +static bool +prepared_values_to_cells(WASMComponentPreparedCall *prepared_call, + const wasm_val_t values[], uint32 value_count, + const wasm_valkind_t kinds[]) +{ + uint32 i, cell_index = 0; + + for (i = 0; i < value_count; i++) { + if (values[i].kind != kinds[i]) { + wasm_component_set_exception( + prepared_call->component_inst, + "component: prepared call value kind does not match signature"); + return false; + } + switch (kinds[i]) { + case WASM_I32: + prepared_call->argv_cells[cell_index++] = + (uint32)values[i].of.i32; + break; + case WASM_I64: + { + union { + uint64 val; + uint32 parts[2]; + } value; + value.val = (uint64)values[i].of.i64; + prepared_call->argv_cells[cell_index++] = value.parts[0]; + prepared_call->argv_cells[cell_index++] = value.parts[1]; + break; + } + case WASM_F32: + { + union { + float32 val; + uint32 part; + } value; + value.val = values[i].of.f32; + prepared_call->argv_cells[cell_index++] = value.part; + break; + } + case WASM_F64: + { + union { + float64 val; + uint32 parts[2]; + } value; + value.val = values[i].of.f64; + prepared_call->argv_cells[cell_index++] = value.parts[0]; + prepared_call->argv_cells[cell_index++] = value.parts[1]; + break; + } + default: + bh_assert(0); + return false; + } + } + return true; +} + +static void +prepared_cells_to_results(const WASMComponentPreparedCall *prepared_call, + wasm_val_t results[]) +{ + uint32 i, cell_index = 0; + + for (i = 0; i < prepared_call->result_count; i++) { + results[i].kind = prepared_call->result_kinds[i]; + switch (prepared_call->result_kinds[i]) { + case WASM_I32: + results[i].of.i32 = + (int32)prepared_call->argv_cells[cell_index++]; + break; + case WASM_I64: + { + union { + uint64 val; + uint32 parts[2]; + } value; + value.parts[0] = prepared_call->argv_cells[cell_index++]; + value.parts[1] = prepared_call->argv_cells[cell_index++]; + results[i].of.i64 = (int64)value.val; + break; + } + case WASM_F32: + { + union { + float32 val; + uint32 part; + } value; + value.part = prepared_call->argv_cells[cell_index++]; + results[i].of.f32 = value.val; + break; + } + case WASM_F64: + { + union { + float64 val; + uint32 parts[2]; + } value; + value.parts[0] = prepared_call->argv_cells[cell_index++]; + value.parts[1] = prepared_call->argv_cells[cell_index++]; + results[i].of.f64 = value.val; + break; + } + default: + bh_assert(0); + break; + } + } +} + +bool +wasm_component_prepared_call_requires_post_return( + const WASMComponentPreparedCall *prepared_call) +{ + return prepared_call && prepared_call->post_return_func; +} + +bool +wasm_component_call_prepared(WASMComponentPreparedCall *prepared_call, + uint32 num_results, wasm_val_t results[], + uint32 num_args, const wasm_val_t args[]) +{ + const char *exception; + + if (!prepared_call) { + return false; + } + if (prepared_call->post_return_pending) { + wasm_component_set_exception( + prepared_call->component_inst, + "component: post-return is required before the next prepared call"); + return false; + } + if (num_args != prepared_call->param_count + || num_results != prepared_call->result_count || (num_args > 0 && !args) + || (num_results > 0 && !results)) { + wasm_component_set_exception(prepared_call->component_inst, + "component: prepared call argument/result " + "count does not match signature"); + return false; + } + if (!prepared_values_to_cells(prepared_call, args, num_args, + prepared_call->param_kinds)) { + return false; + } + + wasm_component_set_exception(prepared_call->component_inst, NULL); + if (!wasm_runtime_call_wasm( + prepared_call->exec_env, + (WASMFunctionInstanceCommon *)prepared_call->core_func, + prepared_call->param_cell_count, prepared_call->argv_cells)) { + exception = wasm_runtime_get_exception( + (WASMModuleInstanceCommon *) + prepared_call->core_func->module_instance); + wasm_component_set_exception( + prepared_call->component_inst, + exception ? exception : "component: prepared core call failed"); + return false; + } + + prepared_cells_to_results(prepared_call, results); + prepared_call->post_return_pending = + prepared_call->post_return_func != NULL; + return true; +} + +bool +wasm_component_prepared_call_post_return( + WASMComponentPreparedCall *prepared_call) +{ + const char *exception; + + if (!prepared_call) { + return false; + } + if (!prepared_call->post_return_pending) { + return true; + } + + /* A post-return invocation is consumed even when it traps. */ + prepared_call->post_return_pending = false; + wasm_component_set_exception(prepared_call->component_inst, NULL); + if (!wasm_runtime_call_wasm( + prepared_call->post_return_exec_env, + (WASMFunctionInstanceCommon *)prepared_call->post_return_func, + prepared_call->result_cell_count, prepared_call->argv_cells)) { + exception = wasm_runtime_get_exception( + (WASMModuleInstanceCommon *) + prepared_call->post_return_func->module_instance); + wasm_component_set_exception( + prepared_call->component_inst, + exception ? exception : "component: post-return failed"); + return false; + } + return true; +} + +void +wasm_component_destroy_prepared_call(WASMComponentPreparedCall *prepared_call) +{ + if (!prepared_call) { + return; + } + bh_assert(!prepared_call->post_return_pending); + wasm_runtime_free(prepared_call); +} + static void print_wit_value(wit_value_t value) { @@ -366,8 +812,8 @@ execute_component_func(WASMComponentInstance *component_inst, char *argv, goto fail; } - LOG_DEBUG("Executing WASM component function: %s with %d arguments\n", - inv.func_name); + LOG_DEBUG("Executing WASM component function: %s with %u arguments\n", + inv.func_name, inv.arg_count); CanonicalOptions *lower_opts = target_func->canon_options; WASMComponentFuncTypeInstance *ft = target_func->func_type; @@ -670,7 +1116,7 @@ execute_component_main(WASMComponentInstance *component_inst, int32 argc, uint32 *argv_offsets = NULL, module_type = 0; bool ret = false, is_import_func = true, is_memory64 = false; -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI_P2 != 0 /* In wasi mode, we should call the function named "_start" which initializes the wasi environment and then calls the actual main function. Directly calling main function @@ -699,7 +1145,7 @@ execute_component_main(WASMComponentInstance *component_inst, int32 argc, } return ret; } -#endif /* end of WASM_ENABLE_LIBC_WASI */ +#endif /* end of WASM_ENABLE_LIBC_WASI_P2 */ func = wasm_component_lookup_function( component_inst, diff --git a/core/iwasm/common/component-model/wasm_component_canon.c b/core/iwasm/common/component-model/wasm_component_canon.c index 7150b7f5ac..1b8d59f4a8 100644 --- a/core/iwasm/common/component-model/wasm_component_canon.c +++ b/core/iwasm/common/component-model/wasm_component_canon.c @@ -6,9 +6,6 @@ #include "wasm_component_canon.h" #include "wasm_component_runtime.h" #include "wasm_component_task.h" -#include "wasm_component_host_resource.h" -#include "wasm_runtime.h" -#include "../libraries/libc-wasi-p2/wasi_p2_common.h" #include "bh_log.h" bool @@ -101,84 +98,19 @@ canon_resource_drop(const WASMComponentResourceInstance *rt, return false; } - bool own = handle->own; - uint32_t rep = handle->rep; Task *borrow_scope = handle->borrow_scope; - - if (!wasm_component_table_remove(inst->table, handle_index)) { - LOG_ERROR("canon resource.drop: failed to remove handle from table"); + if (borrow_scope && borrow_scope->num_borrows == 0) { + LOG_ERROR("canon resource.drop: borrow count underflow"); return false; } - if (own) { - if (rt->is_wasi) { - - HostResourceTable *hr_table = get_global_host_resource_table(); - if (!hr_table) { - LOG_ERROR("canon resource.drop: failed to retrieve host " - "resource table"); - return false; - } - - HostResource *hr = host_resource_table_get(hr_table, rep); - if (!hr) { - LOG_ERROR("canon resource.drop: failed to retrieve host " - "resource handle"); - return false; - } - - if (!host_resource_table_delete(hr_table, rep)) { - LOG_ERROR("canon resource.drop: failed to remove handle from " - "host table"); - return false; - } - } - - if (rt->dtor_method) { - WASMExecEnv *dtor_exec_env = wasm_runtime_get_exec_env_singleton( - (WASMModuleInstanceCommon *)rt->dtor_method->module_instance); - if (!dtor_exec_env) { - LOG_ERROR("canon resource.drop: no exec_env for dtor"); - return false; - } - - WASMModuleInstanceCommon *saved_inst = - wasm_runtime_get_module_inst(dtor_exec_env); - wasm_exec_env_set_module_inst( - dtor_exec_env, - (WASMModuleInstanceCommon *)rt->dtor_method->module_instance); - - wasm_val_t arg = { .kind = WASM_I32, .of.i32 = (int32_t)rep }; - -#ifdef OS_ENABLE_HW_BOUND_CHECK - WASMExecEnv *saved_tls = wasm_runtime_get_exec_env_tls(); - wasm_runtime_set_exec_env_tls(NULL); -#endif - if (!wasm_runtime_call_wasm_a( - dtor_exec_env, - (WASMFunctionInstanceCommon *)rt->dtor_method, 0, NULL, 1, - &arg)) { - const char *ex = wasm_runtime_get_exception( - (WASMModuleInstanceCommon *) - rt->dtor_method->module_instance); - LOG_ERROR("canon resource.drop: dtor call failed: %s", - ex ? ex : "(unknown)"); -#ifdef OS_ENABLE_HW_BOUND_CHECK - wasm_runtime_set_exec_env_tls(saved_tls); -#endif - wasm_exec_env_restore_module_inst(dtor_exec_env, saved_inst); - return false; - } -#ifdef OS_ENABLE_HW_BOUND_CHECK - wasm_runtime_set_exec_env_tls(saved_tls); -#endif - wasm_exec_env_restore_module_inst(dtor_exec_env, saved_inst); - } + if (!wasm_component_table_drop_resource(inst->table, handle_index)) { + LOG_ERROR("canon resource.drop: failed to drop handle from table"); + return false; } - else { - if (borrow_scope) { - borrow_scope->num_borrows--; - } + + if (borrow_scope) { + borrow_scope->num_borrows--; } return true; diff --git a/core/iwasm/common/component-model/wasm_component_canonical.c b/core/iwasm/common/component-model/wasm_component_canonical.c index d05b016d35..4960a2494d 100644 --- a/core/iwasm/common/component-model/wasm_component_canonical.c +++ b/core/iwasm/common/component-model/wasm_component_canonical.c @@ -159,7 +159,10 @@ load_int(LiftLowerContext *cx, uint32_t ptr, uint32_t nbytes, bool is_signed, { const WASMMemoryInstance *mem = get_mem_from_cx(cx); bh_assert(nbytes >= 1 && nbytes <= 8); - trap_if((uint64)(ptr + nbytes) > mem->memory_data_size); + /* Widen ptr to 64-bit BEFORE the add: ptr and nbytes are both 32-bit, so + * `ptr + nbytes` wraps mod 2^32 before the cast, and a ptr near UINT32_MAX + * would bypass the bounds check and read outside linear memory. */ + trap_if((uint64)ptr + nbytes > mem->memory_data_size); const uint8_t *src = mem->memory_data + ptr; @@ -337,7 +340,13 @@ load_string_from_range(LiftLowerContext *cx, uint32_t begin, WASMMemoryInstance *mem = get_mem_from_cx(cx); uint32_t alignment = 0; - uint32_t byte_length = 0; + /* byte_length must be 64-bit: for UTF-16 it is 2 * code_units, and a + * crafted code-unit count near UINT32_MAX would overflow a uint32_t + * multiply (wrapping to a small value) BEFORE the bounds check below, + * letting a truncated string slip past the guard. Keep the byte count in + * 64 bits and enforce the Canonical-ABI MAX_STRING_BYTE_LENGTH precondition + * on the post-tag-strip code-unit count (mirrors the STORE path). */ + uint64_t byte_length = 0; StringDecoding decoding = DECODING_UTF_8; switch (encoding) { @@ -352,7 +361,11 @@ load_string_from_range(LiftLowerContext *cx, uint32_t begin, case ENCODING_UTF_16: { alignment = 2; - byte_length = 2 * tagged_code_units; + /* code-unit count must satisfy the Canonical-ABI precondition; + * trap before the (now 64-bit) multiply so a count >= 2^31 cannot + * wrap byte_length. */ + trap_if(tagged_code_units > (uint32_t)MAX_STRING_BYTE_LENGTH); + byte_length = 2 * (uint64_t)tagged_code_units; decoding = DECODING_UTF_16_LE; break; } @@ -361,7 +374,9 @@ load_string_from_range(LiftLowerContext *cx, uint32_t begin, { alignment = 2; if (tagged_code_units & UTF16_TAG) { - byte_length = 2 * (tagged_code_units ^ UTF16_TAG); + uint32_t code_units = tagged_code_units ^ UTF16_TAG; + trap_if(code_units > (uint32_t)MAX_STRING_BYTE_LENGTH); + byte_length = 2 * (uint64_t)code_units; decoding = DECODING_UTF_16_LE; } else { @@ -379,13 +394,19 @@ load_string_from_range(LiftLowerContext *cx, uint32_t begin, } trap_if(begin != align_to(begin, alignment)); - trap_if((uint64_t)(begin + byte_length) > mem->memory_data_size); + /* Widen before the add: begin is uint32_t and byte_length is uint64_t, so + * `begin + byte_length` is evaluated in 64 bits and cannot wrap; a crafted + * begin near UINT32_MAX would otherwise pass this check yet read out of + * linear memory. */ + trap_if((uint64_t)begin + byte_length > mem->memory_data_size); const uint8_t *src = mem->memory_data + begin; char *decoded_str = NULL; uint32_t decoded_len = 0; - if (!decode_string(cx, src, byte_length, decoding, &decoded_str, + /* byte_length is now bounded by the checks above (<= memory_data_size, + * which fits in 32 bits), so this narrowing cast cannot lose bits. */ + if (!decode_string(cx, src, (uint32_t)byte_length, decoding, &decoded_str, &decoded_len)) { return false; } @@ -589,7 +610,11 @@ load_list_from_range(LiftLowerContext *cx, uint32_t ptr, uint32_t length, uint32_t elem_sz = get_elem_size(type); const WASMMemoryInstance *mem = get_mem_from_cx(cx); trap_if(ptr != align_to(ptr, elem_alignment)); - trap_if((uint64_t)(ptr + (length * elem_sz)) > mem->memory_data_size); + /* Widen before the arithmetic: ptr, length and elem_sz are all 32-bit, so + * both `length * elem_sz` and `ptr + ...` wrap mod 2^32 before the cast. + * A crafted length/ptr would pass this check yet index out of linear + * memory in load_list_from_valid_range below. */ + trap_if((uint64_t)ptr + (uint64_t)length * elem_sz > mem->memory_data_size); return load_list_from_valid_range(cx, ptr, length, type, out); } @@ -898,6 +923,7 @@ lift_own(LiftLowerContext *cx, uint32_t index, // 2. Validate it's an own handle trap_if(!handle->own); + trap_if(handle->rt != type->resource); // 3. Get the rep uint32_t rep = handle->rep; @@ -939,9 +965,9 @@ load(LiftLowerContext *cx, uint32_t ptr, WASMComponentTypeInstance *type, { if (!type) return false; - bh_assert(ptr == align_to(ptr, type->alignment)); - bh_assert((uint64_t)(ptr + type->elem_size) - <= get_mem_from_cx(cx)->memory_data_size); + trap_if(ptr != align_to(ptr, type->alignment)); + trap_if((uint64_t)ptr + type->elem_size + > get_mem_from_cx(cx)->memory_data_size); switch (type->type) { case COMPONENT_VAL_TYPE_PRIMVAL: @@ -1033,8 +1059,12 @@ store_int(LiftLowerContext *cx, uint32_t ptr, uint32_t nbytes, uint64_t val) { WASMMemoryInstance *mem = get_mem_from_cx(cx); bh_assert(nbytes >= 1 && nbytes <= 8); + trap_if(nbytes < 1 || nbytes > 8); + /* Widen before adding and validate before forming the host pointer. Both + * operands are 32-bit, so casting the sum lets a guest pointer near + * UINT32_MAX wrap past this check in release builds. */ + trap_if((uint64)ptr + nbytes > mem->memory_data_size); uint8_t *dst = mem->memory_data + ptr; - trap_if((uint64)(ptr + nbytes) > mem->memory_data_size); for (uint32_t i = 0; i < nbytes; i++) { dst[i] = (uint8_t)(val >> (i * 8)); @@ -1810,6 +1840,13 @@ bool lower_own(LiftLowerContext *cx, WASMComponentResourceHandleInstance *type, wit_value_t value, uint32_t *out_index) { + if (!value + || (value->type != COMPONENT_VAL_TYPE_RESOURCE_SYNC + && value->type != COMPONENT_VAL_TYPE_RESOURCE_ASYNC)) { + set_component_exception(cx, "expected resource value for own handle"); + return false; + } + // 1. Get rep from wit_value uint32_t rep = value->value.resource_value.value; @@ -1840,6 +1877,14 @@ lower_borrow(LiftLowerContext *cx, WASMComponentResourceHandleInstance *type, { bh_assert(cx->borrow_scope_type == BORROW_SCOPE_TASK); + if (!value + || (value->type != COMPONENT_VAL_TYPE_RESOURCE_SYNC + && value->type != COMPONENT_VAL_TYPE_RESOURCE_ASYNC)) { + set_component_exception(cx, + "expected resource value for borrow handle"); + return false; + } + // Check if lowering a borrow to the same component that owns the resource, // just pass the rep directly. // 1. Get rep from wit_value @@ -1987,9 +2032,9 @@ store(LiftLowerContext *cx, uint32_t ptr, WASMComponentTypeInstance *type, { if (!type) return false; - bh_assert(ptr == align_to(ptr, type->alignment)); - bh_assert((uint64_t)(ptr + type->elem_size) - <= get_mem_from_cx(cx)->memory_data_size); + trap_if(ptr != align_to(ptr, type->alignment)); + trap_if((uint64_t)ptr + type->elem_size + > get_mem_from_cx(cx)->memory_data_size); switch (type->type) { case COMPONENT_VAL_TYPE_PRIMVAL: diff --git a/core/iwasm/common/component-model/wasm_component_canons_section.c b/core/iwasm/common/component-model/wasm_component_canons_section.c index fd705d8cae..ad3dccb741 100644 --- a/core/iwasm/common/component-model/wasm_component_canons_section.c +++ b/core/iwasm/common/component-model/wasm_component_canons_section.c @@ -108,6 +108,11 @@ parse_canon_opt(const uint8_t **payload, const uint8_t *end, { const uint8_t *p = *payload; + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing canon option"); + return false; + } uint8_t tag = *p++; out->tag = tag; switch (tag) { @@ -192,6 +197,7 @@ parse_canon_opts(const uint8_t **payload, const uint8_t *end, "Failed to allocate memory for canon opts"); return false; } + memset(*out, 0, sizeof(WASMComponentCanonOpts)); uint64_t canon_opts_count = 0; if (!read_leb((uint8_t **)&p, end, 32, false, &canon_opts_count, error_buf, @@ -203,11 +209,10 @@ parse_canon_opts(const uint8_t **payload, const uint8_t *end, (*out)->canon_opts_count = (uint32_t)canon_opts_count; if (canon_opts_count > 0) { - (*out)->canon_opts = wasm_runtime_malloc(sizeof(WASMComponentCanonOpt) - * canon_opts_count); + (*out)->canon_opts = wasm_component_checked_calloc( + (uint32_t)canon_opts_count, sizeof(WASMComponentCanonOpt), p, end, + 1, "canonical option", error_buf, error_buf_size); if (!(*out)->canon_opts) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for canon opts"); free_canon_opts_struct(*out); *out = NULL; return false; @@ -268,12 +273,17 @@ parse_single_canon(const uint8_t **payload, const uint8_t *end, { const uint8_t *p = *payload; + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing canon"); + return false; + } uint8_t tag = *p++; out->tag = tag; switch (tag) { case WASM_COMP_CANON_LIFT: { // 0x00 0x00 f: opts: ft: - if (*p != 0x00) { + if (p >= end || *p != 0x00) { set_error_buf_ex(error_buf, error_buf_size, "Invalid canon tag: %02x", tag); return false; @@ -314,7 +324,7 @@ parse_single_canon(const uint8_t **payload, const uint8_t *end, case WASM_COMP_CANON_LOWER: { // 0x01 0x00 f: opts: - if (*p != 0x00) { + if (p >= end || *p != 0x00) { set_error_buf_ex(error_buf, error_buf_size, "Invalid canon tag: %02x", tag); return false; @@ -427,7 +437,7 @@ parse_single_canon(const uint8_t **payload, const uint8_t *end, case WASM_COMP_CANON_CONTEXT_GET: { // 0x0a 0x7f i: - if (*p != 0x7f) { + if (p >= end || *p != 0x7f) { set_error_buf_ex(error_buf, error_buf_size, "Invalid canon tag: %02x", tag); return false; @@ -448,7 +458,7 @@ parse_single_canon(const uint8_t **payload, const uint8_t *end, case WASM_COMP_CANON_CONTEXT_SET: { // 0x0b 0x7f i: - if (*p != 0x7f) { + if (p >= end || *p != 0x7f) { set_error_buf_ex(error_buf, error_buf_size, "Invalid canon tag: %02x", tag); return false; @@ -469,6 +479,11 @@ parse_single_canon(const uint8_t **payload, const uint8_t *end, case WASM_COMP_CANON_YIELD: { // 0x0c cancel?: + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing cancel tag"); + return false; + } uint8_t b = *p++; if (b == WASM_COMP_OPTIONAL_TRUE) { out->canon_data.yield.cancellable = true; @@ -486,6 +501,11 @@ parse_single_canon(const uint8_t **payload, const uint8_t *end, case WASM_COMP_CANON_SUBTASK_CANCEL: { // 0x06 async?: + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing async tag"); + return false; + } uint8_t b = *p++; if (b == WASM_COMP_OPTIONAL_TRUE) { out->canon_data.subtask_cancel.async = true; @@ -567,6 +587,11 @@ parse_single_canon(const uint8_t **payload, const uint8_t *end, out->canon_data.stream_cancel_read_write.stream_type_idx = (uint32_t)type_idx; + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing stream async tag"); + return false; + } uint8_t b = *p++; if (b == WASM_COMP_OPTIONAL_TRUE) { out->canon_data.stream_cancel_read_write.async = true; @@ -593,6 +618,11 @@ parse_single_canon(const uint8_t **payload, const uint8_t *end, out->canon_data.stream_cancel_read_write.stream_type_idx = (uint32_t)type_idx; + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing stream async tag"); + return false; + } uint8_t b = *p++; if (b == WASM_COMP_OPTIONAL_TRUE) { out->canon_data.stream_cancel_read_write.async = true; @@ -692,6 +722,11 @@ parse_single_canon(const uint8_t **payload, const uint8_t *end, out->canon_data.future_cancel_read_write.future_type_idx = (uint32_t)type_idx; + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing future async tag"); + return false; + } uint8_t b = *p++; if (b == WASM_COMP_OPTIONAL_TRUE) { out->canon_data.future_cancel_read_write.async = true; @@ -718,6 +753,11 @@ parse_single_canon(const uint8_t **payload, const uint8_t *end, out->canon_data.future_cancel_read_write.future_type_idx = (uint32_t)type_idx; + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing future async tag"); + return false; + } uint8_t b = *p++; if (b == WASM_COMP_OPTIONAL_TRUE) { out->canon_data.future_cancel_read_write.async = true; @@ -780,6 +820,11 @@ parse_single_canon(const uint8_t **payload, const uint8_t *end, case WASM_COMP_CANON_WAITABLE_SET_WAIT: { // 0x20 cancel?: m: + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing waitable cancel tag"); + return false; + } uint8_t b = *p++; if (b == WASM_COMP_OPTIONAL_TRUE) { out->canon_data.waitable_set_wait_poll.cancellable = true; @@ -808,6 +853,11 @@ parse_single_canon(const uint8_t **payload, const uint8_t *end, case WASM_COMP_CANON_WAITABLE_SET_POLL: { // 0x21 cancel?: m: + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing waitable cancel tag"); + return false; + } uint8_t b = *p++; if (b == WASM_COMP_OPTIONAL_TRUE) { out->canon_data.waitable_set_wait_poll.cancellable = true; @@ -923,19 +973,15 @@ wasm_component_parse_canons_section(const uint8_t **payload, out->count = (uint32_t)canon_count; if (canon_count > 0) { - out->canons = - wasm_runtime_malloc(sizeof(WASMComponentCanon) * canon_count); + out->canons = wasm_component_checked_calloc( + (uint32_t)canon_count, sizeof(WASMComponentCanon), p, end, 1, + "canonical definition", error_buf, error_buf_size); if (!out->canons) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for canons"); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - // Initialize all canons to zero to avoid garbage data - memset(out->canons, 0, sizeof(WASMComponentCanon) * canon_count); - for (uint32_t i = 0; i < canon_count; ++i) { // Check bounds before reading tag if (p >= end) { @@ -948,15 +994,10 @@ wasm_component_parse_canons_section(const uint8_t **payload, if (!parse_single_canon(&p, end, &out->canons[i], error_buf, error_buf_size)) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to parse canon"); - // Free previously parsed canons to avoid leaks - for (uint32_t j = 0; j < i; ++j) { - free_single_canon_allocs(&out->canons[j]); + if (!error_buf || error_buf[0] == '\0') { + set_error_buf_ex(error_buf, error_buf_size, + "Failed to parse canon"); } - wasm_runtime_free(out->canons); - out->canons = NULL; - out->count = 0; if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; @@ -973,104 +1014,20 @@ wasm_component_parse_canons_section(const uint8_t **payload, void wasm_component_free_canons_section(WASMComponentSection *section) { - if (!section || !section->parsed.canon_section - || !section->parsed.canon_section->canons) { + if (!section || !section->parsed.canon_section) { return; } WASMComponentCanonSection *canons_section = section->parsed.canon_section; - for (uint32_t i = 0; i < canons_section->count; i++) { - WASMComponentCanon *canon = &canons_section->canons[i]; - - // Free canon options for each canon that has them - switch (canon->tag) { - case WASM_COMP_CANON_LIFT: - if (canon->canon_data.lift.canon_opts) { - if (canon->canon_data.lift.canon_opts->canon_opts) { - wasm_runtime_free( - canon->canon_data.lift.canon_opts->canon_opts); - } - wasm_runtime_free(canon->canon_data.lift.canon_opts); - } - break; - - case WASM_COMP_CANON_LOWER: - if (canon->canon_data.lower.canon_opts) { - if (canon->canon_data.lower.canon_opts->canon_opts) { - wasm_runtime_free( - canon->canon_data.lower.canon_opts->canon_opts); - } - wasm_runtime_free(canon->canon_data.lower.canon_opts); - } - break; - - case WASM_COMP_CANON_TASK_RETURN: - if (canon->canon_data.task_return.result_list) { - if (canon->canon_data.task_return.result_list->results) { - wasm_runtime_free( - canon->canon_data.task_return.result_list->results); - } - wasm_runtime_free( - canon->canon_data.task_return.result_list); - } - if (canon->canon_data.task_return.canon_opts) { - if (canon->canon_data.task_return.canon_opts->canon_opts) { - wasm_runtime_free(canon->canon_data.task_return - .canon_opts->canon_opts); - } - wasm_runtime_free(canon->canon_data.task_return.canon_opts); - } - break; - - case WASM_COMP_CANON_STREAM_READ: - case WASM_COMP_CANON_STREAM_WRITE: - if (canon->canon_data.stream_read_write.canon_opts) { - if (canon->canon_data.stream_read_write.canon_opts - ->canon_opts) { - wasm_runtime_free(canon->canon_data.stream_read_write - .canon_opts->canon_opts); - } - wasm_runtime_free( - canon->canon_data.stream_read_write.canon_opts); - } - break; - - case WASM_COMP_CANON_FUTURE_READ: - case WASM_COMP_CANON_FUTURE_WRITE: - if (canon->canon_data.future_read_write.canon_opts) { - if (canon->canon_data.future_read_write.canon_opts - ->canon_opts) { - wasm_runtime_free(canon->canon_data.future_read_write - .canon_opts->canon_opts); - } - wasm_runtime_free( - canon->canon_data.future_read_write.canon_opts); - } - break; - - case WASM_COMP_CANON_ERROR_CONTEXT_NEW: - case WASM_COMP_CANON_ERROR_CONTEXT_DEBUG: - if (canon->canon_data.error_context_new_debug.canon_opts) { - if (canon->canon_data.error_context_new_debug.canon_opts - ->canon_opts) { - wasm_runtime_free( - canon->canon_data.error_context_new_debug - .canon_opts->canon_opts); - } - wasm_runtime_free( - canon->canon_data.error_context_new_debug.canon_opts); - } - break; - - default: - // Other canon types don't have nested allocations - break; - } + for (uint32_t i = 0; i < canons_section->count && canons_section->canons; + i++) { + free_single_canon_allocs(&canons_section->canons[i]); } // Free the canons array itself - wasm_runtime_free(canons_section->canons); + if (canons_section->canons) + wasm_runtime_free(canons_section->canons); canons_section->canons = NULL; canons_section->count = 0; // Free the section struct and null the pointer for consistency with other diff --git a/core/iwasm/common/component-model/wasm_component_component_section.c b/core/iwasm/common/component-model/wasm_component_component_section.c index dd7c878cc4..792fededb3 100644 --- a/core/iwasm/common/component-model/wasm_component_component_section.c +++ b/core/iwasm/common/component-model/wasm_component_component_section.c @@ -40,12 +40,9 @@ wasm_component_parse_component_section(const uint8_t **payload, unsigned int new_depth = depth + 1; // Parse the nested component with incremented depth - bool status = wasm_component_parse_sections(*payload, payload_len, out, - args, new_depth); + bool status = wasm_component_parse_sections_ex( + *payload, payload_len, out, args, new_depth, error_buf, error_buf_size); if (!status) { - set_error_buf_ex(error_buf, error_buf_size, - "Could not parse sub component with depth: %d", - new_depth); return false; } @@ -65,4 +62,4 @@ wasm_component_free_component_section(WASMComponentSection *section) wasm_component_free(section->parsed.component); wasm_runtime_free(section->parsed.component); section->parsed.component = NULL; -} \ No newline at end of file +} diff --git a/core/iwasm/common/component-model/wasm_component_core_instance_section.c b/core/iwasm/common/component-model/wasm_component_core_instance_section.c index c4a20c299f..0969cbca0b 100644 --- a/core/iwasm/common/component-model/wasm_component_core_instance_section.c +++ b/core/iwasm/common/component-model/wasm_component_core_instance_section.c @@ -36,8 +36,9 @@ wasm_component_parse_core_instance_section(const uint8_t **payload, return false; } - const uint8_t *p = *payload; - const uint8_t *end = *payload + payload_len; + const uint8_t *start = *payload; + const uint8_t *p = start; + const uint8_t *end = start + payload_len; uint64_t instance_count = 0; if (!read_leb((uint8_t **)&p, end, 32, false, &instance_count, error_buf, @@ -49,20 +50,15 @@ wasm_component_parse_core_instance_section(const uint8_t **payload, out->count = (uint32_t)instance_count; if (instance_count > 0) { - out->instances = - wasm_runtime_malloc(sizeof(WASMComponentCoreInst) * instance_count); + out->instances = wasm_component_checked_calloc( + (uint32_t)instance_count, sizeof(WASMComponentCoreInst), p, end, 1, + "core instance", error_buf, error_buf_size); if (!out->instances) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for core instances"); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - // Initialize all instances to zero to avoid garbage data - memset(out->instances, 0, - sizeof(WASMComponentCoreInst) * instance_count); - for (uint32_t i = 0; i < instance_count; ++i) { // Check bounds before reading tag if (p >= end) { @@ -101,21 +97,16 @@ wasm_component_parse_core_instance_section(const uint8_t **payload, if (arg_len > 0) { out->instances[i].expression.with_args.args = - wasm_runtime_malloc(sizeof(WASMComponentInstArg) - * arg_len); + wasm_component_checked_calloc( + (uint32_t)arg_len, sizeof(WASMComponentInstArg), + p, end, 1, "core instantiate argument", + error_buf, error_buf_size); if (!out->instances[i].expression.with_args.args) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for " - "core instantiate args"); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - // Initialize args to zero - memset(out->instances[i].expression.with_args.args, 0, - sizeof(WASMComponentInstArg) * arg_len); - for (uint32_t j = 0; j < arg_len; ++j) { // core:instantiatearg ::= n: 0x12 // i: Parse core:name (LEB128 length + @@ -160,8 +151,6 @@ wasm_component_parse_core_instance_section(const uint8_t **payload, error_buf, error_buf_size, "Failed to read 0x12 flag identifier for " "core instantiatearg field"); - free_core_name(core_name); - wasm_runtime_free(core_name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; @@ -172,8 +161,6 @@ wasm_component_parse_core_instance_section(const uint8_t **payload, if (!read_leb((uint8_t **)&p, end, 32, false, &instance_idx, error_buf, error_buf_size)) { - free_core_name(core_name); - wasm_runtime_free(core_name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; @@ -205,26 +192,18 @@ wasm_component_parse_core_instance_section(const uint8_t **payload, if (inline_expr_len > 0) { out->instances[i].expression.without_args.inline_expr = - wasm_runtime_malloc( - sizeof(WASMComponentInlineExport) - * inline_expr_len); + wasm_component_checked_calloc( + (uint32_t)inline_expr_len, + sizeof(WASMComponentInlineExport), p, end, 1, + "core inline export", error_buf, + error_buf_size); if (!out->instances[i] .expression.without_args.inline_expr) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for " - "core inline exports"); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - // Initialize inline exports to zero - memset(out->instances[i] - .expression.without_args.inline_expr, - 0, - sizeof(WASMComponentInlineExport) - * inline_expr_len); - for (uint32_t j = 0; j < inline_expr_len; j++) { // core:inlineexport ::= n: // si: @@ -273,14 +252,15 @@ wasm_component_parse_core_instance_section(const uint8_t **payload, set_error_buf_ex(error_buf, error_buf_size, "Failed to allocate memory " "for core sort idx"); - free_core_name(name); - wasm_runtime_free(name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } // Zero-initialize sort_idx memset(sort_idx, 0, sizeof(WASMComponentSortIdx)); + out->instances[i] + .expression.without_args.inline_expr[j] + .sort_idx = sort_idx; bool status = parse_sort_idx(&p, end, sort_idx, error_buf, @@ -289,17 +269,10 @@ wasm_component_parse_core_instance_section(const uint8_t **payload, set_error_buf_ex( error_buf, error_buf_size, "Failed to parse core sort idx"); - wasm_runtime_free(sort_idx); - free_core_name(name); - wasm_runtime_free(name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - - out->instances[i] - .expression.without_args.inline_expr[j] - .sort_idx = sort_idx; } } else { @@ -322,7 +295,8 @@ wasm_component_parse_core_instance_section(const uint8_t **payload, } } if (consumed_len) - *consumed_len = payload_len; + *consumed_len = (uint32_t)(p - start); + *payload = p; return true; } @@ -407,6 +381,7 @@ wasm_create_core_inst_from_expression(WASMComponentCoreInst *core_inst, char *error_buf, uint32 error_buf_size) { uint32 total_size = 0, idx = 0; + uint64 total_size_64; WASMComponentInlineExport *inline_expr = NULL; if (!core_inst) { set_error_buf_ex(error_buf, error_buf_size, "Invalid core instance\n"); @@ -436,13 +411,24 @@ wasm_create_core_inst_from_expression(WASMComponentCoreInst *core_inst, } } - total_size = sizeof(WASMModuleInstance) - + sizeof(WASMExportFuncInstance) * func_count - + sizeof(WASMExportGlobInstance) * global_count - + sizeof(WASMExportTabInstance) * table_count - + sizeof(WASMExportMemInstance) * mem_count; + total_size_64 = sizeof(WASMModuleInstance) + + (uint64)sizeof(WASMExportFuncInstance) * func_count + + (uint64)sizeof(WASMExportGlobInstance) * global_count + + (uint64)sizeof(WASMExportTabInstance) * table_count + + (uint64)sizeof(WASMExportMemInstance) * mem_count; + if (total_size_64 > UINT32_MAX) { + set_error_buf_ex(error_buf, error_buf_size, + "Inline core instance allocation is too large\n"); + return false; + } + total_size = (uint32)total_size_64; WASMModuleInstance *new_inst = (WASMModuleInstance *)wasm_runtime_malloc(total_size); + if (!new_inst) { + set_error_buf_ex(error_buf, error_buf_size, + "Failed to allocate inline core instance\n"); + return false; + } memset(new_inst, 0, total_size); new_inst->export_functions = @@ -575,200 +561,63 @@ wasm_resolve_core_instance(WASMComponentCoreInstSection *instance_section, "Empty core module\n"); return false; } - WASMCoreImports found_imports; - found_imports.func_count = 0; - found_imports.globals_count = 0; - found_imports.tables_count = 0; - found_imports.mem_count = 0; - found_imports.func_instance = NULL; - found_imports.table_instance = NULL; - found_imports.mem_instance = NULL; - found_imports.global_instance = NULL; - bool imports_ok = true; - - if (target_module->import_function_count) - found_imports.func_instance = - (WASMFunctionInstance **)wasm_runtime_malloc( - target_module->import_function_count - * sizeof(WASMFunctionInstance *)); - if (target_module->import_table_count) - found_imports.table_instance = - (WASMTableInstance **)wasm_runtime_malloc( - target_module->import_table_count - * sizeof(WASMTableInstance *)); - if (target_module->import_memory_count) - found_imports.mem_instance = - (WASMMemoryInstance **)wasm_runtime_malloc( - target_module->import_memory_count - * sizeof(WASMMemoryInstance *)); - if (target_module->import_global_count) - found_imports.global_instance = - (WASMGlobalInstance **)wasm_runtime_malloc( - target_module->import_global_count - * sizeof(WASMGlobalInstance *)); - - // Match provided instantiation arguments with the imports required - // by the core_module - if (core_inst->expression.with_args.arg_len - && !wasm_resolve_core_imports( - &core_inst->expression, target_module, comp_instance, - &found_imports, error_buf, error_buf_size)) { - set_error_buf_ex( - error_buf, error_buf_size, - "Imports could not be resolved for core instance\n"); - goto fail_imports; - } + WASMCoreImports found_imports = { 0 }; WASMModuleInstance *core_instance = NULL; - struct InstantiationArgs2 inst_args; - wasm_runtime_instantiation_args_set_defaults(&inst_args); - wasm_runtime_instantiation_args_set_default_stack_size(&inst_args, - STACK_SIZE); - wasm_runtime_instantiation_args_set_host_managed_heap_size( - &inst_args, HEAP_SIZE); - core_instance = - wasm_instantiate(target_module, NULL, NULL, &inst_args, - error_buf, sizeof(error_buf)); - if (!core_instance) { - set_error_buf_ex(error_buf, error_buf_size, - "ERROR: Core module instantiation failed\n"); - goto fail_imports; + bool imports_ok = false; + + if (target_module->import_function_count + && !(found_imports.func_instance = + (WASMFunctionInstance **)wasm_component_checked_calloc( + target_module->import_function_count, + sizeof(WASMFunctionInstance *), NULL, NULL, 0, + "core function import", error_buf, + error_buf_size))) { + goto done_imports; } - - uint32 import_idx = 0, func_idx = 0; - for (func_idx = 0; func_idx < core_instance->e->function_count; - func_idx++) { - core_instance->e->functions[func_idx].module_instance = - core_instance; - core_instance->e->functions[func_idx].func_idx = func_idx; + if (target_module->import_table_count + && !(found_imports.table_instance = + (WASMTableInstance **)wasm_component_checked_calloc( + target_module->import_table_count, + sizeof(WASMTableInstance *), NULL, NULL, 0, + "core table import", error_buf, error_buf_size))) { + goto done_imports; } - for (import_idx = 0; import_idx < found_imports.func_count; - import_idx++) { - if (import_idx > core_instance->e->function_count - || !core_instance->e->functions[import_idx] - .is_import_func) { - goto fail_imports; - } - if (!found_imports.func_instance) { - set_error_buf_ex(error_buf, error_buf_size, - "function not found correctly\n"); - goto fail_imports; - } - if (found_imports.func_instance[import_idx]->is_import_func) { - core_instance->e->functions[import_idx] - .u.func_import->func_ptr_linked = - found_imports.func_instance[import_idx] - ->u.func_import->func_ptr_linked; - core_instance->e->functions[import_idx] - .u.func_import->signature = - found_imports.func_instance[import_idx] - ->u.func_import->signature; - core_instance->e->functions[import_idx] - .u.func_import->attachment = - found_imports.func_instance[import_idx] - ->u.func_import->attachment; - core_instance->e->functions[import_idx] - .u.func_import->call_conv_raw = - found_imports.func_instance[import_idx] - ->u.func_import->call_conv_raw; - core_instance->import_func_ptrs[import_idx] = - found_imports.func_instance[import_idx] - ->u.func_import->func_ptr_linked; - core_instance->e->functions[import_idx].import_func_inst = - found_imports.func_instance[import_idx] - ->import_func_inst; - } - else { - core_instance->e->functions[import_idx].local_types = - found_imports.func_instance[import_idx]->local_types; - core_instance->e->functions[import_idx].local_offsets = - found_imports.func_instance[import_idx]->local_offsets; - core_instance->e->functions[import_idx].u.func = - found_imports.func_instance[import_idx]->u.func; - core_instance->e->functions[import_idx].import_module_inst = - found_imports.func_instance[import_idx] - ->module_instance; - core_instance->e->functions[import_idx].import_func_inst = - found_imports.func_instance[import_idx]; - } -#if WASM_ENABLE_SIMD != 0 && WASM_ENABLE_FAST_INTERP != 0 - /* Copy const_cell_num so fast interp sets outs_area->lp at the - * correct offset when dispatching to this function. Without - * this, params written at operand+0 but callee reads from - * operand+const_cell_num. */ - core_instance->e->functions[import_idx].const_cell_num = - found_imports.func_instance[import_idx]->const_cell_num; -#endif - core_instance->e->functions[import_idx].is_canon_func = - found_imports.func_instance[import_idx]->is_canon_func; - core_instance->e->functions[import_idx].canon_type = - found_imports.func_instance[import_idx]->canon_type; - core_instance->e->functions[import_idx].resource = - found_imports.func_instance[import_idx]->resource; - core_instance->e->functions[import_idx].canon_options = - found_imports.func_instance[import_idx]->canon_options; - core_instance->e->functions[import_idx].component_function = - found_imports.func_instance[import_idx]->component_function; - core_instance->e->functions[import_idx].canon_options = - found_imports.func_instance[import_idx]->canon_options; + if (target_module->import_memory_count + && !(found_imports.mem_instance = + (WASMMemoryInstance **)wasm_component_checked_calloc( + target_module->import_memory_count, + sizeof(WASMMemoryInstance *), NULL, NULL, 0, + "core memory import", error_buf, + error_buf_size))) { + goto done_imports; } - for (import_idx = 0; import_idx < found_imports.tables_count; - import_idx++) { - if (import_idx > core_instance->table_count) { - goto fail_imports; - } - uint32 elem_idx = 0; - for (elem_idx = 0; - elem_idx < core_instance->tables[import_idx]->max_size; - elem_idx++) { - uint32 table_func_idx = - core_instance->tables[import_idx]->elems[elem_idx]; - if (table_func_idx >= core_instance->e->function_count) { - set_error_buf_ex( - error_buf, error_buf_size, - "Invalid function index passed from table\n"); - goto fail_imports; - } - WASMFunctionInstance *table_func_instance = - &core_instance->e->functions[table_func_idx]; - found_imports.table_instance[import_idx]->elems[elem_idx] = - (table_elem_type_t)table_func_instance; - } - found_imports.table_instance[import_idx]->elem_type = - VALUE_TYPE_EXTERNREF; + if (target_module->import_global_count + && !(found_imports.global_instance = + (WASMGlobalInstance **)wasm_component_checked_calloc( + target_module->import_global_count, + sizeof(WASMGlobalInstance *), NULL, NULL, 0, + "core global import", error_buf, + error_buf_size))) { + goto done_imports; } - for (import_idx = 0; import_idx < found_imports.mem_count; - import_idx++) { - if (import_idx > core_instance->memory_count) { - goto fail_imports; - } - /* Free resources of the memory allocated by wasm_instantiate - before replacing with the imported memory */ - WASMMemoryInstance *orig_mem = - core_instance->memories[import_idx]; - if (orig_mem) { - if (orig_mem->heap_handle) { - mem_allocator_destroy(orig_mem->heap_handle); - wasm_runtime_free(orig_mem->heap_handle); - orig_mem->heap_handle = NULL; - } - if (orig_mem->memory_data) { - wasm_deallocate_linear_memory(orig_mem); - orig_mem->memory_data = NULL; - } - } - core_instance->memories[import_idx] = - found_imports.mem_instance[import_idx]; + + /* Resolve every declared core import. Supplying no arguments to + an importing module is an error, not a request for placeholder + allocations that can be patched after its start function. */ + if (target_module->import_count + && !wasm_resolve_core_imports( + &core_inst->expression, target_module, comp_instance, + &found_imports, error_buf, error_buf_size)) { + goto done_imports; } - for (import_idx = 0; import_idx < found_imports.globals_count; - import_idx++) { - if (import_idx > core_instance->e->global_count) { - goto fail_imports; - } - memcpy(&core_instance->e->globals[import_idx], - found_imports.global_instance[import_idx], - sizeof(WASMGlobalInstance)); + + core_instance = wasm_instantiate_with_imports( + target_module, comp_instance, + target_module->import_count ? &found_imports : NULL, + &comp_instance->instantiation_args, error_buf, error_buf_size); + if (!core_instance) { + goto done_imports; } - core_instance->comp_instance = comp_instance; comp_instance->core_module_instances [comp_instance->core_module_instances_count] = core_instance; core_instance->core_instance_idx = @@ -778,23 +627,7 @@ wasm_resolve_core_instance(WASMComponentCoreInstSection *instance_section, [comp_instance->defined_core_instances_count] = core_instance; comp_instance->defined_core_instances_count++; - WASMComponentInstance *root_comp_inst = comp_instance; - while (root_comp_inst->parent) - root_comp_inst = root_comp_inst->parent; - WASMComponent *root_comp = root_comp_inst->component; - -#if WASM_ENABLE_LIBC_WASI != 0 - wasm_runtime_set_wasi_ctx((WASMModuleInstanceCommon *)core_instance, - comp_instance->wasi_ctx); - WASIContext *wasi_ctx = wasm_runtime_get_wasi_ctx( - (WASMModuleInstanceCommon *)core_instance); - if (wasi_ctx) { - wasi_ctx->wasi_options = root_comp->wasi_args.wasi_options; - } -#endif - goto done_imports; - fail_imports: - imports_ok = false; + imports_ok = true; done_imports: if (found_imports.func_instance) wasm_runtime_free((void *)found_imports.func_instance); diff --git a/core/iwasm/common/component-model/wasm_component_core_module_section.c b/core/iwasm/common/component-model/wasm_component_core_module_section.c index 38fa336859..832547faed 100644 --- a/core/iwasm/common/component-model/wasm_component_core_module_section.c +++ b/core/iwasm/common/component-model/wasm_component_core_module_section.c @@ -31,15 +31,37 @@ wasm_component_parse_core_module_section(const uint8_t **payload, LOG_DEBUG(" Module section: embedded Core WebAssembly module\n"); - // Use the core wasm loader to parse the module - wasm_module_t mod = wasm_runtime_load_ex((uint8 *)*payload, payload_len, - args, error_buf, error_buf_size); + /* + * Component core-module imports are linked by the enclosing component + * instance. Never consult or mutate the process-global native registry + * while parsing the embedded module. + */ + LoadArgs core_args = { 0 }; + if (args) { + core_args = *args; + } + core_args.no_resolve = true; + core_args.is_component = false; + wasm_module_t mod = wasm_runtime_load_ex( + (uint8 *)*payload, payload_len, &core_args, error_buf, error_buf_size); if (!mod) { LOG_DEBUG(" Failed to load embedded core wasm module: %s\n", error_buf); return false; } +#if WASM_ENABLE_MULTI_MODULE != 0 + /* Embedded component modules are owned by the component tree, not by the + * process-wide core-module registry. wasm_runtime_load_ex() registers all + * core modules for legacy multi-module resolution, so transfer ownership + * back immediately and let component teardown unload this module. */ + if (!wasm_runtime_unregister_module(mod)) { + set_error_buf_ex(error_buf, error_buf_size, + "failed to take ownership of embedded core module"); + return false; + } +#endif + // Print some basic info about the embedded module LOG_DEBUG(" Types: %u function types\n", wasm_runtime_get_import_count(mod)); @@ -110,4 +132,4 @@ wasm_component_free_core_module_section(WASMComponentSection *section) } wasm_runtime_free(section->parsed.core_module); section->parsed.core_module = NULL; -} \ No newline at end of file +} diff --git a/core/iwasm/common/component-model/wasm_component_core_type_section.c b/core/iwasm/common/component-model/wasm_component_core_type_section.c index 422b17c0a7..e741d9b67a 100644 --- a/core/iwasm/common/component-model/wasm_component_core_type_section.c +++ b/core/iwasm/common/component-model/wasm_component_core_type_section.c @@ -17,6 +17,11 @@ parse_core_limits(const uint8_t **payload, const uint8_t *end, WASMComponentCoreLimits *out, char *error_buf, uint32_t error_buf_size) { + if (!payload || !*payload || !out || !end || *payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing core limits"); + return false; + } const uint8_t *p = *payload; uint8_t tag = *p++; @@ -69,6 +74,11 @@ parse_core_heaptype(const uint8_t **payload, const uint8_t *end, WASMComponentCoreHeapType *out, char *error_buf, uint32_t error_buf_size) { + if (!payload || !*payload || !out || !end || *payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing core heaptype"); + return false; + } const uint8_t *p = *payload; if (p >= end) { set_error_buf_ex(error_buf, error_buf_size, @@ -110,6 +120,11 @@ parse_core_valtype(const uint8_t **payload, const uint8_t *end, WASMComponentCoreValType *out, char *error_buf, uint32_t error_buf_size) { + if (!payload || !*payload || !out || !end || *payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing core valtype"); + return false; + } const uint8_t *p = *payload; uint8_t tag = *p++; @@ -191,6 +206,11 @@ parse_core_import_desc(const uint8_t **payload, const uint8_t *end, WASMComponentCoreImportDesc *out, char *error_buf, uint32_t error_buf_size) { + if (!payload || !*payload || !out || !end || *payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing core import descriptor"); + return false; + } const uint8_t *p = *payload; uint8_t tag = *p++; @@ -210,6 +230,11 @@ parse_core_import_desc(const uint8_t **payload, const uint8_t *end, case WASM_CORE_IMPORTDESC_TABLE: { + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing core table reference type"); + return false; + } uint8_t ref_type_tag = *p++; if (ref_type_tag == WASM_CORE_REFTYPE_FUNC_REF) { out->type = WASM_CORE_IMPORTDESC_TABLE; @@ -272,6 +297,11 @@ parse_core_import_desc(const uint8_t **payload, const uint8_t *end, } // mut ::= 0x00 => const, 0x01 => var (spec) + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing core global mutability"); + return false; + } uint8_t mutable_tag = *p++; if (mutable_tag == WASM_CORE_GLOBAL_MUTABLE) { out->desc.global_type.is_mutable = true; @@ -304,6 +334,11 @@ parse_core_import(const uint8_t **payload, const uint8_t *end, WASMComponentCoreImport *out, char *error_buf, uint32_t error_buf_size) { + if (!payload || !*payload || !out || !end || *payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing core import"); + return false; + } const uint8_t *p = *payload; WASMComponentCoreName *mod_name = NULL; @@ -344,6 +379,11 @@ parse_alias_target(const uint8_t **payload, const uint8_t *end, WASMComponentCoreAliasTarget *out, char *error_buf, uint32_t error_buf_size) { + if (!payload || !*payload || !out || !end || *payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing core alias target"); + return false; + } const uint8_t *p = *payload; uint8_t tag = *p++; @@ -434,6 +474,12 @@ parse_core_module_decl(const uint8_t **payload, const uint8_t *end, WASMComponentCoreModuleDecl *out, char *error_buf, uint32_t error_buf_size) { + if (!payload || !*payload || !out || !end || *payload >= end) { + set_error_buf_ex( + error_buf, error_buf_size, + "Unexpected end while parsing core module declaration"); + return false; + } const uint8_t *p = *payload; uint8_t tag = *p++; out->tag = tag; @@ -545,16 +591,12 @@ parse_core_moduletype(const uint8_t **payload, const uint8_t *end, out->decl_count = count; if (count > 0) { - out->declarations = - wasm_runtime_malloc(sizeof(WASMComponentCoreModuleDecl) * count); + out->declarations = wasm_component_checked_calloc( + count, sizeof(WASMComponentCoreModuleDecl), p, end, 1, + "core module declaration", error_buf, error_buf_size); if (!out->declarations) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for declarations"); return false; } - // Zero-initialize declarations array - memset(out->declarations, 0, - sizeof(WASMComponentCoreModuleDecl) * count); for (uint32_t i = 0; i < count; i++) { if (!parse_core_module_decl(&p, end, &out->declarations[i], @@ -593,6 +635,7 @@ parse_single_core_type(const uint8_t **payload, const uint8_t *end, "OOM allocating core moduletype"); return false; } + memset(out->type.moduletype, 0, sizeof(WASMComponentCoreModuleType)); if (!parse_core_moduletype(&p, end, out->type.moduletype, error_buf, error_buf_size)) { return false; @@ -661,14 +704,13 @@ wasm_component_parse_core_type_section(const uint8_t **payload, out->count = count; if (count > 0) { - out->types = wasm_runtime_malloc(sizeof(WASMComponentCoreType) * count); + out->types = wasm_component_checked_calloc( + count, sizeof(WASMComponentCoreType), p, end, 1, "core type", + error_buf, error_buf_size); if (!out->types) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for types"); return false; } - memset(out->types, 0, sizeof(WASMComponentCoreType) * count); for (uint32_t i = 0; i < count; i++) { WASMComponentCoreDefType *dt = wasm_runtime_malloc(sizeof(WASMComponentCoreDefType)); @@ -678,12 +720,11 @@ wasm_component_parse_core_type_section(const uint8_t **payload, return false; } memset(dt, 0, sizeof(WASMComponentCoreDefType)); + out->types[i].deftype = dt; if (!parse_single_core_type(&p, end, dt, error_buf, error_buf_size)) { - wasm_runtime_free(dt); return false; } - out->types[i].deftype = dt; } } @@ -1002,4 +1043,4 @@ free_core_module_subtype(WASMComponentCoreModuleSubType *module_subtype) free_core_comptype(module_subtype->comptype); wasm_runtime_free(module_subtype->comptype); } -} \ No newline at end of file +} diff --git a/core/iwasm/common/component-model/wasm_component_export.c b/core/iwasm/common/component-model/wasm_component_export.c index a24d67594f..cb80bc5d27 100644 --- a/core/iwasm/common/component-model/wasm_component_export.c +++ b/core/iwasm/common/component-model/wasm_component_export.c @@ -7,20 +7,6 @@ #include "wasm_component.h" #include "wasm_component_runtime.h" -static bool is_component = false; - -bool -is_component_runtime() -{ - return is_component; -} - -void -set_component_runtime(bool type) -{ - is_component = type; -} - bool wasm_resolve_exports(WASMComponentExportSection *export_section, WASMComponentInstance *comp_instance, char *error_buf, diff --git a/core/iwasm/common/component-model/wasm_component_export.h b/core/iwasm/common/component-model/wasm_component_export.h index a4233a11fa..3a8f1320ef 100644 --- a/core/iwasm/common/component-model/wasm_component_export.h +++ b/core/iwasm/common/component-model/wasm_component_export.h @@ -6,11 +6,4 @@ #ifndef WASM_COMPONENT_EXPORT_H #define WASM_COMPONENT_EXPORT_H -#include "stdbool.h" - -bool -is_component_runtime(); -void -set_component_runtime(bool type); - -#endif \ No newline at end of file +#endif diff --git a/core/iwasm/common/component-model/wasm_component_exports_section.c b/core/iwasm/common/component-model/wasm_component_exports_section.c index 3b48cd5ce6..7490e64c7c 100644 --- a/core/iwasm/common/component-model/wasm_component_exports_section.c +++ b/core/iwasm/common/component-model/wasm_component_exports_section.c @@ -52,18 +52,14 @@ wasm_component_parse_exports_section(const uint8_t **payload, out->count = export_count; if (export_count > 0) { - out->exports = - wasm_runtime_malloc(sizeof(WASMComponentExport) * export_count); + out->exports = wasm_component_checked_calloc( + export_count, sizeof(WASMComponentExport), p, end, 1, + "component export", error_buf, error_buf_size); if (!out->exports) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for exports"); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - // Ensure all fields (including optional extern_desc) are initialized to - // NULL/0 - memset(out->exports, 0, sizeof(WASMComponentExport) * export_count); // Parsing every export for (uint32_t i = 0; i < export_count; i++) { @@ -86,6 +82,8 @@ wasm_component_parse_exports_section(const uint8_t **payload, *consumed_len = (uint32_t)(p - *payload); return false; } + memset(export_name, 0, sizeof(*export_name)); + out->exports[i].export_name = export_name; bool status = parse_component_export_name( &p, end, export_name, error_buf, error_buf_size); @@ -93,32 +91,28 @@ wasm_component_parse_exports_section(const uint8_t **payload, set_error_buf_ex(error_buf, error_buf_size, "Failed to parse component name for export %u", i); - wasm_runtime_free(export_name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - out->exports[i].export_name = export_name; - // Parsing 'sortidx' out->exports[i].sort_idx = wasm_runtime_malloc(sizeof(WASMComponentSortIdx)); if (!out->exports[i].sort_idx) { set_error_buf_ex(error_buf, error_buf_size, "Failed to allocate memory for sort_idx"); - wasm_runtime_free(export_name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } + memset(out->exports[i].sort_idx, 0, + sizeof(*out->exports[i].sort_idx)); status = parse_sort_idx(&p, end, out->exports[i].sort_idx, error_buf, error_buf_size, false); if (!status) { set_error_buf_ex(error_buf, error_buf_size, "Failed to parse sort_idx for export %u", i); - wasm_runtime_free(out->exports[i].sort_idx); - wasm_runtime_free(export_name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; @@ -135,9 +129,6 @@ wasm_component_parse_exports_section(const uint8_t **payload, "Unexpected end of buffer when reading " "optional extern_desc for export %u", i); - wasm_runtime_free(out->exports[i].sort_idx->sort); - wasm_runtime_free(out->exports[i].sort_idx); - wasm_runtime_free(export_name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; @@ -149,30 +140,33 @@ wasm_component_parse_exports_section(const uint8_t **payload, "Unexpected end of buffer when parsing " "extern_desc for export %u", i); - wasm_runtime_free(out->exports[i].sort_idx->sort); - wasm_runtime_free(out->exports[i].sort_idx); - wasm_runtime_free(export_name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } WASMComponentExternDesc *extern_desc = wasm_runtime_malloc(sizeof(WASMComponentExternDesc)); + if (!extern_desc) { + set_error_buf_ex(error_buf, error_buf_size, + "Failed to allocate extern_desc for " + "export %u", + i); + if (consumed_len) + *consumed_len = (uint32_t)(p - *payload); + return false; + } + memset(extern_desc, 0, sizeof(*extern_desc)); + out->exports[i].extern_desc = extern_desc; bool extern_status = parse_extern_desc( &p, end, extern_desc, error_buf, error_buf_size); if (!extern_status) { set_error_buf_ex( error_buf, error_buf_size, "Failed to parse extern_desc for export %u", i); - wasm_runtime_free(extern_desc); - wasm_runtime_free(out->exports[i].sort_idx->sort); - wasm_runtime_free(out->exports[i].sort_idx); - wasm_runtime_free(export_name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - out->exports[i].extern_desc = extern_desc; LOG_DEBUG("Extern desc added\n"); } else if (opt_extern_desc == WASM_COMP_OPTIONAL_FALSE) { @@ -237,4 +231,4 @@ wasm_component_free_exports_section(WASMComponentSection *section) } wasm_runtime_free(export_sec); section->parsed.export_section = NULL; -} \ No newline at end of file +} diff --git a/core/iwasm/common/component-model/wasm_component_flat.c b/core/iwasm/common/component-model/wasm_component_flat.c index 09aac2801b..1d9d870a94 100644 --- a/core/iwasm/common/component-model/wasm_component_flat.c +++ b/core/iwasm/common/component-model/wasm_component_flat.c @@ -469,6 +469,8 @@ flatten_functype(LiftLowerContext *cx, WASMComponentFuncTypeInstance *ft, // Clean up params on failure if (out->params.val_types) { wasm_runtime_free(out->params.val_types); + out->params.val_types = NULL; + out->params.count = 0; } set_component_exception(cx, "failed to build core results"); return false; @@ -515,6 +517,8 @@ flatten_functype(LiftLowerContext *cx, WASMComponentFuncTypeInstance *ft, // Clean up params on failure if (out->params.val_types) { wasm_runtime_free(out->params.val_types); + out->params.val_types = NULL; + out->params.count = 0; } set_component_exception(cx, "failed to build core results"); return false; diff --git a/core/iwasm/common/component-model/wasm_component_helpers.c b/core/iwasm/common/component-model/wasm_component_helpers.c index 8668eca458..e7d04d09c1 100644 --- a/core/iwasm/common/component-model/wasm_component_helpers.c +++ b/core/iwasm/common/component-model/wasm_component_helpers.c @@ -28,11 +28,69 @@ set_error_buf_ex(char *error_buf, uint32_t error_buf_size, const char *format, } } +void * +wasm_component_checked_calloc(uint32_t count, uint32_t element_size, + const uint8_t *payload, const uint8_t *end, + uint32_t min_element_size, + const char *description, char *error_buf, + uint32_t error_buf_size) +{ + uint64_t allocation_size; + void *result; + + if (count == 0 || element_size == 0) { + set_error_buf_ex(error_buf, error_buf_size, + "Invalid %s array dimensions", + description ? description : "component parser"); + return NULL; + } + + if (payload || end) { + if (!payload || !end || payload > end) { + set_error_buf_ex(error_buf, error_buf_size, + "Invalid payload bounds for %s array", + description ? description : "component parser"); + return NULL; + } + if (min_element_size > 0 + && (uint64_t)count > (uint64_t)(end - payload) / min_element_size) { + set_error_buf_ex(error_buf, error_buf_size, + "%s count %u exceeds remaining payload", + description ? description : "Component parser", + count); + return NULL; + } + } + + allocation_size = (uint64_t)count * element_size; + if (allocation_size > UINT32_MAX) { + set_error_buf_ex(error_buf, error_buf_size, + "%s array is too large (%u elements)", + description ? description : "Component parser", count); + return NULL; + } + + result = wasm_runtime_malloc((uint32_t)allocation_size); + if (!result) { + set_error_buf_ex(error_buf, error_buf_size, + "Failed to allocate %s array", + description ? description : "component parser"); + return NULL; + } + memset(result, 0, (uint32_t)allocation_size); + return result; +} + bool parse_result_list(const uint8_t **payload, const uint8_t *end, WASMComponentResultList **out, char *error_buf, uint32_t error_buf_size) { + if (!payload || !*payload || !out || *payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing result list"); + return false; + } const uint8_t *p = *payload; // Allocate memory for the result list structure @@ -74,6 +132,11 @@ parse_result_list(const uint8_t **payload, const uint8_t *end, { (*out)->results = NULL; // Binary.md encodes empty resultlist as 0x01 0x00 + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing empty result list terminator"); + return false; + } uint8_t terminator = *p++; if (terminator != 0x00) { set_error_buf_ex(error_buf, error_buf_size, @@ -99,13 +162,19 @@ bool parse_sort(const uint8_t **payload, const uint8_t *end, WASMComponentSort *out, char *error_buf, uint32_t error_buf_size, bool is_core) { - if (!payload || !*payload || !out || !end) { + if (!payload || !*payload || !out || !end || *payload >= end) { set_error_buf_ex(error_buf, error_buf_size, - "Invalid payload or output pointer"); + "Unexpected end while parsing sort"); return false; } const uint8_t *p = *payload; + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end of buffer when parsing sort"); + return false; + } + if (!is_core) { // Read the first byte, which is the main sort out->sort = *p++; @@ -197,6 +266,7 @@ parse_sort_idx(const uint8_t **payload, const uint8_t *end, parse_sort(payload, end, out->sort, error_buf, error_buf_size, is_core); if (!status) { wasm_runtime_free(out->sort); + out->sort = NULL; return false; } @@ -205,6 +275,7 @@ parse_sort_idx(const uint8_t **payload, const uint8_t *end, if (!read_leb((uint8_t **)payload, end, 32, false, &idx_leb, error_buf, error_buf_size)) { wasm_runtime_free(out->sort); + out->sort = NULL; return false; } @@ -225,6 +296,12 @@ parse_extern_desc(const uint8_t **payload, const uint8_t *end, const uint8_t *p = *payload; + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing extern descriptor"); + return false; + } + out->type = *p++; switch (out->type) { @@ -232,6 +309,11 @@ parse_extern_desc(const uint8_t **payload, const uint8_t *end, { // 0x00 0x11 i: => (core module (type i)) // Read type_specific byte (should be 0x11) + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing core module descriptor type"); + return false; + } uint8_t type_specific = *p++; if (type_specific != 0x11) { *payload = p; // Update pointer even on error @@ -267,6 +349,11 @@ parse_extern_desc(const uint8_t **payload, const uint8_t *end, // valuebound ::= 0x00 i: => (eq i) // | 0x01 t: => t // Read value bound tag (0x00 = eq, 0x01 = type) + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing value bound tag"); + return false; + } uint8_t value_bound_tag = *p++; out->extern_desc.value.value_bound = wasm_runtime_malloc(sizeof(WASMComponentValueBound)); @@ -286,6 +373,7 @@ parse_extern_desc(const uint8_t **payload, const uint8_t *end, if (!read_leb((uint8_t **)&p, end, 32, false, &value_idx, error_buf, error_buf_size)) { wasm_runtime_free(out->extern_desc.value.value_bound); + out->extern_desc.value.value_bound = NULL; *payload = p; // Update pointer even on error return false; } @@ -304,6 +392,7 @@ parse_extern_desc(const uint8_t **payload, const uint8_t *end, error_buf, error_buf_size, "Failed to allocate memory for value_type"); wasm_runtime_free(out->extern_desc.value.value_bound); + out->extern_desc.value.value_bound = NULL; *payload = p; // Update pointer even on error return false; } @@ -315,7 +404,10 @@ parse_extern_desc(const uint8_t **payload, const uint8_t *end, error_buf, error_buf_size)) { wasm_runtime_free(out->extern_desc.value.value_bound ->bound.value_type); + out->extern_desc.value.value_bound->bound.value_type = + NULL; wasm_runtime_free(out->extern_desc.value.value_bound); + out->extern_desc.value.value_bound = NULL; *payload = p; // Update pointer even on error return false; } @@ -327,6 +419,7 @@ parse_extern_desc(const uint8_t **payload, const uint8_t *end, "Unknown value bound tag: 0x%02X", value_bound_tag); wasm_runtime_free(out->extern_desc.value.value_bound); + out->extern_desc.value.value_bound = NULL; *payload = p; // Update pointer even on error return false; } @@ -338,6 +431,11 @@ parse_extern_desc(const uint8_t **payload, const uint8_t *end, // Parse typebound for extern_desc type // typebound ::= 0x00 i: => (eq i) // | 0x01 => (sub resource) + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing type bound tag"); + return false; + } uint8_t type_bound_tag = *p++; out->extern_desc.type.type_bound = wasm_runtime_malloc(sizeof(WASMComponentTypeBound)); @@ -357,6 +455,7 @@ parse_extern_desc(const uint8_t **payload, const uint8_t *end, if (!read_leb((uint8_t **)&p, end, 32, false, &type_idx, error_buf, error_buf_size)) { wasm_runtime_free(out->extern_desc.type.type_bound); + out->extern_desc.type.type_bound = NULL; *payload = p; // Update pointer even on error return false; } @@ -369,6 +468,7 @@ parse_extern_desc(const uint8_t **payload, const uint8_t *end, "Invalid typebound tag: 0x%02x", type_bound_tag); wasm_runtime_free(out->extern_desc.type.type_bound); + out->extern_desc.type.type_bound = NULL; *payload = p; return false; } @@ -565,9 +665,9 @@ parse_component_import_name(const uint8_t **payload, const uint8_t *end, WASMComponentImportName *out, char *error_buf, uint32_t error_buf_size) { - if (!payload || !*payload || !out || !end) { + if (!payload || !*payload || !out || !end || *payload >= end) { set_error_buf_ex(error_buf, error_buf_size, - "Invalid payload or output pointer"); + "Unexpected end while parsing import name"); return false; } @@ -657,9 +757,9 @@ parse_component_export_name(const uint8_t **payload, const uint8_t *end, WASMComponentExportName *out, char *error_buf, uint32_t error_buf_size) { - if (!payload || !*payload || !out || !end) { + if (!payload || !*payload || !out || !end || *payload >= end) { set_error_buf_ex(error_buf, error_buf_size, - "Invalid payload or output pointer"); + "Unexpected end while parsing export name"); return false; } @@ -874,9 +974,9 @@ parse_valtype(const uint8_t **payload, const uint8_t *end, WASMComponentValueType *out, char *error_buf, uint32_t error_buf_size) { - if (!payload || !*payload || !out || !end) { + if (!payload || !*payload || !out || !end || *payload >= end) { set_error_buf_ex(error_buf, error_buf_size, - "Invalid payload or output pointer"); + "Unexpected end while parsing value type"); return false; } @@ -922,8 +1022,6 @@ parse_labelvaltype(const uint8_t **payload, const uint8_t *end, // Parse the value type (required) out->value_type = wasm_runtime_malloc(sizeof(WASMComponentValueType)); if (!out->value_type) { - free_core_name(out->label); - wasm_runtime_free(out->label); set_error_buf_ex(error_buf, error_buf_size, "Failed to allocate memory for label value type"); return false; @@ -931,11 +1029,6 @@ parse_labelvaltype(const uint8_t **payload, const uint8_t *end, memset(out->value_type, 0, sizeof(WASMComponentValueType)); if (!parse_valtype(payload, end, out->value_type, error_buf, error_buf_size)) { - wasm_runtime_free(out->value_type); - out->value_type = NULL; - free_core_name(out->label); - wasm_runtime_free(out->label); - out->label = NULL; return false; } @@ -960,6 +1053,11 @@ parse_case(const uint8_t **payload, const uint8_t *end, } // Parse optional valtype + if (*payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing case optional value type tag"); + return false; + } uint8_t optional_tag = *(*payload)++; if (optional_tag == WASM_COMP_OPTIONAL_TRUE) { @@ -968,14 +1066,11 @@ parse_case(const uint8_t **payload, const uint8_t *end, if (!out->value_type) { set_error_buf_ex(error_buf, error_buf_size, "Failed to allocate memory for case value type"); - free_core_name(out->label); return false; } if (!parse_valtype(payload, end, out->value_type, error_buf, error_buf_size)) { - wasm_runtime_free(out->value_type); - free_core_name(out->label); return false; } } @@ -991,15 +1086,15 @@ parse_case(const uint8_t **payload, const uint8_t *end, } // Parse the ending 0x00 + if (*payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, "Missing case terminator"); + return false; + } uint8_t ending_byte = *(*payload)++; if (ending_byte != WASM_COMP_CASE_END) { set_error_buf_ex(error_buf, error_buf_size, "Expected 0x00 at end of case, got 0x%02x", ending_byte); - if (out->value_type) { - wasm_runtime_free(out->value_type); - } - free_core_name(out->label); return false; } @@ -1046,8 +1141,6 @@ parse_label_prime(const uint8_t **payload, const uint8_t *end, // Allocate and copy the label string (*out)->name = wasm_runtime_malloc(len + 1); if (!(*out)->name) { - wasm_runtime_free(*out); - *out = NULL; set_error_buf_ex(error_buf, error_buf_size, "Failed to allocate memory for label string"); return false; @@ -1135,18 +1228,13 @@ parse_label_prime_vector(const uint8_t **payload, const uint8_t *end, uint32_t count = (uint32_t)count_leb; if (count > 0) { - // Allocate the labels array - *out_labels = - wasm_runtime_malloc(sizeof(WASMComponentCoreName) * count); + *out_labels = wasm_component_checked_calloc( + count, sizeof(WASMComponentCoreName), p, end, 1, "component label", + error_buf, error_buf_size); if (!*out_labels) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for labels array"); return false; } - // Initialize to zero - memset(*out_labels, 0, sizeof(WASMComponentCoreName) * count); - // Parse each label for (uint32_t i = 0; i < count; i++) { if (!fill_label_prime(&p, end, &((*out_labels)[i]), error_buf, diff --git a/core/iwasm/common/component-model/wasm_component_host_resource.c b/core/iwasm/common/component-model/wasm_component_host_resource.c index 919b771a08..1865ce4343 100644 --- a/core/iwasm/common/component-model/wasm_component_host_resource.c +++ b/core/iwasm/common/component-model/wasm_component_host_resource.c @@ -26,6 +26,10 @@ HostResourceTable *g_host_resource_table = NULL; // Counter starts at 1 static uint32_t next_id_counter = 1; +/* Table initialization/destruction require a quiescent runtime. This lock + * protects ID allocation while component instances are running. */ +static korp_mutex host_resource_id_lock; +static bool host_resource_id_lock_inited = false; // Hash function for uint32_t keys static uint32 @@ -52,25 +56,22 @@ destroy_u32_key_hash_map(void *key) static void destroy_host_resource_hash_map(void *value) { - HostResource *hr = (HostResource *)value; - if (hr) { - if (hr->data) { - if (hr->dtor) { - hr->dtor(hr->data); - } - BH_FREE(hr->data); - } - BH_FREE(hr); - } + destroy_host_resource((HostResource *)value); } bool instantiate_host_resource_table() { if (g_host_resource_table != NULL) { - LOG_WARNING("Global host resource table already initialized\n"); + return true; + } + + if (os_mutex_init(&host_resource_id_lock) != BHT_OK) { + LOG_ERROR("Failed to create host resource ID lock.\n"); return false; } + host_resource_id_lock_inited = true; + next_id_counter = 1; // Create HashMap: size, with lock for thread safety, hash_func, key_equal, // key_destroy, value_destroy @@ -80,6 +81,8 @@ instantiate_host_resource_table() if (!g_host_resource_table) { LOG_ERROR("Failed to create host resource table.\n"); + os_mutex_destroy(&host_resource_id_lock); + host_resource_id_lock_inited = false; return false; } @@ -96,6 +99,14 @@ destroy_host_resource_table() // HashMap destroy will call destroy functions for all keys/values bh_hash_map_destroy(g_host_resource_table); g_host_resource_table = NULL; + + if (host_resource_id_lock_inited) { + os_mutex_lock(&host_resource_id_lock); + next_id_counter = 1; + os_mutex_unlock(&host_resource_id_lock); + os_mutex_destroy(&host_resource_id_lock); + host_resource_id_lock_inited = false; + } } HostResourceTable * @@ -109,9 +120,17 @@ host_resource_table_get_next_id(HostResourceType type) { uint32_t id = 0; + if (!host_resource_id_lock_inited) { + LOG_ERROR("Host resource table is not initialized.\n"); + return 0; + } + + os_mutex_lock(&host_resource_id_lock); + // Check if counter would overflow the 24-bit space if (next_id_counter > HOST_RESOURCE_ID_MASK) { LOG_ERROR("Host resource ID counter overflow.\n"); + os_mutex_unlock(&host_resource_id_lock); return 0; } @@ -120,6 +139,8 @@ host_resource_table_get_next_id(HostResourceType type) next_id_counter++; + os_mutex_unlock(&host_resource_id_lock); + return id; } diff --git a/core/iwasm/common/component-model/wasm_component_imports_section.c b/core/iwasm/common/component-model/wasm_component_imports_section.c index e058af8bd5..988c8f6977 100644 --- a/core/iwasm/common/component-model/wasm_component_imports_section.c +++ b/core/iwasm/common/component-model/wasm_component_imports_section.c @@ -5,6 +5,7 @@ #include "wasm_component.h" #include "wasm_component_runtime.h" +#include "wasm_component_flat.h" #include #include #include @@ -12,7 +13,9 @@ #include "wasm_runtime_common.h" #include "wasm_export.h" #include +#if WASM_ENABLE_LIBC_WASI_P2 != 0 #include "../../libraries/libc-wasi-p2/libc_wasi_p2_wrapper.h" +#endif // Section 10: imports section bool @@ -47,19 +50,15 @@ wasm_component_parse_imports_section(const uint8_t **payload, out->count = import_count; if (import_count > 0) { - out->imports = - wasm_runtime_malloc(sizeof(WASMComponentImport) * import_count); + out->imports = wasm_component_checked_calloc( + import_count, sizeof(WASMComponentImport), p, end, 1, + "component import", error_buf, error_buf_size); if (!out->imports) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for imports"); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - // Initialize all imports to zero to avoid garbage data - memset(out->imports, 0, sizeof(WASMComponentImport) * import_count); - for (uint32_t i = 0; i < import_count; ++i) { // importname' ::= 0x00 len: in: => in (if len = // |in|) @@ -77,6 +76,7 @@ wasm_component_parse_imports_section(const uint8_t **payload, } // Initialize the struct to zero to avoid garbage data memset(import_name, 0, sizeof(WASMComponentImportName)); + out->imports[i].import_name = import_name; bool status = parse_component_import_name( &p, end, import_name, error_buf, error_buf_size); @@ -84,7 +84,6 @@ wasm_component_parse_imports_section(const uint8_t **payload, set_error_buf_ex(error_buf, error_buf_size, "Failed to parse component name for import %u", i); - wasm_runtime_free(import_name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; @@ -104,13 +103,13 @@ wasm_component_parse_imports_section(const uint8_t **payload, if (!extern_desc) { set_error_buf_ex(error_buf, error_buf_size, "Failed to allocate memory for extern_desc"); - wasm_runtime_free(import_name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } // Initialize the struct to zero to avoid garbage data memset(extern_desc, 0, sizeof(WASMComponentExternDesc)); + out->imports[i].extern_desc = extern_desc; status = parse_extern_desc(&p, end, extern_desc, error_buf, error_buf_size); @@ -118,16 +117,12 @@ wasm_component_parse_imports_section(const uint8_t **payload, set_error_buf_ex(error_buf, error_buf_size, "Failed to parse extern_desc for import %u", i); - wasm_runtime_free(extern_desc); - wasm_runtime_free(import_name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } // Store the parsed import (importname' + externdesc) - out->imports[i].import_name = import_name; - out->imports[i].extern_desc = extern_desc; } } @@ -184,22 +179,31 @@ wasm_import_find_in_args(WASMImport *import, WASMInstExpr *expression, const char *module_name = NULL, *field_name = NULL; WASMComponentInstArg *arg = NULL; WASMModuleInstance *arg_instance = NULL; + WASMCoreExport resolved_export = { 0 }; + WASMCoreExport *found_export = NULL; + bool found = false; module_name = import->u.names.module_name; field_name = import->u.names.field_name; - if (!module_name) { + if (!module_name || !field_name) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: invalid core import name\n"); return NULL; } - WASMCoreExport *found_export = wasm_runtime_malloc(sizeof(WASMCoreExport)); - found_export->kind = import->kind; + resolved_export.kind = import->kind; for (arg_idx = 0; arg_idx < expression->with_args.arg_len; arg_idx++) { arg = &expression->with_args.args[arg_idx]; + if (!arg->name || !arg->name->name) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: invalid instantiation argument name\n"); + return NULL; + } // Find Module instance by name if (!strcmp(module_name, arg->name->name)) { if (arg->idx.instance_idx - > comp_instance->core_module_instances_count) { + >= comp_instance->core_module_instances_count) { set_error_buf_ex(error_buf, error_buf_size, "ERROR: Instantiation argument Core instance " "%d not yet defined\n", @@ -208,6 +212,13 @@ wasm_import_find_in_args(WASMImport *import, WASMInstExpr *expression, } arg_instance = comp_instance->core_module_instances[arg->idx.instance_idx]; + if (!arg_instance) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Instantiation argument Core instance " + "%d is null\n", + arg->idx.instance_idx); + return NULL; + } // Search thought the respective export list of the provided core // instance switch (import->kind) { @@ -221,10 +232,11 @@ wasm_import_find_in_args(WASMImport *import, WASMInstExpr *expression, // TODO: Will need to implement a check for function // signature as well LOG_DEBUG("Func import %s found", field_name); - found_export->exp.func_instance = + resolved_export.exp.func_instance = arg_instance->export_functions[export_idx] .function; - return found_export; + found = true; + break; } } break; @@ -236,9 +248,10 @@ wasm_import_find_in_args(WASMImport *import, WASMInstExpr *expression, field_name, arg_instance->export_tables[export_idx].name)) { LOG_DEBUG("table import %s found", field_name); - found_export->exp.table_instance = + resolved_export.exp.table_instance = arg_instance->export_tables[export_idx].table; - return found_export; + found = true; + break; } } break; @@ -250,10 +263,11 @@ wasm_import_find_in_args(WASMImport *import, WASMInstExpr *expression, arg_instance->export_memories[export_idx] .name)) { LOG_DEBUG("mem import %s found", field_name); - found_export->exp.mem_instance = + resolved_export.exp.mem_instance = arg_instance->export_memories[export_idx] .memory; - return found_export; + found = true; + break; } } break; @@ -265,9 +279,10 @@ wasm_import_find_in_args(WASMImport *import, WASMInstExpr *expression, arg_instance->export_globals[export_idx] .name)) { LOG_DEBUG("global import %s found", field_name); - found_export->exp.global_instance = + resolved_export.exp.global_instance = arg_instance->export_globals[export_idx].global; - return found_export; + found = true; + break; } } break; @@ -275,9 +290,19 @@ wasm_import_find_in_args(WASMImport *import, WASMInstExpr *expression, LOG_WARNING("Import kind not supported\n"); break; } + if (found) { + found_export = wasm_runtime_malloc(sizeof(WASMCoreExport)); + if (!found_export) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: failed to allocate resolved core " + "export\n"); + return NULL; + } + *found_export = resolved_export; + return found_export; + } } } - wasm_runtime_free(found_export); return NULL; } @@ -344,24 +369,962 @@ wasm_resolve_core_imports(WASMInstExpr *expression, WASMModule *target, return true; } +#define HOST_IMPORT_CLONE_MAP_TYPE 0x1 +#define HOST_IMPORT_CLONE_MAP_LOCAL 0x2 +#define HOST_IMPORT_CORE_TYPE_MAX_VALUES (MAX_FLAT_PARAMS + MAX_FLAT_RESULTS) + +typedef struct WASMHostImportCloneMapEntry { + const void *source; + void *clone; + uint8 flags; +} WASMHostImportCloneMapEntry; + +typedef struct WASMHostImportCloneContext { + const WASMComponentInstTypeInstance *type_template; + WASMComponentInstance *instance; + uint8 *next; + uint64 remaining; + WASMHostImportCloneMapEntry *map; + uint32 map_capacity; +} WASMHostImportCloneContext; + +static uint64 +host_import_clone_align_size(uint64 size) +{ + uint64 alignment = sizeof(void *); + + return (size + alignment - 1) & ~(alignment - 1); +} + +static bool +host_import_clone_add_size(uint64 *total, uint64 size) +{ + uint64 aligned_size; + + if (!total || size > UINT64_MAX - (sizeof(void *) - 1)) { + return false; + } + + aligned_size = host_import_clone_align_size(size); + if (*total > UINT64_MAX - aligned_size) { + return false; + } + + *total += aligned_size; + return true; +} + +static bool +host_import_type_is_local(const WASMComponentInstTypeInstance *type_template, + const WASMComponentTypeInstance *type) +{ + uintptr_t begin; + uintptr_t end; + uintptr_t address; + + if (!type_template || !type || !type_template->owned_types_begin + || type_template->owned_types_size == 0) { + return false; + } + + begin = (uintptr_t)type_template->owned_types_begin; + if (begin > UINTPTR_MAX - type_template->owned_types_size) { + return false; + } + end = begin + type_template->owned_types_size; + address = (uintptr_t)type; + return address >= begin && address < end; +} + +static bool +host_import_clone_map_capacity(uint32 type_count, uint32 *capacity) +{ + uint64 required; + uint32 result = 8; + + if (!capacity) { + return false; + } + if (type_count == 0) { + *capacity = 0; + return true; + } + + /* Each unique type contributes its wrapper plus at most one resource or + * function implementation key. Keep the open-addressed map <= 50% full. */ + required = (uint64)type_count * 4; + while (result < required) { + if (result > UINT32_MAX / 2) { + return false; + } + result *= 2; + } + + *capacity = result; + return true; +} + +static uint32 +host_import_clone_map_hash(const void *source, uint32 capacity) +{ + uintptr_t value = (uintptr_t)source; + + value >>= 3; + value ^= value >> 16; + value *= (uintptr_t)2654435761U; + return (uint32)value & (capacity - 1); +} + +static WASMHostImportCloneMapEntry * +host_import_clone_map_find_in(WASMHostImportCloneMapEntry *map, uint32 capacity, + const void *source) +{ + uint32 index; + + if (!map || capacity == 0 || !source) { + return NULL; + } + + index = host_import_clone_map_hash(source, capacity); + for (uint32 probe = 0; probe < capacity; probe++) { + WASMHostImportCloneMapEntry *entry = + &map[(index + probe) & (capacity - 1)]; + + if (!entry->source) { + return NULL; + } + if (entry->source == source) { + return entry; + } + } + + return NULL; +} + +static WASMHostImportCloneMapEntry * +host_import_clone_map_find(const WASMHostImportCloneContext *clone_ctx, + const void *source) +{ + if (!clone_ctx) { + return NULL; + } + return host_import_clone_map_find_in(clone_ctx->map, + clone_ctx->map_capacity, source); +} + +static bool +host_import_clone_map_insert(WASMHostImportCloneMapEntry *map, uint32 capacity, + const void *source, void *clone, uint8 flags) +{ + uint32 index; + + if (!map || capacity == 0 || !source || !clone) { + return false; + } + + index = host_import_clone_map_hash(source, capacity); + for (uint32 probe = 0; probe < capacity; probe++) { + WASMHostImportCloneMapEntry *entry = + &map[(index + probe) & (capacity - 1)]; + + if (!entry->source) { + entry->source = source; + entry->clone = clone; + entry->flags = flags; + return true; + } + if (entry->source == source) { + if (entry->clone != clone) { + return false; + } + entry->flags |= flags; + return true; + } + } + + return false; +} + +static bool +host_import_clone_specific_size(const WASMComponentTypeInstance *type, + uint64 *size) +{ + if (!type || !size) { + return false; + } + + *size = 0; + switch (type->type) { + case COMPONENT_VAL_TYPE_RECORD: + if (!type->type_specific.record) { + return false; + } + *size = sizeof(WASMComponentRecordInstance) + + (uint64)type->type_specific.record->count + * sizeof(WASMComponentLabelValTypeInstance); + break; + case COMPONENT_VAL_TYPE_VARIANT: + if (!type->type_specific.variant) { + return false; + } + *size = sizeof(WASMComponentVariantInstance) + + (uint64)type->type_specific.variant->count + * sizeof(WASMComponentCaseValInstance); + break; + case COMPONENT_VAL_TYPE_LIST: + if (!type->type_specific.list) { + return false; + } + *size = sizeof(WASMComponentListInstance); + break; + case COMPONENT_VAL_TYPE_FIXED_SIZE_LIST: + if (!type->type_specific.list_len) { + return false; + } + *size = sizeof(WASMComponentListLenInstance); + break; + case COMPONENT_VAL_TYPE_TUPLE: + if (!type->type_specific.tuple) { + return false; + } + *size = sizeof(WASMComponentTupleInstance) + + (uint64)type->type_specific.tuple->count + * sizeof(WASMComponentTypeInstance *); + break; + case COMPONENT_VAL_TYPE_OPTION: + if (!type->type_specific.option) { + return false; + } + *size = sizeof(WASMComponentOptionInstance); + break; + case COMPONENT_VAL_TYPE_RESULT: + if (!type->type_specific.result) { + return false; + } + *size = sizeof(WASMComponentResultInstance); + break; + case COMPONENT_VAL_TYPE_OWN: + case COMPONENT_VAL_TYPE_BORROW: + if (!type->type_specific.resource_handle) { + return false; + } + *size = sizeof(WASMComponentResourceHandleInstance); + break; + case COMPONENT_VAL_TYPE_FUNCTION: + if (!type->type_specific.function + || !type->type_specific.function->params + || !type->type_specific.function->results) { + return false; + } + *size = sizeof(WASMComponentFuncTypeInstance) + + sizeof(WASMComponentParamListInstance) + + (uint64)type->type_specific.function->params->count + * sizeof(WASMComponentLabelValTypeInstance) + + sizeof(WASMComponentResultListInstance); + break; + case COMPONENT_VAL_TYPE_RESOURCE_SYNC: + case COMPONENT_VAL_TYPE_RESOURCE_ASYNC: + if (!type->type_specific.resource) { + return false; + } + *size = sizeof(WASMComponentResourceInstance) + + 3 * sizeof(WASMFunctionInstance); + break; + default: + break; + } + + return true; +} + +static bool +host_import_clone_storage_size( + const WASMComponentInstTypeInstance *type_template, uint64 *size, + uint32 *map_capacity, char *error_buf, uint32 error_buf_size) +{ + uint64 total = 0; + uint64 map_size = 0; + uint64 core_type_size = + offsetof(WASMFuncType, types) + HOST_IMPORT_CORE_TYPE_MAX_VALUES; + WASMHostImportCloneMapEntry *seen = NULL; + bool success = false; + + if (!type_template || !size || !map_capacity + || !host_import_clone_map_capacity(type_template->types_count, + map_capacity)) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Invalid host import type template\n"); + return false; + } + + if (*map_capacity > 0) { + map_size = (uint64)*map_capacity * sizeof(WASMHostImportCloneMapEntry); + if (map_size > UINT32_MAX + || !host_import_clone_add_size(&total, map_size) + || !(seen = wasm_runtime_malloc((uint32)map_size))) { + set_error_buf_ex( + error_buf, error_buf_size, + "ERROR: Failed to allocate host import type map\n"); + return false; + } + memset(seen, 0, (size_t)map_size); + } + + for (uint32 type_idx = 0; type_idx < type_template->types_count; + type_idx++) { + WASMComponentTypeInstance *source = type_template->types[type_idx]; + uint64 specific_size; + + if (!source) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Invalid host import type graph\n"); + goto done; + } + if (!host_import_type_is_local(type_template, source) + || host_import_clone_map_find_in(seen, *map_capacity, source)) { + continue; + } + if (!host_import_clone_map_insert(seen, *map_capacity, source, source, + HOST_IMPORT_CLONE_MAP_TYPE + | HOST_IMPORT_CLONE_MAP_LOCAL)) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Host import type map is full\n"); + goto done; + } + + if (!host_import_clone_specific_size(source, &specific_size) + || !host_import_clone_add_size(&total, + sizeof(WASMComponentTypeInstance)) + || (specific_size + && !host_import_clone_add_size(&total, specific_size))) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Invalid host import type graph\n"); + goto done; + } + } + + for (uint32 func_idx = 0; func_idx < type_template->func_count; + func_idx++) { + if (!host_import_clone_add_size(&total, sizeof(WASMFunctionImport)) + || !host_import_clone_add_size(&total, core_type_size)) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Host import clone size overflow\n"); + goto done; + } + } + + if (total > UINT32_MAX) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Host import clone is too large\n"); + goto done; + } + + *size = total; + success = true; + +done: + if (seen) { + wasm_runtime_free(seen); + } + return success; +} + +static void * +host_import_clone_alloc(WASMHostImportCloneContext *clone_ctx, uint64 size) +{ + uint64 aligned_size; + void *result; + + if (!clone_ctx || size > UINT64_MAX - (sizeof(void *) - 1)) { + return NULL; + } + + aligned_size = host_import_clone_align_size(size); + if (aligned_size > clone_ctx->remaining) { + return NULL; + } + + result = clone_ctx->next; + memset(result, 0, (size_t)aligned_size); + clone_ctx->next += aligned_size; + clone_ctx->remaining -= aligned_size; + return result; +} + +static bool +host_import_core_valtype_to_byte(const WASMComponentCoreValType *type, + uint8 *byte) +{ + if (!type || !byte) { + return false; + } + + switch (type->tag) { + case WASM_CORE_VALTYPE_NUM: + *byte = (uint8)type->type.num_type; + return true; + case WASM_CORE_VALTYPE_VECTOR: + *byte = (uint8)type->type.vector_type; + return true; + case WASM_CORE_VALTYPE_REF: + *byte = (uint8)type->type.ref_type; + return true; + default: + return false; + } +} + +static bool +host_import_build_core_func_type( + WASMHostImportCloneContext *clone_ctx, + WASMComponentFuncTypeInstance *component_func_type, WASMFuncType **result, + char *error_buf, uint32 error_buf_size) +{ + CanonicalOptions canonical_options = { 0 }; + LiftLowerContext flatten_context = { 0 }; + WASMComponentCoreFuncType flattened = { 0 }; + WASMFuncType *core_func_type = NULL; + uint32 total_value_count; + uint32 param_cell_num; + uint32 ret_cell_num; + bool success = false; + + if (!clone_ctx || !component_func_type || !result) { + return false; + } + + flatten_context.canonical_opts = &canonical_options; + flatten_context.inst = clone_ctx->instance; + flatten_context.borrow_scope_type = BORROW_SCOPE_NONE; + if (!flatten_functype(&flatten_context, component_func_type, + FLATTEN_CONTEXT_LOWER, &flattened)) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Failed to flatten host function type\n"); + return false; + } + + total_value_count = flattened.params.count + flattened.results.count; + if (total_value_count > HOST_IMPORT_CORE_TYPE_MAX_VALUES + || !(core_func_type = host_import_clone_alloc( + clone_ctx, offsetof(WASMFuncType, types) + + HOST_IMPORT_CORE_TYPE_MAX_VALUES))) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Failed to allocate host core function type\n"); + goto done; + } + +#if WASM_ENABLE_GC != 0 + core_func_type->base_type.type_flag = WASM_TYPE_FUNC; +#endif + core_func_type->param_count = (uint16)flattened.params.count; + core_func_type->result_count = (uint16)flattened.results.count; + for (uint32 idx = 0; idx < flattened.params.count; idx++) { + if (!host_import_core_valtype_to_byte(&flattened.params.val_types[idx], + &core_func_type->types[idx])) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Invalid flattened host parameter type\n"); + goto done; + } + } + for (uint32 idx = 0; idx < flattened.results.count; idx++) { + if (!host_import_core_valtype_to_byte( + &flattened.results.val_types[idx], + &core_func_type->types[flattened.params.count + idx])) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Invalid flattened host result type\n"); + goto done; + } + } + + param_cell_num = + wasm_get_cell_num(core_func_type->types, core_func_type->param_count); + ret_cell_num = + wasm_get_cell_num(core_func_type->types + core_func_type->param_count, + core_func_type->result_count); + if (param_cell_num > UINT16_MAX || ret_cell_num > UINT16_MAX) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Flattened host function type is too large\n"); + goto done; + } + core_func_type->param_cell_num = (uint16)param_cell_num; + core_func_type->ret_cell_num = (uint16)ret_cell_num; + *result = core_func_type; + success = true; + +done: + free_core_functype(&flattened); + return success; +} + +static WASMComponentTypeInstance * +host_import_find_type_clone(const WASMHostImportCloneContext *clone_ctx, + WASMComponentTypeInstance *source) +{ + WASMHostImportCloneMapEntry *entry; + + if (!clone_ctx || !source) { + return NULL; + } + + entry = host_import_clone_map_find(clone_ctx, source); + return entry ? entry->clone : source; +} + +static WASMComponentResourceInstance * +host_import_find_resource_clone(const WASMHostImportCloneContext *clone_ctx, + WASMComponentResourceInstance *source) +{ + WASMHostImportCloneMapEntry *entry; + + if (!clone_ctx || !source) { + return NULL; + } + + entry = host_import_clone_map_find(clone_ctx, source); + return entry ? entry->clone : source; +} + +static WASMComponentFuncTypeInstance * +host_import_find_func_type_clone(const WASMHostImportCloneContext *clone_ctx, + WASMComponentFuncTypeInstance *source) +{ + WASMHostImportCloneMapEntry *entry; + + if (!clone_ctx || !source) { + return NULL; + } + + entry = host_import_clone_map_find(clone_ctx, source); + return entry ? entry->clone : source; +} + +static bool +host_import_clone_type(WASMHostImportCloneContext *clone_ctx, + const WASMComponentTypeInstance *source, + WASMComponentTypeInstance **result) +{ + WASMComponentTypeInstance *clone; + uint64 specific_size; + void *specific; + + if (!clone_ctx || !source || !result + || !host_import_clone_specific_size(source, &specific_size) + || !(clone = host_import_clone_alloc( + clone_ctx, sizeof(WASMComponentTypeInstance)))) { + return false; + } + + *clone = *source; + if (!specific_size) { + *result = clone; + return true; + } + + specific = host_import_clone_alloc(clone_ctx, specific_size); + if (!specific) { + return false; + } + + switch (source->type) { + case COMPONENT_VAL_TYPE_RECORD: + memcpy(specific, source->type_specific.record, + (size_t)specific_size); + clone->type_specific.record = specific; + break; + case COMPONENT_VAL_TYPE_VARIANT: + memcpy(specific, source->type_specific.variant, + (size_t)specific_size); + clone->type_specific.variant = specific; + break; + case COMPONENT_VAL_TYPE_LIST: + memcpy(specific, source->type_specific.list, (size_t)specific_size); + clone->type_specific.list = specific; + break; + case COMPONENT_VAL_TYPE_FIXED_SIZE_LIST: + memcpy(specific, source->type_specific.list_len, + (size_t)specific_size); + clone->type_specific.list_len = specific; + break; + case COMPONENT_VAL_TYPE_TUPLE: + { + WASMComponentTupleInstance *tuple = specific; + + memcpy(tuple, source->type_specific.tuple, + sizeof(WASMComponentTupleInstance)); + tuple->element_types = + (WASMComponentTypeInstance **)((uint8 *)tuple + sizeof(*tuple)); + memcpy(tuple->element_types, + source->type_specific.tuple->element_types, + (size_t)source->type_specific.tuple->count + * sizeof(WASMComponentTypeInstance *)); + clone->type_specific.tuple = tuple; + break; + } + case COMPONENT_VAL_TYPE_OPTION: + memcpy(specific, source->type_specific.option, + (size_t)specific_size); + clone->type_specific.option = specific; + break; + case COMPONENT_VAL_TYPE_RESULT: + memcpy(specific, source->type_specific.result, + (size_t)specific_size); + clone->type_specific.result = specific; + break; + case COMPONENT_VAL_TYPE_OWN: + case COMPONENT_VAL_TYPE_BORROW: + memcpy(specific, source->type_specific.resource_handle, + (size_t)specific_size); + clone->type_specific.resource_handle = specific; + break; + case COMPONENT_VAL_TYPE_FUNCTION: + { + WASMComponentFuncTypeInstance *func_type = specific; + WASMComponentParamListInstance *params = + (WASMComponentParamListInstance *)((uint8 *)func_type + + sizeof(*func_type)); + uint64 params_size = + sizeof(*params) + + (uint64)source->type_specific.function->params->count + * sizeof(WASMComponentLabelValTypeInstance); + WASMComponentResultListInstance *results = + (WASMComponentResultListInstance *)((uint8 *)params + + params_size); + + memcpy(params, source->type_specific.function->params, + (size_t)params_size); + memcpy(results, source->type_specific.function->results, + sizeof(*results)); + func_type->params = params; + func_type->results = results; + clone->type_specific.function = func_type; + break; + } + case COMPONENT_VAL_TYPE_RESOURCE_SYNC: + case COMPONENT_VAL_TYPE_RESOURCE_ASYNC: + { + WASMComponentResourceInstance *resource = specific; + WASMFunctionInstance *drop_method = + (WASMFunctionInstance *)((uint8 *)resource + sizeof(*resource)); + WASMFunctionInstance *new_method = drop_method + 1; + WASMFunctionInstance *rep_method = new_method + 1; + + *resource = *source->type_specific.resource; + if (resource->drop_method) { + *drop_method = *resource->drop_method; + drop_method->resource = resource; + } + if (resource->new_method) { + *new_method = *resource->new_method; + new_method->resource = resource; + } + if (resource->rep_method) { + *rep_method = *resource->rep_method; + rep_method->resource = resource; + } + resource->drop_method = resource->drop_method ? drop_method : NULL; + resource->new_method = resource->new_method ? new_method : NULL; + resource->rep_method = resource->rep_method ? rep_method : NULL; + clone->type_specific.resource = resource; + break; + } + default: + return false; + } + + *result = clone; + return true; +} + +static void +host_import_remap_type(WASMHostImportCloneContext *clone_ctx, + WASMComponentTypeInstance *type) +{ + if (!clone_ctx || !type) { + return; + } + + switch (type->type) { + case COMPONENT_VAL_TYPE_RECORD: + for (uint32 idx = 0; idx < type->type_specific.record->count; + idx++) { + type->type_specific.record->fields[idx].type = + host_import_find_type_clone( + clone_ctx, + type->type_specific.record->fields[idx].type); + } + break; + case COMPONENT_VAL_TYPE_VARIANT: + for (uint32 idx = 0; idx < type->type_specific.variant->count; + idx++) { + type->type_specific.variant->cases[idx].value_type = + host_import_find_type_clone( + clone_ctx, + type->type_specific.variant->cases[idx].value_type); + } + break; + case COMPONENT_VAL_TYPE_LIST: + type->type_specific.list->element_type = + host_import_find_type_clone( + clone_ctx, type->type_specific.list->element_type); + break; + case COMPONENT_VAL_TYPE_FIXED_SIZE_LIST: + type->type_specific.list_len->element_type = + host_import_find_type_clone( + clone_ctx, type->type_specific.list_len->element_type); + break; + case COMPONENT_VAL_TYPE_TUPLE: + for (uint32 idx = 0; idx < type->type_specific.tuple->count; + idx++) { + type->type_specific.tuple->element_types[idx] = + host_import_find_type_clone( + clone_ctx, + type->type_specific.tuple->element_types[idx]); + } + break; + case COMPONENT_VAL_TYPE_OPTION: + type->type_specific.option->element_type = + host_import_find_type_clone( + clone_ctx, type->type_specific.option->element_type); + break; + case COMPONENT_VAL_TYPE_RESULT: + type->type_specific.result->result_type = + host_import_find_type_clone( + clone_ctx, type->type_specific.result->result_type); + type->type_specific.result->error_type = + host_import_find_type_clone( + clone_ctx, type->type_specific.result->error_type); + break; + case COMPONENT_VAL_TYPE_OWN: + case COMPONENT_VAL_TYPE_BORROW: + type->type_specific.resource_handle->resource = + host_import_find_resource_clone( + clone_ctx, type->type_specific.resource_handle->resource); + break; + case COMPONENT_VAL_TYPE_FUNCTION: + for (uint32 idx = 0; + idx < type->type_specific.function->params->count; idx++) { + type->type_specific.function->params->params[idx].type = + host_import_find_type_clone( + clone_ctx, + type->type_specific.function->params->params[idx].type); + } + type->type_specific.function->results->result = + host_import_find_type_clone( + clone_ctx, type->type_specific.function->results->result); + break; + default: + break; + } +} + +static bool +host_import_clone_types(WASMHostImportCloneContext *clone_ctx, char *error_buf, + uint32 error_buf_size) +{ + if (!clone_ctx || !clone_ctx->type_template || !clone_ctx->instance) { + return false; + } + + if (clone_ctx->map_capacity > 0) { + clone_ctx->map = host_import_clone_alloc( + clone_ctx, (uint64)clone_ctx->map_capacity + * sizeof(WASMHostImportCloneMapEntry)); + if (!clone_ctx->map) { + set_error_buf_ex( + error_buf, error_buf_size, + "ERROR: Failed to allocate host import type map\n"); + return false; + } + } + + for (uint32 type_idx = 0; type_idx < clone_ctx->type_template->types_count; + type_idx++) { + WASMComponentTypeInstance *source = + clone_ctx->type_template->types[type_idx]; + WASMHostImportCloneMapEntry *entry = + host_import_clone_map_find(clone_ctx, source); + WASMComponentTypeInstance *clone = NULL; + bool is_local; + + if (entry) { + clone = entry->clone; + } + else { + is_local = + host_import_type_is_local(clone_ctx->type_template, source); + if (is_local + && !host_import_clone_type(clone_ctx, source, &clone)) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Failed to clone host import types\n"); + return false; + } + if (!is_local) { + clone = source; + } + if (!host_import_clone_map_insert( + clone_ctx->map, clone_ctx->map_capacity, source, clone, + HOST_IMPORT_CLONE_MAP_TYPE + | (is_local ? HOST_IMPORT_CLONE_MAP_LOCAL : 0))) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Host import type map is full\n"); + return false; + } + if ((source->type == COMPONENT_VAL_TYPE_RESOURCE_SYNC + || source->type == COMPONENT_VAL_TYPE_RESOURCE_ASYNC) + && !host_import_clone_map_insert( + clone_ctx->map, clone_ctx->map_capacity, + source->type_specific.resource, + clone->type_specific.resource, + is_local ? HOST_IMPORT_CLONE_MAP_LOCAL : 0)) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Host resource type map is full\n"); + return false; + } + if (source->type == COMPONENT_VAL_TYPE_FUNCTION + && !host_import_clone_map_insert( + clone_ctx->map, clone_ctx->map_capacity, + source->type_specific.function, + clone->type_specific.function, + is_local ? HOST_IMPORT_CLONE_MAP_LOCAL : 0)) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Host function type map is full\n"); + return false; + } + } + clone_ctx->instance->types[type_idx] = clone; + } + + clone_ctx->instance->types_count = clone_ctx->type_template->types_count; + for (uint32 map_idx = 0; map_idx < clone_ctx->map_capacity; map_idx++) { + WASMHostImportCloneMapEntry *entry = &clone_ctx->map[map_idx]; + + if (entry->source + && (entry->flags + & (HOST_IMPORT_CLONE_MAP_TYPE | HOST_IMPORT_CLONE_MAP_LOCAL)) + == (HOST_IMPORT_CLONE_MAP_TYPE + | HOST_IMPORT_CLONE_MAP_LOCAL)) { + host_import_remap_type(clone_ctx, entry->clone); + } + } + + return true; +} + +static uint32 +host_import_bind_resources(WASMHostImportCloneContext *clone_ctx, + WASMComponentInstance *root_instance, + char *interface_name) +{ + uint32 resource_count = 0; + + for (uint32 map_idx = 0; map_idx < clone_ctx->map_capacity; map_idx++) { + WASMHostImportCloneMapEntry *entry = &clone_ctx->map[map_idx]; + WASMComponentTypeInstance *type; + WASMComponentResourceInstance *resource; + wasm_component_host_resource_drop_callback_t drop_callback = NULL; + + if (!entry->source + || (entry->flags + & (HOST_IMPORT_CLONE_MAP_TYPE | HOST_IMPORT_CLONE_MAP_LOCAL)) + != (HOST_IMPORT_CLONE_MAP_TYPE + | HOST_IMPORT_CLONE_MAP_LOCAL)) { + continue; + } + type = entry->clone; + if (!type + || (type->type != COMPONENT_VAL_TYPE_RESOURCE_SYNC + && type->type != COMPONENT_VAL_TYPE_RESOURCE_ASYNC)) { + continue; + } + + resource = type->type_specific.resource; + if (!resource->interface_name) { + resource->interface_name = interface_name; + resource_count++; + } + resource->is_builtin_wasi = false; + resource->is_host = true; + resource->host_drop_callback = NULL; + resource->host_drop_attachment = NULL; + resource->host_drop_attachment_is_custom_data = false; + if (resource->name + && wasm_component_find_host_resource_drop_callback( + root_instance->component, resource->interface_name, + resource->name, &drop_callback)) { + resource->host_drop_callback = drop_callback; + resource->host_drop_attachment = root_instance->custom_data; + resource->host_drop_attachment_is_custom_data = true; + } + } + + return resource_count; +} + +static void +host_import_set_builtin_wasi_resource_provenance( + WASMHostImportCloneContext *clone_ctx, bool allow_builtin_wasi) +{ + for (uint32 map_idx = 0; map_idx < clone_ctx->map_capacity; map_idx++) { + WASMHostImportCloneMapEntry *entry = &clone_ctx->map[map_idx]; + WASMComponentTypeInstance *type; + + if (!entry->source + || (entry->flags + & (HOST_IMPORT_CLONE_MAP_TYPE | HOST_IMPORT_CLONE_MAP_LOCAL)) + != (HOST_IMPORT_CLONE_MAP_TYPE + | HOST_IMPORT_CLONE_MAP_LOCAL)) { + continue; + } + type = entry->clone; + if (type + && (type->type == COMPONENT_VAL_TYPE_RESOURCE_SYNC + || type->type == COMPONENT_VAL_TYPE_RESOURCE_ASYNC)) { + WASMComponentResourceInstance *resource = + type->type_specific.resource; +#if WASM_ENABLE_LIBC_WASI_P2 != 0 + resource->is_builtin_wasi = + allow_builtin_wasi + && wasm_native_has_builtin_wasi_p2_resource( + resource->interface_name, resource->name); +#else + resource->is_builtin_wasi = false; +#endif + } + } +} + bool -wasm_resolve_imports_WASI(WASMComponentImportSection *import_section, +wasm_resolve_imports_host(WASMComponentImportSection *import_section, WASMComponentInstance *comp_instance, char *error_buf, uint32 error_buf_size) { char *interface_name = NULL; - uint32 idx = 0; - uint32 func_export_count = 0; - uint32 resource_count = 0; + uint32 import_idx = 0; WASMComponentImport *import = NULL; WASMComponentInstTypeInstance *instance_type = NULL; WASMComponentInstance *new_inst = NULL; - WASMFunctionImport *func_import = NULL; void *func_ptr = NULL; char *field_name = NULL; - for (idx = 0; idx < import_section->count; idx++) { - import = &import_section->imports[idx]; + if (!import_section || !comp_instance) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Invalid root component imports\n"); + return false; + } + + for (import_idx = 0; import_idx < import_section->count; import_idx++) { + uint32 func_export_count = 0; + uint32 resource_count; + uint32 clone_map_capacity; + uint64 clone_storage_size; + bool is_wasi = false; +#if WASM_ENABLE_LIBC_WASI_P2 != 0 + bool resolved_static_host_function = false; +#endif + bool resources_use_builtin_wasi = false; + WASMHostImportCloneContext clone_ctx = { 0 }; + + import = &import_section->imports[import_idx]; if (!import->extern_desc || import->extern_desc->type != WASM_COMP_EXTERN_INSTANCE) { set_error_buf_ex( @@ -381,14 +1344,9 @@ wasm_resolve_imports_WASI(WASMComponentImportSection *import_section, import->import_name->tag); return false; } - if (!wasm_check_wasi_p2_version(interface_name)) { - set_error_buf_ex(error_buf, error_buf_size, - "ERROR: Incompatible WASI version for %s", - interface_name); - return false; - } + is_wasi = strncmp(interface_name, "wasi:", strlen("wasi:")) == 0; - LOG_DEBUG(" WASI Import : %s", interface_name); + LOG_DEBUG(" Host Import : %s", interface_name); if (import->extern_desc->extern_desc.instance.type_idx >= comp_instance->types_count @@ -411,31 +1369,37 @@ wasm_resolve_imports_WASI(WASMComponentImportSection *import_section, index_count.types = instance_type->types_count; index_count.functions = instance_type->func_count; index_count.exports = instance_type->exports_count; + index_count.defined_core_functions = instance_type->func_count; index_count.defined_functions = instance_type->func_count; + if (!host_import_clone_storage_size(instance_type, &clone_storage_size, + &clone_map_capacity, error_buf, + error_buf_size)) { + return false; + } + index_count.types_total_size = clone_storage_size; new_inst = wasm_component_instance_allocate(&index_count, error_buf, error_buf_size); - for (idx = 0; idx < instance_type->types_count; idx++) { - new_inst->types[new_inst->types_count] = instance_type->types[idx]; - if (new_inst->types[new_inst->types_count]->type - == COMPONENT_VAL_TYPE_RESOURCE_SYNC - || new_inst->types[new_inst->types_count]->type - == COMPONENT_VAL_TYPE_RESOURCE_ASYNC) { - if (new_inst->types[new_inst->types_count] - ->type_specific.resource->interface_name) { - // Not a local sub resource - continue; - } - new_inst->types[new_inst->types_count] - ->type_specific.resource->is_wasi = true; - new_inst->types[new_inst->types_count] - ->type_specific.resource->interface_name = interface_name; - resource_count++; - } - new_inst->types_count++; + if (!new_inst) { + return false; } - for (idx = 0; idx < instance_type->func_count; idx++) { + + clone_ctx.type_template = instance_type; + clone_ctx.instance = new_inst; + clone_ctx.next = new_inst->defined_types; + clone_ctx.remaining = clone_storage_size; + clone_ctx.map_capacity = clone_map_capacity; + if (!host_import_clone_types(&clone_ctx, error_buf, error_buf_size)) { + wasm_component_deinstantiate(new_inst); + return false; + } + resource_count = host_import_bind_resources(&clone_ctx, comp_instance, + interface_name); + + for (uint32 func_idx = 0; func_idx < instance_type->func_count; + func_idx++) { new_inst->defined_functions[new_inst->defined_functions_count] - .func_type = instance_type->funcs[idx]; + .func_type = host_import_find_func_type_clone( + &clone_ctx, instance_type->funcs[func_idx]); new_inst->defined_functions[new_inst->defined_functions_count] .core_func = NULL; new_inst->functions[new_inst->functions_count] = @@ -443,31 +1407,106 @@ wasm_resolve_imports_WASI(WASMComponentImportSection *import_section, new_inst->defined_functions_count++; new_inst->functions_count++; } - for (idx = 0; idx < instance_type->exports_count; idx++) { - new_inst->exports[idx].export_name = - instance_type->exports[idx].export_name; - new_inst->exports[idx].type = instance_type->exports[idx].type; - func_import = instance_type->defined_core_funcs[func_export_count] - .u.func_import; - if (instance_type->exports[idx].type == WASM_COMP_EXTERN_FUNC) { - if (func_export_count > new_inst->functions_count) { + for (uint32 export_idx = 0; export_idx < instance_type->exports_count; + export_idx++) { + new_inst->exports[export_idx].export_name = + instance_type->exports[export_idx].export_name; + new_inst->exports[export_idx].type = + instance_type->exports[export_idx].type; + if (instance_type->exports[export_idx].type + == WASM_COMP_EXTERN_FUNC) { +#if WASM_ENABLE_LIBC_WASI_P2 != 0 + const NativeSymbol *wasi_symbol = NULL; +#endif + if (func_export_count >= new_inst->functions_count) { set_error_buf_ex(error_buf, error_buf_size, "ERROR: Import function index exceeded\n"); + wasm_component_deinstantiate(new_inst); return false; } - field_name = instance_type->exports[idx] + WASMFunctionInstance *template_core_func = + &instance_type->defined_core_funcs[func_export_count]; + WASMFunctionInstance *core_func = + &new_inst->defined_core_functions[func_export_count]; + WASMFunctionImport *func_import = host_import_clone_alloc( + &clone_ctx, sizeof(WASMFunctionImport)); + if (!template_core_func->u.func_import || !func_import) { + set_error_buf_ex( + error_buf, error_buf_size, + "ERROR: Failed to clone host function import\n"); + wasm_component_deinstantiate(new_inst); + return false; + } + *func_import = *template_core_func->u.func_import; + func_import->func_ptr_linked = NULL; + func_import->signature = NULL; + func_import->attachment = NULL; + func_import->call_conv_raw = false; + func_import->call_conv_wasm_c_api = false; +#if WASM_ENABLE_MULTI_MODULE != 0 + func_import->import_module = NULL; + func_import->import_func_linked = NULL; +#endif + *core_func = *template_core_func; + core_func->is_import_func = true; + core_func->u.func_import = func_import; +#if WASM_ENABLE_MULTI_MODULE != 0 || WASM_ENABLE_COMPONENT_MODEL != 0 + core_func->import_module_inst = NULL; + core_func->import_func_inst = NULL; +#endif + core_func->canon_options = NULL; + core_func->module_instance = NULL; + core_func->component_function = + &new_inst->defined_functions[func_export_count]; + if (!host_import_build_core_func_type( + &clone_ctx, core_func->component_function->func_type, + &func_import->func_type, error_buf, error_buf_size)) { + wasm_component_deinstantiate(new_inst); + return false; + } + core_func->param_count = func_import->func_type->param_count; + core_func->param_cell_num = + func_import->func_type->param_cell_num; + core_func->ret_cell_num = func_import->func_type->ret_cell_num; + core_func->param_types = func_import->func_type->types; + new_inst->defined_core_functions_count++; + field_name = instance_type->exports[export_idx] .export_name->exported.simple.name->name; - func_ptr = wasm_native_resolve_symbol( - interface_name, field_name, NULL, &func_import->signature, - &func_import->attachment, &func_import->call_conv_raw); +#if WASM_ENABLE_LIBC_WASI_P2 != 0 + wasi_symbol = is_wasi ? wasm_native_get_wasi_p2_module_func( + interface_name, field_name) + : NULL; +#endif + func_ptr = wasm_native_resolve_symbol_exact( + interface_name, field_name, func_import->func_type, + &func_import->signature, &func_import->attachment, + &func_import->call_conv_raw); +#if WASM_ENABLE_LIBC_WASI_P2 != 0 + if (func_ptr + && (!wasi_symbol || func_ptr != wasi_symbol->func_ptr)) { + resolved_static_host_function = true; + } +#endif - if (!func_ptr) { - wasm_native_register_wasi_p2_module_func(interface_name, - field_name); - func_ptr = wasm_native_resolve_symbol( - interface_name, field_name, NULL, - &func_import->signature, &func_import->attachment, - &func_import->call_conv_raw); + if (!func_ptr && is_wasi) { +#if WASM_ENABLE_LIBC_WASI_P2 != 0 + if (!wasm_check_wasi_p2_version(interface_name)) { + set_error_buf_ex( + error_buf, error_buf_size, + "ERROR: Incompatible WASI version for %s", + interface_name); + wasm_component_deinstantiate(new_inst); + return false; + } + if (wasi_symbol + && wasm_native_validate_symbol_signature( + func_import->func_type, wasi_symbol->signature)) { + func_ptr = wasi_symbol->func_ptr; + func_import->signature = wasi_symbol->signature; + func_import->attachment = wasi_symbol->attachment; + func_import->call_conv_raw = false; + } +#endif } if (!func_ptr) { @@ -475,50 +1514,74 @@ wasm_resolve_imports_WASI(WASMComponentImportSection *import_section, error_buf, error_buf_size, "ERROR: Function %s not found in native symbols\n", field_name); + wasm_component_deinstantiate(new_inst); return false; } LOG_DEBUG( - " WASI function : %s %s found successfully, %d params", + " Host function : %s %s found successfully, %d params", interface_name, field_name, instance_type->defined_core_funcs[func_export_count] .param_count); new_inst->defined_functions[func_export_count].core_func = - &instance_type->defined_core_funcs[func_export_count]; - new_inst->defined_functions[func_export_count] - .core_func->is_import_func = true; - new_inst->defined_functions[func_export_count] - .core_func->u.func_import->func_ptr_linked = func_ptr; - new_inst->exports[idx].exp.function = + core_func; + func_import->func_ptr_linked = func_ptr; + new_inst->exports[export_idx].exp.function = &new_inst->defined_functions[func_export_count]; func_export_count++; new_inst->exports_count++; } - else if (instance_type->exports[idx].type + else if (instance_type->exports[export_idx].type == WASM_COMP_EXTERN_TYPE) { - new_inst->exports[idx].exp.type = - instance_type->exports[idx].exp.type; + new_inst->exports[export_idx].exp.type = + host_import_find_type_clone( + &clone_ctx, + instance_type->exports[export_idx].exp.type); new_inst->exports_count++; } else { set_error_buf_ex( error_buf, error_buf_size, - "ERROR: Import type %d not supported for WASI imports\n", - instance_type->exports[idx].type); + "ERROR: Import type %d not supported for host imports\n", + instance_type->exports[export_idx].type); + wasm_component_deinstantiate(new_inst); return false; } } + + /* A wasi: namespace does not imply ownership by WAMR's global host + * resource table. Only an interface implemented exclusively by the + * built-in WASI fallback may use that table during resource.drop. + * Resource-only and mixed static/built-in interfaces fail closed and + * require an exact owner-drop callback. */ +#if WASM_ENABLE_LIBC_WASI_P2 != 0 + resources_use_builtin_wasi = is_wasi && !resolved_static_host_function; +#endif + host_import_set_builtin_wasi_resource_provenance( + &clone_ctx, resources_use_builtin_wasi); + + comp_instance->resources_count += resource_count; + comp_instance + ->component_instances[comp_instance->component_instances_count] = + new_inst; + comp_instance->component_instances_count++; + comp_instance + ->defined_instances[comp_instance->defined_instances_count] = + new_inst; + comp_instance->defined_instances_count++; + new_inst = NULL; } - comp_instance->resources_count += resource_count; - comp_instance - ->component_instances[comp_instance->component_instances_count] = - new_inst; - comp_instance->component_instances_count++; - comp_instance->defined_instances[comp_instance->defined_instances_count] = - new_inst; - comp_instance->defined_instances_count++; return true; } +bool +wasm_resolve_imports_WASI(WASMComponentImportSection *import_section, + WASMComponentInstance *comp_instance, char *error_buf, + uint32 error_buf_size) +{ + return wasm_resolve_imports_host(import_section, comp_instance, error_buf, + error_buf_size); +} + bool wasm_resolve_imports(WASMComponentImportSection *import_section, WASMComponentInstance *comp_instance, diff --git a/core/iwasm/common/component-model/wasm_component_instances_section.c b/core/iwasm/common/component-model/wasm_component_instances_section.c index abc0134fd9..ec817a05f2 100644 --- a/core/iwasm/common/component-model/wasm_component_instances_section.c +++ b/core/iwasm/common/component-model/wasm_component_instances_section.c @@ -32,8 +32,9 @@ wasm_component_parse_instances_section(const uint8_t **payload, return false; } - const uint8_t *p = *payload; - const uint8_t *end = *payload + payload_len; + const uint8_t *start = *payload; + const uint8_t *p = start; + const uint8_t *end = start + payload_len; uint64_t instance_count = 0; if (!read_leb((uint8_t **)&p, end, 32, false, &instance_count, error_buf, @@ -45,19 +46,15 @@ wasm_component_parse_instances_section(const uint8_t **payload, out->count = (uint32_t)instance_count; if (instance_count > 0) { - out->instances = - wasm_runtime_malloc(sizeof(WASMComponentInst) * instance_count); + out->instances = wasm_component_checked_calloc( + (uint32_t)instance_count, sizeof(WASMComponentInst), p, end, 1, + "component instance", error_buf, error_buf_size); if (!out->instances) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for instances"); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - // Initialize all instances to zero to avoid garbage data - memset(out->instances, 0, sizeof(WASMComponentInst) * instance_count); - for (uint32_t i = 0; i < instance_count; ++i) { // Check bounds before reading tag if (p >= end) { @@ -96,21 +93,16 @@ wasm_component_parse_instances_section(const uint8_t **payload, if (arg_len > 0) { out->instances[i].expression.with_args.args = - wasm_runtime_malloc(sizeof(WASMComponentInstArg) - * arg_len); + wasm_component_checked_calloc( + (uint32_t)arg_len, sizeof(WASMComponentInstArg), + p, end, 1, "component instantiate argument", + error_buf, error_buf_size); if (!out->instances[i].expression.with_args.args) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for " - "component instantiate args"); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - // Initialize args to zero - memset(out->instances[i].expression.with_args.args, 0, - sizeof(WASMComponentInstArg) * arg_len); - for (uint32_t j = 0; j < arg_len; ++j) { // Parse core:name (LEB128 length + UTF-8 bytes) WASMComponentCoreName *core_name = NULL; @@ -138,8 +130,6 @@ wasm_component_parse_instances_section(const uint8_t **payload, set_error_buf_ex(error_buf, error_buf_size, "Failed to allocate memory " "for component arg sort idx"); - free_core_name(core_name); - wasm_runtime_free(core_name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; @@ -161,8 +151,6 @@ wasm_component_parse_instances_section(const uint8_t **payload, set_error_buf_ex( error_buf, error_buf_size, "Failed to parse component arg sort idx"); - free_core_name(core_name); - wasm_runtime_free(core_name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; @@ -191,45 +179,35 @@ wasm_component_parse_instances_section(const uint8_t **payload, if (inline_expr_len > 0) { out->instances[i].expression.without_args.inline_expr = - wasm_runtime_malloc( - sizeof(WASMComponentInlineExport) - * inline_expr_len); + wasm_component_checked_calloc( + (uint32_t)inline_expr_len, + sizeof(WASMComponentInlineExport), p, end, 1, + "component inline export", error_buf, + error_buf_size); if (!out->instances[i] .expression.without_args.inline_expr) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for " - "component inline exports"); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - // Initialize inline exports to zero - memset(out->instances[i] - .expression.without_args.inline_expr, - 0, - sizeof(WASMComponentInlineExport) - * inline_expr_len); - for (uint32_t j = 0; j < inline_expr_len; j++) { // inlineexport ::= n: si: - WASMComponentCoreName *name = wasm_runtime_malloc( - sizeof(WASMComponentCoreName)); - if (!name) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory " - "for component export name"); - if (consumed_len) - *consumed_len = (uint32_t)(p - *payload); - return false; - } + /* parse_core_name() allocates the result itself and + * only writes *out on success. Pre-allocating here + * leaked that struct on success and, worse, left + * `name` pointing at uninitialized memory whose + * ->name field free_core_name() then dereferenced + * on the failure path (heap-use-after-free found by + * the component-parser fuzz target). Pass NULL and + * let parse_core_name own the allocation + its own + * cleanup on failure. */ + WASMComponentCoreName *name = NULL; // Parse export name (component-level name) bool name_parse_success = parse_core_name( &p, end, &name, error_buf, error_buf_size); if (!name_parse_success) { - free_core_name(name); - wasm_runtime_free(name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; @@ -253,6 +231,9 @@ wasm_component_parse_instances_section(const uint8_t **payload, } // Zero-initialize sort_idx memset(sort_idx, 0, sizeof(WASMComponentSortIdx)); + out->instances[i] + .expression.without_args.inline_expr[j] + .sort_idx = sort_idx; bool status = parse_sort_idx(&p, end, sort_idx, error_buf, @@ -261,17 +242,10 @@ wasm_component_parse_instances_section(const uint8_t **payload, set_error_buf_ex( error_buf, error_buf_size, "Failed to parse component sort idx"); - wasm_runtime_free(sort_idx); - free_core_name(name); - wasm_runtime_free(name); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - - out->instances[i] - .expression.without_args.inline_expr[j] - .sort_idx = sort_idx; } } else { @@ -293,7 +267,8 @@ wasm_component_parse_instances_section(const uint8_t **payload, } } if (consumed_len) - *consumed_len = payload_len; + *consumed_len = (uint32_t)(p - start); + *payload = p; return true; } @@ -412,14 +387,13 @@ wasm_resolve_instance(struct WASMComponentInstSection *instance_section, bool inst_ok = true; if (instance_expression.arg_len) { - instance_expression.args = - (WASMComponentInstArgInstance *)wasm_runtime_malloc( - instance_expression.arg_len - * sizeof(WASMComponentInstArgInstance)); + instance_expression.args = (WASMComponentInstArgInstance *) + wasm_component_checked_calloc( + instance_expression.arg_len, + sizeof(WASMComponentInstArgInstance), NULL, NULL, 0, + "resolved component instantiate argument", error_buf, + error_buf_size); if (!instance_expression.args) { - set_error_buf_ex( - error_buf, error_buf_size, - "ERROR: Failed to allocate instance expression args"); goto fail_inst; } } @@ -539,7 +513,8 @@ wasm_resolve_instance(struct WASMComponentInstSection *instance_section, fail_inst: inst_ok = false; done_inst: - if (instance_expression.args) wasm_runtime_free(instance_expression.args); + if (instance_expression.args) + wasm_runtime_free(instance_expression.args); instance_expression.args = NULL; if (!inst_ok) return false; diff --git a/core/iwasm/common/component-model/wasm_component_resource.c b/core/iwasm/common/component-model/wasm_component_resource.c index 514bd93994..aad6a9e691 100644 --- a/core/iwasm/common/component-model/wasm_component_resource.c +++ b/core/iwasm/common/component-model/wasm_component_resource.c @@ -4,7 +4,11 @@ */ #include "wasm_component_resource.h" +#include "wasm_component_host_resource.h" +#include "wasm_component_runtime.h" +#include "wasm_runtime.h" #include "wasm_runtime_common.h" +#include "bh_log.h" #include WASMResourceHandle * @@ -42,7 +46,103 @@ wasm_destroy_resource_handle(WASMResourceHandle *handle) if (!handle) return; - // TODO: Call component destructor (handle->rt->dtor_func_idx) + wasm_runtime_free(handle); +} + +bool +wasm_drop_resource_handle(WASMResourceHandle *handle) +{ + bool success = true; + + if (!handle) { + return false; + } + + if (handle->own) { + WASMComponentResourceInstance *rt = handle->rt; + uint32_t rep = handle->rep; + + if (!rt) { + LOG_ERROR("resource drop: missing resource type"); + success = false; + } + else { + if (rt->host_drop_callback) { + if (!rt->host_drop_callback(rt->host_drop_attachment, rep)) { + LOG_ERROR("resource drop: host callback failed for %s/%s " + "representation %u", + rt->interface_name ? rt->interface_name : "?", + rt->name ? rt->name : "?", rep); + success = false; + } + } + else if (rt->is_builtin_wasi) { + HostResourceTable *hr_table = get_global_host_resource_table(); + + if (!hr_table) { + LOG_ERROR("resource drop: host resource table is not " + "initialized"); + success = false; + } + else if (!host_resource_table_delete(hr_table, rep)) { + LOG_ERROR("resource drop: host resource %u was not found", + rep); + success = false; + } + } + else if (rt->is_host) { + LOG_ERROR("resource drop: imported host resource %s/%s has no " + "owner-drop callback", + rt->interface_name ? rt->interface_name : "?", + rt->name ? rt->name : "?"); + success = false; + } + + if (rt->dtor_method) { + WASMModuleInstanceCommon *dtor_module_inst = + (WASMModuleInstanceCommon *) + rt->dtor_method->module_instance; + WASMExecEnv *dtor_exec_env = + wasm_runtime_get_exec_env_singleton(dtor_module_inst); + + if (!dtor_exec_env) { + LOG_ERROR("resource drop: no exec_env for dtor"); + success = false; + } + else { + WASMModuleInstanceCommon *saved_inst = + wasm_runtime_get_module_inst(dtor_exec_env); + wasm_val_t arg = { + .kind = WASM_I32, + .of.i32 = (int32_t)rep, + }; +#ifdef OS_ENABLE_HW_BOUND_CHECK + WASMExecEnv *saved_tls = wasm_runtime_get_exec_env_tls(); + wasm_runtime_set_exec_env_tls(NULL); +#endif + + wasm_exec_env_set_module_inst(dtor_exec_env, + dtor_module_inst); + if (!wasm_runtime_call_wasm_a( + dtor_exec_env, + (WASMFunctionInstanceCommon *)rt->dtor_method, 0, + NULL, 1, &arg)) { + const char *ex = + wasm_runtime_get_exception(dtor_module_inst); + LOG_ERROR("resource drop: dtor call failed: %s", + ex ? ex : "(unknown)"); + success = false; + } + wasm_exec_env_restore_module_inst(dtor_exec_env, + saved_inst); +#ifdef OS_ENABLE_HW_BOUND_CHECK + wasm_runtime_set_exec_env_tls(saved_tls); +#endif + } + } + } + } wasm_runtime_free(handle); -} \ No newline at end of file + return success; +} diff --git a/core/iwasm/common/component-model/wasm_component_resource.h b/core/iwasm/common/component-model/wasm_component_resource.h index 58bbff03e1..790515e379 100644 --- a/core/iwasm/common/component-model/wasm_component_resource.h +++ b/core/iwasm/common/component-model/wasm_component_resource.h @@ -29,9 +29,22 @@ typedef struct WASMResourceHandle { WASMResourceHandle * wasm_create_resource_handle(WASMComponentResourceInstance *rt, uint32_t rep, bool own); + +/** Release only the handle wrapper without dropping its representation. */ void wasm_destroy_resource_handle(WASMResourceHandle *handle); +/** + * Drop a handle from a component resource table. Owned handles destroy their + * underlying representation before the handle wrapper is released. Borrowed + * handles only release the wrapper; their task bookkeeping remains the + * caller's responsibility. + * + * The handle wrapper is released even when representation teardown fails. + */ +bool +wasm_drop_resource_handle(WASMResourceHandle *handle); + static inline uint32_t wasm_resource_handle_get_rep_i32(const WASMResourceHandle *handle) { diff --git a/core/iwasm/common/component-model/wasm_component_resource_table.c b/core/iwasm/common/component-model/wasm_component_resource_table.c index ba629e4ab2..68f5e42f05 100644 --- a/core/iwasm/common/component-model/wasm_component_resource_table.c +++ b/core/iwasm/common/component-model/wasm_component_resource_table.c @@ -11,7 +11,8 @@ WASMComponentResourceTable * wasm_component_table_init(uint32_t initial_size, uint32_t resize_percent) { - if (initial_size == 0 || resize_percent == 0) { + if (initial_size == 0 || initial_size > WASM_COMPONENT_TABLE_MAX_LENGTH + || resize_percent == 0) { return NULL; } @@ -61,9 +62,14 @@ wasm_component_table_destroy(WASMComponentResourceTable *table) if (table->array[i]) { WASMTableElement *elem = table->array[i]; + /* Clear the slot before calling user-defined destructor code so a + * reentrant lookup cannot observe a half-dropped handle. */ + table->array[i] = NULL; + // Destroy the underlying object based on type if (elem->type == WASM_TABLE_ELEM_RESOURCE_HANDLE && elem->ptr) { - wasm_destroy_resource_handle((WASMResourceHandle *)elem->ptr); + (void)wasm_drop_resource_handle( + (WASMResourceHandle *)elem->ptr); } // TODO: Add destructors for other element types @@ -108,8 +114,17 @@ wasm_component_table_remove(WASMComponentResourceTable *table, uint32_t index) return false; } + if (table->free_count >= table->array_size) { + return false; // Should not happen + } + WASMTableElement *elem = table->array[index]; + /* Detach first. wasm_component_table_remove() transfers ownership and must + * only destroy the table's handle wrapper, never the representation. */ + table->array[index] = NULL; + table->free_list[table->free_count++] = index; + // Destroy the underlying object based on type if (elem->type == WASM_TABLE_ELEM_RESOURCE_HANDLE && elem->ptr) { wasm_destroy_resource_handle((WASMResourceHandle *)elem->ptr); @@ -118,18 +133,33 @@ wasm_component_table_remove(WASMComponentResourceTable *table, uint32_t index) wasm_runtime_free(elem); - // Remove element from table - table->array[index] = NULL; + return true; +} - // Add to free list - if (table->free_count >= table->array_size) { - return false; // Should not happen +bool +wasm_component_table_drop_resource(WASMComponentResourceTable *table, + uint32_t index) +{ + WASMTableElement *elem; + bool success; + + if (!table || index == 0 || index >= table->next_index + || table->array[index] == NULL + || table->array[index]->type != WASM_TABLE_ELEM_RESOURCE_HANDLE + || table->free_count >= table->array_size) { + return false; } - table->free_list[table->free_count] = index; - table->free_count++; + elem = table->array[index]; - return true; + /* Commit the removal before invoking destructor code. Even when teardown + * reports an error, this handle must not be dropped a second time. */ + table->array[index] = NULL; + table->free_list[table->free_count++] = index; + + success = wasm_drop_resource_handle((WASMResourceHandle *)elem->ptr); + wasm_runtime_free(elem); + return success; } static bool @@ -138,34 +168,50 @@ wasm_component_table_resize(WASMComponentResourceTable *table) if (!table) return false; - // Calculate new size based on resize_percent - uint32_t new_size = - table->array_size + (table->array_size * table->resize_percent / 100); + /* Calculate in 64 bits so a hostile resize percentage cannot wrap. */ + uint64_t growth = (uint64_t)table->array_size * table->resize_percent / 100; + uint64_t requested_size = (uint64_t)table->array_size + growth; // Ensure minimum growth (at least 1 more slot) - if (new_size <= table->array_size) { - new_size = table->array_size + 1; + if (requested_size <= table->array_size) { + requested_size = (uint64_t)table->array_size + 1; } - if (new_size > WASM_COMPONENT_TABLE_MAX_LENGTH) { + if (requested_size > WASM_COMPONENT_TABLE_MAX_LENGTH) { return false; } - // Reallocate both array and free_list to same size - WASMTableElement **new_array = (WASMTableElement **)wasm_runtime_realloc( - (void *)table->array, sizeof(WASMTableElement *) * new_size); - uint32_t *new_free_list = - wasm_runtime_realloc(table->free_list, sizeof(uint32_t) * new_size); + uint32_t new_size = (uint32_t)requested_size; + uint32_t array_bytes = (uint32_t)(sizeof(WASMTableElement *) * new_size); + uint32_t free_list_bytes = (uint32_t)(sizeof(uint32_t) * new_size); + + /* Allocate-copy-commit keeps both old buffers valid unless the complete + * resize succeeds. Two realloc calls cannot provide that guarantee. */ + WASMTableElement **new_array = + (WASMTableElement **)wasm_runtime_malloc(array_bytes); + uint32_t *new_free_list = wasm_runtime_malloc(free_list_bytes); if (!new_array || !new_free_list) { + if (new_array) { + wasm_runtime_free((void *)new_array); + } + if (new_free_list) { + wasm_runtime_free(new_free_list); + } return false; } + memcpy((void *)new_array, (const void *)table->array, + sizeof(WASMTableElement *) * table->array_size); + memcpy(new_free_list, table->free_list, + sizeof(uint32_t) * table->array_size); memset((void *)&new_array[table->array_size], 0, sizeof(WASMTableElement *) * (new_size - table->array_size)); memset(&new_free_list[table->array_size], 0, sizeof(uint32_t) * (new_size - table->array_size)); + wasm_runtime_free((void *)table->array); + wasm_runtime_free(table->free_list); table->array = new_array; table->free_list = new_free_list; table->array_size = new_size; diff --git a/core/iwasm/common/component-model/wasm_component_resource_table.h b/core/iwasm/common/component-model/wasm_component_resource_table.h index 1fd54c8d26..ca53d2b4c3 100644 --- a/core/iwasm/common/component-model/wasm_component_resource_table.h +++ b/core/iwasm/common/component-model/wasm_component_resource_table.h @@ -39,9 +39,20 @@ typedef struct WASMComponentResourceTable { bool wasm_component_table_add(WASMComponentResourceTable *table, void *ptr, WASMTableElementType type, uint32_t *out); + +/** Remove an element without dropping its underlying representation. */ bool wasm_component_table_remove(WASMComponentResourceTable *table, uint32_t index); +/** + * Remove and drop a resource handle. Unlike wasm_component_table_remove(), + * this destroys an owned representation and is therefore not suitable for an + * ownership transfer. + */ +bool +wasm_component_table_drop_resource(WASMComponentResourceTable *table, + uint32_t index); + void * wasm_component_table_get(WASMComponentResourceTable *table, uint32_t index, WASMTableElementType expected_type); diff --git a/core/iwasm/common/component-model/wasm_component_runtime.c b/core/iwasm/common/component-model/wasm_component_runtime.c index 269020b98e..a83974bad4 100644 --- a/core/iwasm/common/component-model/wasm_component_runtime.c +++ b/core/iwasm/common/component-model/wasm_component_runtime.c @@ -9,6 +9,7 @@ #include "wasm_component.h" #include "wasm_loader.h" #include "wasm_component_runtime.h" +#include "wasm_component_canon.h" #include "bh_log.h" #include "stdio.h" #include "wasm_component_canonical.h" @@ -438,17 +439,29 @@ compute_elem_size(WASMComponentTypeInstance *type) } } -/// @brief Calculate size needed for the default value types defined in this -/// component -/// @param def_val_type -/// @return -uint32 -wasm_get_def_val_type_size(WASMComponentDefValType *def_val_type) +static bool +component_layout_add(uint64 *total, uint64 count, uint64 element_size) { - if (!def_val_type) { - return 0; + if (!total + || (element_size != 0 + && count > (UINT64_MAX - *total) / element_size)) { + return false; + } + *total += count * element_size; + return true; +} + +bool +wasm_get_def_val_type_size(WASMComponentDefValType *def_val_type, uint64 *size) +{ + uint64 total = 0; + + if (!def_val_type || !size + || !component_layout_add(&total, 1, + sizeof(WASMComponentTypeInstance))) { + return false; } - uint32 size = sizeof(WASMComponentTypeInstance); + switch (def_val_type->tag) { case WASM_COMP_DEF_VAL_PRIMVAL: // pvt: // Flags type @@ -460,75 +473,123 @@ wasm_get_def_val_type_size(WASMComponentDefValType *def_val_type) break; // Record type (labeled fields) case WASM_COMP_DEF_VAL_RECORD: // 0x72 lt*:vec() - size += (uint32)(sizeof(WASMComponentRecordInstance) - + (def_val_type->def_val.record->count - * sizeof(WASMComponentLabelValTypeInstance))); - LOG_DEBUG("Detected size of Record is %d", size); + if (!def_val_type->def_val.record + || (def_val_type->def_val.record->count > 0 + && !def_val_type->def_val.record->fields) + || !component_layout_add(&total, 1, + sizeof(WASMComponentRecordInstance)) + || !component_layout_add( + &total, def_val_type->def_val.record->count, + sizeof(WASMComponentLabelValTypeInstance))) { + return false; + } break; // Variant type (labeled cases) case WASM_COMP_DEF_VAL_VARIANT: // 0x71 case*:vec() - size += (uint32)(sizeof(WASMComponentVariantInstance) - + (def_val_type->def_val.variant->count - * sizeof(WASMComponentCaseValInstance))); - LOG_DEBUG("Detected size of Variant is %d", size); + if (!def_val_type->def_val.variant + || (def_val_type->def_val.variant->count > 0 + && !def_val_type->def_val.variant->cases) + || !component_layout_add(&total, 1, + sizeof(WASMComponentVariantInstance)) + || !component_layout_add( + &total, def_val_type->def_val.variant->count, + sizeof(WASMComponentCaseValInstance))) { + return false; + } break; // List types case WASM_COMP_DEF_VAL_LIST: // 0x70 t: - size += sizeof(WASMComponentListInstance); - LOG_DEBUG("Detected size of list is %d", size); + if (!def_val_type->def_val.list + || !component_layout_add(&total, 1, + sizeof(WASMComponentListInstance))) { + return false; + } break; case WASM_COMP_DEF_VAL_LIST_LEN: // 0x67 t: len: - size += sizeof(WASMComponentListLenInstance); - LOG_DEBUG("Detected size of fixed size list is %d", size); + if (!def_val_type->def_val.list_len + || !component_layout_add( + &total, 1, sizeof(WASMComponentListLenInstance))) { + return false; + } break; // Tuple type case WASM_COMP_DEF_VAL_TUPLE: // 0x6f t*:vec() - size += (uint32)(sizeof(WASMComponentTupleInstance) - + (def_val_type->def_val.tuple->count - * sizeof(WASMComponentTypeInstance *))); - LOG_DEBUG("Detected size of Tuple is %d", size); + if (!def_val_type->def_val.tuple + || (def_val_type->def_val.tuple->count > 0 + && !def_val_type->def_val.tuple->element_types) + || !component_layout_add(&total, 1, + sizeof(WASMComponentTupleInstance)) + || !component_layout_add(&total, + def_val_type->def_val.tuple->count, + sizeof(WASMComponentTypeInstance *))) { + return false; + } break; // Option type case WASM_COMP_DEF_VAL_OPTION: // 0x6b t: - size += sizeof(WASMComponentOptionInstance); - LOG_DEBUG("Detected size of option is %d", size); + if (!def_val_type->def_val.option + || !component_layout_add(&total, 1, + sizeof(WASMComponentOptionInstance))) { + return false; + } break; // Result type case WASM_COMP_DEF_VAL_RESULT: // 0x6a t?:? u?:? - size += sizeof(WASMComponentResultInstance); - LOG_DEBUG("Detected size of result is %d", size); + if (!def_val_type->def_val.result + || !component_layout_add(&total, 1, + sizeof(WASMComponentResultInstance))) { + return false; + } break; // Handle types case WASM_COMP_DEF_VAL_OWN: // 0x69 i: case WASM_COMP_DEF_VAL_BORROW: // 0x68 i: - size += sizeof(WASMComponentResourceInstance); + if ((def_val_type->tag == WASM_COMP_DEF_VAL_OWN + && !def_val_type->def_val.owned) + || (def_val_type->tag == WASM_COMP_DEF_VAL_BORROW + && !def_val_type->def_val.borrow) + || !component_layout_add( + &total, 1, sizeof(WASMComponentResourceHandleInstance))) { + return false; + } break; // Async types case WASM_COMP_DEF_VAL_STREAM: // 0x66 t?:? case WASM_COMP_DEF_VAL_FUTURE: // 0x65 t?:? LOG_WARNING("Not yet supported\n"); break; + default: + return false; } - return size; + *size = total; + return true; } /// @brief Calculate size needed for the function types defined in this /// component /// @param func_type /// @return -uint32 -wasm_get_func_type_size(WASMComponentFuncType *func_type) +bool +wasm_get_func_type_size(WASMComponentFuncType *func_type, uint64 *size) { - if (!func_type) { - return 0; + uint64 total = 0; + + if (!func_type || !func_type->params || !func_type->results || !size + || (func_type->params->count > 0 && !func_type->params->params) + || !component_layout_add(&total, 1, + sizeof(WASMComponentFuncTypeInstance)) + || !component_layout_add(&total, 1, + sizeof(WASMComponentParamListInstance)) + || !component_layout_add(&total, func_type->params->count, + sizeof(WASMComponentLabelValTypeInstance)) + || !component_layout_add(&total, 1, + sizeof(WASMComponentResultListInstance)) + || !component_layout_add(&total, 1, + sizeof(WASMComponentTypeInstance))) { + return false; } - uint32 size = (uint32)(sizeof(WASMComponentFuncTypeInstance) - + sizeof(WASMComponentParamListInstance) - + (func_type->params->count - * sizeof(WASMComponentLabelValTypeInstance)) - + sizeof(WASMComponentResultListInstance) - + sizeof(WASMComponentTypeInstance)); - return size; + *size = total; + return true; } /// @brief Calculates total size required by component instance type (including @@ -537,15 +598,20 @@ wasm_get_func_type_size(WASMComponentFuncType *func_type) /// @param instance_type_size OUT: structure that will hold the index counts + /// total defined types memory size /// @return -uint32 +bool wasm_get_inst_decl_size(WASMComponentInstType *instance_type, - WASMComponentInstanceDeclTypeSize *instance_type_size) + WASMComponentInstanceDeclTypeSize *instance_type_size, + uint64 *size) { - if (!instance_type) { - return 0; + uint64 total = 0, types_size = 0, funcs_count = 0, types_count = 0, + exports_count = 0, resource_count = 0, method_count = 0; + uint32 idx = 0; + + if (!instance_type || !size + || (instance_type->count > 0 && !instance_type->instance_decls)) { + return false; } - uint32 size = 0, types_size = 0, funcs_count = 0, types_count = 0, - exports_count = 0, resource_count = 0, idx = 0; + for (idx = 0; idx < instance_type->count; idx++) { WASMComponentInstDecl *instance_decl = &instance_type->instance_decls[idx]; @@ -555,15 +621,34 @@ wasm_get_inst_decl_size(WASMComponentInstType *instance_type, break; case WASM_COMP_COMPONENT_DECL_INSTANCE_TYPE: // The new types defined in this instance type + if (!instance_decl->decl.type) { + return false; + } switch (instance_decl->decl.type->tag) { case WASM_COMP_DEF_TYPE: - types_size += wasm_get_def_val_type_size( - instance_decl->decl.type->type.def_val_type); + { + uint64 nested_size = 0; + if (!wasm_get_def_val_type_size( + instance_decl->decl.type->type.def_val_type, + &nested_size) + || !component_layout_add(&types_size, 1, + nested_size)) { + return false; + } break; + } case WASM_COMP_FUNC_TYPE: - types_size += wasm_get_func_type_size( - instance_decl->decl.type->type.func_type); + { + uint64 nested_size = 0; + if (!wasm_get_func_type_size( + instance_decl->decl.type->type.func_type, + &nested_size) + || !component_layout_add(&types_size, 1, + nested_size)) { + return false; + } break; + } default: LOG_WARNING("Instance declaration type only supports " "types and functions for now \n"); @@ -573,6 +658,10 @@ wasm_get_inst_decl_size(WASMComponentInstType *instance_type, break; case WASM_COMP_COMPONENT_DECL_INSTANCE_ALIAS: // Increase index count for the aliased sort + if (!instance_decl->decl.alias + || !instance_decl->decl.alias->sort) { + return false; + } switch (instance_decl->decl.alias->sort->sort) { case WASM_COMP_SORT_FUNC: funcs_count++; @@ -589,22 +678,34 @@ wasm_get_inst_decl_size(WASMComponentInstType *instance_type, case WASM_COMP_COMPONENT_DECL_INSTANCE_EXPORTDECL: // Increase index count + export count for export definition exports_count++; + if (!instance_decl->decl.export_decl + || !instance_decl->decl.export_decl->extern_desc) { + return false; + } switch (instance_decl->decl.export_decl->extern_desc->type) { case WASM_COMP_EXTERN_FUNC: funcs_count++; break; case WASM_COMP_EXTERN_TYPE: types_count++; + if (!instance_decl->decl.export_decl->extern_desc + ->extern_desc.type.type_bound) { + return false; + } if (instance_decl->decl.export_decl->extern_desc ->extern_desc.type.type_bound->tag == WASM_COMP_TYPEBOUND_TYPE) { - types_size += - sizeof(WASMComponentTypeInstance) - + sizeof(WASMComponentResourceInstance) - + 3 - * sizeof( - WASMFunctionInstance); // sub resource - // definition + if (!component_layout_add( + &types_size, 1, + sizeof(WASMComponentTypeInstance)) + || !component_layout_add( + &types_size, 1, + sizeof(WASMComponentResourceInstance)) + || !component_layout_add( + &types_size, 3, + sizeof(WASMFunctionInstance))) { + return false; + } resource_count++; } break; @@ -620,24 +721,40 @@ wasm_get_inst_decl_size(WASMComponentInstType *instance_type, } } + if (types_count > UINT32_MAX || funcs_count > UINT32_MAX + || exports_count > UINT32_MAX || resource_count > UINT32_MAX) { + return false; + } + + method_count = funcs_count; + if (!component_layout_add(&method_count, resource_count, 3) + || !component_layout_add(&total, 1, sizeof(WASMComponentTypeInstance)) + || !component_layout_add(&total, 1, + sizeof(WASMComponentInstTypeInstance)) + || !component_layout_add(&total, 1, types_size) + || !component_layout_add(&total, types_count, + sizeof(WASMComponentTypeInstance)) + || !component_layout_add(&total, funcs_count, + sizeof(WASMComponentFunctionInstance)) + || !component_layout_add(&total, exports_count, + sizeof(WASMComponentExportInstance)) + || !component_layout_add(&total, method_count, + sizeof(WASMFunctionInstance)) + || !component_layout_add(&total, method_count, + sizeof(WASMFunctionImport))) { + return false; + } + if (instance_type_size) { // Needed for instantiating the WASMComponentInstTypeInstance structure - instance_type_size->exports_count = exports_count; - instance_type_size->func_count = funcs_count; - instance_type_size->types_count = types_count; + instance_type_size->exports_count = (uint32)exports_count; + instance_type_size->func_count = (uint32)funcs_count; + instance_type_size->types_count = (uint32)types_count; instance_type_size->types_size = types_size; - instance_type_size->resource_count = resource_count; - } - size = (uint32)(sizeof(WASMComponentTypeInstance) - + sizeof(WASMComponentInstTypeInstance) + types_size - + (types_count * sizeof(WASMComponentTypeInstance)) - + (funcs_count * sizeof(WASMComponentFunctionInstance)) - + (exports_count * sizeof(WASMComponentExportInstance)) - + ((funcs_count + (resource_count * 3)) - * sizeof(WASMFunctionInstance)) - + ((funcs_count + (resource_count * 3)) - * sizeof(WASMFunctionImport))); - return size; + instance_type_size->resource_count = (uint32)resource_count; + } + *size = total; + return true; } /// @brief Calculate total size that needs to be allocated for the instance == @@ -704,6 +821,7 @@ wasm_component_get_index_count(WASMComponent *component, char *error_buf, (uint32)section->parsed.instance_section->count; break; case WASM_COMP_SECTION_ALIASES: + { uint32 aliases_count = (uint32)section->parsed.alias_section->count; for (idx = 0; idx < aliases_count; idx++) { @@ -762,6 +880,7 @@ wasm_component_get_index_count(WASMComponent *component, char *error_buf, } } break; + } case WASM_COMP_SECTION_TYPE: index_count->types += (uint32)section->parsed.type_section->count; @@ -769,6 +888,8 @@ wasm_component_get_index_count(WASMComponent *component, char *error_buf, idx++) { WASMComponentTypes *type = §ion->parsed.type_section->types[idx]; + uint64 type_size = 0; + bool has_layout = true; if (!type) { set_error_buf_ex(error_buf, error_buf_size, "ERROR: Invalid type section"); @@ -776,28 +897,50 @@ wasm_component_get_index_count(WASMComponent *component, char *error_buf, } switch (type->tag) { case WASM_COMP_DEF_TYPE: - index_count->types_total_size += - wasm_get_def_val_type_size( - type->type.def_val_type); + if (!wasm_get_def_val_type_size( + type->type.def_val_type, &type_size)) { + has_layout = false; + } break; case WASM_COMP_FUNC_TYPE: - index_count->types_total_size += - wasm_get_func_type_size(type->type.func_type); + if (!wasm_get_func_type_size(type->type.func_type, + &type_size)) { + has_layout = false; + } break; case WASM_COMP_INSTANCE_TYPE: - index_count->types_total_size += - wasm_get_inst_decl_size( - type->type.instance_type, NULL); + if (!wasm_get_inst_decl_size( + type->type.instance_type, NULL, + &type_size)) { + has_layout = false; + } break; case WASM_COMP_RESOURCE_TYPE_SYNC: - index_count->types_total_size += - sizeof(WASMComponentTypeInstance) - + sizeof(WASMComponentResourceInstance) - + 3 * sizeof(WASMFunctionInstance); + if (!component_layout_add( + &type_size, 1, + sizeof(WASMComponentTypeInstance)) + || !component_layout_add( + &type_size, 1, + sizeof(WASMComponentResourceInstance)) + || !component_layout_add( + &type_size, 3, + sizeof(WASMFunctionInstance))) { + has_layout = false; + } break; default: LOG_WARNING("Other types not supported for now\n"); - break; + continue; + } + if (!has_layout + || !component_layout_add(&index_count->types_total_size, + 1, type_size)) { + set_error_buf_ex( + error_buf, error_buf_size, + "ERROR: Component type layout is too large or " + "invalid\n"); + wasm_runtime_free(index_count); + return NULL; } } break; @@ -880,6 +1023,7 @@ wasm_component_get_index_count(WASMComponent *component, char *error_buf, // TODO: not in scope for now break; case WASM_COMP_SECTION_CANONS: + { WASMComponentCanon *canon = NULL; for (idx = 0; idx < section->parsed.canon_section->count; idx++) { @@ -918,6 +1062,7 @@ wasm_component_get_index_count(WASMComponent *component, char *error_buf, } } break; + } default: set_error_buf_ex( error_buf, error_buf_size, @@ -929,10 +1074,37 @@ wasm_component_get_index_count(WASMComponent *component, char *error_buf, return index_count; } +static bool +component_instance_add_storage(uint64 *total_size, uint64 count, + uint64 element_size, const char *description, + char *error_buf, uint32 error_buf_size) +{ + if (!total_size || element_size == 0 || *total_size > UINT32_MAX + || count > ((uint64)UINT32_MAX - *total_size) / element_size) { + set_error_buf_ex(error_buf, error_buf_size, + "Component instance allocation is too large at %s", + description); + return false; + } + + *total_size += count * element_size; + return true; +} + WASMComponentInstance * wasm_component_instance_allocate(WASMComponentIndexCount *index_count, char *error_buf, uint32 error_buf_size) { + uint64 total_size_64 = 0; + uint32 total_size; + WASMComponentInstance *comp_instance; + + if (!index_count) { + set_error_buf_ex(error_buf, error_buf_size, + "Component index counts are NULL"); + return NULL; + } + LOG_DEBUG("Allocate memory for component instance with:"); LOG_DEBUG(" %d components", index_count->components); LOG_DEBUG(" %d instances", index_count->instances); @@ -947,48 +1119,85 @@ wasm_component_instance_allocate(WASMComponentIndexCount *index_count, LOG_DEBUG(" %d types", index_count->types); LOG_DEBUG(" %d values", index_count->values); LOG_DEBUG(" %d exports", index_count->exports); - // Allocate memory for the Component instance + defined types - uint32 total_size = - (uint32)(sizeof(WASMComponentInstance) - + (index_count->functions - * sizeof(WASMComponentFunctionInstance *)) - + (index_count->values * sizeof(WASMComponentValue *)) - + (index_count->types * sizeof(WASMComponentTypeInstance *)) - + (index_count->instances * sizeof(WASMComponentInstance *)) - + (index_count->components * sizeof(WASMComponent *)) - + (index_count->core_functions - * sizeof(WASMFunctionInstance *)) - + (index_count->core_tables * sizeof(WASMTableInstance *)) - + (index_count->core_memories * sizeof(WASMMemoryInstance *)) - + (index_count->core_globals * sizeof(WASMGlobalInstance *)) - + (index_count->core_types * sizeof(WASMType *)) - + (index_count->core_instances * sizeof(WASMModuleInstance *)) - + (index_count->core_modules * sizeof(WASMModule *)) - + (index_count->exports * sizeof(WASMComponentExportInstance)) - + index_count->types_total_size - + (index_count->defined_core_functions - * sizeof(WASMFunctionInstance)) - + (index_count->defined_functions - * sizeof(WASMComponentFunctionInstance)) - + (index_count->defined_core_instances - * sizeof(WASMModuleInstance *)) - + (index_count->defined_instances - * sizeof(WASMComponentInstance *)) - + (index_count->canon_options_funcs - * sizeof(WASMComponentCanonOptsInstance)) - + (index_count->canon_options - * sizeof(WASMComponentCanonOptInstance))); - WASMComponentInstance *comp_instance = wasm_runtime_malloc(total_size); + +#define ADD_INSTANCE_STORAGE(count, type, description) \ + do { \ + if (!component_instance_add_storage(&total_size_64, (uint64)(count), \ + sizeof(type), description, \ + error_buf, error_buf_size)) { \ + return NULL; \ + } \ + } while (0) + + ADD_INSTANCE_STORAGE(1, WASMComponentInstance, "instance header"); + ADD_INSTANCE_STORAGE(index_count->functions, + WASMComponentFunctionInstance *, "functions"); + ADD_INSTANCE_STORAGE(index_count->values, WASMComponentValue *, "values"); + ADD_INSTANCE_STORAGE(index_count->types, WASMComponentTypeInstance *, + "types"); + ADD_INSTANCE_STORAGE(index_count->instances, WASMComponentInstance *, + "component instances"); + ADD_INSTANCE_STORAGE(index_count->components, WASMComponent *, + "components"); + ADD_INSTANCE_STORAGE(index_count->core_functions, WASMFunctionInstance *, + "core functions"); + ADD_INSTANCE_STORAGE(index_count->core_tables, WASMTableInstance *, + "core tables"); + ADD_INSTANCE_STORAGE(index_count->core_memories, WASMMemoryInstance *, + "core memories"); + ADD_INSTANCE_STORAGE(index_count->core_globals, WASMGlobalInstance *, + "core globals"); + ADD_INSTANCE_STORAGE(index_count->core_types, WASMType *, "core types"); + ADD_INSTANCE_STORAGE(index_count->core_instances, WASMModuleInstance *, + "core instances"); + ADD_INSTANCE_STORAGE(index_count->core_modules, WASMModule *, + "core modules"); + ADD_INSTANCE_STORAGE(index_count->exports, WASMComponentExportInstance, + "exports"); + ADD_INSTANCE_STORAGE(index_count->types_total_size, uint8, + "defined type data"); + ADD_INSTANCE_STORAGE(index_count->defined_core_functions, + WASMFunctionInstance, "defined core functions"); + ADD_INSTANCE_STORAGE(index_count->defined_functions, + WASMComponentFunctionInstance, + "defined component functions"); + ADD_INSTANCE_STORAGE(index_count->defined_core_instances, + WASMModuleInstance *, "defined core instances"); + ADD_INSTANCE_STORAGE(index_count->defined_instances, + WASMComponentInstance *, + "defined component instances"); + ADD_INSTANCE_STORAGE(index_count->canon_options_funcs, + WASMComponentCanonOptsInstance, + "canonical option groups"); + ADD_INSTANCE_STORAGE(index_count->canon_options, + WASMComponentCanonOptInstance, "canonical options"); + +#undef ADD_INSTANCE_STORAGE + + total_size = (uint32)total_size_64; + comp_instance = wasm_runtime_malloc(total_size); + if (!comp_instance) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Failed to allocate component instance\n"); + return NULL; + } memset(comp_instance, 0, total_size); + wasm_runtime_instantiation_args_set_defaults( + &comp_instance->instantiation_args); + wasm_runtime_instantiation_args_set_default_stack_size( + &comp_instance->instantiation_args, COMPONENT_DEFAULT_STACK_SIZE); + wasm_runtime_instantiation_args_set_host_managed_heap_size( + &comp_instance->instantiation_args, COMPONENT_DEFAULT_HEAP_SIZE); + comp_instance->default_wasm_stack_size = COMPONENT_DEFAULT_STACK_SIZE; // The types defined in this component will be stored in the memory region // right after the component instance, similar to Wasm Module implementation - comp_instance->defined_types_size = index_count->types_total_size; + comp_instance->defined_types_size = (uint32)index_count->types_total_size; comp_instance->defined_canon_opts_size = - (uint32)((index_count->canon_options_funcs - * sizeof(WASMComponentCanonOptsInstance)) - + (index_count->canon_options - * sizeof(WASMComponentCanonOptInstance))); + (uint32)((uint64)index_count->canon_options_funcs + * sizeof(WASMComponentCanonOptsInstance) + + (uint64)index_count->canon_options + * sizeof(WASMComponentCanonOptInstance)); comp_instance->functions = (WASMComponentFunctionInstance **)((uint8_t *)comp_instance + sizeof(WASMComponentInstance)); @@ -1156,9 +1365,10 @@ get_core_index_count(WASMInstExpr *instance_expression) } WASMComponentInstance * -wasm_component_instantiate_internal( +wasm_component_instantiate_internal_ex( WASMComponent *component, - WASMComponentInstArgInstances *instance_expression, char *error_buf, + WASMComponentInstArgInstances *instance_expression, + const struct InstantiationArgs2 *args, char *error_buf, uint32 error_buf_size) { LOG_DEBUG("Instantiate internal"); @@ -1185,24 +1395,40 @@ wasm_component_instantiate_internal( if (instance_expression) { comp_instance->parent = instance_expression->parent; } + if (args) { + comp_instance->instantiation_args = *args; + } + comp_instance->default_wasm_stack_size = + comp_instance->instantiation_args.v1.default_stack_size; + comp_instance->custom_data = comp_instance->instantiation_args.custom_data; comp_instance->component = component; uint32 section_idx = 0; WASMComponentSection *section = NULL; -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI_P2 != 0 if (!comp_instance->parent) { + const WASIArguments *wasi_args = &component->wasi_args; + if (component->wasi_args.set_by_user + && comp_instance->instantiation_args.wasi.set_by_user) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: WASI configuration was given via both " + "the component and InstantiationArgs2\n"); + wasm_component_deinstantiate(comp_instance); + comp_instance = NULL; + goto done; + } + if (comp_instance->instantiation_args.wasi.set_by_user + || !component->wasi_args.set_by_user) { + wasi_args = &comp_instance->instantiation_args.wasi; + } if (!wasm_component_runtime_init_wasi( - comp_instance, component->wasi_args.dir_list, - component->wasi_args.dir_count, - component->wasi_args.map_dir_list, - component->wasi_args.map_dir_count, component->wasi_args.env, - component->wasi_args.env_count, component->wasi_args.addr_pool, - component->wasi_args.addr_count, - component->wasi_args.ns_lookup_pool, - component->wasi_args.ns_lookup_count, component->wasi_args.argv, - component->wasi_args.argc, component->wasi_args.stdio[0], - component->wasi_args.stdio[1], component->wasi_args.stdio[2], - error_buf, error_buf_size)) { + comp_instance, wasi_args->dir_list, wasi_args->dir_count, + wasi_args->map_dir_list, wasi_args->map_dir_count, + wasi_args->env, wasi_args->env_count, wasi_args->addr_pool, + wasi_args->addr_count, wasi_args->ns_lookup_pool, + wasi_args->ns_lookup_count, wasi_args->argv, wasi_args->argc, + wasi_args->stdio[0], wasi_args->stdio[1], wasi_args->stdio[2], + wasi_args->wasi_options, error_buf, error_buf_size)) { set_error_buf_ex(error_buf, error_buf_size, "ERROR: Failed to initiate component wasi args\n"); wasm_component_deinstantiate(comp_instance); @@ -1245,10 +1471,12 @@ wasm_component_instantiate_internal( if (!wasm_resolve_core_instance( section->parsed.core_instance_section, comp_instance, error_buf, error_buf_size)) { - set_error_buf_ex(error_buf, error_buf_size, - "ERROR: Core instance section %d " - "instantiation failed\n", - section->id); + if (!error_buf || error_buf[0] == '\0') { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Core instance section %d " + "instantiation failed\n", + section->id); + } wasm_component_deinstantiate(comp_instance); comp_instance = NULL; goto done; @@ -1337,8 +1565,12 @@ wasm_component_instantiate_internal( } break; case WASM_COMP_SECTION_START: - // TODO: Not in scope for now - break; + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Component start sections are not " + "supported\n"); + wasm_component_deinstantiate(comp_instance); + comp_instance = NULL; + goto done; case WASM_COMP_SECTION_IMPORTS: LOG_DEBUG("%d : --Import section", section_idx); bool import_failed = false; @@ -1346,17 +1578,17 @@ wasm_component_instantiate_internal( import_failed = !wasm_resolve_imports( section->parsed.import_section, comp_instance, instance_expression, error_buf, error_buf_size); -#if WASM_ENABLE_LIBC_WASI != 0 else if (!comp_instance->parent) - import_failed = !wasm_resolve_imports_WASI( + import_failed = !wasm_resolve_imports_host( section->parsed.import_section, comp_instance, error_buf, error_buf_size); -#endif if (import_failed) { - set_error_buf_ex( - error_buf, error_buf_size, - "ERROR: Import section %d instantiation failed\n", - section->id); + if (!error_buf || error_buf[0] == '\0') { + set_error_buf_ex( + error_buf, error_buf_size, + "ERROR: Import section %d instantiation failed\n", + section->id); + } wasm_component_deinstantiate(comp_instance); comp_instance = NULL; goto done; @@ -1377,8 +1609,12 @@ wasm_component_instantiate_internal( } break; case WASM_COMP_SECTION_VALUES: - // TODO, not in scope for now - break; + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Component value sections are not " + "supported\n"); + wasm_component_deinstantiate(comp_instance); + comp_instance = NULL; + goto done; default: set_error_buf_ex( error_buf, error_buf_size, @@ -1394,6 +1630,20 @@ wasm_component_instantiate_internal( return comp_instance; } +WASMComponentInstance * +wasm_component_instantiate_internal( + WASMComponent *component, + WASMComponentInstArgInstances *instance_expression, char *error_buf, + uint32 error_buf_size) +{ + const struct InstantiationArgs2 *args = + instance_expression && instance_expression->parent + ? &instance_expression->parent->instantiation_args + : NULL; + return wasm_component_instantiate_internal_ex( + component, instance_expression, args, error_buf, error_buf_size); +} + WASMComponentInstance * wasm_component_instantiate(WASMComponent *component, char *error_buf, uint32 error_buf_size) @@ -1403,7 +1653,415 @@ wasm_component_instantiate(WASMComponent *component, char *error_buf, error_buf_size); } -#if WASM_ENABLE_LIBC_WASI != 0 +WASMComponentInstance * +wasm_component_instantiate_ex2(WASMComponent *component, + const struct InstantiationArgs2 *args, + char *error_buf, uint32 error_buf_size) +{ + if (!args) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: InstantiationArgs2 is NULL\n"); + return NULL; + } + return wasm_component_instantiate_internal_ex(component, NULL, args, + error_buf, error_buf_size); +} + +static void +component_set_custom_data(WASMComponentInstance *comp_instance, + void *custom_data, uint32 depth) +{ + if (!comp_instance || depth > MAX_DEPTH_RECURSION) { + return; + } + + comp_instance->custom_data = custom_data; + comp_instance->instantiation_args.custom_data = custom_data; + + for (uint32 idx = 0; idx < comp_instance->types_count; idx++) { + WASMComponentTypeInstance *type = comp_instance->types[idx]; + WASMComponentResourceInstance *resource = NULL; + + if (type + && (type->type == COMPONENT_VAL_TYPE_RESOURCE_SYNC + || type->type == COMPONENT_VAL_TYPE_RESOURCE_ASYNC) + && (resource = type->type_specific.resource) + && resource->host_drop_attachment_is_custom_data) { + resource->host_drop_attachment = custom_data; + } + } + + for (uint32 idx = 0; idx < comp_instance->defined_core_instances_count; + idx++) { + WASMModuleInstance *core_inst = + comp_instance->defined_core_instances[idx]; + + if (core_inst && core_inst->e) { + wasm_runtime_set_custom_data_internal( + (WASMModuleInstanceCommon *)core_inst, custom_data); + } + } + + for (uint32 idx = 0; idx < comp_instance->defined_instances_count; idx++) { + component_set_custom_data(comp_instance->defined_instances[idx], + custom_data, depth + 1); + } +} + +void +wasm_component_set_custom_data(WASMComponentInstance *comp_instance, + void *custom_data) +{ + component_set_custom_data(comp_instance, custom_data, 0); +} + +void * +wasm_component_get_custom_data(WASMComponentInstance *comp_instance) +{ + return comp_instance ? comp_instance->custom_data : NULL; +} + +void * +wasm_component_get_custom_data_from_exec_env(wasm_exec_env_t exec_env) +{ + WASMExecEnv *internal_exec_env = (WASMExecEnv *)exec_env; + WASMComponentInstance *comp_instance = + internal_exec_env ? internal_exec_env->component_inst : NULL; + + while (comp_instance && comp_instance->parent) { + comp_instance = comp_instance->parent; + } + return wasm_component_get_custom_data(comp_instance); +} + +bool +wasm_component_exec_env_is_callback(wasm_exec_env_t exec_env) +{ + WASMExecEnv *internal_exec_env = (WASMExecEnv *)exec_env; + + return internal_exec_env && internal_exec_env->component_callback_active; +} + +typedef struct HostResourceMatch { + const char *interface_name; + const char *resource_name; + WASMComponentResourceInstance *resource; + bool ambiguous; +} HostResourceMatch; + +static void +match_host_resource(HostResourceMatch *match, + WASMComponentResourceInstance *resource) +{ + if (!match || !resource || !resource->is_host || !resource->interface_name + || !resource->name + || strcmp(resource->interface_name, match->interface_name) != 0 + || strcmp(resource->name, match->resource_name) != 0) { + return; + } + + if (match->resource && match->resource != resource) { + match->ambiguous = true; + } + else { + match->resource = resource; + } +} + +static void +find_host_resource_in_type(HostResourceMatch *match, + WASMComponentTypeInstance *type, uint32 depth) +{ + if (!match || !type || match->ambiguous || depth > MAX_DEPTH_RECURSION) { + return; + } + + switch (type->type) { + case COMPONENT_VAL_TYPE_RESOURCE_SYNC: + case COMPONENT_VAL_TYPE_RESOURCE_ASYNC: + match_host_resource(match, type->type_specific.resource); + break; + case COMPONENT_VAL_TYPE_OWN: + case COMPONENT_VAL_TYPE_BORROW: + if (type->type_specific.resource_handle) { + match_host_resource( + match, type->type_specific.resource_handle->resource); + } + break; + case COMPONENT_VAL_TYPE_RECORD: + if (type->type_specific.record) { + for (uint32 idx = 0; idx < type->type_specific.record->count; + idx++) { + find_host_resource_in_type( + match, type->type_specific.record->fields[idx].type, + depth + 1); + } + } + break; + case COMPONENT_VAL_TYPE_VARIANT: + if (type->type_specific.variant) { + for (uint32 idx = 0; idx < type->type_specific.variant->count; + idx++) { + find_host_resource_in_type( + match, + type->type_specific.variant->cases[idx].value_type, + depth + 1); + } + } + break; + case COMPONENT_VAL_TYPE_LIST: + if (type->type_specific.list) { + find_host_resource_in_type( + match, type->type_specific.list->element_type, depth + 1); + } + break; + case COMPONENT_VAL_TYPE_FIXED_SIZE_LIST: + if (type->type_specific.list_len) { + find_host_resource_in_type( + match, type->type_specific.list_len->element_type, + depth + 1); + } + break; + case COMPONENT_VAL_TYPE_TUPLE: + if (type->type_specific.tuple) { + for (uint32 idx = 0; idx < type->type_specific.tuple->count; + idx++) { + find_host_resource_in_type( + match, type->type_specific.tuple->element_types[idx], + depth + 1); + } + } + break; + case COMPONENT_VAL_TYPE_OPTION: + if (type->type_specific.option) { + find_host_resource_in_type( + match, type->type_specific.option->element_type, depth + 1); + } + break; + case COMPONENT_VAL_TYPE_RESULT: + if (type->type_specific.result) { + find_host_resource_in_type( + match, type->type_specific.result->result_type, depth + 1); + find_host_resource_in_type( + match, type->type_specific.result->error_type, depth + 1); + } + break; + default: + break; + } +} + +static WASMComponentResourceInstance * +find_host_resource_in_func(WASMComponentFuncTypeInstance *func_type, + const char *interface_name, + const char *resource_name, bool search_results) +{ + HostResourceMatch match = { + .interface_name = interface_name, + .resource_name = resource_name, + }; + + if (!func_type || !interface_name || !resource_name) { + return NULL; + } + + if (search_results) { + if (func_type->results) { + find_host_resource_in_type(&match, func_type->results->result, 0); + } + } + else if (func_type->params) { + for (uint32 idx = 0; idx < func_type->params->count; idx++) { + find_host_resource_in_type(&match, + func_type->params->params[idx].type, 0); + } + } + + return match.ambiguous ? NULL : match.resource; +} + +bool +wasm_component_host_resource_new(wasm_exec_env_t exec_env, + const char *interface_name, + const char *resource_name, + uint32_t representation, uint32_t *out_handle) +{ + WASMExecEnv *internal_exec_env = (WASMExecEnv *)exec_env; + WASMComponentFuncTypeInstance *func_type = NULL; + WASMComponentInstance *component_inst = NULL; + WASMComponentResourceInstance *resource = NULL; + + if (!out_handle) { + return false; + } + *out_handle = 0; + if (!internal_exec_env || !interface_name || !resource_name + || representation == 0 + || !(component_inst = internal_exec_env->component_inst) + || !(func_type = wasm_get_component_func_type(internal_exec_env))) { + return false; + } + + resource = find_host_resource_in_func(func_type, interface_name, + resource_name, true); + if (!resource + || !canon_resource_new(resource, component_inst, representation, + out_handle)) { + return false; + } + + return true; +} + +bool +wasm_component_host_resource_rep(wasm_exec_env_t exec_env, + const char *interface_name, + const char *resource_name, uint32_t handle, + uint32_t *out_representation) +{ + WASMExecEnv *internal_exec_env = (WASMExecEnv *)exec_env; + WASMComponentFuncTypeInstance *func_type = NULL; + WASMComponentInstance *component_inst = NULL; + WASMComponentResourceInstance *resource = NULL; + + if (!out_representation) { + return false; + } + *out_representation = 0; + if (!internal_exec_env || !interface_name || !resource_name + || !(component_inst = internal_exec_env->component_inst) + || !(func_type = wasm_get_component_func_type(internal_exec_env))) { + return false; + } + + resource = find_host_resource_in_func(func_type, interface_name, + resource_name, false); + return resource + && canon_resource_rep(resource, component_inst, handle, + out_representation); +} + +bool +wasm_component_host_resource_take(wasm_exec_env_t exec_env, + const char *interface_name, + const char *resource_name, uint32_t handle, + uint32_t *out_representation) +{ + WASMExecEnv *internal_exec_env = (WASMExecEnv *)exec_env; + WASMComponentFuncTypeInstance *func_type = NULL; + WASMComponentInstance *component_inst = NULL; + WASMComponentResourceInstance *resource = NULL; + WASMResourceHandle *resource_handle = NULL; + + if (!out_representation) { + return false; + } + *out_representation = 0; + if (!internal_exec_env || !interface_name || !resource_name + || !(component_inst = internal_exec_env->component_inst) + || !(func_type = wasm_get_component_func_type(internal_exec_env))) { + return false; + } + + resource = find_host_resource_in_func(func_type, interface_name, + resource_name, false); + resource_handle = + resource ? wasm_component_table_get(component_inst->table, handle, + WASM_TABLE_ELEM_RESOURCE_HANDLE) + : NULL; + if (!resource_handle || resource_handle->rt != resource + || !resource_handle->own || resource_handle->num_lends != 0) { + return false; + } + + *out_representation = resource_handle->rep; + if (!wasm_component_table_remove(component_inst->table, handle)) { + *out_representation = 0; + return false; + } + return true; +} + +static uint32 +set_host_resource_drop_callback( + WASMComponentInstance *comp_instance, const char *interface_name, + const char *resource_name, + wasm_component_host_resource_drop_callback_t callback, void *attachment, + uint32 depth) +{ + uint32 match_count = 0; + + if (!comp_instance || depth > MAX_DEPTH_RECURSION) { + return 0; + } + + for (uint32 idx = 0; idx < comp_instance->types_count; idx++) { + WASMComponentTypeInstance *type = comp_instance->types[idx]; + WASMComponentResourceInstance *resource = NULL; + + if (!type + || (type->type != COMPONENT_VAL_TYPE_RESOURCE_SYNC + && type->type != COMPONENT_VAL_TYPE_RESOURCE_ASYNC) + || !(resource = type->type_specific.resource) || !resource->is_host + || !resource->interface_name || !resource->name + || strcmp(resource->interface_name, interface_name) != 0 + || strcmp(resource->name, resource_name) != 0) { + continue; + } + + resource->host_drop_callback = callback; + resource->host_drop_attachment = attachment; + resource->host_drop_attachment_is_custom_data = false; + match_count++; + } + + for (uint32 idx = 0; idx < comp_instance->defined_instances_count; idx++) { + match_count += set_host_resource_drop_callback( + comp_instance->defined_instances[idx], interface_name, + resource_name, callback, attachment, depth + 1); + } + + return match_count; +} + +bool +wasm_component_set_host_resource_drop_callback( + WASMComponentInstance *comp_instance, const char *interface_name, + const char *resource_name, + wasm_component_host_resource_drop_callback_t callback, void *attachment) +{ + if (!comp_instance || !interface_name || !resource_name || !callback) { + return false; + } + + return set_host_resource_drop_callback(comp_instance, interface_name, + resource_name, callback, attachment, + 0) + > 0; +} + +void +wasm_component_terminate(WASMComponentInstance *comp_instance) +{ + if (!comp_instance) { + return; + } + + for (uint32 idx = 0; idx < comp_instance->defined_core_instances_count; + idx++) { + WASMModuleInstance *core_inst = + comp_instance->defined_core_instances[idx]; + if (core_inst && core_inst->e) { + wasm_runtime_terminate((wasm_module_inst_t)core_inst); + } + } + + for (uint32 idx = 0; idx < comp_instance->defined_instances_count; idx++) { + wasm_component_terminate(comp_instance->defined_instances[idx]); + } +} + +#if WASM_ENABLE_LIBC_WASI_P2 != 0 void wasm_component_runtime_destroy_wasi(WASMComponentInstance *comp_instance) { @@ -1454,7 +2112,23 @@ wasm_component_deinstantiate(WASMComponentInstance *comp_instance) } uint32 idx = 0; - // Free core instances + /* Drop owned resources while all destructor implementations are still + * live. This must precede both nested-component and core deinstantiation. + */ + if (comp_instance->table) { + wasm_component_table_destroy(comp_instance->table); + comp_instance->table = NULL; + } + + // Free nested component instances + for (idx = 0; idx < comp_instance->defined_instances_count; idx++) { + if (comp_instance->defined_instances[idx]) { + wasm_component_deinstantiate(comp_instance->defined_instances[idx]); + comp_instance->defined_instances[idx] = NULL; + } + } + + // Free core instances after every component resource has been dropped for (idx = 0; idx < comp_instance->defined_core_instances_count; idx++) { if (!comp_instance->defined_core_instances[idx]->e) { wasm_runtime_free( @@ -1468,14 +2142,6 @@ wasm_component_deinstantiate(WASMComponentInstance *comp_instance) comp_instance->defined_core_instances[idx] = NULL; } - // Free nested component instances - for (idx = 0; idx < comp_instance->defined_instances_count; idx++) { - if (comp_instance->defined_instances[idx]) { - wasm_component_deinstantiate(comp_instance->defined_instances[idx]); - comp_instance->defined_instances[idx] = NULL; - } - } - // Free runtime canonical options for component functions (canon.lift) for (idx = 0; idx < comp_instance->defined_functions_count; idx++) { WASMComponentFunctionInstance *func = @@ -1496,18 +2162,12 @@ wasm_component_deinstantiate(WASMComponentInstance *comp_instance) } } -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI_P2 != 0 if (comp_instance->wasi_ctx && !comp_instance->parent) { // destroy component wasi context wasm_component_runtime_destroy_wasi(comp_instance); } #endif - // Destroy the component instance table - if (comp_instance->table) { - wasm_component_table_destroy(comp_instance->table); - comp_instance->table = NULL; - } - wasm_runtime_free(comp_instance); comp_instance = NULL; } @@ -1574,6 +2234,79 @@ wasm_component_lookup_function(const WASMComponentInstance *component_inst, return NULL; // Function not found } +static const char * +component_export_name(const WASMComponentExportInstance *export_inst) +{ + WASMComponentExportName *export_name; + + if (!export_inst || !(export_name = export_inst->export_name)) { + return NULL; + } + + if (export_name->tag == WASM_COMP_IMPORTNAME_SIMPLE) { + return export_name->exported.simple.name + ? export_name->exported.simple.name->name + : NULL; + } + if (export_name->tag == WASM_COMP_IMPORTNAME_VERSIONED) { + return export_name->exported.versioned.name + ? export_name->exported.versioned.name->name + : NULL; + } + return NULL; +} + +WASMComponentFunctionInstance * +wasm_component_lookup_function_qualified( + const WASMComponentInstance *component_inst, const char *interface_name, + const char *function_name) +{ + uint32 interface_index, function_index; + + if (!component_inst || !component_inst->exports || !interface_name + || !function_name) { + return NULL; + } + + for (interface_index = 0; interface_index < component_inst->exports_count; + interface_index++) { + WASMComponentExportInstance *interface_export = + &component_inst->exports[interface_index]; + WASMComponentInstance *interface_inst; + const char *exported_interface_name; + + if (interface_export->type != WASM_COMP_EXTERN_INSTANCE + || !(interface_inst = interface_export->exp.instance) + || !(exported_interface_name = + component_export_name(interface_export)) + || strcmp(exported_interface_name, interface_name) != 0) { + continue; + } + + for (function_index = 0; function_index < interface_inst->exports_count; + function_index++) { + WASMComponentExportInstance *function_export = + &interface_inst->exports[function_index]; + const char *exported_function_name; + + if (function_export->type != WASM_COMP_EXTERN_FUNC + || !(exported_function_name = + component_export_name(function_export))) { + continue; + } + if (strcmp(exported_function_name, function_name) == 0) { + return function_export->exp.function; + } + } + + /* Component export names are unique, so an exact interface match + * cannot be followed by a second candidate. */ + return NULL; + } + + return NULL; +} + WASMMemoryInstance * canon_get_memory(CanonicalOptions *canon_opts) { @@ -1585,6 +2318,126 @@ canon_get_memory(CanonicalOptions *canon_opts) return canon_opts->lift_lower_opts->lift_opts->memory; } } + +static WASMMemoryInstance * +get_active_component_callback_memory(WASMExecEnv *exec_env) +{ + WASMMemoryInstance *memory = NULL; + + if (!exec_env || !exec_env->component_inst || !exec_env->core_func + || !exec_env->cx || !exec_env->cx->canonical_opts + || !exec_env->core_func->component_function || !exec_env->memory) { + return NULL; + } + + if (exec_env->cx->inst != exec_env->component_inst + || (exec_env->core_func->canon_options + && exec_env->cx->canonical_opts + != exec_env->core_func->canon_options)) { + return NULL; + } + + memory = canon_get_memory(exec_env->cx->canonical_opts); + return memory == exec_env->memory ? memory : NULL; +} + +static bool +get_component_callback_memory_range(WASMExecEnv *exec_env, uint32 app_offset, + uint32 size, uint8 **p_native_addr) +{ + const uint64 wasm32_address_space_size = (uint64)UINT32_MAX + 1; + WASMMemoryInstance *memory = NULL; + WASMModuleInstanceCommon *module_inst_comm = NULL; + uint64 range_end = 0; + bool is_valid = false; + + if (p_native_addr) { + *p_native_addr = NULL; + } + if (!p_native_addr + || !(memory = get_active_component_callback_memory(exec_env))) { + return false; + } + + module_inst_comm = wasm_runtime_get_module_inst(exec_env); + if (!module_inst_comm + || (module_inst_comm->module_type != Wasm_Module_Bytecode + && module_inst_comm->module_type != Wasm_Module_AoT)) { + return false; + } + + range_end = (uint64)app_offset + (uint64)size; + if (range_end > wasm32_address_space_size) { + goto fail; + } + + SHARED_MEMORY_LOCK(memory); + if (range_end <= memory->memory_data_size + && (memory->memory_data || range_end == 0)) { + *p_native_addr = + memory->memory_data ? memory->memory_data + app_offset : NULL; + is_valid = true; + } + SHARED_MEMORY_UNLOCK(memory); + + if (is_valid) { + return true; + } + +fail: + wasm_runtime_set_exception(module_inst_comm, "out of bounds memory access"); + return false; +} + +bool +wasm_component_validate_memory_range(wasm_exec_env_t exec_env, + uint32_t app_offset, uint32_t size) +{ + uint8 *native_addr = NULL; + + return get_component_callback_memory_range(exec_env, app_offset, size, + &native_addr); +} + +bool +wasm_component_get_memory_range(wasm_exec_env_t exec_env, uint32_t app_offset, + uint32_t size, uint8_t **p_native_addr) +{ + uint8 *native_addr = NULL; + + if (p_native_addr) { + *p_native_addr = NULL; + } + if (!p_native_addr + || !get_component_callback_memory_range(exec_env, app_offset, size, + &native_addr)) { + return false; + } + + *p_native_addr = native_addr; + return true; +} + +bool +wasm_component_get_memory_range_const(wasm_exec_env_t exec_env, + uint32_t app_offset, uint32_t size, + const uint8_t **p_native_addr) +{ + uint8 *native_addr = NULL; + + if (p_native_addr) { + *p_native_addr = NULL; + } + if (!p_native_addr + || !get_component_callback_memory_range(exec_env, app_offset, size, + &native_addr)) { + return false; + } + + *p_native_addr = native_addr; + return true; +} + typedef void (*GenericFunctionPointer)(void); /// @brief Allocates memory inside the wasm linerar memory (WASMMemoryInstance) @@ -1922,8 +2775,14 @@ wasm_runtime_invoke_native_p2(WASMExecEnv *exec_env, WASMModuleInstance *module = (WASMModuleInstance *)wasm_runtime_get_module_inst(exec_env); + WASMComponentInstance *saved_component_inst = exec_env->component_inst; + WASMFunctionInstance *saved_core_func = exec_env->core_func; + WASMMemoryInstance *saved_memory = exec_env->memory; + LiftLowerContext *saved_cx = exec_env->cx; + void *saved_attachment = exec_env->attachment; + WASMComponentInstance *root_comp = module->comp_instance; - while (root_comp->parent) + while (root_comp && root_comp->parent) root_comp = root_comp->parent; exec_env->component_inst = root_comp; @@ -1945,17 +2804,19 @@ wasm_runtime_invoke_native_p2(WASMExecEnv *exec_env, #if WASM_ENABLE_SIMD != 0 && WASM_ENABLE_FAST_INTERP != 0 /* SIMD assembly: each xmm slot = 16 bytes = 2 uint64 slots */ - argc1 = - 1 + MAX_REG_FLOATS * 2 + (uint32)func_type->param_count + ext_ret_count; + argc1 = 1 + COMPONENT_MAX_REG_FLOATS * 2 + (uint32)func_type->param_count + + ext_ret_count; #else /* Non-SIMD assembly: each xmm slot = 8 bytes = 1 uint64 slot */ - argc1 = 1 + MAX_REG_FLOATS + (uint32)func_type->param_count + ext_ret_count; + argc1 = 1 + COMPONENT_MAX_REG_FLOATS + (uint32)func_type->param_count + + ext_ret_count; #endif if (argc1 > sizeof(argv_buf) / sizeof(uint64)) { size = (uint64)(sizeof(uint64) * (uint64)argc1); argv1 = wasm_runtime_malloc((uint32)size); if (!argv1) { + exec_env->component_inst = saved_component_inst; return false; } } @@ -1965,6 +2826,9 @@ wasm_runtime_invoke_native_p2(WASMExecEnv *exec_env, Subtask *subtask = subtask_create(); if (!subtask) { wasm_set_exception(module, "failed to create subtask"); + if (argv1 != argv_buf) + wasm_runtime_free(argv1); + exec_env->component_inst = saved_component_inst; return false; } @@ -1980,18 +2844,16 @@ wasm_runtime_invoke_native_p2(WASMExecEnv *exec_env, exec_env->core_func = cur_func; #if WASM_ENABLE_SIMD != 0 && WASM_ENABLE_FAST_INTERP != 0 - /* fp as v128*: each step = 16 bytes; ints at byte offset MAX_REG_FLOATS*16 - */ + /* Each SIMD register occupies two uint64 slots in argv1. */ fps = (V128 *)argv1; - ints = (uint64 *)(fps + MAX_REG_FLOATS); + ints = argv1 + COMPONENT_MAX_REG_FLOATS * 2; #else - /* fp as v128*: each step = 16 bytes; ints at byte offset MAX_REG_FLOATS*8 - */ + /* fp slots are 8 bytes; integer registers follow. */ fps = argv1; - ints = fps + MAX_REG_FLOATS; + ints = fps + COMPONENT_MAX_REG_FLOATS; #endif - stacks = ints + MAX_REG_INTS; + stacks = ints + COMPONENT_MAX_REG_INTS; ints[n_ints++] = (uint64)(uintptr_t)exec_env; @@ -2040,21 +2902,21 @@ wasm_runtime_invoke_native_p2(WASMExecEnv *exec_env, arg_i64 = (uint64)memory->memory_data + app_offset; } } - if (n_ints < MAX_REG_INTS) + if (n_ints < COMPONENT_MAX_REG_INTS) ints[n_ints++] = arg_i64; else stacks[n_stacks++] = arg_i64; break; } case VALUE_TYPE_I64: - if (n_ints < MAX_REG_INTS) + if (n_ints < COMPONENT_MAX_REG_INTS) ints[n_ints++] = *(uint64 *)argv_src; else stacks[n_stacks++] = *(uint64 *)argv_src; argv_src += 2; break; case VALUE_TYPE_F32: - if (n_fps < MAX_REG_FLOATS) { + if (n_fps < COMPONENT_MAX_REG_FLOATS) { *(float32 *)&fps[n_fps++] = *(float32 *)argv_src++; } else { @@ -2062,7 +2924,7 @@ wasm_runtime_invoke_native_p2(WASMExecEnv *exec_env, } break; case VALUE_TYPE_F64: - if (n_fps < MAX_REG_FLOATS) { + if (n_fps < COMPONENT_MAX_REG_FLOATS) { *(float64 *)&fps[n_fps++] = *(float64 *)argv_src; } else { @@ -2077,7 +2939,7 @@ wasm_runtime_invoke_native_p2(WASMExecEnv *exec_env, } /* Save extra result values' address to argv1 */ for (i = 0; i < ext_ret_count; i++) { - if (n_ints < MAX_REG_INTS) + if (n_ints < COMPONENT_MAX_REG_INTS) ints[n_ints++] = *(uint64 *)argv_src; else stacks[n_stacks++] = *(uint64 *)argv_src; @@ -2122,12 +2984,24 @@ wasm_runtime_invoke_native_p2(WASMExecEnv *exec_env, PUT_I64_TO_ADDR(argv_ret, invokeNative_Int64(func_ptr, argv1, n_stacks)); break; + case VALUE_TYPE_F32: + { + float32 value = invokeNative_Float32(func_ptr, argv1, n_stacks); + memcpy(argv_ret, &value, sizeof(value)); + break; + } + case VALUE_TYPE_F64: + { + float64 value = invokeNative_Float64(func_ptr, argv1, n_stacks); + memcpy(argv_ret, &value, sizeof(value)); + break; + } default: bh_assert(0); break; } } - exec_env->attachment = NULL; + exec_env->attachment = saved_attachment; subtask->state = SUBTASK_STATE_RETURNED; @@ -2136,14 +3010,25 @@ wasm_runtime_invoke_native_p2(WASMExecEnv *exec_env, ret = !wasm_copy_exception(module, NULL); fail: + exec_env->attachment = saved_attachment; + exec_env->cx = saved_cx; + exec_env->memory = saved_memory; + exec_env->core_func = saved_core_func; + exec_env->component_inst = saved_component_inst; if (argv1 != argv_buf) wasm_runtime_free(argv1); subtask_destroy(subtask); + exec_env->attachment = saved_attachment; + exec_env->cx = saved_cx; + exec_env->memory = saved_memory; + exec_env->core_func = saved_core_func; + exec_env->component_inst = saved_component_inst; + return ret; } -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI_P2 != 0 bool wasm_component_runtime_init_wasi( @@ -2153,19 +3038,39 @@ wasm_component_runtime_init_wasi( uint32 addr_pool_size, const char *ns_lookup_pool[], uint32 ns_lookup_pool_size, char *argv[], uint32 argc, os_raw_file_handle stdinfd, os_raw_file_handle stdoutfd, - os_raw_file_handle stderrfd, char *error_buf, uint32 error_buf_size) + os_raw_file_handle stderrfd, const libc_wasi_options_t *wasi_options, + char *error_buf, uint32 error_buf_size) { - WASIContext *wasi_ctx = NULL; + libc_wasi_options_t *owned_options = + wasm_runtime_malloc(sizeof(*owned_options)); + + if (!owned_options) { + set_error_buf_ex(error_buf, error_buf_size, + "ERROR: Failed to allocate WASI Preview 2 options"); + return false; + } + if (wasi_options) { + *owned_options = *wasi_options; + } + else { + memset(owned_options, 0, sizeof(*owned_options)); + owned_options->cli = 1; + owned_options->common = 1; + owned_options->preview2 = 1; + } + wasi_ctx = wasm_runtime_init_wasi_internal( dir_list, dir_count, map_dir_list, map_dir_count, env, env_count, addr_pool, addr_pool_size, ns_lookup_pool, ns_lookup_pool_size, argv, argc, stdinfd, stdoutfd, stderrfd, error_buf, error_buf_size); if (!wasi_ctx) { + wasm_runtime_free(owned_options); LOG_DEBUG("Component wasi args init failed\n"); return false; } + wasi_ctx->wasi_options = owned_options; comp_instance->wasi_ctx = wasi_ctx; return true; } @@ -2206,6 +3111,7 @@ wasm_component_runtime_set_wasi_args_ex( wasi_args->stdio[0] = (os_raw_file_handle)stdinfd; wasi_args->stdio[1] = (os_raw_file_handle)stdoutfd; wasi_args->stdio[2] = (os_raw_file_handle)stderrfd; + wasi_args->set_by_user = true; component->import_wasi_api = true; } @@ -2237,6 +3143,7 @@ wasm_component_runtime_set_wasi_addr_pool(WASMComponent *component, if (wasi_args) { wasi_args->addr_pool = addr_pool; wasi_args->addr_count = addr_pool_size; + wasi_args->set_by_user = true; } } @@ -2254,6 +3161,7 @@ wasm_component_runtime_set_wasi_ns_lookup_pool(WASMComponent *component, if (wasi_args) { wasi_args->ns_lookup_pool = ns_lookup_pool; wasi_args->ns_lookup_count = ns_lookup_pool_size; + wasi_args->set_by_user = true; } } @@ -2269,6 +3177,7 @@ wasm_component_runtime_set_wasi_options(WASMComponent *component, if (wasi_args) { wasi_args->wasi_options = wasi_options; + wasi_args->set_by_user = true; } } @@ -2290,5 +3199,5 @@ wasm_component_runtime_lookup_wasi_start_function( return NULL; } -#endif /*WASM_ENABLE_LIBC_WASI != 0*/ +#endif /* WASM_ENABLE_LIBC_WASI_P2 != 0 */ #endif /* WASM_ENABLE_COMPONENT_MODEL != 0*/ diff --git a/core/iwasm/common/component-model/wasm_component_runtime.h b/core/iwasm/common/component-model/wasm_component_runtime.h index 4cac9c3f00..ea7304b5be 100644 --- a/core/iwasm/common/component-model/wasm_component_runtime.h +++ b/core/iwasm/common/component-model/wasm_component_runtime.h @@ -23,11 +23,21 @@ extern "C" { #define EXCEPTION_BUF_LEN 128 #endif -#define HEAP_SIZE (100 * 1024 * 1024) // 100 MB -#define STACK_SIZE (16 * 1024) // 100 MB - -#define MAX_REG_FLOATS 8 -#define MAX_REG_INTS 6 +#define COMPONENT_DEFAULT_HEAP_SIZE (100 * 1024 * 1024) +#define COMPONENT_DEFAULT_STACK_SIZE (16 * 1024) + +#if defined(_WIN32) || defined(_WIN32_) +#define COMPONENT_MAX_REG_FLOATS 4 +#define COMPONENT_MAX_REG_INTS 4 +#else +#define COMPONENT_MAX_REG_FLOATS 8 +#if defined(BUILD_TARGET_AARCH64) || defined(BUILD_TARGET_RISCV64_LP64D) \ + || defined(BUILD_TARGET_RISCV64_LP64) +#define COMPONENT_MAX_REG_INTS 8 +#else +#define COMPONENT_MAX_REG_INTS 6 +#endif +#endif typedef struct WASMComponentTypeInstance WASMComponentTypeInstance; typedef struct WASMComponentInstance WASMComponentInstance; @@ -228,7 +238,7 @@ typedef struct WASMComponentFunctionInstance { // Used for storing the sizes of the defined types/ exports/ index spaces of the // Component instance type typedef struct WASMComponentInstanceDeclTypeSize { - uint32 types_size; + uint64 types_size; uint32 types_count; uint32 func_count; uint32 exports_count; @@ -250,6 +260,12 @@ typedef struct WASMComponentInstTypeInstance { WASMFunctionInstance *defined_core_funcs; + /* Only types in this range are declared by this instance type. Entries + * outside it are nominal outer aliases and must retain their owner and + * identity when a host import is materialized. */ + void *owned_types_begin; + uint32 owned_types_size; + // Memory layout at instantiation: /* +-------------------------------------------+ @@ -272,7 +288,14 @@ typedef struct WASMComponentInstTypeInstance { typedef struct WASMComponentResourceInstance { char *name; char *interface_name; - bool is_wasi; + /* True only when this resource's host interface was resolved entirely + * through WAMR's built-in WASI Preview 2 implementation. A wasi: name by + * itself does not grant access to the global built-in resource table. */ + bool is_builtin_wasi; + bool is_host; + wasm_component_host_resource_drop_callback_t host_drop_callback; + void *host_drop_attachment; + bool host_drop_attachment_is_custom_data; WASMComponentInstance *impl; WASMFunctionInstance *drop_method; WASMFunctionInstance *new_method; @@ -376,10 +399,11 @@ typedef struct WASMComponentInstance { WASMExecEnv *cur_exec_env; // Currently active exec env uint32 default_wasm_stack_size; // Stack configuration void *custom_data; + struct InstantiationArgs2 instantiation_args; WASMComponent *component; // Reference to component definition WASMComponentResourceTable *table; -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI_P2 != 0 WASIContext *wasi_ctx; #endif // Index spaces: @@ -489,9 +513,10 @@ WASMComponentInstance * wasm_component_instance_allocate(WASMComponentIndexCount *index_count, char *error_buf, uint32 error_buf_size); -uint32 +bool wasm_get_inst_decl_size(WASMComponentInstType *instance_type, - WASMComponentInstanceDeclTypeSize *instance_type_size); + WASMComponentInstanceDeclTypeSize *instance_type_size, + uint64 *size); bool wasm_resolve_types(WASMComponentTypeSection *type_section, @@ -514,6 +539,17 @@ wasm_resolve_imports_WASI(WASMComponentImportSection *import_section, WASMComponentInstance *comp_instance, char *error_buf, uint32 error_buf_size); +/* + * Resolve statically registered root component imports. WASI Preview 2 + * interfaces may fall back to the built-in libc-wasi-p2 implementation; + * non-WASI interfaces must already be registered through the native-symbol + * API by the embedder. + */ +bool +wasm_resolve_imports_host(WASMComponentImportSection *import_section, + WASMComponentInstance *comp_instance, char *error_buf, + uint32 error_buf_size); + bool wasm_resolve_alias(WASMComponentAliasSection *alias_section, WASMComponentInstance *comp_instance, char *error_buf, @@ -533,6 +569,11 @@ wasm_resolve_core_instance(WASMComponentCoreInstSection *instance_section, WASMComponentInstance *comp_instance, char *error_buf, uint32 error_buf_size); +WASMCoreExport * +wasm_import_find_in_args(WASMImport *import, WASMInstExpr *expression, + WASMComponentInstance *comp_instance, char *error_buf, + uint32 error_buf_size); + bool wasm_resolve_core_imports(WASMInstExpr *expression, WASMModule *target, WASMComponentInstance *comp_instance, @@ -544,8 +585,11 @@ wasm_create_core_inst_from_expression(WASMComponentCoreInst *core_inst, WASMComponentInstance *comp_instance, char *error_buf, uint32 error_buf_size); -uint32 -wasm_get_func_type_size(WASMComponentFuncType *func_type); +bool +wasm_get_def_val_type_size(WASMComponentDefValType *def_val_type, uint64 *size); + +bool +wasm_get_func_type_size(WASMComponentFuncType *func_type, uint64 *size); bool wasm_resolve_canon(WASMComponentCanonSection *canon_section, @@ -561,6 +605,34 @@ bool wasm_component_application_execute_func_ex(WASMComponentInstance *, char *argv, uint32 *argc1, uint32 **argv1); +WASMComponentPreparedCall * +wasm_component_prepare_export_call(WASMComponentInstance *comp_inst, + const char *export_name, char *error_buf, + uint32 error_buf_size); + +WASMComponentPreparedCall * +wasm_component_prepare_export_call_qualified(WASMComponentInstance *comp_inst, + const char *interface_name, + const char *export_name, + char *error_buf, + uint32 error_buf_size); + +bool +wasm_component_prepared_call_requires_post_return( + const WASMComponentPreparedCall *prepared_call); + +bool +wasm_component_call_prepared(WASMComponentPreparedCall *prepared_call, + uint32 num_results, wasm_val_t results[], + uint32 num_args, const wasm_val_t args[]); + +bool +wasm_component_prepared_call_post_return( + WASMComponentPreparedCall *prepared_call); + +void +wasm_component_destroy_prepared_call(WASMComponentPreparedCall *prepared_call); + uint32_t align_to(uint32_t ptr, uint32_t alignment); uint32_t @@ -580,7 +652,12 @@ WASMComponentFunctionInstance * wasm_component_lookup_function(const WASMComponentInstance *component_inst, const char *name); -#if WASM_ENABLE_LIBC_WASI != 0 +WASMComponentFunctionInstance * +wasm_component_lookup_function_qualified( + const WASMComponentInstance *component_inst, const char *interface_name, + const char *function_name); + +#if WASM_ENABLE_LIBC_WASI_P2 != 0 bool wasm_component_runtime_init_wasi( @@ -590,7 +667,8 @@ wasm_component_runtime_init_wasi( uint32 addr_pool_size, const char *ns_lookup_pool[], uint32 ns_lookup_pool_size, char *argv[], uint32 argc, os_raw_file_handle stdinfd, os_raw_file_handle stdoutfd, - os_raw_file_handle stderrfd, char *error_buf, uint32 error_buf_size); + os_raw_file_handle stderrfd, const libc_wasi_options_t *wasi_options, + char *error_buf, uint32 error_buf_size); void wasm_component_runtime_set_wasi_args(WASMComponent *component, @@ -624,13 +702,65 @@ wasm_component_instantiate_internal( WASMComponentInstArgInstances *instance_expression, char *error_buf, uint32 error_buf_size); +WASMComponentInstance * +wasm_component_instantiate_internal_ex( + WASMComponent *component, + WASMComponentInstArgInstances *instance_expression, + const struct InstantiationArgs2 *args, char *error_buf, + uint32 error_buf_size); + WASMComponentInstance * wasm_component_instantiate(WASMComponent *component, char *error_buf, uint32 error_buf_size); +WASMComponentInstance * +wasm_component_instantiate_ex2(WASMComponent *component, + const struct InstantiationArgs2 *args, + char *error_buf, uint32 error_buf_size); + void wasm_component_deinstantiate(WASMComponentInstance *comp_instance); +void +wasm_component_terminate(WASMComponentInstance *comp_instance); + +void +wasm_component_set_custom_data(WASMComponentInstance *comp_instance, + void *custom_data); + +void * +wasm_component_get_custom_data(WASMComponentInstance *comp_instance); + +void * +wasm_component_get_custom_data_from_exec_env(wasm_exec_env_t exec_env); + +bool +wasm_component_exec_env_is_callback(wasm_exec_env_t exec_env); + +bool +wasm_component_host_resource_new(wasm_exec_env_t exec_env, + const char *interface_name, + const char *resource_name, + uint32_t representation, uint32_t *out_handle); + +bool +wasm_component_host_resource_rep(wasm_exec_env_t exec_env, + const char *interface_name, + const char *resource_name, uint32_t handle, + uint32_t *out_representation); + +bool +wasm_component_host_resource_take(wasm_exec_env_t exec_env, + const char *interface_name, + const char *resource_name, uint32_t handle, + uint32_t *out_representation); + +bool +wasm_component_set_host_resource_drop_callback( + WASMComponentInstance *comp_instance, const char *interface_name, + const char *resource_name, + wasm_component_host_resource_drop_callback_t callback, void *attachment); + void * wasm_runtime_addr_app_to_native_p2(WASMExecEnv *exec_env, uint64 app_offset); diff --git a/core/iwasm/common/component-model/wasm_component_start_section.c b/core/iwasm/common/component-model/wasm_component_start_section.c index 189379f8fd..9034e71fc5 100644 --- a/core/iwasm/common/component-model/wasm_component_start_section.c +++ b/core/iwasm/common/component-model/wasm_component_start_section.c @@ -53,10 +53,10 @@ wasm_component_parse_start_section(const uint8_t **payload, out->value_args_count = (uint32_t)args_count; if (args_count > 0) { - out->value_args = wasm_runtime_malloc(sizeof(uint32_t) * args_count); + out->value_args = wasm_component_checked_calloc( + (uint32_t)args_count, sizeof(uint32_t), p, end, 1, + "component start argument", error_buf, error_buf_size); if (!out->value_args) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for value args"); return false; } @@ -109,4 +109,4 @@ wasm_component_free_start_section(WASMComponentSection *section) } wasm_runtime_free(start_section); section->parsed.start_section = NULL; -} \ No newline at end of file +} diff --git a/core/iwasm/common/component-model/wasm_component_types_section.c b/core/iwasm/common/component-model/wasm_component_types_section.c index 800224f082..12d537b1b9 100644 --- a/core/iwasm/common/component-model/wasm_component_types_section.c +++ b/core/iwasm/common/component-model/wasm_component_types_section.c @@ -115,6 +115,9 @@ static WASMComponentTypeInstance primitive_type_error_context = { static void free_component_instance_decl(WASMComponentInstDecl *decl); +static void +free_component_decl(WASMComponentComponentDecl *decl); + // Free helpers for nested component/instance/resource types static void free_component_types_entry(WASMComponentTypes *type); @@ -210,28 +213,24 @@ parse_record_type(const uint8_t **payload, const uint8_t *end, } // Allocate the fields array - (*out)->def_val.record->fields = - wasm_runtime_malloc(sizeof(WASMComponentLabelValType) * count); + (*out)->def_val.record->fields = wasm_component_checked_calloc( + count, sizeof(WASMComponentLabelValType), p, end, 1, "record field", + error_buf, error_buf_size); if (!(*out)->def_val.record->fields) { wasm_runtime_free((*out)->def_val.record); (*out)->def_val.record = NULL; - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for record fields"); return false; } (*out)->def_val.record->count = count; - // Initialize all fields to zero - memset((*out)->def_val.record->fields, 0, - sizeof(WASMComponentLabelValType) * count); - // Parse each field for (uint32_t i = 0; i < count; i++) { if (!parse_labelvaltype(&p, end, &(*out)->def_val.record->fields[i], error_buf, error_buf_size)) { - // Clean up already parsed fields - for (uint32_t j = 0; j < i; j++) { + /* parse_labelvaltype may allocate part of the current field + * before failing, so include i in the cleanup. */ + for (uint32_t j = 0; j <= i; j++) { free_labelvaltype(&(*out)->def_val.record->fields[j]); } wasm_runtime_free((*out)->def_val.record->fields); @@ -286,28 +285,24 @@ parse_variant_type(const uint8_t **payload, const uint8_t *end, } // Allocate the cases array - (*out)->def_val.variant->cases = - wasm_runtime_malloc(sizeof(WASMComponentCaseValType) * count); + (*out)->def_val.variant->cases = wasm_component_checked_calloc( + count, sizeof(WASMComponentCaseValType), p, end, 1, "variant case", + error_buf, error_buf_size); if (!(*out)->def_val.variant->cases) { wasm_runtime_free((*out)->def_val.variant); (*out)->def_val.variant = NULL; - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for variant cases"); return false; } (*out)->def_val.variant->count = count; - // Initialize all cases to zero - memset((*out)->def_val.variant->cases, 0, - sizeof(WASMComponentCaseValType) * count); - // Parse each case for (uint32_t i = 0; i < count; i++) { if (!parse_case(&p, end, &(*out)->def_val.variant->cases[i], error_buf, error_buf_size)) { - // Clean up already parsed cases - for (uint32_t j = 0; j < i; j++) { + /* parse_case may allocate part of the current case before + * failing, so include i in the cleanup. */ + for (uint32_t j = 0; j <= i; j++) { free_case(&(*out)->def_val.variant->cases[j]); } wasm_runtime_free((*out)->def_val.variant->cases); @@ -362,23 +357,17 @@ parse_tuple_type(const uint8_t **payload, const uint8_t *end, } // Allocate the element types array - (*out)->def_val.tuple->element_types = - wasm_runtime_malloc(sizeof(WASMComponentValueType) * count); + (*out)->def_val.tuple->element_types = wasm_component_checked_calloc( + count, sizeof(WASMComponentValueType), p, end, 1, "tuple element", + error_buf, error_buf_size); if (!(*out)->def_val.tuple->element_types) { wasm_runtime_free((*out)->def_val.tuple); (*out)->def_val.tuple = NULL; - set_error_buf_ex( - error_buf, error_buf_size, - "Failed to allocate memory for tuple element types"); return false; } (*out)->def_val.tuple->count = count; - // Initialize all element types to zero - memset((*out)->def_val.tuple->element_types, 0, - sizeof(WASMComponentValueType) * count); - // Parse each element type for (uint32_t i = 0; i < count; i++) { if (!parse_valtype(&p, end, @@ -436,29 +425,25 @@ parse_flags_type(const uint8_t **payload, const uint8_t *end, } // Allocate the labels array - (*out)->def_val.flag->flags = - wasm_runtime_malloc(sizeof(WASMComponentCoreName) * count); + (*out)->def_val.flag->flags = wasm_component_checked_calloc( + count, sizeof(WASMComponentCoreName), p, end, 1, "flag label", + error_buf, error_buf_size); if (!(*out)->def_val.flag->flags) { wasm_runtime_free((*out)->def_val.flag); (*out)->def_val.flag = NULL; - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for flag labels"); return false; } (*out)->def_val.flag->count = count; - // Initialize all labels to zero - memset((*out)->def_val.flag->flags, 0, - sizeof(WASMComponentCoreName) * count); - // Parse each label for (uint32_t i = 0; i < count; i++) { WASMComponentCoreName *temp_ptr = &(*out)->def_val.flag->flags[i]; if (!parse_label_prime(&p, end, &temp_ptr, error_buf, error_buf_size)) { - // Clean up on error - for (uint32_t j = 0; j < i; j++) { + /* The current array element may already own its label string, + * so include it in owner cleanup. */ + for (uint32_t j = 0; j <= i; j++) { free_label_prime(&(*out)->def_val.flag->flags[j]); } wasm_runtime_free((*out)->def_val.flag->flags); @@ -513,30 +498,26 @@ parse_enum_type(const uint8_t **payload, const uint8_t *end, } // Allocate the labels array - (*out)->def_val.enum_type->labels = - wasm_runtime_malloc(sizeof(WASMComponentCoreName) * count); + (*out)->def_val.enum_type->labels = wasm_component_checked_calloc( + count, sizeof(WASMComponentCoreName), p, end, 1, "enum label", + error_buf, error_buf_size); if (!(*out)->def_val.enum_type->labels) { wasm_runtime_free((*out)->def_val.enum_type); (*out)->def_val.enum_type = NULL; - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for enum labels"); return false; } (*out)->def_val.enum_type->count = count; - // Initialize all labels to zero - memset((*out)->def_val.enum_type->labels, 0, - sizeof(WASMComponentCoreName) * count); - // Parse each label for (uint32_t i = 0; i < count; i++) { WASMComponentCoreName *temp_ptr = &(*out)->def_val.enum_type->labels[i]; if (!parse_label_prime(&p, end, &temp_ptr, error_buf, error_buf_size)) { - // Clean up on error - for (uint32_t j = 0; j < i; j++) { + /* The current array element may already own its label string, + * so include it in owner cleanup. */ + for (uint32_t j = 0; j <= i; j++) { free_label_prime(&(*out)->def_val.enum_type->labels[j]); } wasm_runtime_free((*out)->def_val.enum_type->labels); @@ -640,6 +621,13 @@ parse_result_type(const uint8_t **payload, const uint8_t *end, // Parse in Binary.md order: result t? then error u? // Optional result type (t?) + if (p >= end) { + wasm_runtime_free((*out)->def_val.result); + (*out)->def_val.result = NULL; + set_error_buf_ex(error_buf, error_buf_size, + "Missing result optional tag"); + return false; + } uint8_t has_result_type = *p++; if (has_result_type == WASM_COMP_OPTIONAL_TRUE) { (*out)->def_val.result->result_type = @@ -665,6 +653,8 @@ parse_result_type(const uint8_t **payload, const uint8_t *end, } } else if (has_result_type != WASM_COMP_OPTIONAL_FALSE) { + wasm_runtime_free((*out)->def_val.result); + (*out)->def_val.result = NULL; set_error_buf_ex(error_buf, error_buf_size, "Malformed binary: invalid optional tag 0x%02x", has_result_type); @@ -672,6 +662,16 @@ parse_result_type(const uint8_t **payload, const uint8_t *end, } // Optional error type (u?) + if (p >= end) { + if ((*out)->def_val.result->result_type) { + wasm_runtime_free((*out)->def_val.result->result_type); + } + wasm_runtime_free((*out)->def_val.result); + (*out)->def_val.result = NULL; + set_error_buf_ex(error_buf, error_buf_size, + "Missing error optional tag"); + return false; + } uint8_t has_error_type = *p++; if (has_error_type == WASM_COMP_OPTIONAL_TRUE) { (*out)->def_val.result->error_type = @@ -702,6 +702,11 @@ parse_result_type(const uint8_t **payload, const uint8_t *end, } } else if (has_error_type != WASM_COMP_OPTIONAL_FALSE) { + if ((*out)->def_val.result->result_type) { + wasm_runtime_free((*out)->def_val.result->result_type); + } + wasm_runtime_free((*out)->def_val.result); + (*out)->def_val.result = NULL; set_error_buf_ex(error_buf, error_buf_size, "Malformed binary: invalid optional tag 0x%02x", has_error_type); @@ -800,6 +805,11 @@ parse_stream_type(const uint8_t **payload, const uint8_t *end, } const uint8_t *p = *payload; + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing stream optional tag"); + return false; + } // Allocate the stream structure (*out)->def_val.stream = @@ -843,6 +853,8 @@ parse_stream_type(const uint8_t **payload, const uint8_t *end, } } else if (has_element_type != WASM_COMP_OPTIONAL_FALSE) { + wasm_runtime_free((*out)->def_val.stream); + (*out)->def_val.stream = NULL; set_error_buf_ex(error_buf, error_buf_size, "Malformed binary: invalid optional tag 0x%02x", has_element_type); @@ -866,6 +878,11 @@ parse_future_type(const uint8_t **payload, const uint8_t *end, } const uint8_t *p = *payload; + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing future optional tag"); + return false; + } // Allocate the future structure (*out)->def_val.future = @@ -909,6 +926,8 @@ parse_future_type(const uint8_t **payload, const uint8_t *end, } } else if (has_element_type != WASM_COMP_OPTIONAL_FALSE) { + wasm_runtime_free((*out)->def_val.future); + (*out)->def_val.future = NULL; set_error_buf_ex(error_buf, error_buf_size, "Malformed binary: invalid optional tag 0x%02x", has_element_type); @@ -1224,7 +1243,9 @@ parse_defvaltype(const uint8_t **payload, const uint8_t *end, WASMComponentDefValType **out, char *error_buf, uint32_t error_buf_size) { - if (!payload || !*payload || !out) { + if (!payload || !*payload || !out || !end || *payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing defined value type"); return false; } @@ -1418,11 +1439,10 @@ parse_param_list(const uint8_t **payload, const uint8_t *end, // Allocate memory for the param list if (param_count > 0) { - (*out)->params = wasm_runtime_malloc(sizeof(WASMComponentLabelValType) - * param_count); + (*out)->params = wasm_component_checked_calloc( + param_count, sizeof(WASMComponentLabelValType), p, end, 1, + "function parameter", error_buf, error_buf_size); if (!(*out)->params) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for param list"); return false; } @@ -1467,8 +1487,10 @@ parse_func_type(const uint8_t **payload, const uint8_t *end, // Parse the param list if (!parse_param_list(&p, end, &(*out)->params, error_buf, error_buf_size)) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to parse param list"); + if (!error_buf || error_buf[0] == '\0') { + set_error_buf_ex(error_buf, error_buf_size, + "Failed to parse param list"); + } return false; } @@ -1722,6 +1744,12 @@ parse_component_decl_export(const uint8_t **payload, const uint8_t *end, return false; } + /* Ownership of export_name now belongs to *out; on any later error + * path it is freed by the centralized cleanup + * (free_component_instance_decl -> free_component_export_name), which + * walks (*out)->export_name. Freeing it locally below as well caused a + * double-free / heap-use-after-free (found by the component-parser + * fuzz target). */ (*out)->export_name = export_name; WASMComponentExternDesc *extern_desc = @@ -1729,7 +1757,7 @@ parse_component_decl_export(const uint8_t **payload, const uint8_t *end, if (!extern_desc) { set_error_buf_ex(error_buf, error_buf_size, "Failed to allocate memory for extern desc"); - wasm_runtime_free(export_name); + /* export_name is owned by *out; the caller frees it. */ return false; } memset(extern_desc, 0, sizeof(WASMComponentExternDesc)); @@ -1738,8 +1766,9 @@ parse_component_decl_export(const uint8_t **payload, const uint8_t *end, if (!parse_extern_desc(&p, end, extern_desc, error_buf, error_buf_size)) { set_error_buf_ex(error_buf, error_buf_size, "Failed to parse extern desc"); + /* extern_desc is not yet owned by *out -> free it here. + * export_name IS owned by *out -> the caller frees it. */ wasm_runtime_free(extern_desc); - wasm_runtime_free(export_name); return false; } @@ -1754,6 +1783,11 @@ parse_component_instance_decl(const uint8_t **payload, const uint8_t *end, WASMComponentInstDecl **out, char *error_buf, uint32_t error_buf_size) { + if (!payload || !*payload || !out || !end || *payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing instance declaration"); + return false; + } const uint8_t *p = *payload; // Allocate memory for the instance decl @@ -1821,6 +1855,11 @@ parse_component_decl(const uint8_t **payload, const uint8_t *end, WASMComponentComponentDecl **out, char *error_buf, uint32_t error_buf_size) { + if (!payload || !*payload || !out || !*out || !end || *payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing component declaration"); + return false; + } const uint8_t *p = *payload; uint8_t tag = *p; @@ -1874,11 +1913,10 @@ parse_component_type(const uint8_t **payload, const uint8_t *end, // Allocate memory for the component list if (component_count > 0) { - (*out)->component_decls = wasm_runtime_malloc( - sizeof(WASMComponentComponentDecl) * component_count); + (*out)->component_decls = wasm_component_checked_calloc( + component_count, sizeof(WASMComponentComponentDecl), p, end, 1, + "component type declaration", error_buf, error_buf_size); if (!(*out)->component_decls) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for component list"); return false; } @@ -1898,6 +1936,13 @@ parse_component_type(const uint8_t **payload, const uint8_t *end, error_buf_size)) { set_error_buf_ex(error_buf, error_buf_size, "Failed to parse component %d", i); + /* parse_component_decl may have populated decl contents + * (e.g. an import's import_name) before failing on a later + * sub-parse such as the extern-desc; free those contents + * first, mirroring the parse_component_instance_type fail + * path. component_decls[i] is still zeroed, so the + * centralized cleanup never reaches this transient shell. */ + free_component_decl(component_decl); wasm_runtime_free(component_decl); return false; } @@ -1951,11 +1996,10 @@ parse_component_instance_type(const uint8_t **payload, const uint8_t *end, // Allocate memory for the instance list if (instance_count > 0) { - instance_decls = - wasm_runtime_malloc(sizeof(WASMComponentInstDecl) * instance_count); + instance_decls = wasm_component_checked_calloc( + instance_count, sizeof(WASMComponentInstDecl), p, end, 1, + "component instance declaration", error_buf, error_buf_size); if (!instance_decls) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for instance list"); goto fail; } @@ -1987,14 +2031,18 @@ parse_component_instance_type(const uint8_t **payload, const uint8_t *end, fail: ret = false; - free_component_instance_decl(instance_decl); - wasm_runtime_free(instance_decl); + if (instance_decl) { + free_component_instance_decl(instance_decl); + wasm_runtime_free(instance_decl); + } for (uint32_t j = 0; j < populated; j++) { free_component_instance_decl(&instance_decls[j]); } - wasm_runtime_free(instance_decls); + if (instance_decls) { + wasm_runtime_free(instance_decls); + } wasm_runtime_free(*out); *out = NULL; done: @@ -2006,6 +2054,11 @@ parse_resource_type_sync(const uint8_t **payload, const uint8_t *end, WASMComponentResourceTypeSync **out, char *error_buf, uint32_t error_buf_size) { + if (!payload || !*payload || !out || !end || *payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing sync resource destructor optional tag"); + return false; + } const uint8_t *p = *payload; // Allocate memory for the resource type sync @@ -2024,6 +2077,8 @@ parse_resource_type_sync(const uint8_t **payload, const uint8_t *end, uint64_t dtor_func_idx_leb = 0; if (!read_leb((uint8_t **)&p, end, 32, false, &dtor_func_idx_leb, error_buf, error_buf_size)) { + wasm_runtime_free(*out); + *out = NULL; set_error_buf_ex(error_buf, error_buf_size, "Failed to parse resource type sync"); return false; @@ -2032,6 +2087,8 @@ parse_resource_type_sync(const uint8_t **payload, const uint8_t *end, (*out)->dtor_func_idx = (uint32_t)dtor_func_idx_leb; } else if (has_result_type != WASM_COMP_OPTIONAL_FALSE) { + wasm_runtime_free(*out); + *out = NULL; set_error_buf_ex(error_buf, error_buf_size, "Malformed binary: invalid optional tag 0x%02x", has_result_type); @@ -2047,6 +2104,11 @@ parse_resource_type_async(const uint8_t **payload, const uint8_t *end, WASMComponentResourceTypeAsync **out, char *error_buf, uint32_t error_buf_size) { + if (!payload || !*payload || !out || !end || *payload >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Missing async resource destructor"); + return false; + } const uint8_t *p = *payload; // Allocate memory for the resource type async @@ -2063,6 +2125,8 @@ parse_resource_type_async(const uint8_t **payload, const uint8_t *end, uint64_t dtor_func_idx_leb = 0; if (!read_leb((uint8_t **)&p, end, 32, false, &dtor_func_idx_leb, error_buf, error_buf_size)) { + wasm_runtime_free(*out); + *out = NULL; set_error_buf_ex(error_buf, error_buf_size, "Failed to parse resource type async"); return false; @@ -2070,12 +2134,21 @@ parse_resource_type_async(const uint8_t **payload, const uint8_t *end, (*out)->dtor_func_idx = (uint32_t)dtor_func_idx_leb; // Read optional cb?:? + if (p >= end) { + wasm_runtime_free(*out); + *out = NULL; + set_error_buf_ex(error_buf, error_buf_size, + "Missing async resource callback optional tag"); + return false; + } uint8_t has_result_type = *p++; if (has_result_type == WASM_COMP_OPTIONAL_TRUE) { // Read the cb funcidx uint64_t callback_func_idx_leb = 0; if (!read_leb((uint8_t **)&p, end, 32, false, &callback_func_idx_leb, error_buf, error_buf_size)) { + wasm_runtime_free(*out); + *out = NULL; set_error_buf_ex(error_buf, error_buf_size, "Failed to parse resource type async"); return false; @@ -2083,6 +2156,8 @@ parse_resource_type_async(const uint8_t **payload, const uint8_t *end, (*out)->callback_func_idx = (uint32_t)callback_func_idx_leb; } else if (has_result_type != WASM_COMP_OPTIONAL_FALSE) { + wasm_runtime_free(*out); + *out = NULL; set_error_buf_ex(error_buf, error_buf_size, "Malformed binary: invalid optional tag 0x%02x", has_result_type); @@ -2098,6 +2173,12 @@ parse_resource_type(const uint8_t **payload, const uint8_t *end, WASMComponentResourceType **out, char *error_buf, uint32_t error_buf_size) { + if (!payload || !*payload || !out || !end || *payload > end + || (size_t)(end - *payload) < 2) { + set_error_buf_ex(error_buf, error_buf_size, + "Truncated resource type header"); + return false; + } const uint8_t *p = *payload; // Allocate memory for the resource type @@ -2114,9 +2195,10 @@ parse_resource_type(const uint8_t **payload, const uint8_t *end, (*out)->tag = tag; // Read the resource type rep - if (*p++ != WASM_COMP_RESOURCE_REP_I32) { + uint8_t rep = *p++; + if (rep != WASM_COMP_RESOURCE_REP_I32) { set_error_buf_ex(error_buf, error_buf_size, - "Invalid resource type rep: 0x%02x", *p); + "Invalid resource type rep: 0x%02x", rep); return false; } @@ -2158,6 +2240,11 @@ parse_single_type(const uint8_t **payload, const uint8_t *end, uint32_t error_buf_size) { const uint8_t *p = *payload; + if (p >= end) { + set_error_buf_ex(error_buf, error_buf_size, + "Unexpected end while parsing component type"); + return false; + } uint8_t tag = *p; WASMComponentTypesTag type_tag = get_type_tag(tag); out->tag = type_tag; @@ -2269,25 +2356,18 @@ wasm_component_parse_types_section(const uint8_t **payload, out->count = type_count; if (type_count > 0) { - // Allocate the types array - out->types = - wasm_runtime_malloc(sizeof(WASMComponentTypes) * type_count); + out->types = wasm_component_checked_calloc( + type_count, sizeof(WASMComponentTypes), p, end, 1, "component type", + error_buf, error_buf_size); if (!out->types) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for types array"); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - // Initialize all types to zero - memset(out->types, 0, sizeof(WASMComponentTypes) * type_count); - for (uint32_t i = 0; i < type_count; ++i) { if (!parse_single_type(&p, end, &out->types[i], error_buf, error_buf_size)) { - wasm_runtime_free(out->types); - out->types = NULL; if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; @@ -2655,7 +2735,7 @@ fill_resource_type_instance(WASMComponentTypeInstance **types, } resource->dtor_method = dtor_func; } - resource->is_wasi = false; + resource->is_builtin_wasi = false; resource->impl = comp_instance; resource->drop_method = drop_method; resource->new_method = new_method; @@ -2690,22 +2770,25 @@ fill_def_type_instance(WASMComponentTypeInstance **types, WASMComponentTypes *type_definition, char *error_buf, uint32 error_buf_size) { - uint32 size = 0, val_idx = 0; - uint32 types_count = *curr_types_count; - if (!types) { - set_error_buf_ex(error_buf, error_buf_size, - "ERROR: invalid types index space\n"); + uint64 size_64 = 0; + uint32 size = 0, val_idx = 0, types_count; + if (!types || !curr_types_count || !defined_types || !type_definition + || !type_definition->type.def_val_type + || !wasm_get_def_val_type_size(type_definition->type.def_val_type, + &size_64) + || size_64 > UINT32_MAX || size_64 > defined_types_size) { + set_error_buf_ex(error_buf, error_buf_size, + "Defined type layout is invalid or too large\n"); return 0; } + size = (uint32)size_64; + types_count = *curr_types_count; WASMComponentTypeInstance *curr_type = (WASMComponentTypeInstance *)defined_types; switch (type_definition->type.def_val_type->tag) { case WASM_COMP_DEF_VAL_PRIMVAL: // pvt: LOG_DEBUG("Fill primval type instance"); - size = sizeof(WASMComponentTypeInstance); - if (size > defined_types_size) - goto fail; curr_type->type = COMPONENT_VAL_TYPE_PRIMVAL; curr_type->type_specific.primval = type_definition->type.def_val_type->def_val.primval; @@ -2716,12 +2799,6 @@ fill_def_type_instance(WASMComponentTypeInstance **types, LOG_DEBUG("Fill record type instance"); WASMComponentRecordType *record_definition = type_definition->type.def_val_type->def_val.record; - size = sizeof(WASMComponentTypeInstance) - + sizeof(WASMComponentRecordInstance) - + (record_definition->count) - * sizeof(WASMComponentLabelValTypeInstance); - if (size > defined_types_size) - goto fail; WASMComponentRecordInstance *record = (WASMComponentRecordInstance *)((uint8_t *)defined_types + sizeof( @@ -2743,12 +2820,6 @@ fill_def_type_instance(WASMComponentTypeInstance **types, LOG_DEBUG("Fill variant type instance"); WASMComponentVariantType *variant_definition = type_definition->type.def_val_type->def_val.variant; - size = sizeof(WASMComponentTypeInstance) - + sizeof(WASMComponentVariantInstance) - + variant_definition->count - * sizeof(WASMComponentCaseValInstance); - if (size > defined_types_size) - goto fail; WASMComponentVariantInstance *variant = (WASMComponentVariantInstance *)((uint8_t *)defined_types @@ -2770,10 +2841,6 @@ fill_def_type_instance(WASMComponentTypeInstance **types, LOG_DEBUG("Fill list type instance"); WASMComponentListType *list_definition = type_definition->type.def_val_type->def_val.list; - size = sizeof(WASMComponentTypeInstance) - + sizeof(WASMComponentListInstance); - if (size > defined_types_size) - goto fail; WASMComponentListInstance *list = (WASMComponentListInstance *)((uint8_t *)defined_types + sizeof( @@ -2787,10 +2854,6 @@ fill_def_type_instance(WASMComponentTypeInstance **types, LOG_DEBUG("Fill fixed size list type instance"); WASMComponentListLenType *list_len_definition = type_definition->type.def_val_type->def_val.list_len; - size = sizeof(WASMComponentTypeInstance) - + sizeof(WASMComponentListLenInstance); - if (size > defined_types_size) - goto fail; WASMComponentListLenInstance *list_len = (WASMComponentListLenInstance *)((uint8_t *)defined_types @@ -2806,12 +2869,6 @@ fill_def_type_instance(WASMComponentTypeInstance **types, LOG_DEBUG("Fill tuple type instance"); WASMComponentTupleType *tuple_definition = type_definition->type.def_val_type->def_val.tuple; - size = - sizeof(WASMComponentTypeInstance) - + sizeof(WASMComponentTupleInstance) - + tuple_definition->count * sizeof(WASMComponentTypeInstance *); - if (size > defined_types_size) - goto fail; WASMComponentTupleInstance *tuple = (WASMComponentTupleInstance *)((uint8_t *)defined_types + sizeof( @@ -2835,9 +2892,6 @@ fill_def_type_instance(WASMComponentTypeInstance **types, // Flags type case WASM_COMP_DEF_VAL_FLAGS: // 0x6e l*:vec() LOG_DEBUG("Fill flags type instance"); - size = sizeof(WASMComponentTypeInstance); - if (size > defined_types_size) - goto fail; WASMComponentFlagType *flag = type_definition->type.def_val_type->def_val.flag; curr_type->type = COMPONENT_VAL_TYPE_FLAGS; @@ -2846,25 +2900,18 @@ fill_def_type_instance(WASMComponentTypeInstance **types, // Enum type case WASM_COMP_DEF_VAL_ENUM: // 0x6d l*:vec() LOG_DEBUG("Fill enum type instance"); - size = sizeof(WASMComponentTypeInstance); WASMComponentEnumType *enum_definition = type_definition->type.def_val_type->def_val.enum_type; - if (size > defined_types_size) - goto fail; curr_type->type = COMPONENT_VAL_TYPE_ENUM; curr_type->type_specific.enum_type = enum_definition; break; // Option type case WASM_COMP_DEF_VAL_OPTION: // 0x6b t: LOG_DEBUG("Fill option type instance"); - size = sizeof(WASMComponentTypeInstance) - + sizeof(WASMComponentOptionInstance); WASMComponentOptionInstance *option = (WASMComponentOptionInstance *)((uint8_t *)defined_types + sizeof( WASMComponentTypeInstance)); - if (size > defined_types_size) - goto fail; WASMComponentOptionType *option_definition = type_definition->type.def_val_type->def_val.option; assign_type_instance(&option->element_type, @@ -2875,14 +2922,10 @@ fill_def_type_instance(WASMComponentTypeInstance **types, // Result type case WASM_COMP_DEF_VAL_RESULT: // 0x6a t?:? u?:? LOG_DEBUG("Fill result type instance"); - size = sizeof(WASMComponentTypeInstance) - + sizeof(WASMComponentResultInstance); WASMComponentResultInstance *result = (WASMComponentResultInstance *)((uint8_t *)defined_types + sizeof( WASMComponentTypeInstance)); - if (size > defined_types_size) - goto fail; WASMComponentResultType *result_definition = type_definition->type.def_val_type->def_val.result; assign_type_instance(&result->result_type, @@ -2895,14 +2938,10 @@ fill_def_type_instance(WASMComponentTypeInstance **types, // Handle types case WASM_COMP_DEF_VAL_OWN: // 0x69 i: LOG_DEBUG("Fill resource own type instance"); - size = sizeof(WASMComponentTypeInstance) - + sizeof(WASMComponentResourceHandleInstance); WASMComponentResourceHandleInstance *resource_own = (WASMComponentResourceHandleInstance *)((uint8_t *)defined_types + sizeof(WASMComponentTypeInstance)); - if (size > defined_types_size) - goto fail; WASMComponentOwnType *own_definition = type_definition->type.def_val_type->def_val.owned; if (own_definition->type_idx >= *curr_types_count @@ -2921,14 +2960,10 @@ fill_def_type_instance(WASMComponentTypeInstance **types, break; case WASM_COMP_DEF_VAL_BORROW: // 0x68 i: LOG_DEBUG("Fill resource borrow type instance"); - size = sizeof(WASMComponentTypeInstance) - + sizeof(WASMComponentResourceHandleInstance); WASMComponentResourceHandleInstance *resource_borrow = (WASMComponentResourceHandleInstance *)((uint8_t *)defined_types + sizeof(WASMComponentTypeInstance)); - if (size > defined_types_size) - goto fail; WASMComponentBorrowType *borrow_definition = type_definition->type.def_val_type->def_val.borrow; if (borrow_definition->type_idx >= *curr_types_count @@ -2966,10 +3001,6 @@ fill_def_type_instance(WASMComponentTypeInstance **types, // defined_types pointer can be incremented by this ammount, // pointing to the next free location in defined types memory // area -fail: - set_error_buf_ex(error_buf, error_buf_size, - "Defined types alocated memory exceeded\n"); - return 0; } /// @brief Adds a new function signature type to defined types memory section + @@ -2992,13 +3023,16 @@ fill_func_type_instance(WASMComponentTypeInstance **types, uint32 *types_count, WASMComponentTypes *type_definition, char *error_buf, uint32 error_buf_size) { + uint64 size_64 = 0; uint32 size = 0, val_idx = 0; - size = wasm_get_func_type_size(type_definition->type.func_type); - if (size > defined_types_size) { + if (!type_definition + || !wasm_get_func_type_size(type_definition->type.func_type, &size_64) + || size_64 > UINT32_MAX || size_64 > defined_types_size) { set_error_buf_ex(error_buf, error_buf_size, - "Defined types alocated memory exceeded"); + "Function type layout is invalid or too large"); return 0; } + size = (uint32)size_64; // Allocation in memory: /* @@ -3066,14 +3100,18 @@ fill_instance_type_instance(WASMComponentTypeInstance **types, uint32 error_buf_size) { WASMComponentInstanceDeclTypeSize instance_decl_size = { 0 }; + uint64 size_64 = 0; uint32 size = 0, val_idx = 0, curr_type_size = 0; - size += wasm_get_inst_decl_size(type_definition->type.instance_type, - &instance_decl_size); - if (size > defined_types_size) { + if (!type_definition + || !wasm_get_inst_decl_size(type_definition->type.instance_type, + &instance_decl_size, &size_64) + || size_64 > UINT32_MAX || size_64 > defined_types_size + || instance_decl_size.types_size > UINT32_MAX) { set_error_buf_ex(error_buf, error_buf_size, - "Defined types alocated memory exceeded"); + "Instance type layout is invalid or too large"); return 0; } + size = (uint32)size_64; LOG_DEBUG("Fill instance type instance: %d definitions, total size %d", type_definition->type.instance_type->count, size); WASMComponentInstDecl *instance_decl = NULL; @@ -3120,6 +3158,9 @@ fill_instance_type_instance(WASMComponentTypeInstance **types, inst_type_instance->exports_count = 0; inst_type_instance->exports = inst_type_instance_exports; inst_type_instance->defined_core_funcs = inst_type_instance_core_funcs; + inst_type_instance->owned_types_begin = inst_type_instance_defined_types; + inst_type_instance->owned_types_size = + (uint32)instance_decl_size.types_size; for (val_idx = 0; val_idx < instance_decl_size.func_count + instance_decl_size.resource_count; val_idx++) { @@ -3160,8 +3201,8 @@ fill_instance_type_instance(WASMComponentTypeInstance **types, inst_type_instance->types, &inst_type_instance->types_count, inst_type_instance_defined_types, - instance_decl_size.types_size, instance_decl->decl.type, - error_buf, error_buf_size); + (uint32)instance_decl_size.types_size, + instance_decl->decl.type, error_buf, error_buf_size); if (!curr_type_size) { return 0; } @@ -3174,8 +3215,8 @@ fill_instance_type_instance(WASMComponentTypeInstance **types, inst_type_instance->types, &inst_type_instance->types_count, inst_type_instance_defined_types, - instance_decl_size.types_size, instance_decl->decl.type, - error_buf, error_buf_size); + (uint32)instance_decl_size.types_size, + instance_decl->decl.type, error_buf, error_buf_size); if (!curr_type_size) { return 0; } @@ -3185,6 +3226,7 @@ fill_instance_type_instance(WASMComponentTypeInstance **types, } break; case WASM_COMP_COMPONENT_DECL_INSTANCE_ALIAS: + { WASMComponentAliasDefinition *alias = instance_decl->decl.alias; if (instance_decl->decl.alias->alias_target_type != WASM_COMP_ALIAS_TARGET_OUTER) { @@ -3234,6 +3276,7 @@ fill_instance_type_instance(WASMComponentTypeInstance **types, break; } break; + } case WASM_COMP_COMPONENT_DECL_INSTANCE_EXPORTDECL: inst_type_instance->exports[inst_type_instance->exports_count] @@ -3344,7 +3387,7 @@ fill_instance_type_instance(WASMComponentTypeInstance **types, ->exports[inst_type_instance->exports_count] .exp.func_type = inst_type_instance - ->funcs[inst_type_instance->func_count]; + ->funcs[inst_type_instance->func_count - 1]; inst_type_instance->exports_count++; break; default: diff --git a/core/iwasm/common/component-model/wasm_component_values_section.c b/core/iwasm/common/component-model/wasm_component_values_section.c index d4aac8c8c4..5ab28e2efb 100644 --- a/core/iwasm/common/component-model/wasm_component_values_section.c +++ b/core/iwasm/common/component-model/wasm_component_values_section.c @@ -64,13 +64,12 @@ parse_value(const uint8_t **payload, const uint8_t *end, "Failed to allocate memory for value type"); return false; } + memset(val_type, 0, sizeof(*val_type)); if (!parse_valtype(&p, end, val_type, error_buf, error_buf_size)) { wasm_runtime_free(val_type); return false; } - out->val_type = val_type; - uint64_t core_data_len_u32_leb = 0; if (!read_leb((uint8_t **)&p, end, 32, false, &core_data_len_u32_leb, error_buf, error_buf_size)) { @@ -474,6 +473,7 @@ parse_value(const uint8_t **payload, const uint8_t *end, } // Keep a borrowed pointer to the raw value bytes window + out->val_type = val_type; out->core_data = v_start; *payload = p; @@ -513,19 +513,15 @@ wasm_component_parse_values_section(const uint8_t **payload, out->count = value_count; if (value_count > 0) { - out->values = - wasm_runtime_malloc(sizeof(WASMComponentValue) * value_count); + out->values = wasm_component_checked_calloc( + value_count, sizeof(WASMComponentValue), p, end, 1, + "component value", error_buf, error_buf_size); if (!out->values) { - set_error_buf_ex(error_buf, error_buf_size, - "Failed to allocate memory for values"); if (consumed_len) *consumed_len = (uint32_t)(p - *payload); return false; } - // Initialize all values to zero to avoid garbage data - memset(out->values, 0, sizeof(WASMComponentValue) * value_count); - for (uint32_t i = 0; i < value_count; ++i) { if (!parse_value(&p, end, &out->values[i], error_buf, error_buf_size)) { @@ -566,4 +562,4 @@ wasm_component_free_values_section(WASMComponentSection *section) } wasm_runtime_free(value_section); section->parsed.value_section = NULL; -} \ No newline at end of file +} diff --git a/core/iwasm/common/wasm_c_api.c b/core/iwasm/common/wasm_c_api.c index 53d3a87f04..89c36f6b1b 100644 --- a/core/iwasm/common/wasm_c_api.c +++ b/core/iwasm/common/wasm_c_api.c @@ -3639,7 +3639,7 @@ interp_global_set(const WASMModuleInstance *inst_interp, uint16 global_idx_rt, const WASMGlobalInstance *global_interp = inst_interp->e->globals + global_idx_rt; uint8 val_type_rt = global_interp->type; -#if WASM_ENABLE_MULTI_MODULE != 0 +#if WASM_ENABLE_MULTI_MODULE != 0 || WASM_ENABLE_COMPONENT_MODEL != 0 uint8 *data = global_interp->import_global_inst ? global_interp->import_module_inst->global_data + global_interp->import_global_inst->data_offset @@ -3658,7 +3658,7 @@ interp_global_get(const WASMModuleInstance *inst_interp, uint16 global_idx_rt, { WASMGlobalInstance *global_interp = inst_interp->e->globals + global_idx_rt; uint8 val_type_rt = global_interp->type; -#if WASM_ENABLE_MULTI_MODULE != 0 +#if WASM_ENABLE_MULTI_MODULE != 0 || WASM_ENABLE_COMPONENT_MODEL != 0 uint8 *data = global_interp->import_global_inst ? global_interp->import_module_inst->global_data + global_interp->import_global_inst->data_offset @@ -4032,11 +4032,14 @@ own wasm_ref_t * wasm_table_get(const wasm_table_t *table, wasm_table_size_t index) { uint32 ref_idx = NULL_REF; + WASMModuleInstanceCommon *ref_inst_comm_rt; if (!table || !table->inst_comm_rt) { return NULL; } + ref_inst_comm_rt = table->inst_comm_rt; + #if WASM_ENABLE_INTERP != 0 if (table->inst_comm_rt->module_type == Wasm_Module_Bytecode) { WASMTableInstance *table_interp = @@ -4045,7 +4048,29 @@ wasm_table_get(const wasm_table_t *table, wasm_table_size_t index) if (index >= table_interp->cur_size) { return NULL; } - ref_idx = (uint32)table_interp->elems[index]; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (table_interp->component_func_refs) { + WASMFunctionInstance *func_ref = + table_interp->component_func_refs[index]; + + if (!func_ref) { + return NULL; + } + if (!func_ref->module_instance || !func_ref->module_instance->e + || func_ref->func_idx + >= func_ref->module_instance->e->function_count) { + return NULL; + } + + ref_idx = func_ref->func_idx; + ref_inst_comm_rt = + (WASMModuleInstanceCommon *)func_ref->module_instance; + } + else +#endif + { + ref_idx = (uint32)table_interp->elems[index]; + } } #endif @@ -4081,7 +4106,7 @@ wasm_table_get(const wasm_table_t *table, wasm_table_size_t index) #endif { return wasm_ref_new_internal(table->store, WASM_REF_func, ref_idx, - table->inst_comm_rt); + ref_inst_comm_rt); } } @@ -4091,6 +4116,9 @@ wasm_table_set(wasm_table_t *table, wasm_table_size_t index, { uint32 *p_ref_idx = NULL; uint32 function_count = 0; +#if WASM_ENABLE_COMPONENT_MODEL != 0 && WASM_ENABLE_INTERP != 0 + WASMFunctionInstance **p_component_func_ref = NULL; +#endif if (!table || !table->inst_comm_rt) { return false; @@ -4119,6 +4147,11 @@ wasm_table_set(wasm_table_t *table, wasm_table_size_t index, p_ref_idx = (uint32 *)(table_interp->elems + index); function_count = ((WASMModuleInstance *)table->inst_comm_rt)->e->function_count; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (table_interp->component_func_refs) { + p_component_func_ref = &table_interp->component_func_refs[index]; + } +#endif } #endif @@ -4154,15 +4187,48 @@ wasm_table_set(wasm_table_t *table, wasm_table_size_t index, { if (ref) { if (NULL_REF != ref->ref_idx_rt) { - if (ref->ref_idx_rt >= function_count) { - return false; +#if WASM_ENABLE_COMPONENT_MODEL != 0 && WASM_ENABLE_INTERP != 0 + if (p_component_func_ref) { + WASMModuleInstance *ref_module_inst; + + if (!ref->inst_comm_rt + || ref->inst_comm_rt->module_type + != Wasm_Module_Bytecode) { + return false; + } + ref_module_inst = (WASMModuleInstance *)ref->inst_comm_rt; + if (!ref_module_inst->e + || ref->ref_idx_rt + >= ref_module_inst->e->function_count) { + return false; + } + *p_component_func_ref = + &ref_module_inst->e->functions[ref->ref_idx_rt]; + } + else +#endif + { + if (ref->inst_comm_rt != table->inst_comm_rt + || ref->ref_idx_rt >= function_count) { + return false; + } } } +#if WASM_ENABLE_COMPONENT_MODEL != 0 && WASM_ENABLE_INTERP != 0 + else if (p_component_func_ref) { + *p_component_func_ref = NULL; + } +#endif *p_ref_idx = ref->ref_idx_rt; wasm_ref_delete(ref); } else { *p_ref_idx = NULL_REF; +#if WASM_ENABLE_COMPONENT_MODEL != 0 && WASM_ENABLE_INTERP != 0 + if (p_component_func_ref) { + *p_component_func_ref = NULL; + } +#endif } } diff --git a/core/iwasm/common/wasm_exec_env.h b/core/iwasm/common/wasm_exec_env.h index b848d913b5..4e2682b282 100644 --- a/core/iwasm/common/wasm_exec_env.h +++ b/core/iwasm/common/wasm_exec_env.h @@ -193,6 +193,7 @@ typedef struct WASMExecEnv { WASMFunctionInstance *core_func; WASMMemoryInstance *memory; LiftLowerContext *cx; + bool component_callback_active; #endif /* The WASM stack of current thread */ diff --git a/core/iwasm/common/wasm_native.c b/core/iwasm/common/wasm_native.c index 56710a0cee..ace6a7bc31 100644 --- a/core/iwasm/common/wasm_native.c +++ b/core/iwasm/common/wasm_native.c @@ -18,15 +18,17 @@ #if WASM_ENABLE_WASI_NN != 0 || WASM_ENABLE_WASI_EPHEMERAL_NN != 0 #include "wasi_nn_host.h" #endif -#if WASM_ENABLE_LIBC_WASI != 0 && WASM_ENABLE_COMPONENT_MODEL != 0 +#if WASM_ENABLE_LIBC_WASI_P2 != 0 +#include "component-model/wasm_component_host_resource.h" #include "../libraries/libc-wasi-p2/libc_wasi_p2_wrapper.h" -#endif /* WASM_ENABLE_LIBC_WASI != 0 && WASM_ENABLE_COMPONENT_MODEL */ +#include "../libraries/libc-wasi-p2/wasi_p2_sockets.h" +#endif /* WASM_ENABLE_LIBC_WASI_P2 */ static NativeSymbolsList g_native_symbols_list = NULL; -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 static void *g_wasi_context_key; -#endif /* WASM_ENABLE_LIBC_WASI */ +#endif /* WASM_ENABLE_LIBC_WASI || WASM_ENABLE_LIBC_WASI_P2 */ uint32 get_libc_builtin_export_apis(NativeSymbol **p_libc_builtin_apis); @@ -78,7 +80,6 @@ get_libc_emcc_export_apis(NativeSymbol **p_libc_emcc_apis); uint32 get_lib_rats_export_apis(NativeSymbol **p_lib_rats_apis); -#if WASM_ENABLE_COMPONENT_MODEL == 0 static bool compare_type_with_signature(uint8 type, const char signature) { @@ -89,6 +90,12 @@ compare_type_with_signature(uint8 type, const char signature) return true; } + /* P2 wrapper signatures use '~' for the i32 length paired with a + preceding pointer/offset parameter. */ + if (type == VALUE_TYPE_I32 && signature == '~') { + return true; + } + #if WASM_ENABLE_REF_TYPES != 0 if ('r' == signature #if WASM_ENABLE_GC != 0 @@ -108,8 +115,9 @@ compare_type_with_signature(uint8 type, const char signature) return false; } -static bool -check_symbol_signature(const WASMFuncType *type, const char *signature) +bool +wasm_native_validate_symbol_signature(const WASMFuncType *type, + const char *signature) { const char *p = signature, *p_end; char sig; @@ -176,8 +184,6 @@ check_symbol_signature(const WASMFuncType *type, const char *signature) return true; } -#endif - static int native_symbol_cmp(const void *native_symbol1, const void *native_symbol2) { @@ -222,11 +228,11 @@ lookup_symbol(NativeSymbol *native_symbols, uint32 n_native_symbols, * allow func_type and all outputs, like p_signature, p_attachment and * p_call_conv_raw to be NULL */ -void * -wasm_native_resolve_symbol(const char *module_name, const char *field_name, - const WASMFuncType *func_type, - const char **p_signature, void **p_attachment, - bool *p_call_conv_raw) +static void * +resolve_symbol(const char *module_name, const char *field_name, + const WASMFuncType *func_type, const char **p_signature, + void **p_attachment, bool *p_call_conv_raw, + bool require_exact_names) { NativeSymbolsNode *node, *node_next; const char *signature = NULL; @@ -235,11 +241,13 @@ wasm_native_resolve_symbol(const char *module_name, const char *field_name, node = g_native_symbols_list; while (node) { node_next = node->next; - if (is_module_registered(module_name, node->module_name)) { + if ((require_exact_names && strcmp(module_name, node->module_name) == 0) + || (!require_exact_names + && is_module_registered(module_name, node->module_name))) { if ((func_ptr = lookup_symbol(node->native_symbols, node->n_native_symbols, field_name, &signature, &attachment)) - || (field_name[0] == '_' + || (!require_exact_names && field_name[0] == '_' && (func_ptr = lookup_symbol( node->native_symbols, node->n_native_symbols, field_name + 1, &signature, &attachment)))) @@ -253,19 +261,22 @@ wasm_native_resolve_symbol(const char *module_name, const char *field_name, if (func_ptr) { if (signature && signature[0] != '\0') { -#if WASM_ENABLE_COMPONENT_MODEL == 0 /* signature is not empty, check its format */ - if (!func_type || !check_symbol_signature(func_type, signature)) { + if (func_type + && !wasm_native_validate_symbol_signature(func_type, + signature)) { #if WASM_ENABLE_WAMR_COMPILER == 0 /* Output warning except running aot compiler */ - LOG_WARNING("failed to check signature '%s' and resolve " - "pointer params for import function (%s, %s)\n", - signature, module_name, field_name); + LOG_WARNING( + "failed to check signature '%s' against %u params/%u " + "results for import function (%s, %s)\n", + signature, func_type ? func_type->param_count : 0, + func_type ? func_type->result_count : 0, module_name, + field_name); #endif return NULL; } else -#endif /*WASM_ENABLE_COMPONENT_MODEL == 0*/ /* Save signature for runtime to do pointer check and address conversion */ *p_signature = signature; @@ -281,6 +292,27 @@ wasm_native_resolve_symbol(const char *module_name, const char *field_name, return func_ptr; } +void * +wasm_native_resolve_symbol(const char *module_name, const char *field_name, + const WASMFuncType *func_type, + const char **p_signature, void **p_attachment, + bool *p_call_conv_raw) +{ + return resolve_symbol(module_name, field_name, func_type, p_signature, + p_attachment, p_call_conv_raw, false); +} + +void * +wasm_native_resolve_symbol_exact(const char *module_name, + const char *field_name, + const WASMFuncType *func_type, + const char **p_signature, void **p_attachment, + bool *p_call_conv_raw) +{ + return resolve_symbol(module_name, field_name, func_type, p_signature, + p_attachment, p_call_conv_raw, true); +} + static bool register_natives(const char *module_name, NativeSymbol *native_symbols, uint32 n_native_symbols, bool call_conv_raw) @@ -471,7 +503,7 @@ wasm_native_inherit_contexts(WASMModuleInstanceCommon *child, } #endif /* WASM_ENABLE_MODULE_INST_CONTEXT != 0 */ -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 WASIContext * wasm_runtime_get_wasi_ctx(WASMModuleInstanceCommon *module_inst_comm) { @@ -493,7 +525,7 @@ wasi_context_dtor(WASMModuleInstanceCommon *inst, void *ctx) } wasm_runtime_destroy_wasi(inst); } -#endif /* end of WASM_ENABLE_LIBC_WASI */ +#endif /* WASM_ENABLE_LIBC_WASI || WASM_ENABLE_LIBC_WASI_P2 */ #if WASM_ENABLE_QUICK_AOT_ENTRY != 0 static bool @@ -527,11 +559,19 @@ wasm_native_init() goto fail; #endif /* WASM_ENABLE_SPEC_TEST */ -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI_P2 != 0 + if (!instantiate_host_resource_table()) + goto fail; +#endif + +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 g_wasi_context_key = wasm_native_create_context_key(wasi_context_dtor); if (g_wasi_context_key == NULL) { goto fail; } +#endif /* WASM_ENABLE_LIBC_WASI || WASM_ENABLE_LIBC_WASI_P2 */ + +#if WASM_ENABLE_LIBC_WASI != 0 n_native_symbols = get_libc_wasi_export_apis(&native_symbols); if (!wasm_native_register_natives("wasi_unstable", native_symbols, n_native_symbols)) @@ -621,13 +661,13 @@ wasm_native_init() #if WASM_ENABLE_QUICK_AOT_ENTRY != 0 if (!quick_aot_entry_init()) { -#if WASM_ENABLE_SPEC_TEST != 0 || WASM_ENABLE_LIBC_BUILTIN != 0 \ - || WASM_ENABLE_BASE_LIB != 0 || WASM_ENABLE_LIBC_EMCC != 0 \ - || WASM_ENABLE_LIB_RATS != 0 || WASM_ENABLE_WASI_NN != 0 \ - || WASM_ENABLE_APP_FRAMEWORK != 0 || WASM_ENABLE_LIBC_WASI != 0 \ - || WASM_ENABLE_LIB_PTHREAD != 0 || WASM_ENABLE_LIB_WASI_THREADS != 0 \ - || WASM_ENABLE_WASI_NN != 0 || WASM_ENABLE_WASI_EPHEMERAL_NN != 0 \ - || WASM_ENABLE_SHARED_HEAP != 0 +#if WASM_ENABLE_SPEC_TEST != 0 || WASM_ENABLE_LIBC_BUILTIN != 0 \ + || WASM_ENABLE_BASE_LIB != 0 || WASM_ENABLE_LIBC_EMCC != 0 \ + || WASM_ENABLE_LIB_RATS != 0 || WASM_ENABLE_WASI_NN != 0 \ + || WASM_ENABLE_APP_FRAMEWORK != 0 || WASM_ENABLE_LIBC_WASI != 0 \ + || WASM_ENABLE_LIBC_WASI_P2 != 0 || WASM_ENABLE_LIB_PTHREAD != 0 \ + || WASM_ENABLE_LIB_WASI_THREADS != 0 || WASM_ENABLE_WASI_NN != 0 \ + || WASM_ENABLE_WASI_EPHEMERAL_NN != 0 || WASM_ENABLE_SHARED_HEAP != 0 goto fail; #else return false; @@ -636,13 +676,13 @@ wasm_native_init() #endif return true; -#if WASM_ENABLE_SPEC_TEST != 0 || WASM_ENABLE_LIBC_BUILTIN != 0 \ - || WASM_ENABLE_BASE_LIB != 0 || WASM_ENABLE_LIBC_EMCC != 0 \ - || WASM_ENABLE_LIB_RATS != 0 || WASM_ENABLE_WASI_NN != 0 \ - || WASM_ENABLE_APP_FRAMEWORK != 0 || WASM_ENABLE_LIBC_WASI != 0 \ - || WASM_ENABLE_LIB_PTHREAD != 0 || WASM_ENABLE_LIB_WASI_THREADS != 0 \ - || WASM_ENABLE_WASI_NN != 0 || WASM_ENABLE_WASI_EPHEMERAL_NN != 0 \ - || WASM_ENABLE_SHARED_HEAP != 0 +#if WASM_ENABLE_SPEC_TEST != 0 || WASM_ENABLE_LIBC_BUILTIN != 0 \ + || WASM_ENABLE_BASE_LIB != 0 || WASM_ENABLE_LIBC_EMCC != 0 \ + || WASM_ENABLE_LIB_RATS != 0 || WASM_ENABLE_WASI_NN != 0 \ + || WASM_ENABLE_APP_FRAMEWORK != 0 || WASM_ENABLE_LIBC_WASI != 0 \ + || WASM_ENABLE_LIBC_WASI_P2 != 0 || WASM_ENABLE_LIB_PTHREAD != 0 \ + || WASM_ENABLE_LIB_WASI_THREADS != 0 || WASM_ENABLE_WASI_NN != 0 \ + || WASM_ENABLE_WASI_EPHEMERAL_NN != 0 || WASM_ENABLE_SHARED_HEAP != 0 fail: wasm_native_destroy(); return false; @@ -654,7 +694,12 @@ wasm_native_destroy() { NativeSymbolsNode *node, *node_next; -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI_P2 != 0 + wasi_p2_sockets_cleanup(); + destroy_host_resource_table(); +#endif + +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 if (g_wasi_context_key != NULL) { wasm_native_destroy_context_key(g_wasi_context_key); g_wasi_context_key = NULL; diff --git a/core/iwasm/common/wasm_native.h b/core/iwasm/common/wasm_native.h index 9a6afee195..53cb78a920 100644 --- a/core/iwasm/common/wasm_native.h +++ b/core/iwasm/common/wasm_native.h @@ -55,6 +55,17 @@ wasm_native_resolve_symbol(const char *module_name, const char *field_name, const char **p_signature, void **p_attachment, bool *p_call_conv_raw); +void * +wasm_native_resolve_symbol_exact(const char *module_name, + const char *field_name, + const WASMFuncType *func_type, + const char **p_signature, void **p_attachment, + bool *p_call_conv_raw); + +bool +wasm_native_validate_symbol_signature(const WASMFuncType *func_type, + const char *signature); + bool wasm_native_register_natives(const char *module_name, NativeSymbol *native_symbols, diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c index 7675df78d5..f6b3690fbe 100644 --- a/core/iwasm/common/wasm_runtime_common.c +++ b/core/iwasm/common/wasm_runtime_common.c @@ -1185,6 +1185,23 @@ wasm_runtime_find_module_registered_by_reference(WASMModuleCommon *module) return reg_module; } +static char * +clone_registered_module_name(const char *module_name, char *error_buf, + uint32 error_buf_size) +{ + uint64 module_name_size = (uint64)strlen(module_name) + 1; + char *module_name_copy = + runtime_malloc(module_name_size, NULL, error_buf, error_buf_size); + + if (!module_name_copy) { + return NULL; + } + + bh_memcpy_s(module_name_copy, (uint32)module_name_size, module_name, + (uint32)module_name_size); + return module_name_copy; +} + bool wasm_runtime_register_module_internal(const char *module_name, WASMModuleCommon *module, @@ -1216,7 +1233,14 @@ wasm_runtime_register_module_internal(const char *module_name, } else { /* module has empty name, reset it */ - node->module_name = module_name; + if (!module_name) { + return true; + } + node->module_name = clone_registered_module_name( + module_name, error_buf, error_buf_size); + if (!node->module_name) { + return false; + } return true; } } @@ -1229,8 +1253,14 @@ wasm_runtime_register_module_internal(const char *module_name, return false; } - /* share the string and the module */ - node->module_name = module_name; + /* The global registry owns the name and module. */ + node->module_name = NULL; + if (module_name + && !(node->module_name = clone_registered_module_name( + module_name, error_buf, error_buf_size))) { + wasm_runtime_free(node); + return false; + } node->module = module; node->orig_file_buf = orig_file_buf; node->orig_file_buf_size = orig_file_buf_size; @@ -1272,10 +1302,11 @@ wasm_runtime_register_module(const char *module_name, WASMModuleCommon *module, error_buf, error_buf_size); } -void -wasm_runtime_unregister_module(const WASMModuleCommon *module) +bool +wasm_runtime_unregister_module(WASMModuleCommon *module) { WASMRegisteredModule *registered_module = NULL; + bool unregistered = true; os_mutex_lock(®istered_module_list_lock); registered_module = bh_list_first_elem(registered_module_list); @@ -1285,10 +1316,21 @@ wasm_runtime_unregister_module(const WASMModuleCommon *module) /* it does not matter if it is not exist. after all, it is gone */ if (registered_module) { - bh_list_remove(registered_module_list, registered_module); - wasm_runtime_free(registered_module); + if (registered_module->orig_file_buf) { + /* Keep reader-owned modules registered so runtime_destroy() can + * release their module and backing buffer in the required order. */ + unregistered = false; + } + else { + bh_list_remove(registered_module_list, registered_module); + if (registered_module->module_name) { + wasm_runtime_free((void *)registered_module->module_name); + } + wasm_runtime_free(registered_module); + } } os_mutex_unlock(®istered_module_list_lock); + return unregistered; } WASMModuleCommon * @@ -1345,6 +1387,9 @@ wasm_runtime_destroy_registered_module_list() reg_module->orig_file_buf_size = 0; } + if (reg_module->module_name) { + wasm_runtime_free((void *)reg_module->module_name); + } wasm_runtime_free(reg_module); reg_module = next_reg_module; } @@ -1655,12 +1700,17 @@ wasm_runtime_load_from_sections(WASMSection *section_list, bool is_aot, void wasm_runtime_unload(WASMModuleCommon *module) { + if (!module) { + return; + } + #if WASM_ENABLE_MULTI_MODULE != 0 - /** - * since we will unload and free all module when runtime_destroy() - * we don't want users to unwillingly disrupt it - */ - return; + /* Registered modules are owned by the runtime and released by + * wasm_runtime_destroy(). An explicitly unregistered module is owned by + * the caller again and can be unloaded below. */ + if (wasm_runtime_find_module_registered_by_reference(module)) { + return; + } #endif #if WASM_ENABLE_INTERP != 0 @@ -1730,7 +1780,7 @@ void wasm_runtime_instantiation_args_set_defaults(struct InstantiationArgs2 *args) { memset(args, 0, sizeof(*args)); -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 wasi_args_set_defaults(&args->wasi); #endif } @@ -1806,7 +1856,7 @@ wasm_runtime_instantiation_args_set_custom_data(struct InstantiationArgs2 *p, p->custom_data = custom_data; } -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 void wasm_runtime_instantiation_args_set_wasi_arg(struct InstantiationArgs2 *p, char *argv[], int argc) @@ -1882,7 +1932,7 @@ wasm_runtime_instantiation_args_set_wasi_ns_lookup_pool( wasi_args->ns_lookup_count = ns_lookup_pool_size; wasi_args->set_by_user = true; } -#endif /* WASM_ENABLE_LIBC_WASI != 0 */ +#endif /* WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 */ WASMModuleInstanceCommon * wasm_runtime_instantiate_ex2(WASMModuleCommon *module, @@ -2329,7 +2379,7 @@ wasm_runtime_get_export_global_inst(WASMModuleInstanceCommon *const module_inst, &e->globals[wasm_export->index]; global_inst->kind = val_type_to_val_kind(global->type); global_inst->is_mutable = global->is_mutable; -#if WASM_ENABLE_MULTI_MODULE == 0 +#if WASM_ENABLE_MULTI_MODULE == 0 && WASM_ENABLE_COMPONENT_MODEL == 0 global_inst->global_data = wasm_module_inst->global_data + global->data_offset; #else @@ -2440,6 +2490,22 @@ wasm_table_get_func_inst(struct WASMModuleInstanceCommon *const module_inst, if (module_inst->module_type == Wasm_Module_Bytecode) { const WASMModuleInstance *wasm_module_inst = (const WASMModuleInstance *)module_inst; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + uint32 table_idx; + + for (table_idx = 0; table_idx < wasm_module_inst->table_count; + table_idx++) { + const WASMTableInstance *internal_table = + wasm_module_inst->tables[table_idx]; + + if (internal_table && internal_table->elems == table_inst->elems) { + if (internal_table->component_func_refs) { + return internal_table->component_func_refs[idx]; + } + break; + } + } +#endif table_elem_type_t tbl_elem_val = ((table_elem_type_t *)table_inst->elems)[idx]; if (tbl_elem_val == NULL_REF) { @@ -3631,7 +3697,7 @@ wasm_runtime_module_dup_data(WASMModuleInstanceCommon *module_inst, return 0; } -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 void wasi_args_set_defaults(WASIArguments *args) @@ -3689,7 +3755,7 @@ wasm_runtime_set_wasi_args_ex(WASMModuleCommon *module, const char *dir_list[], wasi_args->stdio[2] = (os_raw_file_handle)stderrfd; wasi_args->set_by_user = true; -#if WASM_ENABLE_MULTI_MODULE != 0 +#if WASM_ENABLE_LIBC_WASI != 0 && WASM_ENABLE_MULTI_MODULE != 0 #if WASM_ENABLE_INTERP != 0 if (module->module_type == Wasm_Module_Bytecode) { wasm_propagate_wasi_args((WASMModule *)module); @@ -4396,7 +4462,7 @@ wasm_runtime_get_wasi_exit_code(WASMModuleInstanceCommon *module_inst) #endif return wasi_ctx->exit_code; } -#endif /* end of WASM_ENABLE_LIBC_WASI */ +#endif /* WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 */ WASMModuleCommon * wasm_exec_env_get_module(WASMExecEnv *exec_env) diff --git a/core/iwasm/common/wasm_runtime_common.h b/core/iwasm/common/wasm_runtime_common.h index 4cb97eec40..c1f1423f7e 100644 --- a/core/iwasm/common/wasm_runtime_common.h +++ b/core/iwasm/common/wasm_runtime_common.h @@ -16,11 +16,12 @@ #include "gc/gc_object.h" #endif -#if WASM_ENABLE_LIBC_WASI != 0 && WASM_ENABLE_COMPONENT_MODEL != 0 +#if (WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0) \ + && WASM_ENABLE_COMPONENT_MODEL != 0 typedef struct libc_wasi_options_t libc_wasi_options_t; #endif -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 #if WASM_ENABLE_UVWASI == 0 #include "posix.h" #else @@ -557,7 +558,7 @@ typedef struct WASMModuleInstMemConsumption { uint32 exports_size; } WASMModuleInstMemConsumption; -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 #if WASM_ENABLE_UVWASI == 0 typedef struct WASIContext { struct fd_table *curfds; @@ -586,7 +587,7 @@ typedef struct WASIContext { #if WASM_ENABLE_MULTI_MODULE != 0 typedef struct WASMRegisteredModule { bh_list_link l; - /* point to a string pool */ + /* Owned in the global registry, borrowed in a parent's import list. */ const char *module_name; WASMModuleCommon *module; /* to store the original module file buffer address */ @@ -630,7 +631,7 @@ wasm_runtime_get_exec_env_tls(void); struct InstantiationArgs2 { InstantiationArgs v1; void *custom_data; -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 WASIArguments wasi; #endif }; @@ -1098,8 +1099,8 @@ wasm_runtime_register_module_internal(const char *module_name, uint32 orig_file_buf_size, char *error_buf, uint32 error_buf_size); -void -wasm_runtime_unregister_module(const WASMModuleCommon *module); +bool +wasm_runtime_unregister_module(WASMModuleCommon *module); WASMModuleCommon * wasm_runtime_find_module_registered(const char *module_name); @@ -1160,7 +1161,7 @@ wasm_exec_env_set_aux_stack(WASMExecEnv *exec_env, uint64 start_offset, uint32 size); #endif -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 WASM_RUNTIME_API_EXTERN void wasm_runtime_set_wasi_args_ex(WASMModuleCommon *module, const char *dir_list[], uint32 dir_count, const char *map_dir_list[], @@ -1228,7 +1229,7 @@ WASM_RUNTIME_API_EXTERN void wasm_runtime_set_wasi_ns_lookup_pool(wasm_module_t module, const char *ns_lookup_pool[], uint32 ns_lookup_pool_size); -#endif /* end of WASM_ENABLE_LIBC_WASI */ +#endif /* WASM_ENABLE_LIBC_WASI || WASM_ENABLE_LIBC_WASI_P2 */ #if WASM_ENABLE_GC != 0 void diff --git a/core/iwasm/common/wave-parser/wave_parser.cmake b/core/iwasm/common/wave-parser/wave_parser.cmake index e63c8e00b6..483efb058d 100644 --- a/core/iwasm/common/wave-parser/wave_parser.cmake +++ b/core/iwasm/common/wave-parser/wave_parser.cmake @@ -12,11 +12,21 @@ set(BISON_SRC ${WAVE_GEN_DIR}/wave_parser.c) set(FLEX_SRC ${WAVE_GEN_DIR}/wave_lexer.c) set(FLEX_HDR ${WAVE_GEN_DIR}/wave_lexer.h) +# --report=all and --feature=caret are diagnostic-only (a .output report and +# caret-style error display) and require Bison >= 2.6. macOS/Xcode ships GNU +# Bison 2.3, which aborts on --feature=caret, so only pass these when the host +# Bison supports them; the generated parser is byte-for-byte identical either +# way. +set(WAVE_BISON_COMPILE_FLAGS "") +if (BISON_VERSION AND NOT BISON_VERSION VERSION_LESS "2.6") + set(WAVE_BISON_COMPILE_FLAGS "--report=all --feature=caret") +endif () + BISON_TARGET(WaveParser ${CMAKE_CURRENT_LIST_DIR}/wave_parser.y ${BISON_SRC} DEFINES_FILE ${BISON_HDR} - COMPILE_FLAGS "--report=all --feature=caret" + COMPILE_FLAGS "${WAVE_BISON_COMPILE_FLAGS}" ) FLEX_TARGET(WaveScanner @@ -27,6 +37,15 @@ FLEX_TARGET(WaveScanner ADD_FLEX_BISON_DEPENDENCY(WaveScanner WaveParser) +# Flex 2.6.4 emits one signed/unsigned buffer-size comparison. Keep warnings +# as errors for handwritten runtime code while isolating that generated-code +# diagnostic to the generated scanner. +if (CMAKE_C_COMPILER_ID MATCHES "Clang|GNU") + set_source_files_properties(${FLEX_SRC} PROPERTIES + COMPILE_OPTIONS "-Wno-sign-compare" + ) +endif () + set(WAVE_PARSER_SOURCES ${BISON_WaveParser_OUTPUTS} @@ -39,4 +58,4 @@ set(WAVE_PARSER_INCLUDE_DIRS ${CMAKE_CURRENT_LIST_DIR} ) -message(STATUS "Wave Parser sources: ${WAVE_PARSER_SOURCES}") \ No newline at end of file +message(STATUS "Wave Parser sources: ${WAVE_PARSER_SOURCES}") diff --git a/core/iwasm/include/wasm_export.h b/core/iwasm/include/wasm_export.h index bc4fd5d626..ed13a1a42d 100644 --- a/core/iwasm/include/wasm_export.h +++ b/core/iwasm/include/wasm_export.h @@ -126,8 +126,14 @@ struct WASMModuleInstanceCommon; typedef struct WASMModuleInstanceCommon *wasm_module_inst_t; #if WASM_ENABLE_COMPONENT_MODEL != 0 +struct WASMComponent; +typedef struct WASMComponent WASMComponent; struct WASMComponentInstance; typedef struct WASMComponentInstance WASMComponentInstance; +struct WASMComponentPreparedCall; +typedef struct WASMComponentPreparedCall WASMComponentPreparedCall; +typedef bool (*wasm_component_host_resource_drop_callback_t)( + void *attachment, uint32_t representation); #endif /* Function instance */ @@ -600,7 +606,7 @@ WASM_RUNTIME_API_EXTERN void wasm_runtime_set_module_reader(const module_reader reader, const module_destroyer destroyer); /** - * Give the "module" a name "module_name". + * Give the "module" a copied name "module_name". * Can not assign a new name to a module if it already has a name * * @param module_name indicate a name @@ -614,6 +620,29 @@ WASM_RUNTIME_API_EXTERN bool wasm_runtime_register_module(const char *module_name, wasm_module_t module, char *error_buf, uint32_t error_buf_size); +/** + * Try to remove a module from the multi-module registry without unloading it. + * + * A registered module is owned by the runtime, so wasm_runtime_unload() does + * not release it. After this call removes the module from the registry, the + * caller owns the module again and should release it with + * wasm_runtime_unload(). Unload parent modules before dependencies that they + * reference. + * + * A dependency loaded by the configured module_reader remains runtime-owned + * and registered so its backing buffer can be released by the configured + * module_destroyer after the module is unloaded during wasm_runtime_destroy(). + * Calling this function for NULL or an unregistered module succeeds without + * taking any action. + * + * @param module the module to remove from the registry + * + * @return true if the module is no longer registered, false if it remains + * registered because it owns a module_reader buffer + */ +WASM_RUNTIME_API_EXTERN bool +wasm_runtime_unregister_module(wasm_module_t module); + /** * Check if there is already a loaded module named module_name in the * runtime. Repeatedly loading a module with the same name is not allowed. @@ -680,6 +709,11 @@ wasm_runtime_load_from_sections(wasm_section_list_t section_list, bool is_aot, /** * Unload a WASM module. * + * When the multi-module feature is enabled, this function leaves registered + * modules intact because the runtime owns them. Call + * wasm_runtime_unregister_module() first to transfer ownership back to the + * caller, and unload the module immediately only when it returns true. + * * @param module the module to be unloaded */ WASM_RUNTIME_API_EXTERN void @@ -716,7 +750,7 @@ wasm_runtime_get_module_hash(wasm_module_t module); * @param env_count The number of elements in env. * @param argv The list of command line arguments. * @param argc The number of elements in argv. - * @param stdin_handle The raw host handle to back WASI STDIN_FILENO. + * @param stdinfd The raw host handle to back WASI STDIN_FILENO. * If an invalid handle is specified (e.g. -1 on POSIX, * INVALID_HANDLE_VALUE on Windows), the platform default * for STDIN is used. @@ -1376,8 +1410,351 @@ WASM_RUNTIME_API_EXTERN const char * wasm_runtime_get_exception(wasm_module_inst_t module_inst); #if WASM_ENABLE_COMPONENT_MODEL != 0 +/** + * Load and validate a WebAssembly component from a byte buffer. + * + * The buffer must be writable and remain alive and unchanged until + * wasm_component_unload() returns. A component and all of its instances are + * confined to the thread which owns them unless an API explicitly documents + * cross-thread use. + * + * @param buf writable component binary data + * @param size size of buf in bytes + * @param load_args optional loader settings; is_component is forced to true + * @param error_buf optional buffer which receives a load error + * @param error_buf_size size of error_buf in bytes + * + * @return a validated component on success, NULL on failure + */ +WASM_RUNTIME_API_EXTERN WASMComponent * +wasm_component_load(uint8_t *buf, uint32_t size, const LoadArgs *load_args, + char *error_buf, uint32_t error_buf_size); + +/** + * Unload a component. + * + * All prepared calls must be destroyed and all component instances must be + * deinstantiated before this function is called. + */ +WASM_RUNTIME_API_EXTERN void +wasm_component_unload(WASMComponent *component); + +/** + * Register the owner-drop callback for an imported resource before component + * instantiation. + * + * Names are exact WIT interface/resource names; interface_name includes its + * version. The registration belongs to component and remains valid until + * wasm_component_unload(). During instantiation it is copied into each + * matching imported resource type before any core start/constructor can run. + * The callback's context argument is the per-instance custom_data supplied in + * InstantiationArgs2. + * + * Call this on the component's owner thread after load and before creating any + * instances. + */ +WASM_RUNTIME_API_EXTERN bool +wasm_component_register_host_resource_drop_callback( + WASMComponent *component, const char *interface_name, + const char *resource_name, + wasm_component_host_resource_drop_callback_t callback); + WASM_RUNTIME_API_EXTERN const char * wasm_component_runtime_get_exception(WASMComponentInstance *comp_inst); + +/** + * Prepare a synchronous component export for allocation-free flat calls. + * + * This resolves the export and creates its core execution environment. The + * returned handle uses the canonical ABI's flattened core signature: only + * WASM_I32, WASM_I64, WASM_F32, and WASM_F64 values are accepted. It does + * not parse WAVE text or perform WIT lifting/lowering. + * + * This compatibility entrypoint resolves a leaf function name using WAMR's + * legacy first-match lookup. Generated bindings should use + * wasm_component_prepare_export_call_qualified() so duplicate function names + * in different interfaces cannot be confused. + * + * The handle is thread-affine and non-reentrant. Prepare it on the thread + * which will call it, and keep both the component instance and that thread + * alive until wasm_component_destroy_prepared_call() returns. + * + * @param comp_inst the component instance containing the export + * @param export_name the exported component function name + * @param error_buf optional buffer which receives a preparation error + * @param error_buf_size size of error_buf in bytes + * + * @return a prepared call on success, NULL on failure + */ +WASM_RUNTIME_API_EXTERN WASMComponentPreparedCall * +wasm_component_prepare_export_call(WASMComponentInstance *comp_inst, + const char *export_name, char *error_buf, + uint32_t error_buf_size); + +/** + * Prepare a synchronous component export by its interface and function name. + * + * interface_name is matched exactly against the component's exported + * interface instance name. For a versioned interface it must contain the + * full version suffix, for example "test:project/my-interface@0.1.0". The + * lookup never falls back to an unqualified or first-matching function. + * + * The returned handle has the same lifetime, thread-affinity, signature, and + * allocation behavior as wasm_component_prepare_export_call(). + * + * @param comp_inst the component instance containing the interface export + * @param interface_name the complete exported interface instance name + * @param export_name the function name within that interface + * @param error_buf optional buffer which receives a preparation error + * @param error_buf_size size of error_buf in bytes + * + * @return a prepared call on success, NULL on failure + */ +WASM_RUNTIME_API_EXTERN WASMComponentPreparedCall * +wasm_component_prepare_export_call_qualified(WASMComponentInstance *comp_inst, + const char *interface_name, + const char *export_name, + char *error_buf, + uint32_t error_buf_size); + +/** + * Report whether this export declares a canonical post-return function. + * + * This property is available immediately after preparation, before calling + * the export. A successful call requires + * wasm_component_prepared_call_post_return() exactly when this returns true. + * Embedders which cannot safely lift guest-backed results may use this query + * to reject the prepared call before executing it. + * + * @return true when successful calls require post-return, false otherwise + */ +WASM_RUNTIME_API_EXTERN bool +wasm_component_prepared_call_requires_post_return( + const WASMComponentPreparedCall *prepared_call); + +/** + * Call a prepared component export with canonical flattened values. + * + * The argument/result counts and kinds must exactly match the prepared core + * signature. After preparation the adapter performs no heap allocation; it + * uses scratch storage owned by the prepared handle. Allocations explicitly + * performed by guest code or host imports are outside this guarantee. + * + * If the export has a canonical post-return function, a successful call + * retains guest-backed result storage. The caller must finish lifting or + * copying those results and then call + * wasm_component_prepared_call_post_return() before another call or destroy. + * + * @return true on success, false on failure; component exception is set + */ +WASM_RUNTIME_API_EXTERN bool +wasm_component_call_prepared(WASMComponentPreparedCall *prepared_call, + uint32_t num_results, wasm_val_t results[], + uint32_t num_args, const wasm_val_t args[]); + +/** + * Complete the last prepared call's canonical post-return step. + * + * This is a no-op when the export has no post-return function. It performs + * no adapter heap allocation. Call it only from the prepared handle's owning + * thread. Guest post-return code may itself allocate. + * + * @return true on success, false on failure; component exception is set + */ +WASM_RUNTIME_API_EXTERN bool +wasm_component_prepared_call_post_return( + WASMComponentPreparedCall *prepared_call); + +/** + * Destroy a prepared component call. + * + * This does not implicitly execute a pending post-return. The caller must + * complete post-return before destroying the handle. Destroy must run on the + * handle's owning thread and before its component instance is deinstantiated. + */ +WASM_RUNTIME_API_EXTERN void +wasm_component_destroy_prepared_call(WASMComponentPreparedCall *prepared_call); + +WASM_RUNTIME_API_EXTERN WASMComponentInstance * +wasm_component_instantiate_ex2(WASMComponent *component, + const struct InstantiationArgs2 *args, + char *error_buf, uint32_t error_buf_size); + +/** + * Deinstantiate a component on its owning thread. + * + * Destroy all prepared calls and join any thread executing this instance + * before deinstantiating it. + */ +WASM_RUNTIME_API_EXTERN void +wasm_component_deinstantiate(WASMComponentInstance *comp_inst); + +/** + * Request asynchronous termination of a component instance. + * + * This is the component API which may be called by a control thread while the + * instance's owning thread is executing Wasm. It recursively terminates the + * instance's defined core and nested component instances. The owner must + * return from execution and be joined before prepared calls, the instance, or + * its component buffer are destroyed. + */ +WASM_RUNTIME_API_EXTERN void +wasm_component_terminate(WASMComponentInstance *comp_inst); + +/** + * Replace the component instance's custom data on its owning thread. + * + * The update is propagated to nested/core instances and to host-resource drop + * callbacks which inherited their attachment from InstantiationArgs2. + * Attachments explicitly installed with + * wasm_component_set_host_resource_drop_callback() remain unchanged. + */ +WASM_RUNTIME_API_EXTERN void +wasm_component_set_custom_data(WASMComponentInstance *comp_inst, + void *custom_data); + +WASM_RUNTIME_API_EXTERN void * +wasm_component_get_custom_data(WASMComponentInstance *comp_inst); + +WASM_RUNTIME_API_EXTERN void * +wasm_component_get_custom_data_from_exec_env(wasm_exec_env_t exec_env); + +/** + * Report whether exec_env is currently handling a raw component host import. + * + * The result is true only for the dynamic extent of the callback on its + * calling thread. It does not require canonical memory or non-NULL custom + * data, and becomes false as soon as the callback returns. + */ +WASM_RUNTIME_API_EXTERN bool +wasm_component_exec_env_is_callback(wasm_exec_env_t exec_env); + +/** + * Create a canonical own-resource handle from a host representation. + * + * Call this only from an exact raw native component import callback. The + * interface name must include its complete version. The resource is resolved + * nominally from the current callback's component signature, so an unrelated + * resource with the same leaf name cannot be selected accidentally. + * + * Generated static bindings should call this once for each host-owned + * resource returned through the canonical flat ABI, then write out_handle to + * the corresponding result cell. The nonzero representation remains owned by + * the component until it is transferred out or its registered owner-drop + * callback runs. + * + * @return true on success; false for invalid context, names, representation, + * ambiguous nominal types, or allocation failure + */ +WASM_RUNTIME_API_EXTERN bool +wasm_component_host_resource_new(wasm_exec_env_t exec_env, + const char *interface_name, + const char *resource_name, + uint32_t representation, uint32_t *out_handle); + +/** + * Resolve a borrowed canonical resource handle to its host representation. + * + * Call this from an exact raw native component import callback for a borrow + * parameter. The function signature and fully versioned names are checked + * nominally. This does not transfer or destroy the handle. + */ +WASM_RUNTIME_API_EXTERN bool +wasm_component_host_resource_rep(wasm_exec_env_t exec_env, + const char *interface_name, + const char *resource_name, uint32_t handle, + uint32_t *out_representation); + +/** + * Consume an owned canonical resource parameter and return its representation. + * + * This is the ownership-transferring counterpart to + * wasm_component_host_resource_rep(). It fails while the handle has active + * borrows and never invokes the registered owner-drop callback. + */ +WASM_RUNTIME_API_EXTERN bool +wasm_component_host_resource_take(wasm_exec_env_t exec_env, + const char *interface_name, + const char *resource_name, uint32_t handle, + uint32_t *out_representation); + +/** + * Install the owner-drop callback for an imported component resource type. + * + * interface_name and resource_name are exact WIT names, including the + * interface version. This per-instance API overrides or installs a callback + * after instantiation; use + * wasm_component_register_host_resource_drop_callback() when core + * starts/constructors may create or drop the resource during instantiation. + * The callback is invoked on the owner thread for each owned representation, + * including during component teardown. A false result traps the drop, but the + * handle is still consumed exactly once. + * + * Resources whose complete interface was resolved through WAMR's built-in + * WASI Preview 2 implementation retain their existing resource-table fallback + * when no callback is installed. An exact statically registered wasi: import, + * like every other imported owned resource, requires a callback; omitting one + * is reported as a drop failure instead of deleting an unrelated built-in + * table entry with the same representation. + * + * @return true when at least one exact resource type was configured + */ +WASM_RUNTIME_API_EXTERN bool +wasm_component_set_host_resource_drop_callback( + WASMComponentInstance *comp_inst, const char *interface_name, + const char *resource_name, + wasm_component_host_resource_drop_callback_t callback, void *attachment); + +/** + * Validate a range in the canonical memory active for a component host + * callback. + * + * This API is only valid while synchronously handling a component host import + * on the callback thread. The exec_env must be the one supplied to that + * callback. + * + * Bounds are always checked as a wasm32 range, including when configurable + * bounds checks are disabled. Empty ranges are valid at the end of memory. + * + * @param exec_env the execution environment supplied to the host callback + * @param app_offset the wasm32 offset of the first byte + * @param size the number of bytes in the range + * @return true on success, false for an invalid callback context or range. An + * out-of-bounds range raises a WebAssembly exception. + */ +WASM_RUNTIME_API_EXTERN bool +wasm_component_validate_memory_range(wasm_exec_env_t exec_env, + uint32_t app_offset, uint32_t size); + +/** + * Validate and translate a mutable range in the canonical memory active for a + * component host callback. + * + * This performs the same complete range validation, and follows the same + * callback rules, as wasm_component_validate_memory_range(). The returned + * pointer is borrowed: it must not be retained after the callback returns or + * across a call that can re-enter WebAssembly or grow the canonical memory. + * + * @param p_native_addr output for the translated mutable address + * + * @return true on success, false for an invalid callback context, output + * pointer, or range. On failure, p_native_addr is set to NULL when it + * is non-NULL. An out-of-bounds range raises a WebAssembly exception. + */ +WASM_RUNTIME_API_EXTERN bool +wasm_component_get_memory_range(wasm_exec_env_t exec_env, uint32_t app_offset, + uint32_t size, uint8_t **p_native_addr); + +/** + * Const-qualified variant of wasm_component_get_memory_range(). + * + * The callback, lifetime, and bounds rules are identical to the mutable API. + * Const qualification limits host access only; guest memory remains mutable. + */ +WASM_RUNTIME_API_EXTERN bool +wasm_component_get_memory_range_const(wasm_exec_env_t exec_env, + uint32_t app_offset, uint32_t size, + const uint8_t **p_native_addr); #endif /** @@ -2069,7 +2446,7 @@ wasm_runtime_sum_wasm_exec_time(wasm_module_inst_t module_inst); * Return execution time in ms of a given wasm function with * func_name. If the function is not found, return 0. * - * @param module_inst the WASM module instance to profile + * @param inst the WASM module instance to profile * @param func_name could be an export name or a name in the * name section */ @@ -2094,7 +2471,7 @@ wasm_runtime_set_max_thread_num(uint32_t num); * Spawn a new exec_env, the spawned exec_env * can be used in other threads * - * @param num the original exec_env + * @param exec_env the original exec_env * * @return the spawned exec_env if success, NULL otherwise */ diff --git a/core/iwasm/interpreter/wasm.h b/core/iwasm/interpreter/wasm.h index 855b2de27d..26ba395407 100644 --- a/core/iwasm/interpreter/wasm.h +++ b/core/iwasm/interpreter/wasm.h @@ -13,7 +13,8 @@ #include "gc_export.h" #endif -#if WASM_ENABLE_LIBC_WASI != 0 && WASM_ENABLE_COMPONENT_MODEL != 0 +#if (WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0) \ + && WASM_ENABLE_COMPONENT_MODEL != 0 typedef struct libc_wasi_options_t libc_wasi_options_t; #endif @@ -687,6 +688,54 @@ typedef struct WASMImport { } u; } WASMImport; +#if WASM_ENABLE_EXCE_HANDLING != 0 && WASM_ENABLE_FAST_INTERP != 0 +/* One typed `catch N` clause inside a single try-region. The handler_pc + * points at the first opcode of the catch body in the rewritten fast- + * interp IR; the loader patches it in pass 2 of the preprocess pass. */ +typedef struct WASMFastEHCatch { + uint32 tag_index; + uint8 *handler_pc; + /* Tag-with-params payload routing (same-function dispatch only). + * When this catch matches, the throw walker copies `param_cell_num` + * 32-bit cells from the throw site's *source* slots (encoded as + * `int16` immediates after the THROW opcode in the rewritten IR) + * into these *destination* slots in the catch body's `frame_lp`, + * then sets `frame_ip = handler_pc`. The destination slots are + * allocated by the CATCH loader at preprocess time, mirroring how + * block-with-params allocate fresh `dynamic_offset` slots via + * `PUSH_OFFSET_TYPE`. NULL iff `param_cell_num == 0` (the typical + * tag-without-params shape, e.g. Porffor's empty-payload tags). + * + * Cross-function dispatch (caller's catch fires for a callee's + * throw) does NOT copy the payload: the callee's source slots + * sit in a frame that's about to be torn down by return_func. + * That gap is documented as an ignored integration test — + * `cross_function_tag_with_params` in + * crates/benchmark-core/tests/eh_correctness.rs. */ + uint32 param_cell_num; + int16 *param_dst_offsets; +} WASMFastEHCatch; + +/* One entry per same-function try-region, indexed by the uint32 immediate + * emitted after the rewritten TRY opcode. Allocated once per function at + * load time, sized by `func->exception_handler_count`. At runtime the + * dispatch loop carries one stack-allocated handle per *active* try- + * region (see frame->eh_stack); hot ops (CALL / LOAD / STORE) never + * touch this table. */ +typedef struct WASMFastEHEntry { + uint32 catch_count; + WASMFastEHCatch *catches; /* may be NULL when catch_count == 0 */ + uint8 *catch_all_pc; /* NULL if no `catch_all` clause */ + /* UINT32_MAX iff the try-region closes with `end`; otherwise the + * LEB depth from `delegate N`. */ + uint32 delegate_target_depth; + /* Rewritten-IR pc of the op immediately after the try-region's `end` + * (or `delegate`). CATCH / CATCH_ALL handlers branch here when their + * body completes; the loader patches it when the `end` is seen. */ + uint8 *end_of_region_pc; +} WASMFastEHEntry; +#endif /* WASM_ENABLE_EXCE_HANDLING && WASM_ENABLE_FAST_INTERP */ + struct WASMFunction { #if WASM_ENABLE_CUSTOM_NAME_SECTION != 0 char *field_name; @@ -727,7 +776,19 @@ struct WASMFunction { #endif #if WASM_ENABLE_EXCE_HANDLING != 0 + /* Number of `try` opcodes in this function. Populated by the loader + * during the preprocess pass (classic-interp uses this to size the + * runtime handler-pointer array stored on the value stack; fast- + * interp uses it to size `exception_handlers[]` below). */ uint32 exception_handler_count; +#if WASM_ENABLE_FAST_INTERP != 0 + /* Per-function table of try-regions in source order, length + * `exception_handler_count`. Allocated and populated in pass 2 of + * the fast-interp preprocess pass; the uint32 immediate emitted + * after the rewritten TRY opcode is the index into this array. + * NULL iff `exception_handler_count == 0`. */ + WASMFastEHEntry *exception_handlers; +#endif #endif #if WASM_ENABLE_FAST_JIT != 0 || WASM_ENABLE_JIT != 0 \ @@ -838,7 +899,7 @@ typedef struct BlockAddr { uint8 *end_addr; } BlockAddr; -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 typedef struct WASIArguments { const char **dir_list; uint32 dir_count; @@ -1018,7 +1079,7 @@ struct WASMModule { bh_list *br_table_cache_list; #endif -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 WASIArguments wasi_args; bool import_wasi_api; #endif diff --git a/core/iwasm/interpreter/wasm_interp.h b/core/iwasm/interpreter/wasm_interp.h index 1416405460..8ca6fe5f23 100644 --- a/core/iwasm/interpreter/wasm_interp.h +++ b/core/iwasm/interpreter/wasm_interp.h @@ -40,6 +40,17 @@ typedef struct WASMInterpFrame { */ bool exception_raised; uint32 tag_index; +#if WASM_ENABLE_FAST_INTERP != 0 + /* Number of *currently-active* try-regions on this frame's eh- + * stack. The stack itself lives in the trailing cells of the + * frame's operand[] block — see call_func_from_entry in + * wasm_interp_fast.c where all_cell_num is grown by + * `exception_handler_count` cells per frame. Read+written only by + * the WASM_OP_TRY / CATCH / CATCH_ALL / END / THROW handlers; the + * hot ops (CALL / LOAD / STORE) never touch it, so this field + * stays cold and clusters with exception_raised/tag_index above. */ + uint32 eh_count; +#endif #endif #if WASM_ENABLE_FAST_INTERP != 0 diff --git a/core/iwasm/interpreter/wasm_interp_classic.c b/core/iwasm/interpreter/wasm_interp_classic.c index 95ebc5aa65..715b7b2710 100644 --- a/core/iwasm/interpreter/wasm_interp_classic.c +++ b/core/iwasm/interpreter/wasm_interp_classic.c @@ -1222,6 +1222,17 @@ wasm_interp_call_func_native(WASMModuleInstance *module_inst, void *native_func_pointer = NULL; char buf[128]; bool ret; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + WASMComponentInstance *saved_component_inst = NULL; + WASMModuleInstanceCommon *saved_raw_module_inst = NULL; + WASMFunctionInstance *saved_raw_core_func = NULL; + WASMMemoryInstance *saved_raw_memory = NULL; + LiftLowerContext *saved_raw_cx = NULL; + void *saved_raw_attachment = NULL; + bool saved_component_callback_active = false; + LiftLowerContext raw_cx = { 0 }; + bool component_raw_call = false; +#endif #if WASM_ENABLE_GC != 0 WASMFuncType *func_type; uint8 *frame_ref; @@ -1281,7 +1292,50 @@ wasm_interp_call_func_native(WASMModuleInstance *module_inst, } #if WASM_ENABLE_COMPONENT_MODEL != 0 - if (cur_func->canon_options && cur_func->component_function) { + component_raw_call = + func_import->call_conv_raw && module_inst->comp_instance != NULL; + if (component_raw_call) { + CanonicalOptions *raw_canon_options = NULL; + WASMMemoryInstance *raw_memory = NULL; + + saved_component_inst = exec_env->component_inst; + saved_raw_core_func = exec_env->core_func; + saved_raw_memory = exec_env->memory; + saved_raw_cx = exec_env->cx; + saved_raw_attachment = exec_env->attachment; + saved_component_callback_active = exec_env->component_callback_active; + /* Resource handles belong to the component that owns the calling + * core instance. Custom-data lookup performs its own root walk. Keep + * only the persistent component-owned core function in the exec env; + * cur_func may be a synchronous call-indirect proxy on this stack. */ + exec_env->component_inst = module_inst->comp_instance; + exec_env->core_func = cur_func->component_function + ? cur_func->component_function->core_func + : NULL; + raw_canon_options = + exec_env->core_func && exec_env->core_func->canon_options + ? exec_env->core_func->canon_options + : cur_func->canon_options; + if (raw_canon_options && raw_canon_options->lift_lower_opts + && raw_canon_options->lift_lower_opts->lift_opts) { + raw_memory = raw_canon_options->lift_lower_opts->lift_opts->memory; + } + raw_cx.canonical_opts = raw_canon_options; + raw_cx.inst = module_inst->comp_instance; + raw_cx.borrow_scope_type = BORROW_SCOPE_NONE; + raw_cx.borrow_scope.task = NULL; + exec_env->memory = raw_memory; + exec_env->cx = &raw_cx; + exec_env->component_callback_active = true; + if (raw_memory && raw_memory->module_instance) { + saved_raw_module_inst = wasm_runtime_get_module_inst(exec_env); + wasm_exec_env_set_module_inst( + exec_env, + (WASMModuleInstanceCommon *)raw_memory->module_instance); + } + } + if (cur_func->canon_options && cur_func->component_function + && !func_import->call_conv_raw) { ret = wasm_runtime_invoke_native_p2(exec_env, cur_func, frame->lp, cur_func->param_cell_num, argv_ret); } @@ -1310,6 +1364,20 @@ wasm_interp_call_func_native(WASMModuleInstance *module_inst, cur_func->param_cell_num, argv_ret); } +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (component_raw_call) { + if (saved_raw_module_inst) { + wasm_exec_env_restore_module_inst(exec_env, saved_raw_module_inst); + } + exec_env->attachment = saved_raw_attachment; + exec_env->cx = saved_raw_cx; + exec_env->memory = saved_raw_memory; + exec_env->core_func = saved_raw_core_func; + exec_env->component_inst = saved_component_inst; + exec_env->component_callback_active = saved_component_callback_active; + } +#endif + if (!ret) return; @@ -1370,8 +1438,17 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst, WASMFunctionInstance *cur_func, WASMInterpFrame *prev_frame) { + WASMFunctionImport *func_import = cur_func->u.func_import; + #if WASM_ENABLE_COMPONENT_MODEL != 0 - if (cur_func->canon_options) { + /* Raw and synthetic host imports already receive the canonical core ABI + * cells. They must follow the prelinked import chain to the native + * dispatcher rather than re-entering component-to-component adaptation. + * A real component callee always has a core module instance. */ + if (cur_func->canon_options && !func_import->call_conv_raw + && cur_func->component_function + && cur_func->component_function->core_func + && cur_func->component_function->core_func->module_instance) { // Lower opts (caller, C1) CanonicalOptions *lower_opts = cur_func->canon_options; @@ -1789,9 +1866,9 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst, #endif WASMModuleInstance *sub_module_inst = cur_func->import_module_inst; WASMFunctionInstance *sub_func_inst = cur_func->import_func_inst; - WASMFunctionImport *func_import = cur_func->u.func_import; uint8 *ip = prev_frame->ip; char buf[128]; + uint32 import_depth = 0; WASMExecEnv *sub_module_exec_env = NULL; uintptr_t aux_stack_origin_boundary = 0; uintptr_t aux_stack_origin_bottom = 0; @@ -1812,6 +1889,19 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst, return; } + /* Resolve component import-to-import hops before entering another + * interpreter loop. Reusing the same caller frame for a nested import + * entry corrupts loop recovery; a final native import must still execute + * with its defining module selected for memory/component context. */ + while (sub_func_inst->is_import_func && sub_func_inst->import_func_inst) { + if (++import_depth > 1024 || !sub_func_inst->import_module_inst) { + wasm_set_exception(module_inst, "cyclic component function import"); + return; + } + sub_module_inst = sub_func_inst->import_module_inst; + sub_func_inst = sub_func_inst->import_func_inst; + } + /* Switch exec_env but keep using the same one by replacing necessary * variables */ sub_module_exec_env = wasm_runtime_get_exec_env_singleton( @@ -1835,9 +1925,16 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst, this function */ prev_frame->ip = NULL; - /* call function of sub-module*/ - wasm_interp_call_func_bytecode(sub_module_inst, exec_env, sub_func_inst, - prev_frame); + /* Call the resolved function without another import-to-import bytecode + entry. */ + if (sub_func_inst->is_import_func) { + wasm_interp_call_func_native(sub_module_inst, exec_env, sub_func_inst, + prev_frame); + } + else { + wasm_interp_call_func_bytecode(sub_module_inst, exec_env, sub_func_inst, + prev_frame); + } /* restore ip and other replaced */ prev_frame->ip = ip; @@ -1978,7 +2075,7 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst, static inline uint8 * get_global_addr(uint8 *global_data, WASMGlobalInstance *global) { -#if WASM_ENABLE_MULTI_MODULE == 0 +#if WASM_ENABLE_MULTI_MODULE == 0 && WASM_ENABLE_COMPONENT_MODEL == 0 return global_data + global->data_offset; #else return global->import_global_inst @@ -2020,6 +2117,10 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, #endif WASMFuncType **wasm_types = (WASMFuncType **)module->module->types; WASMGlobalInstance *globals = module->e->globals, *global; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + WASMFunctionInstance table_func_proxy; + WASMFunctionImport table_func_proxy_import; +#endif uint8 *global_data = module->global_data; uint8 opcode_IMPDEP = WASM_OP_IMPDEP; WASMInterpFrame *frame = NULL; @@ -2847,39 +2948,52 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, /* clang-format off */ #if WASM_ENABLE_COMPONENT_MODEL != 0 - if (tbl_inst->elem_type == VALUE_TYPE_EXTERNREF) { - cur_func = (WASMFunctionInstance *) tbl_inst->elems[val]; - goto call_func_from_interp; + if (tbl_inst->component_func_refs) { + WASMFunctionInstance *source = + tbl_inst->component_func_refs[val]; + if (!source) { + wasm_set_exception(module, "uninitialized element"); + goto got_exception; + } + if (source->module_instance == module) { + cur_func = source; + } + else if (!wasm_component_build_table_func_proxy( + module, source, &table_func_proxy, + &table_func_proxy_import)) { + wasm_set_exception(module, "unknown function"); + goto got_exception; + } + else { + cur_func = &table_func_proxy; + } } + else #endif + { #if WASM_ENABLE_GC == 0 - fidx = tbl_inst->elems[val]; - if (fidx == (uint32)-1) { - wasm_set_exception(module, "uninitialized element"); - goto got_exception; - } + fidx = tbl_inst->elems[val]; + if (fidx == (uint32)-1) { + wasm_set_exception(module, "uninitialized element"); + goto got_exception; + } #else - func_obj = (WASMFuncObjectRef)tbl_inst->elems[val]; - if (!func_obj) { - wasm_set_exception(module, "uninitialized element"); - goto got_exception; - } - fidx = wasm_func_obj_get_func_idx_bound(func_obj); + func_obj = (WASMFuncObjectRef)tbl_inst->elems[val]; + if (!func_obj) { + wasm_set_exception(module, "uninitialized element"); + goto got_exception; + } + fidx = wasm_func_obj_get_func_idx_bound(func_obj); #endif - /* clang-format on */ + if (fidx >= module->e->function_count) { + wasm_set_exception(module, "unknown function"); + goto got_exception; + } - /* - * we might be using a table injected by host or - * another module. In that case, we don't validate - * the elem value while loading - */ - if (fidx >= module->e->function_count) { - wasm_set_exception(module, "unknown function"); - goto got_exception; + /* Non-imported tables keep module-relative indexes. */ + cur_func = module->e->functions + fidx; } - - /* always call module own functions */ - cur_func = module->e->functions + fidx; + /* clang-format on */ if (cur_func->is_import_func) cur_func_type = cur_func->u.func_import->func_type; @@ -2888,7 +3002,10 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, /* clang-format off */ #if WASM_ENABLE_GC == 0 - if (cur_type != cur_func_type) { + if (!wasm_type_equal((WASMType *)cur_type, + (WASMType *)cur_func_type, + module->module->types, + module->module->type_count)) { wasm_set_exception(module, "indirect call type mismatch"); goto got_exception; } @@ -3010,6 +3127,14 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, goto got_exception; } +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (!wasm_component_table_get_is_local(module, tbl_inst, + (uint32)elem_idx)) { + wasm_set_exception(module, "foreign function reference in " + "table.get"); + goto got_exception; + } +#endif #if WASM_ENABLE_GC == 0 PUSH_I32(tbl_inst->elems[elem_idx]); #else @@ -3045,6 +3170,23 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, } tbl_inst->elems[elem_idx] = elem_val; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (tbl_inst->component_func_refs) { +#if WASM_ENABLE_GC == 0 + fidx = (uint32)elem_val; +#else + fidx = elem_val == NULL_REF + ? UINT32_MAX + : wasm_func_obj_get_func_idx_bound( + (WASMFuncObjectRef)elem_val); +#endif + if (!wasm_component_set_table_func_ref( + module, tbl_inst, (uint32)elem_idx, fidx)) { + wasm_set_exception(module, "unknown function"); + goto got_exception; + } + } +#endif HANDLE_OP_END(); } @@ -6443,6 +6585,14 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, else { table_elems[i] = NULL_REF; } +#endif +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (!wasm_component_set_table_func_ref( + module, tbl_inst, (uint32)d + (uint32)i, + init_values[i].u.unary.v.ref_index)) { + wasm_set_exception(module, "unknown function"); + goto got_exception; + } #endif } break; @@ -6501,6 +6651,16 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, goto got_exception; } +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (!wasm_component_prepare_table_copy( + module, dst_tbl_inst, (uint32)d, src_tbl_inst, + (uint32)s, (uint32)n)) { + wasm_set_exception( + module, + "foreign function reference in table.copy"); + goto got_exception; + } +#endif /* if s >= d, copy from front to back */ /* if s < d, copy from back to front */ /* merge all together */ @@ -6605,6 +6765,21 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, for (; n != 0; elem_idx++, n--) { tbl_inst->elems[elem_idx] = fill_val; +#if WASM_ENABLE_COMPONENT_MODEL != 0 +#if WASM_ENABLE_GC == 0 + fidx = (uint32)fill_val; +#else + fidx = fill_val == NULL_REF + ? UINT32_MAX + : wasm_func_obj_get_func_idx_bound( + (WASMFuncObjectRef)fill_val); +#endif + if (!wasm_component_set_table_func_ref( + module, tbl_inst, (uint32)elem_idx, fidx)) { + wasm_set_exception(module, "unknown function"); + goto got_exception; + } +#endif } break; } diff --git a/core/iwasm/interpreter/wasm_interp_fast.c b/core/iwasm/interpreter/wasm_interp_fast.c index c3ff5fec18..af71a89043 100644 --- a/core/iwasm/interpreter/wasm_interp_fast.c +++ b/core/iwasm/interpreter/wasm_interp_fast.c @@ -23,6 +23,16 @@ #if WASM_ENABLE_SIMDE != 0 #include "simde/wasm/simd128.h" +#if WASM_ENABLE_RELAXED_SIMD != 0 +/* SIMDe ships relaxed-SIMD intrinsics in a separate header — pull + * them in only when the cmake flag asks for it so legacy-SIMD-only + * builds don't drag in extra inline definitions. The header + * itself is self-contained (depends on simd128.h above) and + * provides 17 of the 20 relaxed-SIMD ops; q15mulr_s and the two + * i8x16_i7x16 dot variants are hand-written in the dispatch + * loop. */ +#include "simde/wasm/relaxed-simd.h" +#endif #endif #if WASM_ENABLE_COMPONENT_MODEL != 0 #include "../common/component-model/wasm_component_runtime.h" @@ -33,6 +43,15 @@ #include "../common/component-model/wasm_component_canon.h" #endif +/* MSVC has no `__builtin_expect`; the cold-path hints below are + * GCC/Clang only. Provide a no-op fallback so the loop still + * compiles on the Windows MSVC build. Branch-predictor hints are + * an optimization, not correctness, so dropping them on MSVC is + * fine. */ +#if !defined(__GNUC__) && !defined(__clang__) +#define __builtin_expect(expr, expected) (expr) +#endif + typedef int32 CellType_I32; typedef int64 CellType_I64; typedef float32 CellType_F32; @@ -110,6 +129,55 @@ typedef float64 CellType_F64; #define CHECK_INSTRUCTION_LIMIT() (void)0 #endif +#if WASM_ENABLE_EXCE_HANDLING != 0 +/* Per-frame eh-stack entries are 2 cells wide. Cell 0 packs the index + * into `func->exception_handlers[]` (low 31 bits) and a state bit + * (top bit): clear when the try-region's handler is *in scope* (TRY + * state — a throw matching one of its catches will dispatch into the + * handler), set once the throw walker has selected one of its + * handlers (CATCH state — further throws raised from inside that + * handler skip the entry and propagate outward). Cell 1 holds the + * wasm tag index of the exception currently being handled (written + * by the throw walker on dispatch; read by RETHROW). The tag is + * undefined while the entry is in TRY state. */ +#define EH_TRY_CATCH_STATE_BIT 0x80000000u +#define EH_ENTRY_CELLS 2 + +/* Base of the per-frame eh-stack, in cells from frame_lp. + * + * Frame setup (see the call-into-wasm-function path) reserves the + * eh-stack region *after* the locals + value stack and, in GC builds, + * *after* the frame_ref root bitmap as well. The accumulation order in + * `all_cell_num` is: + * + * locals + value stack : cell_num_of_local_stack cells + * [GC only] frame_ref : (cell_num_of_local_stack + 3) / 4 cells + * eh-stack : exception_handler_count * EH_ENTRY_CELLS + * + * where cell_num_of_local_stack = param_cell_num + local_cell_num + * + max_stack_cell_num. The runtime pointer must skip the same regions. + * In non-GC builds the frame_ref bitmap doesn't exist, so the offset + * collapses to cell_num_of_local_stack — leaving non-GC behavior byte- + * for-byte identical. In GC builds, omitting the bitmap term made the + * eh-stack alias frame->frame_ref (both start at + * frame_lp + cell_num_of_local_stack), so WASM_OP_TRY corrupted GC + * roots; adding it lands the eh-stack in its reserved trailing cells. */ +#if WASM_ENABLE_GC != 0 +#define EH_FRAME_REF_CELLS(cur_func, cur_wasm_func) \ + ((((cur_func)->param_cell_num + (cur_func)->local_cell_num \ + + (cur_wasm_func)->max_stack_cell_num) \ + + 3) \ + / 4) +#else +#define EH_FRAME_REF_CELLS(cur_func, cur_wasm_func) 0 +#endif + +#define EH_STACK_BASE(frame_lp, cur_func, cur_wasm_func) \ + ((frame_lp) + (cur_func)->param_cell_num + (cur_func)->local_cell_num \ + + (cur_wasm_func)->max_stack_cell_num \ + + EH_FRAME_REF_CELLS(cur_func, cur_wasm_func)) +#endif + static inline uint32 rotl32(uint32 n, uint32 c) { @@ -1212,6 +1280,17 @@ wasm_interp_call_func_native(WASMModuleInstance *module_inst, uint32 argv_ret[2], cur_func_index; void *native_func_pointer = NULL; bool ret; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + WASMComponentInstance *saved_component_inst = NULL; + WASMModuleInstanceCommon *saved_raw_module_inst = NULL; + WASMFunctionInstance *saved_raw_core_func = NULL; + WASMMemoryInstance *saved_raw_memory = NULL; + LiftLowerContext *saved_raw_cx = NULL; + void *saved_raw_attachment = NULL; + bool saved_component_callback_active = false; + LiftLowerContext raw_cx = { 0 }; + bool component_raw_call = false; +#endif #if WASM_ENABLE_GC != 0 WASMFuncType *func_type; uint8 *frame_ref; @@ -1271,7 +1350,50 @@ wasm_interp_call_func_native(WASMModuleInstance *module_inst, } #if WASM_ENABLE_COMPONENT_MODEL != 0 - if (cur_func->canon_options && cur_func->component_function) { + component_raw_call = + func_import->call_conv_raw && module_inst->comp_instance != NULL; + if (component_raw_call) { + CanonicalOptions *raw_canon_options = NULL; + WASMMemoryInstance *raw_memory = NULL; + + saved_component_inst = exec_env->component_inst; + saved_raw_core_func = exec_env->core_func; + saved_raw_memory = exec_env->memory; + saved_raw_cx = exec_env->cx; + saved_raw_attachment = exec_env->attachment; + saved_component_callback_active = exec_env->component_callback_active; + /* Resource handles belong to the component that owns the calling + * core instance. Custom-data lookup performs its own root walk. Keep + * only the persistent component-owned core function in the exec env; + * cur_func may be a synchronous call-indirect proxy on this stack. */ + exec_env->component_inst = module_inst->comp_instance; + exec_env->core_func = cur_func->component_function + ? cur_func->component_function->core_func + : NULL; + raw_canon_options = + exec_env->core_func && exec_env->core_func->canon_options + ? exec_env->core_func->canon_options + : cur_func->canon_options; + if (raw_canon_options && raw_canon_options->lift_lower_opts + && raw_canon_options->lift_lower_opts->lift_opts) { + raw_memory = raw_canon_options->lift_lower_opts->lift_opts->memory; + } + raw_cx.canonical_opts = raw_canon_options; + raw_cx.inst = module_inst->comp_instance; + raw_cx.borrow_scope_type = BORROW_SCOPE_NONE; + raw_cx.borrow_scope.task = NULL; + exec_env->memory = raw_memory; + exec_env->cx = &raw_cx; + exec_env->component_callback_active = true; + if (raw_memory && raw_memory->module_instance) { + saved_raw_module_inst = wasm_runtime_get_module_inst(exec_env); + wasm_exec_env_set_module_inst( + exec_env, + (WASMModuleInstanceCommon *)raw_memory->module_instance); + } + } + if (cur_func->canon_options && cur_func->component_function + && !func_import->call_conv_raw) { ret = wasm_runtime_invoke_native_p2(exec_env, cur_func, frame->lp, cur_func->param_cell_num, argv_ret); } @@ -1300,6 +1422,20 @@ wasm_interp_call_func_native(WASMModuleInstance *module_inst, cur_func->param_cell_num, argv_ret); } +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (component_raw_call) { + if (saved_raw_module_inst) { + wasm_exec_env_restore_module_inst(exec_env, saved_raw_module_inst); + } + exec_env->attachment = saved_raw_attachment; + exec_env->cx = saved_raw_cx; + exec_env->memory = saved_raw_memory; + exec_env->core_func = saved_raw_core_func; + exec_env->component_inst = saved_component_inst; + exec_env->component_callback_active = saved_component_callback_active; + } +#endif + if (!ret) return; @@ -1347,12 +1483,20 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst, WASMFunctionImport *func_import = cur_func->u.func_import; uint8 *ip = prev_frame->ip; char buf[128]; + uint32 import_depth = 0; WASMExecEnv *sub_module_exec_env = NULL; uintptr_t aux_stack_origin_boundary = 0; uintptr_t aux_stack_origin_bottom = 0; #if WASM_ENABLE_COMPONENT_MODEL != 0 - if (cur_func->canon_options) { + /* Raw and synthetic host imports already receive the canonical core ABI + * cells. They must follow the prelinked import chain to the native + * dispatcher rather than re-entering component-to-component adaptation. + * A real component callee always has a core module instance. */ + if (cur_func->canon_options && !func_import->call_conv_raw + && cur_func->component_function + && cur_func->component_function->core_func + && cur_func->component_function->core_func->module_instance) { // Lower opts (caller, C1) CanonicalOptions *lower_opts = cur_func->canon_options; @@ -1769,6 +1913,22 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst, return; } + /* A component table can hold a function imported by the core instance + * that initialized the table. Resolve such import-to-import hops here + * instead of recursively entering the bytecode loop with the same caller + * frame: that loop recovery assumes one import boundary and otherwise + * resumes through the wrong frame. Keep the final importing module for a + * native target so its memory and component context remain authoritative. + */ + while (sub_func_inst->is_import_func && sub_func_inst->import_func_inst) { + if (++import_depth > 1024 || !sub_func_inst->import_module_inst) { + wasm_set_exception(module_inst, "cyclic component function import"); + return; + } + sub_module_inst = sub_func_inst->import_module_inst; + sub_func_inst = sub_func_inst->import_func_inst; + } + /* Switch exec_env but keep using the same one by replacing necessary * variables */ sub_module_exec_env = wasm_runtime_get_exec_env_singleton( @@ -1792,9 +1952,16 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst, this function */ prev_frame->ip = NULL; - /* call function of sub-module*/ - wasm_interp_call_func_bytecode(sub_module_inst, exec_env, sub_func_inst, - prev_frame); + /* Call the resolved function without another import-to-import bytecode + entry. Native imports still run with their defining module selected. */ + if (sub_func_inst->is_import_func) { + wasm_interp_call_func_native(sub_module_inst, exec_env, sub_func_inst, + prev_frame); + } + else { + wasm_interp_call_func_bytecode(sub_module_inst, exec_env, sub_func_inst, + prev_frame); + } /* restore ip and other replaced */ prev_frame->ip = ip; @@ -1909,7 +2076,7 @@ static void **global_handle_table; static inline uint8 * get_global_addr(uint8 *global_data, WASMGlobalInstance *global) { -#if WASM_ENABLE_MULTI_MODULE == 0 +#if WASM_ENABLE_MULTI_MODULE == 0 && WASM_ENABLE_COMPONENT_MODEL == 0 return global_data + global->data_offset; #else return global->import_global_inst @@ -1939,6 +2106,10 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, #endif WASMGlobalInstance *globals = module->e ? module->e->globals : NULL; WASMGlobalInstance *global; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + WASMFunctionInstance table_func_proxy; + WASMFunctionImport table_func_proxy_import; +#endif uint8 *global_data = module->global_data; uint8 opcode_IMPDEP = WASM_OP_IMPDEP; WASMInterpFrame *frame = NULL; @@ -1965,6 +2136,30 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, uint8 *maddr = NULL; uint32 local_idx, local_offset, global_idx; uint8 opcode = 0, local_type, *global_addr; +#if WASM_ENABLE_EXCE_HANDLING != 0 + /* Carries the wasm tag index from WASM_OP_THROW to the + * find_a_catch_handler label, and from a callee's return through + * frame->tag_index back to a caller-side find_a_catch_handler. + * Cold path only — the dispatch loop's hot ops never reference + * this variable, so the compiler is free to spill it. */ + uint32 exception_tag_index = 0; + /* Tag-with-params payload routing for same-function dispatch. + * Read off the IR after THROW's tag_index immediate; + * `throw_src_offsets` points at the first src-slot int16 in the + * rewritten IR, and `throw_param_cell_num` is the total cell + * count across all of the tag's params. find_a_catch_handler + * uses these to copy frame_lp[src[i]] into the matched catch's + * pre-allocated dst slots. Both are cold-path-only — like + * exception_tag_index, the dispatch loop's hot ops never + * reference them. RETHROW re-points throw_src_offsets at the + * still-alive catch's `param_dst_offsets` (the original + * payload values, unchanged by the catch body since they live + * in a different slot range from locals) so the re-raised + * exception carries the same payload across outer try-regions + * in this frame. */ + uint32 throw_param_cell_num = 0; + int16 *throw_src_offsets = NULL; +#endif #if WASM_ENABLE_INSTRUCTION_METERING != 0 int instructions_left = -1; @@ -2201,46 +2396,82 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, val = GET_OPERAND(uint32, I32, 0); frame_ip += 2; - if ((uint32)val >= tbl_inst->cur_size) { + /* Bounds / null / type-mismatch checks below are + * structurally cold paths — well-formed wasm modules + * pass them on every dispatched CALL_INDIRECT. Marking + * them `__builtin_expect(cond, 0)` lets the compiler + * (a) hint the branch predictor with a static-bias + * fallback for unseen call sites, and (b) lay out the + * error-handling tail away from the hot path so each + * fall-through case stays in one straight-line I-cache + * line. Apple Silicon E-cores (Icestorm, iPhone 12) + * showed ~27 % `Discarded` (bad-spec / mispredict) + * on the AS variant of graphql-validation under + * fast-interp, where megamorphic vtable dispatch + * hits CALL_INDIRECT thousands of times; the layout + * hint matters more than the branch hint on Apple's + * sophisticated predictor. PMU bucket shares stay + * within run-to-run noise on both Porffor and AS + * graphql-validation workloads, so the change is + * documentation-as-code more than a speedup — + * keep it because the cold-path semantic is real + * and the cost is zero. */ + if (__builtin_expect((uint32)val >= tbl_inst->cur_size, 0)) { wasm_set_exception(module, "undefined element"); goto got_exception; } /* clang-format off */ #if WASM_ENABLE_COMPONENT_MODEL != 0 - if (tbl_inst->elem_type == VALUE_TYPE_EXTERNREF) { - cur_func = (WASMFunctionInstance *) tbl_inst->elems[val]; - goto call_func_from_interp; + if (tbl_inst->component_func_refs) { + WASMFunctionInstance *source = + tbl_inst->component_func_refs[val]; + if (__builtin_expect(!source, 0)) { + wasm_set_exception(module, "uninitialized element"); + goto got_exception; + } + if (source->module_instance == module) { + cur_func = source; + } + else if (__builtin_expect( + !wasm_component_build_table_func_proxy( + module, source, &table_func_proxy, + &table_func_proxy_import), + 0)) { + wasm_set_exception(module, "unknown function"); + goto got_exception; + } + else { + cur_func = &table_func_proxy; + } } + else #endif + { #if WASM_ENABLE_GC == 0 - fidx = (uint32)tbl_inst->elems[val]; - if (fidx == (uint32)-1) { - wasm_set_exception(module, "uninitialized element"); - goto got_exception; - } + fidx = (uint32)tbl_inst->elems[val]; + if (__builtin_expect(fidx == (uint32)-1, 0)) { + wasm_set_exception(module, "uninitialized element"); + goto got_exception; + } #else - func_obj = (WASMFuncObjectRef)tbl_inst->elems[val]; - if (!func_obj) { - wasm_set_exception(module, "uninitialized element"); - goto got_exception; - } - fidx = wasm_func_obj_get_func_idx_bound(func_obj); + func_obj = (WASMFuncObjectRef)tbl_inst->elems[val]; + if (__builtin_expect(!func_obj, 0)) { + wasm_set_exception(module, "uninitialized element"); + goto got_exception; + } + fidx = wasm_func_obj_get_func_idx_bound(func_obj); #endif - /* clang-format on */ + if (__builtin_expect( + fidx >= module->e->function_count, 0)) { + wasm_set_exception(module, "unknown function"); + goto got_exception; + } - /* - * we might be using a table injected by host or - * another module. in that case, we don't validate - * the elem value while loading - */ - if (fidx >= module->e->function_count) { - wasm_set_exception(module, "unknown function"); - goto got_exception; + /* Non-imported tables keep module-relative indexes. */ + cur_func = module->e->functions + fidx; } - - /* always call module own functions */ - cur_func = module->e->functions + fidx; + /* clang-format on */ if (cur_func->is_import_func) cur_func_type = cur_func->u.func_import->func_type; @@ -2249,12 +2480,19 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, /* clang-format off */ #if WASM_ENABLE_GC == 0 - if (cur_type != cur_func_type) { + if (__builtin_expect( + !wasm_type_equal((WASMType *)cur_type, + (WASMType *)cur_func_type, + module->module->types, + module->module->type_count), + 0)) { wasm_set_exception(module, "indirect call type mismatch"); goto got_exception; } #else - if (!wasm_func_type_is_super_of(cur_type, cur_func_type)) { + if (__builtin_expect( + !wasm_func_type_is_super_of(cur_type, cur_func_type), + 0)) { wasm_set_exception(module, "indirect call type mismatch"); goto got_exception; } @@ -2269,14 +2507,406 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, } #if WASM_ENABLE_EXCE_HANDLING != 0 + HANDLE_OP(WASM_OP_THROW) + { + /* Loader emits + * `WASM_OP_THROW + * + * ... + * `. Read the tag plus payload-source + * metadata, then walk the eh-stack in find_a_catch_handler to + * find a matching catch — first in this frame, then via + * return_func's hook in caller frames, and finally + * falling out to the host via got_exception when no + * match is found anywhere. + * + * Payload routing: when a same-function catch matches, + * find_a_catch_handler copies frame_lp[src[i]] into + * the catch's pre-allocated dst slots (recorded on + * `WASMFastEHCatch.param_dst_offsets` at load time). + * For tag-without-params (the typical Porffor shape), + * `throw_param_cell_num == 0` makes the copy a no-op. + * For cross-function dispatch the source frame is + * torn down before the caller's walker runs, so the + * payload is dropped — this gap is documented in + * AGENTS.md and exercised as + * `cross_function_tag_with_params` (#[ignore]). */ + exception_tag_index = read_uint32(frame_ip); + throw_param_cell_num = read_uint32(frame_ip); + throw_src_offsets = (int16 *)frame_ip; + frame_ip += sizeof(int16) * throw_param_cell_num; + goto find_a_catch_handler; + } + + find_a_catch_handler: + { + /* The eh-stack lives in the trailing cells of + * frame->operand[] (see call_func_from_entry and the + * runtime push from WASM_OP_TRY). Each entry packs the + * eh-table index into the low 31 bits; the top bit + * (EH_TRY_CATCH_STATE_BIT) is set on entries whose + * catch handler is *already running* — those are + * skipped here so a throw raised from inside a catch + * body propagates outward rather than re-entering the + * same handler. + * + * Cost shape: the walk runs only on the throw path + * (cold). CALL / LOAD / STORE handlers are untouched, + * and the eh-stack cells share a cache line with the + * value stack they're allocated next to, so the walk + * hits warm memory. + * + * Known limitation in this patch: try-regions with a + * non-void result-type are *not yet supported* by the + * normal-flow path. The fix is a loader-side + * try-body→block-dynamic-offset COPY emit at CATCH + * processing time (mirrors how WASM_OP_ELSE aligns + * the if-body's result via reserve_block_ret). See the + * AGENTS.md "Open follow-up — WAMR fast-interp legacy + * exception handling" section for the architectural + * note. The throw → catch dispatch implemented here + * still works correctly for void-result try-regions + * (which is what graphql-validation-porf-accurate's + * single try-block is). */ + WASMFunction *cur_wasm_func = cur_func->u.func; + uint32 *eh_stack = EH_STACK_BASE(frame_lp, cur_func, cur_wasm_func); + uint32 i; + for (i = frame->eh_count; i > 0; i--) { + uint32 *cells = eh_stack + (i - 1) * EH_ENTRY_CELLS; + uint32 packed = cells[0]; + uint32 eh_idx; + WASMFastEHEntry *entry; + uint32 j; + if (packed & EH_TRY_CATCH_STATE_BIT) + continue; /* in-progress catch — skip */ + eh_idx = packed & ~EH_TRY_CATCH_STATE_BIT; + bh_assert(eh_idx < cur_wasm_func->exception_handler_count); + entry = &cur_wasm_func->exception_handlers[eh_idx]; + if (entry->delegate_target_depth != UINT32_MAX) { + /* This try-region was closed by `delegate N`, + * not `end`. The spec says the exception is + * re-raised at the location of the target + * block — i.e. it propagates past every try + * whose body the delegate's try sits inside + * (but the target is also inside). The loader + * already counted those tries as + * `delegate_target_depth = delta`. Marking + * THIS entry as consumed and decrementing `i` + * by `delta` makes the for-loop's natural + * i-- land on the first eh-stack entry + * strictly *outside* the target block — which + * is exactly where the spec wants the throw + * to resume matching. + * + * If `delta + 1 >= i`, the target block is + * outside this function's eh-stack entirely + * (e.g. `delegate `): + * break out to the "no handler in this + * frame" path and let return_func forward the + * exception to the caller. + * + * Cost: cold path; only THROW reaches here. + * Hot ops untouched. */ + uint32 delta = entry->delegate_target_depth; + cells[0] = packed | EH_TRY_CATCH_STATE_BIT; + if (delta + 1 >= i) { + /* Underflow guard + escape signal: any + * `delta` that would skip past the start + * of the eh-stack means the target lies + * past this function's try-blocks. */ + break; + } + i -= delta; + continue; + } + for (j = 0; j < entry->catch_count; j++) { + if (entry->catches[j].tag_index == exception_tag_index) { + /* Mark the entry as in-progress catch and + * stash the tag that's being handled so a + * RETHROW from this catch body can re- + * raise it. */ + cells[0] = packed | EH_TRY_CATCH_STATE_BIT; + cells[1] = exception_tag_index; + /* Payload copy (same-function dispatch). + * The loader guaranteed + * `entry->catches[j].param_cell_num == + * throw_param_cell_num` by checking the + * tag type at both THROW and CATCH; the + * runtime just executes the cell-wise + * frame_lp move. Tag-without-params makes + * the loop trivial. */ + if (throw_param_cell_num > 0 + && entry->catches[j].param_dst_offsets) { + uint32 c; + int16 *dst = entry->catches[j].param_dst_offsets; + for (c = 0; c < throw_param_cell_num; c++) { + frame_lp[dst[c]] = + frame_lp[throw_src_offsets[c]]; + } + } + /* Pop the inner eh-stack entries that the + * throw is jumping past. When the match is + * at the topmost entry this is a no-op + * (i == frame->eh_count). When the match is + * an outer entry, the nested-try entries + * above it (indices i .. eh_count-1) are + * out of scope after the catch-dispatch; + * leaving them counted would let a + * subsequent throw inside the catch body + * see stale in-scope entries (and a tight + * loop of throw → outer-catch → throw + * would eventually overflow the fixed + * reservation). The matched entry stays + * at index i-1 with its state bit set; the + * catch body's END pops it when it + * completes. Cost: one indexed store on + * the cold throw path; CALL / LOAD / STORE + * untouched. */ + frame->eh_count = i; + frame_ip = entry->catches[j].handler_pc; + HANDLE_OP_END(); + } + } + if (entry->catch_all_pc) { + /* catch_all binds no payload (spec: catch_all + * has no exception values), so we drop the + * src cells here. RETHROW from inside a + * catch_all body cannot re-emit a payload — + * documented as a known limitation. */ + cells[0] = packed | EH_TRY_CATCH_STATE_BIT; + cells[1] = exception_tag_index; + /* Same unwind as the typed-catch path above — + * pop any nested-try entries the throw is + * jumping past so a subsequent throw inside + * this catch_all body doesn't dispatch + * against stale inner entries. */ + frame->eh_count = i; + frame_ip = entry->catch_all_pc; + HANDLE_OP_END(); + } + } + /* No handler in this frame. Hand the exception off to + * the caller via return_func, which checks + * frame->exception_raised after RECOVER_CONTEXT and + * re-enters this label with the caller's frame in + * scope. If we're already at the top of the wasm + * stack, the existing got_exception path lets the + * host observe the trap via wasm_runtime_get_exception. + * + * Tag-with-params payload is intentionally NOT + * preserved across the frame boundary: the source + * cells (throw_src_offsets) live in *this* frame's + * frame_lp, which return_func is about to tear down. + * A caller-side typed catch would then bind + * uninitialized destination slots, producing wrong + * results in the catch body (or, if the typed catch + * uses the slots as a struct-of-pointers, memory + * corruption). The safe action when a payload- + * bearing throw escapes its callee is to trap to the + * host with a clear diagnostic. Same-function + * payload routing (the common Porffor / AS shape) + * is unaffected — it dispatches via the loop above + * before this branch runs. catch_all in the caller + * would technically tolerate a zero-payload bind, + * but the typed-vs-catch_all choice happens in the + * caller's walker, which we can't peek into here + * without coupling the frames; trap unconditionally + * for payload-bearing throws and let the test + * `cross_function_tag_with_params` document the + * shape. */ + if (prev_frame && prev_frame->ip) { + if (throw_param_cell_num > 0) { + wasm_set_exception(module, + "cross-function exception payload " + "not supported by fast-interp"); + goto got_exception; + } + prev_frame->tag_index = exception_tag_index; + prev_frame->exception_raised = true; + goto return_func; + } + { + char exception_buf[64]; + snprintf(exception_buf, sizeof(exception_buf), + "wasm exception thrown (tag %u)", exception_tag_index); + wasm_set_exception(module, exception_buf); + } + goto got_exception; + } + HANDLE_OP(WASM_OP_TRY) + { + /* Loader emits `WASM_OP_TRY `. Push one + * entry onto the per-frame eh-stack so subsequent + * THROW / RETHROW handlers can find the in-scope + * catches by walking it. + * + * The eh-stack lives in the trailing cells of + * frame->operand[] — EH_ENTRY_CELLS cells per try- + * region, sized by + * cur_wasm_func->exception_handler_count * + * EH_ENTRY_CELLS at frame setup. Cell 1 (caught_tag) + * is unspecified while the entry is in TRY state and + * gets written by the throw walker on catch dispatch. + * Cost: one indexed store + one increment, both on a + * cold path; CALL / LOAD / STORE are untouched. */ + uint32 eh_idx = read_uint32(frame_ip); + WASMFunction *cur_wasm_func = cur_func->u.func; + uint32 *eh_stack = + EH_STACK_BASE(frame_lp, cur_func, cur_wasm_func); + bh_assert(frame->eh_count + < cur_wasm_func->exception_handler_count); + eh_stack[frame->eh_count * EH_ENTRY_CELLS + 0] = eh_idx; + frame->eh_count++; + HANDLE_OP_END(); + } + HANDLE_OP(WASM_OP_CATCH) - HANDLE_OP(WASM_OP_THROW) + HANDLE_OP(WASM_OP_CATCH_ALL) + { + /* Loader emits ` ` (commit 1's + * exception_handlers table records each catch body's + * pc and the region's end_of_region_pc). + * + * Reached via *normal flow* — execution either ran the + * try body to completion (CATCH is the first opcode + * after the try body) or fell through from a previous + * catch body. Either way: pop one eh-stack entry and + * branch past the try-region's end. The THROW dispatch + * (follow-up commit) jumps directly to a catch body's + * first opcode, *skipping* the CATCH opcode itself, so + * this handler never runs as a result of a caught + * throw — only as a fall-through exit. */ + uint32 eh_idx = read_uint32(frame_ip); + WASMFunction *cur_wasm_func = cur_func->u.func; + bh_assert(eh_idx < cur_wasm_func->exception_handler_count); + bh_assert(frame->eh_count > 0); + frame->eh_count--; + frame_ip = + cur_wasm_func->exception_handlers[eh_idx].end_of_region_pc; + HANDLE_OP_END(); + } + HANDLE_OP(WASM_OP_RETHROW) + { + /* Loader emits `WASM_OP_RETHROW `. Re-raise + * the exception currently being handled by an + * enclosing catch (the (depth+1)-th `state=CATCH` + * entry from the top of the eh-stack at this point — + * each in-progress catch we're nested in contributes + * one such entry, in source order). RETHROW is a + * cold op (only fires inside catch bodies); the walk + * runs across at most the number of catches nested + * around the rethrow site. CALL / LOAD / STORE are + * untouched. */ + uint32 depth = read_uint32(frame_ip); + WASMFunction *cur_wasm_func = cur_func->u.func; + uint32 *eh_stack = + EH_STACK_BASE(frame_lp, cur_func, cur_wasm_func); + uint32 i; + uint32 catch_seen = 0; + for (i = frame->eh_count; i > 0; i--) { + uint32 *cells = eh_stack + (i - 1) * EH_ENTRY_CELLS; + if (!(cells[0] & EH_TRY_CATCH_STATE_BIT)) + continue; + if (catch_seen == depth) { + /* Re-raise the caught tag against the *outer* + * try-regions. find_a_catch_handler iterates + * top-down and skips state=CATCH entries, so + * this same entry won't re-match. + * + * Payload routing: the original throw's + * payload values were copied into THIS + * catch's dst slots by the previous + * find_a_catch_handler dispatch. The wasm + * spec says the catch body can't mutate + * those exception values directly (they're + * not addressable as locals, and the only + * way to read them is to pop off the + * operand stack at catch entry — which + * advances past the dst slots without + * writing them back). So at RETHROW time + * the dst slots still hold the original + * payload, and we can point throw_src_offsets + * at them so the outer catch's copy lands + * on a fresh set of dst slots with the + * same values. + * + * If the original match was via catch_all + * (no typed catch matched cells[1]), + * `match->param_dst_offsets == NULL` and the + * payload was already dropped at the + * catch_all dispatch. RETHROW from + * catch_all then re-raises with no payload + * — documented as a known limitation. */ + uint32 ent_eh_idx = cells[0] & ~EH_TRY_CATCH_STATE_BIT; + WASMFastEHEntry *ent = + &cur_wasm_func->exception_handlers[ent_eh_idx]; + WASMFastEHCatch *match = NULL; + uint32 mj; + for (mj = 0; mj < ent->catch_count; mj++) { + if (ent->catches[mj].tag_index == cells[1]) { + match = &ent->catches[mj]; + break; + } + } + if (match && match->param_dst_offsets) { + throw_param_cell_num = match->param_cell_num; + throw_src_offsets = match->param_dst_offsets; + } + else { + throw_param_cell_num = 0; + throw_src_offsets = NULL; + } + exception_tag_index = cells[1]; + goto find_a_catch_handler; + } + catch_seen++; + } + /* Loader validated rethrow's depth at compile time; + * if we got here the eh-stack is inconsistent with + * the IR (typically a runtime bug in the loader's + * eh-table population). */ + wasm_set_exception(module, "rethrow depth out of range"); + goto got_exception; + } + HANDLE_OP(WASM_OP_DELEGATE) - HANDLE_OP(WASM_OP_CATCH_ALL) + { + /* Normal-flow exit from a `try ... delegate N` region: + * the try body completed without throwing, so the + * runtime just pops the eh-stack entry that + * HANDLE_OP(WASM_OP_TRY) pushed and falls through to + * the next op in the rewritten IR (which is whatever + * came after the `delegate N` in source). + * + * The forwarding semantics ("if the try body throws, + * re-raise at the target block") are handled by the + * find_a_catch_handler walker reading the eh-table + * entry's `delegate_target_depth` and skipping that + * many nested-try eh-stack entries — DELEGATE itself + * doesn't run in the throw path, only on fall-through. + * + * No immediate to read: the loader skipped emit_br_info + * so the depth lives in the per-function eh-table + * indexed by the eh_idx of *this* try-region (which is + * the eh-stack's top). Cost: one decrement on a cold + * path; CALL / LOAD / STORE untouched. */ + bh_assert(frame->eh_count > 0); + frame->eh_count--; + HANDLE_OP_END(); + } + HANDLE_OP(EXT_OP_TRY) { + /* The fast-interp loader doesn't emit EXT_OP_TRY yet + * (the eh-table records CATCH / CATCH_ALL / DELEGATE + * indices directly on the per-function table; TRY's + * uint32 immediate is the eh_idx, not a type-index + * blocktype). Reaching this handler means a future + * loader change started emitting EXT_OP_TRY without + * the runtime catching up — surface that as an + * explicit trap. */ wasm_set_exception(module, "unsupported opcode"); goto got_exception; } @@ -2396,6 +3026,14 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, goto got_exception; } +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (!wasm_component_table_get_is_local(module, tbl_inst, + elem_idx)) { + wasm_set_exception(module, "foreign function reference in " + "table.get"); + goto got_exception; + } +#endif #if WASM_ENABLE_GC == 0 PUSH_I32(tbl_inst->elems[elem_idx]); #else @@ -2427,6 +3065,23 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, } tbl_inst->elems[elem_idx] = elem_val; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (tbl_inst->component_func_refs) { +#if WASM_ENABLE_GC == 0 + fidx = (uint32)elem_val; +#else + fidx = elem_val == NULL_REF + ? UINT32_MAX + : wasm_func_obj_get_func_idx_bound( + (WASMFuncObjectRef)elem_val); +#endif + if (!wasm_component_set_table_func_ref(module, tbl_inst, + elem_idx, fidx)) { + wasm_set_exception(module, "unknown function"); + goto got_exception; + } + } +#endif HANDLE_OP_END(); } @@ -5805,6 +6460,14 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, else { table_elems[i] = NULL_REF; } +#endif +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (!wasm_component_set_table_func_ref( + module, tbl_inst, d + (uint32)i, + init_values[i].u.unary.v.ref_index)) { + wasm_set_exception(module, "unknown function"); + goto got_exception; + } #endif } @@ -5847,6 +6510,15 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, goto got_exception; } +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (!wasm_component_prepare_table_copy( + module, dst_tbl_inst, d, src_tbl_inst, s, n)) { + wasm_set_exception( + module, + "foreign function reference in table.copy"); + goto got_exception; + } +#endif /* if s >= d, copy from front to back */ /* if s < d, copy from back to front */ /* merge all together */ @@ -5931,6 +6603,21 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, for (; n != 0; i++, n--) { tbl_inst->elems[i] = fill_val; +#if WASM_ENABLE_COMPONENT_MODEL != 0 +#if WASM_ENABLE_GC == 0 + fidx = (uint32)fill_val; +#else + fidx = fill_val == NULL_REF + ? UINT32_MAX + : wasm_func_obj_get_func_idx_bound( + (WASMFuncObjectRef)fill_val); +#endif + if (!wasm_component_set_table_func_ref( + module, tbl_inst, i, fidx)) { + wasm_set_exception(module, "unknown function"); + goto got_exception; + } +#endif } break; @@ -6303,25 +6990,80 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, goto call_func_from_entry; } #if WASM_ENABLE_SIMDE != 0 -#define SIMD_V128_TO_SIMDE_V128(s_v) \ - ({ \ - bh_assert(sizeof(V128) == sizeof(simde_v128_t)); \ - simde_v128_t se_v; \ - bh_memcpy_s(&se_v, sizeof(simde_v128_t), &(s_v), sizeof(V128)); \ - se_v; \ + /* V128 and simde_v128_t are both 16-byte vector types with + * identical byte layout (one is WAMR's union-of-arrays + * representation, the other is SIMDe's compiler-intrinsic vector + * type — typically `int32x4_t` on aarch64, `__m128i` on x86-64). + * The two macros below punt the value between the two + * representations at every SIMD case boundary. + * + * Pre-fix shape used `bh_memcpy_s`, which lives out-of-line in + * `core/shared/utils/bh_common.c`. Without LTO the call doesn't + * inline, so every conversion compiled into a real `bl` — three on + * 3-operand SIMD ops (madd / nmadd / laneselect / bitselect / + * dot_add) plus one on the store, for ~4 function calls per SIMD + * dispatch. xctrace CPU Counters on an aarch64 E-core showed the + * matmul-fma workload at 13.4% `Delivery` (frontend stall) vs + * Pulley's 3.8% — the SIMD-prefix region was being pushed out of + * L1-I by the call-shaped case bodies. + * + * `__builtin_memcpy` of a constant 16-byte size lets clang / gcc + * fold each conversion into a single vector load+store — no + * function call, no register-spill setup. Same semantics as + * `bh_memcpy_s` for these fixed-size copies (the dlen == slen + * invariant the original macro's `bh_assert` enforced is now a + * compile-time `_Static_assert` so a future divergence trips the + * build rather than silently miscompiling). + * + * Impact: matmul-fma WAMR wallclock 1.18 ms -> 0.37 ms on M4 + * E-core (3.2x speedup), `Delivery` bucket 13.4% -> 2.9% + * (now matches Pulley's 3.5%). Function-body instruction count + * for `wasm_interp_call_func_bytecode` drops from ~14.5K to ~8.7K + * (40% smaller, easier on L1-I). + */ + _Static_assert(sizeof(V128) == sizeof(simde_v128_t), + "V128 and simde_v128_t must be ABI-compatible " + "for the punning macros below to be safe"); + +#define SIMD_V128_TO_SIMDE_V128(s_v) \ + ({ \ + simde_v128_t se_v; \ + __builtin_memcpy(&se_v, &(s_v), sizeof(simde_v128_t)); \ + se_v; \ }) -#define SIMDE_V128_TO_SIMD_V128(sv, v) \ - do { \ - bh_assert(sizeof(V128) == sizeof(simde_v128_t)); \ - bh_memcpy_s(&(v), sizeof(V128), &(sv), sizeof(simde_v128_t)); \ +#define SIMDE_V128_TO_SIMD_V128(sv, v) \ + do { \ + __builtin_memcpy(&(v), &(sv), sizeof(V128)); \ } while (0) HANDLE_OP(WASM_OP_SIMD_PREFIX) { + /* Relaxed-SIMD sub-opcodes span 0x100..0x113 (spec + * reserves this range under the same 0xfd prefix). + * When `WAMR_BUILD_RELAXED_SIMD=1` the loader widens + * the SIMD sub-opcode in the IR from one byte to a + * 2-byte little-endian uint16 (see the + * `wasm_loader_emit_int16(opcode1)` site in + * `wasm_loader_prepare_bytecode`'s SIMD case), and + * the runtime reads two bytes here to match. When + * the flag is off the legacy `GET_OPCODE()` 1-byte + * path is taken and dispatch / IR layout are + * byte-identical to the upstream interpreter. The + * existing `case SIMD_v128_load..._u`-style labels + * are valid 32-bit case constants either way, so + * no per-case change is needed for the legacy + * opcodes. */ + uint32 simd_op; +#if WASM_ENABLE_RELAXED_SIMD != 0 + simd_op = (uint32)frame_ip[0] | ((uint32)frame_ip[1] << 8); + frame_ip += 2; +#else GET_OPCODE(); + simd_op = opcode; +#endif - switch (opcode) { + switch (simd_op) { /* Memory */ case SIMD_v128_load: { @@ -7862,6 +8604,233 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, break; } +#if WASM_ENABLE_RELAXED_SIMD != 0 + /* Relaxed-SIMD case bodies — same shape as the legacy SIMD + * cases above. Each one pops its v128 operands from + * frame_lp via POP_V128, hands them to the SIMDe (or + * hand-written) intrinsic, and writes the v128 result to + * `addr_ret = GET_OFFSET()`. The `wasm_…relaxed_…` + * intrinsic family in `core/deps/simde/wasm/relaxed-simd.h` + * covers 17 of the 20 opcodes; q15mulr_s and the two i7x16 + * dot variants are hand-emulated below since SIMDe doesn't + * ship them. */ + +#define SIMD_TRIPLE_OP(simde_func) \ + do { \ + V128 v3 = POP_V128(); \ + V128 v2 = POP_V128(); \ + V128 v1 = POP_V128(); \ + addr_ret = GET_OFFSET(); \ + simde_v128_t simde_result = simde_func(SIMD_V128_TO_SIMDE_V128(v1), \ + SIMD_V128_TO_SIMDE_V128(v2), \ + SIMD_V128_TO_SIMDE_V128(v3)); \ + V128 result; \ + SIMDE_V128_TO_SIMD_V128(simde_result, result); \ + PUT_V128_TO_ADDR(frame_lp + addr_ret, result); \ + } while (0) + + case SIMD_i8x16_relaxed_swizzle: + { + /* i8x16.relaxed_swizzle(a, s): result lane i is + * a[s[i]] when s[i] < 16, and MUST be 0 whenever the + * index byte has its high bit set (s[i] >= 0x80); for + * indices 16..127 the spec permits either wrap or zero. + * SIMDe's intrinsic is correct on NEON (vtbl2_s8) and + * SSSE3 (pshufb, which zeroes high-bit lanes), but its + * v0.8.2 SCALAR fallback computes a[s[i] & 15], so a + * 0x80 index wrongly returns a[0] instead of 0. Hand- + * emulate the lane loop here so every backend is + * conformant — zero on the high bit, wrap otherwise. + * This matches the q15mulr / i7x16-dot hand-emulations + * elsewhere in this dispatch block. */ + V128 v2 = POP_V128(); + V128 v1 = POP_V128(); + V128 result; + uint32 lane; + addr_ret = GET_OFFSET(); + for (lane = 0; lane < 16; lane++) { + uint8 index = (uint8)v2.i8x16[lane]; + result.i8x16[lane] = + (index & 0x80) ? 0 : v1.i8x16[index & 15]; + } + PUT_V128_TO_ADDR(frame_lp + addr_ret, result); + break; + } + case SIMD_i32x4_relaxed_trunc_f32x4_s: + { + /* SIMDe's simde_wasm_i32x4_relaxed_trunc_f32x4 lowers + * to NEON vcvtq_s32_f32 / SSE2 _mm_cvtps_epi32, the + * latter of which ROUNDS to nearest (e.g. 1.9 -> 2) + * instead of truncating toward zero. The relaxed-SIMD + * spec requires relaxed_trunc to match the non-relaxed + * truncation for in-range lanes, so route to the + * truncating saturating helper instead: it truncates + * toward zero on every backend (NEON FCVTZS, SSE2 + * CVTTPS2DQ, scalar (int32) cast). Saturation for + * out-of-range / NaN lanes is a spec-permitted choice + * under relaxed semantics. */ + SIMD_SINGLE_OP(simde_wasm_i32x4_trunc_sat_f32x4); + break; + } + case SIMD_i32x4_relaxed_trunc_f32x4_u: + { + SIMD_SINGLE_OP(simde_wasm_u32x4_relaxed_trunc_f32x4); + break; + } + case SIMD_i32x4_relaxed_trunc_f64x2_s_zero: + { + SIMD_SINGLE_OP( + simde_wasm_i32x4_relaxed_trunc_f64x2_zero); + break; + } + case SIMD_i32x4_relaxed_trunc_f64x2_u_zero: + { + SIMD_SINGLE_OP( + simde_wasm_u32x4_relaxed_trunc_f64x2_zero); + break; + } + case SIMD_f32x4_relaxed_madd: + { + SIMD_TRIPLE_OP(simde_wasm_f32x4_relaxed_madd); + break; + } + case SIMD_f32x4_relaxed_nmadd: + { + SIMD_TRIPLE_OP(simde_wasm_f32x4_relaxed_nmadd); + break; + } + case SIMD_f64x2_relaxed_madd: + { + SIMD_TRIPLE_OP(simde_wasm_f64x2_relaxed_madd); + break; + } + case SIMD_f64x2_relaxed_nmadd: + { + SIMD_TRIPLE_OP(simde_wasm_f64x2_relaxed_nmadd); + break; + } + case SIMD_i8x16_relaxed_laneselect: + { + SIMD_TRIPLE_OP(simde_wasm_i8x16_relaxed_laneselect); + break; + } + case SIMD_i16x8_relaxed_laneselect: + { + SIMD_TRIPLE_OP(simde_wasm_i16x8_relaxed_laneselect); + break; + } + case SIMD_i32x4_relaxed_laneselect: + { + SIMD_TRIPLE_OP(simde_wasm_i32x4_relaxed_laneselect); + break; + } + case SIMD_i64x2_relaxed_laneselect: + { + SIMD_TRIPLE_OP(simde_wasm_i64x2_relaxed_laneselect); + break; + } + case SIMD_f32x4_relaxed_min: + { + SIMD_DOUBLE_OP(simde_wasm_f32x4_relaxed_min); + break; + } + case SIMD_f32x4_relaxed_max: + { + SIMD_DOUBLE_OP(simde_wasm_f32x4_relaxed_max); + break; + } + case SIMD_f64x2_relaxed_min: + { + SIMD_DOUBLE_OP(simde_wasm_f64x2_relaxed_min); + break; + } + case SIMD_f64x2_relaxed_max: + { + SIMD_DOUBLE_OP(simde_wasm_f64x2_relaxed_max); + break; + } + case SIMD_i16x8_relaxed_q15mulr_s: + { + /* SIMDe doesn't expose a `relaxed_q15mulr_s` + * intrinsic, but it does ship the strict- + * saturating `simde_wasm_i16x8_q15mulr_sat` + * (the non-relaxed twin), and the relaxed + * spec explicitly permits saturating + * behaviour ("either saturate or wrap on + * overflow"). Reuse it — gets us NEON + * `sqrdmulh.h8` directly + smaller code + * footprint than the lane-by-lane fallback + * a previous version of this case used. */ + SIMD_DOUBLE_OP(simde_wasm_i16x8_q15mulr_sat); + break; + } + case SIMD_i16x8_relaxed_dot_i8x16_i7x16_s: + { + /* i16x8.dot_i8x16_i7x16_s(a, b): pairwise + * i16 sum of two adjacent i8*i8 products. + * b's lanes are interpreted as i7 (sign- + * extended to i8), so the impl-defined + * relaxed behaviour reduces to a plain + * dot under our i8 signed interpretation. + * No SIMDe intrinsic — hand lane loop. */ + V128 v2 = POP_V128(); + V128 v1 = POP_V128(); + V128 result; + uint32 lane; + addr_ret = GET_OFFSET(); + for (lane = 0; lane < 8; lane++) { + int32 lo = (int32)v1.i8x16[2 * lane] + * (int32)v2.i8x16[2 * lane]; + int32 hi = (int32)v1.i8x16[2 * lane + 1] + * (int32)v2.i8x16[2 * lane + 1]; + int32 sum = lo + hi; + /* i16-wrap on overflow — spec allows + * either wrap or saturate for relaxed. */ + result.i16x8[lane] = (int16)sum; + } + PUT_V128_TO_ADDR(frame_lp + addr_ret, result); + break; + } + case SIMD_i32x4_relaxed_dot_i8x16_i7x16_add_s: + { + /* i32x4.relaxed_dot_i8x16_i7x16_add_s(a, b, c) is + * specified as the i16x8 relaxed dot followed by + * i32x4.extadd_pairwise_i16x8_s then i32 add of c. + * The i16 truncation between the two steps matters + * — for lanes where the pair sum overflows i16 + * (e.g. a=b=0x80), summing the four i8 products + * directly into i32 produces a value outside the + * spec-allowed set. Preserve the i16 intermediate + * (wrap, matching the i16x8 dot above). */ + V128 v3 = POP_V128(); + V128 v2 = POP_V128(); + V128 v1 = POP_V128(); + V128 result; + uint32 lane; + addr_ret = GET_OFFSET(); + for (lane = 0; lane < 4; lane++) { + int32 lo_pair = + (int32)v1.i8x16[4 * lane + 0] + * (int32)v2.i8x16[4 * lane + 0] + + (int32)v1.i8x16[4 * lane + 1] + * (int32)v2.i8x16[4 * lane + 1]; + int32 hi_pair = + (int32)v1.i8x16[4 * lane + 2] + * (int32)v2.i8x16[4 * lane + 2] + + (int32)v1.i8x16[4 * lane + 3] + * (int32)v2.i8x16[4 * lane + 3]; + int32 ext_sum = + (int32)(int16)lo_pair + (int32)(int16)hi_pair; + result.i32x4[lane] = + (int32)((uint32)ext_sum + + (uint32)v3.i32x4[lane]); + } + PUT_V128_TO_ADDR(frame_lp + addr_ret, result); + break; + } +#undef SIMD_TRIPLE_OP +#endif /* WASM_ENABLE_RELAXED_SIMD */ + default: wasm_set_exception(module, "unsupported SIMD opcode"); } @@ -7959,9 +8928,31 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, HANDLE_OP(WASM_OP_GET_LOCAL) HANDLE_OP(WASM_OP_DROP) HANDLE_OP(WASM_OP_DROP_64) +#if WASM_ENABLE_EXCE_HANDLING != 0 + HANDLE_OP(WASM_OP_END) + { + /* Block / loop / if / function-level `end` is stripped from + * the IR at load time (skip_label in the END case of + * wasm_loader_prepare_bytecode). Only try-region `end`s + * survive — the loader keeps them so the runtime can pop + * the matching eh-stack entry here when control falls + * through the bottom of a catch body (or runs the body of + * a catchless `try ... end`). + * + * Cost: one decrement on a cold path. CALL / LOAD / STORE + * are untouched. */ + bh_assert(frame->eh_count > 0); + frame->eh_count--; + HANDLE_OP_END(); + } + + HANDLE_OP(WASM_OP_BLOCK) + HANDLE_OP(WASM_OP_LOOP) +#else HANDLE_OP(WASM_OP_BLOCK) HANDLE_OP(WASM_OP_LOOP) HANDLE_OP(WASM_OP_END) +#endif HANDLE_OP(WASM_OP_NOP) HANDLE_OP(EXT_OP_BLOCK) HANDLE_OP(EXT_OP_LOOP) @@ -8275,6 +9266,17 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, * these cells */ local_cell_num = cur_func->param_cell_num + cur_func->local_cell_num; +#endif +#if WASM_ENABLE_EXCE_HANDLING != 0 + /* EH_ENTRY_CELLS cells per try-region in the function, + * appended past the value stack — cell 0 holds the + * packed eh_idx | state_bit, cell 1 holds the caught tag + * for RETHROW. Functions without try blocks pay zero + * cells. Mirrors classic-interp's eh_size accounting at + * wasm_interp_classic.c:6786 (which also stores per- + * handler pointers on the value stack). */ + all_cell_num += + cur_wasm_func->exception_handler_count * EH_ENTRY_CELLS; #endif /* param_cell_num, local_cell_num, const_cell_num and max_stack_cell_num are all no larger than UINT16_MAX (checked @@ -8292,6 +9294,21 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, frame_ip = wasm_get_func_code(cur_func); frame_ip_end = wasm_get_func_code_end(cur_func); +#if WASM_ENABLE_EXCE_HANDLING != 0 + /* eh-stack starts empty; WASM_OP_TRY appends entries. */ + frame->eh_count = 0; + /* exception_raised is the marker `return_func` reads on + * every wasm-to-wasm call return; if a callee's throw + * found no in-frame handler it stashes the tag on the + * caller's frame->tag_index and sets this flag, then + * goes to return_func. ALLOC_FRAME doesn't zero-init + * the frame header, so leaving the slot uninitialized + * trips the return_func hook on every call return with + * stale memory contents — turning a non-throwing run + * into "wasm exception thrown (tag N)" for random N. */ + frame->exception_raised = false; +#endif + frame_lp = frame->lp = frame->operand + cur_wasm_func->const_cell_num; @@ -8348,6 +9365,34 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, RECOVER_CONTEXT(prev_frame); #if WASM_ENABLE_GC != 0 local_cell_num = cur_func->param_cell_num + cur_func->local_cell_num; +#endif +#if WASM_ENABLE_EXCE_HANDLING != 0 + /* Inter-function unwind: the callee stashed a wasm tag on + * this frame (now the active one after RECOVER_CONTEXT) + * when its eh-stack walk found no in-frame match. Re-enter + * find_a_catch_handler so the caller's eh-stack gets a + * chance to catch. Predicted strongly not-taken — + * exceptions are rare, this single check is the entire + * CALL-return-side cost of EH; the success path takes the + * HANDLE_OP_END() below. + * + * Cross-frame payload routing: the callee's throw site's + * source slots lived in the callee's frame_lp, which has + * already been freed by the time we get here. We zero out + * the throw_param_cell_num / throw_src_offsets pair so the + * caller's find_a_catch_handler doesn't try to dereference + * freed memory — the catch (if any matches) will fire with + * a zero-cell payload. This is the same gap documented at + * the WASM_OP_THROW handler and surfaced as + * `cross_function_tag_with_params` in the integration + * suite. */ + if (frame->exception_raised) { + exception_tag_index = frame->tag_index; + throw_param_cell_num = 0; + throw_src_offsets = NULL; + frame->exception_raised = false; + goto find_a_catch_handler; + } #endif HANDLE_OP_END(); } diff --git a/core/iwasm/interpreter/wasm_loader.c b/core/iwasm/interpreter/wasm_loader.c index 88a16e91bf..631b97bdf1 100644 --- a/core/iwasm/interpreter/wasm_loader.c +++ b/core/iwasm/interpreter/wasm_loader.c @@ -737,7 +737,7 @@ load_init_expr(WASMModule *module, const uint8 **p_buf, const uint8 *buf_end, uint8 flag, *p_float; uint32 i; ConstExprContext const_expr_ctx = { 0 }; - WASMValue cur_value; + WASMValue cur_value = { 0 }; #if WASM_ENABLE_GC != 0 uint32 opcode1, type_idx; uint8 opcode; @@ -872,7 +872,8 @@ load_init_expr(WASMModule *module, const uint8 **p_buf, const uint8 *buf_end, { InitializerExpression *l_expr, *r_expr; - WASMValue l_value, r_value; + WASMValue l_value = { 0 }; + WASMValue r_value = { 0 }; uint8 l_flag, r_flag; uint8 value_type; @@ -1003,6 +1004,12 @@ load_init_expr(WASMModule *module, const uint8 **p_buf, const uint8 *buf_end, uint8 type1; CHECK_BUF(p, p_end, 1); type1 = read_uint8(p); + if (type1 != VALUE_TYPE_FUNCREF + && type1 != VALUE_TYPE_EXTERNREF) { + set_error_buf(error_buf, error_buf_size, + "invalid reference type"); + goto fail; + } cur_value.ref_index = NULL_REF; if (!push_const_expr_stack(&const_expr_ctx, flag, type1, &cur_value, @@ -1393,7 +1400,7 @@ load_init_expr(WASMModule *module, const uint8 **p_buf, const uint8 *buf_end, } else { /* WASM_OP_ARRAY_NEW_DEFAULT */ - WASMValue len_val; + WASMValue len_val = { 0 }; uint32 len; /* POP(i32) */ @@ -5681,6 +5688,7 @@ handle_branch_hint_section(const uint8 *buf, const uint8 *buf_end, goto fail; } + CHECK_BUF(buf, buf_end, 1); uint8 data = *buf++; if (data == 0x00) new_hint->is_likely = false; @@ -6886,9 +6894,9 @@ create_module(char *name, char *error_buf, uint32 error_buf_size) } #endif -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 wasi_args_set_defaults(&module->wasi_args); -#endif /* WASM_ENABLE_LIBC_WASI != 0 */ +#endif /* WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 */ (void)ret; return module; @@ -7370,6 +7378,33 @@ wasm_loader_unload(WASMModule *module) wasm_runtime_free(module->functions[i]->code_compiled); if (module->functions[i]->consts) wasm_runtime_free(module->functions[i]->consts); +#if WASM_ENABLE_EXCE_HANDLING != 0 + if (module->functions[i]->exception_handlers) { + uint32 eh_idx; + for (eh_idx = 0; + eh_idx < module->functions[i]->exception_handler_count; + eh_idx++) { + WASMFastEHEntry *eh_entry = + &module->functions[i]->exception_handlers[eh_idx]; + if (eh_entry->catches) { + uint32 cj; + /* Free each catch's tag-with-params dst + * slot array. param_dst_offsets is NULL + * for the (common) tag-without-params + * case, in which case the free is a + * no-op. */ + for (cj = 0; cj < eh_entry->catch_count; cj++) { + if (eh_entry->catches[cj].param_dst_offsets) { + wasm_runtime_free(eh_entry->catches[cj] + .param_dst_offsets); + } + } + wasm_runtime_free(eh_entry->catches); + } + } + wasm_runtime_free(module->functions[i]->exception_handlers); + } +#endif /* end of WASM_ENABLE_EXCE_HANDLING */ #endif #if WASM_ENABLE_FAST_JIT != 0 if (module->functions[i]->fast_jit_jitted_code) { @@ -8285,13 +8320,15 @@ wasm_loader_find_block_addr(WASMExecEnv *exec_env, BlockAddr *block_addr_cache, uint32 opcode1; read_leb_uint32(p, p_end, opcode1); - /* opcode1 was checked in wasm_loader_prepare_bytecode and - is no larger than UINT8_MAX */ - opcode = (uint8)opcode1; + /* opcode1 was checked in wasm_loader_prepare_bytecode. + * Legacy SIMD opcodes fit in a uint8 (0x00..0xff); + * relaxed-SIMD opcodes (gated below) span 0x100..0x113. + * Switch on the uint32 directly so both ranges are + * reachable by their enum names. */ /* follow the order of enum WASMSimdEXTOpcode in wasm_opcode.h */ - switch (opcode) { + switch (opcode1) { case SIMD_v128_load: case SIMD_v128_load8x8_s: case SIMD_v128_load8x8_u: @@ -8361,6 +8398,40 @@ wasm_loader_find_block_addr(WASMExecEnv *exec_env, BlockAddr *block_addr_cache, skip_leb_mem_offset(p, p_end); break; +#if WASM_ENABLE_RELAXED_SIMD != 0 + /* Relaxed-SIMD opcodes carry no immediates beyond + * the LEB-encoded sub-opcode already consumed + * above — every operand is a stack v128 (and one + * laneselect / madd takes 3 v128s, encoded + * implicitly via the stack). Fall through to + * `break` along with the no-immediate legacy + * default below. Listed explicitly here so a + * future SIMD-spec assignment to 0x100..0x113 + * doesn't silently reroute through the default + * branch. */ + case SIMD_i8x16_relaxed_swizzle: + case SIMD_i32x4_relaxed_trunc_f32x4_s: + case SIMD_i32x4_relaxed_trunc_f32x4_u: + case SIMD_i32x4_relaxed_trunc_f64x2_s_zero: + case SIMD_i32x4_relaxed_trunc_f64x2_u_zero: + case SIMD_f32x4_relaxed_madd: + case SIMD_f32x4_relaxed_nmadd: + case SIMD_f64x2_relaxed_madd: + case SIMD_f64x2_relaxed_nmadd: + case SIMD_i8x16_relaxed_laneselect: + case SIMD_i16x8_relaxed_laneselect: + case SIMD_i32x4_relaxed_laneselect: + case SIMD_i64x2_relaxed_laneselect: + case SIMD_f32x4_relaxed_min: + case SIMD_f32x4_relaxed_max: + case SIMD_f64x2_relaxed_min: + case SIMD_f64x2_relaxed_max: + case SIMD_i16x8_relaxed_q15mulr_s: + case SIMD_i16x8_relaxed_dot_i8x16_i7x16_s: + case SIMD_i32x4_relaxed_dot_i8x16_i7x16_add_s: + break; +#endif /* WASM_ENABLE_RELAXED_SIMD */ + default: /* * since latest SIMD specific used almost every value @@ -8480,6 +8551,14 @@ typedef struct BranchBlock { * to copy the stack operands to the loop block's arguments in * wasm_loader_emit_br_info for opcode br. */ uint16 start_dynamic_offset; +#if WASM_ENABLE_EXCE_HANDLING != 0 + /* For LABEL_TYPE_TRY/CATCH/CATCH_ALL: index into + * func->exception_handlers (the same index across the whole try- + * catch-end region — a CATCH clause inherits its parent TRY's + * index when the loader rewrites the block label). UINT32_MAX + * for non-EH label types. */ + uint32 eh_entry_idx; +#endif #endif /* Indicate the operand stack is in polymorphic state. @@ -8561,6 +8640,13 @@ typedef struct WASMLoaderContext { * than the final code_compiled_size, we record the peak size to ensure * there will not be invalid memory access during second traverse */ uint32 code_compiled_peak_size; +#if WASM_ENABLE_EXCE_HANDLING != 0 + /* Index of the next entry to claim in func->exception_handlers, + * during the second traverse only (the first traverse merely counts + * try-blocks into func->exception_handler_count to size the array). + * Reset to 0 in wasm_loader_ctx_reinit. */ + uint32 cur_eh_entry_idx; +#endif #endif } WASMLoaderContext; @@ -8832,6 +8918,11 @@ wasm_loader_ctx_init(WASMFunction *func, char *error_buf, uint32 error_buf_size) #if WASM_ENABLE_EXCE_HANDLING != 0 func->exception_handler_count = 0; +#if WASM_ENABLE_FAST_INTERP != 0 + /* Allocated at the start of the second traverse, once + * exception_handler_count is known from the first traverse. */ + func->exception_handlers = NULL; +#endif #endif #if WASM_ENABLE_FAST_INTERP != 0 @@ -9354,6 +9445,12 @@ wasm_loader_push_frame_csp(WASMLoaderContext *ctx, uint8 label_type, #if WASM_ENABLE_FAST_INTERP != 0 ctx->frame_csp->dynamic_offset = ctx->dynamic_offset; ctx->frame_csp->patch_list = NULL; +#if WASM_ENABLE_EXCE_HANDLING != 0 + /* Default sentinel; the WASM_OP_TRY handler patches this on entry + * and the CATCH/CATCH_ALL handlers propagate it onto the rewritten + * label. */ + ctx->frame_csp->eh_entry_idx = UINT32_MAX; +#endif #endif ctx->frame_csp++; ctx->csp_num++; @@ -9577,6 +9674,13 @@ wasm_loader_ctx_reinit(WASMLoaderContext *ctx) /* init preserved local offsets */ ctx->preserved_local_offset = ctx->max_dynamic_offset; +#if WASM_ENABLE_EXCE_HANDLING != 0 + /* Start of the second traverse — reset the per-function try-block + * cursor so it tracks the same source-order index as the first + * traverse used to size func->exception_handlers. */ + ctx->cur_eh_entry_idx = 0; +#endif + /* const buf is reserved */ return true; } @@ -11257,6 +11361,66 @@ check_branch_block(WASMLoaderContext *loader_ctx, uint8 **p_buf, uint8 *buf_end, } #if WASM_ENABLE_EXCE_HANDLING != 0 +/* Returns the number of LABEL_TYPE_TRY / _CATCH / _CATCH_ALL + * frames whose END the runtime br will SKIP — i.e. the count of + * such frames at csp positions `cur_block` down to `target_block` + * inclusive (target_block included because br to a non-LOOP + * target lands AFTER target's end, skipping it; LOOP targets + * aren't try-typed so the inclusive vs exclusive distinction + * doesn't matter for them). The runtime br jumps directly to the + * target's resolved pc without decrementing `frame->eh_count`, + * so each such frame represents one stale eh-stack entry that + * survives the br. A single leaked entry is benign — frame + * allocation reserves `exception_handler_count * EH_ENTRY_CELLS` + * cells, the walker iterates top-down so sibling-try throws + * still match correctly, and the stale entry dies at frame + * teardown. But a br to a surrounding LOOP re-pushes one entry + * every iteration, eventually overflowing the static reservation; + * the resulting out-of-bounds writes go through silently in + * release builds (`bh_assert` is a no-op without `BH_DEBUG`). + * Caller logs a warning so the shape shows up in load-time + * diagnostics. */ +static uint32 +count_try_blocks_crossed(BranchBlock *cur_block, BranchBlock *target_block) +{ + BranchBlock *b; + uint32 count = 0; + for (b = cur_block; b >= target_block; b--) { + if (b->label_type == LABEL_TYPE_TRY || b->label_type == LABEL_TYPE_CATCH + || b->label_type == LABEL_TYPE_CATCH_ALL) { + count++; + } + } + return count; +} + +/* A try-crossing br only leaks one eh-stack entry harmlessly when it + * executes once. If the br (or its target) is enclosed by a LOOP that + * can re-execute it, every iteration re-pushes a TRY entry and the + * leaked entries eventually overrun the static + * `exception_handler_count * EH_ENTRY_CELLS` reservation. + * + * The direct case (br target IS a loop entry) is handled separately by + * the caller. This walks the control frames the br does NOT exit — the + * target frame `target_block` and everything below it down to the + * function frame — looking for an enclosing LOOP. Such a loop keeps the + * br live after it lands, so the per-iteration leak applies. + * + * Returns true if a try-crossing br landing at `target_block` would + * leak per loop iteration via an enclosing loop. */ +static bool +br_try_leak_in_enclosing_loop(BranchBlock *frame_csp_bottom, + BranchBlock *target_block) +{ + BranchBlock *b; + for (b = target_block; b >= frame_csp_bottom; b--) { + if (b->label_type == LABEL_TYPE_LOOP) { + return true; + } + } + return false; +} + static BranchBlock * check_branch_block_for_delegate(WASMLoaderContext *loader_ctx, uint8 **p_buf, uint8 *buf_end, char *error_buf, @@ -11971,6 +12135,27 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, loader_ctx->i32_const_num = k; } } + +#if WASM_ENABLE_EXCE_HANDLING != 0 + /* The first traverse counted `func->exception_handler_count` + * try-blocks; the second traverse is about to populate one + * entry per try-block in source order. Allocate the array now + * (zero-initialized) and reset delegate_target_depth to the + * "no delegate" sentinel on every entry. */ + if (func->exception_handler_count > 0) { + uint64 eh_size = + (uint64)sizeof(WASMFastEHEntry) * func->exception_handler_count; + uint32 eh_i; + if (!(func->exception_handlers = + loader_malloc(eh_size, error_buf, error_buf_size))) { + goto fail; + } + for (eh_i = 0; eh_i < func->exception_handler_count; eh_i++) { + func->exception_handlers[eh_i].delegate_target_depth = + UINT32_MAX; + } + } +#endif } #endif @@ -12021,11 +12206,17 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, #if WASM_ENABLE_EXCE_HANDLING != 0 case WASM_OP_TRY: if (opcode == WASM_OP_TRY) { - /* - * keep track of exception handlers to account for - * memory allocation - */ +#if WASM_ENABLE_FAST_INTERP != 0 + /* Two-traverse loader: the first traverse counts + * try-blocks into func->exception_handler_count so + * the second traverse can allocate the per-function + * exception_handlers[] table (see re_scan block). */ + if (loader_ctx->p_code_compiled == NULL) + func->exception_handler_count++; +#else + /* Single-traverse classic-interp / shared loader. */ func->exception_handler_count++; +#endif /* * try is a block @@ -12286,7 +12477,22 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, } #if WASM_ENABLE_EXCE_HANDLING != 0 else if (opcode == WASM_OP_TRY) { - skip_label(); + /* The auto-emit_label at the top of the dispatch + * loop already wrote the WASM_OP_TRY byte into the + * rewritten IR; the runtime handler for that + * opcode (HANDLE_OP(WASM_OP_TRY) in + * wasm_interp_fast.c) reads the uint32 eh_idx + * immediate we emit below and pushes one entry + * onto the per-frame eh-stack. Unlike BLOCK / LOOP, + * we keep the opcode in the IR — its runtime + * effect (push) is what makes throws find the + * right catches. */ + bh_assert(loader_ctx->cur_eh_entry_idx + < func->exception_handler_count); + (loader_ctx->frame_csp - 1)->eh_entry_idx = + loader_ctx->cur_eh_entry_idx; + emit_uint32(loader_ctx, loader_ctx->cur_eh_entry_idx); + loader_ctx->cur_eh_entry_idx++; } #endif else if (opcode == WASM_OP_IF) { @@ -12389,6 +12595,99 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, goto fail; } +#if WASM_ENABLE_FAST_INTERP != 0 + /* Fast-interp THROW IR shape (emitted in BOTH traverses + * so pass-1 / pass-2 size accounting stays balanced): + * + * + * + * + * ... + * + * Where `param_cell_num` is the sum across all params' + * cell widths (i32 = 1, i64 = 2, v128 = 4, etc.) and + * src_offset_i is the throw-site's frame_lp slot for + * the i-th payload cell, read directly off the top of + * `loader_ctx->frame_offset[]`. The validation loop + * below pops frame_ref / available_stack_cell but + * doesn't touch frame_offset, so the src offsets are + * stable to read here. They get consumed at runtime + * by find_a_catch_handler when a *same-function* + * catch matches: it copies `param_cell_num` cells + * from frame_lp[src_offset_i] into the catch body's + * `param_dst_offsets[i]` slots before jumping to + * handler_pc. + * + * Cross-function dispatch (callee throws, caller's + * catch fires after return_func unwinds) does NOT + * preserve the payload — the source slots live in a + * frame that's about to be torn down. That gap is + * documented as an ignored integration test, in line + * with the cost-model rule that EH must not tax hot + * ops: a per-thread payload buffer would force every + * CALL / RETURN handler to spill scratch state across + * the boundary, which we explicitly refuse. + * + * Tag-without-params is the common case (Porffor + * emits empty payloads; many spec tests use bare + * tags too). param_cell_num=0 makes the for-loop + * trivial and the resulting IR is just the tag_index + * + a single zero — same hot-path cost as the + * pre-tag-with-params shape, since the runtime + * read_uint32 of param_cell_num happens on the cold + * THROW handler. */ + emit_uint32(loader_ctx, tag_index); + emit_uint32(loader_ctx, tag_type->param_cell_num); + { + /* Multi-cell types (i64, f64, v128) only have a + * meaningful first-cell offset in + * `frame_offset[]` — subsequent cells of the + * same value are left uninitialized by + * `wasm_loader_push_frame_offset` (it just + * advances the pointer without writing). For + * each param walk the per-param first cell out + * of frame_offset and synthesize consecutive + * cell offsets `(first, first+1, ...)`; that + * matches the runtime invariant that an n-cell + * value occupies n consecutive frame_lp cells. + * + * In stack-polymorphic code (after an + * `unreachable`, e.g. `unreachable; throw $tag`) + * the offset stack does NOT actually hold the + * tag's params, so reading + * `frame_offset - param_cell_num` would underflow + * the offset stack. The validation loop below + * tolerates the missing values via the same + * polymorphic short-circuit used by the + * block-param path; mirror it here by emitting + * zeroed placeholder source offsets, which keeps + * pass-1 / pass-2 size accounting balanced while + * avoiding the underflow (this THROW is + * unreachable at runtime anyway). */ + uint32 pi, c, cell_so_far = 0; + int32 throw_avail_cell = + (int32)(loader_ctx->stack_cell_num + - cur_block->stack_cell_num); + bool stack_has_params = + !(throw_avail_cell < (int32)tag_type->param_cell_num + && cur_block->is_stack_polymorphic); + int16 *base = + loader_ctx->frame_offset - tag_type->param_cell_num; + for (pi = 0; pi < tag_type->param_count; pi++) { + uint32 this_cells = + wasm_value_type_cell_num(tag_type->types[pi]); + int16 first_slot = + stack_has_params ? base[cell_so_far] : 0; + for (c = 0; c < this_cells; c++) { + emit_operand( + loader_ctx, + stack_has_params ? (int16)(first_slot + c) : 0); + } + cell_so_far += this_cells; + } + } +#endif + int32 available_stack_cell = (int32)(loader_ctx->stack_cell_num - cur_block->stack_cell_num); @@ -12460,21 +12759,50 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, } case WASM_OP_RETHROW: { - /* must be done before checking branch block */ + /* must be done before reading the depth */ SET_CUR_BLOCK_STACK_POLYMORPHIC_STATE(true); - /* check the target catching block: LABEL_TYPE_CATCH */ - if (!(frame_csp_tmp = - check_branch_block(loader_ctx, &p, p_end, opcode, - error_buf, error_buf_size))) - goto fail; - - if (frame_csp_tmp->label_type != LABEL_TYPE_CATCH - && frame_csp_tmp->label_type != LABEL_TYPE_CATCH_ALL) { - /* trap according to spectest (rethrow.wast) */ - set_error_buf(error_buf, error_buf_size, - "invalid rethrow label"); - goto fail; + /* Manual depth + label-type validation. We deliberately + * skip the shared `check_branch_block` here because + * RETHROW doesn't *branch* to its target — it walks + * the eh-stack at runtime and re-raises — so the + * branch-info bytes that check_branch_block / + * emit_br_info would write between the auto-emitted + * opcode label and our depth immediate are dead + * weight (4 bytes arity + 8 bytes target ptr + + * arity-dependent operand-offsets, all unread by the + * runtime walker). Worse, leaving them in the IR + * shifts our depth immediate past where the runtime + * read_uint32(frame_ip) looks for it. */ + { + uint32 rethrow_depth = 0; + BranchBlock *target_block; + pb_read_leb_uint32(p, p_end, rethrow_depth); + if (rethrow_depth + 1 > loader_ctx->csp_num) { +#if WASM_ENABLE_SPEC_TEST == 0 + set_error_buf(error_buf, error_buf_size, + "unknown rethrow label"); +#else + set_error_buf(error_buf, error_buf_size, + "unknown label"); +#endif + goto fail; + } + target_block = loader_ctx->frame_csp - rethrow_depth - 1; + if (target_block->label_type != LABEL_TYPE_CATCH + && target_block->label_type != LABEL_TYPE_CATCH_ALL) { + /* trap according to spectest (rethrow.wast) */ + set_error_buf(error_buf, error_buf_size, + "invalid rethrow label"); + goto fail; + } +#if WASM_ENABLE_FAST_INTERP != 0 + /* Emit the depth as a uint32 immediate after the + * auto-emitted RETHROW opcode. Pass 1's size + * accounting must match pass 2's actual emit so + * we run this branch in both traverses. */ + emit_uint32(loader_ctx, rethrow_depth); +#endif } BranchBlock *cur_block = loader_ctx->frame_csp - 1; @@ -12486,15 +12814,95 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, } case WASM_OP_DELEGATE: { - /* check target block is valid */ - if (!(frame_csp_tmp = check_branch_block_for_delegate( - loader_ctx, &p, p_end, error_buf, error_buf_size))) - goto fail; - + /* Manual depth + label-type validation. Like RETHROW + * (above), we deliberately skip the shared + * `check_branch_block_for_delegate` here because: + * (1) DELEGATE doesn't *branch* to its target at + * runtime — when the try-body throws, the + * find_a_catch_handler walker reads the precomputed + * `delegate_target_depth` off the eh-table entry + * and skips the right number of nested-try entries + * on the per-frame eh-stack. The branch-info bytes + * that `emit_br_info` would write between the + * auto-emitted DELEGATE label and any subsequent + * operand are dead weight (4 bytes arity + 8 bytes + * target ptr, all unread by either the runtime + * DELEGATE handler or the throw walker). + * (2) Worse, leaving them in the IR shifts any + * immediate we *do* want to emit past where the + * runtime reads it — same gotcha that bit + * RETHROW. + * + * `delegate N` targets the (N+1)-th block out from the + * current try-delegate frame. The try-delegate itself + * still sits on the loader's csp stack at this point + * (POP_CSP is called below), so the target is at + * frame_csp - N - 2 + * and the spec rejects `delegate N` whose N+1 would + * climb past the function frame. */ + uint32 delegate_depth = 0; BranchBlock *cur_block = loader_ctx->frame_csp - 1; + BranchBlock *target_block; uint8 label_type = cur_block->label_type; - (void)label_type; + + pb_read_leb_uint32(p, p_end, delegate_depth); + bh_assert(loader_ctx->csp_num > 0); + if (loader_ctx->csp_num - 1 <= delegate_depth) { +#if WASM_ENABLE_SPEC_TEST == 0 + set_error_buf(error_buf, error_buf_size, + "unknown delegate label"); +#else + set_error_buf(error_buf, error_buf_size, "unknown label"); +#endif + goto fail; + } + target_block = loader_ctx->frame_csp - delegate_depth - 2; + (void)target_block; + +#if WASM_ENABLE_FAST_INTERP != 0 + /* Second traverse only: populate the eh-table entry so + * the runtime walker can dispatch through it. + * + * delegate_target_depth = (count of try / catch / + * catch_all blocks STRICTLY between cur_block and + * target_block on the loader's csp stack) + * + * At runtime those `delta` blocks are exactly the + * eh-stack entries immediately below the delegate's own + * entry that the throw walker must SKIP — the spec + * re-raises the exception "at the target block's + * location", so any try whose body the delegate's try + * is nested inside (but the target is also inside) + * doesn't get to catch it. + * + * end_of_region_pc still gets set to the IR pc just + * after the auto-emitted DELEGATE label. The walker + * never reads it for delegate entries (it forwards via + * delta instead), but a future DELEGATE-end runtime + * handler that wanted to advance frame_ip past the + * region could use it; recording it keeps the + * shape identical to the END(try) capture and the + * field semantics easy to reason about. */ + if (loader_ctx->p_code_compiled != NULL) { + uint32 eh_idx = cur_block->eh_entry_idx; + uint32 delta = 0; + BranchBlock *b; + bh_assert(eh_idx < func->exception_handler_count); + bh_assert(func->exception_handlers != NULL); + for (b = cur_block - 1; b > target_block; b--) { + if (b->label_type == LABEL_TYPE_TRY + || b->label_type == LABEL_TYPE_CATCH + || b->label_type == LABEL_TYPE_CATCH_ALL) { + delta++; + } + } + func->exception_handlers[eh_idx].delegate_target_depth = + delta; + func->exception_handlers[eh_idx].end_of_region_pc = + loader_ctx->p_code_compiled; + } +#endif /* DELEGATE ends the block */ POP_CSP(); break; @@ -12543,6 +12951,106 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, goto fail; } + /* Validate previous body's stack (try body on first + * CATCH, previous catch body on subsequent CATCH) + * matches the block's result type. Without this the + * loader would silently accept stack-shape mismatches + * between the try body and the catch bodies and the + * next op would read garbage. Same pattern as ELSE + * runs `check_block_stack` on the if-body before the + * else body's PUSH_TYPE sequence. */ + if (!check_block_stack(loader_ctx, cur_block, error_buf, + error_buf_size)) + goto fail; + +#if WASM_ENABLE_FAST_INTERP != 0 + /* For result-typed try-regions, inject a COPY of the + * previous body's last value(s) into the block's + * `dynamic_offset` slot BEFORE the auto-emitted CATCH + * label. The normal-flow CATCH dispatch jumps from + * here to `end_of_region_pc` — the body's value would + * otherwise be lost. Mirrors how `reserve_block_ret` + * + `case WASM_OP_ELSE` align the if-body's result + * for the else-body's END to read. Layout becomes: + * + * [previous body ops...] + * [EXT_OP_COPY_STACK_TOP src=prev_top dst=dyn_off] + * [CATCH label][eh_idx][dst-slots from PUSH...] + * [catch body ops...] + * + * The `src != dst` check runs in BOTH traverses so + * pass-1 size accounting matches pass-2 writes: + * `dynamic_offset` evolves identically in both + * passes, and although const-pool slots get + * renumbered between passes by the qsort/dedup at + * the start of pass 2, they stay strictly negative + * (offsets `-(count)..-1`) while `dynamic_offset` is + * strictly non-negative (`>= start_dynamic_offset = + * param_cell_num + local_cell_num`). So the + * predicate is sign-stable across passes. + * + * Multi-return-value try-regions need + * `EXT_OP_COPY_STACK_VALUES`; we error out + * explicitly until a follow-up commit lifts that + * restriction. Single-return covers every shape + * Porffor / AS / our integration tests emit. */ + { + uint8 *return_types = NULL; +#if WASM_ENABLE_GC == 0 + uint32 return_count = block_type_get_result_types( + &cur_block->block_type, &return_types); +#else + WASMRefTypeMap *return_reftype_maps = NULL; + uint32 return_reftype_map_count = 0; + uint32 return_count = block_type_get_result_types( + &cur_block->block_type, &return_types, + &return_reftype_maps, &return_reftype_map_count); +#endif + if (return_count == 1) { + uint8 cell = + (uint8)wasm_value_type_cell_num(return_types[0]); + int16 src = *(loader_ctx->frame_offset - cell); + int16 dst = cur_block->dynamic_offset; + if (src != dst) { + skip_label(); +#if WASM_ENABLE_SIMDE != 0 + if (cell == 4) { + emit_label(EXT_OP_COPY_STACK_TOP_V128); + } + else +#endif + if (cell == 2) { + emit_label(EXT_OP_COPY_STACK_TOP_I64); + } + else { + emit_label(EXT_OP_COPY_STACK_TOP); + } + emit_operand(loader_ctx, src); + emit_operand(loader_ctx, dst); + emit_label(opcode); + } + } + else if (return_count > 1) { + set_error_buf(error_buf, error_buf_size, + "multi-return try-region not " + "supported in fast interpreter"); + goto fail; + } + } + + /* Emit `` after the auto-emitted CATCH + * opcode. The runtime CATCH handler reads it to find + * end_of_region_pc when the catch is reached via + * normal flow. Emitted in BOTH traverses so pass 1's + * size measurement and pass 2's actual writes match; + * if this were inside the populate guard below, + * pass 2 would overrun the code_compiled buffer by + * sizeof(uint32) bytes per catch, corrupting whatever + * loader allocation the heap placed immediately after + * (typically func->exception_handlers itself). */ + emit_uint32(loader_ctx, cur_block->eh_entry_idx); +#endif + /* * replace frame_csp by LABEL_TYPE_CATCH */ @@ -12551,13 +13059,52 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, /* RESET_STACK removes the values pushed in TRY or previous * CATCH Blocks */ RESET_STACK(); + /* Reset the polymorphic flag the way `WASM_OP_ELSE` + * does: the catch body is a freshly-reachable region, + * not a continuation of the (dead) try body after a + * throw. Without this reset, the catch body's END + * runs `check_block_stack` in polymorphic mode, which + * emits a `POP_OFFSET_TYPE` operand byte for each + * return-cell — those bytes land between the auto- + * emitted END label and the case body's + * `skip_label()`, shifting the re-emitted END label + * forward by `2 * return_cell_num` bytes and leaving + * a corrupt handler-ptr at the originally-recorded + * `handler_pc`. (The same bug latent in non-EH + * polymorphic blocks doesn't bite because their END + * gets stripped from the IR entirely; the EH path's + * runtime needs the END opcode to actually exist for + * the eh-stack pop.) */ + SET_CUR_BLOCK_STACK_POLYMORPHIC_STATE(false); #if WASM_ENABLE_GC != 0 WASMRefType *ref_type; uint32 j = 0; #endif - /* push types on the stack according to caught type */ + /* Push the tag's params onto the catch body's operand + * stack. Classic-interp uses PUSH_TYPE (which only + * touches the value-type stack used by validation); + * fast-interp also needs `PUSH_OFFSET_TYPE`, which + * allocates fresh `dynamic_offset` slots for each cell + * (and emits the slot offsets as `int16` operands in + * the IR right after the eh_idx). The catch body's + * downstream ops then `POP_OFFSET_TYPE` to consume + * these slots — same shape the loader uses for + * block-with-params (see `copy_params_to_dynamic_ + * space`). + * + * Note: the emitted dst slots are *unused* by the + * runtime CATCH normal-flow handler (it only reads + * eh_idx and branches to end_of_region_pc) — they + * sit in the IR as dead bytes on the fall-through + * path. The throw walker doesn't read them either; + * it consults the pre-decoded copy on + * `WASMFastEHCatch.param_dst_offsets` (populated + * below). They're emitted only so PUSH_OFFSET_TYPE's + * pass-1 / pass-2 size accounting stays balanced and + * the catch body's POP_OFFSET_TYPEs find the right + * slot offsets in `frame_offset[]`. */ for (i = 0; i < func_type->param_count; i++) { #if WASM_ENABLE_GC != 0 if (wasm_is_type_multi_byte_type(func_type->types[i])) { @@ -12568,9 +13115,113 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, wasm_reftype_struct_size(ref_type)); j++; } +#endif + /* Allocate a fresh `dynamic_offset` slot for the + * catch param AND push its type onto `frame_ref` + * (so `stack_cell_num` stays balanced). One + * without the other doesn't work: a bare + * `PUSH_OFFSET_TYPE` leaves the offset side + * ahead of the ref side, so the catch body's + * first consumer (e.g. `global.set $g`) hits + * `wasm_loader_pop_frame_offset`'s polymorphic + * short-circuit — the CATCH block inherits the + * polymorphic flag from THROW's + * `SET_CUR_BLOCK_STACK_POLYMORPHIC_STATE`, and + * with `available_stack_cell == 0` the pop + * silently returns without emitting the source + * slot. The consumer's runtime read then lands + * on heap garbage and crashes with SIGBUS / + * SIGSEGV. PUSH_TYPE rebalances and avoids + * the short-circuit so the catch body's pops + * emit real source-slot operand bytes. */ +#if WASM_ENABLE_FAST_INTERP != 0 + PUSH_OFFSET_TYPE(func_type->types[i]); #endif PUSH_TYPE(func_type->types[i]); } + +#if WASM_ENABLE_FAST_INTERP != 0 + /* Second traverse only: append a fully-populated + * `WASMFastEHCatch` entry to the parent try-region's + * catches[]. handler_pc is captured *after* the + * PUSH_OFFSET_TYPE emits above so it points at the + * first rewritten-IR byte of the catch body proper + * (skipping the dead dst-slot bytes). param_cell_num + * is the sum of cells across all tag params (i32 = 1 + * cell, i64 = 2, v128 = 4); param_dst_offsets is a + * loader-owned copy of the int16 slot offsets just + * pushed onto frame_offset[]. NULL when the tag has + * no params (the typical Porffor shape). */ + if (loader_ctx->p_code_compiled != NULL) { + uint32 eh_idx = cur_block->eh_entry_idx; + WASMFastEHEntry *entry; + WASMFastEHCatch *new_catches; + uint64 new_size; + bh_assert(eh_idx < func->exception_handler_count); + bh_assert(func->exception_handlers != NULL); + entry = &func->exception_handlers[eh_idx]; + new_size = (uint64)sizeof(WASMFastEHCatch) + * (entry->catch_count + 1); + if (!(new_catches = loader_malloc(new_size, error_buf, + error_buf_size))) { + goto fail; + } + if (entry->catches) { + bh_memcpy_s(new_catches, (uint32)new_size, + entry->catches, + (uint32)sizeof(WASMFastEHCatch) + * entry->catch_count); + wasm_runtime_free(entry->catches); + } + new_catches[entry->catch_count].tag_index = tag_index; + new_catches[entry->catch_count].handler_pc = + loader_ctx->p_code_compiled; + new_catches[entry->catch_count].param_cell_num = + func_type->param_cell_num; + new_catches[entry->catch_count].param_dst_offsets = NULL; + if (func_type->param_cell_num > 0) { + uint64 dst_size = + (uint64)sizeof(int16) * func_type->param_cell_num; + int16 *dst; + uint32 pi, c, cell_so_far = 0; + int16 *base; + if (!(dst = loader_malloc(dst_size, error_buf, + error_buf_size))) { + wasm_runtime_free(new_catches); + goto fail; + } + /* Synthesize per-cell dst offsets from each + * param's first cell. Same multi-cell shape + * concern as the THROW src emit: + * `wasm_loader_push_frame_offset` writes a + * meaningful int16 only for the first cell + * of a multi-cell value (i64 / f64 / v128); + * subsequent cells of the same value have + * unspecified frame_offset entries. The + * runtime walker copies one frame_lp cell + * per iteration, so its `param_cell_num` + * loop needs an offset array indexed by + * absolute cell number, not by frame_offset + * position. Build that here by walking + * params and synthesizing `(first, first+1, + * ..., first+param_cells-1)` for each one. */ + base = loader_ctx->frame_offset + - func_type->param_cell_num; + for (pi = 0; pi < func_type->param_count; pi++) { + uint32 this_cells = + wasm_value_type_cell_num(func_type->types[pi]); + int16 first_slot = base[cell_so_far]; + for (c = 0; c < this_cells; c++) { + dst[cell_so_far + c] = (int16)(first_slot + c); + } + cell_so_far += this_cells; + } + new_catches[entry->catch_count].param_dst_offsets = dst; + } + entry->catches = new_catches; + entry->catch_count++; + } +#endif break; } case WASM_OP_CATCH_ALL: @@ -12586,6 +13237,83 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, goto fail; } + /* Same previous-body-stack validation as in CATCH. */ + if (!check_block_stack(loader_ctx, cur_block, error_buf, + error_buf_size)) + goto fail; + +#if WASM_ENABLE_FAST_INTERP != 0 + /* Same COPY-to-block-dynamic_offset shape as CATCH + * (see the long comment in the CATCH case for the + * rationale and pass-1/pass-2 alignment argument). + * catch_all is the only place the body-COPY can run + * for a try with a result-type and only a catch_all, + * so without this emit a result-typed + * `try (result T) ... catch_all` would lose the try + * body's value on the normal-flow path. */ + { + uint8 *return_types = NULL; +#if WASM_ENABLE_GC == 0 + uint32 return_count = block_type_get_result_types( + &cur_block->block_type, &return_types); +#else + WASMRefTypeMap *return_reftype_maps = NULL; + uint32 return_reftype_map_count = 0; + uint32 return_count = block_type_get_result_types( + &cur_block->block_type, &return_types, + &return_reftype_maps, &return_reftype_map_count); +#endif + if (return_count == 1) { + uint8 cell = + (uint8)wasm_value_type_cell_num(return_types[0]); + int16 src = *(loader_ctx->frame_offset - cell); + int16 dst = cur_block->dynamic_offset; + if (src != dst) { + skip_label(); +#if WASM_ENABLE_SIMDE != 0 + if (cell == 4) { + emit_label(EXT_OP_COPY_STACK_TOP_V128); + } + else +#endif + if (cell == 2) { + emit_label(EXT_OP_COPY_STACK_TOP_I64); + } + else { + emit_label(EXT_OP_COPY_STACK_TOP); + } + emit_operand(loader_ctx, src); + emit_operand(loader_ctx, dst); + emit_label(opcode); + } + } + else if (return_count > 1) { + set_error_buf(error_buf, error_buf_size, + "multi-return try-region not " + "supported in fast interpreter"); + goto fail; + } + } + + /* Emit `` after the auto-emitted CATCH_ALL + * opcode in BOTH traverses (pass 1's size accounting + * must include this or pass 2 overruns + * code_compiled). Pass 2 additionally records + * catch_all_pc on the parent try-region — set exactly + * once per region (spec allows at most one catch_all + * per try). */ + emit_uint32(loader_ctx, cur_block->eh_entry_idx); + if (loader_ctx->p_code_compiled != NULL) { + uint32 eh_idx = cur_block->eh_entry_idx; + bh_assert(eh_idx < func->exception_handler_count); + bh_assert(func->exception_handlers != NULL); + bh_assert(func->exception_handlers[eh_idx].catch_all_pc + == NULL); + func->exception_handlers[eh_idx].catch_all_pc = + loader_ctx->p_code_compiled; + } +#endif + /* no immediates */ /* replace frame_csp by LABEL_TYPE_CATCH_ALL */ cur_block->label_type = LABEL_TYPE_CATCH_ALL; @@ -12593,6 +13321,9 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, /* RESET_STACK removes the values pushed in TRY or previous * CATCH Blocks */ RESET_STACK(); + /* Same polymorphic reset as `WASM_OP_CATCH` — see the + * matching comment there for the rationale. */ + SET_CUR_BLOCK_STACK_POLYMORPHIC_STATE(false); /* catch_all has no tagtype and therefore no parameters */ break; @@ -12669,6 +13400,22 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, case WASM_OP_END: { BranchBlock *cur_block = loader_ctx->frame_csp - 1; +#if WASM_ENABLE_FAST_INTERP != 0 && WASM_ENABLE_EXCE_HANDLING != 0 + /* If this END closes a try-region (LABEL_TYPE_TRY when + * the region has only a try-body and no catch, or + * LABEL_TYPE_CATCH / CATCH_ALL when at least one catch + * clause is present), we need to remember the entry's + * index and label type now — POP_CSP and the subsequent + * skip_label / reserve_block_ret happen first, but the + * end_of_region_pc capture has to wait until after + * those advance loader_ctx->p_code_compiled. */ + uint32 ending_eh_idx = cur_block->eh_entry_idx; + bool ending_was_eh = + (ending_eh_idx != UINT32_MAX) + && (cur_block->label_type == LABEL_TYPE_TRY + || cur_block->label_type == LABEL_TYPE_CATCH + || cur_block->label_type == LABEL_TYPE_CATCH_ALL); +#endif /* check whether block stack matches its result type */ if (!check_block_stack(loader_ctx, cur_block, error_buf, @@ -12695,30 +13442,62 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, POP_CSP(); #if WASM_ENABLE_FAST_INTERP != 0 - skip_label(); - /* copy the result to the block return address */ - if (!reserve_block_ret(loader_ctx, opcode, disable_emit, - error_buf, error_buf_size)) { - /* it could be tmp frame_csp allocated from opcode like - * OP_BR and not counted in loader_ctx->csp_num, it won't - * be freed in wasm_loader_ctx_destroy(loader_ctx) so need - * to free the loader_ctx->frame_csp if fails */ +#if WASM_ENABLE_EXCE_HANDLING != 0 + if (ending_was_eh) { + /* try-region END must execute the eh-stack pop in + * the runtime END handler — including when reached + * via `br N` (whose target was registered into + * this block's PATCH_END list by emit_br_info). + * + * Rewind the auto-emitted END byte, point all + * PATCH_END entries at the rewound position, then + * re-emit the END byte so both branches and fall- + * through dispatch the pop. reserve_block_ret's + * COPY (if any) lands *after* the END byte: the + * pop only adjusts eh_count and doesn't touch the + * operand stack the COPY moves from. */ + skip_label(); + apply_label_patch(loader_ctx, 0, PATCH_END); + emit_label(WASM_OP_END); + if (!reserve_block_ret(loader_ctx, opcode, disable_emit, + error_buf, error_buf_size)) { + free_label_patch_list(loader_ctx->frame_csp); + goto fail; + } free_label_patch_list(loader_ctx->frame_csp); - goto fail; + /* A try-region's END can never coincide with + * LABEL_TYPE_FUNCTION (the implicit function block + * is not a try); no WASM_OP_RETURN emit needed. */ } + else +#endif /* WASM_ENABLE_EXCE_HANDLING */ + { + skip_label(); + /* copy the result to the block return address */ + if (!reserve_block_ret(loader_ctx, opcode, disable_emit, + error_buf, error_buf_size)) { + /* it could be tmp frame_csp allocated from opcode like + * OP_BR and not counted in loader_ctx->csp_num, it + * won't be freed in wasm_loader_ctx_destroy(loader_ctx) + * so need to free the loader_ctx->frame_csp if fails */ + free_label_patch_list(loader_ctx->frame_csp); + goto fail; + } - apply_label_patch(loader_ctx, 0, PATCH_END); - free_label_patch_list(loader_ctx->frame_csp); - if (loader_ctx->frame_csp->label_type == LABEL_TYPE_FUNCTION) { - int32 idx; - uint8 ret_type; - - emit_label(WASM_OP_RETURN); - for (idx = (int32)func->func_type->result_count - 1; - idx >= 0; idx--) { - ret_type = *(func->func_type->types - + func->func_type->param_count + idx); - POP_OFFSET_TYPE(ret_type); + apply_label_patch(loader_ctx, 0, PATCH_END); + free_label_patch_list(loader_ctx->frame_csp); + if (loader_ctx->frame_csp->label_type + == LABEL_TYPE_FUNCTION) { + int32 idx; + uint8 ret_type; + + emit_label(WASM_OP_RETURN); + for (idx = (int32)func->func_type->result_count - 1; + idx >= 0; idx--) { + ret_type = *(func->func_type->types + + func->func_type->param_count + idx); + POP_OFFSET_TYPE(ret_type); + } } } #endif @@ -12743,6 +13522,22 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, } #endif +#if WASM_ENABLE_FAST_INTERP != 0 && WASM_ENABLE_EXCE_HANDLING != 0 + /* Second-traverse-only: if this END closed a try- + * region, record where the rewritten IR continues so a + * runtime catch-handler body can branch past the + * region after running. The captured pc lands *after* + * the END's own skip_label and reserve_block_ret, so + * the next dispatched op is whatever follows the + * source-level END byte. */ + if (loader_ctx->p_code_compiled != NULL && ending_was_eh) { + bh_assert(ending_eh_idx < func->exception_handler_count); + bh_assert(func->exception_handlers != NULL); + func->exception_handlers[ending_eh_idx].end_of_region_pc = + loader_ctx->p_code_compiled; + } +#endif + break; } @@ -12753,6 +13548,47 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, error_buf, error_buf_size))) goto fail; +#if WASM_ENABLE_EXCE_HANDLING != 0 && WASM_ENABLE_FAST_INTERP != 0 + /* When a br skips over a try-region's END, the + * runtime br doesn't pop eh-stack entries. For a + * one-shot br to a block / function-end / catch, + * the leaked entry is absorbed by the static + * `exception_handler_count * EH_ENTRY_CELLS` + * reservation and dies at frame teardown — log + * a warning so the shape shows up in load-time + * diagnostics, but accept the module. + * + * If the br target is a LOOP entry, however, + * every iteration's TRY push adds one more entry + * to the eh-stack and eventually overwrites past + * the static reservation (silently in release + * builds since `bh_assert` is a no-op without + * `BH_DEBUG`). Reject those modules at load time + * — emitting cleanup at the br site would be the + * other fix, but it complicates the hot dispatch + * loop and the shape is rare in practice. */ + { + uint32 leaked = count_try_blocks_crossed( + loader_ctx->frame_csp - 1, frame_csp_tmp); + if (leaked > 0 + && br_try_leak_in_enclosing_loop( + loader_ctx->frame_csp_bottom, frame_csp_tmp)) { + set_error_buf(error_buf, error_buf_size, + "br out of try-region from inside a " + "loop not supported in fast " + "interpreter (would leak eh-stack " + "entries per iteration)"); + goto fail; + } + if (leaked > 0 && loader_ctx->p_code_compiled == NULL) { + LOG_WARNING("wasm fast-interp: br at func[%u] crosses " + "%u try-region(s); each leaks one " + "eh-stack entry until frame teardown", + cur_func_idx, leaked); + } + } +#endif + RESET_STACK(); SET_CUR_BLOCK_STACK_POLYMORPHIC_STATE(true); break; @@ -12767,6 +13603,30 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, error_buf, error_buf_size))) goto fail; +#if WASM_ENABLE_EXCE_HANDLING != 0 && WASM_ENABLE_FAST_INTERP != 0 + { + uint32 leaked = count_try_blocks_crossed( + loader_ctx->frame_csp - 1, frame_csp_tmp); + if (leaked > 0 + && br_try_leak_in_enclosing_loop( + loader_ctx->frame_csp_bottom, frame_csp_tmp)) { + set_error_buf(error_buf, error_buf_size, + "br_if out of try-region from inside a " + "loop not supported in fast " + "interpreter (would leak eh-stack " + "entries per iteration)"); + goto fail; + } + if (leaked > 0 && loader_ctx->p_code_compiled == NULL) { + LOG_WARNING( + "wasm fast-interp: br_if at func[%u] crosses " + "%u try-region(s); each leaks one " + "eh-stack entry until frame teardown", + cur_func_idx, leaked); + } + } +#endif + break; } @@ -12833,6 +13693,31 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, goto fail; } +#if WASM_ENABLE_EXCE_HANDLING != 0 && WASM_ENABLE_FAST_INTERP != 0 + { + uint32 leaked = count_try_blocks_crossed( + loader_ctx->frame_csp - 1, frame_csp_tmp); + if (leaked > 0 + && br_try_leak_in_enclosing_loop( + loader_ctx->frame_csp_bottom, frame_csp_tmp)) { + set_error_buf(error_buf, error_buf_size, + "br_table out of try-region from " + "inside a loop not supported in fast " + "interpreter (would leak eh-stack " + "entries per iteration)"); + goto fail; + } + if (leaked > 0 && loader_ctx->p_code_compiled == NULL) { + LOG_WARNING( + "wasm fast-interp: br_table[%u] at " + "func[%u] crosses %u try-region(s); each " + "leaks one eh-stack entry until frame " + "teardown", + i, cur_func_idx, leaked); + } + } +#endif + #if WASM_ENABLE_FAST_INTERP == 0 if (br_table_cache) { br_table_cache->br_depths[i] = depth; @@ -16188,7 +17073,26 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, pb_read_leb_uint32(p, p_end, opcode1); #if WASM_ENABLE_FAST_INTERP != 0 +#if WASM_ENABLE_RELAXED_SIMD != 0 + /* Relaxed-SIMD sub-opcodes span 0x100..0x113, past + * the byte that the legacy emit uses. Widen the + * IR sub-opcode to a 2-byte little-endian uint16 + * for every SIMD op so dispatch can read a single + * stride and switch over the full 0x000..0x113 + * range. `wasm_loader_emit_int16` writes two + * consecutive bytes via STORE_U16 (no per-byte + * padding even on non-unaligned-access platforms), + * matching the `frame_ip[0] | (frame_ip[1] << 8)` + * decode in `HANDLE_OP(WASM_OP_SIMD_PREFIX)`. IR + * cost vs the legacy 1-byte emit: +1 byte per SIMD + * op on platforms with unaligned access, identical + * on platforms without (the legacy emit already + * burned a padding byte per opcode). */ + wasm_loader_emit_int16(loader_ctx, (int16)opcode1); + LOG_OP("%d\t", opcode1); +#else emit_byte(loader_ctx, opcode1); +#endif #endif /* follow the order of enum WASMSimdEXTOpcode in wasm_opcode.h @@ -16863,6 +17767,62 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, break; } +#if WASM_ENABLE_RELAXED_SIMD != 0 + /* Relaxed-SIMD — type signatures from + * https://github.com/WebAssembly/relaxed-simd/blob/ + * main/proposals/relaxed-simd/Overview.md. + * + * unary (1 v128 -> 1 v128): all four trunc variants. + * binary (2 v128 -> 1 v128): swizzle, min/max, + * q15mulr, dot_i8x16_i7x16_s. + * ternary (3 v128 -> 1 v128): madd, nmadd, + * laneselect, dot_i8x16_i7x16_add_s. + * + * The 3-input shape is encoded as POP_V128 (one + * extra v128) + POP2_AND_PUSH (the standard + * 2-pop-1-push) — same pattern bitselect uses + * above so the loader's stack tracker doesn't + * need a new macro. */ + case SIMD_i32x4_relaxed_trunc_f32x4_s: + case SIMD_i32x4_relaxed_trunc_f32x4_u: + case SIMD_i32x4_relaxed_trunc_f64x2_s_zero: + case SIMD_i32x4_relaxed_trunc_f64x2_u_zero: + { + POP_AND_PUSH(VALUE_TYPE_V128, VALUE_TYPE_V128); + break; + } + + case SIMD_i8x16_relaxed_swizzle: + case SIMD_f32x4_relaxed_min: + case SIMD_f32x4_relaxed_max: + case SIMD_f64x2_relaxed_min: + case SIMD_f64x2_relaxed_max: + case SIMD_i16x8_relaxed_q15mulr_s: + case SIMD_i16x8_relaxed_dot_i8x16_i7x16_s: + { + POP2_AND_PUSH(VALUE_TYPE_V128, VALUE_TYPE_V128); + break; + } + + case SIMD_f32x4_relaxed_madd: + case SIMD_f32x4_relaxed_nmadd: + case SIMD_f64x2_relaxed_madd: + case SIMD_f64x2_relaxed_nmadd: + case SIMD_i8x16_relaxed_laneselect: + case SIMD_i16x8_relaxed_laneselect: + case SIMD_i32x4_relaxed_laneselect: + case SIMD_i64x2_relaxed_laneselect: + case SIMD_i32x4_relaxed_dot_i8x16_i7x16_add_s: + { + /* Three v128 inputs: extra POP_V128 first, + * then standard 2-pop-1-push. Same shape as + * SIMD_v128_bitselect above. */ + POP_V128(); + POP2_AND_PUSH(VALUE_TYPE_V128, VALUE_TYPE_V128); + break; + } +#endif /* WASM_ENABLE_RELAXED_SIMD */ + default: { if (error_buf != NULL) { diff --git a/core/iwasm/interpreter/wasm_mini_loader.c b/core/iwasm/interpreter/wasm_mini_loader.c index 1e2aa08c62..f8501e2073 100644 --- a/core/iwasm/interpreter/wasm_mini_loader.c +++ b/core/iwasm/interpreter/wasm_mini_loader.c @@ -500,6 +500,13 @@ load_init_expr(WASMModule *module, const uint8 **p_buf, const uint8 *buf_end, CHECK_BUF(p, p_end, 1); type1 = read_uint8(p); + if (type1 != VALUE_TYPE_FUNCREF + && type1 != VALUE_TYPE_EXTERNREF) { + set_error_buf(error_buf, error_buf_size, + "invalid reference type"); + goto fail; + } + cur_value.ref_index = UINT32_MAX; if (!push_const_expr_stack(&const_expr_ctx, flag, type1, &cur_value, @@ -3321,9 +3328,9 @@ create_module(char *name, char *error_buf, uint32 error_buf_size) } #endif -#if WASM_ENABLE_LIBC_WASI != 0 +#if WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 wasi_args_set_defaults(&module->wasi_args); -#endif /* WASM_ENABLE_LIBC_WASI != 0 */ +#endif /* WASM_ENABLE_LIBC_WASI != 0 || WASM_ENABLE_LIBC_WASI_P2 != 0 */ (void)ret; return module; diff --git a/core/iwasm/interpreter/wasm_opcode.h b/core/iwasm/interpreter/wasm_opcode.h index 1147384131..c94991baf3 100644 --- a/core/iwasm/interpreter/wasm_opcode.h +++ b/core/iwasm/interpreter/wasm_opcode.h @@ -701,6 +701,38 @@ typedef enum WASMSimdEXTOpcode { SIMD_i32x4_trunc_sat_f64x2_u_zero = 0xfd, SIMD_f64x2_convert_low_i32x4_s = 0xfe, SIMD_f64x2_convert_low_i32x4_u = 0xff, + +#if WASM_ENABLE_RELAXED_SIMD != 0 + /* Relaxed-SIMD proposal — finalized as a wasm 2.0 extension. + * The spec uses the same `0xfd` SIMD prefix and reserves + * sub-opcodes 0x100..0x113. Listing the constants here lets + * the loader case-label them directly; the IR encoder/decoder + * widens the SIMD sub-opcode from 1 byte to 2 bytes when this + * macro is set (see emit / GET_OPCODE in wasm_loader.c and + * wasm_interp_fast.c). When WAMR_BUILD_RELAXED_SIMD=0 these + * constants disappear and the SIMD IR / dispatch is + * byte-identical to the legacy-SIMD-only build. */ + SIMD_i8x16_relaxed_swizzle = 0x100, + SIMD_i32x4_relaxed_trunc_f32x4_s = 0x101, + SIMD_i32x4_relaxed_trunc_f32x4_u = 0x102, + SIMD_i32x4_relaxed_trunc_f64x2_s_zero = 0x103, + SIMD_i32x4_relaxed_trunc_f64x2_u_zero = 0x104, + SIMD_f32x4_relaxed_madd = 0x105, + SIMD_f32x4_relaxed_nmadd = 0x106, + SIMD_f64x2_relaxed_madd = 0x107, + SIMD_f64x2_relaxed_nmadd = 0x108, + SIMD_i8x16_relaxed_laneselect = 0x109, + SIMD_i16x8_relaxed_laneselect = 0x10a, + SIMD_i32x4_relaxed_laneselect = 0x10b, + SIMD_i64x2_relaxed_laneselect = 0x10c, + SIMD_f32x4_relaxed_min = 0x10d, + SIMD_f32x4_relaxed_max = 0x10e, + SIMD_f64x2_relaxed_min = 0x10f, + SIMD_f64x2_relaxed_max = 0x110, + SIMD_i16x8_relaxed_q15mulr_s = 0x111, + SIMD_i16x8_relaxed_dot_i8x16_i7x16_s = 0x112, + SIMD_i32x4_relaxed_dot_i8x16_i7x16_add_s = 0x113, +#endif /* WASM_ENABLE_RELAXED_SIMD */ } WASMSimdEXTOpcode; typedef enum WASMAtomicEXTOpcode { diff --git a/core/iwasm/interpreter/wasm_runtime.c b/core/iwasm/interpreter/wasm_runtime.c index a69a267385..cdfd518820 100644 --- a/core/iwasm/interpreter/wasm_runtime.c +++ b/core/iwasm/interpreter/wasm_runtime.c @@ -13,8 +13,8 @@ #include "../common/wasm_runtime_common.h" #include "../common/wasm_memory.h" #if WASM_ENABLE_COMPONENT_MODEL != 0 -#include "../common/component-model/wasm_component_export.h" #include "../common/component-model/wasm_component.h" +#include "../common/component-model/wasm_component_flat.h" #include "../common/component-model/wasm_component_runtime.h" #endif #if WASM_ENABLE_GC != 0 @@ -60,6 +60,265 @@ set_error_buf_v(char *error_buf, uint32 error_buf_size, const char *format, ...) } } +#if WASM_ENABLE_COMPONENT_MODEL != 0 +static bool +validate_canon_import_type(const WASMFunctionInstance *source, + const WASMFuncType *target_type) +{ + uint16 result_count; + + switch (source->canon_type) { + case WASM_COMP_CANON_RESOURCE_DROP: + result_count = 0; + break; + case WASM_COMP_CANON_RESOURCE_NEW: + case WASM_COMP_CANON_RESOURCE_REP: + result_count = 1; + break; + default: + return false; + } + + return target_type->param_count == 1 + && target_type->result_count == result_count + && target_type->types[0] == VALUE_TYPE_I32 + && (!result_count + || target_type->types[target_type->param_count] + == VALUE_TYPE_I32); +} + +static bool +validate_lower_import_type(const WASMFunctionInstance *source, + const WASMFuncType *target_type, + WASMComponentInstance *component_inst) +{ + WASMComponentCoreFuncType expected = { 0 }; + LiftLowerContext context = { 0 }; + bool matches = false; + uint32 i; + + if (!source || !target_type || !component_inst + || !source->component_function || !source->component_function->func_type + || !source->canon_options) { + return false; + } + + context.canonical_opts = source->canon_options; + context.inst = component_inst; + context.borrow_scope_type = BORROW_SCOPE_NONE; + if (!flatten_functype(&context, source->component_function->func_type, + FLATTEN_CONTEXT_LOWER, &expected)) { + goto done; + } + if (expected.params.count != target_type->param_count + || expected.results.count != target_type->result_count) { + goto done; + } + + for (i = 0; i < expected.params.count; i++) { + if (expected.params.val_types[i].tag != WASM_CORE_VALTYPE_NUM + || expected.params.val_types[i].type.num_type + != target_type->types[i]) { + goto done; + } + } + for (i = 0; i < expected.results.count; i++) { + if (expected.results.val_types[i].tag != WASM_CORE_VALTYPE_NUM + || expected.results.val_types[i].type.num_type + != target_type->types[target_type->param_count + i]) { + goto done; + } + } + matches = true; + +done: + free_core_functype(&expected); + return matches; +} + +static bool +validate_prelinked_imports(const WASMModule *module, + const WASMCoreImports *imports, + WASMComponentInstance *component_inst, + char *error_buf, uint32 error_buf_size) +{ + uint32 i; + + if (!imports) + return true; + + if (imports->func_count != module->import_function_count + || imports->tables_count != module->import_table_count + || imports->mem_count != module->import_memory_count + || imports->globals_count != module->import_global_count) { + set_error_buf(error_buf, error_buf_size, + "component import count mismatch"); + return false; + } + + if ((imports->func_count && !imports->func_instance) + || (imports->tables_count && !imports->table_instance) + || (imports->mem_count && !imports->mem_instance) + || (imports->globals_count && !imports->global_instance)) { + set_error_buf(error_buf, error_buf_size, + "component import binding array is null"); + return false; + } + + for (i = 0; i < imports->func_count; i++) { + WASMFunctionInstance *source = imports->func_instance[i]; + WASMFuncType *target_type = + module->import_functions[i].u.function.func_type; + WASMFuncType *source_type; + + if (!source) { + set_error_buf_v(error_buf, error_buf_size, + "component function import %u is null", i); + return false; + } + if (source->is_canon_func) { + if (!validate_canon_import_type(source, target_type)) { + set_error_buf_v(error_buf, error_buf_size, + "incompatible canonical component function " + "import %u (kind=%u, target params=%u, " + "results=%u)", + i, source->canon_type, target_type->param_count, + target_type->result_count); + return false; + } + continue; + } + + /* Synthetic host functions carry their native binding and component + metadata, but their template core func type is not authoritative. + functions_instantiate deliberately retains the consuming core + import's type and copies only this binding metadata. Validate the + native signature against that target type instead. */ + if (source->is_import_func && source->u.func_import + && source->u.func_import->func_ptr_linked + && !source->import_func_inst) { + if (source->u.func_import->signature + && !wasm_native_validate_symbol_signature( + target_type, source->u.func_import->signature)) { + set_error_buf_v(error_buf, error_buf_size, + "native signature mismatch for component " + "function import %u", + i); + return false; + } + continue; + } + if (source->component_function) { + if (!validate_lower_import_type(source, target_type, + component_inst)) { + set_error_buf_v(error_buf, error_buf_size, + "canonical lower import %u type mismatch", i); + return false; + } + continue; + } + + source_type = + source->is_import_func + ? (source->u.func_import ? source->u.func_import->func_type + : NULL) + : (source->u.func ? source->u.func->func_type : NULL); + if (!source_type + || !wasm_type_equal((WASMType *)target_type, + (WASMType *)source_type, module->types, + module->type_count)) { + set_error_buf_v(error_buf, error_buf_size, + "component func import %u type mismatch " + "s=%u/%u t=%u/%u imp=%u canon=%u kind=%u", + i, source_type ? source_type->param_count : 0, + source_type ? source_type->result_count : 0, + target_type->param_count, target_type->result_count, + source->is_import_func, source->is_canon_func, + source->canon_type); + return false; + } + if (source->is_import_func && source->u.func_import->signature + && !wasm_native_validate_symbol_signature( + target_type, source->u.func_import->signature)) { + set_error_buf_v(error_buf, error_buf_size, + "native signature mismatch for component " + "function import %u", + i); + return false; + } + } + + for (i = 0; i < imports->tables_count; i++) { + WASMTableInstance *source = imports->table_instance[i]; + WASMTableType *target = &module->import_tables[i].u.table.table_type; + bool target_has_max = !!(target->flags & MAX_TABLE_SIZE_FLAG); + + if (!source || !source->module_instance + || source->elem_type != target->elem_type + || source->cur_size < target->init_size + || source->is_table64 != !!(target->flags & TABLE64_FLAG) + || (target_has_max + && (!source->has_max || source->max_size > target->max_size))) { + set_error_buf_v(error_buf, error_buf_size, + "component table import %u mismatch " + "cur=%u/%u max=%u/%u has-max=%u/%u", + i, source ? source->cur_size : 0, target->init_size, + source ? source->max_size : 0, target->max_size, + source ? source->has_max : 0, target_has_max); + return false; + } + } + + for (i = 0; i < imports->mem_count; i++) { + WASMMemoryInstance *source = imports->mem_instance[i]; + WASMMemoryType *target = &module->import_memories[i].u.memory.mem_type; + bool target_has_max = !!(target->flags & MAX_PAGE_COUNT_FLAG); + uint32 target_page_size = target->num_bytes_per_page + ? target->num_bytes_per_page + : DEFAULT_NUM_BYTES_PER_PAGE; + + if (!source || source->num_bytes_per_page != target_page_size + || source->cur_page_count < target->init_page_count + || source->is_memory64 != !!(target->flags & MEMORY64_FLAG) + || source->is_shared_memory + != !!(target->flags & SHARED_MEMORY_FLAG) + || (target_has_max + && (!source->has_max + || source->max_page_count > target->max_page_count))) { + set_error_buf_v( + error_buf, error_buf_size, + "component memory import %u mismatch " + "page=%u/%u cur=%u min=%u max=%u/%u " + "has-max=%u/%u mem64=%u/%u shared=%u/%u", + i, source ? source->num_bytes_per_page : 0, target_page_size, + source ? source->cur_page_count : 0, target->init_page_count, + source ? source->max_page_count : 0, target->max_page_count, + source ? source->has_max : 0, target_has_max, + source ? source->is_memory64 : 0, + !!(target->flags & MEMORY64_FLAG), + source ? source->is_shared_memory : 0, + !!(target->flags & SHARED_MEMORY_FLAG)); + return false; + } + } + + for (i = 0; i < imports->globals_count; i++) { + WASMGlobalInstance *source = imports->global_instance[i]; + WASMGlobalType *target = &module->import_globals[i].u.global.type; + + if (!source || source->type != target->val_type + || source->is_mutable != target->is_mutable + || !source->module_instance) { + set_error_buf_v(error_buf, error_buf_size, + "incompatible component global import %u", i); + return false; + } + } + + return true; +} +#endif + WASMModule * wasm_load(uint8 *buf, uint32 size, #if WASM_ENABLE_MULTI_MODULE != 0 @@ -179,11 +438,6 @@ wasm_resolve_import_func(const WASMModule *module, WASMFunctionImport *function) return true; } -#if WASM_ENABLE_COMPONENT_MODEL != 0 - if (is_component_runtime()) { - return true; - } -#endif #if WASM_ENABLE_MULTI_MODULE != 0 if (!wasm_runtime_is_built_in_module(function->module_name)) { @@ -252,6 +506,13 @@ memories_deinstantiate(WASMModuleInstance *module_inst, if (memories) { for (i = 0; i < count; i++) { if (memories[i]) { +#if WASM_ENABLE_COMPONENT_MODEL != 0 + /* Component core imports are borrowed from an earlier core + instance. Do not inspect, decrement, or free them: their + owner may already have been torn down. */ + if (i < module_inst->prelinked_import_memory_count) + continue; +#endif #if WASM_ENABLE_MULTI_MODULE != 0 WASMModule *module = module_inst->module; if (i < module->import_memory_count @@ -315,6 +576,11 @@ memory_instantiate(WASMModuleInstance *module_inst, WASMModuleInstance *parent, (void)flags; #endif /* end of WASM_ENABLE_SHARED_MEMORY */ +#if WASM_ENABLE_COMPONENT_MODEL != 0 + memory->has_max = !!(flags & MAX_PAGE_COUNT_FLAG); + memory->module_instance = module_inst; +#endif + #if WASM_ENABLE_MEMORY64 != 0 if (flags & MEMORY64_FLAG) { memory->is_memory64 = 1; @@ -512,8 +778,11 @@ memory_instantiate(WASMModuleInstance *module_inst, WASMModuleInstance *parent, static WASMMemoryInstance ** memories_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, WASMModuleInstance *parent, uint32 heap_size, - uint32 max_memory_pages, char *error_buf, - uint32 error_buf_size) + uint32 max_memory_pages, +#if WASM_ENABLE_COMPONENT_MODEL != 0 + const WASMCoreImports *prelinked_imports, +#endif + char *error_buf, uint32 error_buf_size) { WASMImport *import; uint32 mem_index = 0, i, @@ -526,6 +795,7 @@ memories_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, if (!(memories = runtime_malloc(total_size, error_buf, error_buf_size))) { return NULL; } + memset(memories, 0, (uint32)total_size); memory = module_inst->global_table_data.memory_instances; @@ -541,6 +811,13 @@ memories_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, uint32 flags = import->u.memory.mem_type.flags; uint32 actual_heap_size = heap_size; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (prelinked_imports) { + memories[mem_index++] = prelinked_imports->mem_instance[i]; + continue; + } +#endif + #if WASM_ENABLE_MULTI_MODULE != 0 if (import->u.memory.import_module != NULL) { WASMModuleInstance *module_inst_linked; @@ -598,10 +875,75 @@ memories_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, /** * Destroy table instances. */ +#if WASM_ENABLE_COMPONENT_MODEL != 0 +static bool +component_table_enable_func_refs(WASMTableInstance *table, char *error_buf, + uint32 error_buf_size) +{ + WASMModuleInstance *owner; + uint32 capacity, i; + uint64 total_size; + + if (table->component_func_refs || table->elem_type != VALUE_TYPE_FUNCREF) + return true; + + capacity = table->max_size; + if (!capacity) + return true; + + total_size = sizeof(WASMFunctionInstance *) * (uint64)capacity; + if (!(table->component_func_refs = + runtime_malloc(total_size, error_buf, error_buf_size))) + return false; + memset(table->component_func_refs, 0, (uint32)total_size); + + /* A table starts carrying cross-instance provenance only when another + component core instance borrows it. Seed existing owner-relative + elements before the borrower can overwrite individual entries. */ + owner = table->module_instance; + for (i = 0; i < table->cur_size; i++) { + uint32 func_idx; +#if WASM_ENABLE_GC == 0 + func_idx = (uint32)table->elems[i]; +#else + func_idx = table->elems[i] == NULL_REF + ? UINT32_MAX + : wasm_func_obj_get_func_idx_bound( + (WASMFuncObjectRef)table->elems[i]); +#endif + if (func_idx == UINT32_MAX) + continue; + if (!owner || !owner->e || func_idx >= owner->e->function_count) { + wasm_runtime_free(table->component_func_refs); + table->component_func_refs = NULL; + set_error_buf(error_buf, error_buf_size, + "unknown function in imported table"); + return false; + } + table->component_func_refs[i] = &owner->e->functions[func_idx]; + } + return true; +} +#endif + static void tables_deinstantiate(WASMModuleInstance *module_inst) { if (module_inst->tables) { +#if WASM_ENABLE_COMPONENT_MODEL != 0 + uint32 i; + + /* Borrowed table pointers may already dangle during component + definition-order teardown. Skip them before dereferencing. */ + for (i = module_inst->prelinked_import_table_count; + i < module_inst->table_count; i++) { + WASMTableInstance *table = module_inst->tables[i]; + if (table && table->component_func_refs) { + wasm_runtime_free(table->component_func_refs); + table->component_func_refs = NULL; + } + } +#endif wasm_runtime_free(module_inst->tables); } #if WASM_ENABLE_MULTI_MODULE != 0 @@ -616,8 +958,11 @@ tables_deinstantiate(WASMModuleInstance *module_inst) */ static WASMTableInstance ** tables_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, - WASMTableInstance *first_table, char *error_buf, - uint32 error_buf_size) + WASMTableInstance *first_table, +#if WASM_ENABLE_COMPONENT_MODEL != 0 + const WASMCoreImports *prelinked_imports, +#endif + char *error_buf, uint32 error_buf_size) { WASMImport *import; uint32 table_index = 0, i; @@ -633,6 +978,7 @@ tables_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, if (!(tables = runtime_malloc(total_size, error_buf, error_buf_size))) { return NULL; } + memset(tables, 0, (uint32)total_size); #if WASM_ENABLE_MULTI_MODULE != 0 if (module->import_table_count > 0 @@ -646,6 +992,17 @@ tables_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, import = module->import_tables; for (i = 0; i < module->import_table_count; i++, import++) { uint32 max_size_fixed = 0; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (prelinked_imports) { + WASMTableInstance *imported_table = + prelinked_imports->table_instance[i]; + if (!component_table_enable_func_refs(imported_table, error_buf, + error_buf_size)) + goto fail; + tables[table_index++] = imported_table; + continue; + } +#endif #if WASM_ENABLE_MULTI_MODULE != 0 WASMTableInstance *table_inst_linked = NULL; WASMModuleInstance *module_inst_linked = NULL; @@ -690,6 +1047,12 @@ tables_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, #endif table->is_table64 = import->u.table.table_type.flags & TABLE64_FLAG; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + table->has_max = + !!(import->u.table.table_type.flags & MAX_TABLE_SIZE_FLAG); + table->module_instance = module_inst; + table->component_func_refs = NULL; +#endif #if WASM_ENABLE_MULTI_MODULE != 0 *table_linked = table_inst_linked; @@ -700,6 +1063,9 @@ tables_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, #endif table->cur_size = table_inst_linked->cur_size; table->max_size = table_inst_linked->max_size; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + table->has_max = table_inst_linked->has_max; +#endif } else #endif @@ -712,7 +1078,6 @@ tables_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, table->cur_size = import->u.table.table_type.init_size; table->max_size = max_size_fixed; } - table = (WASMTableInstance *)((uint8 *)table + (uint32)total_size); #if WASM_ENABLE_MULTI_MODULE != 0 table_linked++; @@ -750,6 +1115,12 @@ tables_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, uninitialized elements */ #endif table->is_table64 = module->tables[i].table_type.flags & TABLE64_FLAG; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + table->has_max = + !!(module->tables[i].table_type.flags & MAX_TABLE_SIZE_FLAG); + table->module_instance = module_inst; + table->component_func_refs = NULL; +#endif table->elem_type = module->tables[i].table_type.elem_type; #if WASM_ENABLE_GC != 0 table->elem_ref_type.elem_ref_type = @@ -757,15 +1128,28 @@ tables_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, #endif table->cur_size = module->tables[i].table_type.init_size; table->max_size = max_size_fixed; - table = (WASMTableInstance *)((uint8 *)table + (uint32)total_size); } bh_assert(table_index == table_count); (void)module_inst; return tables; -#if WASM_ENABLE_MULTI_MODULE != 0 +#if WASM_ENABLE_MULTI_MODULE != 0 || WASM_ENABLE_COMPONENT_MODEL != 0 fail: +#if WASM_ENABLE_MULTI_MODULE != 0 + if (module_inst->e->table_insts_linked) { + wasm_runtime_free(module_inst->e->table_insts_linked); + module_inst->e->table_insts_linked = NULL; + } +#endif +#if WASM_ENABLE_COMPONENT_MODEL != 0 + for (i = module_inst->prelinked_import_table_count; i < table_index; i++) { + if (tables[i] && tables[i]->component_func_refs) { + wasm_runtime_free(tables[i]->component_func_refs); + tables[i]->component_func_refs = NULL; + } + } +#endif wasm_runtime_free(tables); return NULL; #endif @@ -787,6 +1171,9 @@ functions_deinstantiate(WASMFunctionInstance *functions) */ static WASMFunctionInstance * functions_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, +#if WASM_ENABLE_COMPONENT_MODEL != 0 + const WASMCoreImports *prelinked_imports, +#endif char *error_buf, uint32 error_buf_size) { WASMImport *import; @@ -794,11 +1181,23 @@ functions_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, function_count = module->import_function_count + module->function_count; uint64 total_size = sizeof(WASMFunctionInstance) * (uint64)function_count; WASMFunctionInstance *functions, *function; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + WASMFunctionImport *instance_imports; + + /* Component bindings are instance-local. Keep a private import + descriptor beside the function array so concurrent component + instances never mutate the shared parsed module. */ + total_size += + sizeof(WASMFunctionImport) * (uint64)module->import_function_count; +#endif if (!(functions = runtime_malloc(total_size, error_buf, error_buf_size))) { return NULL; } memset(functions, 0, (uint32)total_size); +#if WASM_ENABLE_COMPONENT_MODEL != 0 + instance_imports = (WASMFunctionImport *)(functions + function_count); +#endif total_size = sizeof(void *) * (uint64)module->import_function_count; if (total_size > 0 @@ -826,7 +1225,43 @@ functions_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, } } #endif /* WASM_ENABLE_MULTI_MODULE */ +#if WASM_ENABLE_COMPONENT_MODEL != 0 + instance_imports[i] = import->u.function; + function->u.func_import = &instance_imports[i]; + + if (prelinked_imports) { + WASMFunctionInstance *source = prelinked_imports->func_instance[i]; + + if (source->is_import_func) { + WASMFunctionImport *source_import = source->u.func_import; + function->u.func_import->func_ptr_linked = + source_import->func_ptr_linked; + function->u.func_import->signature = source_import->signature; + function->u.func_import->attachment = source_import->attachment; + function->u.func_import->call_conv_raw = + source_import->call_conv_raw; + function->u.func_import->call_conv_wasm_c_api = + source_import->call_conv_wasm_c_api; + function->import_module_inst = source->import_module_inst; + function->import_func_inst = source->import_func_inst; + } + else { + function->import_module_inst = source->module_instance; + function->import_func_inst = source; + } + +#if WASM_ENABLE_FAST_INTERP != 0 + function->const_cell_num = source->const_cell_num; +#endif + function->is_canon_func = source->is_canon_func; + function->canon_type = source->canon_type; + function->resource = source->resource; + function->canon_options = source->canon_options; + function->component_function = source->component_function; + } +#else function->u.func_import = &import->u.function; +#endif function->param_cell_num = import->u.function.func_type->param_cell_num; function->ret_cell_num = import->u.function.func_type->ret_cell_num; function->param_count = @@ -840,6 +1275,11 @@ functions_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, module_inst->import_func_ptrs[i] = function->u.func_import->func_ptr_linked; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + function->module_instance = module_inst; + function->func_idx = i; +#endif + function++; } @@ -864,6 +1304,11 @@ functions_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, function->const_cell_num = function->u.func->const_cell_num; #endif +#if WASM_ENABLE_COMPONENT_MODEL != 0 + function->module_instance = module_inst; + function->func_idx = module->import_function_count + i; +#endif + function++; } bh_assert((uint32)(function - functions) == function_count); @@ -1257,6 +1702,9 @@ get_init_value_recursive(WASMModule *module, InitializerExpression *expr, */ static WASMGlobalInstance * globals_instantiate(WASMModule *module, WASMModuleInstance *module_inst, +#if WASM_ENABLE_COMPONENT_MODEL != 0 + const WASMCoreImports *prelinked_imports, +#endif char *error_buf, uint32 error_buf_size) { WASMImport *import; @@ -1268,6 +1716,7 @@ globals_instantiate(WASMModule *module, WASMModuleInstance *module_inst, if (!(globals = runtime_malloc(total_size, error_buf, error_buf_size))) { return NULL; } + memset(globals, 0, (uint32)total_size); /* instantiate globals from import section */ global = globals; @@ -1279,32 +1728,56 @@ globals_instantiate(WASMModule *module, WASMModuleInstance *module_inst, #if WASM_ENABLE_GC != 0 global->ref_type = global_import->ref_type; #endif -#if WASM_ENABLE_MULTI_MODULE != 0 - if (global_import->import_module) { - if (!(global->import_module_inst = get_sub_module_inst( - module_inst, global_import->import_module))) { - set_error_buf(error_buf, error_buf_size, "unknown global"); - goto fail; - } - - if (!(global->import_global_inst = wasm_lookup_global( - global->import_module_inst, global_import->field_name))) { - set_error_buf(error_buf, error_buf_size, "unknown global"); - goto fail; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + global->module_instance = module_inst; + if (prelinked_imports) { + WASMGlobalInstance *source = prelinked_imports->global_instance[i]; + WASMModuleInstance *source_module = source->module_instance; + uint32 value_size = wasm_value_type_size(source->type); + + /* Flatten import chains so the hot interpreter path only needs + one indirection and always reaches the owning global data. */ + while (source->import_global_inst) { + source_module = source->import_module_inst; + source = source->import_global_inst; } - - /* The linked global instance has been initialized, we - just need to copy the value. */ - global->initial_value = - global_import->import_global_linked->init_expr.u.unary.v; + global->import_module_inst = source_module; + global->import_global_inst = source; + bh_memcpy_s(&global->initial_value, sizeof(WASMValue), + source_module->global_data + source->data_offset, + value_size); } else #endif { - /* native globals share their initial_values in one module */ - bh_memcpy_s(&(global->initial_value), sizeof(WASMValue), - &(global_import->global_data_linked), - sizeof(WASMValue)); +#if WASM_ENABLE_MULTI_MODULE != 0 + if (global_import->import_module) { + if (!(global->import_module_inst = get_sub_module_inst( + module_inst, global_import->import_module))) { + set_error_buf(error_buf, error_buf_size, "unknown global"); + goto fail; + } + + if (!(global->import_global_inst = + wasm_lookup_global(global->import_module_inst, + global_import->field_name))) { + set_error_buf(error_buf, error_buf_size, "unknown global"); + goto fail; + } + + /* The linked global instance has been initialized, we + just need to copy the value. */ + global->initial_value = + global_import->import_global_linked->init_expr.u.unary.v; + } + else +#endif + { + /* native globals share their initial_values in one module */ + bh_memcpy_s(&(global->initial_value), sizeof(WASMValue), + &(global_import->global_data_linked), + sizeof(WASMValue)); + } } #if WASM_ENABLE_FAST_JIT != 0 bh_assert(global_data_offset == global_import->data_offset); @@ -1322,6 +1795,9 @@ globals_instantiate(WASMModule *module, WASMModuleInstance *module_inst, global->type = module->globals[i].type.val_type; global->is_mutable = module->globals[i].type.is_mutable; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + global->module_instance = module_inst; +#endif #if WASM_ENABLE_FAST_JIT != 0 bh_assert(global_data_offset == module->globals[i].data_offset); #endif @@ -1583,19 +2059,19 @@ export_tags_instantiate(const WASMModule *module, } #endif /* end of WASM_ENABLE_TAGS != 0 */ -#if WASM_ENABLE_MULTI_MEMORY != 0 static void -export_memories_deinstantiate(WASMExportMemInstance *memories) +export_tables_deinstantiate(WASMExportTabInstance *tables) { - if (memories) - wasm_runtime_free(memories); + if (tables) + wasm_runtime_free(tables); } +#if WASM_ENABLE_MULTI_MEMORY != 0 || WASM_ENABLE_COMPONENT_MODEL != 0 static void -export_tables_deinstantiate(WASMExportTabInstance *tables) +export_memories_deinstantiate(WASMExportMemInstance *memories) { - if (tables) - wasm_runtime_free(tables); + if (memories) + wasm_runtime_free(memories); } static WASMExportMemInstance * @@ -1625,9 +2101,9 @@ export_memories_instantiate(const WASMModule *module, bh_assert((uint32)(export_memory - export_memories) == export_mem_count); return export_memories; } -#endif /* end of if WASM_ENABLE_MULTI_MEMORY != 0 */ +#endif /* WASM_ENABLE_MULTI_MEMORY || WASM_ENABLE_COMPONENT_MODEL */ -#if WASM_ENABLE_MULTI_MODULE != 0 +#if WASM_ENABLE_MULTI_MODULE != 0 || WASM_ENABLE_COMPONENT_MODEL != 0 static void export_globals_deinstantiate(WASMExportGlobInstance *globals) { @@ -1663,7 +2139,7 @@ export_globals_instantiate(const WASMModule *module, return export_globals; } -#endif /* end of if WASM_ENABLE_MULTI_MODULE != 0 */ +#endif /* WASM_ENABLE_MULTI_MODULE || WASM_ENABLE_COMPONENT_MODEL */ static WASMFunctionInstance * lookup_post_instantiate_func(WASMModuleInstance *module_inst, @@ -1686,7 +2162,9 @@ lookup_post_instantiate_func(WASMModuleInstance *module_inst, static bool execute_post_instantiate_functions(WASMModuleInstance *module_inst, - bool is_sub_inst, WASMExecEnv *exec_env_main) + bool is_sub_inst, + bool is_component_core_inst, + WASMExecEnv *exec_env_main) { WASMFunctionInstance *start_func = module_inst->e->start_function; WASMFunctionInstance *initialize_func = NULL; @@ -1708,7 +2186,7 @@ execute_post_instantiate_functions(WASMModuleInstance *module_inst, * the environment at most once, and that none of their other exports * are accessed before that call. */ - if (!is_sub_inst && module->import_wasi_api) { + if (!is_sub_inst && !is_component_core_inst && module->import_wasi_api) { initialize_func = lookup_post_instantiate_func(module_inst, "_initialize"); } @@ -1716,7 +2194,7 @@ execute_post_instantiate_functions(WASMModuleInstance *module_inst, /* Execute possible "__post_instantiate" function if wasm app is compiled by emsdk's early version */ - if (!is_sub_inst) { + if (!is_sub_inst && !is_component_core_inst) { post_inst_func = lookup_post_instantiate_func(module_inst, "__post_instantiate"); } @@ -1724,7 +2202,7 @@ execute_post_instantiate_functions(WASMModuleInstance *module_inst, #if WASM_ENABLE_BULK_MEMORY != 0 /* Only execute the memory init function for main instance since the data segments will be dropped once initialized */ - if (!is_sub_inst + if (!is_sub_inst && !is_component_core_inst #if WASM_ENABLE_LIBC_WASI != 0 && !module->import_wasi_api #endif @@ -2471,11 +2949,15 @@ wasm_set_running_mode(WASMModuleInstance *module_inst, RunningMode running_mode) /** * Instantiate module */ -WASMModuleInstance * -wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, - WASMExecEnv *exec_env_main, - const struct InstantiationArgs2 *args, char *error_buf, - uint32 error_buf_size) +static WASMModuleInstance * +wasm_instantiate_internal(WASMModule *module, WASMModuleInstance *parent, + WASMExecEnv *exec_env_main, + const struct InstantiationArgs2 *args, +#if WASM_ENABLE_COMPONENT_MODEL != 0 + WASMComponentInstance *component_inst, + const WASMCoreImports *prelinked_imports, +#endif + char *error_buf, uint32 error_buf_size) { WASMModuleInstance *module_inst; WASMGlobalInstance *globals = NULL, *global; @@ -2499,6 +2981,12 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, if (!module) return NULL; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (!validate_prelinked_imports(module, prelinked_imports, component_inst, + error_buf, error_buf_size)) + return NULL; +#endif + /* Check the heap size */ heap_size = align_uint(heap_size, 8); if (heap_size > APP_HEAP_SIZE_MAX) @@ -2566,6 +3054,21 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, (WASMModuleInstanceExtra *)((uint8 *)module_inst + extra_info_offset); wasm_runtime_set_custom_data_internal( (WASMModuleInstanceCommon *)module_inst, args->custom_data); +#if WASM_ENABLE_COMPONENT_MODEL != 0 + module_inst->comp_instance = component_inst; + module_inst->core_instance_idx = + component_inst ? component_inst->core_module_instances_count : 0; + module_inst->prelinked_import_memory_count = + prelinked_imports ? module->import_memory_count : 0; + module_inst->prelinked_import_table_count = + prelinked_imports ? module->import_table_count : 0; +#if WASM_ENABLE_LIBC_WASI_P2 != 0 + if (component_inst) { + wasm_runtime_set_wasi_ctx((WASMModuleInstanceCommon *)module_inst, + component_inst->wasi_ctx); + } +#endif +#endif #if WASM_ENABLE_THREAD_MGR != 0 if (os_mutex_init(&module_inst->e->common.exception_lock) != 0) { wasm_runtime_free(module_inst); @@ -2650,8 +3153,11 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, /* Instantiate global firstly to get the mutable data size */ global_count = module->import_global_count + module->global_count; if (global_count - && !(globals = globals_instantiate(module, module_inst, error_buf, - error_buf_size))) { + && !(globals = globals_instantiate(module, module_inst, +#if WASM_ENABLE_COMPONENT_MODEL != 0 + prelinked_imports, +#endif + error_buf, error_buf_size))) { goto fail; } module_inst->e->global_count = global_count; @@ -2673,7 +3179,7 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, /* export */ module_inst->export_func_count = get_export_count(module, EXPORT_KIND_FUNC); -#if WASM_ENABLE_MULTI_MEMORY != 0 +#if WASM_ENABLE_MULTI_MEMORY != 0 || WASM_ENABLE_COMPONENT_MODEL != 0 module_inst->export_memory_count = get_export_count(module, EXPORT_KIND_MEMORY); #endif @@ -2692,18 +3198,28 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, if ((module_inst->memory_count > 0 && !(module_inst->memories = memories_instantiate( module, module_inst, parent, heap_size, max_memory_pages, +#if WASM_ENABLE_COMPONENT_MODEL != 0 + prelinked_imports, +#endif error_buf, error_buf_size))) || (module_inst->table_count > 0 && !(module_inst->tables = tables_instantiate(module, module_inst, first_table, +#if WASM_ENABLE_COMPONENT_MODEL != 0 + prelinked_imports, +#endif error_buf, error_buf_size))) || (module_inst->export_table_count > 0 && !(module_inst->export_tables = export_tables_instantiate( module, module_inst, module_inst->export_table_count, error_buf, error_buf_size))) || (module_inst->e->function_count > 0 - && !(module_inst->e->functions = functions_instantiate( - module, module_inst, error_buf, error_buf_size))) + && !(module_inst->e->functions = + functions_instantiate(module, module_inst, +#if WASM_ENABLE_COMPONENT_MODEL != 0 + prelinked_imports, +#endif + error_buf, error_buf_size))) || (module_inst->export_func_count > 0 && !(module_inst->export_functions = export_functions_instantiate( module, module_inst, module_inst->export_func_count, @@ -2717,13 +3233,13 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, module, module_inst, module_inst->e->export_tag_count, error_buf, error_buf_size))) #endif -#if WASM_ENABLE_MULTI_MODULE != 0 +#if WASM_ENABLE_MULTI_MODULE != 0 || WASM_ENABLE_COMPONENT_MODEL != 0 || (module_inst->export_global_count > 0 && !(module_inst->export_globals = export_globals_instantiate( module, module_inst, module_inst->export_global_count, error_buf, error_buf_size))) #endif -#if WASM_ENABLE_MULTI_MEMORY != 0 +#if WASM_ENABLE_MULTI_MEMORY != 0 || WASM_ENABLE_COMPONENT_MODEL != 0 || (module_inst->export_memory_count > 0 && !(module_inst->export_memories = export_memories_instantiate( module, module_inst, module_inst->export_memory_count, @@ -3009,6 +3525,17 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, (void *)table->init_expr.u.unary.v.gc_obj); for (j = 0; j < table_inst->cur_size; j++) { *(table_data + j) = table->init_expr.u.unary.v.gc_obj; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (table_inst->component_func_refs) { + uint32 func_idx = table->init_expr.u.unary.v.ref_index; + table_inst->component_func_refs[j] = + table->init_expr.init_expr_type + == INIT_EXPR_TYPE_FUNCREF_CONST + && func_idx != UINT32_MAX + ? &module_inst->e->functions[func_idx] + : NULL; + } +#endif } } #endif /* end of WASM_ENABLE_GC != 0 */ @@ -3020,6 +3547,9 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, /* has check it in loader */ WASMTableInstance *table = module_inst->tables[table_seg->table_index]; table_elem_type_t *table_data; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + WASMFunctionInstance **component_func_refs; +#endif WASMValue offset_value; uint32 j; #if WASM_ENABLE_REF_TYPES != 0 || WASM_ENABLE_GC != 0 @@ -3064,6 +3594,9 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, #endif /* end of WASM_ENABLE_REF_TYPES != 0 || WASM_ENABLE_GC != 0 */ table_data = table->elems; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + component_func_refs = table->component_func_refs; +#endif #if WASM_ENABLE_MULTI_MODULE != 0 if (table_seg->table_index < module->import_table_count && module_inst->e->table_insts_linked[table_seg->table_index]) { @@ -3313,6 +3846,26 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, } *(table_data + offset_value.i32 + j) = (table_elem_type_t)ref; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (component_func_refs) { + uint32 table_offset = (uint32)offset_value.i32 + j; + + if (flag == INIT_EXPR_TYPE_FUNCREF_CONST + && init_expr->u.unary.v.ref_index != UINT32_MAX) { + uint32 func_idx = init_expr->u.unary.v.ref_index; + if (func_idx >= module_inst->e->function_count) { + set_error_buf(error_buf, error_buf_size, + "unknown function in table segment"); + goto fail; + } + component_func_refs[table_offset] = + &module_inst->e->functions[func_idx]; + } + else { + component_func_refs[table_offset] = NULL; + } + } +#endif } } @@ -3388,13 +3941,19 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, } if (module->start_function != (uint32)-1) { - /* TODO: fix start function can be import function issue */ - if (module->start_function >= module->import_function_count) - module_inst->e->start_function = - &module_inst->e->functions[module->start_function]; + /* The start index may name an imported function. Component imports + are already bound at this point, so both imported and defined + starts are safe to execute. */ + module_inst->e->start_function = + &module_inst->e->functions[module->start_function]; } if (!execute_post_instantiate_functions(module_inst, is_sub_inst, +#if WASM_ENABLE_COMPONENT_MODEL != 0 + component_inst != NULL, +#else + false, +#endif exec_env_main)) { set_error_buf(error_buf, error_buf_size, module_inst->cur_exception); goto fail; @@ -3413,6 +3972,37 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, return NULL; } +WASMModuleInstance * +wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, + WASMExecEnv *exec_env_main, + const struct InstantiationArgs2 *args, char *error_buf, + uint32 error_buf_size) +{ + return wasm_instantiate_internal(module, parent, exec_env_main, args, +#if WASM_ENABLE_COMPONENT_MODEL != 0 + NULL, NULL, +#endif + error_buf, error_buf_size); +} + +#if WASM_ENABLE_COMPONENT_MODEL != 0 +WASMModuleInstance * +wasm_instantiate_with_imports(WASMModule *module, + WASMComponentInstance *component_inst, + const WASMCoreImports *imports, + const struct InstantiationArgs2 *args, + char *error_buf, uint32 error_buf_size) +{ + if (module && module->import_count && !imports) { + set_error_buf(error_buf, error_buf_size, + "component core imports were not supplied"); + return NULL; + } + return wasm_instantiate_internal(module, NULL, NULL, args, component_inst, + imports, error_buf, error_buf_size); +} +#endif + #if WASM_ENABLE_DUMP_CALL_STACK != 0 static void destroy_c_api_frames(Vector *frames) @@ -3523,11 +4113,11 @@ wasm_deinstantiate(WASMModuleInstance *module_inst, bool is_sub_inst) export_tags_deinstantiate(module_inst->e->export_tags); #endif -#if WASM_ENABLE_MULTI_MODULE != 0 +#if WASM_ENABLE_MULTI_MODULE != 0 || WASM_ENABLE_COMPONENT_MODEL != 0 export_globals_deinstantiate(module_inst->export_globals); #endif -#if WASM_ENABLE_MULTI_MEMORY != 0 +#if WASM_ENABLE_MULTI_MEMORY != 0 || WASM_ENABLE_COMPONENT_MODEL != 0 export_memories_deinstantiate(module_inst->export_memories); #endif @@ -4070,6 +4660,22 @@ wasm_enlarge_table(WASMModuleInstance *module_inst, uint32 table_idx, new_table_data_start = table_inst->elems + table_inst->cur_size; for (i = 0; i < inc_size; ++i) { new_table_data_start[i] = init_val; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (table_inst->component_func_refs) { + uint32 func_idx; +#if WASM_ENABLE_GC == 0 + func_idx = (uint32)init_val; +#else + func_idx = init_val == NULL_REF ? UINT32_MAX + : wasm_func_obj_get_func_idx_bound( + (WASMFuncObjectRef)init_val); +#endif + if (!wasm_component_set_table_func_ref(module_inst, table_inst, + table_inst->cur_size + i, + func_idx)) + return false; + } +#endif } table_inst->cur_size = total_size; @@ -4086,6 +4692,10 @@ call_indirect(WASMExecEnv *exec_env, uint32 tbl_idx, uint32 tbl_elem_idx, table_elem_type_t tbl_elem_val = NULL_REF; uint32 func_idx = 0; WASMFunctionInstance *func_inst = NULL; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + WASMFunctionInstance table_func_proxy; + WASMFunctionImport table_func_proxy_import; +#endif module_inst = (WASMModuleInstance *)exec_env->module_inst; bh_assert(module_inst); @@ -4101,29 +4711,52 @@ call_indirect(WASMExecEnv *exec_env, uint32 tbl_idx, uint32 tbl_elem_idx, goto got_exception; } - tbl_elem_val = ((table_elem_type_t *)table_inst->elems)[tbl_elem_idx]; - if (tbl_elem_val == NULL_REF) { - wasm_set_exception(module_inst, "uninitialized element"); - goto got_exception; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (table_inst->component_func_refs) { + WASMFunctionInstance *source = + table_inst->component_func_refs[tbl_elem_idx]; + if (!source) { + wasm_set_exception(module_inst, "uninitialized element"); + goto got_exception; + } + if (source->module_instance == module_inst) { + func_inst = source; + } + else if (!wasm_component_build_table_func_proxy( + module_inst, source, &table_func_proxy, + &table_func_proxy_import)) { + wasm_set_exception(module_inst, "unknown function"); + goto got_exception; + } + else { + func_inst = &table_func_proxy; + } } - + else +#endif + { + tbl_elem_val = ((table_elem_type_t *)table_inst->elems)[tbl_elem_idx]; #if WASM_ENABLE_GC == 0 - func_idx = (uint32)tbl_elem_val; + func_idx = (uint32)tbl_elem_val; + if (func_idx == (uint32)-1) { + wasm_set_exception(module_inst, "uninitialized element"); + goto got_exception; + } #else - func_idx = - wasm_func_obj_get_func_idx_bound((WASMFuncObjectRef)tbl_elem_val); + if (tbl_elem_val == NULL_REF) { + wasm_set_exception(module_inst, "uninitialized element"); + goto got_exception; + } + func_idx = + wasm_func_obj_get_func_idx_bound((WASMFuncObjectRef)tbl_elem_val); #endif - - /** - * we insist to call functions owned by the module itself - **/ - if (func_idx >= module_inst->e->function_count) { - wasm_set_exception(module_inst, "unknown function"); - goto got_exception; + if (func_idx >= module_inst->e->function_count) { + wasm_set_exception(module_inst, "unknown function"); + goto got_exception; + } + func_inst = module_inst->e->functions + func_idx; } - func_inst = module_inst->e->functions + func_idx; - if (check_type_idx) { WASMType *cur_type = module_inst->module->types[type_idx]; WASMType *cur_func_type; @@ -4133,7 +4766,9 @@ call_indirect(WASMExecEnv *exec_env, uint32 tbl_idx, uint32 tbl_elem_idx, else cur_func_type = (WASMType *)func_inst->u.func->func_type; - if (cur_type != cur_func_type) { + if (!wasm_type_equal(cur_type, cur_func_type, + module_inst->module->types, + module_inst->module->type_count)) { wasm_set_exception(module_inst, "indirect call type mismatch"); goto got_exception; } @@ -4378,10 +5013,32 @@ wasm_interp_copy_callstack(WASMExecEnv *exec_env, WASMCApiFrame *buffer, while (cur_frame && (uint8_t *)cur_frame >= bottom && (uint8_t *)cur_frame + sizeof(WASMInterpFrame) <= top_boundary && count < (skip_n + length)) { + uintptr_t functions_begin, functions_end, function_addr; + uint64 functions_size; + if (!cur_frame->function) { cur_frame = cur_frame->prev_frame; continue; } + + /* Component cross-core calls may leave a stack-local proxy in a + * frame. Pointer subtraction against the root instance's function + * array would be undefined and could produce an attacker-controlled + * index. This allocation-free API cannot safely chase the proxy, so + * omit frames which are not provably owned by the current instance. */ + functions_begin = (uintptr_t)module_inst->e->functions; + functions_size = sizeof(WASMFunctionInstance) + * (uint64)module_inst->e->function_count; + function_addr = (uintptr_t)cur_frame->function; + if (functions_size > UINTPTR_MAX - functions_begin + || (functions_end = functions_begin + (uintptr_t)functions_size) + < functions_begin + || function_addr < functions_begin || function_addr >= functions_end + || (function_addr - functions_begin) % sizeof(WASMFunctionInstance) + != 0) { + cur_frame = cur_frame->prev_frame; + continue; + } if (count < skip_n) { ++count; cur_frame = cur_frame->prev_frame; @@ -4391,8 +5048,8 @@ wasm_interp_copy_callstack(WASMExecEnv *exec_env, WASMCApiFrame *buffer, record_frame.module_offset = 0; // It's safe to dereference module_inst->e because "e" is asigned only // once in wasm_instantiate - record_frame.func_index = - (uint32)(cur_frame->function - module_inst->e->functions); + record_frame.func_index = (uint32)((function_addr - functions_begin) + / sizeof(WASMFunctionInstance)); buffer[count - skip_n] = record_frame; cur_frame = cur_frame->prev_frame; ++count; @@ -4402,12 +5059,61 @@ wasm_interp_copy_callstack(WASMExecEnv *exec_env, WASMCApiFrame *buffer, #endif // WASM_ENABLE_COPY_CALL_STACK #if WASM_ENABLE_DUMP_CALL_STACK != 0 +#if WASM_ENABLE_COMPONENT_MODEL != 0 +static bool +resolve_component_call_stack_function(WASMModuleInstance *fallback_module, + WASMFunctionInstance **function, + WASMModuleInstance **function_module, + uint32 *function_index) +{ + WASMFunctionInstance *candidate = *function; + uint32 depth = 0; + + while (candidate && depth++ < 1024) { + WASMModuleInstance *candidate_module = candidate->module_instance + ? candidate->module_instance + : fallback_module; + uintptr_t functions_begin, functions_end, candidate_addr; + uint64 functions_size; + + if (candidate_module && candidate_module->e + && candidate_module->e->functions) { + functions_begin = (uintptr_t)candidate_module->e->functions; + functions_size = sizeof(WASMFunctionInstance) + * (uint64)candidate_module->e->function_count; + functions_end = functions_begin + (uintptr_t)functions_size; + candidate_addr = (uintptr_t)candidate; + if (functions_end >= functions_begin + && candidate_addr >= functions_begin + && candidate_addr < functions_end + && (candidate_addr - functions_begin) + % sizeof(WASMFunctionInstance) + == 0) { + *function = candidate; + *function_module = candidate_module; + *function_index = (uint32)((candidate_addr - functions_begin) + / sizeof(WASMFunctionInstance)); + return true; + } + } + + /* Cross-core table calls use a stack-local import proxy. Resolve it + to the stable function instance before recording a call frame. */ + if (!candidate->is_import_func || !candidate->import_func_inst) { + break; + } + candidate = candidate->import_func_inst; + } + + return false; +} +#endif + bool wasm_interp_create_call_stack(struct WASMExecEnv *exec_env) { WASMModuleInstance *module_inst = (WASMModuleInstance *)wasm_exec_env_get_module_inst(exec_env); - WASMModule *module = module_inst->module; WASMInterpFrame *first_frame, *cur_frame = wasm_exec_env_get_cur_frame(exec_env); uint32 n = 0; @@ -4433,6 +5139,8 @@ wasm_interp_create_call_stack(struct WASMExecEnv *exec_env) while (cur_frame) { WASMCApiFrame frame = { 0 }; WASMFunctionInstance *func_inst = cur_frame->function; + WASMModuleInstance *frame_module_inst = module_inst; + WASMModule *frame_module; const char *func_name = NULL; const uint8 *func_code_base = NULL; uint32 max_local_cell_num, max_stack_cell_num; @@ -4443,10 +5151,22 @@ wasm_interp_create_call_stack(struct WASMExecEnv *exec_env) continue; } +#if WASM_ENABLE_COMPONENT_MODEL != 0 + if (!resolve_component_call_stack_function(module_inst, &func_inst, + &frame_module_inst, + &frame.func_index)) { + cur_frame = cur_frame->prev_frame; + continue; + } +#else + frame.func_index = + (uint32)(func_inst - frame_module_inst->e->functions); +#endif + frame_module = frame_module_inst->module; + /* place holder, will overwrite it in wasm_c_api */ - frame.instance = module_inst; + frame.instance = frame_module_inst; frame.module_offset = 0; - frame.func_index = (uint32)(func_inst - module_inst->e->functions); func_code_base = wasm_get_func_code(func_inst); if (!cur_frame->ip || !func_code_base) { @@ -4454,31 +5174,35 @@ wasm_interp_create_call_stack(struct WASMExecEnv *exec_env) } else { #if WASM_ENABLE_FAST_INTERP == 0 - frame.func_offset = (uint32)(cur_frame->ip - module->load_addr); + frame.func_offset = + (uint32)(cur_frame->ip - frame_module->load_addr); #else frame.func_offset = (uint32)(cur_frame->ip - func_code_base); #endif } - func_name = get_func_name_from_index(module_inst, frame.func_index); + func_name = + get_func_name_from_index(frame_module_inst, frame.func_index); frame.func_name_wp = func_name; - if (frame.func_index >= module->import_function_count) { + if (frame.func_index >= frame_module->import_function_count) { uint32 wasm_func_idx = - frame.func_index - module->import_function_count; + frame.func_index - frame_module->import_function_count; max_local_cell_num = - module->functions[wasm_func_idx]->param_cell_num - + module->functions[wasm_func_idx]->local_cell_num; + frame_module->functions[wasm_func_idx]->param_cell_num + + frame_module->functions[wasm_func_idx]->local_cell_num; max_stack_cell_num = - module->functions[wasm_func_idx]->max_stack_cell_num; + frame_module->functions[wasm_func_idx]->max_stack_cell_num; all_cell_num = max_local_cell_num + max_stack_cell_num; #if WASM_ENABLE_FAST_INTERP != 0 - all_cell_num += module->functions[wasm_func_idx]->const_cell_num; + all_cell_num += + frame_module->functions[wasm_func_idx]->const_cell_num; #endif } else { WASMFuncType *func_type = - module->import_functions[frame.func_index].u.function.func_type; + frame_module->import_functions[frame.func_index] + .u.function.func_type; max_local_cell_num = func_type->param_cell_num > 2 ? func_type->param_cell_num : 2; max_stack_cell_num = 0; diff --git a/core/iwasm/interpreter/wasm_runtime.h b/core/iwasm/interpreter/wasm_runtime.h index a92db98dc2..1cb22cfd4b 100644 --- a/core/iwasm/interpreter/wasm_runtime.h +++ b/core/iwasm/interpreter/wasm_runtime.h @@ -28,6 +28,7 @@ typedef struct WASMGlobalInstance WASMGlobalInstance; #if WASM_ENABLE_COMPONENT_MODEL != 0 typedef struct WASMComponentInstance WASMComponentInstance; +typedef struct WASMCoreImports WASMCoreImports; typedef struct WASMComponentCanonOptsInstance WASMComponentCanonOptsInstance; typedef struct WASMComponentFunctionInstance WASMComponentFunctionInstance; typedef struct CanonicalOptions CanonicalOptions; @@ -139,7 +140,13 @@ struct WASMMemoryInstance { /* Four-byte paddings to ensure the layout of WASMMemoryInstance is the same * in both 64-bit and 32-bit */ +#if WASM_ENABLE_COMPONENT_MODEL != 0 + /* Preserve whether the defining core memory declared a maximum. */ + uint8 has_max; + uint8 _paddings[3]; +#else uint8 _paddings[4]; +#endif /* Number bytes per page */ uint32 num_bytes_per_page; @@ -176,6 +183,13 @@ struct WASMMemoryInstance { MemBound mem_bound_check_8bytes; MemBound mem_bound_check_16bytes; #endif +#if WASM_ENABLE_COMPONENT_MODEL != 0 + /* The canonical ABI can pass an offset through a core module which does + not itself import the selected memory. Preserve the defining instance + so a raw component host call can expose the correct linear memory via + its execution environment. */ + DefPointer(WASMModuleInstance *, module_instance); +#endif }; /* WASMTableInstance is used to represent table instance in @@ -189,7 +203,13 @@ struct WASMTableInstance { /* The element type */ uint8 elem_type; uint8 is_table64; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + /* Preserve whether the defining core table declared a maximum. */ + uint8 has_max; + uint8 __padding__[5]; +#else uint8 __padding__[6]; +#endif union { #if WASM_ENABLE_GC != 0 WASMRefType *elem_ref_type; @@ -200,6 +220,14 @@ struct WASMTableInstance { uint32 cur_size; /* Maximum size */ uint32 max_size; +#if WASM_ENABLE_COMPONENT_MODEL != 0 + /* Core wasm stores funcrefs as module-relative indexes. Keep the owning + instance for lifetime accounting and an instance-local provenance + sidecar so a borrowed table can contain functions written by several + different core instances without rewriting its public element data. */ + DefPointer(WASMModuleInstance *, module_instance); + DefPointer(WASMFunctionInstance **, component_func_refs); +#endif /* Table elements */ table_elem_type_t elems[1]; }; @@ -222,6 +250,11 @@ struct WASMGlobalInstance { WASMModuleInstance *import_module_inst; WASMGlobalInstance *import_global_inst; #endif +#if WASM_ENABLE_COMPONENT_MODEL != 0 + /* Owner of this global, including globals re-exported through an inline + core instance. */ + WASMModuleInstance *module_instance; +#endif }; struct WASMFunctionInstance { @@ -274,6 +307,52 @@ struct WASMFunctionInstance { #endif }; +#if WASM_ENABLE_COMPONENT_MODEL != 0 +static inline bool +wasm_component_build_table_func_proxy(WASMModuleInstance *caller, + WASMFunctionInstance *source, + WASMFunctionInstance *proxy, + WASMFunctionImport *proxy_import) +{ + if (!caller || !source || !source->module_instance || !proxy + || !proxy_import) + return false; + + memset(proxy, 0, sizeof(*proxy)); + memset(proxy_import, 0, sizeof(*proxy_import)); + if (source->is_import_func) { + if (!source->u.func_import) + return false; + *proxy_import = *source->u.func_import; + } + else { + if (!source->u.func) + return false; + proxy_import->func_type = source->u.func->func_type; + } + + proxy->is_import_func = true; + proxy->param_count = source->param_count; + proxy->param_cell_num = source->param_cell_num; + proxy->ret_cell_num = source->ret_cell_num; + proxy->param_types = source->param_types; + proxy->u.func_import = proxy_import; + proxy->import_module_inst = source->module_instance; + proxy->import_func_inst = source; +#if WASM_ENABLE_FAST_INTERP != 0 + proxy->const_cell_num = source->const_cell_num; +#endif + proxy->module_instance = caller; + proxy->func_idx = source->func_idx; + proxy->is_canon_func = source->is_canon_func; + proxy->canon_type = source->canon_type; + proxy->resource = source->resource; + proxy->canon_options = source->canon_options; + proxy->component_function = source->component_function; + return true; +} +#endif + #if WASM_ENABLE_TAGS != 0 struct WASMTagInstance { bool is_import_tag; @@ -490,8 +569,17 @@ struct WASMModuleInstance { uint32 reserved[7]; #if WASM_ENABLE_COMPONENT_MODEL != 0 - WASMComponentInstance *comp_instance; + DefPointer(WASMComponentInstance *, comp_instance); uint32 core_instance_idx; + /* Component core imports borrow their defining instance's memories. + These entries must never be deallocated by the importing instance. */ + uint32 prelinked_import_memory_count; + /* The same ownership rule applies to imported tables and their funcref + provenance sidecars. */ + uint32 prelinked_import_table_count; + /* Keep the component extension 64-bit aligned in AOT-enabled 32-bit + builds, matching the fixed-width DefPointer fields above. */ + uint32 component_fields_padding; #endif /* @@ -512,6 +600,115 @@ struct WASMModuleInstance { } global_table_data; }; +#if WASM_ENABLE_COMPONENT_MODEL != 0 +static inline bool +wasm_component_set_table_func_ref(WASMModuleInstance *module_inst, + WASMTableInstance *table, uint32 elem_idx, + uint32 func_idx) +{ + if (!table->component_func_refs) + return true; + + if (func_idx == UINT32_MAX) { + table->component_func_refs[elem_idx] = NULL; + return true; + } + + if (!module_inst->e || func_idx >= module_inst->e->function_count) + return false; + + table->component_func_refs[elem_idx] = &module_inst->e->functions[func_idx]; + return true; +} + +static inline bool +wasm_component_table_elem_to_local_ref(WASMModuleInstance *module_inst, + WASMTableInstance *table, + uint32 elem_idx, + WASMFunctionInstance **func_ref) +{ + uint32 func_idx; + +#if WASM_ENABLE_GC == 0 + func_idx = (uint32)table->elems[elem_idx]; +#else + func_idx = table->elems[elem_idx] == NULL_REF + ? UINT32_MAX + : wasm_func_obj_get_func_idx_bound( + (WASMFuncObjectRef)table->elems[elem_idx]); +#endif + if (func_idx == UINT32_MAX) { + *func_ref = NULL; + return true; + } + + if (!module_inst->e || func_idx >= module_inst->e->function_count) + return false; + + *func_ref = &module_inst->e->functions[func_idx]; + return true; +} + +static inline bool +wasm_component_table_get_is_local(WASMModuleInstance *module_inst, + WASMTableInstance *table, uint32 elem_idx) +{ + WASMFunctionInstance *func_ref; + + if (!table->component_func_refs) + return true; + + func_ref = table->component_func_refs[elem_idx]; + return !func_ref || func_ref->module_instance == module_inst; +} + +/* Prepare provenance before the raw element memmove. This either translates + module-relative elements into the borrowed destination's sidecar or proves + that sidecar elements can safely return to a local table. */ +static inline bool +wasm_component_prepare_table_copy(WASMModuleInstance *module_inst, + WASMTableInstance *dst_table, + uint32 dst_offset, + WASMTableInstance *src_table, + uint32 src_offset, uint32 count) +{ + uint32 i; + + if (!count) + return true; + + if (dst_table->component_func_refs && src_table->component_func_refs) { + memmove(dst_table->component_func_refs + dst_offset, + src_table->component_func_refs + src_offset, + sizeof(WASMFunctionInstance *) * count); + } + else if (dst_table->component_func_refs) { + for (i = 0; i < count; i++) { + WASMFunctionInstance *func_ref; + + if (!wasm_component_table_elem_to_local_ref( + module_inst, src_table, src_offset + i, &func_ref)) + return false; + dst_table->component_func_refs[dst_offset + i] = func_ref; + } + } + else if (src_table->component_func_refs) { + for (i = 0; i < count; i++) { + WASMFunctionInstance *func_ref, *local_ref; + + func_ref = src_table->component_func_refs[src_offset + i]; + if (!wasm_component_table_elem_to_local_ref( + module_inst, src_table, src_offset + i, &local_ref) + || func_ref != local_ref + || (func_ref && func_ref->module_instance != module_inst)) + return false; + } + } + + return true; +} +#endif + struct WASMInterpFrame; typedef struct WASMInterpFrame WASMRuntimeFrame; @@ -588,6 +785,15 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, const struct InstantiationArgs2 *args, char *error_buf, uint32 error_buf_size); +#if WASM_ENABLE_COMPONENT_MODEL != 0 +WASMModuleInstance * +wasm_instantiate_with_imports(WASMModule *module, + WASMComponentInstance *component_inst, + const WASMCoreImports *imports, + const struct InstantiationArgs2 *args, + char *error_buf, uint32 error_buf_size); +#endif + void wasm_dump_perf_profiling(const WASMModuleInstance *module_inst); diff --git a/core/iwasm/libraries/debug-engine/debug_engine.c b/core/iwasm/libraries/debug-engine/debug_engine.c index 24d57d7068..9b9fdd6bd4 100644 --- a/core/iwasm/libraries/debug-engine/debug_engine.c +++ b/core/iwasm/libraries/debug-engine/debug_engine.c @@ -1346,7 +1346,7 @@ wasm_debug_instance_get_global(WASMDebugInstance *instance, int32 frame_index, } global = globals + global_index; -#if WASM_ENABLE_MULTI_MODULE == 0 +#if WASM_ENABLE_MULTI_MODULE == 0 && WASM_ENABLE_COMPONENT_MODEL == 0 global_addr = global_data + global->data_offset; #else global_addr = global->import_global_inst diff --git a/core/iwasm/libraries/libc-uvwasi/libc_uvwasi_wrapper.c b/core/iwasm/libraries/libc-uvwasi/libc_uvwasi_wrapper.c index 35d091e78d..fad77406fe 100644 --- a/core/iwasm/libraries/libc-uvwasi/libc_uvwasi_wrapper.c +++ b/core/iwasm/libraries/libc-uvwasi/libc_uvwasi_wrapper.c @@ -927,13 +927,16 @@ wasi_poll_oneoff(wasm_exec_env_t exec_env, const wasi_subscription_t *in, wasm_module_inst_t module_inst = get_module_inst(exec_env); uvwasi_t *uvwasi = get_wasi_ctx(module_inst); uvwasi_size_t nevents; + uint64 subscriptions_size = + (uint64)nsubscriptions * sizeof(wasi_subscription_t); + uint64 events_size = (uint64)nsubscriptions * sizeof(wasi_event_t); wasi_errno_t err; if (!uvwasi) return (wasi_errno_t)-1; - if (!validate_native_addr((void *)in, (uint64)sizeof(wasi_subscription_t)) - || !validate_native_addr(out, (uint64)sizeof(wasi_event_t)) + if (!validate_native_addr((void *)in, subscriptions_size) + || !validate_native_addr(out, events_size) || !validate_native_addr(nevents_app, (uint64)sizeof(uint32))) return (wasi_errno_t)-1; diff --git a/core/iwasm/libraries/libc-wasi-p2/libc_wasi_p2.cmake b/core/iwasm/libraries/libc-wasi-p2/libc_wasi_p2.cmake index d1d7e4a5f0..f506ab9ac5 100644 --- a/core/iwasm/libraries/libc-wasi-p2/libc_wasi_p2.cmake +++ b/core/iwasm/libraries/libc-wasi-p2/libc_wasi_p2.cmake @@ -8,13 +8,20 @@ # Set the directory for this module set(LIBC_WASI_P2_DIR ${CMAKE_CURRENT_LIST_DIR}) +set(LIBC_WASI_P2_SSP_DIR + ${WAMR_ROOT_DIR}/core/iwasm/libraries/libc-wasi/sandboxed-system-primitives) + +add_definitions(-DWASM_ENABLE_LIBC_WASI_P2=1) # Gather all source files for the library file(GLOB LIBC_WASI_P2_SOURCE "${LIBC_WASI_P2_DIR}/*.c" ) list(APPEND LIBC_WASI_P2_SOURCE - "${WAMR_ROOT_DIR}/core/iwasm/libraries/libc-wasi/sandboxed-system-primitives/src/random.c" + "${LIBC_WASI_P2_SSP_DIR}/src/blocking_op.c" + "${LIBC_WASI_P2_SSP_DIR}/src/posix.c" + "${LIBC_WASI_P2_SSP_DIR}/src/random.c" + "${LIBC_WASI_P2_SSP_DIR}/src/str.c" "${WAMR_ROOT_DIR}/core/shared/platform/common/libc-util/libc_errno.c" ) @@ -29,8 +36,10 @@ set(LIBC_WASI_P2_INCLUDE_DIRS ${WAMR_ROOT_DIR}/core/shared/platform/linux ${WAMR_ROOT_DIR}/core/iwasm/interpreter ${WAMR_ROOT_DIR}/core/iwasm/libraries/lib-socket/src/wasi - ${WAMR_ROOT_DIR}/core/iwasm/libraries/libc-wasi/sandboxed-system-primitives/include - ${WAMR_ROOT_DIR}/core/iwasm/libraries/libc-wasi/sandboxed-system-primitives/src + ${LIBC_WASI_P2_SSP_DIR}/include + ${LIBC_WASI_P2_SSP_DIR}/src ${WAMR_ROOT_DIR}/core/shared/platform/common/libc-util # Note: OpenSSL include is handled at the target level in the root CMakeLists ) + +include_directories(${LIBC_WASI_P2_INCLUDE_DIRS}) diff --git a/core/iwasm/libraries/libc-wasi-p2/libc_wasi_p2_wrapper.c b/core/iwasm/libraries/libc-wasi-p2/libc_wasi_p2_wrapper.c index 8aba012766..68f3a79caa 100644 --- a/core/iwasm/libraries/libc-wasi-p2/libc_wasi_p2_wrapper.c +++ b/core/iwasm/libraries/libc-wasi-p2/libc_wasi_p2_wrapper.c @@ -78,6 +78,10 @@ extern "C" { sizeof(name##_symbols) / sizeof(NativeSymbol) \ } +/* The built-in host surface implements the stable WASI Preview 2 package set + * published as 0.2.6. Keep the symbol tables synchronized with its WIT. */ +#define WASI_P2_IMPLEMENTED_VERSION "0.2.6" + static NativeSymbol cli_environment_symbols[] = { REG_WASI_P2_FUNCTION("get-environment", wasi_cli_get_environment, "(i)"), REG_WASI_P2_FUNCTION("get-arguments", wasi_cli_get_arguments, "(i)"), @@ -190,7 +194,7 @@ static NativeSymbol filesystem_types_symbols[] = { REG_WASI_P2_FUNCTION("[method]descriptor.metadata-hash-at", wasi_filesystem_metadata_hash_at, "(iii~i)"), REG_WASI_P2_FUNCTION("[method]directory-entry-stream.read-directory-entry", - wasi_filesystem_read_directory_entry, "(Ii)"), + wasi_filesystem_read_directory_entry, "(ii)"), REG_WASI_P2_FUNCTION("filesystem-error-code", wasi_filesystem_filesystem_error_code, "(ii)"), }; @@ -276,7 +280,7 @@ static NativeSymbol sockets_ip_name_lookup_symbols[] = { "(ii)"), REG_WASI_P2_FUNCTION( "[method]resolve-address-stream.subscribe", - wasi_sockets_ip_name_lookup_resolve_address_stream_subscribe, "(i)"), + wasi_sockets_ip_name_lookup_resolve_address_stream_subscribe, "(i)i"), }; static NativeSymbol sockets_tcp_create_socket_symbols[] = { @@ -414,33 +418,66 @@ static NativeSymbol sockets_udp_symbols[] = { }; static wasi_p2_module_t wasi_p2_modules[] = { - WASI_P2_MODULE(cli_environment, "cli/environment", "0.2.0"), - WASI_P2_MODULE(cli_exit, "cli/exit", "0.2.0"), - WASI_P2_MODULE(cli_stdin, "cli/stdin", "0.2.0"), - WASI_P2_MODULE(cli_stdout, "cli/stdout", "0.2.0"), - WASI_P2_MODULE(cli_stderr, "cli/stderr", "0.2.0"), - WASI_P2_MODULE(cli_terminal_stdin, "cli/terminal-stdin", "0.2.0"), - WASI_P2_MODULE(cli_terminal_stdout, "cli/terminal-stdout", "0.2.0"), - WASI_P2_MODULE(cli_terminal_stderr, "cli/terminal-stderr", "0.2.0"), - WASI_P2_MODULE(clocks_monotonic_clock, "clocks/monotonic-clock", "0.2.0"), - WASI_P2_MODULE(clocks_wall_clock, "clocks/wall-clock", "0.2.0"), - WASI_P2_MODULE(filesystem_preopens, "filesystem/preopens", "0.2.0"), - WASI_P2_MODULE(filesystem_types, "filesystem/types", "0.2.0"), - WASI_P2_MODULE(random_random, "random/random", "0.2.0"), - WASI_P2_MODULE(random_insecure, "random/insecure", "0.2.0"), - WASI_P2_MODULE(random_insecure_seed, "random/insecure-seed", "0.2.0"), - WASI_P2_MODULE(io_error, "io/error", "0.2.0"), - WASI_P2_MODULE(io_poll, "io/poll", "0.2.0"), - WASI_P2_MODULE(io_streams, "io/streams", "0.2.0"), + WASI_P2_MODULE(cli_environment, "cli/environment", + WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(cli_exit, "cli/exit", WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(cli_stdin, "cli/stdin", WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(cli_stdout, "cli/stdout", WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(cli_stderr, "cli/stderr", WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(cli_terminal_stdin, "cli/terminal-stdin", + WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(cli_terminal_stdout, "cli/terminal-stdout", + WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(cli_terminal_stderr, "cli/terminal-stderr", + WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(clocks_monotonic_clock, "clocks/monotonic-clock", + WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(clocks_wall_clock, "clocks/wall-clock", + WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(filesystem_preopens, "filesystem/preopens", + WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(filesystem_types, "filesystem/types", + WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(random_random, "random/random", WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(random_insecure, "random/insecure", + WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(random_insecure_seed, "random/insecure-seed", + WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(io_error, "io/error", WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(io_poll, "io/poll", WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(io_streams, "io/streams", WASI_P2_IMPLEMENTED_VERSION), WASI_P2_MODULE(sockets_instance_network, "sockets/instance-network", - "0.2.0"), - WASI_P2_MODULE(sockets_ip_name_lookup, "sockets/ip-name-lookup", "0.2.0"), + WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(sockets_ip_name_lookup, "sockets/ip-name-lookup", + WASI_P2_IMPLEMENTED_VERSION), WASI_P2_MODULE(sockets_tcp_create_socket, "sockets/tcp-create-socket", - "0.2.0"), - WASI_P2_MODULE(sockets_tcp, "sockets/tcp", "0.2.0"), + WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(sockets_tcp, "sockets/tcp", WASI_P2_IMPLEMENTED_VERSION), WASI_P2_MODULE(sockets_udp_create_socket, "sockets/udp-create-socket", - "0.2.0"), - WASI_P2_MODULE(sockets_udp, "sockets/udp", "0.2.0"), + WASI_P2_IMPLEMENTED_VERSION), + WASI_P2_MODULE(sockets_udp, "sockets/udp", WASI_P2_IMPLEMENTED_VERSION), +}; + +typedef struct wasi_p2_resource { + const char *interface_name; + const char *resource_name; +} wasi_p2_resource_t; + +static const wasi_p2_resource_t wasi_p2_resources[] = { + { "wasi:io/error", "error" }, + { "wasi:io/streams", "input-stream" }, + { "wasi:io/streams", "output-stream" }, + { "wasi:io/poll", "pollable" }, + { "wasi:filesystem/types", "directory-entry-stream" }, + { "wasi:filesystem/types", "descriptor" }, + { "wasi:cli/terminal-input", "terminal-input" }, + { "wasi:cli/terminal-output", "terminal-output" }, + { "wasi:sockets/tcp", "tcp-socket" }, + { "wasi:sockets/udp", "udp-socket" }, + { "wasi:sockets/network", "network" }, + { "wasi:sockets/udp", "incoming-datagram-stream" }, + { "wasi:sockets/udp", "outgoing-datagram-stream" }, + { "wasi:sockets/ip-name-lookup", "resolve-address-stream" }, }; static bool @@ -455,54 +492,101 @@ convert_version(int *value, const char version_string[]) return true; } -bool -wasm_check_wasi_p2_version(const char *required_interface) +static bool +wasi_p2_version_is_compatible(const char *required_interface, + const char *runtime_version) { const char *at = strchr(required_interface, '@'); if (!at) return true; // no version requirement, always ok const char *required_ver = at + 1; + int req_maj = 0, req_min = 0, req_pat = 0, run_maj = 0, run_min = 0, + run_pat = 0; + char req_maj_str[20] = { 0 }, req_min_str[20] = { 0 }, + req_pat_str[20] = { 0 }, run_maj_str[20] = { 0 }, + run_min_str[20] = { 0 }, run_pat_str[20] = { 0 }; + + if (sscanf(required_ver, "%19[^.].%19[^.].%19[^.]", req_maj_str, + req_min_str, req_pat_str) + != 3 + || sscanf(runtime_version, "%19[^.].%19[^.].%19[^.]", run_maj_str, + run_min_str, run_pat_str) + != 3) { + return false; + } + + if (!convert_version(&req_maj, req_maj_str) + || !convert_version(&req_min, req_min_str) + || !convert_version(&req_pat, req_pat_str) + || !convert_version(&run_maj, run_maj_str) + || !convert_version(&run_min, run_min_str) + || !convert_version(&run_pat, run_pat_str)) { + return false; + } + + // Hard fail if the runtime does not implement the requested major/minor + // line or a sufficiently recent patch release. + if (req_maj != run_maj || req_min != run_min || run_pat < req_pat) { + LOG_ERROR("Incompatible WASI version for %s: " + "required %d.%d.%d, runtime implements %d.%d.%d", + required_interface, req_maj, req_min, req_pat, run_maj, + run_min, run_pat); + return false; + } + return true; +} + +bool +wasm_check_wasi_p2_version(const char *required_interface) +{ wasi_p2_module_t *modules = NULL; uint32_t count = get_libc_wasi_p2_export_apis(&modules); for (uint32_t i = 0; i < count; i++) { if (is_module_equal(modules[i].module_name, required_interface)) { - int req_maj = 0, req_min = 0, req_pat = 0, run_maj = 0, run_min = 0, - run_pat = 0; - char req_maj_str[20] = { 0 }, req_min_str[20] = { 0 }, - req_pat_str[20] = { 0 }, run_maj_str[20] = { 0 }, - run_min_str[20] = { 0 }, run_pat_str[20] = { 0 }; - - if (sscanf(required_ver, "%19[^.].%19[^.].%19[^.]", req_maj_str, - req_min_str, req_pat_str) - != 3 - || sscanf(modules[i].version, "%19[^.].%19[^.].%19[^.]", - run_maj_str, run_min_str, run_pat_str) - != 3) { - return false; - } + return wasi_p2_version_is_compatible(required_interface, + modules[i].version); + } + } + return true; +} - if (!convert_version(&req_maj, req_maj_str) - || !convert_version(&req_min, req_min_str) - || !convert_version(&req_pat, req_pat_str) - || !convert_version(&run_maj, run_maj_str) - || !convert_version(&run_min, run_min_str) - || !convert_version(&run_pat, run_pat_str)) { - return false; - } +bool +wasm_native_has_builtin_wasi_p2_module(const char *module_name) +{ + wasi_p2_module_t *modules = NULL; + uint32_t count; - // Hard fail: major or minor mismatch = incompatible API - if (req_maj != run_maj || req_min != run_min || req_pat < run_pat) { - LOG_ERROR("Incompatible WASI version for %s: " - "required %d.%d.%d, runtime implements %d.%d.%d", - required_interface, req_maj, req_min, req_pat, - run_maj, run_min, run_pat); - return false; - } - return true; + if (!module_name) { + return false; + } + + count = get_libc_wasi_p2_export_apis(&modules); + for (uint32_t i = 0; i < count; i++) { + if (is_module_equal(modules[i].module_name, module_name)) { + return wasm_check_wasi_p2_version(module_name); } } - return true; + return false; +} + +bool +wasm_native_has_builtin_wasi_p2_resource(const char *interface_name, + const char *resource_name) +{ + if (!interface_name || !resource_name || !strchr(interface_name, '@')) { + return false; + } + + for (uint32_t i = 0; + i < sizeof(wasi_p2_resources) / sizeof(wasi_p2_resources[0]); i++) { + if (is_module_equal(interface_name, wasi_p2_resources[i].interface_name) + && strcmp(resource_name, wasi_p2_resources[i].resource_name) == 0) { + return wasi_p2_version_is_compatible(interface_name, + WASI_P2_IMPLEMENTED_VERSION); + } + } + return false; } /** @@ -615,8 +699,9 @@ wasm_native_unregister_wasi_p2_module(const char *module_name) * @param func_name The name of the function to find. * @return A pointer to the native symbol if found, otherwise NULL. */ -static const NativeSymbol * -find_wasi_p2_module_func(const char *module_name, const char *func_name) +const NativeSymbol * +wasm_native_get_wasi_p2_module_func(const char *module_name, + const char *func_name) { wasi_p2_module_t *modules = NULL; uint32_t wasi_p2_module_count = get_libc_wasi_p2_export_apis(&modules); @@ -644,7 +729,7 @@ wasm_native_register_wasi_p2_module_func(const char *module_name, const char *func_name) { const NativeSymbol *symbol = - find_wasi_p2_module_func(module_name, func_name); + wasm_native_get_wasi_p2_module_func(module_name, func_name); if (symbol) { return wasm_native_register_natives(module_name, (NativeSymbol *)symbol, 1); @@ -662,7 +747,7 @@ wasm_native_unregister_wasi_p2_module_func(const char *module_name, const char *func_name) { const NativeSymbol *symbol = - find_wasi_p2_module_func(module_name, func_name); + wasm_native_get_wasi_p2_module_func(module_name, func_name); if (symbol) { wasm_native_unregister_natives(module_name, (NativeSymbol *)symbol); } diff --git a/core/iwasm/libraries/libc-wasi-p2/libc_wasi_p2_wrapper.h b/core/iwasm/libraries/libc-wasi-p2/libc_wasi_p2_wrapper.h index a4f5e6429d..d3d27081a3 100644 --- a/core/iwasm/libraries/libc-wasi-p2/libc_wasi_p2_wrapper.h +++ b/core/iwasm/libraries/libc-wasi-p2/libc_wasi_p2_wrapper.h @@ -30,6 +30,13 @@ wasm_native_register_wasi_p2_modules(); bool wasm_check_wasi_p2_version(const char *required_interface); +bool +wasm_native_has_builtin_wasi_p2_module(const char *module_name); + +bool +wasm_native_has_builtin_wasi_p2_resource(const char *interface_name, + const char *resource_name); + void wasm_native_unregister_wasi_p2_modules(); @@ -43,6 +50,10 @@ bool wasm_native_register_wasi_p2_module_func(const char *module_name, const char *func_name); +const NativeSymbol * +wasm_native_get_wasi_p2_module_func(const char *module_name, + const char *func_name); + void wasm_native_unregister_wasi_p2_module_func(const char *module_name, const char *func_name); diff --git a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_cli.c b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_cli.c index 286e46169e..1def732e5b 100644 --- a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_cli.c +++ b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_cli.c @@ -96,13 +96,6 @@ wasi_cli_get_environment(uint32_t *ret_len) } env = environ; - const wasi_tuple_string_string_t *ret = - wasm_runtime_calloc(count, sizeof(wasi_tuple_string_string_t)); - if (!ret) { - *ret_len = 0; - return NULL; - } - *ret_len = count; return wasi_cli_environment_split_str(env, count); } @@ -252,4 +245,4 @@ wasi_cli_initial_cwd(bool *is_some) } size *= 2; } -} \ No newline at end of file +} diff --git a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_cli_wrapper.c b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_cli_wrapper.c index 8b26bce90b..095c118b01 100644 --- a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_cli_wrapper.c +++ b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_cli_wrapper.c @@ -309,7 +309,7 @@ wasi_cli_get_stdin_wrapper(wasm_exec_env_t exec_env) return 0; } - wit_value_t out_val = wit_u32_ctor(index_rep); + wit_value_t out_val = wit_resource_ctor(index_rep); lower_own(exec_env->cx, func_type->results->result[0].type_specific.resource_handle, out_val, &index_rep); @@ -360,7 +360,7 @@ wasi_cli_get_stdout_wrapper(wasm_exec_env_t exec_env) return 0; } - wit_value_t out_val = wit_u32_ctor(index_rep); + wit_value_t out_val = wit_resource_ctor(index_rep); lower_own(exec_env->cx, func_type->results->result[0].type_specific.resource_handle, out_val, &index_rep); @@ -410,7 +410,7 @@ wasi_cli_get_stderr_wrapper(wasm_exec_env_t exec_env) return 0; } - wit_value_t out_val = wit_u32_ctor(index_rep); + wit_value_t out_val = wit_resource_ctor(index_rep); lower_own(exec_env->cx, func_type->results->result[0].type_specific.resource_handle, out_val, &index_rep); @@ -471,7 +471,7 @@ wasi_cli_get_terminal_stdin_wrapper(wasm_exec_env_t exec_env, goto end; } - wit_value_t wrapped_index_rep = wit_u32_ctor(index_rep); + wit_value_t wrapped_index_rep = wit_resource_ctor(index_rep); optional_result = wit_option_ctor(wrapped_index_rep); end: @@ -534,7 +534,7 @@ wasi_cli_get_terminal_stdout_wrapper(wasm_exec_env_t exec_env, goto end; } - wit_value_t wrapped_index_rep = wit_u32_ctor(index_rep); + wit_value_t wrapped_index_rep = wit_resource_ctor(index_rep); optional_result = wit_option_ctor(wrapped_index_rep); end: @@ -597,7 +597,7 @@ wasi_cli_get_terminal_stderr_wrapper(wasm_exec_env_t exec_env, goto end; } - wit_value_t wrapped_index_rep = wit_u32_ctor(index_rep); + wit_value_t wrapped_index_rep = wit_resource_ctor(index_rep); optional_result = wit_option_ctor(wrapped_index_rep); end: diff --git a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_clocks.c b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_clocks.c index bb5cd1d998..d2c93dabec 100644 --- a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_clocks.c +++ b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_clocks.c @@ -7,7 +7,16 @@ #include #include +#if defined(__APPLE__) +/* macOS/iOS have no timerfd; a monotonic one-shot timer pollable is + * emulated with a kqueue + EVFILT_TIMER (see + * wasi_monotonic_clock_subscribe below). */ +#include +#include +#include +#else #include +#endif /** * @brief Read the current value of the wall-clock. @@ -103,12 +112,63 @@ wasi_monotonic_clock_resolution(void) * @return A pollable context wrapping the timerfd. On error, `fd` is -1. */ static wasi_pollable_context_t -wasi_monotonic_clock_subscribe(wasi_duration_t when, int flags) +wasi_monotonic_clock_subscribe(wasi_duration_t when, int is_absolute) { wasi_pollable_context_t pollable = { .fd = -1, .own_fd = false, .type = WASI_POLLABLE_IN }; +#if defined(__APPLE__) + /* macOS/iOS: emulate the monotonic one-shot timerfd with a kqueue + + * EVFILT_TIMER. A kqueue descriptor is itself selectable: when an event is + * pending it becomes readable to poll(2)/select(2) (kqueue(2): "The queue + * is represented by a descriptor that may be monitored with select(2), + * poll(2)..."). The shared WASI poll path (wasi_pollable_block / wasi_poll + * in wasi_p2_io.c) only ever poll(2)s pollable->fd for POLLIN and never + * read()s it, so a kqueue fd that becomes POLLIN-readable when its + * EV_ONESHOT EVFILT_TIMER fires is a correct drop-in for the timerfd here + * (verified: poll() returns POLLIN after the timer expires). The pollable + * is one-shot and is close()d on drop, so the event need never be drained + * via kevent(). EVFILT_TIMER is relative, so an absolute deadline is + * converted to a remaining duration against the monotonic clock. */ + uint64_t rel_ns; + if (is_absolute) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + uint64_t now_ns = + (uint64_t)now.tv_sec * 1000000000ull + (uint64_t)now.tv_nsec; + rel_ns = (when > now_ns) ? (when - now_ns) : 0; + } + else { + rel_ns = when; + } + /* A zero duration would disarm a timerfd; fire promptly instead. */ + if (rel_ns == 0) { + rel_ns = 1; + } + if (rel_ns > (uint64_t)INT64_MAX) { + rel_ns = (uint64_t)INT64_MAX; + } + + int kq = kqueue(); + if (kq < 0) { + return pollable; + } + fcntl(kq, F_SETFD, FD_CLOEXEC); /* match TFD_CLOEXEC */ + + struct kevent ev; + EV_SET(&ev, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE | EV_ONESHOT, NOTE_NSECONDS, + (int64_t)rel_ns, NULL); + if (kevent(kq, &ev, 1, NULL, 0, NULL) < 0) { + close(kq); + return pollable; + } + + SET_INPUT_POLLABLE(&pollable, kq, true); + return pollable; +#else + int flags = is_absolute ? TFD_TIMER_ABSTIME : 0; + // Create a timer file descriptor based on the monotonic clock. int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); if (tfd < 0) { @@ -138,6 +198,7 @@ wasi_monotonic_clock_subscribe(wasi_duration_t when, int flags) SET_INPUT_POLLABLE(&pollable, tfd, true); return pollable; +#endif } /** @@ -150,9 +211,8 @@ wasi_monotonic_clock_subscribe(wasi_duration_t when, int flags) wasi_pollable_context_t wasi_monotonic_clock_subscribe_instant(wasi_instant_t when) { - // Call the helper with the TFD_TIMER_ABSTIME flag to treat `when` as an - // absolute timestamp. - return wasi_monotonic_clock_subscribe(when, TFD_TIMER_ABSTIME); + // Treat `when` as an absolute timestamp. + return wasi_monotonic_clock_subscribe(when, /*is_absolute=*/1); } /** @@ -166,6 +226,6 @@ wasi_monotonic_clock_subscribe_instant(wasi_instant_t when) wasi_pollable_context_t wasi_monotonic_clock_subscribe_duration(wasi_duration_t when) { - // Call the helper with a flag of 0 to treat `when` as a relative duration. - return wasi_monotonic_clock_subscribe(when, 0); + // Treat `when` as a relative duration. + return wasi_monotonic_clock_subscribe(when, /*is_absolute=*/0); } \ No newline at end of file diff --git a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_clocks_wrapper.c b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_clocks_wrapper.c index d9a0d9c663..4295a21b65 100644 --- a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_clocks_wrapper.c +++ b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_clocks_wrapper.c @@ -159,7 +159,7 @@ wasi_monotonic_clock_subscribe_instant_wrapper(wasm_exec_env_t exec_env, return 0; } - wit_value_t out_val = wit_u32_ctor(out); + wit_value_t out_val = wit_resource_ctor(out); lower_own(exec_env->cx, func_type->results->result[0].type_specific.resource_handle, out_val, &out); @@ -219,7 +219,7 @@ wasi_monotonic_clock_subscribe_duration_wrapper(wasm_exec_env_t exec_env, return 0; } - wit_value_t out_val = wit_u32_ctor(out); + wit_value_t out_val = wit_resource_ctor(out); lower_own(exec_env->cx, func_type->results->result[0].type_specific.resource_handle, out_val, &out); diff --git a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_error.c b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_error.c index 1811b3350a..ada06442f5 100644 --- a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_error.c +++ b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_error.c @@ -42,7 +42,7 @@ get_stream_error_val(bool is_closed, uint32_t error_idx) } else { ret = wit_variant_ctor("last-operation-failed", 21, - wit_u32_ctor(error_idx)); + wit_resource_ctor(error_idx)); } return wit_result_ctor(true, ret); } diff --git a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_filesystem.c b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_filesystem.c index dac13f854e..3fe02fbe01 100644 --- a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_filesystem.c +++ b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_filesystem.c @@ -15,11 +15,26 @@ #include #include #include +#if !defined(__APPLE__) #include #include +#endif #include "wasi_p2_common.h" +#if defined(__APPLE__) +/* Darwin/BSD names the nanosecond-resolution stat timestamp fields + * st_*timespec rather than POSIX-2008's st_*tim. Both are 'struct timespec', + * so a textual rename keeps every st_atim.tv_nsec etc. site working unchanged. + */ +#define st_atim st_atimespec +#define st_mtim st_mtimespec +#define st_ctim st_ctimespec +/* macOS has no fdatasync(); fsync() flushes both data and metadata, which is a + * conformant (stronger-than-required) superset of fdatasync semantics. */ +#define fdatasync fsync +#endif + void filesystem_descriptor_dtor(void *data) { @@ -403,6 +418,17 @@ int wasi_filesystem_advise(wasi_descriptor_t fd, wasi_filesize_t offset, wasi_filesize_t length, wasi_advice_t advice) { +#if defined(__APPLE__) + /* macOS/iOS have no posix_fadvise. The advice is purely an optional + * performance hint to the kernel, so ignoring it is conformant behavior. + * (F_RDADVISE/F_NOCACHE exist but do not map cleanly onto the WASI advice + * enum, so we treat advise as a successful no-op.) */ + (void)fd; + (void)offset; + (void)length; + (void)advice; + return WASI_ERROR_CODE_SUCCESS; +#else int advice_posix = 0; // Translate the abstract WASI advice enum to the corresponding POSIX @@ -436,6 +462,7 @@ wasi_filesystem_advise(wasi_descriptor_t fd, wasi_filesize_t offset, } return WASI_ERROR_CODE_SUCCESS; +#endif } /** @@ -879,16 +906,64 @@ wasi_filesystem_open_at(wasi_descriptor_t fd, wasi_path_flags_t path_flags, } #endif - struct open_how how = {0}; +#if defined(__APPLE__) + /* macOS/iOS have no openat2 / RESOLVE_BENEATH. Emulate the sandbox the + * Linux path gets from open_how.resolve: + * - RESOLVE_BENEATH: the resolved path must stay beneath the preopened + * directory `fd`. We reject absolute paths and walk the components + * tracking depth: a ".." that would pop above the root escapes and is + * rejected, but a non-escaping ".." such as "subdir/../file" stays + * beneath `fd` and is allowed, exactly as RESOLVE_BENEATH permits. + * - RESOLVE_NO_SYMLINKS / RESOLVE_NO_MAGICLINKS: O_NOFOLLOW_ANY refuses + * a symlink in *any* path component (not just the last, as plain + * O_NOFOLLOW would). Requires macOS 10.15+ / iOS 13+, which all of + * our deployment targets satisfy. + * SECURITY: this is the sandbox boundary for the embedder. Because + * O_NOFOLLOW_ANY guarantees no component is a symlink, the purely textual + * depth check below cannot be subverted by symlink redirection. */ + if (path[0] == '/') { + *err = EPERM; + *ret = -1; + return; + } + int depth = 0; + for (const char *seg = path; seg && *seg;) { + const char *slash = strchr(seg, '/'); + size_t seg_len = slash ? (size_t)(slash - seg) : strlen(seg); + if (seg_len == 2 && seg[0] == '.' && seg[1] == '.') { + /* ".." pops a level; popping above the preopen root escapes. */ + if (--depth < 0) { + *err = EPERM; + *ret = -1; + return; + } + } + else if (!(seg_len == 0 || (seg_len == 1 && seg[0] == '.'))) { + /* a real name component (ignore "" from "//" and ".") */ + depth++; + } + seg = slash ? slash + 1 : NULL; + } + int r = openat(fd, path, internal_flags | O_NOFOLLOW_ANY, + (internal_flags & O_CREAT) ? mode : 0); + if (r < 0) { + *err = (errno == EXDEV) ? EPERM : errno; + *ret = -1; + return; + } + *err = 0; + *ret = r; +#else + struct open_how how = { 0 }; how.flags = internal_flags; how.mode = (how.flags & (O_CREAT)) ? mode : 0; how.resolve = RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS | RESOLVE_NO_MAGICLINKS; int r = syscall(SYS_openat2, fd, path, &how, sizeof(how)); if (r < 0) { - if(errno == EXDEV){ + if (errno == EXDEV) { *err = EPERM; } - else{ + else { *err = errno; } *ret = -1; @@ -896,6 +971,7 @@ wasi_filesystem_open_at(wasi_descriptor_t fd, wasi_path_flags_t path_flags, } *err = 0; *ret = r; +#endif } /** @@ -1308,8 +1384,10 @@ wasi_filesystem_get_flags(wasi_descriptor_t fd, wasi_descriptor_flags_t *ret, // Translate POSIX synchronization flags to their WASI equivalents. if (flags & O_DSYNC) f |= WASI_DESCRIPTOR_FLAGS_DATA_INTEGRITY_SYNC; +#ifdef O_RSYNC if (flags & O_RSYNC) f |= WASI_DESCRIPTOR_FLAGS_REQUESTED_WRITE_SYNC; +#endif if (flags & O_SYNC) f |= WASI_DESCRIPTOR_FLAGS_FILE_INTEGRITY_SYNC; diff --git a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_filesystem_wrapper.c b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_filesystem_wrapper.c index f55ccc587f..039fbc8e04 100644 --- a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_filesystem_wrapper.c +++ b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_filesystem_wrapper.c @@ -97,9 +97,8 @@ wasi_filesystem_get_directories_wrapper(wasm_exec_env_t exec_env, if (!fd_table_get_host_handle(curfds, i, &host_fd)) continue; - uint32_t dir_len = strlen(prestats->prestats[i].dir); - char *dir = (char *)wasm_runtime_malloc(sizeof(char) * dir_len); - strcpy(dir, prestats->prestats[i].dir); + const char *dir = prestats->prestats[i].dir; + uint32_t dir_len = strlen(dir); HostResourceTable *hr_table = get_global_host_resource_table(); HostResource *hr = host_resource_create( WASI_P2_FILESYSTEM_DESCRIPTOR, sizeof(uint32_t)); @@ -111,14 +110,14 @@ wasi_filesystem_get_directories_wrapper(wasm_exec_env_t exec_env, wit_value_t *tuple_elems = (wit_value_t *)wasm_runtime_malloc(2 * sizeof(wit_value_t)); - tuple_elems[0] = wit_u32_ctor(fs_rep); + tuple_elems[0] = wit_resource_ctor(fs_rep); StringEncoding encoding = wasm_get_string_encoding(exec_env); uint8_t *encoded_str = NULL; uint32_t encoded_str_len = 0; uint32_t encoded_code_units = 0; - encode_string(exec_env->cx, dir, strlen(dir), encoding, - &encoded_str, &encoded_str_len, &encoded_code_units); + encode_string(exec_env->cx, dir, dir_len, encoding, &encoded_str, + &encoded_str_len, &encoded_code_units); tuple_elems[1] = wit_string_ctor((char *)encoded_str, encoded_str_len, encoded_code_units, encoding); @@ -219,7 +218,7 @@ wasi_filesystem_read_via_stream_wrapper(wasm_exec_env_t exec_env, result = get_result_error_val(WASI_FILESYSTEM_CODE_INVALID); goto end; } - wit_value_t index_val = wit_u32_ctor(index_rep); + wit_value_t index_val = wit_resource_ctor(index_rep); result = wit_result_ctor(false, index_val); } else { @@ -311,7 +310,7 @@ wasi_filesystem_write_via_stream_wrapper(wasm_exec_env_t exec_env, result = get_result_error_val(WASI_FILESYSTEM_CODE_INVALID); goto end; } - wit_value_t index_val = wit_u32_ctor(index_rep); + wit_value_t index_val = wit_resource_ctor(index_rep); result = wit_result_ctor(false, index_val); } else { @@ -401,7 +400,7 @@ wasi_filesystem_append_via_stream_wrapper(wasm_exec_env_t exec_env, result = get_result_error_val(WASI_FILESYSTEM_CODE_INVALID); goto end; } - wit_value_t index_val = wit_u32_ctor(index_rep); + wit_value_t index_val = wit_resource_ctor(index_rep); result = wit_result_ctor(false, index_val); } else { @@ -1075,7 +1074,7 @@ wasi_filesystem_read_directory_wrapper(wasm_exec_env_t exec_env, goto end; } - wit_value_t index_val = wit_u32_ctor(index_rep); + wit_value_t index_val = wit_resource_ctor(index_rep); result = wit_result_ctor(false, index_val); } else { @@ -1595,10 +1594,10 @@ wasi_filesystem_link_at_wrapper(wasm_exec_env_t exec_env, char *new_path = name_val->value.string_value.chars; HostResourceTable *hr_table = get_global_host_resource_table(); - HostResource *hr1 = - host_resource_table_get(hr_table, lifted_handle_old->value.u32_value); - HostResource *hr2 = - host_resource_table_get(hr_table, lifted_handle_new->value.u32_value); + HostResource *hr1 = host_resource_table_get( + hr_table, lifted_handle_old->value.resource_value.value); + HostResource *hr2 = host_resource_table_get( + hr_table, lifted_handle_new->value.resource_value.value); if (!(hr1 && hr2)) { wasm_runtime_set_exception(exec_env->module_inst, @@ -1661,6 +1660,7 @@ wasi_filesystem_open_at_wrapper(wasm_exec_env_t exec_env, wasi_descriptor_t fd, WASMComponentFuncTypeInstance *func_type = wasm_get_component_func_type(exec_env); wit_value_t lifted_handle = NULL; + wit_value_t path_val = NULL; if (!wasi_ctx->wasi_options->cli || !wasi_ctx->wasi_options->common) { result = get_result_error_val(WASI_FILESYSTEM_CODE_UNSUPPORTED); @@ -1675,7 +1675,6 @@ wasi_filesystem_open_at_wrapper(wasm_exec_env_t exec_env, wasi_descriptor_t fd, goto end; } - wit_value_t path_val = NULL; if (!load_string_from_range(exec_env->cx, path_ptr, path_len, &path_val)) { result = get_result_error_val(WASI_NETWORK_ERROR_CODE_INVALID_ARGUMENT); goto end; @@ -1729,7 +1728,7 @@ wasi_filesystem_open_at_wrapper(wasm_exec_env_t exec_env, wasi_descriptor_t fd, goto end; } - wit_value_t index_val = wit_u32_ctor(index_rep); + wit_value_t index_val = wit_resource_ctor(index_rep); result = wit_result_ctor(false, index_val); } else { @@ -1768,6 +1767,7 @@ wasi_filesystem_readlink_at_wrapper(wasm_exec_env_t exec_env, WASMComponentFuncTypeInstance *func_type = wasm_get_component_func_type(exec_env); wit_value_t lifted_handle = NULL; + char *link_content = NULL; if (!wasi_ctx->wasi_options->cli || !wasi_ctx->wasi_options->common) { result = get_result_error_val(WASI_FILESYSTEM_CODE_UNSUPPORTED); @@ -1782,7 +1782,6 @@ wasi_filesystem_readlink_at_wrapper(wasm_exec_env_t exec_env, char *path = name_val->value.string_value.chars; - char *link_content; int err = 0; if (!lift_borrow( exec_env->cx, fd, @@ -1982,10 +1981,10 @@ wasi_filesystem_rename_at_wrapper(wasm_exec_env_t exec_env, char *new_path = name_val->value.string_value.chars; HostResourceTable *hr_table = get_global_host_resource_table(); - HostResource *hr1 = - host_resource_table_get(hr_table, lifted_handle_old->value.u32_value); - HostResource *hr2 = - host_resource_table_get(hr_table, lifted_handle_new->value.u32_value); + HostResource *hr1 = host_resource_table_get( + hr_table, lifted_handle_old->value.resource_value.value); + HostResource *hr2 = host_resource_table_get( + hr_table, lifted_handle_new->value.resource_value.value); if (!(hr1 && hr2)) { wasm_runtime_set_exception(exec_env->module_inst, @@ -2233,10 +2232,10 @@ wasi_filesystem_is_same_object_wrapper(wasm_exec_env_t exec_env, } HostResourceTable *hr_table = get_global_host_resource_table(); - HostResource *hr1 = - host_resource_table_get(hr_table, lifted_handle_1->value.u32_value); - HostResource *hr2 = - host_resource_table_get(hr_table, lifted_handle_2->value.u32_value); + HostResource *hr1 = host_resource_table_get( + hr_table, lifted_handle_1->value.resource_value.value); + HostResource *hr2 = host_resource_table_get( + hr_table, lifted_handle_2->value.resource_value.value); if (!(hr1 && hr2)) { wasm_runtime_set_exception(exec_env->module_inst, @@ -2429,7 +2428,7 @@ wasi_filesystem_metadata_hash_at_wrapper(wasm_exec_env_t exec_env, */ void wasi_filesystem_read_directory_entry_wrapper(wasm_exec_env_t exec_env, - int64_t stream, + uint32_t stream, uint32_t offset_addr) { wasm_module_inst_t module_inst = wasm_runtime_get_module_inst(exec_env); @@ -2554,4 +2553,4 @@ wasi_filesystem_filesystem_error_code_wrapper(wasm_exec_env_t exec_env, store(exec_env->cx, offset_addr, func_type->results->result, result); free_wit_value(result); -} \ No newline at end of file +} diff --git a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_filesystem_wrapper.h b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_filesystem_wrapper.h index 4e66b6a381..21d0d1b609 100644 --- a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_filesystem_wrapper.h +++ b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_filesystem_wrapper.h @@ -153,7 +153,7 @@ wasi_filesystem_metadata_hash_at_wrapper(wasm_exec_env_t exec_env, /* directory-entry-stream */ void wasi_filesystem_read_directory_entry_wrapper(wasm_exec_env_t exec_env, - int64_t stream, + uint32_t stream, uint32_t offset_addr); void diff --git a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_io.c b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_io.c index 89cf383951..0802e38e6a 100644 --- a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_io.c +++ b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_io.c @@ -3,6 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ +#if defined(__linux__) && !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif + #include "wasi_p2_io.h" #include "wasi_p2_error.h" #include @@ -532,6 +536,13 @@ wasi_output_stream_check_write(wasi_output_stream_t stream, // If the stream is currently flushing, we must check if it's done. if (is_in_flushing_list(stream)) { int queue_size = 0; +#if defined(__APPLE__) + // macOS/Darwin has no TIOCOUTQ to query the bytes still queued in the + // socket/tty output buffer. Treat the output queue as already drained + // (0 bytes queued) so the flush is reported complete and writes are not + // blocked. This is the conservative "is the queue empty" answer. + queue_size = 0; +#else // Use ioctl with TIOCOUTQ to get the number of bytes in the output // buffer. if (ioctl(stream, TIOCOUTQ, &queue_size) < 0) { @@ -541,6 +552,7 @@ wasi_output_stream_check_write(wasi_output_stream_t stream, ret->u.err.payload.error = errno; return; } +#endif if (queue_size == 0) { // If the output queue is empty, the flush is complete. @@ -578,10 +590,37 @@ wasi_output_stream_check_write(wasi_output_stream_t stream, return; } +#if !defined(__APPLE__) int used_space = 0; if (ioctl(stream, TIOCOUTQ, &used_space) == 0) { available_space -= used_space; } +#else + // macOS/Darwin has no TIOCOUTQ to learn how many bytes are already + // queued, so SO_SNDBUF cannot be turned into exact free space. + // Reporting the full SO_SNDBUF would over-grant: the socket is + // non-blocking and wasi_output_stream_write treats a short write as a + // stream error, so an over-large permit turns normal TCP backpressure + // into a write failure. Report a readiness-based permit instead — only + // when the socket is writable (POLLOUT), and capped at SO_SNDLOWAT, the + // send low-water mark that POLLOUT guarantees is free. + struct pollfd wpfd = { .fd = stream, .events = POLLOUT }; + if (poll(&wpfd, 1, 0) <= 0 || !(wpfd.revents & POLLOUT)) { + available_space = 0; + } + else { + int low_water = 0; + socklen_t lwlen = sizeof(low_water); + if (getsockopt(stream, SOL_SOCKET, SO_SNDLOWAT, &low_water, &lwlen) + < 0 + || low_water <= 0) { + low_water = 1024; // macOS default send low-water mark + } + if (available_space > low_water) { + available_space = low_water; + } + } +#endif ret->is_err = false; ret->u.ok = available_space > 0 ? available_space : 0; @@ -752,6 +791,13 @@ wasi_output_stream_flush(wasi_output_stream_t stream, pthread_mutex_lock(&flushing_streams_list_lock); int queue_size = 0; +#if defined(__APPLE__) + // macOS/Darwin has no TIOCOUTQ to query the bytes still queued in the + // kernel's output buffer. Treat the output queue as already drained + // (0 bytes queued) so the stream is never marked as flushing and + // subsequent check-write calls proceed immediately. + queue_size = 0; +#else // Use ioctl with TIOCOUTQ to get the number of bytes in the kernel's output // buffer. if (ioctl(stream, TIOCOUTQ, &queue_size) < 0) { @@ -761,6 +807,7 @@ wasi_output_stream_flush(wasi_output_stream_t stream, ret->u.err.payload.error = errno; return; } +#endif // If there is data in the output queue, mark the stream as flushing. if (queue_size > 0) { diff --git a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_io_wrapper.c b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_io_wrapper.c index f8536ea420..3a050cba7b 100644 --- a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_io_wrapper.c +++ b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_io_wrapper.c @@ -336,7 +336,7 @@ wasi_io_streams_input_stream_read_wrapper(wasm_exec_env_t exec_env, wit_value_t *elems = (wit_value_t *)wasm_runtime_malloc( sizeof(wit_value_t) * wasi_ret.u.ok.buf_len); for (idx = 0; idx < wasi_ret.u.ok.buf_len; idx++) { - elems[idx] = wit_u32_ctor(wasi_ret.u.ok.buf[idx]); + elems[idx] = wit_u8_ctor(wasi_ret.u.ok.buf[idx]); } wit_value_t result_list = wit_list_ctor(elems, wasi_ret.u.ok.buf_len); result = wit_result_ctor(false, result_list); @@ -426,7 +426,7 @@ wasi_io_streams_input_stream_blocking_read_wrapper(wasm_exec_env_t exec_env, wit_value_t *elems = (wit_value_t *)wasm_runtime_malloc( sizeof(wit_value_t) * wasi_ret.u.ok.buf_len); for (idx = 0; idx < wasi_ret.u.ok.buf_len; idx++) { - elems[idx] = wit_u32_ctor(wasi_ret.u.ok.buf[idx]); + elems[idx] = wit_u8_ctor(wasi_ret.u.ok.buf[idx]); } wit_value_t result_list = wit_list_ctor(elems, wasi_ret.u.ok.buf_len); result = wit_result_ctor(false, result_list); @@ -647,7 +647,7 @@ wasi_io_streams_input_stream_subscribe_wrapper(wasm_exec_env_t exec_env, return 0; } - wit_value_t out_val = wit_u32_ctor(index_rep); + wit_value_t out_val = wit_resource_ctor(index_rep); lower_own(exec_env->cx, func_type->results->result[0].type_specific.resource_handle, out_val, &index_rep); @@ -1115,7 +1115,7 @@ wasi_io_streams_output_stream_subscribe_wrapper(wasm_exec_env_t exec_env, return 0; } - wit_value_t out_val = wit_u32_ctor(index_rep); + wit_value_t out_val = wit_resource_ctor(index_rep); lower_own(exec_env->cx, func_type->results->result[0].type_specific.resource_handle, out_val, &index_rep); @@ -1294,8 +1294,8 @@ wasi_io_streams_output_stream_splice_wrapper(wasm_exec_env_t exec_env, WASMComponentFuncTypeInstance *func_type = wasm_get_component_func_type(exec_env); - wit_value_t lifted_output_handle; - wit_value_t lifted_input_handle; + wit_value_t lifted_output_handle = NULL; + wit_value_t lifted_input_handle = NULL; wit_value_t result = NULL; if (!wasi_ctx->wasi_options->cli || !wasi_ctx->wasi_options->common) { @@ -1328,7 +1328,7 @@ wasi_io_streams_output_stream_splice_wrapper(wasm_exec_env_t exec_env, HostResourceTable *hr_table = get_global_host_resource_table(); HostResource *hr_stream_out = host_resource_table_get( - hr_table, lifted_output_handle->value.u32_value); + hr_table, lifted_output_handle->value.resource_value.value); if (!hr_stream_out) { wasm_runtime_set_exception(exec_env->module_inst, @@ -1339,8 +1339,8 @@ wasi_io_streams_output_stream_splice_wrapper(wasm_exec_env_t exec_env, goto end; } - HostResource *hr_input_stream = - host_resource_table_get(hr_table, lifted_input_handle->value.u32_value); + HostResource *hr_input_stream = host_resource_table_get( + hr_table, lifted_input_handle->value.resource_value.value); if (!hr_input_stream) { wasm_runtime_set_exception(exec_env->module_inst, @@ -1400,8 +1400,8 @@ wasi_io_streams_output_stream_blocking_splice_wrapper( WASMComponentFuncTypeInstance *func_type = wasm_get_component_func_type(exec_env); - wit_value_t lifted_output_handle; - wit_value_t lifted_input_handle; + wit_value_t lifted_output_handle = NULL; + wit_value_t lifted_input_handle = NULL; wit_value_t result = NULL; if (!wasi_ctx->wasi_options->cli || !wasi_ctx->wasi_options->common) { @@ -1434,7 +1434,7 @@ wasi_io_streams_output_stream_blocking_splice_wrapper( HostResourceTable *hr_table = get_global_host_resource_table(); HostResource *hr_stream_out = host_resource_table_get( - hr_table, lifted_output_handle->value.u32_value); + hr_table, lifted_output_handle->value.resource_value.value); if (!hr_stream_out) { wasm_runtime_set_exception(exec_env->module_inst, @@ -1445,8 +1445,8 @@ wasi_io_streams_output_stream_blocking_splice_wrapper( goto end; } - HostResource *hr_input_stream = - host_resource_table_get(hr_table, lifted_input_handle->value.u32_value); + HostResource *hr_input_stream = host_resource_table_get( + hr_table, lifted_input_handle->value.resource_value.value); if (!hr_input_stream) { wasm_runtime_set_exception(exec_env->module_inst, @@ -1484,4 +1484,4 @@ wasi_io_streams_output_stream_blocking_splice_wrapper( free_wit_value(result); free_wit_value(lifted_input_handle); free_wit_value(lifted_output_handle); -} \ No newline at end of file +} diff --git a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_random.c b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_random.c index 4b35ceb6aa..ca2b0b81bb 100644 --- a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_random.c +++ b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_random.c @@ -7,10 +7,37 @@ #include #include +#include +#include +#include +#if !defined(__APPLE__) #include +#endif #include #include "wasm_export.h" +#if defined(__APPLE__) +/* + * Apple platforms (macOS/iOS/tvOS/watchOS/visionOS) do not provide the Linux + * getrandom() syscall. arc4random_buf() is available across Apple's supported + * SDKs and provides cryptographically secure random bytes without an extra + * framework dependency. Wrap it in a getrandom()-style shim so the call sites + * below keep their ssize_t/loop semantics unchanged. + */ +static ssize_t +getrandom(void *buf, size_t buflen, unsigned int flags) +{ + size_t chunk = buflen; + + (void)flags; + if (chunk > SSIZE_MAX) { + chunk = SSIZE_MAX; + } + arc4random_buf(buf, chunk); + return (ssize_t)chunk; +} +#endif + // wasi:random /** @@ -33,6 +60,12 @@ wasi_random_get_random_bytes(uint64_t len, wasi_list_u8_t *ret) return; } + if (len == 0) { + ret->buf = NULL; + ret->buf_len = 0; + return; + } + ret->buf = wasm_runtime_malloc(len); if (!ret->buf) { ret->buf_len = 0; @@ -124,6 +157,12 @@ wasi_random_get_insecure_random_bytes(uint64_t len, wasi_list_u8_t *ret) ensure_insecure_seed_initialized(); + if (len == 0) { + ret->buf = NULL; + ret->buf_len = 0; + return; + } + ret->buf = wasm_runtime_malloc(len); if (!ret->buf) { ret->buf_len = 0; diff --git a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_sockets.c b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_sockets.c index 454d6ddb67..c70a997337 100644 --- a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_sockets.c +++ b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_sockets.c @@ -3,6 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ +#if defined(__linux__) && !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif + #include "wasi_p2_sockets.h" #include "wasi_p2_common.h" @@ -24,6 +28,72 @@ #include "bh_hashmap.h" #include "wasm_export.h" +#if defined(__APPLE__) +/* Darwin/BSD libc lacks the Linux-only mmsg(2) batch API, the SOCK_NONBLOCK / + SOCK_CLOEXEC / accept4 atomic-flag extensions and a few constants. Provide + local equivalents so the non-Apple path below stays byte-for-byte unchanged. + */ +#include /* IOV_MAX */ + +/* macOS has no UIO_MAXIOV; IOV_MAX from is the equivalent cap. */ +#ifndef UIO_MAXIOV +#define UIO_MAXIOV IOV_MAX +#endif + +/* macOS has no MSG_NOSIGNAL send flag; SIGPIPE is suppressed per-socket via + the SO_NOSIGPIPE option set right after the socket is created. */ +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +/* macOS provides neither struct mmsghdr nor recvmmsg()/sendmmsg(). Define the + struct and implement the two calls as a recvmsg()/sendmsg() loop matching the + glibc signatures, so the existing __linux__ batch paths have a portable + fallback if they are ever compiled on Apple. The udp receive/send paths below + take the non-__linux__ recvmsg/sendmsg loops, so these are marked unused to + keep the build clean under -Werror. */ +struct mmsghdr { + struct msghdr msg_hdr; + unsigned int msg_len; +}; + +static int __attribute__((unused)) +recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, int flags, + struct timespec *timeout) +{ + /* timeout is unsupported in this fallback; the caller passes NULL. */ + (void)timeout; + unsigned int i; + for (i = 0; i < vlen; i++) { + ssize_t s = recvmsg(sockfd, &msgvec[i].msg_hdr, flags); + if (s < 0) { + /* Return what we have so far, mirroring recvmmsg() semantics. */ + if (i > 0) + break; + return -1; + } + msgvec[i].msg_len = (unsigned int)s; + } + return (int)i; +} + +static int __attribute__((unused)) +sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, int flags) +{ + unsigned int i; + for (i = 0; i < vlen; i++) { + ssize_t s = sendmsg(sockfd, &msgvec[i].msg_hdr, flags); + if (s < 0) { + if (i > 0) + break; + return -1; + } + msgvec[i].msg_len = (unsigned int)s; + } + return (int)i; +} +#endif /* __APPLE__ */ + /** * @brief A singleton resource representing the host's network capability. * @details The `wasi:sockets/network` interface represents access to the @@ -49,8 +119,8 @@ udp_socket_dtor(void *data) close(((wasi_socket_context_t *)data)->fd); } -/* TCP input/output streams from tcp.accept carry a dup()'d socket fd - that duplicate is independently owned and must be closed on drop. */ +/* TCP input/output streams carry a duplicated socket fd that is independently + owned and must be closed on drop. */ void tcp_owned_stream_dtor(void *data) { @@ -65,6 +135,38 @@ udp_datagram_stream_dtor(void *data) close((int)*(uint32_t *)data); } +static int +duplicate_socket_streams(int socket_fd, int32_t *input_stream, + int32_t *output_stream) +{ + int input_fd; + int output_fd; + int error; + + if (!input_stream || !output_stream) { + return EINVAL; + } + + *input_stream = -1; + *output_stream = -1; + + input_fd = fcntl(socket_fd, F_DUPFD_CLOEXEC, 0); + if (input_fd < 0) { + return errno; + } + + output_fd = fcntl(socket_fd, F_DUPFD_CLOEXEC, 0); + if (output_fd < 0) { + error = errno; + close(input_fd); + return error; + } + + *input_stream = input_fd; + *output_stream = output_fd; + return 0; +} + // --- Asynchronous DNS Resolution Infrastructure --- /** @@ -100,6 +202,8 @@ static HashMap *resolve_streams_map = NULL; * @brief A mutex to ensure thread-safe access to the `resolve_streams_map`. */ static pthread_mutex_t resolve_streams_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t resolve_workers_cond = PTHREAD_COND_INITIALIZER; +static uint32_t resolve_active_workers = 0; /** * @brief A simple counter to generate unique IDs for resolve-stream resources. @@ -172,16 +276,18 @@ init_resolve_streams_map() void destroy_resolve_streams_map() { - resolve_streams_map = NULL; + wasi_p2_sockets_cleanup(); } void wasi_sockets_drop_resolve_stream(uint32_t stream_id) { - if (!resolve_streams_map) - return; void *old_key = NULL, *old_val = NULL; pthread_mutex_lock(&resolve_streams_lock); + if (!resolve_streams_map) { + pthread_mutex_unlock(&resolve_streams_lock); + return; + } bool removed = bh_hash_map_remove( resolve_streams_map, (void *)(uintptr_t)stream_id, &old_key, &old_val); pthread_mutex_unlock(&resolve_streams_lock); @@ -273,6 +379,12 @@ resolve_thread(void *arg) } wasm_runtime_free(args->name); wasm_runtime_free(args); + + pthread_mutex_lock(&resolve_streams_lock); + bh_assert(resolve_active_workers > 0); + resolve_active_workers--; + pthread_cond_broadcast(&resolve_workers_cond); + pthread_mutex_unlock(&resolve_streams_lock); return NULL; } @@ -286,12 +398,20 @@ resolve_thread(void *arg) void wasi_p2_sockets_cleanup(void) { - // Destroy the hash map, which will also call the destructor for each - // remaining resolve_stream_t. + pthread_mutex_lock(&resolve_streams_lock); + while (resolve_active_workers > 0) { + pthread_cond_wait(&resolve_workers_cond, &resolve_streams_lock); + } + + // Destroy the map only after detached workers have stopped using both it + // and the runtime allocator. if (resolve_streams_map) { bh_hash_map_destroy(resolve_streams_map); resolve_streams_map = NULL; } + resolve_stream_id_counter = 0; + network_resource.in_use = false; + pthread_mutex_unlock(&resolve_streams_lock); } /** @@ -453,6 +573,7 @@ wasi_sockets_resolve_addresses(wasi_network_t network, const char *name, struct resolve_thread_args *args = NULL; long id = -1; bool lock_held = false; + bool stream_inserted = false; int pipefd[2] = { -1, -1 }; if (!is_valid_network(network)) { @@ -489,10 +610,23 @@ wasi_sockets_resolve_addresses(wasi_network_t network, const char *name, // Create a pipe to signal completion from the worker thread. // The read-end of this pipe will become the pollable for the stream's // subscribe method. +#if defined(__APPLE__) + // macOS lacks pipe2; create the pipe then set FD_CLOEXEC on both ends. + if (pipe(pipefd) < 0) { + *err = errno; + goto fail; + } + if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) < 0 + || fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) < 0) { + *err = errno; + goto fail; + } +#else if (pipe2(pipefd, O_CLOEXEC) < 0) { *err = errno; goto fail; } +#endif stream->pipe_fd = pipefd[0]; args->pipe_write_fd = pipefd[1]; pipefd[0] = pipefd[1] = -1; @@ -513,21 +647,35 @@ wasi_sockets_resolve_addresses(wasi_network_t network, const char *name, *err = EINVAL; goto fail; } + stream_inserted = true; - // Create and detach a worker thread to perform the blocking getaddrinfo - // call. + // Create a detached worker thread to perform the blocking getaddrinfo + // call. Track it before creation while holding the same lock the worker + // needs to finish, so runtime cleanup cannot miss it. pthread_t th; - if (pthread_create(&th, NULL, resolve_thread, args) != 0) { - bh_hash_map_remove(resolve_streams_map, (void *)id, NULL, NULL); - stream = NULL; /* freed by remove */ + pthread_attr_t thread_attr; + if (pthread_attr_init(&thread_attr) != 0) { + *err = EIO; + goto fail; + } + if (pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED) + != 0) { + pthread_attr_destroy(&thread_attr); *err = EIO; goto fail; } - args = NULL; /* ownership transferred to thread */ - if (pthread_detach(th) != 0) { - fprintf(stderr, "wasi-sockets: warning: pthread_detach failed\n"); + resolve_active_workers++; + if (pthread_create(&th, &thread_attr, resolve_thread, args) != 0) { + resolve_active_workers--; + pthread_attr_destroy(&thread_attr); + *err = EIO; + goto fail; } + pthread_attr_destroy(&thread_attr); + stream->thread = th; + args = NULL; /* ownership transferred to thread */ + *ret = id; *err = 0; pthread_mutex_unlock(&resolve_streams_lock); @@ -535,6 +683,9 @@ wasi_sockets_resolve_addresses(wasi_network_t network, const char *name, fail: // Robust cleanup logic in case of any failure during setup. + if (lock_held && stream_inserted) { + bh_hash_map_remove(resolve_streams_map, (void *)id, NULL, NULL); + } if (lock_held) pthread_mutex_unlock(&resolve_streams_lock); @@ -737,7 +888,33 @@ create_socket(wasi_ip_address_family_t family, int type, int *ret_sock, *err = WASI_NETWORK_ERROR_CODE_NOT_SUPPORTED; return; } - // Create a non-blocking, close-on-exec socket of the specified type. + // Create a non-blocking, close-on-exec socket of the specified type. +#if defined(__APPLE__) + // macOS lacks the SOCK_NONBLOCK/SOCK_CLOEXEC socket() flags: create the + // socket then set O_NONBLOCK and FD_CLOEXEC via fcntl. Also set + // SO_NOSIGPIPE so writes to a broken connection fail with EPIPE instead of + // raising SIGPIPE (macOS has no MSG_NOSIGNAL send flag). + int sock = socket(af, type, 0); + if (sock < 0) { + *err = errno; + } + else { + int flags = fcntl(sock, F_GETFL, 0); + int nosigpipe = 1; + if (flags < 0 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0 + || fcntl(sock, F_SETFD, FD_CLOEXEC) < 0 + || setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, + sizeof(nosigpipe)) + < 0) { + *err = errno; + close(sock); + } + else { + *err = 0; + *ret_sock = sock; + } + } +#else int sock = socket(af, type | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); if (sock < 0) { *err = errno; @@ -746,6 +923,7 @@ create_socket(wasi_ip_address_family_t family, int type, int *ret_sock, *err = 0; *ret_sock = sock; } +#endif } // wasi:sockets/tcp-create-socket @@ -955,10 +1133,9 @@ wasi_sockets_tcp_finish_connect(wasi_tcp_socket_t socket, *err = EWOULDBLOCK; } - // On success, a TCP socket can be used for both reading and writing. + // On success, create independently owned input and output stream fds. if (*err == 0) { - *input_stream = socket; - *output_stream = socket; + *err = duplicate_socket_streams(socket, input_stream, output_stream); } } @@ -1058,11 +1235,28 @@ wasi_sockets_tcp_accept(wasi_tcp_socket_t socket, wasi_tcp_socket_t *ret, } #endif - // On success, the new socket represents a bidirectional stream. - *err = 0; +#if defined(__APPLE__) + // macOS has no MSG_NOSIGNAL: suppress SIGPIPE per-socket on the accepted + // fd, matching the SO_NOSIGPIPE set in create_socket(). + { + int nosigpipe = 1; + if (setsockopt(new_socket, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, + sizeof(nosigpipe)) + < 0) { + *err = errno; + close(new_socket); + return; + } + } +#endif + + // On success, the new socket and each stream own distinct descriptors. + *err = duplicate_socket_streams(new_socket, input_stream, output_stream); + if (*err != 0) { + close(new_socket); + return; + } *ret = new_socket; - *input_stream = dup(new_socket); - *output_stream = dup(new_socket); } /** @@ -1284,7 +1478,12 @@ wasi_sockets_tcp_keep_alive_idle_time(wasi_tcp_socket_t socket, int val; socklen_t len = sizeof(val); // Use the helper to query the TCP_KEEPIDLE socket option. +#if defined(__APPLE__) + // macOS spells the idle-before-first-probe option (seconds) TCP_KEEPALIVE. + get_socket_option(socket, IPPROTO_TCP, TCP_KEEPALIVE, &val, &len, err); +#else get_socket_option(socket, IPPROTO_TCP, TCP_KEEPIDLE, &val, &len, err); +#endif if (*err != 0) { return; } @@ -1307,8 +1506,14 @@ wasi_sockets_tcp_set_keep_alive_idle_time(wasi_tcp_socket_t socket, // The POSIX option requires seconds; convert from nanoseconds. int val = value / 1000000000; // Use the helper to set the TCP_KEEPIDLE socket option. +#if defined(__APPLE__) + // macOS spells the idle-before-first-probe option (seconds) TCP_KEEPALIVE. + return set_socket_option(socket, IPPROTO_TCP, TCP_KEEPALIVE, &val, + sizeof(val)); +#else return set_socket_option(socket, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)); +#endif } /** @@ -1704,24 +1909,7 @@ wasi_sockets_udp_stream(wasi_udp_socket_t socket, return; } } - // Duplicate the socket descriptor to create independent handles for the - // input and output streams, allowing their lifecycles to be managed - // separately. Use F_DUPFD_CLOEXEC to atomically create a new file - // descriptor with the close-on-exec flag set. The O_NONBLOCK flag is - // inherited. - *input_stream = fcntl(socket, F_DUPFD_CLOEXEC, 0); - if (*input_stream < 0) { - *err = errno; - return; - } - - *output_stream = fcntl(socket, F_DUPFD_CLOEXEC, 0); - if (*output_stream < 0) { - close(*input_stream); - *err = errno; - return; - } - *err = 0; + *err = duplicate_socket_streams(socket, input_stream, output_stream); } /** @@ -2258,4 +2446,4 @@ wasi_sockets_udp_send(wasi_outgoing_datagram_stream_t stream, wasm_runtime_free(msgs); wasm_runtime_free(iovs); wasm_runtime_free(native_addrs); -} \ No newline at end of file +} diff --git a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_sockets_wrapper.c b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_sockets_wrapper.c index a37d608143..e706d6eec5 100644 --- a/core/iwasm/libraries/libc-wasi-p2/wasi_p2_sockets_wrapper.c +++ b/core/iwasm/libraries/libc-wasi-p2/wasi_p2_sockets_wrapper.c @@ -20,9 +20,109 @@ #include #include #include +#include // Helper function +static uint32_t +add_tcp_stream_resource(HostResourceTable *table, HostResourceType type, + int32_t fd) +{ + HostResource *resource; + uint32_t rep; + + if (fd < 0) { + return 0; + } + + resource = host_resource_create(type, sizeof(StreamResourceType)); + if (!resource) { + close(fd); + return 0; + } + + ((StreamResourceType *)resource->data)->fd = (uint32_t)fd; + ((StreamResourceType *)resource->data)->type = STREAM_TYPE_SOCKET; + host_resource_set_dtor(resource, tcp_owned_stream_dtor); + + rep = host_resource_table_add(table, resource); + if (rep == 0) { + destroy_host_resource(resource); + } + return rep; +} + +static uint32_t +add_udp_datagram_stream_resource(HostResourceTable *table, + HostResourceType type, int32_t fd) +{ + HostResource *resource; + uint32_t rep; + + if (fd < 0) { + return 0; + } + + resource = host_resource_create(type, sizeof(uint32_t)); + if (!resource) { + close(fd); + return 0; + } + + *(uint32_t *)resource->data = (uint32_t)fd; + host_resource_set_dtor(resource, udp_datagram_stream_dtor); + + rep = host_resource_table_add(table, resource); + if (rep == 0) { + destroy_host_resource(resource); + } + return rep; +} + +static wit_value_t +make_resource_tuple_result(const uint32_t *reps, uint32_t count) +{ + wit_value_t *elems; + wit_value_t tuple; + wit_value_t result; + uint32_t i; + + if (!reps || count == 0) { + return NULL; + } + + elems = wasm_runtime_malloc(count * sizeof(wit_value_t)); + if (!elems) { + return NULL; + } + + for (i = 0; i < count; i++) { + elems[i] = wit_resource_ctor(reps[i]); + if (!elems[i]) { + while (i > 0) { + free_wit_value(elems[--i]); + } + wasm_runtime_free(elems); + return NULL; + } + } + + tuple = wit_tuple_ctor(elems, count); + if (!tuple) { + for (i = 0; i < count; i++) { + free_wit_value(elems[i]); + } + wasm_runtime_free(elems); + return NULL; + } + + result = wit_result_ctor(false, tuple); + if (!result) { + free_wit_value(tuple); + } + return result; +} + /** * @brief Helper function to convert a WASI IP socket address struct into a WIT * Variant value. @@ -204,7 +304,7 @@ wasi_sockets_instance_network_instance_network_wrapper(wasm_exec_env_t exec_env) return 0; } - wit_value_t out_val = wit_u32_ctor(out); + wit_value_t out_val = wit_resource_ctor(out); lower_own(exec_env->cx, func_type->results->result[0].type_specific.resource_handle, out_val, &out); @@ -240,7 +340,6 @@ wasi_sockets_ip_name_lookup_resolve_addresses_wrapper(wasm_exec_env_t exec_env, wit_value_t lifted_handle = NULL; wit_value_t result = NULL; - if (!lift_borrow( exec_env->cx, network_handle, func_type->params->params[0].type->type_specific.resource_handle, @@ -313,7 +412,7 @@ wasi_sockets_ip_name_lookup_resolve_addresses_wrapper(wasm_exec_env_t exec_env, get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); goto end; } - wit_value_t out_val = wit_u32_ctor(out); + wit_value_t out_val = wit_resource_ctor(out); result = wit_result_ctor(false, out_val); goto end; } @@ -474,7 +573,7 @@ wasi_sockets_ip_name_lookup_resolve_address_stream_subscribe_wrapper( return 0; } - wit_value_t out_val = wit_u32_ctor(index_rep); + wit_value_t out_val = wit_resource_ctor(index_rep); lower_own(exec_env->cx, func_type->results->result[0].type_specific.resource_handle, out_val, &index_rep); @@ -542,7 +641,7 @@ wasi_sockets_tcp_create_socket_create_tcp_socket_wrapper( goto end; } else { - wit_value_t out_rep = wit_u32_ctor(index_rep); + wit_value_t out_rep = wit_resource_ctor(index_rep); result = wit_result_ctor(false, out_rep); goto end; } @@ -646,9 +745,9 @@ wasi_sockets_tcp_tcp_socket_start_bind_wrapper( HostResourceTable *hr_table = get_global_host_resource_table(); HostResource *socket_hr = host_resource_table_get( - hr_table, lifted_socket_handle->value.u32_value); + hr_table, lifted_socket_handle->value.resource_value.value); HostResource *network_hr = host_resource_table_get( - hr_table, lifted_network_handle->value.u32_value); + hr_table, lifted_network_handle->value.resource_value.value); if (!(socket_hr && network_hr)) { result = get_result_error_val(WASI_NETWORK_ERROR_CODE_INVALID_ARGUMENT); @@ -821,9 +920,9 @@ wasi_sockets_tcp_tcp_socket_start_connect_wrapper( HostResourceTable *hr_table = get_global_host_resource_table(); HostResource *socket_hr = host_resource_table_get( - hr_table, lifted_socket_handle->value.u32_value); + hr_table, lifted_socket_handle->value.resource_value.value); HostResource *network_hr = host_resource_table_get( - hr_table, lifted_network_handle->value.u32_value); + hr_table, lifted_network_handle->value.resource_value.value); if (!(socket_hr && network_hr)) { result = get_result_error_val(WASI_NETWORK_ERROR_CODE_INVALID_ARGUMENT); @@ -910,27 +1009,32 @@ wasi_sockets_tcp_tcp_socket_finish_connect_wrapper(wasm_exec_env_t exec_env, goto end; } else { - HostResource *hr_input_stream = host_resource_create( - WASI_P2_IO_INPUT_STREAM, sizeof(StreamResourceType)); - ((StreamResourceType *)hr_input_stream->data)->fd = input_stream_fd; - ((StreamResourceType *)hr_input_stream->data)->type = - STREAM_TYPE_SOCKET; - uint32_t input_stream = - host_resource_table_add(hr_table, hr_input_stream); - - HostResource *hr_output_stream = host_resource_create( - WASI_P2_IO_OUTPUT_STREAM, sizeof(StreamResourceType)); - ((StreamResourceType *)hr_output_stream->data)->fd = output_stream_fd; - ((StreamResourceType *)hr_output_stream->data)->type = - STREAM_TYPE_SOCKET; - uint32_t output_stream = - host_resource_table_add(hr_table, hr_output_stream); - - wit_value_t elems[2]; - elems[0] = wit_u32_ctor(input_stream); - elems[1] = wit_u32_ctor(output_stream); - wit_value_t result_tuple = wit_tuple_ctor(elems, 2); - result = wit_result_ctor(false, result_tuple); + uint32_t input_stream = add_tcp_stream_resource( + hr_table, WASI_P2_IO_INPUT_STREAM, input_stream_fd); + if (input_stream == 0) { + close(output_stream_fd); + result = + get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); + goto end; + } + + uint32_t output_stream = add_tcp_stream_resource( + hr_table, WASI_P2_IO_OUTPUT_STREAM, output_stream_fd); + if (output_stream == 0) { + host_resource_table_delete(hr_table, input_stream); + result = + get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); + goto end; + } + + uint32_t reps[] = { input_stream, output_stream }; + result = make_resource_tuple_result(reps, 2); + if (!result) { + host_resource_table_delete(hr_table, output_stream); + host_resource_table_delete(hr_table, input_stream); + result = + get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); + } goto end; } @@ -1128,8 +1232,11 @@ wasi_sockets_tcp_tcp_socket_accept_wrapper(wasm_exec_env_t exec_env, WASI_P2_TCP_SOCKET, sizeof(wasi_socket_context_t)); if (!hr_new) { - result = get_result_error_val( - WASI_NETWORK_ERROR_CODE_ADDRESS_NOT_BINDABLE); + close(new_socket); + close(input_stream); + close(output_stream); + result = + get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); goto end; } @@ -1141,77 +1248,44 @@ wasi_sockets_tcp_tcp_socket_accept_wrapper(wasm_exec_env_t exec_env, uint32_t out = host_resource_table_add(hr_table, hr_new); if (out < 1) { - destroy_host_resource( - hr_new); // Clean up the HostResource on failure - result = get_result_error_val( - WASI_NETWORK_ERROR_CODE_ADDRESS_NOT_BINDABLE); - goto end; - } - - // In stream - HostResource *hr_incoming_stream = host_resource_create( - WASI_P2_IO_INPUT_STREAM, sizeof(StreamResourceType)); - ((StreamResourceType *)hr_incoming_stream->data)->fd = input_stream; - ((StreamResourceType *)hr_incoming_stream->data)->type = - STREAM_TYPE_SOCKET; - host_resource_set_dtor(hr_incoming_stream, tcp_owned_stream_dtor); - - if (!hr_incoming_stream) { + destroy_host_resource(hr_new); + close(input_stream); + close(output_stream); result = get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); goto end; } - ((StreamResourceType *)hr_incoming_stream->data)->fd = input_stream; - ((StreamResourceType *)hr_incoming_stream->data)->type = - STREAM_TYPE_SOCKET; - - uint32_t incoming_rep = - host_resource_table_add(hr_table, hr_incoming_stream); - if (incoming_rep < 1) { - destroy_host_resource(hr_incoming_stream); + uint32_t incoming_rep = add_tcp_stream_resource( + hr_table, WASI_P2_IO_INPUT_STREAM, input_stream); + if (incoming_rep == 0) { + host_resource_table_delete(hr_table, out); + close(output_stream); result = get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); goto end; } - // Out stream - HostResource *hr_outgoing_stream = host_resource_create( - WASI_P2_IO_OUTPUT_STREAM, sizeof(StreamResourceType)); - ((StreamResourceType *)hr_outgoing_stream->data)->fd = output_stream; - ((StreamResourceType *)hr_outgoing_stream->data)->type = - STREAM_TYPE_SOCKET; - host_resource_set_dtor(hr_outgoing_stream, tcp_owned_stream_dtor); - - if (!hr_outgoing_stream) { - destroy_host_resource(hr_incoming_stream); + uint32_t outgoing_rep = add_tcp_stream_resource( + hr_table, WASI_P2_IO_OUTPUT_STREAM, output_stream); + if (outgoing_rep == 0) { + host_resource_table_delete(hr_table, incoming_rep); + host_resource_table_delete(hr_table, out); result = get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); goto end; } - ((StreamResourceType *)hr_outgoing_stream->data)->fd = output_stream; - ((StreamResourceType *)hr_outgoing_stream->data)->type = - STREAM_TYPE_SOCKET; - - uint32_t outgoing_rep = - host_resource_table_add(hr_table, hr_outgoing_stream); - if (outgoing_rep < 1) { - destroy_host_resource(hr_incoming_stream); - destroy_host_resource(hr_outgoing_stream); + uint32_t reps[] = { out, incoming_rep, outgoing_rep }; + result = make_resource_tuple_result(reps, 3); + if (!result) { + host_resource_table_delete(hr_table, outgoing_rep); + host_resource_table_delete(hr_table, incoming_rep); + host_resource_table_delete(hr_table, out); result = get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); - goto end; } - wit_value_t *elems = - (wit_value_t *)wasm_runtime_malloc(3 * sizeof(wit_value_t)); - elems[0] = wit_s32_ctor(out); - elems[1] = wit_s32_ctor(incoming_rep); - elems[2] = wit_s32_ctor(outgoing_rep); - wit_value_t result_tuple = wit_tuple_ctor(elems, 3); - result = wit_result_ctor(false, result_tuple); - goto end; } end: @@ -2482,7 +2556,7 @@ wasi_sockets_tcp_tcp_socket_subscribe_wrapper(wasm_exec_env_t exec_env, return 0; } - wit_value_t out_val = wit_u32_ctor(index_rep); + wit_value_t out_val = wit_resource_ctor(index_rep); lower_own(exec_env->cx, func_type->results->result[0].type_specific.resource_handle, out_val, &index_rep); @@ -2612,7 +2686,7 @@ wasi_sockets_udp_create_socket_create_udp_socket_wrapper( get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); goto end; } - wit_value_t out_rep = wit_u32_ctor(index_rep); + wit_value_t out_rep = wit_resource_ctor(index_rep); result = wit_result_ctor(false, out_rep); goto end; } @@ -2679,9 +2753,9 @@ wasi_sockets_udp_udp_socket_start_bind_wrapper( HostResourceTable *hr_table = get_global_host_resource_table(); HostResource *socket_hr = host_resource_table_get( - hr_table, lifted_socket_handle->value.u32_value); + hr_table, lifted_socket_handle->value.resource_value.value); HostResource *network_hr = host_resource_table_get( - hr_table, lifted_network_handle->value.u32_value); + hr_table, lifted_network_handle->value.resource_value.value); if (!(socket_hr && network_hr)) { result = get_result_error_val(WASI_NETWORK_ERROR_CODE_INVALID_ARGUMENT); @@ -2845,59 +2919,32 @@ wasi_sockets_udp_udp_socket_stream_wrapper( goto end; } else { - // In stream - HostResource *hr_incoming_stream = host_resource_create( - WASI_P2_UDP_INCOMING_DATAGRAM_STREAM, sizeof(incoming_stream)); - *((uint32_t *)hr_incoming_stream->data) = incoming_stream; - host_resource_set_dtor(hr_incoming_stream, udp_datagram_stream_dtor); - - if (!hr_incoming_stream) { + uint32_t incoming_rep = add_udp_datagram_stream_resource( + hr_table, WASI_P2_UDP_INCOMING_DATAGRAM_STREAM, incoming_stream); + if (incoming_rep == 0) { + close(outgoing_stream); result = get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); goto end; } - *((uint32_t *)hr_incoming_stream->data) = incoming_stream; - - uint32_t incoming_rep = - host_resource_table_add(hr_table, hr_incoming_stream); - if (incoming_rep < 1) { - destroy_host_resource(hr_incoming_stream); + uint32_t outgoing_rep = add_udp_datagram_stream_resource( + hr_table, WASI_P2_UDP_OUTGOING_DATAGRAM_STREAM, outgoing_stream); + if (outgoing_rep == 0) { + host_resource_table_delete(hr_table, incoming_rep); result = get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); goto end; } - // Out stream — fcntl(F_DUPFD_CLOEXEC) fd from udp_stream(), owned - HostResource *hr_outgoing_stream = host_resource_create( - WASI_P2_UDP_OUTGOING_DATAGRAM_STREAM, sizeof(outgoing_stream)); - *((uint32_t *)hr_outgoing_stream->data) = outgoing_stream; - host_resource_set_dtor(hr_outgoing_stream, udp_datagram_stream_dtor); - - if (!hr_outgoing_stream) { - destroy_host_resource(hr_incoming_stream); - result = - get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); - goto end; - } - - *((uint32_t *)hr_outgoing_stream->data) = outgoing_stream; - - uint32_t outgoing_rep = - host_resource_table_add(hr_table, hr_outgoing_stream); - if (outgoing_rep < 1) { - destroy_host_resource(hr_incoming_stream); - destroy_host_resource(hr_outgoing_stream); + uint32_t reps[] = { incoming_rep, outgoing_rep }; + result = make_resource_tuple_result(reps, 2); + if (!result) { + host_resource_table_delete(hr_table, outgoing_rep); + host_resource_table_delete(hr_table, incoming_rep); result = get_result_error_val(WASI_NETWORK_ERROR_CODE_OUT_OF_MEMORY); - goto end; } - - wit_value_t *elems = wasm_runtime_malloc(2 * sizeof(wit_value_t)); - elems[0] = wit_u32_ctor(incoming_rep); - elems[1] = wit_u32_ctor(outgoing_rep); - wit_value_t result_tuple = wit_tuple_ctor(elems, 2); - result = wit_result_ctor(false, result_tuple); goto end; } @@ -3510,7 +3557,7 @@ wasi_sockets_udp_udp_socket_subscribe_wrapper(wasm_exec_env_t exec_env, return 0; } - wit_value_t out_val = wit_u32_ctor(index_rep); + wit_value_t out_val = wit_resource_ctor(index_rep); lower_own(exec_env->cx, func_type->results->result[0].type_specific.resource_handle, out_val, &index_rep); @@ -3540,6 +3587,9 @@ wasi_sockets_udp_incoming_datagram_stream_receive_wrapper( wit_value_t lifted_handle = NULL; wit_value_t result = NULL; + wasi_incoming_datagram_t *datagrams = NULL; + uint64_t datagrams_len = 0; + uint64_t i = 0; if (!wasi_ctx->wasi_options->cli || !wasi_ctx->wasi_options->common || !wasi_ctx->wasi_options->inherit_network @@ -3556,10 +3606,7 @@ wasi_sockets_udp_incoming_datagram_stream_receive_wrapper( goto end; } - wasi_incoming_datagram_t *datagrams = NULL; - uint64_t datagrams_len = 0; int err = 0; - uint64_t i; HostResourceTable *hr_table = get_global_host_resource_table(); HostResource *hr = host_resource_table_get( @@ -3671,7 +3718,7 @@ wasi_sockets_udp_incoming_datagram_stream_subscribe_wrapper( return 0; } - wit_value_t out_val = wit_u32_ctor(index_rep); + wit_value_t out_val = wit_resource_ctor(index_rep); lower_own(exec_env->cx, func_type->results->result[0].type_specific.resource_handle, out_val, &index_rep); @@ -3771,6 +3818,7 @@ wasi_sockets_udp_outgoing_datagram_stream_send_wrapper(wasm_exec_env_t exec_env, wit_value_t lifted_handle = NULL; wasi_outgoing_datagram_t *datagrams_native = NULL; + uint64_t i = 0; if (!wasi_ctx->wasi_options->cli || !wasi_ctx->wasi_options->common || !wasi_ctx->wasi_options->inherit_network @@ -3781,8 +3829,6 @@ wasi_sockets_udp_outgoing_datagram_stream_send_wrapper(wasm_exec_env_t exec_env, uint64_t sent_count = 0; int err = 0; - uint64_t i = 0; - wit_value_t datagrams_val; load_list_from_range( exec_env->cx, datagrams_ptr, datagrams_len, @@ -4011,7 +4057,7 @@ wasi_sockets_udp_outgoing_datagram_stream_subscribe_wrapper( return 0; } - wit_value_t out_val = wit_u32_ctor(index_rep); + wit_value_t out_val = wit_resource_ctor(index_rep); lower_own(exec_env->cx, func_type->results->result[0].type_specific.resource_handle, out_val, &index_rep); @@ -4019,4 +4065,4 @@ wasi_sockets_udp_outgoing_datagram_stream_subscribe_wrapper( free_wit_value(out_val); return index_rep; -} \ No newline at end of file +} diff --git a/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c b/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c index 5b76231b46..a7cd2abd2e 100644 --- a/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c +++ b/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c @@ -1093,13 +1093,16 @@ wasi_poll_oneoff(wasm_exec_env_t exec_env, const wasi_subscription_t *in, wasi_ctx_t wasi_ctx = get_wasi_ctx(module_inst); struct fd_table *curfds = wasi_ctx_get_curfds(wasi_ctx); size_t nevents = 0; + uint64 subscriptions_size = + (uint64)nsubscriptions * sizeof(wasi_subscription_t); + uint64 events_size = (uint64)nsubscriptions * sizeof(wasi_event_t); wasi_errno_t err; if (!wasi_ctx) return (wasi_errno_t)-1; - if (!validate_native_addr((void *)in, (uint64)sizeof(wasi_subscription_t)) - || !validate_native_addr(out, (uint64)sizeof(wasi_event_t)) + if (!validate_native_addr((void *)in, subscriptions_size) + || !validate_native_addr(out, events_size) || !validate_native_addr(nevents_app, (uint64)sizeof(uint32))) return (wasi_errno_t)-1; diff --git a/core/shared/platform/common/math/math.c b/core/shared/platform/common/math/math.c index 3c0171570f..e8d186a0d2 100644 --- a/core/shared/platform/common/math/math.c +++ b/core/shared/platform/common/math/math.c @@ -1408,7 +1408,8 @@ freebsd_pow(double x, double y) n = (hx>>31)+1; but ANSI C says a right shift of a signed negative quantity is implementation defined. */ - n = ((u_int32_t)hx >> 31) - 1; + /* equal to n = (hx < 0) - 1. But if it works, don't improve it */ + n = (int32_t)((u_int32_t)hx >> 31) - 1; /* (x<0)**(non-int) is NaN */ if ((n | yisint) == 0) diff --git a/core/shared/platform/common/posix/platform_api_posix.cmake b/core/shared/platform/common/posix/platform_api_posix.cmake index 2553a7d04a..bb46cab9e1 100644 --- a/core/shared/platform/common/posix/platform_api_posix.cmake +++ b/core/shared/platform/common/posix/platform_api_posix.cmake @@ -5,14 +5,17 @@ set (PLATFORM_COMMON_POSIX_DIR ${CMAKE_CURRENT_LIST_DIR}) file (GLOB_RECURSE source_all ${PLATFORM_COMMON_POSIX_DIR}/*.c) -if (NOT WAMR_BUILD_LIBC_WASI EQUAL 1) +if (NOT WAMR_BUILD_LIBC_WASI EQUAL 1 + AND NOT WAMR_BUILD_LIBC_WASI_P2 EQUAL 1) list(REMOVE_ITEM source_all ${PLATFORM_COMMON_POSIX_DIR}/posix_file.c ${PLATFORM_COMMON_POSIX_DIR}/posix_clock.c ) endif() -if ((NOT WAMR_BUILD_LIBC_WASI EQUAL 1) AND (NOT WAMR_BUILD_DEBUG_INTERP EQUAL 1)) +if ((NOT WAMR_BUILD_LIBC_WASI EQUAL 1) + AND (NOT WAMR_BUILD_LIBC_WASI_P2 EQUAL 1) + AND (NOT WAMR_BUILD_DEBUG_INTERP EQUAL 1)) list(REMOVE_ITEM source_all ${PLATFORM_COMMON_POSIX_DIR}/posix_socket.c ) diff --git a/core/shared/platform/common/posix/posix_memmap.c b/core/shared/platform/common/posix/posix_memmap.c index d5cad885ca..7e83453a58 100644 --- a/core/shared/platform/common/posix/posix_memmap.c +++ b/core/shared/platform/common/posix/posix_memmap.c @@ -41,12 +41,7 @@ void * os_mmap(void *hint, size_t size, int prot, int flags, os_file_handle file) { int map_prot = PROT_NONE; -#if (defined(__APPLE__) || defined(__MACH__)) && defined(__arm64__) \ - && defined(TARGET_OS_OSX) && TARGET_OS_OSX != 0 - int map_flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_JIT; -#else int map_flags = MAP_ANONYMOUS | MAP_PRIVATE; -#endif uint64 request_size, page_size; uint8 *addr = MAP_FAILED; uint32 i; @@ -82,6 +77,15 @@ os_mmap(void *hint, size_t size, int prot, int flags, os_file_handle file) if (prot & MMAP_PROT_EXEC) map_prot |= PROT_EXEC; +#if (defined(__APPLE__) || defined(__MACH__)) && defined(__arm64__) \ + && defined(TARGET_OS_OSX) && TARGET_OS_OSX != 0 + /* MAP_JIT is only needed for executable mappings. Adding it to ordinary + * interpreter memory requires an unnecessary JIT entitlement in hardened + * macOS applications. */ + if (prot & MMAP_PROT_EXEC) + map_flags |= MAP_JIT; +#endif + #if defined(BUILD_TARGET_X86_64) || defined(BUILD_TARGET_AMD_64) #ifndef __APPLE__ if (flags & MMAP_MAP_32BIT) diff --git a/core/shared/platform/include/platform_wasi_types.h b/core/shared/platform/include/platform_wasi_types.h index 980d3f84ec..c372711e06 100644 --- a/core/shared/platform/include/platform_wasi_types.h +++ b/core/shared/platform/include/platform_wasi_types.h @@ -36,7 +36,8 @@ extern "C" { /* There is no need to check the WASI layout if we're using uvwasi or libc-wasi * is not enabled at all. */ -#if WASM_ENABLE_UVWASI != 0 || WASM_ENABLE_LIBC_WASI == 0 +#if WASM_ENABLE_UVWASI != 0 \ + || (WASM_ENABLE_LIBC_WASI == 0 && WASM_ENABLE_LIBC_WASI_P2 == 0) #define assert_wasi_layout(expr, message) /* nothing */ #else #define assert_wasi_layout(expr, message) _Static_assert(expr, message) diff --git a/core/shared/platform/linux-sgx/sgx_platform.c b/core/shared/platform/linux-sgx/sgx_platform.c index d259908634..f2e5b42991 100644 --- a/core/shared/platform/linux-sgx/sgx_platform.c +++ b/core/shared/platform/linux-sgx/sgx_platform.c @@ -119,7 +119,7 @@ strcpy(char *dest, const char *src) return dest; } -#if WASM_ENABLE_LIBC_WASI == 0 +#if WASM_ENABLE_LIBC_WASI == 0 && WASM_ENABLE_LIBC_WASI_P2 == 0 bool os_is_handle_valid(os_file_handle *handle) { diff --git a/core/shared/platform/linux-sgx/shared_platform.cmake b/core/shared/platform/linux-sgx/shared_platform.cmake index e8e1670058..5203dc6d22 100644 --- a/core/shared/platform/linux-sgx/shared_platform.cmake +++ b/core/shared/platform/linux-sgx/shared_platform.cmake @@ -26,7 +26,8 @@ endif () file (GLOB source_all ${PLATFORM_SHARED_DIR}/*.c) -if (NOT WAMR_BUILD_LIBC_WASI EQUAL 1) +if (NOT WAMR_BUILD_LIBC_WASI EQUAL 1 + AND NOT WAMR_BUILD_LIBC_WASI_P2 EQUAL 1) add_definitions(-DSGX_DISABLE_WASI) else() list(APPEND source_all @@ -42,4 +43,3 @@ file (GLOB source_all_untrusted ${PLATFORM_SHARED_DIR}/untrusted/*.c) set (PLATFORM_SHARED_SOURCE ${source_all}) set (PLATFORM_SHARED_SOURCE_UNTRUSTED ${source_all_untrusted}) - diff --git a/core/shared/platform/nuttx/shared_platform.cmake b/core/shared/platform/nuttx/shared_platform.cmake index d691068f2f..a1f8f9d706 100644 --- a/core/shared/platform/nuttx/shared_platform.cmake +++ b/core/shared/platform/nuttx/shared_platform.cmake @@ -14,7 +14,7 @@ include (${SHARED_DIR}/utils/uncommon/shared_uncommon.cmake) file (GLOB_RECURSE source_all ${PLATFORM_SHARED_DIR}/*.c) -if (WAMR_BUILD_LIBC_WASI EQUAL 1) +if (WAMR_BUILD_LIBC_WASI EQUAL 1 OR WAMR_BUILD_LIBC_WASI_P2 EQUAL 1) list(APPEND source_all ${PLATFORM_SHARED_DIR}/../common/posix/posix_file.c) include (${CMAKE_CURRENT_LIST_DIR}/../common/libc-util/platform_common_libc_util.cmake) set(source_all ${source_all} ${PLATFORM_COMMON_LIBC_UTIL_SOURCE}) @@ -23,4 +23,3 @@ endif () set (PLATFORM_SHARED_SOURCE ${source_all} ${PLATFORM_COMMON_MATH_SOURCE} ${PLATFORM_COMMON_POSIX_SOURCE} ${UNCOMMON_SHARED_SOURCE}) # remove posix_memmap.c for NuttX list(REMOVE_ITEM ${PLATFORM_SHARED_SOURCE} ${PLATFORM_COMMON_POSIX_DIR}/posix_memmap.c) - diff --git a/core/shared/platform/windows/shared_platform.cmake b/core/shared/platform/windows/shared_platform.cmake index 7a3331eff1..21cca1f14a 100644 --- a/core/shared/platform/windows/shared_platform.cmake +++ b/core/shared/platform/windows/shared_platform.cmake @@ -14,7 +14,8 @@ include_directories(${PLATFORM_SHARED_DIR}/../include) file (GLOB_RECURSE source_all ${PLATFORM_SHARED_DIR}/*.c ${PLATFORM_SHARED_DIR}/*.cpp) -if (NOT WAMR_BUILD_LIBC_WASI EQUAL 1) +if (NOT WAMR_BUILD_LIBC_WASI EQUAL 1 + AND NOT WAMR_BUILD_LIBC_WASI_P2 EQUAL 1) list(REMOVE_ITEM source_all ${PLATFORM_SHARED_DIR}/win_file.c) elseif (WAMR_BUILD_LIBC_UVWASI EQUAL 1) # uvwasi doesn't need to compile win_file.c diff --git a/core/shared/platform/zephyr/shared_platform.cmake b/core/shared/platform/zephyr/shared_platform.cmake index f424b97204..5df92798a6 100644 --- a/core/shared/platform/zephyr/shared_platform.cmake +++ b/core/shared/platform/zephyr/shared_platform.cmake @@ -15,7 +15,8 @@ if(${CONFIG_MINIMAL_LIBC}) set (source_all ${source_all} ${PLATFORM_COMMON_MATH_SOURCE}) endif() -if (NOT WAMR_BUILD_LIBC_WASI EQUAL 1) +if (NOT WAMR_BUILD_LIBC_WASI EQUAL 1 + AND NOT WAMR_BUILD_LIBC_WASI_P2 EQUAL 1) list(REMOVE_ITEM source_all ${PLATFORM_SHARED_DIR}/zephyr_socket.c) list(REMOVE_ITEM source_all ${PLATFORM_SHARED_DIR}/zephyr_file.c) list(REMOVE_ITEM source_all ${PLATFORM_SHARED_DIR}/zephyr_clock.c) diff --git a/doc/component_conformance_plan.md b/doc/component_conformance_plan.md new file mode 100644 index 0000000000..390b37176d --- /dev/null +++ b/doc/component_conformance_plan.md @@ -0,0 +1,39 @@ +# Component Model / WASIp2 conformance plan + +The bespoke gtest unit tests verify that a given constructor/layout produces +the value the test author hand-wrote. They almost never verify that a malformed +or hostile input **traps**, and they have no independent oracle: a systematic +encoding bug implemented identically in the production code and in the test's +expected value passes forever. This plan tracks closing both gaps, prioritized +for an interpreter-only on-device runtime (no JIT/CFI safety net). + +## Status + +| Increment | State | +|-----------|-------| +| Canonical-ABI bounds/alignment/overflow trap tests | **this PR** (`tests/unit/canonical-abi/test_canonical_abi_traps.cc`) | +| ptr+len overflow fix in the bounds checks | **this PR** (`wasm_component_canonical.c`: widen `load_int` / `load_string_from_range` / `load_list_from_range` before the add) | +| Invalid enum/variant discriminant trap tests | next | +| Resource-handle safety traps (unknown-handle, wrong-type, lift-own-from-borrow, remove-owned-while-borrowed) | next | +| UTF-16 / Latin1+UTF-16 transcode round-trip + surrogate/NaN-canonicalization coverage | next | +| Differential canonical-ABI execution vs wasmtime deterministic mode | planned | +| Official component-model `.wast` conformance runner | planned (needs a text→binary component encoder; WAMR loads binary only) | + +## Differential oracle (the highest-value item) + +Lift/lower the same byte buffers through both WAMR and wasmtime +(`Config::relaxed_simd_deterministic`-style deterministic component mode) and +assert (a) byte-identical lifted values and (b) identical trap/no-trap +outcomes. This converts every hand-authored constant into an oracle-checked +value and simultaneously lands most of the trap-coverage gaps (wasmtime traps +where WAMR must also trap). Same pattern already used for relaxed-SIMD in the +benchmark repo's `relaxed_simd_diff_fuzz` harness. + +## Why these specific traps matter on-device + +Every canonical-ABI `trap_if` guard is the boundary between a clean trap and a +wild read/write of linear memory. With no JIT/CFI on iOS/watchOS, an unchecked +ptr+len overflow or an out-of-range variant discriminant is a direct path from +a crafted component binary to silent memory corruption. The trap tests in this +PR are the cheapest high-value coverage because they target that path directly +and need no second runtime. diff --git a/doc/security_issue_runbook.md b/doc/security_issue_runbook.md index 8b0bc7970b..54cc31f25a 100644 --- a/doc/security_issue_runbook.md +++ b/doc/security_issue_runbook.md @@ -17,6 +17,7 @@ For information on what types of issues are considered security vulnerabilities ## Step 3: Communication and Collaboration - Use Non-Public Channels: Communicate through non-public channels, preferably email, during the resolution process. Avoid filing issues or pull requests on third-party repositories if they are involved. +- Work should be done in a private fork created for the security advisory. Additional collaborators should be invited to this private fork as needed. - Workaround for Third-Party Dependencies: If third-party dependencies are involved, consider a workaround to patch the issue quickly unless the third party can release a fix promptly. ## Step 4: Finalizing and Preparing for Release @@ -28,7 +29,7 @@ For information on what types of issues are considered security vulnerabilities ``` markdown > A template for the advanced disclosure email -The Wamr project would like to announce a forthcoming security release. +The WAMR project would like to announce a forthcoming security release. The release will be made available on approximately YYYY-MM-DD. Additionally, an advisory will be made available on the same date at https://github.com/advisories. @@ -41,9 +42,9 @@ The highest severity issue fixed in this release is classified as XXX based on t - Run Full Test Suite: Run the full test suite locally for the main branch. Attempt to run as much of the CI matrix locally as possible. ## Step 6: Public Release and Communication - +On Release day: - Open Version Bump PRs: Open version bump pull requests on the public repository without including patch notes or release notes for the fix. -- Manually Make PRs from Private Fork: Transfer the necessary pull requests from the private fork to the public repository. +- Manually Make PRs from Private Fork: Make public PRs from all of the previously-created PRs on the private fork. You'll need to push the changes to your own personal repository for this (in order to submit PRs to the official repository). It's ok since it's release day and time to make these fixes public anyway. NOTE: DO NOT merge via the GitHub Security Advisory as this has generally not worked well. - Merge and Trigger Releases: Merge the version bump PRs and trigger the release process. - Publish GitHub Advisories: Delete the private forks and use the Big Green Button to publish the advisory. - Send Security Release Email: Send a follow-up email to sec-announce@bytecodealliance.org describing the security release. Other communication channels can also be used to inform users about the security release. diff --git a/product-mini/platforms/posix/main.c b/product-mini/platforms/posix/main.c index f5a39efde8..73ce6c4de5 100644 --- a/product-mini/platforms/posix/main.c +++ b/product-mini/platforms/posix/main.c @@ -14,13 +14,15 @@ #include "bh_read_file.h" #include "wasm_export.h" #if WASM_ENABLE_COMPONENT_MODEL != 0 +/* The component-model headers below pull in core-module types from the + * interpreter's wasm.h. That header lives in core/iwasm/interpreter, which is + * only on the include path when WAMR_BUILD_INTERP=1, so keep this include + * inside the component-model guard: a pure AOT-only build (INTERP=0) does not + * build the component model and must not reference wasm.h. */ +#include "wasm.h" #include "wasm_component.h" #include "wasm_component_runtime.h" -#include "component-model/wasm_component_host_resource.h" -#include "wasm_component_export.h" -#include "component-model/wasm_component_validate.h" #endif -#include "wasm.h" #if WASM_ENABLE_LIBC_WASI != 0 #include "../common/libc_wasi.c" #endif @@ -646,7 +648,7 @@ execute_wasm_module(uint8 *wasm_file_buf, uint32 wasm_file_size, #if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0 bool disable_bounds_checks, #endif - char *error_buf, int32 *ret_value, + char *error_buf, uint32 error_buf_size, int32 *ret_value, wasm_module_t *wasm_module_out, wasm_module_inst_t *wasm_module_inst_out, int argc, char *argv[] @@ -682,14 +684,14 @@ execute_wasm_module(uint8 *wasm_file_buf, uint32 wasm_file_size, #endif if (!(wasm_module = wasm_runtime_load(wasm_file_buf, wasm_file_size, - error_buf, sizeof(error_buf)))) { + error_buf, error_buf_size))) { printf("%s\n", error_buf); return false; } #if WASM_ENABLE_DYNAMIC_AOT_DEBUG != 0 if (!wasm_runtime_set_module_name(wasm_module, wasm_file, error_buf, - sizeof(error_buf))) { + error_buf_size)) { printf("set aot module name failed in dynamic aot debug mode, %s\n", error_buf); wasm_runtime_unload(wasm_module); @@ -711,11 +713,18 @@ execute_wasm_module(uint8 *wasm_file_buf, uint32 wasm_file_size, libc_wasi_set_init_args(inst_args, argc, argv, wasi_parse_ctx); #endif - wasm_module_inst = wasm_runtime_instantiate_ex2( - wasm_module, inst_args, error_buf, sizeof(error_buf)); + wasm_module_inst = wasm_runtime_instantiate_ex2(wasm_module, inst_args, + error_buf, error_buf_size); wasm_runtime_instantiation_args_destroy(inst_args); if (!wasm_module_inst) { - LOG_ERROR("%s\n", error_buf); + /* Print the instantiation error directly (matching upstream and the + * module-load error path above). LOG_ERROR routes through bh_log, + * which prefixes a "[time - tid]:" banner that pollutes iwasm's + * stdout; the spec-test harness reads that stream for the + * "webassembly> " prompt / expected trap text, so the banner makes + * data/elem/start (and the ba-issues regression suite) spuriously + * fail. */ + printf("%s\n", error_buf); wasm_runtime_unload(wasm_module); return false; } @@ -824,7 +833,7 @@ execute_wasm_component(uint8 *wasm_file_buf, uint32 wasm_file_size, uint32 stack_size, uint32 heap_size, bool is_repl_mode, bool is_component_func_invoke, char *error_buf, uint32 error_buf_size, int32 *ret_value, - WASMComponent *component_out, + WASMComponent **component_out, WASMComponentInstance **component_inst_out #if WASM_ENABLE_LIBC_WASI != 0 , @@ -832,6 +841,7 @@ execute_wasm_component(uint8 *wasm_file_buf, uint32 wasm_file_size, #endif ) { + WASMComponent *component = NULL; LOG_DEBUG("Loading WASM Component (Preview 2)...\n"); LoadArgs load_args = { 0 }; @@ -840,52 +850,40 @@ execute_wasm_component(uint8 *wasm_file_buf, uint32 wasm_file_size, load_args.clone_wasm_binary = false; load_args.no_resolve = false; load_args.is_component = true; - struct InstantiationArgs2 *inst_args; + struct InstantiationArgs2 *inst_args = NULL; const char *exception = NULL; - memset(component_out, 0, sizeof(WASMComponent)); - - if (!wasm_decode_header(wasm_file_buf, wasm_file_size, - &component_out->header)) { - snprintf(error_buf, error_buf_size, "Failed to decode WASM header"); - *ret_value = -1; - return false; - } - - if (!wasm_component_parse_sections(wasm_file_buf, wasm_file_size, - component_out, &load_args, 0)) { - snprintf(error_buf, error_buf_size, - "Failed to parse WASM component sections"); - *ret_value = -1; - return false; - } - - if (!wasm_component_validate(component_out, NULL, error_buf, - error_buf_size)) { + *component_out = NULL; + *component_inst_out = NULL; + component = wasm_component_load(wasm_file_buf, wasm_file_size, &load_args, + error_buf, error_buf_size); + if (!component) { LOG_DEBUG("Validation failed: %s\n", error_buf); *ret_value = -1; return false; } #if WASM_ENABLE_LIBC_WASI != 0 - libc_component_wasi_init(component_out, app_argc, app_argv, wasi_parse_ctx); + libc_component_wasi_init(component, app_argc, app_argv, wasi_parse_ctx); #endif if (!wasm_runtime_instantiation_args_create(&inst_args)) { - LOG_ERROR("failed to create instantiate args\n"); - return false; + snprintf(error_buf, error_buf_size, + "Failed to create component instantiation arguments"); + goto fail; } wasm_runtime_instantiation_args_set_default_stack_size(inst_args, stack_size); wasm_runtime_instantiation_args_set_host_managed_heap_size(inst_args, heap_size); - WASMComponentInstance *component_inst = - wasm_component_instantiate(component_out, error_buf, error_buf_size); + WASMComponentInstance *component_inst = wasm_component_instantiate_ex2( + component, inst_args, error_buf, error_buf_size); wasm_runtime_instantiation_args_destroy(inst_args); + inst_args = NULL; if (!component_inst) { LOG_ERROR("Error in instantiation: %s\n", error_buf); - return false; + goto fail; } *ret_value = 0; @@ -910,8 +908,16 @@ execute_wasm_component(uint8 *wasm_file_buf, uint32 wasm_file_size, if (exception) LOG_ERROR("Exception occured: %s\n", exception); + *component_out = component; *component_inst_out = component_inst; return true; + +fail: + if (inst_args) + wasm_runtime_instantiation_args_destroy(inst_args); + wasm_component_unload(component); + *ret_value = -1; + return false; } #endif @@ -951,10 +957,11 @@ main(int argc, char *argv[]) wasm_module_t wasm_module = NULL; wasm_module_inst_t wasm_module_inst = NULL; #if WASM_ENABLE_COMPONENT_MODEL != 0 - WASMComponent component; + WASMComponent *component = NULL; WASMComponentInstance *component_inst = NULL; bool component_loaded = false; - bool component_func_invoke = false; // 'true' if component function is invoked + bool component_func_invoke = + false; // 'true' if component function is invoked #endif #if WASM_ENABLE_COMPONENT_MODEL != 0 bool module_func_invoke = false; // 'true' if module function is invoked @@ -1356,7 +1363,6 @@ main(int argc, char *argv[]) } LOG_DEBUG("Detected WASM Component (Preview 2)\n"); LOG_DEBUG("Executing WASM Component...\n"); - set_component_runtime(true); if (!execute_wasm_component( wasm_file_buf, wasm_file_size, stack_size, heap_size, is_repl_mode, component_func_invoke, error_buf, @@ -1372,7 +1378,17 @@ main(int argc, char *argv[]) } else #endif - if (is_wasm_module(header)) { + if (is_wasm_module(header) +#if WASM_ENABLE_AOT != 0 + /* AoT files carry the "\0aot" magic, so is_wasm_module() (which + * only matches the "\0asm" bytecode magic) rejects them. Route + * them through the same execute_wasm_module()/wasm_runtime_load() + * path, which dispatches AoT internally — otherwise `iwasm + * foo.aot` falls through to the "Unknown WASM file type" error. */ + || get_package_type(wasm_file_buf, wasm_file_size) + == Wasm_Module_AoT +#endif + ) { #if WASM_ENABLE_COMPONENT_MODEL != 0 if (component_func_invoke) { ret = print_help(); @@ -1400,8 +1416,9 @@ main(int argc, char *argv[]) #if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0 disable_bounds_checks, #endif - error_buf, &ret, &wasm_module, - &wasm_module_inst, app_argc, app_argv + error_buf, sizeof(error_buf), &ret, + &wasm_module, &wasm_module_inst, app_argc, + app_argv #if WASM_ENABLE_LIBC_WASI != 0 , &wasi_parse_ctx @@ -1436,10 +1453,9 @@ main(int argc, char *argv[]) /* cleanup module/component resources */ #if WASM_ENABLE_COMPONENT_MODEL != 0 if (component_loaded) { - if (component_inst) { + if (component_inst) wasm_component_deinstantiate(component_inst); - wasm_component_free(&component); - } + wasm_component_unload(component); } #endif if (wasm_module_inst) { diff --git a/tests/apple-app-store-interpreter/apple_wasip2_fast_interp.cmake b/tests/apple-app-store-interpreter/apple_wasip2_fast_interp.cmake new file mode 100644 index 0000000000..2c9a2fd137 --- /dev/null +++ b/tests/apple-app-store-interpreter/apple_wasip2_fast_interp.cmake @@ -0,0 +1,48 @@ +# Copyright (C) 2026 Matt Hargett. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +# Cache initializer for the Apple App Store embedding profile. The product +# supplies custom host interfaces as statically linked native symbols and uses +# WAMR's Preview 2 implementation for standard wasi:0.2 imports. Preview 1 and +# every dynamically loaded/guest-threading facility stay off. + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "" FORCE) +set(CMAKE_C_FLAGS_RELEASE + "-O3 -DNDEBUG -Werror -flto=full" + CACHE STRING "" FORCE) + +set(WAMR_BUILD_INTERP 1 CACHE BOOL "" FORCE) +set(WAMR_BUILD_FAST_INTERP 1 CACHE BOOL "" FORCE) +set(WAMR_BUILD_DEBUG_INTERP 0 CACHE BOOL "" FORCE) +set(WAMR_BUILD_MINI_LOADER 0 CACHE BOOL "" FORCE) + +set(WAMR_BUILD_AOT 0 CACHE BOOL "" FORCE) +set(WAMR_BUILD_JIT 0 CACHE BOOL "" FORCE) +set(WAMR_BUILD_FAST_JIT 0 CACHE BOOL "" FORCE) +set(WAMR_BUILD_WAMR_COMPILER 0 CACHE BOOL "" FORCE) + +set(WAMR_BUILD_COMPONENT_MODEL 1 CACHE BOOL "" FORCE) +set(WAMR_BUILD_MULTI_MEMORY 0 CACHE BOOL "" FORCE) +set(WAMR_BUILD_LIBC_WASI 0 CACHE BOOL "" FORCE) +set(WAMR_BUILD_LIBC_WASI_P2 1 CACHE BOOL "" FORCE) +set(WAMR_BUILD_LIBC_UVWASI 0 CACHE BOOL "" FORCE) +set(WAMR_BUILD_LIBC_BUILTIN 0 CACHE BOOL "" FORCE) + +# Native host threads may own independent component instances. Guest shared +# memory, guest pthreads, wasi-threads, and dynamic core-module linking are not +# part of this statically composed wasm32-wasip2 profile. +set(WAMR_BUILD_THREAD_MGR 1 CACHE BOOL "" FORCE) +set(WAMR_BUILD_SHARED_MEMORY 0 CACHE BOOL "" FORCE) +set(WAMR_BUILD_LIB_PTHREAD 0 CACHE BOOL "" FORCE) +set(WAMR_BUILD_LIB_PTHREAD_SEMAPHORE 0 CACHE BOOL "" FORCE) +set(WAMR_BUILD_LIB_WASI_THREADS 0 CACHE BOOL "" FORCE) +set(WAMR_BUILD_MULTI_MODULE 0 CACHE BOOL "" FORCE) + +set(WAMR_BUILD_SIMD 1 CACHE BOOL "" FORCE) +set(WAMR_BUILD_RELAXED_SIMD 1 CACHE BOOL "" FORCE) +set(WAMR_BUILD_EXCE_HANDLING 1 CACHE BOOL "" FORCE) +set(WAMR_BUILD_REF_TYPES 1 CACHE BOOL "" FORCE) +set(WAMR_BUILD_CUSTOM_NAME_SECTION 1 CACHE BOOL "" FORCE) +set(WAMR_BUILD_DUMP_CALL_STACK 1 CACHE BOOL "" FORCE) +set(WAMR_DISABLE_HW_BOUND_CHECK 1 CACHE BOOL "" FORCE) +set(WAMR_DISABLE_WAKEUP_BLOCKING_OP 1 CACHE BOOL "" FORCE) diff --git a/tests/apple-app-store-interpreter/component_host_import_smoke.c b/tests/apple-app-store-interpreter/component_host_import_smoke.c new file mode 100644 index 0000000000..d73e393361 --- /dev/null +++ b/tests/apple-app-store-interpreter/component_host_import_smoke.c @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2026 Matt Hargett. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include +#include +#include + +#include "wasm_export.h" + +enum { + ERROR_BUFFER_SIZE = 256, + CUSTOM_DATA_COOKIE = 0xC0DEC0DE, + PROBE_OFFSET = 32, + PROBE_SIZE = 4, + WASM_PAGE_SIZE = 64 * 1024, +}; + +typedef enum ProbeMode { + PROBE_VALID, + PROBE_INVALID_RANGES, +} ProbeMode; + +typedef struct MemoryProbeState { + ProbeMode mode; + wasm_exec_env_t callback_exec_env; + bool valid_const; + bool valid_mutable; + bool pointers_match; + bool mutable_round_trip; + bool empty_at_end; + bool null_output_rejected; + bool bounds_rejected; + bool overflow_rejected; + bool failed_outputs_cleared; + bool callback_kind; +} MemoryProbeState; + +static bool saw_custom_data; +static int32_t exact_increment = 1; +static int32_t fallback_increment = 100; +static MemoryProbeState memory_probe; + +static void +bump_raw(wasm_exec_env_t exec_env, uint64_t *canonical_cells) +{ + const uint32_t *custom_data = + wasm_component_get_custom_data_from_exec_env(exec_env); + const int32_t *increment = wasm_runtime_get_function_attachment(exec_env); + uint8_t *mutable_data = NULL; + const uint8_t *const_data = NULL; + + saw_custom_data = custom_data && *custom_data == CUSTOM_DATA_COOKIE; + memory_probe.callback_exec_env = exec_env; + memory_probe.callback_kind = wasm_component_exec_env_is_callback(exec_env); + + if (memory_probe.mode == PROBE_INVALID_RANGES) { + uint8_t *mutable_sentinel = (uint8_t *)(uintptr_t)1; + const uint8_t *const_sentinel = (const uint8_t *)(uintptr_t)1; + + memory_probe.bounds_rejected = + !wasm_component_validate_memory_range(exec_env, 0, UINT32_MAX) + && !wasm_component_get_memory_range(exec_env, 0, UINT32_MAX, + &mutable_sentinel); + memory_probe.overflow_rejected = + !wasm_component_validate_memory_range(exec_env, UINT32_MAX, 2) + && !wasm_component_get_memory_range_const(exec_env, UINT32_MAX, 2, + &const_sentinel); + memory_probe.failed_outputs_cleared = + mutable_sentinel == NULL && const_sentinel == NULL; + return; + } + + memory_probe.valid_const = wasm_component_get_memory_range_const( + exec_env, PROBE_OFFSET, PROBE_SIZE, &const_data); + memory_probe.valid_mutable = wasm_component_get_memory_range( + exec_env, PROBE_OFFSET, PROBE_SIZE, &mutable_data); + memory_probe.pointers_match = + memory_probe.valid_const && memory_probe.valid_mutable + && const_data == mutable_data + && memcmp(const_data, "wamr", PROBE_SIZE) == 0; + if (memory_probe.pointers_match) { + mutable_data[0] = 'W'; + memory_probe.mutable_round_trip = const_data[0] == 'W'; + mutable_data[0] = 'w'; + } + memory_probe.empty_at_end = + wasm_component_validate_memory_range(exec_env, WASM_PAGE_SIZE, 0); + memory_probe.null_output_rejected = !wasm_component_get_memory_range( + exec_env, PROBE_OFFSET, PROBE_SIZE, NULL); + canonical_cells[0] = + (uint32_t)((int32_t)canonical_cells[0] + (increment ? *increment : 0)); +} + +static bool +rejects_non_callback_context(wasm_exec_env_t exec_env) +{ + uint8_t *mutable_sentinel = (uint8_t *)(uintptr_t)1; + const uint8_t *const_sentinel = (const uint8_t *)(uintptr_t)1; + + return !wasm_component_validate_memory_range(exec_env, 0, 0) + && !wasm_component_exec_env_is_callback(exec_env) + && !wasm_component_get_memory_range(exec_env, 0, 0, + &mutable_sentinel) + && !wasm_component_get_memory_range_const(exec_env, 0, 0, + &const_sentinel) + && mutable_sentinel == NULL && const_sentinel == NULL; +} + +static bool +rejects_standalone_core_context(char *error, uint32_t error_size) +{ + static uint8_t empty_core_module[] = { 0x00, 0x61, 0x73, 0x6d, + 0x01, 0x00, 0x00, 0x00 }; + wasm_module_t module = NULL; + wasm_module_inst_t instance = NULL; + wasm_exec_env_t exec_env = NULL; + bool rejected = false; + + module = wasm_runtime_load(empty_core_module, sizeof(empty_core_module), + error, error_size); + if (!module) + return false; + instance = wasm_runtime_instantiate(module, 8 * 1024, 0, error, error_size); + if (!instance) + goto unload; + exec_env = wasm_runtime_create_exec_env(instance, 8 * 1024); + if (!exec_env) + goto deinstantiate; + + rejected = rejects_non_callback_context(exec_env); + wasm_runtime_destroy_exec_env(exec_env); +deinstantiate: + wasm_runtime_deinstantiate(instance); +unload: + wasm_runtime_unload(module); + return rejected; +} + +static uint8_t * +read_file(const char *path, uint32_t *size_out) +{ + FILE *file = fopen(path, "rb"); + uint8_t *bytes = NULL; + long size = 0; + + if (!file) + return NULL; + if (fseek(file, 0, SEEK_END) != 0 || (size = ftell(file)) <= 0 + || size > UINT32_MAX || fseek(file, 0, SEEK_SET) != 0) { + fclose(file); + return NULL; + } + + bytes = malloc((size_t)size); + if (!bytes || fread(bytes, 1, (size_t)size, file) != (size_t)size) { + free(bytes); + fclose(file); + return NULL; + } + + fclose(file); + *size_out = (uint32_t)size; + return bytes; +} + +int +main(int argc, char **argv) +{ + static NativeSymbol exact_symbols[] = { + { "bump", (void *)bump_raw, "(i)i", &exact_increment }, + }; + static NativeSymbol fallback_symbols[] = { + { "bump", (void *)bump_raw, "(i)i", &fallback_increment }, + }; + RuntimeInitArgs init_args = { 0 }; + LoadArgs load_args = { 0 }; + struct InstantiationArgs2 *instantiation_args = NULL; + WASMComponent *component = NULL; + WASMComponentInstance *instance = NULL; + WASMComponentPreparedCall *prepared_call = NULL; + uint8_t *bytes = NULL; + uint32_t size = 0; + uint32_t custom_data = CUSTOM_DATA_COOKIE; + wasm_val_t argument = { .kind = WASM_I32, .of.i32 = 41 }; + wasm_val_t result = { 0 }; + char error[ERROR_BUFFER_SIZE] = { 0 }; + int exit_code = 1; + + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 2; + } + bytes = read_file(argv[1], &size); + if (!bytes) { + fprintf(stderr, "failed to read static-host component: %s\n", argv[1]); + return 1; + } + + init_args.mem_alloc_type = Alloc_With_System_Allocator; + if (!wasm_runtime_full_init(&init_args)) { + fprintf(stderr, "wasm_runtime_full_init failed\n"); + goto cleanup; + } + if (!wasm_runtime_register_natives_raw( + "test:project/static-host", fallback_symbols, + (uint32_t)(sizeof(fallback_symbols) / sizeof(fallback_symbols[0]))) + || !wasm_runtime_register_natives_raw( + "test:project/static-host@0.1.0", exact_symbols, + (uint32_t)(sizeof(exact_symbols) / sizeof(exact_symbols[0])))) { + fprintf(stderr, "static host registration failed\n"); + goto destroy_runtime; + } + + load_args.name = "static-host-smoke"; + load_args.is_component = true; + component = + wasm_component_load(bytes, size, &load_args, error, sizeof(error)); + if (!component) { + fprintf(stderr, "component load failed: %s\n", error); + goto destroy_runtime; + } + if (!wasm_runtime_instantiation_args_create(&instantiation_args)) { + fprintf(stderr, "component instantiation argument creation failed\n"); + goto unload_component; + } + wasm_runtime_instantiation_args_set_default_stack_size(instantiation_args, + 64 * 1024); + /* The fixture's one-page memory is deliberately exact so the empty-range + * check can exercise the wasm32 end address. It does not allocate. */ + wasm_runtime_instantiation_args_set_host_managed_heap_size( + instantiation_args, 0); + wasm_runtime_instantiation_args_set_custom_data(instantiation_args, + &custom_data); + instance = wasm_component_instantiate_ex2(component, instantiation_args, + error, sizeof(error)); + wasm_runtime_instantiation_args_destroy(instantiation_args); + instantiation_args = NULL; + if (!instance) { + fprintf(stderr, "component instantiation failed: %s\n", error); + goto unload_component; + } + + prepared_call = wasm_component_prepare_export_call(instance, "call", error, + sizeof(error)); + if (!prepared_call + || wasm_component_prepared_call_requires_post_return(NULL) + || wasm_component_prepared_call_requires_post_return(prepared_call) + || !wasm_component_call_prepared(prepared_call, 1, &result, 1, + &argument) + || !wasm_component_prepared_call_post_return(prepared_call)) { + const char *exception = wasm_component_runtime_get_exception(instance); + fprintf(stderr, "component host call failed: %s\n", + exception ? exception : error); + goto deinstantiate; + } + if (result.kind != WASM_I32 || result.of.i32 != 42 || !saw_custom_data) { + fprintf(stderr, + "component host call returned kind=%u value=%d context=%d\n", + result.kind, result.of.i32, saw_custom_data); + goto deinstantiate; + } + if (!memory_probe.callback_kind || !memory_probe.valid_const + || !memory_probe.valid_mutable || !memory_probe.pointers_match + || !memory_probe.mutable_round_trip || !memory_probe.empty_at_end + || !memory_probe.null_output_rejected) { + fprintf(stderr, + "valid component callback memory probe failed: " + "const=%d mutable=%d match=%d round-trip=%d end=%d null=%d\n", + memory_probe.valid_const, memory_probe.valid_mutable, + memory_probe.pointers_match, memory_probe.mutable_round_trip, + memory_probe.empty_at_end, memory_probe.null_output_rejected); + goto deinstantiate; + } + if (!rejects_non_callback_context(NULL) + || !rejects_non_callback_context(memory_probe.callback_exec_env) + || wasm_runtime_get_function_attachment(memory_probe.callback_exec_env) + != NULL + || !rejects_standalone_core_context(error, sizeof(error))) { + fprintf(stderr, + "component memory API accepted a non-callback context: %s\n", + error); + goto deinstantiate; + } + + memset(&memory_probe, 0, sizeof(memory_probe)); + memory_probe.mode = PROBE_INVALID_RANGES; + if (wasm_component_call_prepared(prepared_call, 1, &result, 1, &argument) + || !memory_probe.bounds_rejected || !memory_probe.overflow_rejected + || !memory_probe.failed_outputs_cleared) { + fprintf(stderr, "invalid component callback memory probe failed\n"); + goto deinstantiate; + } + + puts("component exact static-host import and memory smoke passed"); + exit_code = 0; + +deinstantiate: + wasm_component_destroy_prepared_call(prepared_call); + wasm_component_deinstantiate(instance); +unload_component: + if (instantiation_args) + wasm_runtime_instantiation_args_destroy(instantiation_args); + wasm_component_unload(component); +destroy_runtime: + wasm_runtime_destroy(); +cleanup: + free(bytes); + return exit_code; +} diff --git a/tests/apple-app-store-interpreter/component_smoke.c b/tests/apple-app-store-interpreter/component_smoke.c new file mode 100644 index 0000000000..e1001533d0 --- /dev/null +++ b/tests/apple-app-store-interpreter/component_smoke.c @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2026 Matt Hargett. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "wasm_export.h" + +enum { + ERROR_BUFFER_SIZE = 256, + CALLS_PER_CYCLE = 8, + WORKER_START_STOP_CYCLES = 4, +}; + +typedef struct LoadedComponent { + uint8_t *bytes; + WASMComponent *component; + WASMComponentInstance *instance; +} LoadedComponent; + +typedef struct WorkerCall { + const uint8_t *bytes; + uint32_t size; + atomic_bool ready; + atomic_bool start; + bool initialized; + bool succeeded; + char error[ERROR_BUFFER_SIZE]; +} WorkerCall; + +static uint8_t * +read_file(const char *path, uint32_t *size_out) +{ + FILE *file = fopen(path, "rb"); + uint8_t *bytes = NULL; + long size = 0; + + if (!file) + return NULL; + if (fseek(file, 0, SEEK_END) != 0 || (size = ftell(file)) <= 0 + || size > UINT32_MAX || fseek(file, 0, SEEK_SET) != 0) { + fclose(file); + return NULL; + } + + bytes = malloc((size_t)size); + if (!bytes || fread(bytes, 1, (size_t)size, file) != (size_t)size) { + free(bytes); + fclose(file); + return NULL; + } + + fclose(file); + *size_out = (uint32_t)size; + return bytes; +} + +static bool +load_component(const uint8_t *source_bytes, uint32_t size, const char *name, + LoadedComponent *loaded, char *error) +{ + LoadArgs load_args = { 0 }; + struct InstantiationArgs2 *instantiation_args = NULL; + + memset(loaded, 0, sizeof(*loaded)); + loaded->bytes = malloc(size); + if (!loaded->bytes) { + snprintf(error, ERROR_BUFFER_SIZE, "failed to copy %s bytes", name); + return false; + } + memcpy(loaded->bytes, source_bytes, size); + + load_args.name = (char *)name; + load_args.wasm_binary_freeable = false; + load_args.clone_wasm_binary = false; + load_args.no_resolve = false; + load_args.is_component = true; + loaded->component = wasm_component_load(loaded->bytes, size, &load_args, + error, ERROR_BUFFER_SIZE); + if (!loaded->component) { + goto fail; + } + if (!wasm_runtime_instantiation_args_create(&instantiation_args)) { + snprintf(error, ERROR_BUFFER_SIZE, + "failed to create %s instantiation arguments", name); + goto fail; + } + wasm_runtime_instantiation_args_set_default_stack_size(instantiation_args, + 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size( + instantiation_args, 64 * 1024); + + loaded->instance = wasm_component_instantiate_ex2( + loaded->component, instantiation_args, error, ERROR_BUFFER_SIZE); + wasm_runtime_instantiation_args_destroy(instantiation_args); + instantiation_args = NULL; + if (!loaded->instance) { + goto fail; + } + return true; + +fail: + if (instantiation_args) + wasm_runtime_instantiation_args_destroy(instantiation_args); + if (loaded->component) + wasm_component_unload(loaded->component); + free(loaded->bytes); + memset(loaded, 0, sizeof(*loaded)); + return false; +} + +static void +unload_component(LoadedComponent *loaded) +{ + if (loaded->instance) + wasm_component_deinstantiate(loaded->instance); + if (loaded->component) + wasm_component_unload(loaded->component); + free(loaded->bytes); + memset(loaded, 0, sizeof(*loaded)); +} + +static bool +call_add(WASMComponentPreparedCall *prepared_call, + WASMComponentInstance *instance, char *error) +{ + wasm_val_t arguments[2] = { + { .kind = WASM_I32, .of.i32 = 20 }, + { .kind = WASM_I32, .of.i32 = 22 }, + }; + wasm_val_t result = { 0 }; + + if (!wasm_component_call_prepared(prepared_call, 1, &result, 2, + arguments)) { + const char *exception = wasm_component_runtime_get_exception(instance); + snprintf(error, ERROR_BUFFER_SIZE, "component add failed: %s", + exception ? exception : "no exception"); + return false; + } + if (!wasm_component_prepared_call_post_return(prepared_call)) { + const char *exception = wasm_component_runtime_get_exception(instance); + snprintf(error, ERROR_BUFFER_SIZE, "component post-return failed: %s", + exception ? exception : "no exception"); + return false; + } + if (result.kind != WASM_I32 || result.of.i32 != 42) { + snprintf(error, ERROR_BUFFER_SIZE, + "component add returned kind=%u result=%d", result.kind, + result.of.i32); + return false; + } + return true; +} + +static void * +run_worker(void *argument) +{ + WorkerCall *worker = argument; + LoadedComponent loaded = { 0 }; + WASMComponentPreparedCall *prepared_call = NULL; + uint32_t call_index = 0; + + worker->initialized = wasm_runtime_init_thread_env(); + if (!worker->initialized) { + snprintf(worker->error, sizeof(worker->error), + "worker thread environment initialization failed"); + atomic_store_explicit(&worker->ready, true, memory_order_release); + return NULL; + } + + if (!load_component(worker->bytes, worker->size, "audio-worker", &loaded, + worker->error)) { + atomic_store_explicit(&worker->ready, true, memory_order_release); + wasm_runtime_destroy_thread_env(); + return NULL; + } + prepared_call = wasm_component_prepare_export_call_qualified( + loaded.instance, "test:project/my-interface@0.1.0", "add", + worker->error, sizeof(worker->error)); + if (!prepared_call) { + unload_component(&loaded); + atomic_store_explicit(&worker->ready, true, memory_order_release); + wasm_runtime_destroy_thread_env(); + return NULL; + } + + atomic_store_explicit(&worker->ready, true, memory_order_release); + while (!atomic_load_explicit(&worker->start, memory_order_acquire)) + sched_yield(); + + worker->succeeded = true; + for (call_index = 0; call_index < CALLS_PER_CYCLE; ++call_index) { + if (!call_add(prepared_call, loaded.instance, worker->error)) { + worker->succeeded = false; + break; + } + } + + wasm_component_destroy_prepared_call(prepared_call); + unload_component(&loaded); + wasm_runtime_destroy_thread_env(); + return NULL; +} + +int +main(int argc, char **argv) +{ + RuntimeInitArgs init_args; + LoadedComponent main_component = { 0 }; + WASMComponentPreparedCall *main_call = NULL; + uint8_t *bytes = NULL; + uint32_t size = 0; + uint32_t cycle = 0; + int exit_code = 1; + char error[ERROR_BUFFER_SIZE] = { 0 }; + + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 2; + } + bytes = read_file(argv[1], &size); + if (!bytes) { + fprintf(stderr, "failed to read component fixture: %s\n", argv[1]); + return 1; + } + + memset(&init_args, 0, sizeof(init_args)); + init_args.mem_alloc_type = Alloc_With_System_Allocator; + if (!wasm_runtime_full_init(&init_args)) { + fprintf(stderr, "wasm_runtime_full_init failed\n"); + goto cleanup; + } + if (!load_component(bytes, size, "main", &main_component, error)) { + fprintf(stderr, "main component load failed: %s\n", error); + goto destroy_runtime; + } + main_call = wasm_component_prepare_export_call_qualified( + main_component.instance, "test:project/my-interface@0.1.0", "add", + error, sizeof(error)); + if (!main_call) { + fprintf(stderr, "main component prepare failed: %s\n", error); + goto unload_main; + } + + for (cycle = 0; cycle < WORKER_START_STOP_CYCLES; ++cycle) { + WorkerCall worker = { + .bytes = bytes, + .size = size, + .ready = ATOMIC_VAR_INIT(false), + .start = ATOMIC_VAR_INIT(false), + .initialized = false, + .succeeded = false, + .error = { 0 }, + }; + pthread_t thread; + uint32_t call_index = 0; + + if (pthread_create(&thread, NULL, run_worker, &worker) != 0) { + fprintf(stderr, "worker thread creation failed\n"); + goto unload_main; + } + while (!atomic_load_explicit(&worker.ready, memory_order_acquire)) + sched_yield(); + if (!worker.initialized) { + pthread_join(thread, NULL); + fprintf(stderr, "%s\n", worker.error); + goto unload_main; + } + + atomic_store_explicit(&worker.start, true, memory_order_release); + for (call_index = 0; call_index < CALLS_PER_CYCLE; ++call_index) { + if (!call_add(main_call, main_component.instance, error)) { + pthread_join(thread, NULL); + fprintf(stderr, "%s\n", error); + goto unload_main; + } + } + pthread_join(thread, NULL); + if (!worker.succeeded) { + fprintf(stderr, "worker component failed: %s\n", worker.error); + goto unload_main; + } + } + + puts("component main/worker start-stop smoke passed"); + exit_code = 0; + +unload_main: + wasm_component_destroy_prepared_call(main_call); + unload_component(&main_component); +destroy_runtime: + wasm_runtime_destroy(); +cleanup: + free(bytes); + return exit_code; +} diff --git a/tests/apple-app-store-interpreter/component_termination_smoke.c b/tests/apple-app-store-interpreter/component_termination_smoke.c new file mode 100644 index 0000000000..1302f969c5 --- /dev/null +++ b/tests/apple-app-store-interpreter/component_termination_smoke.c @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2026 Matt Hargett. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "wasm_export.h" + +enum { + ERROR_BUFFER_SIZE = 256, + THREAD_START_TIMEOUT_MILLISECONDS = 1000, + TERMINATION_TIMEOUT_MILLISECONDS = 2000, +}; + +/* + * Generated from spin_component.wat with `wasm-tools parse`. Embedding the + * tiny fixture keeps the Apple smoke independent of a component toolchain. + */ +static const uint8_t spin_component_wasm[] = { + 0x00, 0x61, 0x73, 0x6d, 0x0d, 0x00, 0x01, 0x00, 0x01, 0x43, 0x00, 0x61, + 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, + 0x03, 0x02, 0x01, 0x00, 0x07, 0x08, 0x01, 0x04, 0x73, 0x70, 0x69, 0x6e, + 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x03, 0x40, 0x0c, 0x00, 0x0b, + 0x0b, 0x00, 0x1a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x07, 0x06, 0x6d, + 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x03, 0x0a, 0x01, 0x00, 0x01, 0x00, 0x05, + 0x61, 0x67, 0x61, 0x69, 0x6e, 0x02, 0x04, 0x01, 0x00, 0x00, 0x00, 0x06, + 0x0a, 0x01, 0x00, 0x00, 0x01, 0x00, 0x04, 0x73, 0x70, 0x69, 0x6e, 0x07, + 0x05, 0x01, 0x40, 0x00, 0x01, 0x00, 0x08, 0x06, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0b, 0x0a, 0x01, 0x00, 0x04, 0x73, 0x70, 0x69, 0x6e, 0x01, + 0x00, 0x00, 0x00, 0x54, 0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, + 0x6e, 0x74, 0x2d, 0x6e, 0x61, 0x6d, 0x65, 0x01, 0x09, 0x00, 0x00, 0x01, + 0x00, 0x04, 0x73, 0x70, 0x69, 0x6e, 0x01, 0x0b, 0x00, 0x11, 0x01, 0x00, + 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x01, 0x0d, 0x00, 0x12, 0x01, + 0x00, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x01, 0x0d, + 0x01, 0x01, 0x00, 0x09, 0x73, 0x70, 0x69, 0x6e, 0x2d, 0x66, 0x75, 0x6e, + 0x63, 0x01, 0x0d, 0x03, 0x01, 0x00, 0x09, 0x73, 0x70, 0x69, 0x6e, 0x2d, + 0x74, 0x79, 0x70, 0x65, +}; + +_Static_assert(sizeof(spin_component_wasm) == 208, + "spin component fixture changed size"); + +typedef struct LoadedComponent { + uint8_t *bytes; + WASMComponent *component; + WASMComponentInstance *instance; +} LoadedComponent; + +typedef struct TerminationCall { + _Atomic(WASMComponentInstance *) instance; + atomic_bool ready; + atomic_bool start; + atomic_bool call_started; + atomic_bool done; + bool initialized; + bool prepared; + bool call_succeeded; + char error[ERROR_BUFFER_SIZE]; + char exception[ERROR_BUFFER_SIZE]; +} TerminationCall; + +static uint64_t +monotonic_milliseconds(void) +{ + struct timespec now; + + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) + return 0; + return (uint64_t)now.tv_sec * 1000 + (uint64_t)now.tv_nsec / 1000000; +} + +static bool +wait_for_flag(atomic_bool *flag, uint32_t timeout_milliseconds) +{ + const uint64_t start = monotonic_milliseconds(); + const struct timespec pause = { + .tv_sec = 0, + .tv_nsec = 1000 * 1000, + }; + + while (!atomic_load_explicit(flag, memory_order_acquire)) { + const uint64_t now = monotonic_milliseconds(); + + if (!start || !now || now - start >= timeout_milliseconds) + return false; + nanosleep(&pause, NULL); + } + return true; +} + +static bool +load_component(LoadedComponent *loaded, char *error) +{ + LoadArgs load_args = { 0 }; + struct InstantiationArgs2 *instantiation_args = NULL; + + memset(loaded, 0, sizeof(*loaded)); + loaded->bytes = malloc(sizeof(spin_component_wasm)); + if (!loaded->bytes) { + snprintf(error, ERROR_BUFFER_SIZE, "failed to copy spin component"); + return false; + } + memcpy(loaded->bytes, spin_component_wasm, sizeof(spin_component_wasm)); + + load_args.name = "termination-spin"; + load_args.wasm_binary_freeable = false; + load_args.clone_wasm_binary = false; + load_args.no_resolve = false; + load_args.is_component = true; + loaded->component = + wasm_component_load(loaded->bytes, sizeof(spin_component_wasm), + &load_args, error, ERROR_BUFFER_SIZE); + if (!loaded->component) { + goto fail; + } + if (!wasm_runtime_instantiation_args_create(&instantiation_args)) { + snprintf(error, ERROR_BUFFER_SIZE, + "failed to create termination instantiation arguments"); + goto fail; + } + wasm_runtime_instantiation_args_set_default_stack_size(instantiation_args, + 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size( + instantiation_args, 64 * 1024); + + loaded->instance = wasm_component_instantiate_ex2( + loaded->component, instantiation_args, error, ERROR_BUFFER_SIZE); + wasm_runtime_instantiation_args_destroy(instantiation_args); + instantiation_args = NULL; + if (!loaded->instance) { + goto fail; + } + return true; + +fail: + if (instantiation_args) + wasm_runtime_instantiation_args_destroy(instantiation_args); + if (loaded->component) + wasm_component_unload(loaded->component); + free(loaded->bytes); + memset(loaded, 0, sizeof(*loaded)); + return false; +} + +static void +unload_component(LoadedComponent *loaded) +{ + if (loaded->instance) + wasm_component_deinstantiate(loaded->instance); + if (loaded->component) + wasm_component_unload(loaded->component); + free(loaded->bytes); + memset(loaded, 0, sizeof(*loaded)); +} + +static void * +run_termination_worker(void *argument) +{ + TerminationCall *call = argument; + LoadedComponent loaded = { 0 }; + WASMComponentPreparedCall *prepared_call = NULL; + const char *exception = NULL; + + call->initialized = wasm_runtime_init_thread_env(); + if (!call->initialized) { + snprintf(call->error, sizeof(call->error), + "worker thread environment initialization failed"); + atomic_store_explicit(&call->ready, true, memory_order_release); + atomic_store_explicit(&call->done, true, memory_order_release); + return NULL; + } + + if (!load_component(&loaded, call->error)) { + atomic_store_explicit(&call->ready, true, memory_order_release); + goto done; + } + atomic_store_explicit(&call->instance, loaded.instance, + memory_order_release); + prepared_call = wasm_component_prepare_export_call( + loaded.instance, "spin", call->error, sizeof(call->error)); + call->prepared = prepared_call != NULL; + atomic_store_explicit(&call->ready, true, memory_order_release); + if (!prepared_call) + goto done; + + while (!atomic_load_explicit(&call->start, memory_order_acquire)) + sched_yield(); + + atomic_store_explicit(&call->call_started, true, memory_order_release); + call->call_succeeded = + wasm_component_call_prepared(prepared_call, 0, NULL, 0, NULL); + exception = wasm_component_runtime_get_exception(loaded.instance); + if (exception) + snprintf(call->exception, sizeof(call->exception), "%s", exception); + + if (call->call_succeeded) + wasm_component_prepared_call_post_return(prepared_call); + wasm_component_destroy_prepared_call(prepared_call); + +done: + unload_component(&loaded); + wasm_runtime_destroy_thread_env(); + atomic_store_explicit(&call->done, true, memory_order_release); + return NULL; +} + +static void +fail_bounded_wait(WASMComponentInstance *instance, const char *message, + uint32_t timeout_milliseconds) +{ + fprintf(stderr, "%s within %u ms\n", message, timeout_milliseconds); + wasm_component_terminate(instance); + _Exit(1); +} + +int +main(void) +{ + const struct timespec settle_time = { + .tv_sec = 0, + .tv_nsec = 20 * 1000 * 1000, + }; + RuntimeInitArgs init_args; + TerminationCall call = { + .instance = ATOMIC_VAR_INIT(NULL), + .ready = ATOMIC_VAR_INIT(false), + .start = ATOMIC_VAR_INIT(false), + .call_started = ATOMIC_VAR_INIT(false), + .done = ATOMIC_VAR_INIT(false), + .initialized = false, + .prepared = false, + .call_succeeded = false, + .error = { 0 }, + .exception = { 0 }, + }; + pthread_t thread; + int exit_code = 1; + + memset(&init_args, 0, sizeof(init_args)); + init_args.mem_alloc_type = Alloc_With_System_Allocator; + if (!wasm_runtime_full_init(&init_args)) { + fprintf(stderr, "wasm_runtime_full_init failed\n"); + return 1; + } + if (pthread_create(&thread, NULL, run_termination_worker, &call) != 0) { + fprintf(stderr, "termination worker thread creation failed\n"); + goto destroy_runtime; + } + if (!wait_for_flag(&call.ready, THREAD_START_TIMEOUT_MILLISECONDS)) + fail_bounded_wait( + atomic_load_explicit(&call.instance, memory_order_acquire), + "termination worker did not become ready", + THREAD_START_TIMEOUT_MILLISECONDS); + if (!call.initialized || !call.prepared) { + if (!wait_for_flag(&call.done, TERMINATION_TIMEOUT_MILLISECONDS)) + fail_bounded_wait( + atomic_load_explicit(&call.instance, memory_order_acquire), + "failed termination worker did not stop", + TERMINATION_TIMEOUT_MILLISECONDS); + pthread_join(thread, NULL); + fprintf(stderr, "termination worker preparation failed: %s\n", + call.error[0] ? call.error : "no error"); + goto destroy_runtime; + } + + atomic_store_explicit(&call.start, true, memory_order_release); + if (!wait_for_flag(&call.call_started, THREAD_START_TIMEOUT_MILLISECONDS)) + fail_bounded_wait( + atomic_load_explicit(&call.instance, memory_order_acquire), + "termination worker did not start its call", + THREAD_START_TIMEOUT_MILLISECONDS); + + nanosleep(&settle_time, NULL); + wasm_component_terminate( + atomic_load_explicit(&call.instance, memory_order_acquire)); + if (!wait_for_flag(&call.done, TERMINATION_TIMEOUT_MILLISECONDS)) + fail_bounded_wait( + atomic_load_explicit(&call.instance, memory_order_acquire), + "component spin call did not terminate", + TERMINATION_TIMEOUT_MILLISECONDS); + if (pthread_join(thread, NULL) != 0) { + fprintf(stderr, "termination worker thread join failed\n"); + goto destroy_runtime; + } + + if (call.call_succeeded) { + fprintf(stderr, "component spin call unexpectedly succeeded\n"); + goto destroy_runtime; + } + if (!strstr(call.exception, "terminated by user")) { + fprintf(stderr, "component spin call exception: %s\n", + call.exception[0] ? call.exception : "no exception"); + goto destroy_runtime; + } + + puts("component cross-thread termination smoke passed"); + exit_code = 0; + +destroy_runtime: + wasm_runtime_destroy(); + return exit_code; +} diff --git a/tests/apple-app-store-interpreter/component_wasip2_command_smoke.c b/tests/apple-app-store-interpreter/component_wasip2_command_smoke.c new file mode 100644 index 0000000000..bf0aa8bcea --- /dev/null +++ b/tests/apple-app-store-interpreter/component_wasip2_command_smoke.c @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2026 Matt Hargett. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include +#include +#include + +#include "wasm_export.h" + +enum { + ERROR_BUFFER_SIZE = 512, + OUTPUT_BUFFER_SIZE = 256, + CUSTOM_DATA_COOKIE = 0xC0DEC0DE, + STDOUT_REPRESENTATION = 101, + STDERR_REPRESENTATION = 102, +}; + +typedef struct CommandHostState { + uint32_t cookie; + uint32_t output_stream_drops; + uint32_t error_drops; + uint32_t calls_with_context; + uint32_t exit_status; + char output[OUTPUT_BUFFER_SIZE]; + size_t output_size; +} CommandHostState; + +static CommandHostState * +host_state(wasm_exec_env_t exec_env) +{ + CommandHostState *state = + wasm_component_get_custom_data_from_exec_env(exec_env); + if (state && state->cookie == CUSTOM_DATA_COOKIE) + state->calls_with_context++; + return state; +} + +static void * +guest_range(wasm_exec_env_t exec_env, uint32_t offset, uint32_t size) +{ + wasm_module_inst_t module_inst = wasm_runtime_get_module_inst(exec_env); + + if (!module_inst + || !wasm_runtime_validate_app_addr(module_inst, offset, size)) + return NULL; + return wasm_runtime_addr_app_to_native(module_inst, offset); +} + +static void +host_error_to_debug_string(wasm_exec_env_t exec_env, uint64_t *cells) +{ + uint32_t *result = guest_range(exec_env, (uint32_t)cells[1], 8); + + (void)host_state(exec_env); + if (result) { + result[0] = 0; + result[1] = 0; + } +} + +static void +host_blocking_write_and_flush(wasm_exec_env_t exec_env, uint64_t *cells) +{ + CommandHostState *state = host_state(exec_env); + uint32_t stream_representation = 0; + uint32_t contents_offset = (uint32_t)cells[1]; + uint32_t contents_size = (uint32_t)cells[2]; + const char *contents = + guest_range(exec_env, contents_offset, contents_size); + uint32_t *result = guest_range(exec_env, (uint32_t)cells[3], 8); + + if (state && contents + && !wasm_component_host_resource_rep( + exec_env, "wasi:io/streams@0.2.5", "output-stream", + (uint32_t)cells[0], &stream_representation) + && wasm_component_host_resource_rep(exec_env, "wasi:io/streams@0.2.6", + "output-stream", (uint32_t)cells[0], + &stream_representation) + && stream_representation == STDOUT_REPRESENTATION) { + size_t available = sizeof(state->output) - state->output_size - 1; + size_t copy_size = + contents_size < available ? contents_size : available; + + memcpy(state->output + state->output_size, contents, copy_size); + state->output_size += copy_size; + state->output[state->output_size] = '\0'; + } + if (result) { + result[0] = 0; + result[1] = 0; + } +} + +static void +host_get_environment(wasm_exec_env_t exec_env, uint64_t *cells) +{ + uint32_t *result = guest_range(exec_env, (uint32_t)cells[0], 8); + + (void)host_state(exec_env); + if (result) { + result[0] = 0; + result[1] = 0; + } +} + +static void +host_exit(wasm_exec_env_t exec_env, uint64_t *cells) +{ + CommandHostState *state = host_state(exec_env); + + if (state) + state->exit_status = (uint32_t)cells[0]; +} + +static void +host_get_stdout(wasm_exec_env_t exec_env, uint64_t *cells) +{ + uint32_t handle = 0; + + (void)host_state(exec_env); + if (!wasm_component_host_resource_new(exec_env, "wasi:io/streams@0.2.5", + "output-stream", + STDOUT_REPRESENTATION, &handle) + && wasm_component_host_resource_new(exec_env, "wasi:io/streams@0.2.6", + "output-stream", + STDOUT_REPRESENTATION, &handle)) { + cells[0] = handle; + } +} + +static void +host_get_stderr(wasm_exec_env_t exec_env, uint64_t *cells) +{ + uint32_t handle = 0; + + (void)host_state(exec_env); + if (wasm_component_host_resource_new(exec_env, "wasi:io/streams@0.2.6", + "output-stream", STDERR_REPRESENTATION, + &handle)) { + cells[0] = handle; + } +} + +static bool +drop_output_stream(void *attachment, uint32_t representation) +{ + CommandHostState *state = attachment; + + if (!state || state->cookie != CUSTOM_DATA_COOKIE + || (representation != STDOUT_REPRESENTATION + && representation != STDERR_REPRESENTATION)) + return false; + state->output_stream_drops++; + return true; +} + +static bool +drop_error(void *attachment, uint32_t representation) +{ + CommandHostState *state = attachment; + + if (!state || state->cookie != CUSTOM_DATA_COOKIE) + return false; + (void)representation; + state->error_drops++; + return true; +} + +static uint8_t * +read_file(const char *path, uint32_t *size_out) +{ + FILE *file = fopen(path, "rb"); + uint8_t *bytes = NULL; + long size = 0; + + if (!file) + return NULL; + if (fseek(file, 0, SEEK_END) != 0 || (size = ftell(file)) <= 0 + || size > UINT32_MAX || fseek(file, 0, SEEK_SET) != 0) { + fclose(file); + return NULL; + } + bytes = malloc((size_t)size); + if (!bytes || fread(bytes, 1, (size_t)size, file) != (size_t)size) { + free(bytes); + fclose(file); + return NULL; + } + fclose(file); + *size_out = (uint32_t)size; + return bytes; +} + +int +main(int argc, char **argv) +{ + static NativeSymbol error_symbols[] = { + { "[method]error.to-debug-string", (void *)host_error_to_debug_string, + NULL, NULL }, + }; + static NativeSymbol stream_symbols[] = { + { "[method]output-stream.blocking-write-and-flush", + (void *)host_blocking_write_and_flush, NULL, NULL }, + }; + static NativeSymbol environment_symbols[] = { + { "get-environment", (void *)host_get_environment, NULL, NULL }, + }; + static NativeSymbol exit_symbols[] = { + { "exit", (void *)host_exit, NULL, NULL }, + }; + static NativeSymbol stdout_symbols[] = { + { "get-stdout", (void *)host_get_stdout, NULL, NULL }, + }; + static NativeSymbol stderr_symbols[] = { + { "get-stderr", (void *)host_get_stderr, NULL, NULL }, + }; + RuntimeInitArgs init_args = { 0 }; + LoadArgs load_args = { 0 }; + struct InstantiationArgs2 *instantiation_args = NULL; + WASMComponent *component = NULL; + WASMComponentInstance *instance = NULL; + WASMComponentPreparedCall *prepared_call = NULL; + CommandHostState state = { + .cookie = CUSTOM_DATA_COOKIE, + .exit_status = UINT32_MAX, + }; + uint8_t *bytes = NULL; + uint32_t size = 0; + wasm_val_t result = { 0 }; + char error[ERROR_BUFFER_SIZE] = { 0 }; + int exit_code = 1; + + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 2; + } + bytes = read_file(argv[1], &size); + if (!bytes) { + fprintf(stderr, "failed to read wasm32-wasip2 command: %s\n", argv[1]); + return 1; + } + + init_args.mem_alloc_type = Alloc_With_System_Allocator; + if (!wasm_runtime_full_init(&init_args)) { + fprintf(stderr, "wasm_runtime_full_init failed\n"); + goto cleanup; + } +#define REGISTER_RAW(interface_name, symbols) \ + wasm_runtime_register_natives_raw( \ + interface_name, symbols, \ + (uint32_t)(sizeof(symbols) / sizeof((symbols)[0]))) + if (!REGISTER_RAW("wasi:io/error@0.2.6", error_symbols) + || !REGISTER_RAW("wasi:io/streams@0.2.6", stream_symbols) + || !REGISTER_RAW("wasi:cli/environment@0.2.6", environment_symbols) + || !REGISTER_RAW("wasi:cli/exit@0.2.6", exit_symbols) + || !REGISTER_RAW("wasi:cli/stdout@0.2.6", stdout_symbols) + || !REGISTER_RAW("wasi:cli/stderr@0.2.6", stderr_symbols)) { + fprintf(stderr, "wasm32-wasip2 host registration failed\n"); + goto destroy_runtime; + } +#undef REGISTER_RAW + + load_args.name = "rust-wasm32-wasip2-command"; + load_args.is_component = true; + component = + wasm_component_load(bytes, size, &load_args, error, sizeof(error)); + if (!component) { + fprintf(stderr, "wasm32-wasip2 component load failed: %s\n", error); + goto destroy_runtime; + } + if (!wasm_component_register_host_resource_drop_callback( + component, "wasi:io/error@0.2.6", "error", drop_error) + || !wasm_component_register_host_resource_drop_callback( + component, "wasi:io/streams@0.2.6", "output-stream", + drop_output_stream)) { + fprintf(stderr, "wasm32-wasip2 resource registration failed\n"); + goto unload_component; + } + if (!wasm_runtime_instantiation_args_create(&instantiation_args)) { + fprintf(stderr, "component instantiation argument creation failed\n"); + goto unload_component; + } + wasm_runtime_instantiation_args_set_default_stack_size(instantiation_args, + 256 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size( + instantiation_args, 256 * 1024); + wasm_runtime_instantiation_args_set_custom_data(instantiation_args, &state); + instance = wasm_component_instantiate_ex2(component, instantiation_args, + error, sizeof(error)); + wasm_runtime_instantiation_args_destroy(instantiation_args); + instantiation_args = NULL; + if (!instance) { + fprintf(stderr, "wasm32-wasip2 component instantiation failed: %s\n", + error); + goto unload_component; + } + + prepared_call = wasm_component_prepare_export_call_qualified( + instance, "wasi:cli/run@0.2.6", "run", error, sizeof(error)); + if (!prepared_call + || !wasm_component_call_prepared(prepared_call, 1, &result, 0, NULL) + || !wasm_component_prepared_call_post_return(prepared_call)) { + const char *exception = wasm_component_runtime_get_exception(instance); + fprintf(stderr, "wasi:cli/run failed: %s\n", + exception ? exception : error); + goto deinstantiate; + } + if (result.kind != WASM_I32 || result.of.i32 != 0 + || strcmp(state.output, "wamr wasm32-wasip2 smoke\n") != 0 + || state.calls_with_context == 0 || state.exit_status != UINT32_MAX) { + fprintf(stderr, + "wasi:cli/run returned kind=%u value=%d output=%s calls=%u " + "exit=%u\n", + result.kind, result.of.i32, state.output, + state.calls_with_context, state.exit_status); + goto deinstantiate; + } + + puts("Rust wasm32-wasip2 command smoke passed"); + exit_code = 0; + +deinstantiate: + wasm_component_destroy_prepared_call(prepared_call); + wasm_component_deinstantiate(instance); + if (exit_code == 0 && state.output_stream_drops == 0) { + fprintf(stderr, "expected an output-stream drop, observed none\n"); + exit_code = 1; + } +unload_component: + if (instantiation_args) + wasm_runtime_instantiation_args_destroy(instantiation_args); + wasm_component_unload(component); +destroy_runtime: + wasm_runtime_destroy(); +cleanup: + free(bytes); + return exit_code; +} diff --git a/tests/apple-app-store-interpreter/component_wasip2_defaults_smoke.c b/tests/apple-app-store-interpreter/component_wasip2_defaults_smoke.c new file mode 100644 index 0000000000..0588dd68f1 --- /dev/null +++ b/tests/apple-app-store-interpreter/component_wasip2_defaults_smoke.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2026 Matt Hargett. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include + +#include "wasm_export.h" + +enum { ERROR_BUFFER_SIZE = 512 }; + +static uint8_t * +read_file(const char *path, uint32_t *size_out) +{ + FILE *file = fopen(path, "rb"); + uint8_t *bytes = NULL; + long size = 0; + + if (!file) + return NULL; + if (fseek(file, 0, SEEK_END) != 0 || (size = ftell(file)) <= 0 + || size > UINT32_MAX || fseek(file, 0, SEEK_SET) != 0) { + fclose(file); + return NULL; + } + bytes = malloc((size_t)size); + if (!bytes || fread(bytes, 1, (size_t)size, file) != (size_t)size) { + free(bytes); + fclose(file); + return NULL; + } + fclose(file); + *size_out = (uint32_t)size; + return bytes; +} + +int +main(int argc, char **argv) +{ + RuntimeInitArgs init_args = { 0 }; + LoadArgs load_args = { 0 }; + struct InstantiationArgs2 *instantiation_args = NULL; + WASMComponent *component = NULL; + WASMComponentInstance *instance = NULL; + WASMComponentPreparedCall *prepared_call = NULL; + wasm_val_t result = { 0 }; + uint8_t *bytes = NULL; + uint32_t size = 0; + char error[ERROR_BUFFER_SIZE] = { 0 }; + int exit_code = 1; + + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 2; + } + bytes = read_file(argv[1], &size); + if (!bytes) { + fprintf(stderr, "failed to read wasm32-wasip2 command: %s\n", argv[1]); + return 1; + } + + init_args.mem_alloc_type = Alloc_With_System_Allocator; + if (!wasm_runtime_full_init(&init_args)) { + fprintf(stderr, "wasm_runtime_full_init failed\n"); + goto cleanup; + } + + load_args.name = "rust-wasm32-wasip2-default-options"; + load_args.is_component = true; + component = + wasm_component_load(bytes, size, &load_args, error, sizeof(error)); + if (!component) { + fprintf(stderr, "wasm32-wasip2 component load failed: %s\n", error); + goto destroy_runtime; + } + if (!wasm_runtime_instantiation_args_create(&instantiation_args)) { + fprintf(stderr, "component instantiation argument creation failed\n"); + goto unload_component; + } + wasm_runtime_instantiation_args_set_default_stack_size(instantiation_args, + 256 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size( + instantiation_args, 256 * 1024); + instance = wasm_component_instantiate_ex2(component, instantiation_args, + error, sizeof(error)); + wasm_runtime_instantiation_args_destroy(instantiation_args); + instantiation_args = NULL; + if (!instance) { + fprintf(stderr, "wasm32-wasip2 component instantiation failed: %s\n", + error); + goto unload_component; + } + + prepared_call = wasm_component_prepare_export_call_qualified( + instance, "wasi:cli/run@0.2.6", "run", error, sizeof(error)); + if (!prepared_call + || !wasm_component_call_prepared(prepared_call, 1, &result, 0, NULL) + || !wasm_component_prepared_call_post_return(prepared_call) + || result.kind != WASM_I32 || result.of.i32 != 0) { + const char *exception = wasm_component_runtime_get_exception(instance); + fprintf(stderr, "default wasi:cli/run failed: %s\n", + exception ? exception : error); + goto deinstantiate; + } + + puts("Rust wasm32-wasip2 public default-options smoke passed"); + exit_code = 0; + +deinstantiate: + wasm_component_destroy_prepared_call(prepared_call); + wasm_component_deinstantiate(instance); +unload_component: + if (instantiation_args) + wasm_runtime_instantiation_args_destroy(instantiation_args); + wasm_component_unload(component); +destroy_runtime: + wasm_runtime_destroy(); +cleanup: + free(bytes); + return exit_code; +} diff --git a/tests/apple-app-store-interpreter/run_local.sh b/tests/apple-app-store-interpreter/run_local.sh new file mode 100755 index 0000000000..862751f887 --- /dev/null +++ b/tests/apple-app-store-interpreter/run_local.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# Copyright (C) 2026 Matt Hargett. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +set -euo pipefail + +repo_root=$(cd "$(dirname "$0")/../.." && pwd) +build_dir=${1:-"$repo_root/build/apple-wasip2-fast-interp-local"} +profile="$repo_root/tests/apple-app-store-interpreter/apple_wasip2_fast_interp.cmake" +library="$build_dir/libiwasm.a" +fixture="$repo_root/tests/unit/component-execution/wasm-apps/add.wasm" +host_fixture="$repo_root/tests/apple-app-store-interpreter/static_host_component.wasm" +wasip2_fixture="$repo_root/tests/apple-app-store-interpreter/wasip2_command.wasm" +architecture=$(uname -m) +bison_executable=${BISON_EXECUTABLE:-} + +if [[ -z "$bison_executable" ]]; then + for candidate in /opt/homebrew/opt/bison/bin/bison \ + /usr/local/opt/bison/bin/bison; do + if [[ -x "$candidate" ]]; then + bison_executable=$candidate + break + fi + done +fi +if [[ -z "$bison_executable" ]]; then + echo "component builds require Bison 3.0 or newer; install Homebrew bison" >&2 + exit 1 +fi + +case "$architecture" in + arm64) wamr_target=AARCH64 ;; + x86_64) wamr_target=X86_64 ;; + *) + echo "unsupported local macOS architecture: $architecture" >&2 + exit 1 + ;; +esac + +cmake \ + -C "$profile" \ + -S "$repo_root/product-mini/platforms/darwin" \ + -B "$build_dir" \ + -DCMAKE_BUILD_TYPE=Release \ + -DBISON_EXECUTABLE="$bison_executable" \ + -DCMAKE_OSX_SYSROOT=macosx \ + -DCMAKE_OSX_ARCHITECTURES="$architecture" \ + -DWAMR_BUILD_TARGET="$wamr_target" +cmake --build "$build_dir" --target vmlib --config Release --parallel 4 + +"$repo_root/tests/apple-app-store-interpreter/verify_archive.sh" \ + "$build_dir" "$library" "$architecture" + +common_flags=( + -std=c11 + -O3 + -DNDEBUG + -Wall + -Wextra + -Werror + -flto=full + -arch "$architecture" + -I "$repo_root/core/iwasm/include" +) +component_flags=( + -DWASM_ENABLE_COMPONENT_MODEL=1 + -DWASM_ENABLE_INTERP=1 + -DWASM_ENABLE_FAST_INTERP=1 + -DWASM_ENABLE_LIBC_WASI_P2=1 + -DWASM_ENABLE_THREAD_MGR=1 + -I "$repo_root/core/iwasm/common" + -I "$repo_root/core/iwasm/common/component-model" + -I "$repo_root/core/iwasm/interpreter" + -I "$repo_root/core/shared/mem-alloc" + -I "$repo_root/core/shared/utils" + -I "$repo_root/core/shared/platform/include" + -I "$repo_root/core/shared/platform/darwin" +) +link_flags=( + "$library" + -flto=full + "-Wl,-dead_strip" + -lpthread + -lm +) + +xcrun --sdk macosx clang "${common_flags[@]}" \ + "$repo_root/tests/apple-app-store-interpreter/smoke.c" \ + "${link_flags[@]}" -o "$build_dir/embed-smoke" + +xcrun --sdk macosx clang "${common_flags[@]}" \ + "${component_flags[@]}" \ + "$repo_root/tests/apple-app-store-interpreter/component_smoke.c" \ + "${link_flags[@]}" -o "$build_dir/component-smoke" +"$repo_root/tests/apple-app-store-interpreter/verify_link.sh" \ + "$build_dir/component-smoke" + +xcrun --sdk macosx clang "${common_flags[@]}" \ + "${component_flags[@]}" \ + "$repo_root/tests/apple-app-store-interpreter/component_termination_smoke.c" \ + "${link_flags[@]}" -o "$build_dir/component-termination-smoke" + +xcrun --sdk macosx clang "${common_flags[@]}" \ + "${component_flags[@]}" \ + "$repo_root/tests/apple-app-store-interpreter/component_host_import_smoke.c" \ + "${link_flags[@]}" -o "$build_dir/component-host-import-smoke" + +xcrun --sdk macosx clang "${common_flags[@]}" \ + "${component_flags[@]}" \ + "$repo_root/tests/apple-app-store-interpreter/component_wasip2_command_smoke.c" \ + "${link_flags[@]}" -o "$build_dir/component-wasip2-command-smoke" + +xcrun --sdk macosx clang "${common_flags[@]}" \ + "${component_flags[@]}" \ + "$repo_root/tests/apple-app-store-interpreter/component_wasip2_defaults_smoke.c" \ + "${link_flags[@]}" -o "$build_dir/component-wasip2-defaults-smoke" + +xcrun --sdk macosx clang --analyze -Xanalyzer -analyzer-werror \ + "${common_flags[@]}" \ + "$repo_root/tests/apple-app-store-interpreter/smoke.c" \ + -o "$build_dir/embed-smoke.plist" +xcrun --sdk macosx clang --analyze -Xanalyzer -analyzer-werror \ + "${common_flags[@]}" "${component_flags[@]}" \ + "$repo_root/tests/apple-app-store-interpreter/component_smoke.c" \ + -o "$build_dir/component-smoke.plist" +xcrun --sdk macosx clang --analyze -Xanalyzer -analyzer-werror \ + "${common_flags[@]}" "${component_flags[@]}" \ + "$repo_root/tests/apple-app-store-interpreter/component_termination_smoke.c" \ + -o "$build_dir/component-termination-smoke.plist" +xcrun --sdk macosx clang --analyze -Xanalyzer -analyzer-werror \ + "${common_flags[@]}" "${component_flags[@]}" \ + "$repo_root/tests/apple-app-store-interpreter/component_host_import_smoke.c" \ + -o "$build_dir/component-host-import-smoke.plist" +xcrun --sdk macosx clang --analyze -Xanalyzer -analyzer-werror \ + "${common_flags[@]}" "${component_flags[@]}" \ + "$repo_root/tests/apple-app-store-interpreter/component_wasip2_command_smoke.c" \ + -o "$build_dir/component-wasip2-command-smoke.plist" +xcrun --sdk macosx clang --analyze -Xanalyzer -analyzer-werror \ + "${common_flags[@]}" "${component_flags[@]}" \ + "$repo_root/tests/apple-app-store-interpreter/component_wasip2_defaults_smoke.c" \ + -o "$build_dir/component-wasip2-defaults-smoke.plist" + +codesign --force --sign - --options runtime "$build_dir/embed-smoke" +codesign --force --sign - --options runtime "$build_dir/component-smoke" +codesign --force --sign - --options runtime \ + "$build_dir/component-termination-smoke" +codesign --force --sign - --options runtime \ + "$build_dir/component-host-import-smoke" +codesign --force --sign - --options runtime \ + "$build_dir/component-wasip2-command-smoke" +codesign --force --sign - --options runtime \ + "$build_dir/component-wasip2-defaults-smoke" +"$build_dir/embed-smoke" +"$build_dir/component-smoke" "$fixture" +"$build_dir/component-termination-smoke" +"$build_dir/component-host-import-smoke" "$host_fixture" +"$build_dir/component-wasip2-command-smoke" "$wasip2_fixture" +"$build_dir/component-wasip2-defaults-smoke" "$wasip2_fixture" diff --git a/tests/apple-app-store-interpreter/smoke.c b/tests/apple-app-store-interpreter/smoke.c new file mode 100644 index 0000000000..a4e0aa134a --- /dev/null +++ b/tests/apple-app-store-interpreter/smoke.c @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2026 Matt Hargett. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include +#include + +#include + +#include "wasm_export.h" + +/* + * (module + * (import "env" "bump" (func $bump (param i32) (result i32))) + * (func (export "run") (result i32) + * i32.const 41 + * call $bump) + * (func (export "spin") + * (loop $again + * br $again))) + */ +static uint8_t wasm_bytes[] = { + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0d, 0x03, + 0x60, 0x01, 0x7f, 0x01, 0x7f, 0x60, 0x00, 0x01, 0x7f, 0x60, 0x00, + 0x00, 0x02, 0x0c, 0x01, 0x03, 0x65, 0x6e, 0x76, 0x04, 0x62, 0x75, + 0x6d, 0x70, 0x00, 0x00, 0x03, 0x03, 0x02, 0x01, 0x02, 0x07, 0x0e, + 0x02, 0x03, 0x72, 0x75, 0x6e, 0x00, 0x01, 0x04, 0x73, 0x70, 0x69, + 0x6e, 0x00, 0x02, 0x0a, 0x10, 0x02, 0x06, 0x00, 0x41, 0x29, 0x10, + 0x00, 0x0b, 0x07, 0x00, 0x03, 0x40, 0x0c, 0x00, 0x0b, 0x0b, +}; + +static int32_t increment = 1; + +static int32_t +bump(wasm_exec_env_t exec_env, int32_t value) +{ + const int32_t *attachment = + (const int32_t *)wasm_runtime_get_function_attachment(exec_env); + return attachment ? value + *attachment : value; +} + +typedef struct SpinCall { + wasm_exec_env_t exec_env; + wasm_function_inst_t function; + bool thread_env_initialized; + bool call_succeeded; +} SpinCall; + +static void * +call_spin(void *arg) +{ + SpinCall *call = (SpinCall *)arg; + uint32_t argv[1] = { 0 }; + + call->thread_env_initialized = wasm_runtime_init_thread_env(); + if (!call->thread_env_initialized) { + call->call_succeeded = false; + return NULL; + } + call->call_succeeded = + wasm_runtime_call_wasm(call->exec_env, call->function, 0, argv); + wasm_runtime_destroy_thread_env(); + return NULL; +} + +int +main(void) +{ + RuntimeInitArgs init_args; + char error_buf[256] = { 0 }; + wasm_module_t module = NULL; + wasm_module_inst_t module_inst = NULL; + wasm_exec_env_t exec_env = NULL; + wasm_exec_env_t spin_exec_env = NULL; + wasm_function_inst_t run = NULL; + wasm_function_inst_t spin = NULL; + uint32_t argv[1] = { 0 }; + pthread_t spin_thread; + int exit_code = 1; + + static NativeSymbol native_symbols[] = { + { "bump", bump, "(i)i", &increment }, + }; + + memset(&init_args, 0, sizeof(init_args)); + init_args.mem_alloc_type = Alloc_With_System_Allocator; + init_args.native_module_name = "env"; + init_args.native_symbols = native_symbols; + init_args.n_native_symbols = + (uint32_t)(sizeof(native_symbols) / sizeof(native_symbols[0])); + + if (!wasm_runtime_full_init(&init_args)) { + fprintf(stderr, "wasm_runtime_full_init failed\n"); + return 1; + } + + module = wasm_runtime_load(wasm_bytes, (uint32_t)sizeof(wasm_bytes), + error_buf, (uint32_t)sizeof(error_buf)); + if (!module) { + fprintf(stderr, "wasm_runtime_load failed: %s\n", error_buf); + goto cleanup; + } + + module_inst = wasm_runtime_instantiate( + module, 64 * 1024, 64 * 1024, error_buf, (uint32_t)sizeof(error_buf)); + if (!module_inst) { + fprintf(stderr, "wasm_runtime_instantiate failed: %s\n", error_buf); + goto cleanup; + } + + exec_env = wasm_runtime_create_exec_env(module_inst, 64 * 1024); + if (!exec_env) { + fprintf(stderr, "wasm_runtime_create_exec_env failed\n"); + goto cleanup; + } + + run = wasm_runtime_lookup_function(module_inst, "run"); + if (!run) { + fprintf(stderr, "run export was not found\n"); + goto cleanup; + } + + if (!wasm_runtime_call_wasm(exec_env, run, 0, argv)) { + fprintf(stderr, "run failed: %s\n", + wasm_runtime_get_exception(module_inst)); + goto cleanup; + } + + if (argv[0] != 42) { + fprintf(stderr, "run returned %u, expected 42\n", argv[0]); + goto cleanup; + } + + spin = wasm_runtime_lookup_function(module_inst, "spin"); + if (!spin) { + fprintf(stderr, "spin export was not found\n"); + goto cleanup; + } + + spin_exec_env = wasm_runtime_create_exec_env(module_inst, 64 * 1024); + if (!spin_exec_env) { + fprintf(stderr, "spin execution environment creation failed\n"); + goto cleanup; + } + + SpinCall spin_call = { + .exec_env = spin_exec_env, + .function = spin, + .thread_env_initialized = false, + .call_succeeded = true, + }; + if (pthread_create(&spin_thread, NULL, call_spin, &spin_call) != 0) { + fprintf(stderr, "spin thread creation failed\n"); + goto cleanup; + } + const struct timespec settle_time = { + .tv_sec = 0, + .tv_nsec = 20 * 1000 * 1000, + }; + nanosleep(&settle_time, NULL); + wasm_runtime_terminate(module_inst); + pthread_join(spin_thread, NULL); + + const char *exception = wasm_runtime_get_exception(module_inst); + if (!spin_call.thread_env_initialized || spin_call.call_succeeded + || !exception || !strstr(exception, "terminated by user")) { + fprintf(stderr, "spin was not terminated cleanly: %s\n", + exception ? exception : "no exception"); + goto cleanup; + } + + exit_code = 0; + +cleanup: + if (spin_exec_env) + wasm_runtime_destroy_exec_env(spin_exec_env); + if (exec_env) + wasm_runtime_destroy_exec_env(exec_env); + if (module_inst) + wasm_runtime_deinstantiate(module_inst); + if (module) + wasm_runtime_unload(module); + wasm_runtime_destroy(); + return exit_code; +} diff --git a/tests/apple-app-store-interpreter/spin_component.wat b/tests/apple-app-store-interpreter/spin_component.wat new file mode 100644 index 0000000000..00f778b610 --- /dev/null +++ b/tests/apple-app-store-interpreter/spin_component.wat @@ -0,0 +1,13 @@ +;; Copyright (C) 2026 Matt Hargett. All rights reserved. +;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +(component + (core module $module + (func (export "spin") + (loop $again + br $again))) + (core instance $instance (instantiate $module)) + (alias core export $instance "spin" (core func $spin)) + (type $spin-type (func)) + (func $spin-func (type $spin-type) (canon lift (core func $spin))) + (export "spin" (func $spin-func))) diff --git a/tests/apple-app-store-interpreter/static_host_component.wasm b/tests/apple-app-store-interpreter/static_host_component.wasm new file mode 100644 index 0000000000..060bb8a522 Binary files /dev/null and b/tests/apple-app-store-interpreter/static_host_component.wasm differ diff --git a/tests/apple-app-store-interpreter/static_host_component.wat b/tests/apple-app-store-interpreter/static_host_component.wat new file mode 100644 index 0000000000..91d2ead623 --- /dev/null +++ b/tests/apple-app-store-interpreter/static_host_component.wat @@ -0,0 +1,41 @@ +;; Copyright (C) 2026 Matt Hargett. All rights reserved. +;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +(component + (type $bump-type (func (param "value" s32) (result s32))) + (type $host-type + (instance + (export "bump" (func (type $bump-type))))) + (import "test:project/static-host@0.1.0" + (instance $host (type $host-type))) + + (alias export $host "bump" (func $bump)) + + (core module $memory-provider + (memory (export "memory") 1)) + (core instance $memory-instance + (instantiate $memory-provider)) + (alias core export $memory-instance "memory" (core memory $memory)) + (core instance $memory-export + (export "memory" (memory $memory))) + + (core func $lowered-bump + (canon lower (func $bump) (memory $memory))) + (core instance $lowered-host + (export "bump" (func $lowered-bump))) + + (core module $guest + (import "env" "memory" (memory 1)) + (import "host" "bump" (func $bump (param i32) (result i32))) + (data (i32.const 32) "wamr") + (func (export "call") (param $value i32) (result i32) + local.get $value + call $bump)) + (core instance $guest-instance + (instantiate $guest + (with "env" (instance $memory-export)) + (with "host" (instance $lowered-host)))) + + (alias core export $guest-instance "call" (core func $call)) + (func $lifted-call (type $bump-type) (canon lift (core func $call))) + (export "call" (func $lifted-call))) diff --git a/tests/apple-app-store-interpreter/verify_archive.sh b/tests/apple-app-store-interpreter/verify_archive.sh new file mode 100755 index 0000000000..c641d0deb7 --- /dev/null +++ b/tests/apple-app-store-interpreter/verify_archive.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# Copyright (C) 2026 Matt Hargett. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +set -euo pipefail + +if [[ $# -ne 3 ]]; then + echo "usage: $0 " >&2 + exit 2 +fi + +build_dir=$1 +library=$2 +architecture=$3 +compile_commands="$build_dir/compile_commands.json" + +if [[ ! -f "$library" || ! -f "$compile_commands" ]]; then + echo "missing archive or compile database for Apple profile verification" >&2 + exit 1 +fi + +lipo -info "$library" | grep -F "architecture: $architecture" + +members=$(ar -t "$library") + +require_member() { + local pattern=$1 + local description=$2 + if ! grep -Eq "$pattern" <<<"$members"; then + echo "missing $description in $library" >&2 + exit 1 + fi +} + +reject_member() { + local pattern=$1 + local description=$2 + if grep -Eiq "$pattern" <<<"$members"; then + echo "unexpected $description in $library" >&2 + grep -Ei "$pattern" <<<"$members" >&2 + exit 1 + fi +} + +require_member '(^|/)wasm_interp_fast\.c\.o$' "fast interpreter object" +require_member '(^|/)wasm_component_runtime\.c\.o$' "component runtime object" +require_member '(^|/)wasm_component_application\.c\.o$' "component application object" +require_member '(^|/)libc_wasi_p2_wrapper\.c\.o$' \ + "Preview 2 wrapper object" +require_member '(^|/)thread_manager\.c\.o$' "native thread-manager object" + +reject_member '(^|/)wasm_interp_classic\.c\.o$' "classic interpreter object" +reject_member '(^|/)(aot_|jit_|fast_jit|llvm_jit|compilation)' \ + "AOT, JIT, or compiler object" +reject_member '(^|/)libc_wasi_wrapper\.c\.o$' "Preview 1 wrapper object" +reject_member '(^|/)(lib_pthread|lib_wasi_threads)' \ + "guest-thread library object" + +if ! grep -Fq -- '-flto=full' "$compile_commands"; then + echo "compile database does not contain the required -flto=full" >&2 + exit 1 +fi +if grep -Fq -- '-flto=thin' "$compile_commands"; then + echo "compile database unexpectedly enables ThinLTO" >&2 + exit 1 +fi + +require_compile_source() { + local source=$1 + if ! grep -Fq "$source" "$compile_commands"; then + echo "compile database is missing $source" >&2 + exit 1 + fi +} + +reject_compile_source() { + local source=$1 + if grep -Fq "$source" "$compile_commands"; then + echo "compile database unexpectedly includes $source" >&2 + exit 1 + fi +} + +require_compile_source '/interpreter/wasm_interp_fast.c' +require_compile_source '/common/component-model/' +require_compile_source '/libraries/thread-mgr/thread_manager.c' +require_compile_source '/libraries/libc-wasi-p2/libc_wasi_p2_wrapper.c' +reject_compile_source '/interpreter/wasm_interp_classic.c' +reject_compile_source '/aot/' +reject_compile_source '/compilation/' +reject_compile_source '/fast-jit/' +reject_compile_source '/libraries/libc-wasi/libc_wasi_wrapper.c' +reject_compile_source '/libraries/lib-pthread/' +reject_compile_source '/libraries/lib-wasi-threads/' + +if ! grep -Fq -- '-DWASM_ENABLE_LIBC_WASI_P2=1' "$compile_commands"; then + echo "compile database does not enable Preview 2" >&2 + exit 1 +fi +if grep -Fq -- '-DWASM_ENABLE_LIBC_WASI=1' "$compile_commands"; then + echo "compile database unexpectedly enables Preview 1" >&2 + exit 1 +fi + +echo "verified Preview 2-only wasm32-wasip2 fast-interpreter full-LTO archive" diff --git a/tests/apple-app-store-interpreter/verify_link.sh b/tests/apple-app-store-interpreter/verify_link.sh new file mode 100755 index 0000000000..8e0eb70280 --- /dev/null +++ b/tests/apple-app-store-interpreter/verify_link.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copyright (C) 2026 Matt Hargett. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "usage: $0 " >&2 + exit 2 +fi + +executable=$1 +symbols=$(nm "$executable") + +# Setup and teardown are cold operations. The two functions used once per +# render quantum must be internalized/inlined by the final full-LTO link so the +# statically composed host pays no extra public adapter-call boundary. +if grep -Eq \ + '[[:space:]][Tt][[:space:]]+_?wasm_component_(call_prepared|prepared_call_post_return)$' \ + <<<"$symbols"; then + echo "full LTO did not eliminate a hot prepared-call adapter boundary" >&2 + grep -E \ + '[[:space:]][Tt][[:space:]]+_?wasm_component_(call_prepared|prepared_call_post_return)$' \ + <<<"$symbols" >&2 + exit 1 +fi + +if grep -Eiq \ + '[[:space:]][Tt][[:space:]]+_?(wasm_interp_classic|aot_|jit_|fast_jit)' \ + <<<"$symbols"; then + echo "final component smoke contains a forbidden runtime symbol" >&2 + exit 1 +fi + +echo "verified full-LTO component embedding link" diff --git a/tests/apple-app-store-interpreter/wasip2_command.rs b/tests/apple-app-store-interpreter/wasip2_command.rs new file mode 100644 index 0000000000..bb886bda43 --- /dev/null +++ b/tests/apple-app-store-interpreter/wasip2_command.rs @@ -0,0 +1,15 @@ +// Copyright (C) 2026 Matt Hargett. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Regenerate the checked fixture with the product's pinned guest toolchain: +// rustc +1.93.1 --crate-name wamr_wasip2_command \ +// --target wasm32-wasip2 -O wasip2_command.rs \ +// -o /tmp/wasip2_command.wasm +// wasm-tools strip /tmp/wasip2_command.wasm -o wasip2_command.wasm + +fn main() { + assert_eq!(std::env::args_os().count(), 0); + assert_eq!(std::env::vars_os().count(), 0); + println!("wamr wasm32-wasip2 smoke"); + eprintln!("wamr wasm32-wasip2 stderr smoke"); +} diff --git a/tests/apple-app-store-interpreter/wasip2_command.wasm b/tests/apple-app-store-interpreter/wasip2_command.wasm new file mode 100644 index 0000000000..be4b1554b8 Binary files /dev/null and b/tests/apple-app-store-interpreter/wasip2_command.wasm differ diff --git a/tests/fuzz/component-fuzz/.gitignore b/tests/fuzz/component-fuzz/.gitignore new file mode 100644 index 0000000000..875f3b4d72 --- /dev/null +++ b/tests/fuzz/component-fuzz/.gitignore @@ -0,0 +1,8 @@ +build/ +corpus/ +corpus_verify/ +findings/ +crash-* +oom-* +leak-* +timeout-* diff --git a/tests/fuzz/component-fuzz/CMakeLists.txt b/tests/fuzz/component-fuzz/CMakeLists.txt new file mode 100644 index 0000000000..898f47b112 --- /dev/null +++ b/tests/fuzz/component-fuzz/CMakeLists.txt @@ -0,0 +1,132 @@ +# Copyright (C) 2026 Rebecker Specialties. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required(VERSION 3.14) + +project(wamr_component_fuzzing LANGUAGES ASM C CXX) + +# libFuzzer requires Clang. +if(NOT CMAKE_C_COMPILER_ID MATCHES ".*Clang") + message(FATAL_ERROR "Please use Clang as the C compiler for libFuzzer compatibility.") +endif() + +# The sanitizer/fuzzer compile flags below are emitted only for C/CXX (see the +# COMPILE_LANGUAGE generator expressions), so they never reach the .s assembly +# TUs (e.g. invokeNative_em64*.s on x86_64). But the ASM compiler must still be +# Clang: the runtime's .s sources are assembled by ${CMAKE_ASM_COMPILER}, and a +# non-Clang assembler (e.g. system gcc/as) can choke on the Clang-driver flags +# CMake still forwards. Require Clang for ASM so the build stays self-consistent. +if(NOT CMAKE_ASM_COMPILER_ID MATCHES ".*Clang") + message(FATAL_ERROR "Please use Clang as the ASM compiler for libFuzzer compatibility.") +endif() + +set(CMAKE_BUILD_TYPE Debug) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} WAMR_BUILD_PLATFORM) + +set(REPO_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) + +# Build target detection (no 32-bit cross here; for a real ILP32 run, see +# the README — point this at an i686 / armv7 toolchain to also catch the +# size_t-is-32-bit overflow class). +if(NOT DEFINED WAMR_BUILD_TARGET) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64)") + set(WAMR_BUILD_TARGET "AARCH64") + elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(WAMR_BUILD_TARGET "X86_64") + elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(WAMR_BUILD_TARGET "X86_32") + else() + message(FATAL_ERROR "Unsupported build target platform!") + endif() +endif() + +if(APPLE) + add_definitions(-DBH_PLATFORM_DARWIN) +endif() + +# +# Interpreter-only, component-model-on, host-layer-off configuration. +# No LLVM / AOT / JIT: this target only exercises the binary parser, +# validator and WAVE helpers. The WASI Preview 2 host layer is excluded +# (LIBC_WASI=0); any unreferenced instantiation / canonical-ABI / host +# code is removed by dead-strip below, so no host symbols are needed. +# +set(WAMR_BUILD_INTERP 1) +set(WAMR_BUILD_FAST_INTERP 1) +set(WAMR_BUILD_AOT 0) +set(WAMR_BUILD_JIT 0) +set(WAMR_BUILD_FAST_JIT 0) +set(WAMR_BUILD_LIBC_BUILTIN 0) +set(WAMR_BUILD_LIBC_WASI 0) +set(WAMR_BUILD_COMPONENT_MODEL 1) +set(WAMR_BUILD_REF_TYPES 1) +set(WAMR_BUILD_SIMD 1) +set(WAMR_BUILD_GC 0) +set(WAMR_BUILD_MULTI_MODULE 0) +set(WAMR_DISABLE_HW_BOUND_CHECK 1) + +add_definitions(-DWASM_ENABLE_FUZZ_TEST=1) +add_compile_options(-Wno-unused-command-line-argument) + +# +# Sanitizers: AddressSanitizer + UndefinedBehaviorSanitizer + libFuzzer. +# Skip when oss-fuzz drives its own instrumentation +# (-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION present in CFLAGS). +# +set(CFLAGS_ENV $ENV{CFLAGS}) +string(FIND "${CFLAGS_ENV}" "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION" FUZZ_POS) +if(FUZZ_POS GREATER -1) + set(IN_OSS_FUZZ 1) +else() + set(IN_OSS_FUZZ 0) +endif() + +# The instrumentation flags below are scoped to C and CXX via +# $<$:...>. The runtime pulls in hand-written assembly +# (e.g. invokeNative_em64*.s on x86_64); the sanitizer/fuzzer flags are +# meaningless there and a non-Clang assembler can reject them, so they must not +# leak onto the ASM TUs. ASan+UBSan+libFuzzer stay fully applied to every C/C++ +# TU. add_link_options is left unscoped: it is per-link, not per-language. +if(NOT IN_OSS_FUZZ) + message(STATUS "Enabling ASan + UBSan for the component-model fuzz target") + add_compile_options( + $<$:-g> + $<$:-O1> + $<$:-fno-omit-frame-pointer>) + add_compile_options( + $<$:-fsanitize=address> + $<$:-fsanitize-address-use-after-scope>) + add_compile_options( + $<$:-fsanitize=array-bounds,bool,builtin,enum,integer-divide-by-zero,null,object-size,return,returns-nonnull-attribute,shift,signed-integer-overflow,unreachable,vla-bound> + $<$:-fno-sanitize-recover=array-bounds,bool,builtin,enum,integer-divide-by-zero,null,object-size,return,returns-nonnull-attribute,shift,signed-integer-overflow,unreachable,vla-bound>) + add_link_options(-fsanitize=address,undefined) +endif() + +# libFuzzer driver (no vptr: RTTI is off in the runtime build). +add_compile_options( + $<$:-fsanitize=fuzzer> + $<$:-fno-sanitize=vptr>) +add_link_options(-fsanitize=fuzzer -fno-sanitize=vptr) + +# Drop unreferenced functions (the instantiation / canonical-ABI / host +# paths the parser fuzzer never calls) so the link needs no host symbols. +if(APPLE) + add_link_options(-Wl,-dead_strip) +else() + add_compile_options(-ffunction-sections -fdata-sections) + add_link_options(-Wl,--gc-sections) +endif() + +include(${REPO_ROOT_DIR}/build-scripts/runtime_lib.cmake) +include(${REPO_ROOT_DIR}/core/shared/utils/uncommon/shared_uncommon.cmake) + +add_library(vmlib STATIC ${WAMR_RUNTIME_LIB_SOURCE}) +target_include_directories(vmlib PUBLIC ${RUNTIME_LIB_HEADER_LIST}) + +add_executable(component_parser_fuzz + component-parser/component_parser_fuzz.cc + component-parser/host_resolve_stubs.c) +target_link_libraries(component_parser_fuzz PRIVATE vmlib m) diff --git a/tests/fuzz/component-fuzz/README.md b/tests/fuzz/component-fuzz/README.md new file mode 100644 index 0000000000..5893e73277 --- /dev/null +++ b/tests/fuzz/component-fuzz/README.md @@ -0,0 +1,100 @@ +# Component Model binary-parser fuzzing + +A libFuzzer target for the WebAssembly [Component Model](https://github.com/WebAssembly/component-model) +binary parser (`wasm_component_parse_sections`). + +The existing `tests/fuzz/wasm-mutator-fuzz` target only loads **core** +wasm modules; it never reaches the component parser. The component +parser, however, decodes a fully untrusted binary across all 13 +section types and drives a large number of LEB-count-controlled +allocations and pointer walks — exactly the surface a fuzzer should +cover. This target closes that gap. + +It is intentionally **LLVM-free**: it builds an interpreter-only +runtime with the component model enabled and the WASI Preview 2 host +layer disabled (`WAMR_BUILD_LIBC_WASI=0`), so only the parser, +validator and WAVE helpers are exercised. Unreferenced instantiation / +canonical-ABI / host code is removed by dead-strip; two inert host +stubs (`component-parser/host_resolve_stubs.c`) satisfy the symbols +that `wasm_resolve_imports_WASI` references but the parser never calls. + +## Build + +Requires Clang (for libFuzzer) and a modern `bison` / `flex` (the WAVE +parser is generated). On macOS: + +```sh +brew install llvm bison flex +export PATH="$(brew --prefix bison)/bin:$(brew --prefix flex)/bin:$PATH" +``` + +```sh +cd tests/fuzz/component-fuzz +mkdir build && cd build +CC=clang CXX=clang++ cmake .. +cmake --build . -j +``` + +ASan + UBSan + libFuzzer are enabled by default (skipped automatically +under oss-fuzz, which provides its own instrumentation). + +## Run + +Seed the corpus from the component fixtures already in the tree, then +fuzz: + +```sh +mkdir -p corpus +find ../../../unit -name '*.wasm' \( -path '*component*' -o -path '*canonical*' \) \ + -exec cp {} corpus/ \; + +ASAN_OPTIONS=detect_leaks=0 ./component_parser_fuzz corpus/ -close_fd_mask=1 +``` + +`-close_fd_mask=1` suppresses the parser's debug output. + +Embedded **core** modules (section 0x01) are handed to the core wasm +loader, which can allocate proportionally to a declared (large) +function count and trip libFuzzer's RSS limit. Those OOMs are core-loader +behaviour, not component-parser defects (the `wasm-mutator-fuzz` target +already covers the core loader); pass `-ignore_ooms=1 -rss_limit_mb=3000` +to keep hunting component-parser bugs past them. + +## ILP32 / 32-bit `size_t` + +Several parser allocations are of the form +`wasm_runtime_malloc(sizeof(T) * count)` with a 32-bit `count` decoded +from the input. On a 64-bit host the multiply cannot overflow (`size_t` +is 64-bit) and a huge `count` simply fails the allocation. On a 32-bit +target (`arm64_32-apple-watchos`, `i686`, ESP32, …) `size_t` is 32-bit +and the multiply can wrap, under-allocating before the populate loop +runs. The count-bounds checks this work adds (a count cannot exceed the +remaining input) defend both cases. To also *fuzz* the ILP32 path, +point `CC`/`CXX` at a 32-bit toolchain (e.g. `clang -m32` on Linux) and +rebuild — the harness is target-independent. + +## Findings + +The first runs of this target found four distinct memory-safety bugs in +the parser's partial-parse / error-path cleanup, all fixed in the same +change set: + +1. **Double-free** in `parse_component_decl_export`: `export_name` was + freed locally on the `extern_desc` error paths after ownership had + already transferred to `*out`, so the centralized cleanup freed it + again. +2. **Use-after-free** in the instances-section inline-export parse: a + `WASMComponentCoreName` was pre-allocated and passed to + `parse_core_name` (which allocates its own and only writes `*out` on + success); on failure the uninitialized pre-allocation's `->name` + field was dereferenced by `free_core_name` (and the struct leaked on + success). +3. **Uninitialized count-arrays** in the type-section parsers + (`parse_func_type` params, `parse_component_instance_type` decls, + `parse_component_type` decls): the array's element count was set to + the full declared count before population, but the array was not + zeroed, so a parse failure partway left an uninitialized tail that + the cleanup walk dereferenced (`free_*` reading a wild `tag` / + `label` pointer at arbitrary nesting depth). +4. Each of those allocation sites also lacked a bound on the + LEB-decoded count (the ILP32 overflow class above). diff --git a/tests/fuzz/component-fuzz/component-parser/component_parser_fuzz.cc b/tests/fuzz/component-fuzz/component-parser/component_parser_fuzz.cc new file mode 100644 index 0000000000..0a48146a66 --- /dev/null +++ b/tests/fuzz/component-fuzz/component-parser/component_parser_fuzz.cc @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2026 Rebecker Specialties. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +/* + * libFuzzer target for the WebAssembly Component Model binary parser. + * + * The component parser ingests a fully untrusted binary buffer + * (wasm_component_parse_sections) and decodes all 13 section types, + * driving a large number of LEB-count-controlled allocations and + * pointer walks. That is exactly the surface a fuzzer should cover and + * the existing wasm-mutator fuzz target does not reach it (it only + * loads core wasm modules). + * + * This target is intentionally LLVM-free: it builds an interpreter-only + * runtime with the component model enabled and the WASI Preview 2 host + * layer disabled (LIBC_WASI=0), so the only code exercised is the + * parser + validator + WAVE helpers. Unreferenced instantiation / + * canonical-ABI / host functions are removed by dead-strip, so no host + * symbols are required to link. + * + * Build + run: see tests/fuzz/component-fuzz/README.md + */ + +#include "wasm_export.h" +#include "wasm_component.h" + +#include +#include +#include +#include + +static bool +ensure_runtime(void) +{ + /* Initialize the runtime once for the whole campaign. Use the + * system allocator (not a fixed pool) so AddressSanitizer + * instruments every component allocation and can see overflows, + * use-after-free, and leaks in the parser. */ + static bool inited = false; + if (inited) + return true; + + RuntimeInitArgs init_args; + memset(&init_args, 0, sizeof(init_args)); + init_args.mem_alloc_type = Alloc_With_System_Allocator; + + if (!wasm_runtime_full_init(&init_args)) + return false; + inited = true; + return true; +} + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + /* The 8-byte header is decoded before section parsing; shorter + * inputs are uninteresting for this target. */ + if (size < 8 || size > UINT32_MAX) + return 0; + + if (!ensure_runtime()) + return 0; + + /* The parser delegates embedded core modules (section 0x01) to the + * core wasm loader, which mutates its input buffer in place. Work + * on a private copy so we never write through libFuzzer's const + * input and so repeated runs over the same input are deterministic. */ + std::vector buf(data, data + size); + + WASMHeader header; + if (!wasm_decode_header(buf.data(), (uint32_t)buf.size(), &header)) + return 0; + + /* Only feed actual components to the component parser; core modules + * are covered by the existing wasm-mutator target. */ + if (!is_wasm_component(header)) + return 0; + + WASMComponent *component = + (WASMComponent *)wasm_runtime_malloc(sizeof(WASMComponent)); + if (!component) + return 0; + memset(component, 0, sizeof(WASMComponent)); + + /* LoadArgs.name must be a non-NULL, mutable buffer (the embedded + * core-module loader copies from it). */ + char name_buf[] = "component-fuzz"; + LoadArgs load_args; + memset(&load_args, 0, sizeof(load_args)); + load_args.name = name_buf; + load_args.is_component = true; + + /* On both success and failure wasm_component_free must leave the + * parsed-section state clean; the top-level struct is owned by us. */ + wasm_component_parse_sections(buf.data(), (uint32_t)buf.size(), + component, &load_args, 0); + wasm_component_free(component); + wasm_runtime_free(component); + + return 0; +} diff --git a/tests/fuzz/component-fuzz/component-parser/host_resolve_stubs.c b/tests/fuzz/component-fuzz/component-parser/host_resolve_stubs.c new file mode 100644 index 0000000000..adb0a7e67c --- /dev/null +++ b/tests/fuzz/component-fuzz/component-parser/host_resolve_stubs.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2026 Rebecker Specialties. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +/* + * Inert stubs for the WASI Preview 2 host-resolution symbols. + * + * This fuzz target builds the component model with the WASI Preview 2 + * host layer disabled (LIBC_WASI=0) so that only the binary parser / + * validator / WAVE helpers are exercised. The binary parser never + * resolves imports — that is an instantiation-time concern in + * wasm_resolve_imports_WASI() — but that function references the two + * symbols below and is not always removed by dead-strip (it is reached + * through a native-registration table the linker cannot prove dead). + * + * These stubs let the parser-only target link without pulling in the + * (Linux-only) WASI Preview 2 wrappers. They are never called on the + * parse path the fuzzer drives; if import resolution is ever exercised + * the conservative "unavailable" return keeps the failure clean. + */ + +#include + +bool +wasm_check_wasi_p2_version(const char *required_interface) +{ + (void)required_interface; + return false; +} + +bool +wasm_native_register_wasi_p2_module_func(const char *module_name, + const char *func_name) +{ + (void)module_name; + (void)func_name; + return false; +} diff --git a/tests/regression/ba-issues/build_wamr.sh b/tests/regression/ba-issues/build_wamr.sh index 5ff5119911..fab5321f3d 100755 --- a/tests/regression/ba-issues/build_wamr.sh +++ b/tests/regression/ba-issues/build_wamr.sh @@ -67,4 +67,8 @@ build_iwasm "-DWAMR_BUILD_BRANCH_HINTS=1" "default-branch-hints-enabled" # build default iwasm for testing tail call with fast-interp build_iwasm "-DWAMR_BUILD_REF_TYPES=1 -DWAMR_BUILD_FAST_INTERP=1 -DWAMR_BUILD_TAIL_CALL=1 -DWAMR_BUILD_LIBC_WASI=0" "default-tail-call-wasi-disabled" +# build classic-interp iwasm with ASAN and hardware bounds checks disabled +# for poll_oneoff host memory safety regressions +build_iwasm "-DWAMR_BUILD_REF_TYPES=1 -DWAMR_BUILD_FAST_INTERP=0 -DWAMR_BUILD_AOT=0 -DWAMR_BUILD_JIT=0 -DWAMR_BUILD_FAST_JIT=0 -DWAMR_BUILD_SIMD=0 -DWAMR_DISABLE_HW_BOUND_CHECK=1" "poll-oneoff-asan" + # TODO: add more version of iwasm, for example, sgx version diff --git a/tests/regression/ba-issues/issues/issue-980004/poll_oneoff_out_of_bounds_write.wasm b/tests/regression/ba-issues/issues/issue-980004/poll_oneoff_out_of_bounds_write.wasm new file mode 100644 index 0000000000..ec56c43f3d Binary files /dev/null and b/tests/regression/ba-issues/issues/issue-980004/poll_oneoff_out_of_bounds_write.wasm differ diff --git a/tests/regression/ba-issues/issues/issue-980004/poll_oneoff_out_of_bounds_write.wat b/tests/regression/ba-issues/issues/issue-980004/poll_oneoff_out_of_bounds_write.wat new file mode 100644 index 0000000000..2121e0b64e --- /dev/null +++ b/tests/regression/ba-issues/issues/issue-980004/poll_oneoff_out_of_bounds_write.wat @@ -0,0 +1,38 @@ +(module + (import "wasi_snapshot_preview1" "poll_oneoff" + (func $poll_oneoff (param i32 i32 i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "proc_exit" + (func $proc_exit (param i32))) + (memory (export "memory") 1) + (func (export "_start") + ;; Two absolute clock subscriptions ensure poll_oneoff writes two events. + i32.const 0 + i64.const 1 + i64.store + i32.const 8 + i32.const 0 + i32.store8 + i32.const 40 + i32.const 1 + i32.store16 + + i32.const 48 + i64.const 2 + i64.store + i32.const 56 + i32.const 0 + i32.store8 + i32.const 88 + i32.const 1 + i32.store16 + + ;; out points to exactly one wasi_event_t at the end of memory. + i32.const 0 + i32.const 65504 + i32.const 2 + i32.const 65500 + call $poll_oneoff + drop + + i32.const 0 + call $proc_exit)) diff --git a/tests/regression/ba-issues/issues/issue-980005/poll_oneoff_out_of_bounds_read.wasm b/tests/regression/ba-issues/issues/issue-980005/poll_oneoff_out_of_bounds_read.wasm new file mode 100644 index 0000000000..30ebce2925 Binary files /dev/null and b/tests/regression/ba-issues/issues/issue-980005/poll_oneoff_out_of_bounds_read.wasm differ diff --git a/tests/regression/ba-issues/issues/issue-980005/poll_oneoff_out_of_bounds_read.wat b/tests/regression/ba-issues/issues/issue-980005/poll_oneoff_out_of_bounds_read.wat new file mode 100644 index 0000000000..cce8e8b299 --- /dev/null +++ b/tests/regression/ba-issues/issues/issue-980005/poll_oneoff_out_of_bounds_read.wat @@ -0,0 +1,28 @@ +(module + (import "wasi_snapshot_preview1" "poll_oneoff" + (func $poll_oneoff (param i32 i32 i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "proc_exit" + (func $proc_exit (param i32))) + (memory (export "memory") 1) + (func (export "_start") + ;; Place exactly one subscription at the end of memory. + i32.const 65488 + i64.const 1 + i64.store + i32.const 65496 + i32.const 0 + i32.store8 + i32.const 65528 + i32.const 1 + i32.store16 + + ;; nsubscriptions=2 forces validation of the full subscription array. + i32.const 65488 + i32.const 64 + i32.const 2 + i32.const 60 + call $poll_oneoff + drop + + i32.const 0 + call $proc_exit)) diff --git a/tests/regression/ba-issues/run.py b/tests/regression/ba-issues/run.py index d50bf9b14e..a341493471 100755 --- a/tests/regression/ba-issues/run.py +++ b/tests/regression/ba-issues/run.py @@ -20,7 +20,10 @@ "./build/build-{runtime}/iwasm {running_options} {running_mode} {file} {argument}" ) -COMPILE_AOT_COMMAND = "./build/build-wamrc/{compiler} {options} -o {out_file} {in_file}" +COMPILE_AOT_COMMAND = ( + "./build/build-wamrc/{compiler} {target_options} {options} " + "-o {out_file} {in_file}" +) TEST_AOT_COMMAND = "./build/build-{runtime}/iwasm {running_options} {file} {argument}" LOG_FILE = "issues_tests.log" @@ -148,7 +151,11 @@ def run_issue_test_wamrc(issue_id, compile_options): out_file_path = os.path.join(issue_path, out_file) cmd = COMPILE_AOT_COMMAND.format( - compiler=compiler, options=options, out_file=out_file_path, in_file=in_file_path + compiler=compiler, + target_options=os.environ.get("WAMRC_TARGET_OPTIONS", ""), + options=options, + out_file=out_file_path, + in_file=in_file_path, ) return run_and_compare_results(issue_id, cmd, description, ret_code, stdout_content) diff --git a/tests/regression/ba-issues/running_config.json b/tests/regression/ba-issues/running_config.json index 9083af9014..ebde50babd 100644 --- a/tests/regression/ba-issues/running_config.json +++ b/tests/regression/ba-issues/running_config.json @@ -1853,6 +1853,38 @@ } }, + { + "deprecated": false, + "ids": [ + 980004 + ], + "runtime": "iwasm-poll-oneoff-asan", + "file": "poll_oneoff_out_of_bounds_write.wasm", + "mode": "classic-interp", + "options": "-f _start", + "argument": "", + "expected return": { + "ret code": 1, + "stdout content": "Exception: out of bounds memory access", + "description": "poll_oneoff rejects an undersized event buffer for multi-subscription WASI calls" + } + }, + { + "deprecated": false, + "ids": [ + 980005 + ], + "runtime": "iwasm-poll-oneoff-asan", + "file": "poll_oneoff_out_of_bounds_read.wasm", + "mode": "classic-interp", + "options": "-f _start", + "argument": "", + "expected return": { + "ret code": 1, + "stdout content": "Exception: out of bounds memory access", + "description": "poll_oneoff rejects an undersized subscription array for multi-subscription WASI calls" + } + }, { "deprecated": false, "ids": [ diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 008308c245..96b91787c8 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -92,7 +92,22 @@ FetchContent_MakeAvailable(cmocka) include(GoogleTest) enable_testing() +set(COMPONENT_MODEL_UNIT_TEST_FOLDERS + component + component-instantiation + component-execution + canonical-abi + canonical-execution + libc-wasi-p2 + wave-parser +) + if(DEFINED UNITTEST_FOLDER AND NOT "${UNITTEST_FOLDER}" STREQUAL "") + if(WAMR_BUILD_TARGET STREQUAL "X86_32" + AND UNITTEST_FOLDER IN_LIST COMPONENT_MODEL_UNIT_TEST_FOLDERS) + message(FATAL_ERROR + "${UNITTEST_FOLDER} requires the 64-bit Preview 2 native-call bridge") + endif() add_subdirectory("${UNITTEST_FOLDER}") return() endif() @@ -108,14 +123,17 @@ add_subdirectory(linux-perf) add_subdirectory(gc) add_subdirectory(unsupported-features) add_subdirectory(exception-handling) +add_subdirectory(relaxed-simd) add_subdirectory(running-modes) -add_subdirectory(component) -add_subdirectory(component-instantiation) -add_subdirectory(component-execution) -add_subdirectory(canonical-abi) -add_subdirectory(canonical-execution) -add_subdirectory(libc-wasi-p2) -add_subdirectory(wave-parser) + +# The Preview 2 native-call bridge currently uses the 64-bit invokeNative ABI. +# Keep the component runtime suites out of X86_32 until that bridge has a +# dedicated 32-bit argument/result implementation. +if(NOT WAMR_BUILD_TARGET STREQUAL "X86_32") + foreach(component_test_folder IN LISTS COMPONENT_MODEL_UNIT_TEST_FOLDERS) + add_subdirectory("${component_test_folder}") + endforeach() +endif() add_subdirectory(mem-alloc) if(FULL_TEST) diff --git a/tests/unit/canonical-abi/test_canonical_abi_traps.cc b/tests/unit/canonical-abi/test_canonical_abi_traps.cc new file mode 100644 index 0000000000..1239c0f43e --- /dev/null +++ b/tests/unit/canonical-abi/test_canonical_abi_traps.cc @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2026 Rebecker Specialties. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +/* + * Canonical-ABI trap coverage. + * + * The existing canonical-abi unit tests only lift values from in-bounds, + * correctly-aligned addresses, so the trap_if() guards in + * load_string_from_range / load_list_from_range / load_int (out-of-bounds, + * ptr+len overflow, misalignment) are never exercised. For an interpreter-only + * on-device runtime those guards are the line between a clean trap and a wild + * read of linear memory, so they need explicit coverage. + * + * These tests drive the public range-load entry points with malformed + * arguments and assert the load TRAPS (returns false) rather than reading out + * of bounds. The *_PtrLenOverflow cases are regression tests for the 32-bit + * wrap in the bounds checks (begin + byte_length / ptr + length*elem_sz were + * added in 32-bit before the uint64 cast, so a pointer near UINT32_MAX could + * bypass the check). + */ + +#include +#include "../component/helpers.h" +#include "wasm_memory.h" +#include "wasm_component_canonical.h" +#include "wasm_component_runtime.h" +#include + +class CanonicalAbiTrapTest : public testing::Test +{ + public: + std::unique_ptr helper; + WASMMemoryInstance *mem = nullptr; + uint32_t mem_size = 0; + + // cx and the option structs it points at must outlive each load() call. + LiftOptions lift_opts; + LiftLowerOptions lift_lower_opts; + CanonicalOptions canonical_opts; + LiftLowerContext cx; + + virtual void SetUp() + { + helper = std::make_unique(); + helper->do_setup(); + + ASSERT_TRUE(helper->read_wasm_file("loaded.wasm")); + ASSERT_TRUE(helper->component_raw != nullptr); + ASSERT_TRUE(helper->load_component()); + ASSERT_TRUE(helper->instantiate_component()); + + auto memories = helper->get_memories(); + ASSERT_GT(memories.size(), 0u); + mem = memories[0]; + ASSERT_NE(mem, nullptr); + mem_size = (uint32_t)mem->memory_data_size; + ASSERT_GT(mem_size, 0u); + } + + virtual void TearDown() + { + helper->do_teardown(); + helper = nullptr; + } + + // Build a lift context bound to this component's memory + the given + // string encoding. Returns &cx (whose backing structs are members). + LiftLowerContext *build_cx(StringEncoding encoding) + { + memset(&lift_opts, 0, sizeof(lift_opts)); + lift_opts.string_encoding = encoding; + lift_opts.memory = mem; + + memset(&lift_lower_opts, 0, sizeof(lift_lower_opts)); + lift_lower_opts.lift_opts = &lift_opts; + lift_lower_opts.realloc_func = nullptr; + + memset(&canonical_opts, 0, sizeof(canonical_opts)); + canonical_opts.lift_lower_opts = &lift_lower_opts; + canonical_opts.post_return_func = nullptr; + canonical_opts.async = true; + canonical_opts.callback_func = nullptr; + + memset(&cx, 0, sizeof(cx)); + cx.canonical_opts = &canonical_opts; + cx.inst = helper->component_inst; + cx.borrow_scope.task = nullptr; + return &cx; + } + + WASMComponentTypeInstance prim_type(WASMComponentPrimValType prim) + { + WASMComponentTypeInstance t; + memset(&t, 0, sizeof(t)); + t.type = COMPONENT_VAL_TYPE_PRIMVAL; + t.type_specific.primval = prim; + t.alignment = compute_alignment(&t); + t.elem_size = compute_elem_size(&t); + return t; + } +}; + +// --- string range traps ------------------------------------------------------ + +TEST_F(CanonicalAbiTrapTest, StringRangeCleanOutOfBoundsTraps) +{ + LiftLowerContext *c = build_cx(ENCODING_UTF_8); + wit_value_t out = nullptr; + // begin near the end of memory, length runs past it. + bool ok = load_string_from_range(c, mem_size - 2, /*code_units=*/64, &out); + EXPECT_FALSE(ok) << "string lift past end of memory must trap"; + if (out) + free_wit_value(out); +} + +TEST_F(CanonicalAbiTrapTest, StringRangePtrLenOverflowTraps) +{ + LiftLowerContext *c = build_cx(ENCODING_UTF_8); + wit_value_t out = nullptr; + // begin + byte_length wraps mod 2^32 (0xFFFFFFF0 + 0x20 == 0x10); the + // bounds check must widen before adding, otherwise this reads at + // memory_data + 0xFFFFFFF0. + bool ok = load_string_from_range(c, 0xFFFFFFF0u, /*code_units=*/0x20, &out); + EXPECT_FALSE(ok) << "ptr+len overflow must trap, not wrap past the check"; + if (out) + free_wit_value(out); +} + +TEST_F(CanonicalAbiTrapTest, StringRangeMisalignedUtf16Traps) +{ + LiftLowerContext *c = build_cx(ENCODING_UTF_16); + wit_value_t out = nullptr; + // UTF-16 requires 2-byte alignment; an odd begin must trap. + bool ok = load_string_from_range(c, /*begin=*/3, /*code_units=*/1, &out); + EXPECT_FALSE(ok) << "misaligned UTF-16 string pointer must trap"; + if (out) + free_wit_value(out); +} + +// --- list range traps -------------------------------------------------------- + +TEST_F(CanonicalAbiTrapTest, ListRangeCleanOutOfBoundsTraps) +{ + LiftLowerContext *c = build_cx(ENCODING_UTF_8); + WASMComponentTypeInstance u32 = prim_type(WASM_COMP_PRIMVAL_U32); + wit_value_t out = nullptr; + // 4-byte elements; a length that runs past memory must trap. + uint32_t length = (mem_size / 4) + 16; + bool ok = load_list_from_range(c, /*ptr=*/0, length, &u32, &out); + EXPECT_FALSE(ok) << "list lift past end of memory must trap"; + if (out) + free_wit_value(out); +} + +TEST_F(CanonicalAbiTrapTest, ListRangePtrLenOverflowTraps) +{ + LiftLowerContext *c = build_cx(ENCODING_UTF_8); + WASMComponentTypeInstance u32 = prim_type(WASM_COMP_PRIMVAL_U32); + wit_value_t out = nullptr; + // length * elem_sz wraps in 32-bit (0x40000000 * 4 == 0); the bounds + // check must widen the multiply, otherwise it reads way out of bounds. + // + // EXPECT_FALSE alone is not enough here: if the overflow guard regressed, + // 0x40000000 * 4 wraps to 0, no trap fires, and load_list_from_valid_range + // then tries to malloc 0x40000000 * sizeof(wit_value_t) (~8GiB) which OOMs + // against the test heap and *also* returns false -- so the load would still + // report failure for the wrong reason. Assert the trap REASON so a + // regressed guard (which yields the "out of memory for list elements" + // message, not the bounds-check trap) actually fails the test. + c->inst->cur_exception[0] = '\0'; + bool ok = + load_list_from_range(c, /*ptr=*/0, /*length=*/0x40000000u, &u32, &out); + EXPECT_FALSE(ok) << "list length*elem_size overflow must trap"; + // The bounds-check trap_if stringifies its condition, which contains + // "memory_data_size"; the OOM fallback does not. Requiring the bounds + // message (and rejecting the OOM one) makes a regressed guard fail. + EXPECT_NE(strstr(c->inst->cur_exception, "memory_data_size"), nullptr) + << "expected the bounds-check trap, got: " << c->inst->cur_exception; + EXPECT_EQ(strstr(c->inst->cur_exception, "out of memory"), nullptr) + << "regressed overflow guard fell through to allocation: " + << c->inst->cur_exception; + if (out) + free_wit_value(out); +} + +TEST_F(CanonicalAbiTrapTest, ListRangeMisalignedTraps) +{ + LiftLowerContext *c = build_cx(ENCODING_UTF_8); + WASMComponentTypeInstance u32 = prim_type(WASM_COMP_PRIMVAL_U32); + wit_value_t out = nullptr; + // u32 list requires 4-byte alignment; ptr=2 must trap. + bool ok = load_list_from_range(c, /*ptr=*/2, /*length=*/1, &u32, &out); + EXPECT_FALSE(ok) << "misaligned list pointer must trap"; + if (out) + free_wit_value(out); +} + +// --- load traps -------------------------------------------------------------- + +TEST_F(CanonicalAbiTrapTest, LoadPtrWidthOverflowTrapsBeforeReading) +{ + LiftLowerContext *c = build_cx(ENCODING_UTF_8); + WASMComponentTypeInstance u32 = prim_type(WASM_COMP_PRIMVAL_U32); + wit_value_t out = nullptr; + + c->inst->cur_exception[0] = '\0'; + bool ok = load(c, 0xFFFFFFFCu, &u32, &out); + EXPECT_FALSE(ok) << "load pointer+width overflow must trap"; + EXPECT_NE(strstr(c->inst->cur_exception, "memory_data_size"), nullptr) + << "expected the bounds-check trap, got: " << c->inst->cur_exception; + if (out) + free_wit_value(out); +} + +TEST_F(CanonicalAbiTrapTest, LoadCleanOutOfBoundsTraps) +{ + LiftLowerContext *c = build_cx(ENCODING_UTF_8); + WASMComponentTypeInstance u32 = prim_type(WASM_COMP_PRIMVAL_U32); + wit_value_t out = nullptr; + + bool ok = load(c, mem_size, &u32, &out); + EXPECT_FALSE(ok) << "load past the end of memory must trap"; + if (out) + free_wit_value(out); +} + +TEST_F(CanonicalAbiTrapTest, LoadMisalignedTrapsInReleasePath) +{ + LiftLowerContext *c = build_cx(ENCODING_UTF_8); + WASMComponentTypeInstance u32 = prim_type(WASM_COMP_PRIMVAL_U32); + wit_value_t out = nullptr; + + bool ok = load(c, 2, &u32, &out); + EXPECT_FALSE(ok) << "misaligned load pointer must trap"; + if (out) + free_wit_value(out); +} + +// --- store traps ------------------------------------------------------------- + +TEST_F(CanonicalAbiTrapTest, StorePtrWidthOverflowTrapsBeforeWriting) +{ + LiftLowerContext *c = build_cx(ENCODING_UTF_8); + WASMComponentTypeInstance u32 = prim_type(WASM_COMP_PRIMVAL_U32); + wit_value_t value = wit_u32_ctor(0xA5A5A5A5u); + + ASSERT_NE(value, nullptr); + c->inst->cur_exception[0] = '\0'; + bool ok = store(c, 0xFFFFFFFCu, &u32, value); + EXPECT_FALSE(ok) << "store pointer+width overflow must trap"; + EXPECT_NE(strstr(c->inst->cur_exception, "memory_data_size"), nullptr) + << "expected the bounds-check trap, got: " << c->inst->cur_exception; + free_wit_value(value); +} + +TEST_F(CanonicalAbiTrapTest, StoreCleanOutOfBoundsTraps) +{ + LiftLowerContext *c = build_cx(ENCODING_UTF_8); + WASMComponentTypeInstance u32 = prim_type(WASM_COMP_PRIMVAL_U32); + wit_value_t value = wit_u32_ctor(0xA5A5A5A5u); + + ASSERT_NE(value, nullptr); + bool ok = store(c, mem_size, &u32, value); + EXPECT_FALSE(ok) << "store past the end of memory must trap"; + free_wit_value(value); +} + +TEST_F(CanonicalAbiTrapTest, StoreMisalignedTrapsInReleasePath) +{ + LiftLowerContext *c = build_cx(ENCODING_UTF_8); + WASMComponentTypeInstance u32 = prim_type(WASM_COMP_PRIMVAL_U32); + wit_value_t value = wit_u32_ctor(0xA5A5A5A5u); + + ASSERT_NE(value, nullptr); + bool ok = store(c, 2, &u32, value); + EXPECT_FALSE(ok) << "misaligned store pointer must trap"; + free_wit_value(value); +} diff --git a/tests/unit/canonical-abi/test_lower_flat.cc b/tests/unit/canonical-abi/test_lower_flat.cc index ee76acbc94..737031d169 100644 --- a/tests/unit/canonical-abi/test_lower_flat.cc +++ b/tests/unit/canonical-abi/test_lower_flat.cc @@ -252,11 +252,13 @@ TEST_F(ComponentInstantiationLowerFlatTest, TestLowerFlatValuesRecord) { cx.borrow_scope.task = nullptr; ComponentWITRecordField *fields = (ComponentWITRecordField *)wasm_runtime_malloc(2 * sizeof(ComponentWITRecordField)); - fields[0].value = wit_u8_ctor(10); - fields[0].key = strdup("a"); - fields[1].value = wit_u32_ctor(200); - fields[1].key = strdup("b"); - + char key_a[] = "a"; + char key_b[] = "b"; + init_record_field(&fields[0], key_a, sizeof(key_a) - 1, wit_u8_ctor(10)); + init_record_field(&fields[1], key_b, sizeof(key_b) - 1, wit_u32_ctor(200)); + ASSERT_NE(fields[0].key, nullptr); + ASSERT_NE(fields[1].key, nullptr); + wit_value_t rec_val = wit_record_ctor(fields, 2); wit_value_t *elems = (wit_value_t *)wasm_runtime_malloc(sizeof(wit_value_t)); elems[0] = rec_val; @@ -547,4 +549,4 @@ TEST_F(ComponentInstantiationLowerFlatTest, TestLowerFlatValuesResult) { free_wit_value(input_list); wasm_runtime_free(params); -} \ No newline at end of file +} diff --git a/tests/unit/canonical-abi/test_store.cc b/tests/unit/canonical-abi/test_store.cc index a21f137f25..d322c0ae62 100644 --- a/tests/unit/canonical-abi/test_store.cc +++ b/tests/unit/canonical-abi/test_store.cc @@ -355,14 +355,15 @@ TEST_F(ComponentInstantiationStoreTest, TestStoreRecordInMemory) { ASSERT_NE(fields, nullptr); // Initialize field "x" = 12345 - fields[0].key = strdup("x"); - fields[0].key_size = 1; - fields[0].value = wit_u32_ctor(12345); + char key_x[] = "x"; + init_record_field(&fields[0], key_x, sizeof(key_x) - 1, + wit_u32_ctor(12345)); + ASSERT_NE(fields[0].key, nullptr); // Initialize field "y" = 42 - fields[1].key = strdup("y"); - fields[1].key_size = 1; - fields[1].value = wit_u8_ctor(42); + char key_y[] = "y"; + init_record_field(&fields[1], key_y, sizeof(key_y) - 1, wit_u8_ctor(42)); + ASSERT_NE(fields[1].key, nullptr); wit_value_t value_to_store = wit_record_ctor(fields, 2); ASSERT_NE(value_to_store, nullptr); @@ -1041,4 +1042,4 @@ TEST_F(ComponentInstantiationStoreTest, TestStoreResult) { // Cleanup free_wit_value(value_to_store_ok); free_wit_value(loaded_ok); free_wit_value(value_to_store_err); free_wit_value(loaded_err); -} \ No newline at end of file +} diff --git a/tests/unit/canonical-execution/CMakeLists.txt b/tests/unit/canonical-execution/CMakeLists.txt index 69033e2bdc..77e22fe002 100644 --- a/tests/unit/canonical-execution/CMakeLists.txt +++ b/tests/unit/canonical-execution/CMakeLists.txt @@ -57,15 +57,20 @@ target_include_directories(canonical-execution PRIVATE ${LIBC_WASI_P2_INCLUDE_DI find_package(Threads REQUIRED) target_link_libraries (canonical-execution ${LLVM_AVAILABLE_LIBS} ${WAMR_TEST_OPENSSL_LIBS} gmock gtest_main Threads::Threads) -target_compile_definitions(canonical-execution PRIVATE WAMR_BUILD_COMPONENT_MODEL=1) +target_compile_definitions(canonical-execution PRIVATE + WAMR_BUILD_COMPONENT_MODEL=1 + WAMR_UNIT_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" + CANONICAL_EXECUTION_TEST_DIR="${CMAKE_CURRENT_SOURCE_DIR}/test-dir" +) gtest_discover_tests(canonical-execution WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + PROPERTIES TIMEOUT 60 ) add_custom_command(TARGET canonical-execution POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/wasm-apps/*.wasm ${CMAKE_CURRENT_BINARY_DIR}/ -) \ No newline at end of file +) diff --git a/tests/unit/canonical-execution/test_canonical.cc b/tests/unit/canonical-execution/test_canonical.cc index ae424b7b81..1b3aadfaa9 100644 --- a/tests/unit/canonical-execution/test_canonical.cc +++ b/tests/unit/canonical-execution/test_canonical.cc @@ -25,27 +25,26 @@ class CanonicalExecutionTest : public testing::Test ~CanonicalExecutionTest() {} RuntimeInitArgs init_args; unsigned char *component_raw = NULL; - libc_wasi_parse_context_t parse_ctx; + libc_wasi_parse_context_t parse_ctx = {}; char error_buf[128]; char global_heap_buf[HEAP_SIZE]; // 100 MB bool runtime_init = false; - char input_path[PATH_MAX]; - char output_path[PATH_MAX]; + char input_path[PATH_MAX] = CANONICAL_EXECUTION_TEST_DIR "/test_input.txt"; + char output_path[PATH_MAX] = + CANONICAL_EXECUTION_TEST_DIR "/test_output.txt"; WASIContext *wasi_ctx; - char test_dir[128]; + char test_dir[PATH_MAX] = CANONICAL_EXECUTION_TEST_DIR; - int32_t dir_fd; char source_dir[8] = "/source"; char dest_dir[6] = "/dest"; char cwd_path[2] = "/"; - char path[PATH_MAX] = ""; - - int log_file_fd; + int log_file_fd = -1; + pid_t child_pid = -1; virtual void SetUp() { printf("Starting setup\n"); @@ -68,94 +67,106 @@ class CanonicalExecutionTest : public testing::Test bool success = instantiate_host_resource_table(); ASSERT_TRUE(success); - char cwd[PATH_MAX]; - getcwd(cwd, sizeof(cwd)); - const char *substr = "wasm-micro-runtime"; - char *pos = strstr(cwd, substr); - ASSERT_TRUE(pos != NULL) << "Could not find 'wasm-micro-runtime' in cwd"; - size_t prefix_len = (pos - cwd) + strlen(substr); - strncat(path, cwd, prefix_len); - strcat(path, "/tests/unit/canonical-execution/test-dir/"); - strcat(input_path, path); - strcat(output_path, path); - strcat(input_path, "test_input.txt"); - strcat(output_path, "test_output.txt"); + FILE *input_file = fopen(input_path, "r"); + ASSERT_NE(input_file, nullptr) << strerror(errno); + fclose(input_file); + + FILE *output_file = fopen(output_path, "w"); + ASSERT_NE(output_file, nullptr) << strerror(errno); + fclose(output_file); printf("Ending setup\n"); } virtual void TearDown() { printf("Starting teardown\n"); + StopChild(); destroy_host_resource_table(); if (runtime_init) { printf("Starting to destroy runtime\n"); wasm_runtime_destroy(); runtime_init = false; } - - fclose(fopen(output_path, "w")); - dup2(log_file_fd, STDOUT_FILENO); // return output to original log file + + fflush(stdout); + if (log_file_fd >= 0) { + dup2(log_file_fd, + STDOUT_FILENO); // return output to original log file + close(log_file_fd); + log_file_fd = -1; + } printf("Ending teardown\n"); } - WASMComponent* LoadfromCandidates(const char *file_name) { - return load_component_from_candidates_internal(file_name, "canonical-execution"); - } + void StopChild() + { + if (child_pid <= 0) + return; - void get_test_dir() { - char cwd[PATH_MAX]; - getcwd(cwd, sizeof(cwd)); - const char *substr = "wasm-micro-runtime"; - char *pos = strstr(cwd, substr); - if (!pos) { - printf("Could not find 'wasm-micro-runtime' in cwd\n"); - return; - } - size_t prefix_len = (pos - cwd) + strlen(substr); - test_dir[0] = '\0'; - strncat(test_dir, cwd, prefix_len); - strcat(test_dir, "/tests/unit/canonical-execution/test-dir"); + (void)kill(child_pid, SIGTERM); + while (waitpid(child_pid, nullptr, 0) == -1 && errno == EINTR) { + } + child_pid = -1; } - void init_prestats(WASMComponentInstance *comp_instance) { - - get_test_dir(); - - WASIContext *wasi_ctx = wasm_runtime_get_wasi_ctx((WASMModuleInstanceCommon *)comp_instance->core_module_instances[0]); - wasi_ctx->prestats = (struct fd_prestats *)wasm_runtime_malloc( - sizeof(struct fd_prestats)); - ASSERT_NE(wasi_ctx->prestats, nullptr); - memset(wasi_ctx->prestats, 0, sizeof(struct fd_prestats)); - wasi_ctx->prestats->size = 10; - wasi_ctx->prestats->prestats = (struct fd_prestat *)wasm_runtime_malloc( - 10 * sizeof(struct fd_prestat)); - ASSERT_NE(wasi_ctx->prestats->prestats, nullptr); - memset(wasi_ctx->prestats->prestats, 0, 10 * sizeof(struct fd_prestat)); - - char source_path[PATH_MAX]; - source_path[0] = '\0'; - strcat(source_path, test_dir); - strcat(source_path, source_dir); - int32_t source_dir_fd = open(source_path, O_RDONLY | O_DIRECTORY); - wasi_ctx->prestats->prestats[source_dir_fd].dir = source_dir; - - fd_table_insert_existing(wasi_ctx->curfds, source_dir_fd, source_dir_fd, false); - - char dest_path[PATH_MAX]; - dest_path[0] = '\0'; - strcat(dest_path, test_dir); - strcat(dest_path, dest_dir); - int32_t dest_dir_fd = open(dest_path, O_RDONLY | O_DIRECTORY); - wasi_ctx->prestats->prestats[dest_dir_fd].dir = dest_dir; - - fd_table_insert_existing(wasi_ctx->curfds, dest_dir_fd, dest_dir_fd, false); - - int32_t cwd_fd = open(test_dir, O_RDONLY | O_DIRECTORY); - wasi_ctx->prestats->prestats[cwd_fd].dir = cwd_path; + WASMComponent *LoadfromCandidates(const char *file_name) + { + return load_component_from_candidates_internal(file_name, + "canonical-execution"); + } - fd_table_insert_existing(wasi_ctx->curfds, cwd_fd, cwd_fd, false); + void ExpectUnsupportedWasiFixture(const char *file_name) + { + WASMComponent *component = LoadfromCandidates(file_name); + ASSERT_NE(component, nullptr) + << "Failed to load/parse component from candidates."; + + libc_component_wasi_init(component, 0, NULL, &parse_ctx); + memset(error_buf, 0, sizeof(error_buf)); + + WASMComponentInstance *comp_instance = + wasm_component_instantiate_internal(component, NULL, error_buf, + sizeof(error_buf)); + ASSERT_EQ(comp_instance, nullptr) + << file_name << " unexpectedly instantiated against WASI 0.2.6"; + ASSERT_NE(strstr(error_buf, "Incompatible WASI version"), nullptr) + << error_buf; + ASSERT_NE(strstr(error_buf, "@0.2.9"), nullptr) << error_buf; + } + void init_prestats(WASMComponentInstance *comp_instance) + { + WASIContext *wasi_ctx = wasm_runtime_get_wasi_ctx( + (WASMModuleInstanceCommon *) + comp_instance->core_module_instances[0]); + char source_path[PATH_MAX]; + source_path[0] = '\0'; + strcat(source_path, test_dir); + strcat(source_path, source_dir); + int32_t source_dir_fd = open(source_path, O_RDONLY | O_DIRECTORY); + ASSERT_NE(source_dir_fd, -1) << strerror(errno); + ASSERT_TRUE( + fd_prestats_insert(wasi_ctx->prestats, source_dir, source_dir_fd)); + ASSERT_TRUE(fd_table_insert_existing(wasi_ctx->curfds, source_dir_fd, + source_dir_fd, false)); + + char dest_path[PATH_MAX]; + dest_path[0] = '\0'; + strcat(dest_path, test_dir); + strcat(dest_path, dest_dir); + int32_t dest_dir_fd = open(dest_path, O_RDONLY | O_DIRECTORY); + ASSERT_NE(dest_dir_fd, -1) << strerror(errno); + ASSERT_TRUE( + fd_prestats_insert(wasi_ctx->prestats, dest_dir, dest_dir_fd)); + ASSERT_TRUE(fd_table_insert_existing(wasi_ctx->curfds, dest_dir_fd, + dest_dir_fd, false)); + + int32_t cwd_fd = open(test_dir, O_RDONLY | O_DIRECTORY); + ASSERT_NE(cwd_fd, -1) << strerror(errno); + ASSERT_TRUE(fd_prestats_insert(wasi_ctx->prestats, cwd_path, cwd_fd)); + ASSERT_TRUE( + fd_table_insert_existing(wasi_ctx->curfds, cwd_fd, cwd_fd, false)); } }; @@ -271,190 +282,24 @@ TEST_F(CanonicalExecutionTest, test_surface) ASSERT_TRUE(status); } -TEST_F(CanonicalExecutionTest, test_wasi_random) +// These Rust fixtures were built with rustc 1.94.1 and import WASI 0.2.9. +// Keep them as negative compatibility coverage while the executable Preview 2 +// tests below and the Apple fixture exercise the implemented 0.2.0-0.2.6 line. +TEST_F(CanonicalExecutionTest, rejects_wasi_0_2_9_random) { - bool status; - - WASMComponent *component = LoadfromCandidates("wasi_random.wasm"); - ASSERT_NE(component, nullptr) << "Failed to load/parse component from candidates."; - - ASSERT_TRUE(component); - libc_component_wasi_init(component, 0, NULL, &parse_ctx); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - // Test component is instantiated - WASMComponentInstance *comp_instance = wasm_component_instantiate_internal(component, NULL, error_buf, sizeof(error_buf)); - ASSERT_TRUE(comp_instance); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - - char func_name[] = "run()"; - status = wasm_component_application_execute_func(comp_instance, func_name); - ASSERT_TRUE(status); + ExpectUnsupportedWasiFixture("wasi_random.wasm"); } -TEST_F(CanonicalExecutionTest, test_wasi_clocks) +TEST_F(CanonicalExecutionTest, rejects_wasi_0_2_9_clocks) { - bool status; - - WASMComponent *component = LoadfromCandidates("wasi_clocks.wasm"); - ASSERT_NE(component, nullptr) << "Failed to load/parse component from candidates."; - - ASSERT_TRUE(component); - libc_component_wasi_init(component, 0, NULL, &parse_ctx); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - // Test component is instantiated - WASMComponentInstance *comp_instance = wasm_component_instantiate_internal(component, NULL, error_buf, sizeof(error_buf)); - ASSERT_TRUE(comp_instance); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - - char func_name[] = "run()"; - status = wasm_component_application_execute_func(comp_instance, func_name); - ASSERT_TRUE(status); -} - - -TEST_F(CanonicalExecutionTest, test_wasi_cli) -{ - bool status; - - WASMComponent *component = LoadfromCandidates("wasi_cli.wasm"); - ASSERT_NE(component, nullptr) << "Failed to load/parse component from candidates."; - - ASSERT_TRUE(component); - libc_component_wasi_init(component, 0, NULL, &parse_ctx); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - // Test component is instantiated - WASMComponentInstance *comp_instance = wasm_component_instantiate_internal(component, NULL, error_buf, sizeof(error_buf)); - ASSERT_TRUE(comp_instance); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - - // Redirect stdin and stdout - FILE *input_file = freopen(input_path, "r", stdin); - ASSERT_TRUE(input_file); - - FILE *output_file = freopen(output_path, "w", stdout); - ASSERT_TRUE(output_file); - - // Call function with no arguments - char func_name[] = "run()"; - status = wasm_component_application_execute_func(comp_instance, func_name); - ASSERT_TRUE(status); - - FILE *output = fopen(output_path, "r"); - ASSERT_TRUE(output); - - char line[256]; - fgets(line, sizeof(line), output); - ASSERT_TRUE(strstr(line, "Arguments (0):")); - fgets(line, sizeof(line), output); - ASSERT_TRUE(strstr(line, "Environment variables (0):")); + ExpectUnsupportedWasiFixture("wasi_clocks.wasm"); } -TEST_F(CanonicalExecutionTest, test_wasi_cli_with_args) +TEST_F(CanonicalExecutionTest, rejects_wasi_0_2_9_cli) { - bool status; - - WASMComponent *component = LoadfromCandidates("wasi_cli.wasm"); - ASSERT_NE(component, nullptr) << "Failed to load/parse component from candidates."; - - ASSERT_TRUE(component); - char *argv = (char *)"arg1"; - - parse_ctx.env_list[0] = "MY_VAR=hello"; - parse_ctx.env_list[1] = "ANOTHER=world"; - parse_ctx.env_list_size = 2; - - ASSERT_FALSE(libc_wasi_parse_options("inherit-env=n", &parse_ctx)); - - libc_component_wasi_init(component, 1, &argv, &parse_ctx); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - // Test component is instantiated - WASMComponentInstance *comp_instance = wasm_component_instantiate_internal(component, NULL, error_buf, sizeof(error_buf)); - ASSERT_TRUE(comp_instance); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - - // Redirect stdin and stdout - FILE *input_file = freopen(input_path, "r", stdin); - ASSERT_TRUE(input_file); - - FILE *output_file = freopen(output_path, "w", stdout); - ASSERT_TRUE(output_file); - - // Call function with no arguments - char func_name[] = "run()"; - status = wasm_component_application_execute_func(comp_instance, func_name); - ASSERT_TRUE(status); - - FILE *output = fopen(output_path, "r"); - ASSERT_TRUE(output); - - char line[256][256]; - uint32_t i = 0; - while(fgets(line[i], sizeof(line[i]), output)) i++; - - ASSERT_TRUE(strstr(line[0], "Arguments (1):")); - ASSERT_TRUE(strstr(line[1], "arg1")); - ASSERT_TRUE(strstr(line[2], "Environment variables (2):")); - ASSERT_EQ(i, 13); + ExpectUnsupportedWasiFixture("wasi_cli.wasm"); } -TEST_F(CanonicalExecutionTest, test_wasi_cli_inherit_env) -{ - bool status; - - WASMComponent *component = LoadfromCandidates("wasi_cli.wasm"); - ASSERT_NE(component, nullptr) << "Failed to load/parse component from candidates."; - - ASSERT_TRUE(component); - char *argv = (char *)"arg1"; - - parse_ctx.env_list[0] = "MY_VAR=hello"; - parse_ctx.env_list[1] = "ANOTHER=world"; - parse_ctx.env_list_size = 2; - - ASSERT_FALSE(libc_wasi_parse_options("inherit-env=y", &parse_ctx)); - - libc_component_wasi_init(component, 1, &argv, &parse_ctx); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - // Test component is instantiated - WASMComponentInstance *comp_instance = wasm_component_instantiate_internal(component, NULL, error_buf, sizeof(error_buf)); - ASSERT_TRUE(comp_instance); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - - // Redirect stdin and stdout - FILE *input_file = freopen(input_path, "r", stdin); - ASSERT_TRUE(input_file); - - FILE *output_file = freopen(output_path, "w", stdout); - ASSERT_TRUE(output_file); - - // Call function with no arguments - char func_name[] = "run()"; - status = wasm_component_application_execute_func(comp_instance, func_name); - ASSERT_TRUE(status); - - FILE *output = fopen(output_path, "r"); - ASSERT_TRUE(output); - - char line[256][256]; - uint32_t i = 0; - while(fgets(line[i], sizeof(line[i]), output)) i++; - - ASSERT_TRUE(strstr(line[0], "Arguments (1):")); - ASSERT_TRUE(strstr(line[1], "arg1")); - ASSERT_TRUE(i > 13); -} - - TEST_F(CanonicalExecutionTest, test_tcp_server) { bool status; @@ -485,52 +330,54 @@ TEST_F(CanonicalExecutionTest, test_tcp_server) printf("[Setup] Cleaning up port %d...\n", port); system(command); - pid_t pid = fork(); + child_pid = fork(); - ASSERT_TRUE(pid >= 0); + ASSERT_TRUE(child_pid >= 0); - if (pid == 0) { - // --- CHILD PROCESS: tcp-server --- - char func_name[] = "run()"; - wasm_component_application_execute_func(comp_instance, func_name); + if (child_pid == 0) { + // --- CHILD PROCESS: tcp-server --- + alarm(30); + char func_name[] = "run()"; + bool child_status = + wasm_component_application_execute_func(comp_instance, func_name); + _exit(child_status ? 0 : 1); + } + else { - } else { + usleep(500000); + printf("[Parent] Connecting via native sockets...\n"); - usleep(500000); - printf("[Parent] Connecting via native sockets...\n"); + // Create socket, configure address, and connect + int sock = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_TRUE(sock >= 0); - // Create socket, configure address, and connect - int sock = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_TRUE(sock >= 0); + struct sockaddr_in serv_addr; + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(7878); + inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr); - struct sockaddr_in serv_addr; - serv_addr.sin_family = AF_INET; - serv_addr.sin_port = htons(7878); - inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr); + // Connect to the server + int connection_status = + connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); + printf("Errno: %d\n", errno); - // Connect to the server - int connection_status = connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); - printf("Errno: %d\n", errno); + ASSERT_TRUE(connection_status == 0); - ASSERT_TRUE(connection_status == 0); + // Send 2 messages + const char *msg1 = "Message 1: Hello Server!\n"; + send(sock, msg1, strlen(msg1), 0); - // Send 2 messages - const char *msg1 = "Message 1: Hello Server!\n"; - send(sock, msg1, strlen(msg1), 0); + sleep(1); - sleep(1); + const char *msg2 = "Message 2: Testing complete.\n"; + send(sock, msg2, strlen(msg2), 0); + sleep(1); - const char *msg2 = "Message 2: Testing complete.\n"; - send(sock, msg2, strlen(msg2), 0); - sleep(1); - - // Cleanup - kill(pid, SIGTERM); - waitpid(pid, NULL, 0); - - printf("[Parent] Messages sent. Cleaning up...\n"); + // Cleanup + StopChild(); - } + printf("[Parent] Messages sent. Cleaning up...\n"); + } FILE *output = fopen(output_path, "r"); ASSERT_TRUE(output); @@ -548,73 +395,10 @@ TEST_F(CanonicalExecutionTest, test_tcp_server) } -TEST_F(CanonicalExecutionTest, test_udp_server) +TEST_F(CanonicalExecutionTest, rejects_wasi_0_2_9_udp_components) { - bool status; - - WASMComponent *component_1 = LoadfromCandidates("udp_server_2.wasm"); - ASSERT_NE(component_1, nullptr) << "Failed to load/parse component from candidates."; - - libc_component_wasi_init(component_1, 0, NULL, &parse_ctx); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - // Test component is instantiated - WASMComponentInstance *comp_instance_1 = wasm_component_instantiate_internal(component_1, NULL, error_buf, sizeof(error_buf)); - ASSERT_TRUE(comp_instance_1); - - WASMComponent *component_2 = LoadfromCandidates("udp_client_2.wasm"); - ASSERT_NE(component_2, nullptr) << "Failed to load/parse component from candidates."; - - ASSERT_TRUE(component_2); - char *argv[2] = {(char *)"-n", (char *)"5"}; - - libc_component_wasi_init(component_2, 2, argv, &parse_ctx); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - // Test component is instantiated - WASMComponentInstance *comp_instance_2 = wasm_component_instantiate_internal(component_2, NULL, error_buf, sizeof(error_buf)); - ASSERT_TRUE(comp_instance_2); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - - char func_name[] = "run()"; - - pid_t pid = fork(); - - ASSERT_TRUE(pid >= 0); - - if (pid == 0) { - - wasm_component_application_execute_func(comp_instance_1, func_name); - - } else { - - // Redirect client output to log file - FILE *output_file = freopen(output_path, "w", stdout); - ASSERT_TRUE(output_file); - // --- Parent PROCESS: udp-client --- - char line[256]; - - usleep(500000); // 0.5 seconds - - wasm_component_application_execute_func(comp_instance_2, func_name); - kill(pid, SIGTERM); - waitpid(pid, NULL, 0); - - } - - FILE *output = fopen(output_path, "r"); - ASSERT_TRUE(output); - - char line[256][256]; - uint32_t i = 0; - while(fgets(line[i], sizeof(line[i]), output)) i++; - - ASSERT_TRUE(strstr(line[0], "Sending 5 messages to 127.0.0.1:9090")); // client connected and sending to server - ASSERT_TRUE(strstr(line[2], "Received echo: Message 1/5")); // first message received by server - ASSERT_TRUE(strstr(line[10], "Received echo: Message 5/5")); // last message received by server - ASSERT_TRUE(strstr(line[11], "Done")); // Transmission done - + ExpectUnsupportedWasiFixture("udp_server_2.wasm"); + ExpectUnsupportedWasiFixture("udp_client_2.wasm"); } TEST_F(CanonicalExecutionTest, test_wasi_filesystem) @@ -644,44 +428,9 @@ TEST_F(CanonicalExecutionTest, test_wasi_filesystem) ASSERT_TRUE(status); } -TEST_F(CanonicalExecutionTest, test_wasi_ip_name_lookup) +TEST_F(CanonicalExecutionTest, rejects_wasi_0_2_9_ip_name_lookup) { - bool status; - - WASMComponent *component = LoadfromCandidates("ip_name_lookup_local.wasm"); - ASSERT_NE(component, nullptr) << "Failed to load/parse component from candidates."; - - ASSERT_TRUE(component); - - libc_component_wasi_init(component, 0, NULL, &parse_ctx); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - // Test component is instantiated - WASMComponentInstance *comp_instance = wasm_component_instantiate_internal(component, NULL, error_buf, sizeof(error_buf)); - ASSERT_TRUE(comp_instance); - - init_prestats(comp_instance); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - - // Redirect output to log file - FILE *output_file = freopen(output_path, "w", stdout); - ASSERT_TRUE(output_file); - - char func_name[] = "run()"; - status = wasm_component_application_execute_func(comp_instance, func_name); - ASSERT_TRUE(status); - - FILE *output = fopen(output_path, "r"); - ASSERT_TRUE(output); - - char line[256][256]; - uint32_t i = 0; - while(fgets(line[i], sizeof(line[i]), output)) i++; - - ASSERT_TRUE(strstr(line[0], "Resolving: localhost")); - ASSERT_TRUE(strstr(line[1], "-> 127.0.0.1")); // address found - ASSERT_TRUE(strstr(line[2], "Resolved 1 address(es) for localhost")); + ExpectUnsupportedWasiFixture("ip_name_lookup_local.wasm"); } // Test apps made with C WASi-SDK @@ -839,50 +588,52 @@ TEST_F(CanonicalExecutionTest, test_tcp_server_c) printf("[Setup] Cleaning up port %d...\n", port); system(command); - pid_t pid = fork(); - - ASSERT_TRUE(pid >= 0); + child_pid = fork(); - if (pid == 0) { - // --- CHILD PROCESS: tcp-server --- - char func_name[] = "run()"; - wasm_component_application_execute_func(comp_instance, func_name); - - } else { - usleep(500000); + ASSERT_TRUE(child_pid >= 0); - printf("[Parent] Connecting via native sockets...\n"); + if (child_pid == 0) { + // --- CHILD PROCESS: tcp-server --- + alarm(30); + char func_name[] = "run()"; + bool child_status = + wasm_component_application_execute_func(comp_instance, func_name); + _exit(child_status ? 0 : 1); + } + else { + usleep(500000); - // Create socket, configure address, and connect - int sock = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_TRUE(sock >= 0); + printf("[Parent] Connecting via native sockets...\n"); - struct sockaddr_in serv_addr; - serv_addr.sin_family = AF_INET; - serv_addr.sin_port = htons(8080); - inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr); + // Create socket, configure address, and connect + int sock = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_TRUE(sock >= 0); - // Connect to the server - int connection_status = connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); - ASSERT_TRUE(connection_status == 0); + struct sockaddr_in serv_addr; + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(8080); + inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr); - // Send 2 messages - const char *msg1 = "Message 1: Hello Server!\n"; - send(sock, msg1, strlen(msg1), 0); + // Connect to the server + int connection_status = + connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); + ASSERT_TRUE(connection_status == 0); - sleep(1); + // Send 2 messages + const char *msg1 = "Message 1: Hello Server!\n"; + send(sock, msg1, strlen(msg1), 0); - const char *msg2 = "Message 2: Testing complete.\n"; - send(sock, msg2, strlen(msg2), 0); - sleep(1); + sleep(1); - // Cleanup - kill(pid, SIGTERM); - waitpid(pid, NULL, 0); + const char *msg2 = "Message 2: Testing complete.\n"; + send(sock, msg2, strlen(msg2), 0); + sleep(1); - printf("[Parent] Messages sent. Cleaning up...\n"); + // Cleanup + StopChild(); - } + printf("[Parent] Messages sent. Cleaning up...\n"); + } } @@ -912,30 +663,34 @@ TEST_F(CanonicalExecutionTest, test_udp_server_c) printf("Here\n"); fflush(stdout); - pid_t pid = fork(); + child_pid = fork(); - ASSERT_TRUE(pid >= 0); + ASSERT_TRUE(child_pid >= 0); - if (pid == 0) { + if (child_pid == 0) { - char command[128]; - // This command finds the PID using the port and kills it. - // We redirect stderr to /dev/null so it doesn't complain if the port is already free. - uint32_t port = 8080; - sprintf(command, "lsof -t -i:%d | xargs kill -9 > /dev/null 2>&1", port); + alarm(30); + char command[128]; + // This command finds the PID using the port and kills it. + // We redirect stderr to /dev/null so it doesn't complain if the port is + // already free. + uint32_t port = 8080; + sprintf(command, "lsof -t -i:%d | xargs kill -9 > /dev/null 2>&1", port); - sleep(1); + sleep(1); - printf("[Setup] Cleaning up port %d...\n", port); - system(command); + printf("[Setup] Cleaning up port %d...\n", port); + system(command); - fflush(stdout); + fflush(stdout); - printf("running server\n"); - fflush(stdout); + printf("running server\n"); + fflush(stdout); // --- CHILD PROCESS: udp-server --- char func_name[] = "run()"; - wasm_component_application_execute_func(comp_instance, func_name); + bool child_status = + wasm_component_application_execute_func(comp_instance, func_name); + _exit(child_status ? 0 : 1); } else { usleep(500000); @@ -961,48 +716,14 @@ TEST_F(CanonicalExecutionTest, test_udp_server_c) sleep(1); - kill(pid, SIGTERM); - waitpid(pid, NULL, 0); + StopChild(); printf("[Parent] Messages sent. Cleaning up...\n"); } } -TEST_F(CanonicalExecutionTest, test_wasi_filesystem_sandboxing) +TEST_F(CanonicalExecutionTest, rejects_wasi_0_2_9_filesystem_sandbox) { - bool status; - const char* file_name = "test_filesystem_paths_comp.wasm"; - WASMComponent *component = LoadfromCandidates(file_name); - ASSERT_NE(component, nullptr) << "Failed to load/parse component from candidates."; - ASSERT_TRUE(component); - - ASSERT_FALSE(libc_wasi_parse_options("inherit-env=y", &parse_ctx)); - - char absolute_escaped_path[PATH_MAX]; - strcat(absolute_escaped_path, path); - strcat(absolute_escaped_path, "example.txt"); - - const char *wasm_argv[] = { - file_name, - "--", - absolute_escaped_path - }; - int wasm_argc = 3; - - libc_component_wasi_init(component, wasm_argc, (char **)wasm_argv, &parse_ctx); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - - WASMComponentInstance *comp_instance = wasm_component_instantiate_internal(component, NULL, error_buf, sizeof(error_buf)); - ASSERT_TRUE(comp_instance); - - init_prestats(comp_instance); - - bh_log_set_verbose_level(WASM_LOG_LEVEL_WARNING); - - char func_name[] = "run()"; - status = wasm_component_application_execute_func(comp_instance, func_name); - - ASSERT_TRUE(status); -} \ No newline at end of file + ExpectUnsupportedWasiFixture("test_filesystem_paths_comp.wasm"); +} diff --git a/tests/unit/component-execution/CMakeLists.txt b/tests/unit/component-execution/CMakeLists.txt index 070ba3101c..0c210b4671 100644 --- a/tests/unit/component-execution/CMakeLists.txt +++ b/tests/unit/component-execution/CMakeLists.txt @@ -15,8 +15,9 @@ set (WAMR_BUILD_COMPONENT_MODEL 1) set (WAMR_BUILD_APP_FRAMEWORK 1) set (WAMR_BUILD_AOT 0) set (WAMR_BUILD_INTERP 1) +set (WAMR_BUILD_FAST_INTERP 1) set (WAMR_BUILD_REF_TYPES 1) -set (WAMR_BUILD_MULTI_MODULE 0) +set (WAMR_BUILD_MULTI_MODULE 1) set (WAMR_BUILD_LOAD_CUSTOM_SECTION 1) set (WAMR_BUILD_SIMD 0) @@ -62,6 +63,9 @@ add_custom_command(TARGET component-execution POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/wasm-apps/*.wasm ${CMAKE_CURRENT_BINARY_DIR}/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/../component/wasm-apps/complex_with_host.wasm + ${CMAKE_CURRENT_BINARY_DIR}/complex_with_host.wasm ) -gtest_discover_tests(component-execution) \ No newline at end of file +gtest_discover_tests(component-execution) diff --git a/tests/unit/component-execution/test_component_host_memory.cc b/tests/unit/component-execution/test_component_host_memory.cc new file mode 100644 index 0000000000..b6f2ff64b4 --- /dev/null +++ b/tests/unit/component-execution/test_component_host_memory.cc @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2026 Matt Hargett. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include + +#include +#include +#include + +#include "helpers.h" + +namespace { + +enum class ProbeMode { + ValidRange, + InvalidRanges, +}; + +struct MemoryProbeState { + ProbeMode mode = ProbeMode::ValidRange; + wasm_exec_env_t callback_exec_env = nullptr; + uint32_t contents_offset = 0; + uint32_t contents_size = 0; + bool callback_called = false; + bool validate_succeeded = false; + bool mutable_succeeded = false; + bool const_succeeded = false; + bool pointers_match = false; + bool mutable_round_trip_succeeded = false; + bool empty_range_succeeded = false; + bool null_output_rejected = false; + bool bounds_rejected = false; + bool overflow_rejected = false; + bool failed_outputs_cleared = false; +}; + +MemoryProbeState *memory_probe_state; + +void +host_log(wasm_exec_env_t exec_env, uint64_t *canonical_cells) +{ + MemoryProbeState *state = memory_probe_state; + uint8_t *mutable_data = nullptr; + const uint8_t *const_data = nullptr; + uint32_t level = static_cast(canonical_cells[0]); + uint32_t contents_offset = static_cast(canonical_cells[1]); + uint32_t contents_size = static_cast(canonical_cells[2]); + + (void)level; + + if (state == nullptr) { + return; + } + + state->callback_called = true; + state->callback_exec_env = exec_env; + state->contents_offset = contents_offset; + state->contents_size = contents_size; + + if (state->mode == ProbeMode::InvalidRanges) { + auto *mutable_sentinel = reinterpret_cast(1); + const auto *const_sentinel = reinterpret_cast(1); + + state->bounds_rejected = + !wasm_component_validate_memory_range(exec_env, 0, UINT32_MAX) + && !wasm_component_get_memory_range(exec_env, 0, UINT32_MAX, + &mutable_sentinel); + state->overflow_rejected = + !wasm_component_validate_memory_range(exec_env, UINT32_MAX, 2) + && !wasm_component_get_memory_range_const(exec_env, UINT32_MAX, 2, + &const_sentinel); + state->failed_outputs_cleared = + mutable_sentinel == nullptr && const_sentinel == nullptr; + return; + } + + state->validate_succeeded = wasm_component_validate_memory_range( + exec_env, contents_offset, contents_size); + state->mutable_succeeded = wasm_component_get_memory_range( + exec_env, contents_offset, contents_size, &mutable_data); + state->const_succeeded = wasm_component_get_memory_range_const( + exec_env, contents_offset, contents_size, &const_data); + state->pointers_match = mutable_data != nullptr && const_data != nullptr + && mutable_data == const_data && contents_size > 0; + + if (state->pointers_match) { + uint8_t original = mutable_data[0]; + mutable_data[0] ^= 0x20; + state->mutable_round_trip_succeeded = + const_data[0] == static_cast(original ^ 0x20); + mutable_data[0] = original; + } + + if ((uint64_t)contents_offset + contents_size <= UINT32_MAX) { + state->empty_range_succeeded = wasm_component_validate_memory_range( + exec_env, contents_offset + contents_size, 0); + } + state->null_output_rejected = !wasm_component_get_memory_range( + exec_env, contents_offset, contents_size, nullptr); +} + +std::array host_symbols = { { + { "log", reinterpret_cast(host_log), "(iii)", nullptr }, +} }; + +class ComponentHostMemoryTest : public testing::Test +{ + protected: + void SetUp() override + { + helper = std::make_unique(); + helper->do_setup(); + ASSERT_TRUE(helper->runtime_init); + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "test:project/imported-interface@0.1.0", host_symbols.data(), + static_cast(host_symbols.size()))); + } + + void TearDown() override + { + memory_probe_state = nullptr; + helper->do_teardown(); + helper = nullptr; + } + + void LoadComponent() + { + ASSERT_TRUE(helper->read_wasm_file("complex_with_host.wasm")); + ASSERT_TRUE(helper->load_component()) << helper->error_buf; + ASSERT_TRUE(helper->instantiate_component()) << helper->error_buf; + } + + auto ExecuteProbe(MemoryProbeState *state) -> bool + { + uint32_t result_cell_count = 0; + auto *result_cells = + static_cast(wasm_runtime_malloc(sizeof(uint32_t) * 16)); + + if (result_cells == nullptr) { + return false; + } + + memory_probe_state = state; + bool succeeded = wasm_component_application_execute_func_ex( + helper->component_inst, const_cast("process-people([])"), + &result_cell_count, &result_cells); + memory_probe_state = nullptr; + wasm_runtime_free(result_cells); + return succeeded; + } + + private: + std::unique_ptr helper; +}; + +TEST_F(ComponentHostMemoryTest, ResolvesMutableAndConstCallbackRanges) +{ + MemoryProbeState state; + auto *stale_mutable = reinterpret_cast(1); + const auto *stale_const = reinterpret_cast(1); + + LoadComponent(); + EXPECT_TRUE(ExecuteProbe(&state)); + + EXPECT_TRUE(state.callback_called); + EXPECT_TRUE(state.validate_succeeded); + EXPECT_TRUE(state.mutable_succeeded); + EXPECT_TRUE(state.const_succeeded); + EXPECT_TRUE(state.pointers_match); + EXPECT_TRUE(state.mutable_round_trip_succeeded); + EXPECT_TRUE(state.empty_range_succeeded); + EXPECT_TRUE(state.null_output_rejected); + + ASSERT_NE(state.callback_exec_env, nullptr); + EXPECT_FALSE(wasm_component_validate_memory_range( + state.callback_exec_env, state.contents_offset, state.contents_size)); + EXPECT_FALSE(wasm_component_get_memory_range( + state.callback_exec_env, state.contents_offset, state.contents_size, + &stale_mutable)); + EXPECT_FALSE(wasm_component_get_memory_range_const( + state.callback_exec_env, state.contents_offset, state.contents_size, + &stale_const)); + EXPECT_EQ(stale_mutable, nullptr); + EXPECT_EQ(stale_const, nullptr); +} + +TEST_F(ComponentHostMemoryTest, RejectsBoundsOverflowAndWrongContext) +{ + MemoryProbeState state; + auto *null_context_output = reinterpret_cast(1); + + state.mode = ProbeMode::InvalidRanges; + LoadComponent(); + EXPECT_FALSE(ExecuteProbe(&state)); + + EXPECT_TRUE(state.callback_called); + EXPECT_TRUE(state.bounds_rejected); + EXPECT_TRUE(state.overflow_rejected); + EXPECT_TRUE(state.failed_outputs_cleared); + EXPECT_FALSE(wasm_component_validate_memory_range(nullptr, 0, 0)); + EXPECT_FALSE( + wasm_component_get_memory_range(nullptr, 0, 0, &null_context_output)); + EXPECT_EQ(null_context_output, nullptr); +} + +} /* namespace */ diff --git a/tests/unit/component-execution/test_execution.cc b/tests/unit/component-execution/test_execution.cc index 62e5bba8bd..98997d7a5d 100644 --- a/tests/unit/component-execution/test_execution.cc +++ b/tests/unit/component-execution/test_execution.cc @@ -7,6 +7,92 @@ #include "helpers.h" #include #include +#include + +namespace { +bool reject_runtime_allocations = false; +uint64_t runtime_allocation_attempts = 0; + +struct NestedHostResourceState { + uint32_t make_calls = 0; + uint32_t rep_calls = 0; + uint32_t drop_calls = 0; + uint32_t dropped_representation = 0; + bool saw_root_custom_data = false; +}; + +static constexpr uint32_t nested_host_representation = 0xC0DE; + +void +nested_host_make(wasm_exec_env_t exec_env, uint64_t *canonical_cells) +{ + auto *state = static_cast( + wasm_component_get_custom_data_from_exec_env(exec_env)); + uint32_t handle = 0; + + if (state) { + state->make_calls++; + state->saw_root_custom_data = true; + } + canonical_cells[0] = wasm_component_host_resource_new( + exec_env, "test:nested/host@1.0.0", "item", + nested_host_representation, &handle) + ? handle + : 0; +} + +void +nested_host_rep(wasm_exec_env_t exec_env, uint64_t *canonical_cells) +{ + auto *state = static_cast( + wasm_component_get_custom_data_from_exec_env(exec_env)); + uint32_t representation = 0; + + if (state) { + state->rep_calls++; + state->saw_root_custom_data = true; + } + canonical_cells[0] = + wasm_component_host_resource_rep( + exec_env, "test:nested/host@1.0.0", "item", + static_cast(canonical_cells[0]), &representation) + ? representation + : 0; +} + +bool +nested_host_drop(void *attachment, uint32_t representation) +{ + auto *state = static_cast(attachment); + + if (!state) { + return false; + } + state->drop_calls++; + state->dropped_representation = representation; + return true; +} + +void * +prepared_call_test_malloc(unsigned int size) +{ + runtime_allocation_attempts++; + return reject_runtime_allocations ? nullptr : std::malloc(size); +} + +void * +prepared_call_test_realloc(void *ptr, unsigned int size) +{ + runtime_allocation_attempts++; + return reject_runtime_allocations ? nullptr : std::realloc(ptr, size); +} + +void +prepared_call_test_free(void *ptr) +{ + std::free(ptr); +} +} // namespace class ComponentExecutionTest : public testing::Test { @@ -23,12 +109,14 @@ class ComponentExecutionTest : public testing::Test bool runtime_init = false; - virtual void SetUp() { + virtual void SetUp() + { helper = std::make_unique(); helper->do_setup(); } - virtual void TearDown() { + virtual void TearDown() + { helper->do_teardown(); helper = nullptr; } @@ -51,10 +139,13 @@ TEST_F(ComponentExecutionTest, TestAddWASM) uint32 *argv1 = (uint32 *)wasm_runtime_malloc(sizeof(uint32) * 10); ASSERT_TRUE(argv1 != NULL); - ret = wasm_component_application_execute_func_ex(this->helper->component_inst, (char*)"add(3,4)", &argc1, &argv1); + ret = wasm_component_application_execute_func_ex( + this->helper->component_inst, (char *)"add(3,4)", &argc1, &argv1); // Check results - bool function_succeeded = ret && !wasm_component_runtime_get_exception(this->helper->component_inst); + bool function_succeeded = + ret + && !wasm_component_runtime_get_exception(this->helper->component_inst); uint32 result = 0; if (function_succeeded && argv1) { @@ -67,11 +158,181 @@ TEST_F(ComponentExecutionTest, TestAddWASM) argv1 = NULL; } - ASSERT_TRUE(function_succeeded); ASSERT_GT(argc1, 0U); // Should have at least one result cell - ASSERT_EQ(result, 7U); // Result should be 7 + ASSERT_EQ(result, 7U); // Result should be 7 +} + +class ComponentPreparedCallAllocationTest : public testing::Test +{ + public: + ComponentHelper helper; + WASMComponentPreparedCall *prepared = nullptr; + char error_buf[128] = {}; + + void SetUp() override + { + RuntimeInitArgs init_args = {}; + init_args.mem_alloc_type = Alloc_With_Allocator; + init_args.mem_alloc_option.allocator.malloc_func = + reinterpret_cast(prepared_call_test_malloc); + init_args.mem_alloc_option.allocator.realloc_func = + reinterpret_cast(prepared_call_test_realloc); + init_args.mem_alloc_option.allocator.free_func = + reinterpret_cast(prepared_call_test_free); + ASSERT_TRUE(wasm_runtime_full_init(&init_args)); + helper.runtime_init = true; + } + + void TearDown() override + { + reject_runtime_allocations = false; + if (prepared) { + wasm_component_prepared_call_post_return(prepared); + wasm_component_destroy_prepared_call(prepared); + prepared = nullptr; + } + helper.do_teardown(); + } +}; + +// Prepared flat calls avoid WAVE parsing, WIT-value lifting/lowering, and +// adapter allocation after the export and execution environment are cached. +TEST_F(ComponentPreparedCallAllocationTest, + TestPreparedFlatAddRepeatedWithoutAllocations) +{ + static constexpr const char *interface_name = + "test:project/my-interface@0.1.0"; + + ASSERT_TRUE(helper.read_wasm_file("add.wasm")); + ASSERT_TRUE(helper.load_component()); + ASSERT_TRUE(helper.instantiate_component()); + + wasm_val_t args[2] = {}; + wasm_val_t results[1] = {}; + args[0].kind = WASM_I32; + args[1].kind = WASM_I32; + + // Preserve the original leaf-name preparation path for compatibility. + prepared = wasm_component_prepare_export_call(helper.component_inst, "add", + error_buf, sizeof(error_buf)); + ASSERT_NE(prepared, nullptr) << error_buf; + args[0].of.i32 = 3; + args[1].of.i32 = 4; + ASSERT_TRUE(wasm_component_call_prepared(prepared, 1, results, 2, args)); + ASSERT_EQ(results[0].of.i32, 7); + ASSERT_TRUE(wasm_component_prepared_call_post_return(prepared)); + wasm_component_destroy_prepared_call(prepared); + prepared = nullptr; + + WASMComponentPreparedCall *wrong_interface = + wasm_component_prepare_export_call_qualified( + helper.component_inst, "test:project/my-interface@0.2.0", "add", + error_buf, sizeof(error_buf)); + ASSERT_EQ(wrong_interface, nullptr); + ASSERT_NE(strstr(error_buf, "qualified prepared export lookup failed"), + nullptr); + + prepared = wasm_component_prepare_export_call_qualified( + helper.component_inst, interface_name, "add", error_buf, + sizeof(error_buf)); + ASSERT_NE(prepared, nullptr) << error_buf; + + runtime_allocation_attempts = 0; + reject_runtime_allocations = true; + + for (uint32_t i = 0; i < 10000; i++) { + args[0].of.i32 = static_cast(i); + args[1].of.i32 = 7; + ASSERT_TRUE( + wasm_component_call_prepared(prepared, 1, results, 2, args)); + ASSERT_EQ(results[0].kind, WASM_I32); + ASSERT_EQ(results[0].of.i32, static_cast(i + 7)); + ASSERT_TRUE(wasm_component_prepared_call_post_return(prepared)); + } + + reject_runtime_allocations = false; + EXPECT_EQ(runtime_allocation_attempts, 0U); + wasm_component_destroy_prepared_call(prepared); + prepared = nullptr; +} + +TEST_F(ComponentExecutionTest, NestedHostResourceUsesCallingComponentTable) +{ + NativeSymbol host_symbols[] = { + { "make", reinterpret_cast(nested_host_make), "()i", nullptr }, + { "rep", reinterpret_cast(nested_host_rep), "(i)i", nullptr }, + }; + NestedHostResourceState state; + + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "test:nested/host@1.0.0", host_symbols, + static_cast(sizeof(host_symbols) / sizeof(host_symbols[0])))); + ASSERT_TRUE(helper->read_wasm_file("nested_host_resource.wasm")); + ASSERT_TRUE(helper->load_component()); + ASSERT_TRUE(wasm_component_register_host_resource_drop_callback( + helper->component, "test:nested/host@1.0.0", "item", nested_host_drop)); + + struct InstantiationArgs2 *args = nullptr; + ASSERT_TRUE(wasm_runtime_instantiation_args_create(&args)); + ASSERT_NE(args, nullptr); + wasm_runtime_instantiation_args_set_default_stack_size(args, 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size(args, 64 * 1024); + wasm_runtime_instantiation_args_set_custom_data(args, &state); + helper->component_inst = wasm_component_instantiate_ex2( + helper->component, args, helper->error_buf, sizeof(helper->error_buf)); + wasm_runtime_instantiation_args_destroy(args); + ASSERT_NE(helper->component_inst, nullptr) << helper->error_buf; + helper->component_instantiated = true; + + WASMComponentPreparedCall *call = wasm_component_prepare_export_call( + helper->component_inst, "round-trip", helper->error_buf, + sizeof(helper->error_buf)); + ASSERT_NE(call, nullptr) << helper->error_buf; + wasm_val_t result = {}; + ASSERT_TRUE(wasm_component_call_prepared(call, 1, &result, 0, nullptr)); + EXPECT_TRUE(wasm_component_prepared_call_post_return(call)); + wasm_component_destroy_prepared_call(call); + + EXPECT_EQ(result.kind, WASM_I32); + EXPECT_EQ(result.of.i32, static_cast(nested_host_representation)); + EXPECT_EQ(state.make_calls, 1U); + EXPECT_EQ(state.rep_calls, 1U); + EXPECT_EQ(state.drop_calls, 1U); + EXPECT_EQ(state.dropped_representation, nested_host_representation); + EXPECT_TRUE(state.saw_root_custom_data); +} + +TEST_F(ComponentExecutionTest, TestRepeatedLoadExecuteAndUnload) +{ + for (uint32_t i = 0; i < 8; i++) { + ASSERT_TRUE(helper->read_wasm_file("add.wasm")); + ASSERT_TRUE(helper->load_component()); + ASSERT_TRUE(helper->instantiate_component()); + + uint32 argc = 0; + uint32 *argv = (uint32 *)wasm_runtime_malloc(sizeof(uint32) * 10); + ASSERT_NE(argv, nullptr); + + bool ret = wasm_component_application_execute_func_ex( + helper->component_inst, (char *)"add(3,4)", &argc, &argv); + ASSERT_TRUE(ret); + ASSERT_EQ(wasm_component_runtime_get_exception(helper->component_inst), + nullptr); + ASSERT_GT(argc, 0U); + EXPECT_EQ(argv[0], 7U); + + wasm_runtime_free(argv); + wasm_component_deinstantiate(helper->component_inst); + helper->component_inst = nullptr; + helper->component_instantiated = false; + helper->reset_component(); + if (helper->component_raw) { + BH_FREE(helper->component_raw); + helper->component_raw = nullptr; + } + } } // Call non-existent function @@ -91,15 +352,20 @@ TEST_F(ComponentExecutionTest, TestCallNonExistentFunction) ASSERT_TRUE(argv1 != NULL); // Call non-existent function - ret = wasm_component_application_execute_func_ex(this->helper->component_inst, (char*)"random_func(3,4)", &argc1, &argv1); + ret = wasm_component_application_execute_func_ex( + this->helper->component_inst, (char *)"random_func(3,4)", &argc1, + &argv1); // Should fail ASSERT_FALSE(ret); // Should have exception message - const char* exception = wasm_component_runtime_get_exception(this->helper->component_inst); + const char *exception = + wasm_component_runtime_get_exception(this->helper->component_inst); ASSERT_TRUE(exception != NULL); - ASSERT_TRUE(strstr(exception, "Exception: lookup function random_func failed") != NULL); + ASSERT_TRUE( + strstr(exception, "Exception: lookup function random_func failed") + != NULL); // Cleanup wasm_runtime_free(argv1); @@ -117,7 +383,6 @@ TEST_F(ComponentExecutionTest, TestCallWithWrongParameterCount) ret = helper->instantiate_component(); ASSERT_TRUE(ret); - uint32 argc1 = 0; uint32 *argv1 = (uint32 *)wasm_runtime_malloc(sizeof(uint32) * 10); ASSERT_TRUE(argv1 != NULL); @@ -130,9 +395,12 @@ TEST_F(ComponentExecutionTest, TestCallWithWrongParameterCount) ASSERT_FALSE(ret); // Should have exception message about argument count - const char* exception = wasm_component_runtime_get_exception(this->helper->component_inst); + const char *exception = + wasm_component_runtime_get_exception(this->helper->component_inst); ASSERT_TRUE(exception != NULL); - ASSERT_TRUE(strstr(exception, "This method waited 2 arguments, but received 3\n") != NULL); + ASSERT_TRUE( + strstr(exception, "This method waited 2 arguments, but received 3\n") + != NULL); // Cleanup wasm_runtime_free(argv1); @@ -155,13 +423,15 @@ TEST_F(ComponentExecutionTest, TestIntDivideByZeroTrap) ASSERT_TRUE(argv1 != NULL); // Call div with divisor = 0 - ret = wasm_component_application_execute_func_ex(this->helper->component_inst, (char *)"div(10, 0)", &argc1, &argv1); + ret = wasm_component_application_execute_func_ex( + this->helper->component_inst, (char *)"div(10, 0)", &argc1, &argv1); // Should fail due to trap ASSERT_FALSE(ret); // Should have exception message about the trap - const char* exception = wasm_component_runtime_get_exception(this->helper->component_inst); + const char *exception = + wasm_component_runtime_get_exception(this->helper->component_inst); ASSERT_TRUE(exception != NULL); ASSERT_TRUE(strstr(exception, "integer divide by zero") != NULL); @@ -187,10 +457,14 @@ TEST_F(ComponentExecutionTest, TestFloatDivideByZeroReturnsInfinity) ASSERT_TRUE(argv1 != NULL); // Call fdiv with divisor = 0.0 - ret = wasm_component_application_execute_func_ex(this->helper->component_inst, (char *)"fdiv(10.0, 0.0)", &argc1, &argv1); + ret = wasm_component_application_execute_func_ex( + this->helper->component_inst, (char *)"fdiv(10.0, 0.0)", &argc1, + &argv1); // Should succeed (no trap for float division by zero) - bool function_succeeded = ret && !wasm_component_runtime_get_exception(this->helper->component_inst); + bool function_succeeded = + ret + && !wasm_component_runtime_get_exception(this->helper->component_inst); float result = 0.0f; if (function_succeeded && argv1) { @@ -205,5 +479,5 @@ TEST_F(ComponentExecutionTest, TestFloatDivideByZeroReturnsInfinity) // Assert result is infinity ASSERT_TRUE(isinf(result)); - ASSERT_TRUE(result > 0); // Positive infinity + ASSERT_TRUE(result > 0); // Positive infinity } diff --git a/tests/unit/component-execution/test_prelinked_core_imports.cc b/tests/unit/component-execution/test_prelinked_core_imports.cc new file mode 100644 index 0000000000..b2274e24d9 --- /dev/null +++ b/tests/unit/component-execution/test_prelinked_core_imports.cc @@ -0,0 +1,646 @@ +/* + * Copyright (C) 2026 Matt Hargett. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include + +#include +#include +#include +#include +#include + +extern "C" { +#include "wasm_component_runtime.h" +#include "wasm_export.h" +#include "wasm_runtime.h" +#include "wasm_c_api_internal.h" +} + +namespace { + +struct RawHostState { + uint32_t expected_cookie; + std::atomic calls{ 0 }; + std::atomic saw_custom_data{ false }; +}; + +struct ThreadResult { + bool ok = false; + char error[128] = {}; +}; + +static void +raw_notify(wasm_exec_env_t exec_env, uint64_t *) +{ + auto *state = static_cast( + wasm_component_get_custom_data_from_exec_env(exec_env)); + if (state && state->expected_cookie == 0xC0DEC0DE) { + state->saw_custom_data.store(true, std::memory_order_relaxed); + state->calls.fetch_add(1, std::memory_order_relaxed); + } +} + +static std::vector +read_fixture(const char *path) +{ + std::ifstream input(path, std::ios::binary); + return std::vector(std::istreambuf_iterator(input), + std::istreambuf_iterator()); +} + +static WASMMemoryInstance * +find_exported_memory(WASMModuleInstance *instance, const char *name) +{ + for (uint32_t i = 0; i < instance->export_memory_count; i++) { + if (strcmp(instance->export_memories[i].name, name) == 0) + return instance->export_memories[i].memory; + } + return nullptr; +} + +static WASMTableInstance * +find_exported_table(WASMModuleInstance *instance, const char *name) +{ + for (uint32_t i = 0; i < instance->export_table_count; i++) { + if (strcmp(instance->export_tables[i].name, name) == 0) + return instance->export_tables[i].table; + } + return nullptr; +} + +static WASMGlobalInstance * +find_exported_global(WASMModuleInstance *instance, const char *name) +{ + for (uint32_t i = 0; i < instance->export_global_count; i++) { + if (strcmp(instance->export_globals[i].name, name) == 0) + return instance->export_globals[i].global; + } + return nullptr; +} + +static void +run_prelinked_instance(WASMModule *owner_module, WASMModule *consumer_module, + WASMFunctionInstance *host_function, + ThreadResult *result) +{ + WASMModuleInstance *owner = nullptr; + WASMModuleInstance *consumer = nullptr; + RawHostState state{ 0xC0DEC0DE }; + WASMComponentInstance component_instance = {}; + struct InstantiationArgs2 args; + + if (!wasm_runtime_init_thread_env()) { + snprintf(result->error, sizeof(result->error), + "failed to initialize WAMR thread environment"); + return; + } + + wasm_runtime_instantiation_args_set_defaults(&args); + wasm_runtime_instantiation_args_set_default_stack_size(&args, 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size(&args, 0); + + owner = wasm_instantiate(owner_module, nullptr, nullptr, &args, + result->error, sizeof(result->error)); + if (owner) { + WASMMemoryInstance *memory = find_exported_memory(owner, "memory"); + WASMTableInstance *table = find_exported_table(owner, "table"); + WASMGlobalInstance *global = find_exported_global(owner, "global"); + WASMFunctionInstance *functions[] = { host_function }; + WASMMemoryInstance *memories[] = { memory }; + WASMTableInstance *tables[] = { table }; + WASMGlobalInstance *globals[] = { global }; + WASMCoreImports imports = {}; + + imports.func_count = 1; + imports.func_instance = functions; + imports.mem_count = 1; + imports.mem_instance = memories; + imports.tables_count = 1; + imports.table_instance = tables; + imports.globals_count = 1; + imports.global_instance = globals; + + component_instance.custom_data = &state; + component_instance.instantiation_args = args; + consumer = wasm_instantiate_with_imports( + consumer_module, &component_instance, &imports, &args, + result->error, sizeof(result->error)); + + if (consumer && memory && table && global) { + wasm_global_inst_t exported_global = {}; + uint32_t stored_global = 0; + uint32_t current_global = 0; + bool found_global = wasm_runtime_get_export_global_inst( + reinterpret_cast(consumer), "global", + &exported_global); + memcpy(&stored_global, memory->memory_data, sizeof(stored_global)); + memcpy(¤t_global, + global->module_instance->global_data + global->data_offset, + sizeof(current_global)); + + result->ok = + state.calls.load(std::memory_order_relaxed) == 1 + && state.saw_custom_data.load(std::memory_order_relaxed) + && stored_global == 41 && current_global == 42 && found_global + && exported_global.global_data + == global->module_instance->global_data + + global->data_offset + && consumer->memories[0] == memory + && consumer->tables[0] == table + && consumer->e->globals[0].import_global_inst == global + && find_exported_memory(consumer, "memory") == memory + && find_exported_table(consumer, "table") == table + && find_exported_global(consumer, "global") + == &consumer->e->globals[0]; + } + } + + /* Component instances are currently destroyed in definition order. The + importer must therefore tolerate its memory owner disappearing first. */ + if (owner) + wasm_deinstantiate(owner, false); + if (consumer) + wasm_deinstantiate(consumer, false); + wasm_runtime_destroy_thread_env(); +} + +static void +run_prelinked_table_instance(WASMModule *owner_module, + WASMModule *provider_module, + WASMModule *initializer_module, + ThreadResult *result) +{ + WASMModuleInstance *owner = nullptr; + WASMModuleInstance *provider = nullptr; + WASMModuleInstance *initializer = nullptr; + wasm_exec_env_t exec_env = nullptr; + WASMComponentInstance component_instance = {}; + struct InstantiationArgs2 args; + bool local_table_ok = false; + bool mutation_ok = false; + bool foreign_get_closed = false; + bool foreign_copy_closed = false; + bool owner_local_ok = false; + bool host_table_api_ok = false; + + if (!wasm_runtime_init_thread_env()) { + snprintf(result->error, sizeof(result->error), + "failed to initialize WAMR thread environment"); + return; + } + + wasm_runtime_instantiation_args_set_defaults(&args); + wasm_runtime_instantiation_args_set_default_stack_size(&args, 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size(&args, 0); + + provider = wasm_instantiate(provider_module, nullptr, nullptr, &args, + result->error, sizeof(result->error)); + owner = wasm_instantiate(owner_module, nullptr, nullptr, &args, + result->error, sizeof(result->error)); + if (owner && provider) { + WASMTableInstance *table = find_exported_table(owner, "table"); + wasm_function_inst_t call_local = wasm_runtime_lookup_function( + reinterpret_cast(provider), "call-local"); + uint32_t local_argv[] = { 5 }; + + exec_env = wasm_runtime_create_exec_env( + reinterpret_cast(provider), 64 * 1024); + local_table_ok = + call_local && exec_env && provider->table_count == 1 + && !provider->tables[0]->component_func_refs + && wasm_runtime_call_wasm(exec_env, call_local, 1, local_argv) + && local_argv[0] == 12; + if (!local_table_ok) { + snprintf(result->error, sizeof(result->error), + "local table regression failed (tables=%u sidecar=%p " + "value=%u)", + provider->table_count, + provider->table_count ? static_cast( + provider->tables[0]->component_func_refs) + : nullptr, + local_argv[0]); + } + if (exec_env) { + wasm_runtime_destroy_exec_env(exec_env); + exec_env = nullptr; + } + + auto *provider_function = reinterpret_cast( + wasm_runtime_lookup_function( + reinterpret_cast(provider), "add-seven")); + WASMFunctionInstance *functions[] = { provider_function }; + WASMTableInstance *tables[] = { table }; + WASMCoreImports imports = {}; + + imports.func_count = 1; + imports.func_instance = functions; + imports.tables_count = 1; + imports.table_instance = tables; + component_instance.instantiation_args = args; + initializer = wasm_instantiate_with_imports( + initializer_module, &component_instance, &imports, &args, + result->error, sizeof(result->error)); + + if (initializer && table) { + wasm_function_inst_t mutate_table = wasm_runtime_lookup_function( + reinterpret_cast(initializer), + "mutate-table"); + wasm_function_inst_t copy_local_to_borrowed = + wasm_runtime_lookup_function( + reinterpret_cast(initializer), + "copy-local-to-borrowed"); + wasm_function_inst_t copy_borrowed_to_local = + wasm_runtime_lookup_function( + reinterpret_cast(initializer), + "copy-borrowed-to-local"); + wasm_function_inst_t call_initializer_local = + wasm_runtime_lookup_function( + reinterpret_cast(initializer), + "call-local"); + wasm_function_inst_t call = wasm_runtime_lookup_function( + reinterpret_cast(owner), "call"); + wasm_function_inst_t get_foreign_and_store = + wasm_runtime_lookup_function( + reinterpret_cast(owner), + "get-foreign-and-store"); + wasm_function_inst_t copy_foreign_to_local = + wasm_runtime_lookup_function( + reinterpret_cast(owner), + "copy-foreign-to-local"); + wasm_function_inst_t call_owner_local = + wasm_runtime_lookup_function( + reinterpret_cast(owner), "call-local"); + uint32_t argv[] = { 5 }; + uint32_t local_argv[] = { 5 }; + + exec_env = wasm_runtime_create_exec_env( + reinterpret_cast(initializer), 64 * 1024); + mutation_ok = + mutate_table && copy_local_to_borrowed && copy_borrowed_to_local + && call_initializer_local && exec_env + && wasm_runtime_call_wasm(exec_env, mutate_table, 0, nullptr) + && wasm_runtime_call_wasm(exec_env, copy_borrowed_to_local, 0, + nullptr) + && wasm_runtime_call_wasm(exec_env, call_initializer_local, 1, + local_argv) + && local_argv[0] == 12 + && wasm_runtime_call_wasm(exec_env, copy_local_to_borrowed, 0, + nullptr) + && table->cur_size == 1 && table->component_func_refs + && table->component_func_refs[0] + == &initializer->e->functions[0]; + if (!mutation_ok && result->error[0] == '\0') { + snprintf(result->error, sizeof(result->error), + "borrowed table mutation failed (size=%u sidecar=%p " + "ref0=%p expected=%p)", + table->cur_size, + static_cast(table->component_func_refs), + table->component_func_refs ? static_cast( + table->component_func_refs[0]) + : nullptr, + static_cast(&initializer->e->functions[0])); + } + if (exec_env) { + wasm_runtime_destroy_exec_env(exec_env); + exec_env = nullptr; + } + + if (mutation_ok) { + wasm_store_t store = {}; + wasm_valtype_t val_type = {}; + wasm_tabletype_t table_type = {}; + wasm_table_t table_api = {}; + wasm_ref_t *foreign_ref; + WASMFunctionInstance *runtime_ref; + wasm_table_inst_t table_view = {}; + bool view_ok; + bool get_ok; + bool clear_ok = false; + bool restore_ok = false; + + val_type.kind = WASM_FUNCREF; + table_type.val_type = &val_type; + table_api.store = &store; + table_api.kind = WASM_EXTERN_TABLE; + table_api.type = &table_type; + table_api.table_idx_rt = 0; + table_api.inst_comm_rt = + reinterpret_cast(owner); + + foreign_ref = wasm_table_get(&table_api, 0); + view_ok = wasm_runtime_get_export_table_inst( + reinterpret_cast(owner), "table", + &table_view); + runtime_ref = + view_ok ? reinterpret_cast( + wasm_table_get_func_inst( + reinterpret_cast(owner), + &table_view, 0)) + : nullptr; + get_ok = view_ok && foreign_ref + && foreign_ref->inst_comm_rt + == reinterpret_cast( + initializer) + && foreign_ref->ref_idx_rt + == initializer->e->functions[0].func_idx + && runtime_ref == &initializer->e->functions[0] + && runtime_ref != &owner->e->functions[0]; + + if (get_ok) { + clear_ok = + wasm_table_set(&table_api, 0, nullptr) + && table->elems[0] == NULL_REF + && table->component_func_refs[0] == nullptr + && wasm_table_get_func_inst( + reinterpret_cast(owner), + &table_view, 0) + == nullptr; + } + if (clear_ok) { + restore_ok = wasm_table_set(&table_api, 0, foreign_ref); + if (restore_ok) { + foreign_ref = nullptr; + } + } + host_table_api_ok = + restore_ok + && table->component_func_refs[0] + == &initializer->e->functions[0] + && reinterpret_cast( + wasm_table_get_func_inst( + reinterpret_cast(owner), + &table_view, 0)) + == &initializer->e->functions[0]; + if (foreign_ref) { + wasm_ref_delete(foreign_ref); + } + if (!host_table_api_ok && result->error[0] == '\0') { + snprintf(result->error, sizeof(result->error), + "host table API lost function provenance"); + } + } + + exec_env = wasm_runtime_create_exec_env( + reinterpret_cast(owner), 64 * 1024); + if (call && get_foreign_and_store && copy_foreign_to_local + && call_owner_local && exec_env + && wasm_runtime_call_wasm(exec_env, call, 1, argv) + && argv[0] == 12) { + foreign_get_closed = !wasm_runtime_call_wasm( + exec_env, get_foreign_and_store, 0, nullptr); + const char *exception = wasm_runtime_get_exception( + reinterpret_cast(owner)); + foreign_get_closed = + foreign_get_closed && exception + && strstr(exception, "foreign function reference"); + wasm_runtime_clear_exception( + reinterpret_cast(owner)); + + foreign_copy_closed = !wasm_runtime_call_wasm( + exec_env, copy_foreign_to_local, 0, nullptr); + exception = wasm_runtime_get_exception( + reinterpret_cast(owner)); + foreign_copy_closed = + foreign_copy_closed && exception + && strstr(exception, "foreign function reference"); + wasm_runtime_clear_exception( + reinterpret_cast(owner)); + + local_argv[0] = 5; + owner_local_ok = wasm_runtime_call_wasm( + exec_env, call_owner_local, 1, local_argv) + && local_argv[0] == 105; + } + result->ok = local_table_ok && mutation_ok && foreign_get_closed + && foreign_copy_closed && owner_local_ok + && host_table_api_ok && initializer->tables[0] == table + && table->component_func_refs + && table->component_func_refs[0] + == &initializer->e->functions[0]; + if (!result->ok && exec_env) { + const char *exception = wasm_runtime_get_exception( + reinterpret_cast(owner)); + if (exception) { + snprintf(result->error, sizeof(result->error), "%s", + exception); + } + } + } + } + + if (exec_env) + wasm_runtime_destroy_exec_env(exec_env); + /* Exercise the component runtime's current definition-order teardown. */ + if (owner) + wasm_deinstantiate(owner, false); + if (initializer) + wasm_deinstantiate(initializer, false); + if (provider) + wasm_deinstantiate(provider, false); + wasm_runtime_destroy_thread_env(); +} + +static bool +run_prelinked_limits_case(WASMModule *source_module, + WASMModule *consumer_module, bool expect_success, + const char *expected_error) +{ + WASMModuleInstance *source = nullptr; + WASMModuleInstance *consumer = nullptr; + WASMComponentInstance component_instance = {}; + struct InstantiationArgs2 args; + char error[256] = {}; + bool result = false; + + wasm_runtime_instantiation_args_set_defaults(&args); + wasm_runtime_instantiation_args_set_default_stack_size(&args, 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size(&args, 0); + + source = wasm_instantiate(source_module, nullptr, nullptr, &args, error, + sizeof(error)); + if (source) { + WASMTableInstance *table = find_exported_table(source, "table"); + WASMMemoryInstance *memory = find_exported_memory(source, "memory"); + WASMTableInstance *tables[] = { table }; + WASMMemoryInstance *memories[] = { memory }; + WASMCoreImports imports = {}; + + imports.tables_count = 1; + imports.table_instance = tables; + imports.mem_count = 1; + imports.mem_instance = memories; + component_instance.instantiation_args = args; + consumer = wasm_instantiate_with_imports(consumer_module, + &component_instance, &imports, + &args, error, sizeof(error)); + result = expect_success ? consumer != nullptr + : consumer == nullptr && expected_error + && strstr(error, expected_error); + if (!result) { + ADD_FAILURE() << "unexpected prelinked limit result: " << error; + } + } + + if (source) + wasm_deinstantiate(source, false); + if (consumer) + wasm_deinstantiate(consumer, false); + return result; +} + +class PrelinkedCoreImportsTest : public testing::Test +{ + protected: + void SetUp() override + { + RuntimeInitArgs init_args = {}; + init_args.mem_alloc_type = Alloc_With_System_Allocator; + ASSERT_TRUE(wasm_runtime_full_init(&init_args)); + } + + void TearDown() override { wasm_runtime_destroy(); } +}; + +TEST_F(PrelinkedCoreImportsTest, + BindsBeforeStartAndBorrowsResourcesAcrossTwoThreads) +{ + std::vector owner_bytes = read_fixture("prelinked_owner.wasm"); + std::vector consumer_bytes = + read_fixture("prelinked_consumer.wasm"); + char error_buf[128] = {}; + LoadArgs load_args = {}; + + ASSERT_FALSE(owner_bytes.empty()); + ASSERT_FALSE(consumer_bytes.empty()); + load_args.no_resolve = true; + load_args.is_component = false; + + auto *owner_module = reinterpret_cast(wasm_runtime_load_ex( + owner_bytes.data(), static_cast(owner_bytes.size()), + &load_args, error_buf, sizeof(error_buf))); + ASSERT_NE(owner_module, nullptr) << error_buf; + auto *consumer_module = reinterpret_cast(wasm_runtime_load_ex( + consumer_bytes.data(), static_cast(consumer_bytes.size()), + &load_args, error_buf, sizeof(error_buf))); + ASSERT_NE(consumer_module, nullptr) << error_buf; + + WASMFunctionImport host_import = {}; + host_import.module_name = const_cast("host"); + host_import.field_name = const_cast("notify"); + host_import.func_type = + consumer_module->import_functions[0].u.function.func_type; + host_import.func_ptr_linked = reinterpret_cast(raw_notify); + host_import.signature = "()"; + host_import.call_conv_raw = true; + + WASMFunctionInstance host_function = {}; + host_function.is_import_func = true; + host_function.u.func_import = &host_import; + + ThreadResult results[2]; + std::thread first(run_prelinked_instance, owner_module, consumer_module, + &host_function, &results[0]); + std::thread second(run_prelinked_instance, owner_module, consumer_module, + &host_function, &results[1]); + first.join(); + second.join(); + + EXPECT_TRUE(results[0].ok) << results[0].error; + EXPECT_TRUE(results[1].ok) << results[1].error; + + wasm_runtime_unload(reinterpret_cast(consumer_module)); + wasm_runtime_unload(reinterpret_cast(owner_module)); +} + +TEST_F(PrelinkedCoreImportsTest, + ImportedFuncrefTableTracksInitializerProvenanceAcrossTwoThreads) +{ + std::vector owner_bytes = + read_fixture("prelinked_table_owner.wasm"); + std::vector provider_bytes = + read_fixture("prelinked_table_provider.wasm"); + std::vector initializer_bytes = + read_fixture("prelinked_table_initializer.wasm"); + char error_buf[128] = {}; + LoadArgs load_args = {}; + + ASSERT_FALSE(owner_bytes.empty()); + ASSERT_FALSE(provider_bytes.empty()); + ASSERT_FALSE(initializer_bytes.empty()); + load_args.no_resolve = true; + load_args.is_component = false; + + auto *owner_module = reinterpret_cast(wasm_runtime_load_ex( + owner_bytes.data(), static_cast(owner_bytes.size()), + &load_args, error_buf, sizeof(error_buf))); + ASSERT_NE(owner_module, nullptr) << error_buf; + auto *provider_module = reinterpret_cast(wasm_runtime_load_ex( + provider_bytes.data(), static_cast(provider_bytes.size()), + &load_args, error_buf, sizeof(error_buf))); + ASSERT_NE(provider_module, nullptr) << error_buf; + auto *initializer_module = reinterpret_cast( + wasm_runtime_load_ex(initializer_bytes.data(), + static_cast(initializer_bytes.size()), + &load_args, error_buf, sizeof(error_buf))); + ASSERT_NE(initializer_module, nullptr) << error_buf; + + ThreadResult results[2]; + std::thread first(run_prelinked_table_instance, owner_module, + provider_module, initializer_module, &results[0]); + std::thread second(run_prelinked_table_instance, owner_module, + provider_module, initializer_module, &results[1]); + first.join(); + second.join(); + + EXPECT_TRUE(results[0].ok) << results[0].error; + EXPECT_TRUE(results[1].ok) << results[1].error; + + wasm_runtime_unload(reinterpret_cast(initializer_module)); + wasm_runtime_unload(reinterpret_cast(provider_module)); + wasm_runtime_unload(reinterpret_cast(owner_module)); +} + +TEST_F(PrelinkedCoreImportsTest, EnforcesTableAndMemoryMaximumSubtyping) +{ + const char *fixture_names[] = { + "prelinked_limits_consumer.wasm", + "prelinked_limits_compatible.wasm", + "prelinked_limits_wide_table.wasm", + "prelinked_limits_wide_memory.wasm", + "prelinked_limits_unbounded.wasm", + "prelinked_limits_unbounded_memory.wasm", + }; + WASMModule *modules[6] = {}; + char error_buf[128] = {}; + LoadArgs load_args = {}; + + load_args.no_resolve = true; + load_args.is_component = false; + for (size_t i = 0; i < 6; i++) { + std::vector bytes = read_fixture(fixture_names[i]); + ASSERT_FALSE(bytes.empty()) << fixture_names[i]; + modules[i] = reinterpret_cast(wasm_runtime_load_ex( + bytes.data(), static_cast(bytes.size()), &load_args, + error_buf, sizeof(error_buf))); + ASSERT_NE(modules[i], nullptr) << fixture_names[i] << ": " << error_buf; + } + + EXPECT_TRUE( + run_prelinked_limits_case(modules[1], modules[0], true, nullptr)); + EXPECT_TRUE(run_prelinked_limits_case(modules[2], modules[0], false, + "table import")); + EXPECT_TRUE(run_prelinked_limits_case(modules[3], modules[0], false, + "memory import")); + EXPECT_TRUE(run_prelinked_limits_case(modules[4], modules[0], false, + "table import")); + EXPECT_TRUE(run_prelinked_limits_case(modules[5], modules[0], false, + "memory import")); + + for (size_t i = 6; i > 0; i--) { + wasm_runtime_unload(reinterpret_cast(modules[i - 1])); + } +} + +} // namespace diff --git a/tests/unit/component-execution/wasm-apps/nested_host_resource.wasm b/tests/unit/component-execution/wasm-apps/nested_host_resource.wasm new file mode 100644 index 0000000000..b17f522634 Binary files /dev/null and b/tests/unit/component-execution/wasm-apps/nested_host_resource.wasm differ diff --git a/tests/unit/component-execution/wasm-apps/nested_host_resource.wat b/tests/unit/component-execution/wasm-apps/nested_host_resource.wat new file mode 100644 index 0000000000..5ed3124042 --- /dev/null +++ b/tests/unit/component-execution/wasm-apps/nested_host_resource.wat @@ -0,0 +1,69 @@ +;; Copyright (C) 2026 Matt Hargett. All rights reserved. +;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +(component + (type $host-type + (instance + (export "item" (type (sub resource))) + (type (own 0)) + (type (func (result 1))) + (export "make" (func (type 2))) + (type (borrow 0)) + (type (func (param "self" 3) (result u32))) + (export "rep" (func (type 4))))) + (import "test:nested/host@1.0.0" + (instance $host (type $host-type))) + + (component $nested + (type $host-type + (instance + (export "item" (type (sub resource))) + (type (own 0)) + (type (func (result 1))) + (export "make" (func (type 2))) + (type (borrow 0)) + (type (func (param "self" 3) (result u32))) + (export "rep" (func (type 4))))) + (import "host" (instance $host (type $host-type))) + + (alias export $host "item" (type $item)) + (alias export $host "make" (func $make)) + (alias export $host "rep" (func $rep)) + (core func $lowered-make (canon lower (func $make))) + (core func $lowered-rep (canon lower (func $rep))) + (core func $drop-item (canon resource.drop $item)) + (core instance $lowered-host + (export "make" (func $lowered-make)) + (export "rep" (func $lowered-rep)) + (export "drop" (func $drop-item))) + + (core module $guest + (import "host" "make" (func $make (result i32))) + (import "host" "rep" (func $rep (param i32) (result i32))) + (import "host" "drop" (func $drop (param i32))) + (func (export "round-trip") (result i32) + (local $handle i32) + (local $representation i32) + call $make + local.tee $handle + call $rep + local.set $representation + local.get $handle + call $drop + local.get $representation)) + (core instance $guest-instance + (instantiate $guest + (with "host" (instance $lowered-host)))) + + (type $round-trip-type (func (result u32))) + (alias core export $guest-instance "round-trip" + (core func $round-trip-core)) + (func $round-trip (type $round-trip-type) + (canon lift (core func $round-trip-core))) + (export "round-trip" (func $round-trip))) + + (instance $nested-instance + (instantiate $nested + (with "host" (instance $host)))) + (alias export $nested-instance "round-trip" (func $round-trip)) + (export "round-trip" (func $round-trip))) diff --git a/tests/unit/component-execution/wasm-apps/prelinked_consumer.wasm b/tests/unit/component-execution/wasm-apps/prelinked_consumer.wasm new file mode 100644 index 0000000000..2e70b54f2a Binary files /dev/null and b/tests/unit/component-execution/wasm-apps/prelinked_consumer.wasm differ diff --git a/tests/unit/component-execution/wasm-apps/prelinked_consumer.wat b/tests/unit/component-execution/wasm-apps/prelinked_consumer.wat new file mode 100644 index 0000000000..cee018b29f --- /dev/null +++ b/tests/unit/component-execution/wasm-apps/prelinked_consumer.wat @@ -0,0 +1,18 @@ +(module + (import "host" "notify" (func $notify)) + (import "shared" "table" (table 1 1 funcref)) + (import "shared" "memory" (memory 1 1)) + (import "shared" "global" (global $global (mut i32))) + + (func $start + i32.const 0 + global.get $global + i32.store + i32.const 42 + global.set $global + call $notify) + (start $start) + + (export "table" (table 0)) + (export "memory" (memory 0)) + (export "global" (global $global))) diff --git a/tests/unit/component-execution/wasm-apps/prelinked_limits_compatible.wasm b/tests/unit/component-execution/wasm-apps/prelinked_limits_compatible.wasm new file mode 100644 index 0000000000..d05d7bce71 Binary files /dev/null and b/tests/unit/component-execution/wasm-apps/prelinked_limits_compatible.wasm differ diff --git a/tests/unit/component-execution/wasm-apps/prelinked_limits_compatible.wat b/tests/unit/component-execution/wasm-apps/prelinked_limits_compatible.wat new file mode 100644 index 0000000000..88c82ce883 --- /dev/null +++ b/tests/unit/component-execution/wasm-apps/prelinked_limits_compatible.wat @@ -0,0 +1,6 @@ +(module + (table (export "table") 2 3 funcref) + (memory (export "memory") 2 3) + (func + (drop (table.grow (ref.null func) (i32.const 0))) + (drop (memory.grow (i32.const 0))))) diff --git a/tests/unit/component-execution/wasm-apps/prelinked_limits_consumer.wasm b/tests/unit/component-execution/wasm-apps/prelinked_limits_consumer.wasm new file mode 100644 index 0000000000..86b24c7ef3 Binary files /dev/null and b/tests/unit/component-execution/wasm-apps/prelinked_limits_consumer.wasm differ diff --git a/tests/unit/component-execution/wasm-apps/prelinked_limits_consumer.wat b/tests/unit/component-execution/wasm-apps/prelinked_limits_consumer.wat new file mode 100644 index 0000000000..520822f72f --- /dev/null +++ b/tests/unit/component-execution/wasm-apps/prelinked_limits_consumer.wat @@ -0,0 +1,6 @@ +(module + (import "source" "table" (table 1 4 funcref)) + (import "source" "memory" (memory 1 4)) + (func + (drop (table.grow (ref.null func) (i32.const 0))) + (drop (memory.grow (i32.const 0))))) diff --git a/tests/unit/component-execution/wasm-apps/prelinked_limits_unbounded.wasm b/tests/unit/component-execution/wasm-apps/prelinked_limits_unbounded.wasm new file mode 100644 index 0000000000..3a95a44ba3 Binary files /dev/null and b/tests/unit/component-execution/wasm-apps/prelinked_limits_unbounded.wasm differ diff --git a/tests/unit/component-execution/wasm-apps/prelinked_limits_unbounded.wat b/tests/unit/component-execution/wasm-apps/prelinked_limits_unbounded.wat new file mode 100644 index 0000000000..c8d16cae31 --- /dev/null +++ b/tests/unit/component-execution/wasm-apps/prelinked_limits_unbounded.wat @@ -0,0 +1,6 @@ +(module + (table (export "table") 2 funcref) + (memory (export "memory") 2) + (func + (drop (table.grow (ref.null func) (i32.const 0))) + (drop (memory.grow (i32.const 0))))) diff --git a/tests/unit/component-execution/wasm-apps/prelinked_limits_unbounded_memory.wasm b/tests/unit/component-execution/wasm-apps/prelinked_limits_unbounded_memory.wasm new file mode 100644 index 0000000000..0b60ee0e99 Binary files /dev/null and b/tests/unit/component-execution/wasm-apps/prelinked_limits_unbounded_memory.wasm differ diff --git a/tests/unit/component-execution/wasm-apps/prelinked_limits_unbounded_memory.wat b/tests/unit/component-execution/wasm-apps/prelinked_limits_unbounded_memory.wat new file mode 100644 index 0000000000..7ab188ca9b --- /dev/null +++ b/tests/unit/component-execution/wasm-apps/prelinked_limits_unbounded_memory.wat @@ -0,0 +1,6 @@ +(module + (table (export "table") 2 3 funcref) + (memory (export "memory") 2) + (func + (drop (table.grow (ref.null func) (i32.const 0))) + (drop (memory.grow (i32.const 0))))) diff --git a/tests/unit/component-execution/wasm-apps/prelinked_limits_wide_memory.wasm b/tests/unit/component-execution/wasm-apps/prelinked_limits_wide_memory.wasm new file mode 100644 index 0000000000..8402bf751d Binary files /dev/null and b/tests/unit/component-execution/wasm-apps/prelinked_limits_wide_memory.wasm differ diff --git a/tests/unit/component-execution/wasm-apps/prelinked_limits_wide_memory.wat b/tests/unit/component-execution/wasm-apps/prelinked_limits_wide_memory.wat new file mode 100644 index 0000000000..d2e81085b1 --- /dev/null +++ b/tests/unit/component-execution/wasm-apps/prelinked_limits_wide_memory.wat @@ -0,0 +1,6 @@ +(module + (table (export "table") 2 3 funcref) + (memory (export "memory") 2 5) + (func + (drop (table.grow (ref.null func) (i32.const 0))) + (drop (memory.grow (i32.const 0))))) diff --git a/tests/unit/component-execution/wasm-apps/prelinked_limits_wide_table.wasm b/tests/unit/component-execution/wasm-apps/prelinked_limits_wide_table.wasm new file mode 100644 index 0000000000..09ac0c480e Binary files /dev/null and b/tests/unit/component-execution/wasm-apps/prelinked_limits_wide_table.wasm differ diff --git a/tests/unit/component-execution/wasm-apps/prelinked_limits_wide_table.wat b/tests/unit/component-execution/wasm-apps/prelinked_limits_wide_table.wat new file mode 100644 index 0000000000..fce5832fd6 --- /dev/null +++ b/tests/unit/component-execution/wasm-apps/prelinked_limits_wide_table.wat @@ -0,0 +1,6 @@ +(module + (table (export "table") 2 5 funcref) + (memory (export "memory") 2 3) + (func + (drop (table.grow (ref.null func) (i32.const 0))) + (drop (memory.grow (i32.const 0))))) diff --git a/tests/unit/component-execution/wasm-apps/prelinked_owner.wasm b/tests/unit/component-execution/wasm-apps/prelinked_owner.wasm new file mode 100644 index 0000000000..32acf05818 Binary files /dev/null and b/tests/unit/component-execution/wasm-apps/prelinked_owner.wasm differ diff --git a/tests/unit/component-execution/wasm-apps/prelinked_owner.wat b/tests/unit/component-execution/wasm-apps/prelinked_owner.wat new file mode 100644 index 0000000000..c9a7009ac6 --- /dev/null +++ b/tests/unit/component-execution/wasm-apps/prelinked_owner.wat @@ -0,0 +1,4 @@ +(module + (memory (export "memory") 1 1) + (table (export "table") 1 1 funcref) + (global (export "global") (mut i32) (i32.const 41))) diff --git a/tests/unit/component-execution/wasm-apps/prelinked_table_initializer.wasm b/tests/unit/component-execution/wasm-apps/prelinked_table_initializer.wasm new file mode 100644 index 0000000000..2fe6c84b13 Binary files /dev/null and b/tests/unit/component-execution/wasm-apps/prelinked_table_initializer.wasm differ diff --git a/tests/unit/component-execution/wasm-apps/prelinked_table_initializer.wat b/tests/unit/component-execution/wasm-apps/prelinked_table_initializer.wat new file mode 100644 index 0000000000..91d6243b6f --- /dev/null +++ b/tests/unit/component-execution/wasm-apps/prelinked_table_initializer.wat @@ -0,0 +1,33 @@ +(module + (type $indirect (func (param i32) (result i32))) + (import "provider" "add-seven" (func $add-seven (type $indirect))) + (import "owner" "table" (table $table 1 1 funcref)) + + (table $local 1 1 funcref) + (elem (table $local) (i32.const 0) func $add-seven) + + ;; This mirrors the Rust Preview 2 adapter: a later core instance populates + ;; an earlier core's borrowed table with its own imported functions. + (elem (i32.const 0) func $add-seven) + + ;; Exercise the mutation paths that must keep per-element provenance in + ;; sync after component instantiation. + (func (export "mutate-table") + (table.fill $table (i32.const 0) (ref.null func) (i32.const 1)) + (table.set $table (i32.const 0) (ref.func $add-seven)) + (table.copy $table $table (i32.const 0) (i32.const 0) (i32.const 1))) + + ;; A local source has no provenance sidecar. Copying into the borrowed table + ;; must translate its module-relative index into an initializer-owned ref. + (func (export "copy-local-to-borrowed") + (table.copy $table $local (i32.const 0) (i32.const 0) (i32.const 1))) + + ;; Entries owned by this module can safely return from the borrowed table to + ;; a local table after the raw index and provenance pointer are cross-checked. + (func (export "copy-borrowed-to-local") + (table.copy $local $table (i32.const 0) (i32.const 0) (i32.const 1))) + + (func (export "call-local") (param $value i32) (result i32) + local.get $value + i32.const 0 + call_indirect $local (type $indirect))) diff --git a/tests/unit/component-execution/wasm-apps/prelinked_table_owner.wasm b/tests/unit/component-execution/wasm-apps/prelinked_table_owner.wasm new file mode 100644 index 0000000000..904a86275d Binary files /dev/null and b/tests/unit/component-execution/wasm-apps/prelinked_table_owner.wasm differ diff --git a/tests/unit/component-execution/wasm-apps/prelinked_table_owner.wat b/tests/unit/component-execution/wasm-apps/prelinked_table_owner.wat new file mode 100644 index 0000000000..3299aa8d8d --- /dev/null +++ b/tests/unit/component-execution/wasm-apps/prelinked_table_owner.wat @@ -0,0 +1,38 @@ +(module + (type $indirect (func (param i32) (result i32))) + ;; Function index zero deliberately has the expected type but the wrong + ;; behavior. Treating an initializer-relative table element as owner-relative + ;; would call this function. + (func $wrong-module (type $indirect) (param $value i32) (result i32) + local.get $value + i32.const 100 + i32.add) + + (table $shared (export "table") 1 1 funcref) + (table $local 1 1 funcref) + (elem (table $local) (i32.const 0) func $wrong-module) + + (func (export "call") (param $value i32) (result i32) + local.get $value + i32.const 0 + call_indirect $shared (type $indirect)) + + (func (export "call-local") (param $value i32) (result i32) + local.get $value + i32.const 0 + call_indirect $local (type $indirect)) + + ;; A foreign funcref cannot be represented on the non-GC operand stack + ;; without losing its defining instance. Both propagation operations must + ;; trap before the local table is modified. + (func (export "get-foreign-and-store") + i32.const 0 + i32.const 0 + table.get $shared + table.set $local) + + (func (export "copy-foreign-to-local") + i32.const 0 + i32.const 0 + i32.const 1 + table.copy $local $shared)) diff --git a/tests/unit/component-execution/wasm-apps/prelinked_table_provider.wasm b/tests/unit/component-execution/wasm-apps/prelinked_table_provider.wasm new file mode 100644 index 0000000000..dfed428fb4 Binary files /dev/null and b/tests/unit/component-execution/wasm-apps/prelinked_table_provider.wasm differ diff --git a/tests/unit/component-execution/wasm-apps/prelinked_table_provider.wat b/tests/unit/component-execution/wasm-apps/prelinked_table_provider.wat new file mode 100644 index 0000000000..25bb907ad3 --- /dev/null +++ b/tests/unit/component-execution/wasm-apps/prelinked_table_provider.wat @@ -0,0 +1,18 @@ +(module + (type $indirect (func (param i32) (result i32))) + (global $bias i32 (i32.const 7)) + + (func (export "add-seven") (type $indirect) + (param $value i32) (result i32) + local.get $value + global.get $bias + i32.add) + + ;; Ordinary local tables must stay on WAMR's existing module-relative fast + ;; path until another core instance actually borrows the table. + (table 1 1 funcref) + (elem (i32.const 0) func 0) + (func (export "call-local") (param $value i32) (result i32) + local.get $value + i32.const 0 + call_indirect (type $indirect))) diff --git a/tests/unit/component-instantiation/CMakeLists.txt b/tests/unit/component-instantiation/CMakeLists.txt index 05a58f8424..b8797bd28e 100644 --- a/tests/unit/component-instantiation/CMakeLists.txt +++ b/tests/unit/component-instantiation/CMakeLists.txt @@ -55,7 +55,10 @@ target_include_directories(component-instantiation PRIVATE ${LIBC_WASI_P2_INCLUD find_package(Threads REQUIRED) target_link_libraries (component-instantiation ${LLVM_AVAILABLE_LIBS} ${WAMR_TEST_OPENSSL_LIBS} gmock gtest_main Threads::Threads) -target_compile_definitions(component-instantiation PRIVATE WAMR_BUILD_COMPONENT_MODEL=1) +target_compile_definitions(component-instantiation PRIVATE + WAMR_BUILD_COMPONENT_MODEL=1 + WAMR_UNIT_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" +) gtest_discover_tests(component-instantiation @@ -66,4 +69,4 @@ add_custom_command(TARGET component-instantiation POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/wasm-apps/*.wasm ${CMAKE_CURRENT_BINARY_DIR}/ -) \ No newline at end of file +) diff --git a/tests/unit/component-instantiation/helpers.cc b/tests/unit/component-instantiation/helpers.cc index 4c7b83acb4..ff4ad8357f 100644 --- a/tests/unit/component-instantiation/helpers.cc +++ b/tests/unit/component-instantiation/helpers.cc @@ -48,64 +48,79 @@ bool component_is_simple_enough(const WASMComponent* c, const char** why) { return true; // “simple” component } -WASMComponent* load_component_from_candidates_internal(const char *file_name, const char *test_dir_name) { - char cwd[PATH_MAX]; - getcwd(cwd, sizeof(cwd)); - if (!file_name) { - printf("Invalid file\n"); - return NULL; - } +WASMComponent * +load_component_from_candidates_internal(const char *file_name, + const char *test_dir_name) +{ + if (!file_name) { + printf("Invalid file\n"); + return NULL; + } - const char *substr = "wasm-micro-runtime"; - char *pos = strstr(cwd, substr); - if (!pos) { - printf("Could not find 'wasm-micro-runtime' in cwd\n"); - return NULL; - } + char full_path[PATH_MAX]; +#ifdef WAMR_UNIT_TEST_SOURCE_DIR + (void)test_dir_name; + int path_length = snprintf(full_path, sizeof(full_path), "%s/wasm-apps/%s", + WAMR_UNIT_TEST_SOURCE_DIR, file_name); +#else + int path_length = + snprintf(full_path, sizeof(full_path), "tests/unit/%s/wasm-apps/%s", + test_dir_name, file_name); +#endif + if (path_length < 0 || (size_t)path_length >= sizeof(full_path)) { + printf("Component fixture path is too long\n"); + return NULL; + } - size_t prefix_len = (pos - cwd) + strlen(substr); - char full_path[prefix_len + strlen("/tests/unit/") + strlen(test_dir_name) + strlen("/wasm-apps/") + strlen(file_name) + 1]; - - full_path[0] = '\0'; - strncat(full_path, cwd, prefix_len); - strcat(full_path, "/tests/unit/"); - strcat(full_path, test_dir_name); - strcat(full_path, "/wasm-apps/"); - strcat(full_path, file_name); - const char* candidates[] = { - file_name, - full_path // absolute path necessary for running debugger - }; - - unsigned char *buf = nullptr; uint32_t size = 0; const char *path = nullptr; - for (const char *p : candidates) { - buf = (unsigned char*)bh_read_file_to_buffer(p, &size); - if (buf) { path = p; break; } - } - if (!buf) return nullptr; + const char *candidates[] = { + file_name, + full_path // absolute path necessary for running debugger + }; - auto *comp = (WASMComponent *)wasm_runtime_malloc(sizeof(WASMComponent)); - if (!comp) { BH_FREE(buf); return nullptr; } - memset(comp, 0, sizeof(WASMComponent)); + unsigned char *buf = nullptr; + uint32_t size = 0; + const char *path = nullptr; + for (const char *p : candidates) { + buf = (unsigned char *)bh_read_file_to_buffer(p, &size); + if (buf) { + path = p; + break; + } + } + if (!buf) + return nullptr; - if (!wasm_decode_header(buf, size, &comp->header) || !is_wasm_component(comp->header)) { - wasm_runtime_free(comp); BH_FREE(buf); return nullptr; - } + auto *comp = (WASMComponent *)wasm_runtime_malloc(sizeof(WASMComponent)); + if (!comp) { + BH_FREE(buf); + return nullptr; + } + memset(comp, 0, sizeof(WASMComponent)); - LoadArgs args{}; char name_buf[32] = {0}; - memset(&args, 0, sizeof(LoadArgs)); - std::snprintf(name_buf, sizeof(name_buf), "%s", "Real Component"); - args.name = name_buf; - args.wasm_binary_freeable = false; - args.clone_wasm_binary = false; - args.no_resolve = true; - args.is_component = true; - - if (!wasm_component_parse_sections(buf, size, comp, &args, 0)) { - wasm_runtime_free(comp); BH_FREE(buf); return nullptr; - } + if (!wasm_decode_header(buf, size, &comp->header) + || !is_wasm_component(comp->header)) { + wasm_runtime_free(comp); + BH_FREE(buf); + return nullptr; + } - return comp; + LoadArgs args{}; + char name_buf[32] = { 0 }; + memset(&args, 0, sizeof(LoadArgs)); + std::snprintf(name_buf, sizeof(name_buf), "%s", "Real Component"); + args.name = name_buf; + args.wasm_binary_freeable = false; + args.clone_wasm_binary = false; + args.no_resolve = true; + args.is_component = true; + + if (!wasm_component_parse_sections(buf, size, comp, &args, 0)) { + wasm_runtime_free(comp); + BH_FREE(buf); + return nullptr; + } + + return comp; } // Pretty name for section ids diff --git a/tests/unit/component-instantiation/helpers.h b/tests/unit/component-instantiation/helpers.h index 0096deb61f..9d9238863a 100644 --- a/tests/unit/component-instantiation/helpers.h +++ b/tests/unit/component-instantiation/helpers.h @@ -17,6 +17,10 @@ extern "C" { #include "wasm_component_runtime.h" } +#ifndef HEAP_SIZE +#define HEAP_SIZE (100 * 1024 * 1024) /* 100 MB */ +#endif + // -------- helpers to inspect real components -------- bool @@ -53,4 +57,4 @@ core_kind_name(uint8_t kind); WASMComponent * parse_component(const unsigned char *buf, uint32_t size); -#endif \ No newline at end of file +#endif diff --git a/tests/unit/component-instantiation/test_instantiation.cc b/tests/unit/component-instantiation/test_instantiation.cc index dd2ae74bb3..ff481a2863 100644 --- a/tests/unit/component-instantiation/test_instantiation.cc +++ b/tests/unit/component-instantiation/test_instantiation.cc @@ -174,55 +174,79 @@ class ComponentInstantiationTest : public testing::Test } } - virtual void test_instance_type(WASMComponentTypeInstance *type_instance, WASMComponentInstType *instance_type_definition, WASMComponentTypeInstance **types) { - WASMComponentInstanceDeclTypeSize instance_decl_size; - memset(&instance_decl_size, 0, sizeof(WASMComponentInstanceDeclTypeSize)); - uint32 size = 0, decl_idx, type_idx = 0, func_idx = 0, export_idx = 0;; - WASMComponentInstTypeInstance *instance_type_instance = type_instance->type_specific.instance; - WASMComponentInstDecl instance_decl; - size += wasm_get_inst_decl_size(instance_type_definition, &instance_decl_size); - ASSERT_TRUE(type_instance); - ASSERT_EQ(type_instance->type, COMPONENT_VAL_TYPE_INSTANCE); - ASSERT_TRUE(type_instance->type_specific.instance->types); - ASSERT_TRUE(type_instance->type_specific.instance->funcs); - ASSERT_TRUE(type_instance->type_specific.instance->exports); - ASSERT_EQ(type_instance->type_specific.instance->types_count, instance_decl_size.types_count); - ASSERT_EQ(type_instance->type_specific.instance->func_count, instance_decl_size.func_count); - ASSERT_EQ(type_instance->type_specific.instance->exports_count, instance_decl_size.exports_count); - - for (decl_idx = 0; decl_idx < instance_type_definition->count; decl_idx++) { - instance_decl = instance_type_definition->instance_decls[decl_idx]; - switch (instance_decl.tag) - { - case WASM_COMP_COMPONENT_DECL_INSTANCE_TYPE: - type_idx++; - break; - case WASM_COMP_COMPONENT_DECL_INSTANCE_ALIAS: - type_idx++; - break; - case WASM_COMP_COMPONENT_DECL_INSTANCE_EXPORTDECL: - switch(instance_decl.decl.export_decl->extern_desc->type) { - case WASM_COMP_EXTERN_TYPE: - type_idx++; - ASSERT_EQ(instance_type_instance->exports[export_idx].export_name, instance_decl.decl.export_decl->export_name); - ASSERT_EQ(instance_type_instance->exports[export_idx].type, instance_decl.decl.export_decl->extern_desc->type); - export_idx++; - break; - case WASM_COMP_EXTERN_FUNC: - func_idx++; - ASSERT_EQ(instance_type_instance->exports[export_idx].export_name, instance_decl.decl.export_decl->export_name); - ASSERT_EQ(instance_type_instance->exports[export_idx].type, instance_decl.decl.export_decl->extern_desc->type); - export_idx++; - break; - default: - break; + virtual void test_instance_type( + WASMComponentTypeInstance *type_instance, + WASMComponentInstType *instance_type_definition, + WASMComponentTypeInstance **types) + { + WASMComponentInstanceDeclTypeSize instance_decl_size; + memset(&instance_decl_size, 0, + sizeof(WASMComponentInstanceDeclTypeSize)); + uint64 size = 0; + uint32 decl_idx, type_idx = 0, func_idx = 0, export_idx = 0; + WASMComponentInstTypeInstance *instance_type_instance = + type_instance->type_specific.instance; + WASMComponentInstDecl instance_decl; + ASSERT_TRUE(wasm_get_inst_decl_size(instance_type_definition, + &instance_decl_size, &size)); + ASSERT_TRUE(type_instance); + ASSERT_EQ(type_instance->type, COMPONENT_VAL_TYPE_INSTANCE); + ASSERT_TRUE(type_instance->type_specific.instance->types); + ASSERT_TRUE(type_instance->type_specific.instance->funcs); + ASSERT_TRUE(type_instance->type_specific.instance->exports); + ASSERT_EQ(type_instance->type_specific.instance->types_count, + instance_decl_size.types_count); + ASSERT_EQ(type_instance->type_specific.instance->func_count, + instance_decl_size.func_count); + ASSERT_EQ(type_instance->type_specific.instance->exports_count, + instance_decl_size.exports_count); + + for (decl_idx = 0; decl_idx < instance_type_definition->count; + decl_idx++) { + instance_decl = instance_type_definition->instance_decls[decl_idx]; + switch (instance_decl.tag) { + case WASM_COMP_COMPONENT_DECL_INSTANCE_TYPE: + type_idx++; + break; + case WASM_COMP_COMPONENT_DECL_INSTANCE_ALIAS: + type_idx++; + break; + case WASM_COMP_COMPONENT_DECL_INSTANCE_EXPORTDECL: + switch (instance_decl.decl.export_decl->extern_desc->type) { + case WASM_COMP_EXTERN_TYPE: + type_idx++; + ASSERT_EQ( + instance_type_instance->exports[export_idx] + .export_name, + instance_decl.decl.export_decl->export_name); + ASSERT_EQ( + instance_type_instance->exports[export_idx] + .type, + instance_decl.decl.export_decl->extern_desc + ->type); + export_idx++; + break; + case WASM_COMP_EXTERN_FUNC: + func_idx++; + ASSERT_EQ( + instance_type_instance->exports[export_idx] + .export_name, + instance_decl.decl.export_decl->export_name); + ASSERT_EQ( + instance_type_instance->exports[export_idx] + .type, + instance_decl.decl.export_decl->extern_desc + ->type); + export_idx++; + break; + default: + break; + } + default: + break; } - default: - break; } - } } - }; TEST_F(ComponentInstantiationTest, TestGetIndexCount) @@ -544,6 +568,7 @@ TEST_F(ComponentInstantiationTest, TestResolveCoreInstance) WASMComponentIndexCount index_count; memset(&index_count, 0, sizeof(WASMComponentIndexCount)); index_count.core_instances = 3; + index_count.defined_core_instances = 2; index_count.core_modules = 1; index_count.core_functions = 1; index_count.core_tables = 1; @@ -614,6 +639,8 @@ TEST_F(ComponentInstantiationTest, TestResolveCoreInstance) WASMComponent *comp_1 = component->sections[3].parsed.component; ASSERT_EQ(comp_1->sections[2].id, 1); WASMModule *core_module_0 = (WASMModule *)component->sections[3].parsed.component->sections[2].parsed.core_module->module_handle; + dummy_core_func_2.u.func->func_type = + core_module_0->import_functions[0].u.function.func_type; ASSERT_EQ(comp_1->sections[5].id, 2); instance_section = comp_1->sections[5].parsed.core_instance_section; @@ -673,8 +700,22 @@ TEST_F(ComponentInstantiationTest, TestResolveCoreInstance) ASSERT_EQ(comp_instance_2->core_module_instances[0]->export_func_count , 1); ASSERT_EQ(comp_instance_2->core_module_instances[0]->export_functions[0].function , &dummy_core_func_2); ASSERT_TRUE(comp_instance_2->core_module_instances[1]->e->functions[0].is_import_func); - ASSERT_EQ(*(comp_instance_2->core_module_instances[1]->e->functions[0].local_types), 8); - ASSERT_EQ(*(comp_instance_2->core_module_instances[1]->e->functions[0].local_offsets), 16); + ASSERT_EQ(comp_instance_2->core_module_instances[1] + ->e->functions[0] + .import_func_inst, + &dummy_core_func_2); + ASSERT_EQ(comp_instance_2->core_module_instances[1] + ->e->functions[0] + .u.func_import->func_type, + core_module_0->import_functions[0].u.function.func_type); + ASSERT_EQ(*(comp_instance_2->core_module_instances[1] + ->e->functions[0] + .import_func_inst->local_types), + 8); + ASSERT_EQ(*(comp_instance_2->core_module_instances[1] + ->e->functions[0] + .import_func_inst->local_offsets), + 16); wasm_component_deinstantiate(comp_instance); @@ -738,7 +779,9 @@ TEST_F(ComponentInstantiationTest, TestResolveCanons) memset(&index_count, 0, sizeof(index_count)); index_count.types = 1; index_count.functions = 2; + index_count.defined_functions = 1; index_count.core_functions = 2; + index_count.defined_core_functions = 1; WASMComponentInstance *comp_instance = wasm_component_instance_allocate(&index_count, NULL, 0); comp_instance->functions[0] = &dummy_comp_func; @@ -813,7 +856,14 @@ TEST_F(ComponentInstantiationTest, TestInstantiateInternal) ASSERT_EQ(comp_instance->component_instances[2]->core_module_instances[0]->export_table_count, 1); // Test core module instantiation (test imports are resolved properly) - ASSERT_EQ(comp_instance->component_instances[2]->core_module_instances[2]->e->functions[0].u.func, comp_instance->component_instances[2]->core_functions[0]->u.func ); + ASSERT_EQ(comp_instance->component_instances[2] + ->core_module_instances[2] + ->e->functions[0] + .u.func_import->func_type, + comp_instance->component_instances[2] + ->core_module_instances[2] + ->module->import_functions[0] + .u.function.func_type); ASSERT_EQ(comp_instance->component_instances[2]->core_module_instances[2]->e->functions[0].import_func_inst, comp_instance->component_instances[2]->core_functions[0]); ASSERT_EQ(comp_instance->component_instances[2]->core_module_instances[2]->e->functions[0].import_module_inst, comp_instance->component_instances[2]->core_functions[0]->module_instance); @@ -1011,44 +1061,70 @@ TEST_F(ComponentInstantiationTest, TestTypesInstantiation) // Test resource type instantiation ASSERT_EQ(comp_instance->types[2]->type_specific.instance->types[2]->type, COMPONENT_VAL_TYPE_RESOURCE_SYNC); ASSERT_FALSE(strcmp(comp_instance->types[2]->type_specific.instance->types[2]->type_specific.resource->name , "output-stream")); - ASSERT_FALSE(strcmp(comp_instance->types[2]->type_specific.instance->types[2]->type_specific.resource->interface_name, "wasi:io/streams@0.2.3")); + ASSERT_EQ(comp_instance->types[2] + ->type_specific.instance->types[2] + ->type_specific.resource->interface_name, + nullptr); + ASSERT_FALSE(comp_instance->types[2] + ->type_specific.instance->types[2] + ->type_specific.resource->is_host); ASSERT_TRUE(comp_instance->types[2]->type_specific.instance->types[2]->type_specific.resource->drop_method); - - + ASSERT_FALSE(strcmp(comp_instance->component_instances[3] + ->types[0] + ->type_specific.resource->interface_name, + "wasi:io/streams@0.2.3")); + ASSERT_TRUE(comp_instance->component_instances[3] + ->types[0] + ->type_specific.resource->is_host); } TEST_F(ComponentInstantiationTest, TestInstantiateSmokeTest) { - std::vector component_files = { - "complex.wasm", - "hello_wasi.wasm", - "hello_wasip1_hacked.component.wasm", - "logging_service.component.wasm", - "processor_and_logging_merged_wac_plug.wasm", - "surface_and_geometry_0_2_0.wasm", - "surface_and_geometry.wasm", - "wasi_server.wasm", - "wasip2_tcp_server.wasm", - "add.wasm", - "sampletypes.wasm" - }; - - WASMComponent *component; - WASMComponentInstance *comp_instance; - - for (uint32 i = 0; i < component_files.size(); i++) - { - const char *file_name = component_files[i].c_str(); - component = load_component_from_candidates(file_name); - ASSERT_NE(component, nullptr) << "Failed to load/parse component from candidates: "<< file_name; - - // Test component is instantiated - printf("Instantiate %s\n", file_name); - comp_instance = wasm_component_instantiate_internal(component, NULL, error_buf, sizeof(error_buf)); - ASSERT_NE(comp_instance, nullptr) << "Failed to instantiate " << file_name; - wasm_component_deinstantiate(comp_instance); + std::vector component_files = { + "complex.wasm", + "hello_wasi.wasm", + "logging_service.component.wasm", + "processor_and_logging_merged_wac_plug.wasm", + "surface_and_geometry_0_2_0.wasm", + "surface_and_geometry.wasm", + "wasi_server.wasm", + "wasip2_tcp_server.wasm", + "add.wasm", + "sampletypes.wasm" + }; + + WASMComponent *component; + WASMComponentInstance *comp_instance; + + for (uint32 i = 0; i < component_files.size(); i++) { + const char *file_name = component_files[i].c_str(); + component = load_component_from_candidates(file_name); + ASSERT_NE(component, nullptr) + << "Failed to load/parse component from candidates: " << file_name; + + // Test component is instantiated + printf("Instantiate %s\n", file_name); + comp_instance = wasm_component_instantiate_internal( + component, NULL, error_buf, sizeof(error_buf)); + ASSERT_NE(comp_instance, nullptr) + << "Failed to instantiate " << file_name; + wasm_component_deinstantiate(comp_instance); + } +} - } +TEST_F(ComponentInstantiationTest, TestRejectMissingCoreImportArguments) +{ + char file_name[] = "hello_wasip1_hacked.component.wasm"; + WASMComponent *component = load_component_from_candidates(file_name); + ASSERT_NE(component, nullptr) + << "Failed to load/parse component from candidates."; + + error_buf[0] = '\0'; + WASMComponentInstance *comp_instance = wasm_component_instantiate_internal( + component, NULL, error_buf, sizeof(error_buf)); + ASSERT_EQ(comp_instance, nullptr); + ASSERT_STREQ(error_buf, + "Import 0 of core instance not found in arguments\n"); } TEST_F(ComponentInstantiationTest, TestInstantiateCanonFunctions) @@ -1104,5 +1180,4 @@ TEST_F(ComponentInstantiationTest, TestInstantiateCanonFunctions) ASSERT_TRUE(comp_instance->component_instances[7]->core_module_instances[7]->e->functions[8].is_canon_func); ASSERT_EQ(comp_instance->component_instances[7]->core_module_instances[7]->e->functions[8].canon_type, WASM_COMP_CANON_RESOURCE_DROP); ASSERT_EQ(comp_instance->component_instances[7]->core_module_instances[7]->e->functions[8].resource, comp_instance->component_instances[7]->types[11]->type_specific.resource); - -} \ No newline at end of file +} diff --git a/tests/unit/component/CMakeLists.txt b/tests/unit/component/CMakeLists.txt index ce410d3b39..4cfcfa9186 100644 --- a/tests/unit/component/CMakeLists.txt +++ b/tests/unit/component/CMakeLists.txt @@ -11,12 +11,13 @@ add_definitions (-Dattr_container_malloc=malloc) add_definitions (-Dattr_container_free=free) set (WAMR_BUILD_INTERP 1) +set (WAMR_BUILD_FAST_INTERP 1) set (WAMR_BUILD_LIBC_WASI 1) set (WAMR_BUILD_COMPONENT_MODEL 1) set (WAMR_BUILD_APP_FRAMEWORK 1) set (WAMR_BUILD_AOT 0) set (WAMR_BUILD_REF_TYPES 1) -set (WAMR_BUILD_MULTI_MODULE 0) +set (WAMR_BUILD_MULTI_MODULE 1) set (WAMR_BUILD_LOAD_CUSTOM_SECTION 1) set (WAMR_BUILD_SIMD 0) diff --git a/tests/unit/component/helpers.cc b/tests/unit/component/helpers.cc index 861911887a..c3f42c066b 100644 --- a/tests/unit/component/helpers.cc +++ b/tests/unit/component/helpers.cc @@ -46,18 +46,16 @@ ComponentHelper::do_teardown() component_instantiated = false; } - if (component_init) { - printf("Starting to unload component\n"); - if (component) { - wasm_component_free(component); - component = NULL; - } - if (component_raw) { - BH_FREE(component_raw); - component_raw = NULL; - } - component_init = false; + printf("Starting to unload component\n"); + if (component) { + wasm_component_unload(component); + component = NULL; } + if (component_raw) { + BH_FREE(component_raw); + component_raw = NULL; + } + component_init = false; if (runtime_init) { printf("Starting to destroy runtime\n"); @@ -88,57 +86,22 @@ bool ComponentHelper::load_component() { printf("Loading wasm component in memory\n"); - - // Allocate component structure if not already allocated - if (!component) { - component = (WASMComponent *)wasm_runtime_malloc(sizeof(WASMComponent)); - if (!component) { - printf("Failed to allocate component structure\n"); - return false; - } - memset(component, 0, sizeof(WASMComponent)); - } - - // First decode the header - if (!wasm_decode_header(component_raw, wasm_file_size, &component->header)) { - printf("Not a valid WASM component file (header mismatch).\n"); - BH_FREE(component_raw); - component_raw = NULL; - wasm_runtime_free(component); - component = NULL; - return false; - } - // Second check if it's a valid component - if (!is_wasm_component(component->header)) { - printf("Not a valid WASM component file (header mismatch).\n"); - BH_FREE(component_raw); - component_raw = NULL; - wasm_runtime_free(component); - component = NULL; - return false; - } - - // Parse the component sections LoadArgs load_args = {0, false, false, false, false}; - char name_buf[32]; - std::memset(name_buf, 0, sizeof(name_buf)); - std::snprintf(name_buf, sizeof(name_buf), "%s", "Test Component"); - load_args.name = name_buf; // provide non-null, mutable name as required by loader + static char test_component_name[] = "Test Component"; + load_args.name = test_component_name; load_args.wasm_binary_freeable = false; load_args.clone_wasm_binary = false; load_args.no_resolve = false; load_args.is_component = true; - - if (!wasm_component_parse_sections(component_raw, wasm_file_size, component, &load_args, 0)) { - printf("Failed to parse WASM component sections.\n"); - BH_FREE(component_raw); - component_raw = NULL; - wasm_runtime_free(component); - component = NULL; + + component = wasm_component_load(component_raw, wasm_file_size, &load_args, + error_buf, sizeof(error_buf)); + if (!component) { + printf("Failed to load WASM component: %s\n", error_buf); return false; } - + printf("Component loaded successfully with %u sections\n", component->section_count); component_init = true; @@ -159,9 +122,14 @@ bool ComponentHelper::is_loaded() const { void ComponentHelper::reset_component() { if (component) { - wasm_component_free(component); + wasm_component_unload(component); component = NULL; } + if (component_raw) { + BH_FREE(component_raw); + component_raw = NULL; + } + wasm_file_size = 0; component_init = false; } @@ -202,7 +170,8 @@ bool ComponentHelper::instantiate_component() { wasm_runtime_instantiation_args_set_default_stack_size(inst_args, stack_size); wasm_runtime_instantiation_args_set_host_managed_heap_size(inst_args, heap_size); - this->component_inst = wasm_component_instantiate(this->component, this->error_buf, 128); + this->component_inst = wasm_component_instantiate_ex2( + this->component, inst_args, this->error_buf, sizeof(this->error_buf)); wasm_runtime_instantiation_args_destroy(inst_args); diff --git a/tests/unit/component/test_binary_parser.cc b/tests/unit/component/test_binary_parser.cc index 17bd8d16ca..c7629bdfe7 100644 --- a/tests/unit/component/test_binary_parser.cc +++ b/tests/unit/component/test_binary_parser.cc @@ -63,6 +63,44 @@ TEST_F(BinaryParserTest, TestAllComponentsLoadAndUnload) } } +TEST_F(BinaryParserTest, TestEmbeddedCoreModulesDoNotAccumulateAcrossLoads) +{ + mem_alloc_info_t first_unload = {}; + + /* The pool allocator can coalesce pre-existing free blocks during the + * first load/unload cycle, so use that cycle as the stable baseline. A + * core module left in the global multi-module registry consumes more pool + * space on every later component load. */ + for (uint32_t i = 0; i < 8; i++) { + helper->reset_component(); + if (helper->component_raw) { + BH_FREE(helper->component_raw); + helper->component_raw = NULL; + } + + ASSERT_TRUE(helper->read_wasm_file("add.wasm")); + ASSERT_TRUE(helper->load_component()); + ASSERT_TRUE(helper->is_loaded()); + + helper->reset_component(); + if (helper->component_raw) { + BH_FREE(helper->component_raw); + helper->component_raw = NULL; + } + + mem_alloc_info_t after_unload = {}; + ASSERT_TRUE(wasm_runtime_get_mem_alloc_info(&after_unload)); + if (i == 0) { + first_unload = after_unload; + } + else { + EXPECT_GE(after_unload.total_free_size, + first_unload.total_free_size) + << "embedded core module retained after iteration " << i; + } + } +} + TEST_F(BinaryParserTest, TestLoadCorruptComponent) { // Corrupt header and expect load failure @@ -141,4 +179,4 @@ TEST_F(BinaryParserTest, TestSectionAliasIndividual) } ASSERT_TRUE(found); -} \ No newline at end of file +} diff --git a/tests/unit/component/test_core_import_resolution.cc b/tests/unit/component/test_core_import_resolution.cc new file mode 100644 index 0000000000..c216dce01e --- /dev/null +++ b/tests/unit/component/test_core_import_resolution.cc @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2026 Matt Hargett. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include + +#include +#include +#include + +extern "C" { +#include "wasm_component_runtime.h" +#include "wasm_export.h" +#include "wasm_runtime.h" +} + +namespace { + +std::unordered_set live_allocations; +int fail_after = -1; +size_t invalid_frees = 0; + +void * +test_malloc(unsigned int size) +{ + if (fail_after == 0) { + return nullptr; + } + if (fail_after > 0) { + fail_after--; + } + + void *result = std::malloc(size ? size : 1); + if (result) { + live_allocations.insert(result); + } + return result; +} + +void * +test_realloc(void *ptr, unsigned int size) +{ + if (!ptr) { + return test_malloc(size); + } + if (live_allocations.find(ptr) == live_allocations.end()) { + invalid_frees++; + return nullptr; + } + if (fail_after == 0) { + return nullptr; + } + if (fail_after > 0) { + fail_after--; + } + + live_allocations.erase(ptr); + void *result = std::realloc(ptr, size ? size : 1); + if (!result) { + live_allocations.insert(ptr); + return nullptr; + } + live_allocations.insert(result); + return result; +} + +void +test_free(void *ptr) +{ + if (!ptr) { + return; + } + if (live_allocations.erase(ptr) == 0) { + invalid_frees++; + return; + } + std::free(ptr); +} + +class CoreImportResolutionTest : public testing::Test +{ + protected: + void SetUp() override + { + RuntimeInitArgs init_args = {}; + init_args.mem_alloc_type = Alloc_With_Allocator; + init_args.mem_alloc_option.allocator.malloc_func = + reinterpret_cast(test_malloc); + init_args.mem_alloc_option.allocator.realloc_func = + reinterpret_cast(test_realloc); + init_args.mem_alloc_option.allocator.free_func = + reinterpret_cast(test_free); + fail_after = -1; + invalid_frees = 0; + live_allocations.clear(); + ASSERT_TRUE(wasm_runtime_full_init(&init_args)); + baseline = live_allocations.size(); + } + + void TearDown() override + { + fail_after = -1; + wasm_runtime_destroy(); + EXPECT_EQ(invalid_frees, 0u); + EXPECT_TRUE(live_allocations.empty()); + } + + void prepare_resolution(uint32_t instance_idx) + { + import.kind = IMPORT_KIND_FUNC; + import.u.names.module_name = module_name; + import.u.names.field_name = field_name; + + argument_name.name_len = 3; + argument_name.name = module_name; + argument.name = &argument_name; + argument.idx.instance_idx = instance_idx; + expression.with_args.arg_len = 1; + expression.with_args.args = &argument; + + exported_function.name = field_name; + exported_function.function = &function; + core_instance.export_func_count = 1; + core_instance.export_functions = &exported_function; + core_instances[0] = &core_instance; + component_instance.core_module_instances = core_instances; + component_instance.core_module_instances_count = 1; + } + + size_t baseline = 0; + char module_name[4] = "mod"; + char field_name[6] = "field"; + char error[256] = {}; + WASMImport import = {}; + WASMComponentCoreName argument_name = {}; + WASMComponentInstArg argument = {}; + WASMInstExpr expression = {}; + WASMFunctionInstance function = {}; + WASMExportFuncInstance exported_function = {}; + WASMModuleInstance core_instance = {}; + WASMModuleInstance *core_instances[1] = {}; + WASMComponentInstance component_instance = {}; +}; + +TEST_F(CoreImportResolutionTest, RejectsIndexEqualToCurrentInstanceCount) +{ + prepare_resolution(1); + + EXPECT_EQ(wasm_import_find_in_args(&import, &expression, + &component_instance, error, + sizeof(error)), + nullptr); + EXPECT_NE(strstr(error, "not yet defined"), nullptr) << error; + EXPECT_EQ(live_allocations.size(), baseline); +} + +TEST_F(CoreImportResolutionTest, AllocationFailureIsReportedWithoutLeak) +{ + prepare_resolution(0); + fail_after = 0; + + EXPECT_EQ(wasm_import_find_in_args(&import, &expression, + &component_instance, error, + sizeof(error)), + nullptr); + EXPECT_NE(strstr(error, "allocate"), nullptr) << error; + EXPECT_EQ(live_allocations.size(), baseline); + + fail_after = -1; + error[0] = '\0'; + WASMCoreExport *resolved = wasm_import_find_in_args( + &import, &expression, &component_instance, error, sizeof(error)); + ASSERT_NE(resolved, nullptr) << error; + EXPECT_EQ(resolved->kind, IMPORT_KIND_FUNC); + EXPECT_EQ(resolved->exp.func_instance, &function); + wasm_runtime_free(resolved); + EXPECT_EQ(live_allocations.size(), baseline); +} + +} // namespace diff --git a/tests/unit/component/test_host_imports.cc b/tests/unit/component/test_host_imports.cc new file mode 100644 index 0000000000..6d03809e5b --- /dev/null +++ b/tests/unit/component/test_host_imports.cc @@ -0,0 +1,893 @@ +/* + * Copyright (C) 2026 Matt Hargett. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include + +#include "helpers.h" + +extern "C" { +#include "wasm_component_host_resource.h" +#include "wasm_component_resource.h" +#include "wasm_native.h" +} + +namespace { + +static void +host_log(wasm_exec_env_t exec_env, uint64_t *canonical_cells) +{ + (void)exec_env; + (void)canonical_cells; +} + +static bool +host_resource_drop(void *, uint32_t) +{ + return true; +} + +struct HostImportDropState { + uint32_t first_count; + uint32_t first_rep; + uint32_t second_count; + uint32_t second_rep; +}; + +struct CustomDataState { + uint32_t marker; + uint32_t raw_calls; + uint32_t drops; + uint32_t last_rep; +}; + +class HostResourceTableScope +{ + public: + bool Initialize() + { + initialized_ = instantiate_host_resource_table(); + return initialized_; + } + + ~HostResourceTableScope() + { + if (initialized_) { + destroy_host_resource_table(); + } + } + + private: + bool initialized_ = false; +}; + +static uint32_t +replace_binary_text(uint8_t *bytes, uint32_t size, const char *from, + const char *to) +{ + size_t length = strlen(from); + uint32_t replacements = 0; + + if (!bytes || strlen(to) != length || length > size) { + return 0; + } + for (uint32_t offset = 0; offset <= size - length; offset++) { + if (memcmp(bytes + offset, from, length) == 0) { + memcpy(bytes + offset, to, length); + replacements++; + offset += (uint32_t)length - 1; + } + } + return replacements; +} + +static void +observe_custom_data(wasm_exec_env_t exec_env, uint64_t *canonical_cells) +{ + auto *component_state = static_cast( + wasm_component_get_custom_data_from_exec_env(exec_env)); + auto *module_state = static_cast( + wasm_runtime_get_custom_data(wasm_runtime_get_module_inst(exec_env))); + + if (!component_state || component_state != module_state) { + canonical_cells[0] = 0; + return; + } + component_state->raw_calls++; + canonical_cells[0] = component_state->marker; +} + +static bool +observe_custom_data_drop(void *attachment, uint32_t rep) +{ + auto *state = static_cast(attachment); + + if (!state) { + return false; + } + state->drops++; + state->last_rep = rep; + return true; +} + +static void +newer_random_u64(wasm_exec_env_t exec_env, uint64_t *canonical_cells) +{ + (void)exec_env; + canonical_cells[0] = 0x0123456789ABCDEFULL; +} + +static void +first_use_item(wasm_exec_env_t exec_env, uint64_t *canonical_cells) +{ + (void)exec_env; + (void)canonical_cells; +} + +static void +second_use_item(wasm_exec_env_t exec_env, uint64_t *canonical_cells) +{ + (void)exec_env; + (void)canonical_cells; +} + +static bool +first_item_drop(void *attachment, uint32_t rep) +{ + auto *state = static_cast(attachment); + + if (!state) { + return false; + } + state->first_count++; + state->first_rep = rep; + return true; +} + +static bool +second_item_drop(void *attachment, uint32_t rep) +{ + auto *state = static_cast(attachment); + + if (!state) { + return false; + } + state->second_count++; + state->second_rep = rep; + return true; +} + +static WASMComponentResourceInstance * +find_direct_resource(WASMComponentInstance *instance) +{ + if (!instance) { + return nullptr; + } + for (uint32_t idx = 0; idx < instance->types_count; idx++) { + WASMComponentTypeInstance *type = instance->types[idx]; + + if (type + && (type->type == COMPONENT_VAL_TYPE_RESOURCE_SYNC + || type->type == COMPONENT_VAL_TYPE_RESOURCE_ASYNC)) { + return type->type_specific.resource; + } + } + return nullptr; +} + +static WASMComponentResourceInstance * +find_template_resource(WASMComponentInstTypeInstance *instance_type) +{ + if (!instance_type) { + return nullptr; + } + for (uint32_t idx = 0; idx < instance_type->types_count; idx++) { + WASMComponentTypeInstance *type = instance_type->types[idx]; + + if (type + && (type->type == COMPONENT_VAL_TYPE_RESOURCE_SYNC + || type->type == COMPONENT_VAL_TYPE_RESOURCE_ASYNC)) { + return type->type_specific.resource; + } + } + return nullptr; +} + +static WASMComponentResourceInstance * +find_resource(WASMComponentInstance *instance, const char *interface_name, + const char *resource_name) +{ + if (!instance) { + return nullptr; + } + for (uint32_t idx = 0; idx < instance->types_count; idx++) { + WASMComponentTypeInstance *type = instance->types[idx]; + if (!type + || (type->type != COMPONENT_VAL_TYPE_RESOURCE_SYNC + && type->type != COMPONENT_VAL_TYPE_RESOURCE_ASYNC) + || !type->type_specific.resource + || !type->type_specific.resource->interface_name + || !type->type_specific.resource->name) { + continue; + } + WASMComponentResourceInstance *resource = type->type_specific.resource; + if (strcmp(resource->interface_name, interface_name) == 0 + && strcmp(resource->name, resource_name) == 0) { + return resource; + } + } + for (uint32_t idx = 0; idx < instance->defined_instances_count; idx++) { + WASMComponentResourceInstance *resource = find_resource( + instance->defined_instances[idx], interface_name, resource_name); + if (resource) { + return resource; + } + } + return nullptr; +} + +class ComponentHostImportsTest : public testing::Test +{ + protected: + ComponentHelper helper; + + void SetUp() override { helper.do_setup(); } + void TearDown() override { helper.do_teardown(); } +}; + +TEST_F(ComponentHostImportsTest, ResolvesStaticCustomAndWasiInterfaces) +{ + ASSERT_TRUE(helper.runtime_init); + + NativeSymbol host_symbols[] = { + { "log", (void *)host_log, "(iii)", nullptr }, + }; + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "test:project/imported-interface@0.1.0", host_symbols, + (uint32_t)(sizeof(host_symbols) / sizeof(host_symbols[0])))); + const char *signature = nullptr; + void *attachment = nullptr; + bool call_conv_raw = false; + EXPECT_EQ(wasm_native_resolve_symbol_exact( + "test:project/imported-interface@0.1.0", "_log", nullptr, + &signature, &attachment, &call_conv_raw), + nullptr); + + ASSERT_TRUE(helper.read_wasm_file("complex_with_host.wasm")); + ASSERT_TRUE(helper.load_component()); + ASSERT_TRUE(wasm_component_register_host_resource_drop_callback( + helper.component, "wasi:io/error@0.2.3", "error", host_resource_drop)); + + struct InstantiationArgs2 *args = nullptr; + ASSERT_TRUE(wasm_runtime_instantiation_args_create(&args)); + ASSERT_NE(args, nullptr); + wasm_runtime_instantiation_args_set_default_stack_size(args, 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size(args, 64 * 1024); + uint32_t custom_data = 0xC0DEC0DE; + wasm_runtime_instantiation_args_set_custom_data(args, &custom_data); + + helper.component_inst = wasm_component_instantiate_ex2( + helper.component, args, helper.error_buf, sizeof(helper.error_buf)); + wasm_runtime_instantiation_args_destroy(args); + ASSERT_NE(helper.component_inst, nullptr) << helper.error_buf; + helper.component_instantiated = true; + + EXPECT_EQ(wasm_component_get_custom_data(helper.component_inst), + &custom_data); + WASMComponentResourceInstance *error_resource = + find_resource(helper.component_inst, "wasi:io/error@0.2.3", "error"); + ASSERT_NE(error_resource, nullptr); + EXPECT_TRUE(error_resource->is_builtin_wasi); + EXPECT_EQ(error_resource->host_drop_callback, host_resource_drop); + EXPECT_EQ(error_resource->host_drop_attachment, &custom_data); + ASSERT_GE(helper.component_inst->defined_instances_count, 9u); + ASSERT_GE(helper.component_inst->component_instances_count, 9u); + + /* Rust-generated WASI components export a resource from its owning + * interface, then outer-alias that nominal type into consumer interfaces. + * The consumer must retain the exact owner type rather than clone and + * rebind it as another host resource. */ + ASSERT_GT(helper.component_inst->types_count, 2u); + WASMComponentInstance *error_interface = + helper.component_inst->component_instances[1]; + WASMComponentInstance *streams_interface = + helper.component_inst->component_instances[2]; + ASSERT_NE(error_interface, nullptr); + ASSERT_NE(streams_interface, nullptr); + ASSERT_GT(error_interface->types_count, 0u); + ASSERT_GT(streams_interface->types_count, 1u); + WASMComponentTypeInstance *nominal_error_type = + helper.component_inst->types[2]; + EXPECT_EQ(error_interface->types[0], nominal_error_type); + EXPECT_EQ(streams_interface->types[0], nominal_error_type); + EXPECT_EQ(streams_interface->types[1], nominal_error_type); + EXPECT_EQ(streams_interface->types[0]->type_specific.resource, + error_resource); + EXPECT_EQ(streams_interface->types[0]->type_specific.resource->drop_method, + error_resource->drop_method); + + WASMComponentInstance *custom_interface = + helper.component_inst->component_instances[0]; + ASSERT_NE(custom_interface, nullptr); + ASSERT_EQ(custom_interface->functions_count, 1u); + ASSERT_NE(custom_interface->functions[0]->core_func, nullptr); + EXPECT_EQ(custom_interface->functions[0] + ->core_func->u.func_import->func_ptr_linked, + (void *)host_log); + EXPECT_TRUE(custom_interface->functions[0] + ->core_func->u.func_import->call_conv_raw); +} + +TEST_F(ComponentHostImportsTest, RejectsUnversionedCustomInterfaceFallback) +{ + ASSERT_TRUE(helper.runtime_init); + + NativeSymbol host_symbols[] = { + { "log", (void *)host_log, "(iii)", nullptr }, + }; + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "test:project/imported-interface", host_symbols, + (uint32_t)(sizeof(host_symbols) / sizeof(host_symbols[0])))); + + ASSERT_TRUE(helper.read_wasm_file("complex_with_host.wasm")); + ASSERT_TRUE(helper.load_component()); + + struct InstantiationArgs2 *args = nullptr; + ASSERT_TRUE(wasm_runtime_instantiation_args_create(&args)); + wasm_runtime_instantiation_args_set_default_stack_size(args, 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size(args, 64 * 1024); + WASMComponentInstance *instance = wasm_component_instantiate_ex2( + helper.component, args, helper.error_buf, sizeof(helper.error_buf)); + wasm_runtime_instantiation_args_destroy(args); + + EXPECT_EQ(instance, nullptr); + if (instance) { + wasm_component_deinstantiate(instance); + } + EXPECT_NE( + strstr(helper.error_buf, "Function log not found in native symbols"), + nullptr) + << helper.error_buf; +} + +TEST_F(ComponentHostImportsTest, + ExactStaticWasiHostPrecedesBuiltInVersionFallback) +{ + NativeSymbol symbols[] = { + { "get-random-u64", (void *)newer_random_u64, "()I", nullptr }, + }; + + ASSERT_TRUE(helper.runtime_init); + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "wasi:random/random@0.3.0", symbols, + (uint32_t)(sizeof(symbols) / sizeof(symbols[0])))); + ASSERT_TRUE(helper.read_wasm_file("exact_newer_wasi_host.wasm")); + ASSERT_TRUE(helper.load_component()); + + struct InstantiationArgs2 *args = nullptr; + ASSERT_TRUE(wasm_runtime_instantiation_args_create(&args)); + wasm_runtime_instantiation_args_set_default_stack_size(args, 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size(args, 64 * 1024); + helper.component_inst = wasm_component_instantiate_ex2( + helper.component, args, helper.error_buf, sizeof(helper.error_buf)); + wasm_runtime_instantiation_args_destroy(args); + ASSERT_NE(helper.component_inst, nullptr) << helper.error_buf; + helper.component_instantiated = true; + + ASSERT_EQ(helper.component_inst->component_instances_count, 1u); + WASMComponentInstance *host = helper.component_inst->component_instances[0]; + ASSERT_NE(host, nullptr); + ASSERT_EQ(host->functions_count, 1u); + ASSERT_NE(host->functions[0], nullptr); + ASSERT_NE(host->functions[0]->core_func, nullptr); + EXPECT_EQ(host->functions[0]->core_func->u.func_import->func_ptr_linked, + (void *)newer_random_u64); +} + +TEST_F(ComponentHostImportsTest, + ExactStaticWasiResourceRequiresCallbackAndPreservesTableCollision) +{ + NativeSymbol first_symbols[] = { + { "use-item", (void *)first_use_item, "(i)", nullptr }, + }; + NativeSymbol second_symbols[] = { + { "use-item", (void *)second_use_item, "(i)", nullptr }, + }; + HostImportDropState drop_state = {}; + + ASSERT_TRUE(helper.runtime_init); + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "wasi:alias/first@1.0.0", first_symbols, + (uint32_t)(sizeof(first_symbols) / sizeof(first_symbols[0])))); + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "wasi:alias/second@1.0.0", second_symbols, + (uint32_t)(sizeof(second_symbols) / sizeof(second_symbols[0])))); + ASSERT_TRUE(helper.read_wasm_file("shared_host_instance_type.wasm")); + ASSERT_EQ(replace_binary_text(helper.component_raw, helper.wasm_file_size, + "test:alias/", "wasi:alias/"), + 2u); + ASSERT_TRUE(helper.load_component()); + + struct InstantiationArgs2 *args = nullptr; + ASSERT_TRUE(wasm_runtime_instantiation_args_create(&args)); + wasm_runtime_instantiation_args_set_default_stack_size(args, 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size(args, 64 * 1024); + helper.component_inst = wasm_component_instantiate_ex2( + helper.component, args, helper.error_buf, sizeof(helper.error_buf)); + wasm_runtime_instantiation_args_destroy(args); + ASSERT_NE(helper.component_inst, nullptr) << helper.error_buf; + helper.component_instantiated = true; + + WASMComponentResourceInstance *resource = + find_resource(helper.component_inst, "wasi:alias/first@1.0.0", "item"); + ASSERT_NE(resource, nullptr); + EXPECT_TRUE(resource->is_host); + EXPECT_FALSE(resource->is_builtin_wasi); + EXPECT_EQ(resource->host_drop_callback, nullptr); + HostResourceTableScope table_scope; + ASSERT_TRUE(table_scope.Initialize()); + HostResourceTable *host_table = get_global_host_resource_table(); + ASSERT_NE(host_table, nullptr); + HostResource *collision = + host_resource_create(WASI_P2_TCP_SOCKET, sizeof(uint32_t)); + ASSERT_NE(collision, nullptr); + uint32_t representation = host_resource_table_add(host_table, collision); + ASSERT_NE(representation, 0u); + + WASMResourceHandle *handle = + wasm_create_resource_handle(resource, representation, true); + ASSERT_NE(handle, nullptr); + EXPECT_FALSE(wasm_drop_resource_handle(handle)); + EXPECT_NE(host_resource_table_get(host_table, representation), nullptr); + + ASSERT_TRUE(wasm_component_set_host_resource_drop_callback( + helper.component_inst, "wasi:alias/first@1.0.0", "item", + first_item_drop, &drop_state)); + handle = wasm_create_resource_handle(resource, representation, true); + ASSERT_NE(handle, nullptr); + EXPECT_TRUE(wasm_drop_resource_handle(handle)); + EXPECT_EQ(drop_state.first_count, 1u); + EXPECT_EQ(drop_state.first_rep, representation); + EXPECT_NE(host_resource_table_get(host_table, representation), nullptr); + EXPECT_EQ(host_resource_table_delete(host_table, representation), 1u); +} + +TEST_F(ComponentHostImportsTest, + MixedStaticAndBuiltInWasiResourceRequiresCallback) +{ + NativeSymbol custom_symbols[] = { + { "log", (void *)host_log, "(iii)", nullptr }, + }; + NativeSymbol override_symbols[] = { + { "[method]descriptor.get-type", (void *)host_log, "(ii)", nullptr }, + }; + HostImportDropState drop_state = {}; + + ASSERT_TRUE(helper.runtime_init); + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "test:project/imported-interface@0.1.0", custom_symbols, + (uint32_t)(sizeof(custom_symbols) / sizeof(custom_symbols[0])))); + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "wasi:filesystem/types@0.2.3", override_symbols, + (uint32_t)(sizeof(override_symbols) / sizeof(override_symbols[0])))); + ASSERT_TRUE(helper.read_wasm_file("complex_with_host.wasm")); + ASSERT_TRUE(helper.load_component()); + ASSERT_TRUE(wasm_component_register_host_resource_drop_callback( + helper.component, "wasi:io/error@0.2.3", "error", host_resource_drop)); + + struct InstantiationArgs2 *args = nullptr; + ASSERT_TRUE(wasm_runtime_instantiation_args_create(&args)); + wasm_runtime_instantiation_args_set_default_stack_size(args, 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size(args, 64 * 1024); + helper.component_inst = wasm_component_instantiate_ex2( + helper.component, args, helper.error_buf, sizeof(helper.error_buf)); + wasm_runtime_instantiation_args_destroy(args); + ASSERT_NE(helper.component_inst, nullptr) << helper.error_buf; + helper.component_instantiated = true; + + WASMComponentResourceInstance *resource = find_resource( + helper.component_inst, "wasi:filesystem/types@0.2.3", "descriptor"); + ASSERT_NE(resource, nullptr); + EXPECT_TRUE(resource->is_host); + EXPECT_FALSE(resource->is_builtin_wasi); + EXPECT_EQ(resource->host_drop_callback, nullptr); + WASMComponentResourceInstance *directory_entry_stream = + find_resource(helper.component_inst, "wasi:filesystem/types@0.2.3", + "directory-entry-stream"); + ASSERT_NE(directory_entry_stream, nullptr); + EXPECT_TRUE(directory_entry_stream->is_host); + EXPECT_FALSE(directory_entry_stream->is_builtin_wasi); + EXPECT_EQ(directory_entry_stream->host_drop_callback, nullptr); + + ASSERT_GT(helper.component_inst->component_instances_count, 7u); + WASMComponentInstance *filesystem_types = + helper.component_inst->component_instances[7]; + ASSERT_NE(filesystem_types, nullptr); + uint32_t static_function_count = 0; + uint32_t builtin_function_count = 0; + for (uint32_t idx = 0; idx < filesystem_types->functions_count; idx++) { + WASMFunctionInstance *core_func = + filesystem_types->functions[idx]->core_func; + ASSERT_NE(core_func, nullptr); + ASSERT_NE(core_func->u.func_import, nullptr); + if (core_func->u.func_import->func_ptr_linked == (void *)host_log) { + static_function_count++; + } + else { + ASSERT_NE(core_func->u.func_import->func_ptr_linked, nullptr); + builtin_function_count++; + } + } + EXPECT_EQ(static_function_count, 1u); + EXPECT_GT(builtin_function_count, 0u); + + HostResourceTableScope table_scope; + ASSERT_TRUE(table_scope.Initialize()); + HostResourceTable *host_table = get_global_host_resource_table(); + ASSERT_NE(host_table, nullptr); + HostResource *collision = + host_resource_create(WASI_P2_TCP_SOCKET, sizeof(uint32_t)); + ASSERT_NE(collision, nullptr); + uint32_t representation = host_resource_table_add(host_table, collision); + ASSERT_NE(representation, 0u); + + WASMResourceHandle *handle = + wasm_create_resource_handle(resource, representation, true); + ASSERT_NE(handle, nullptr); + EXPECT_FALSE(wasm_drop_resource_handle(handle)); + EXPECT_NE(host_resource_table_get(host_table, representation), nullptr); + + ASSERT_TRUE(wasm_component_set_host_resource_drop_callback( + helper.component_inst, "wasi:filesystem/types@0.2.3", "descriptor", + first_item_drop, &drop_state)); + handle = wasm_create_resource_handle(resource, representation, true); + ASSERT_NE(handle, nullptr); + EXPECT_TRUE(wasm_drop_resource_handle(handle)); + EXPECT_EQ(drop_state.first_count, 1u); + EXPECT_EQ(drop_state.first_rep, representation); + EXPECT_NE(host_resource_table_get(host_table, representation), nullptr); + EXPECT_EQ(host_resource_table_delete(host_table, representation), 1u); +} + +TEST_F(ComponentHostImportsTest, + CustomDataUpdatePropagatesButPreservesExplicitResourceAttachment) +{ + NativeSymbol symbols[] = { + { "observe", (void *)observe_custom_data, "()i", nullptr }, + }; + CustomDataState initial = {}; + CustomDataState updated = {}; + CustomDataState final_state = {}; + CustomDataState explicit_drop_state = {}; + WASMComponentPreparedCall *prepared_call = nullptr; + wasm_val_t result = {}; + + initial.marker = 11; + updated.marker = 22; + final_state.marker = 33; + explicit_drop_state.marker = 44; + + ASSERT_TRUE(helper.runtime_init); + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "test:context/host@1.0.0", symbols, + (uint32_t)(sizeof(symbols) / sizeof(symbols[0])))); + ASSERT_TRUE(helper.read_wasm_file("custom_data_update.wasm")); + ASSERT_TRUE(helper.load_component()); + ASSERT_TRUE(wasm_component_register_host_resource_drop_callback( + helper.component, "test:context/host@1.0.0", "item", + observe_custom_data_drop)); + + struct InstantiationArgs2 *args = nullptr; + ASSERT_TRUE(wasm_runtime_instantiation_args_create(&args)); + wasm_runtime_instantiation_args_set_default_stack_size(args, 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size(args, 64 * 1024); + wasm_runtime_instantiation_args_set_custom_data(args, &initial); + helper.component_inst = wasm_component_instantiate_ex2( + helper.component, args, helper.error_buf, sizeof(helper.error_buf)); + wasm_runtime_instantiation_args_destroy(args); + ASSERT_NE(helper.component_inst, nullptr) << helper.error_buf; + helper.component_instantiated = true; + + WASMComponentResourceInstance *resource = + find_resource(helper.component_inst, "test:context/host@1.0.0", "item"); + ASSERT_NE(resource, nullptr); + ASSERT_TRUE(resource->host_drop_attachment_is_custom_data); + EXPECT_EQ(resource->host_drop_attachment, &initial); + + wasm_component_set_custom_data(helper.component_inst, &updated); + EXPECT_EQ(wasm_component_get_custom_data(helper.component_inst), &updated); + EXPECT_EQ(resource->host_drop_attachment, &updated); + + prepared_call = wasm_component_prepare_export_call( + helper.component_inst, "call", helper.error_buf, + sizeof(helper.error_buf)); + ASSERT_NE(prepared_call, nullptr) << helper.error_buf; + ASSERT_TRUE( + wasm_component_call_prepared(prepared_call, 1, &result, 0, nullptr)); + ASSERT_TRUE(wasm_component_prepared_call_post_return(prepared_call)); + EXPECT_EQ(result.kind, WASM_I32); + EXPECT_EQ(result.of.i32, 22); + EXPECT_EQ(updated.raw_calls, 1u); + EXPECT_EQ(initial.raw_calls, 0u); + + WASMResourceHandle *handle = + wasm_create_resource_handle(resource, 101, true); + ASSERT_NE(handle, nullptr); + uint32_t handle_index = 0; + ASSERT_TRUE(wasm_component_table_add(helper.component_inst->table, handle, + WASM_TABLE_ELEM_RESOURCE_HANDLE, + &handle_index)); + EXPECT_TRUE(wasm_component_table_drop_resource(helper.component_inst->table, + handle_index)); + EXPECT_EQ(updated.drops, 1u); + EXPECT_EQ(updated.last_rep, 101u); + EXPECT_EQ(initial.drops, 0u); + + ASSERT_TRUE(wasm_component_set_host_resource_drop_callback( + helper.component_inst, "test:context/host@1.0.0", "item", + observe_custom_data_drop, &explicit_drop_state)); + EXPECT_FALSE(resource->host_drop_attachment_is_custom_data); + wasm_component_set_custom_data(helper.component_inst, &final_state); + EXPECT_EQ(resource->host_drop_attachment, &explicit_drop_state); + + result = {}; + ASSERT_TRUE( + wasm_component_call_prepared(prepared_call, 1, &result, 0, nullptr)); + ASSERT_TRUE(wasm_component_prepared_call_post_return(prepared_call)); + EXPECT_EQ(result.kind, WASM_I32); + EXPECT_EQ(result.of.i32, 33); + EXPECT_EQ(final_state.raw_calls, 1u); + + handle = wasm_create_resource_handle(resource, 202, true); + ASSERT_NE(handle, nullptr); + handle_index = 0; + ASSERT_TRUE(wasm_component_table_add(helper.component_inst->table, handle, + WASM_TABLE_ELEM_RESOURCE_HANDLE, + &handle_index)); + EXPECT_TRUE(wasm_component_table_drop_resource(helper.component_inst->table, + handle_index)); + EXPECT_EQ(explicit_drop_state.drops, 1u); + EXPECT_EQ(explicit_drop_state.last_rep, 202u); + EXPECT_EQ(final_state.drops, 0u); + + wasm_component_destroy_prepared_call(prepared_call); +} + +TEST_F(ComponentHostImportsTest, RejectsMismatchedExactNativeSignature) +{ + NativeSymbol first_symbols[] = { + { "use-item", (void *)first_use_item, "()", nullptr }, + }; + NativeSymbol second_symbols[] = { + { "use-item", (void *)second_use_item, "(i)", nullptr }, + }; + + ASSERT_TRUE(helper.runtime_init); + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "test:alias/first@1.0.0", first_symbols, + (uint32_t)(sizeof(first_symbols) / sizeof(first_symbols[0])))); + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "test:alias/second@1.0.0", second_symbols, + (uint32_t)(sizeof(second_symbols) / sizeof(second_symbols[0])))); + ASSERT_TRUE(helper.read_wasm_file("shared_host_instance_type.wasm")); + ASSERT_TRUE(helper.load_component()); + + struct InstantiationArgs2 *args = nullptr; + ASSERT_TRUE(wasm_runtime_instantiation_args_create(&args)); + wasm_runtime_instantiation_args_set_default_stack_size(args, 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size(args, 64 * 1024); + WASMComponentInstance *instance = wasm_component_instantiate_ex2( + helper.component, args, helper.error_buf, sizeof(helper.error_buf)); + wasm_runtime_instantiation_args_destroy(args); + + EXPECT_EQ(instance, nullptr); + if (instance) { + wasm_component_deinstantiate(instance); + } + EXPECT_NE(strstr(helper.error_buf, + "Function use-item not found in native symbols"), + nullptr) + << helper.error_buf; +} + +TEST_F(ComponentHostImportsTest, LargeAliasSetUsesBoundedCloneStorage) +{ + NativeSymbol symbols[] = { + { "ping", (void *)host_log, "()", nullptr }, + }; + + ASSERT_TRUE(helper.runtime_init); + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "test:alias/large@1.0.0", symbols, + (uint32_t)(sizeof(symbols) / sizeof(symbols[0])))); + ASSERT_TRUE(helper.read_wasm_file("large_shared_host_aliases.wasm")); + ASSERT_TRUE(helper.load_component()); + + struct InstantiationArgs2 *args = nullptr; + ASSERT_TRUE(wasm_runtime_instantiation_args_create(&args)); + wasm_runtime_instantiation_args_set_default_stack_size(args, 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size(args, 64 * 1024); + helper.component_inst = wasm_component_instantiate_ex2( + helper.component, args, helper.error_buf, sizeof(helper.error_buf)); + wasm_runtime_instantiation_args_destroy(args); + ASSERT_NE(helper.component_inst, nullptr) << helper.error_buf; + helper.component_instantiated = true; + + ASSERT_EQ(helper.component_inst->component_instances_count, 1u); + WASMComponentInstance *host = helper.component_inst->component_instances[0]; + ASSERT_NE(host, nullptr); + ASSERT_EQ(host->types_count, 66u); + for (uint32_t idx = 1; idx <= 64; idx++) { + EXPECT_EQ(host->types[idx], host->types[0]); + } + EXPECT_LT(host->defined_types_size, 20u * 1024u); + EXPECT_NE( + host->types[0], + helper.component_inst->types[0]->type_specific.instance->types[0]); +} + +TEST_F(ComponentHostImportsTest, + OwnsBindingsForStructurallySharedHostInstanceTypes) +{ + static int first_native_attachment; + static int second_native_attachment; + NativeSymbol first_symbols[] = { + { "use-item", (void *)first_use_item, "(i)", &first_native_attachment }, + }; + NativeSymbol second_symbols[] = { + { "use-item", (void *)second_use_item, "(i)", + &second_native_attachment }, + }; + + ASSERT_TRUE(helper.runtime_init); + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "test:alias/first@1.0.0", first_symbols, + (uint32_t)(sizeof(first_symbols) / sizeof(first_symbols[0])))); + ASSERT_TRUE(wasm_runtime_register_natives_raw( + "test:alias/second@1.0.0", second_symbols, + (uint32_t)(sizeof(second_symbols) / sizeof(second_symbols[0])))); + + ASSERT_TRUE(helper.read_wasm_file("shared_host_instance_type.wasm")); + ASSERT_TRUE(helper.load_component()); + ASSERT_TRUE(wasm_component_register_host_resource_drop_callback( + helper.component, "test:alias/first@1.0.0", "item", first_item_drop)); + ASSERT_TRUE(wasm_component_register_host_resource_drop_callback( + helper.component, "test:alias/second@1.0.0", "item", second_item_drop)); + + auto import_sections = helper.get_section(WASM_COMP_SECTION_IMPORTS); + ASSERT_EQ(import_sections.size(), 1u); + WASMComponentImportSection *imports = + import_sections[0]->parsed.import_section; + ASSERT_NE(imports, nullptr); + ASSERT_EQ(imports->count, 2u); + ASSERT_NE(imports->imports[0].extern_desc, nullptr); + ASSERT_NE(imports->imports[1].extern_desc, nullptr); + EXPECT_EQ(imports->imports[0].extern_desc->extern_desc.instance.type_idx, + imports->imports[1].extern_desc->extern_desc.instance.type_idx); + + HostImportDropState drop_state = {}; + struct InstantiationArgs2 *args = nullptr; + ASSERT_TRUE(wasm_runtime_instantiation_args_create(&args)); + ASSERT_NE(args, nullptr); + wasm_runtime_instantiation_args_set_default_stack_size(args, 64 * 1024); + wasm_runtime_instantiation_args_set_host_managed_heap_size(args, 64 * 1024); + wasm_runtime_instantiation_args_set_custom_data(args, &drop_state); + + helper.component_inst = wasm_component_instantiate_ex2( + helper.component, args, helper.error_buf, sizeof(helper.error_buf)); + wasm_runtime_instantiation_args_destroy(args); + ASSERT_NE(helper.component_inst, nullptr) << helper.error_buf; + helper.component_instantiated = true; + + ASSERT_EQ(helper.component_inst->types_count, 1u); + ASSERT_EQ(helper.component_inst->component_instances_count, 2u); + WASMComponentInstTypeInstance *type_template = + helper.component_inst->types[0]->type_specific.instance; + ASSERT_NE(type_template, nullptr); + ASSERT_EQ(type_template->func_count, 1u); + WASMComponentInstance *first = + helper.component_inst->component_instances[0]; + WASMComponentInstance *second = + helper.component_inst->component_instances[1]; + ASSERT_NE(first, nullptr); + ASSERT_NE(second, nullptr); + ASSERT_EQ(first->functions_count, 1u); + ASSERT_EQ(second->functions_count, 1u); + + WASMComponentResourceInstance *template_resource = + find_template_resource(type_template); + WASMComponentResourceInstance *first_resource = find_direct_resource(first); + WASMComponentResourceInstance *second_resource = + find_direct_resource(second); + ASSERT_NE(template_resource, nullptr); + ASSERT_NE(first_resource, nullptr); + ASSERT_NE(second_resource, nullptr); + EXPECT_NE(first_resource, template_resource); + EXPECT_NE(second_resource, template_resource); + EXPECT_NE(first_resource, second_resource); + ASSERT_NE(first_resource->interface_name, nullptr); + ASSERT_NE(second_resource->interface_name, nullptr); + EXPECT_STREQ(first_resource->interface_name, "test:alias/first@1.0.0"); + EXPECT_STREQ(second_resource->interface_name, "test:alias/second@1.0.0"); + EXPECT_EQ(first_resource->host_drop_callback, first_item_drop); + EXPECT_EQ(second_resource->host_drop_callback, second_item_drop); + EXPECT_EQ(first_resource->host_drop_attachment, &drop_state); + EXPECT_EQ(second_resource->host_drop_attachment, &drop_state); + + /* Resolving the imports must not turn the parsed structural template into + * either of its two concrete host interfaces. */ + EXPECT_EQ(template_resource->interface_name, nullptr); + EXPECT_FALSE(template_resource->is_host); + EXPECT_EQ(template_resource->host_drop_callback, nullptr); + EXPECT_EQ(template_resource->host_drop_attachment, nullptr); + + WASMFunctionInstance *template_core = &type_template->defined_core_funcs[0]; + WASMFunctionInstance *first_core = first->functions[0]->core_func; + WASMFunctionInstance *second_core = second->functions[0]->core_func; + ASSERT_NE(template_core->u.func_import, nullptr); + ASSERT_NE(first_core, nullptr); + ASSERT_NE(second_core, nullptr); + ASSERT_NE(first_core->u.func_import, nullptr); + ASSERT_NE(second_core->u.func_import, nullptr); + ASSERT_NE(first_core->u.func_import->func_type, nullptr); + ASSERT_NE(second_core->u.func_import->func_type, nullptr); + EXPECT_EQ(first_core->u.func_import->func_type->param_count, 1u); + EXPECT_EQ(first_core->u.func_import->func_type->result_count, 0u); + EXPECT_EQ(first_core->u.func_import->func_type->types[0], VALUE_TYPE_I32); + EXPECT_EQ(second_core->u.func_import->func_type->param_count, 1u); + EXPECT_EQ(second_core->u.func_import->func_type->result_count, 0u); + EXPECT_EQ(second_core->u.func_import->func_type->types[0], VALUE_TYPE_I32); + EXPECT_NE(first_core, template_core); + EXPECT_NE(second_core, template_core); + EXPECT_NE(first_core, second_core); + EXPECT_NE(first_core->u.func_import, template_core->u.func_import); + EXPECT_NE(second_core->u.func_import, template_core->u.func_import); + EXPECT_NE(first_core->u.func_import, second_core->u.func_import); + EXPECT_EQ(first_core->u.func_import->func_ptr_linked, + (void *)first_use_item); + EXPECT_EQ(second_core->u.func_import->func_ptr_linked, + (void *)second_use_item); + EXPECT_EQ(first_core->u.func_import->attachment, &first_native_attachment); + EXPECT_EQ(second_core->u.func_import->attachment, + &second_native_attachment); + EXPECT_TRUE(first_core->u.func_import->call_conv_raw); + EXPECT_TRUE(second_core->u.func_import->call_conv_raw); + EXPECT_EQ(template_core->u.func_import->func_ptr_linked, nullptr); + EXPECT_EQ(template_core->u.func_import->signature, nullptr); + EXPECT_EQ(template_core->u.func_import->attachment, nullptr); + EXPECT_FALSE(template_core->u.func_import->call_conv_raw); + + EXPECT_NE(first->functions[0]->func_type, type_template->funcs[0]); + EXPECT_NE(second->functions[0]->func_type, type_template->funcs[0]); + EXPECT_NE(first->functions[0]->func_type, second->functions[0]->func_type); + WASMComponentTypeInstance *first_param = + first->functions[0]->func_type->params->params[0].type; + WASMComponentTypeInstance *second_param = + second->functions[0]->func_type->params->params[0].type; + ASSERT_NE(first_param, nullptr); + ASSERT_NE(second_param, nullptr); + ASSERT_NE(first_param->type_specific.resource_handle, nullptr); + ASSERT_NE(second_param->type_specific.resource_handle, nullptr); + EXPECT_NE(first_param, second_param); + EXPECT_EQ(first_param->type_specific.resource_handle->resource, + first_resource); + EXPECT_EQ(second_param->type_specific.resource_handle->resource, + second_resource); + + WASMResourceHandle *first_handle = + wasm_create_resource_handle(first_resource, 101, true); + WASMResourceHandle *second_handle = + wasm_create_resource_handle(second_resource, 202, true); + ASSERT_NE(first_handle, nullptr); + ASSERT_NE(second_handle, nullptr); + EXPECT_TRUE(wasm_drop_resource_handle(first_handle)); + EXPECT_TRUE(wasm_drop_resource_handle(second_handle)); + EXPECT_EQ(drop_state.first_count, 1u); + EXPECT_EQ(drop_state.first_rep, 101u); + EXPECT_EQ(drop_state.second_count, 1u); + EXPECT_EQ(drop_state.second_rep, 202u); +} + +} // namespace diff --git a/tests/unit/component/test_host_resource_table.cc b/tests/unit/component/test_host_resource_table.cc index 616ba91cc5..5798e71e1a 100644 --- a/tests/unit/component/test_host_resource_table.cc +++ b/tests/unit/component/test_host_resource_table.cc @@ -9,6 +9,8 @@ #include #include #include +#include +#include extern "C" { #include "component-model/wasm_component_host_resource.h" @@ -38,7 +40,13 @@ class HostResourceTableTest : public testing::Test ~HostResourceTableTest() {} virtual void SetUp() { - wasm_runtime_init(); + RuntimeInitArgs init_args = {}; + init_args.mem_alloc_type = Alloc_With_Pool; + init_args.mem_alloc_option.pool.heap_buf = runtime_heap_; + init_args.mem_alloc_option.pool.heap_size = sizeof(runtime_heap_); + runtime_initialized_ = wasm_runtime_full_init(&init_args); + ASSERT_TRUE(runtime_initialized_); + bool success = instantiate_host_resource_table(); ASSERT_TRUE(success); table_ = get_global_host_resource_table(); @@ -50,10 +58,16 @@ class HostResourceTableTest : public testing::Test destroy_host_resource_table(); table_ = nullptr; } - wasm_runtime_destroy(); + if (runtime_initialized_) { + wasm_runtime_destroy(); + runtime_initialized_ = false; + } } protected: + static constexpr size_t RUNTIME_HEAP_SIZE = 1024 * 1024; + alignas(8) char runtime_heap_[RUNTIME_HEAP_SIZE]; + bool runtime_initialized_ = false; HostResourceTable *table_; // Helper function to create a test host resource @@ -231,6 +245,39 @@ TEST_F(HostResourceTableTest, Table_GetNextId) EXPECT_EQ(test_get_counter_from_id(id3), 3u); } +TEST_F(HostResourceTableTest, Table_GetNextIdConcurrent) +{ + constexpr size_t thread_count = 8; + constexpr size_t ids_per_thread = 256; + std::vector> ids( + thread_count, std::vector(ids_per_thread)); + std::vector threads; + + for (size_t thread_index = 0; thread_index < thread_count; thread_index++) { + threads.emplace_back([thread_index, &ids]() { + for (size_t id_index = 0; id_index < ids_per_thread; id_index++) { + ids[thread_index][id_index] = + host_resource_table_get_next_id(WASI_P2_TCP_SOCKET); + } + }); + } + + for (std::thread &thread : threads) { + thread.join(); + } + + std::unordered_set unique_ids; + for (const auto &thread_ids : ids) { + for (uint32_t id : thread_ids) { + EXPECT_NE(id, 0u); + EXPECT_EQ(test_get_type_from_id(id), WASI_P2_TCP_SOCKET); + unique_ids.insert(id); + } + } + + EXPECT_EQ(unique_ids.size(), thread_count * ids_per_thread); +} + // Test error conditions TEST_F(HostResourceTableTest, Table_ErrorConditions) { diff --git a/tests/unit/component/test_parser_failure_cleanup.cc b/tests/unit/component/test_parser_failure_cleanup.cc new file mode 100644 index 0000000000..05e390e3bd --- /dev/null +++ b/tests/unit/component/test_parser_failure_cleanup.cc @@ -0,0 +1,817 @@ +/* + * Copyright (C) 2026 Airbus Defence and Space Romania SRL. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include "wasm_component.h" +#include "wasm_component_runtime.h" +#include "wasm_export.h" + +bool +parse_core_limits(const uint8_t **payload, const uint8_t *end, + WASMComponentCoreLimits *out, char *error_buf, + uint32_t error_buf_size); +bool +parse_core_import_desc(const uint8_t **payload, const uint8_t *end, + WASMComponentCoreImportDesc *out, char *error_buf, + uint32_t error_buf_size); +bool +parse_core_module_decl(const uint8_t **payload, const uint8_t *end, + WASMComponentCoreModuleDecl *out, char *error_buf, + uint32_t error_buf_size); +} + +namespace { + +constexpr size_t kNoFailure = std::numeric_limits::max(); + +struct AllocationTracker { + std::mutex mutex; + std::unordered_set live; + size_t attempt = 0; + size_t fail_at = kNoFailure; + size_t invalid_frees = 0; + bool injected_failure = false; +}; + +AllocationTracker allocation_tracker; + +bool +should_fail_allocation() +{ + if (allocation_tracker.attempt++ == allocation_tracker.fail_at) { + allocation_tracker.injected_failure = true; + return true; + } + return false; +} + +void * +tracking_malloc(unsigned int size) +{ + std::lock_guard lock(allocation_tracker.mutex); + if (should_fail_allocation()) { + return nullptr; + } + void *result = std::malloc(size ? size : 1); + if (result) { + allocation_tracker.live.insert(result); + } + return result; +} + +void * +tracking_realloc(void *ptr, unsigned int size) +{ + std::lock_guard lock(allocation_tracker.mutex); + if (!ptr) { + if (should_fail_allocation()) { + return nullptr; + } + void *result = std::malloc(size ? size : 1); + if (result) { + allocation_tracker.live.insert(result); + } + return result; + } + if (allocation_tracker.live.find(ptr) == allocation_tracker.live.end()) { + allocation_tracker.invalid_frees++; + return nullptr; + } + if (should_fail_allocation()) { + return nullptr; + } + + allocation_tracker.live.erase(ptr); + void *result = std::realloc(ptr, size ? size : 1); + if (!result) { + allocation_tracker.live.insert(ptr); + return nullptr; + } + allocation_tracker.live.insert(result); + return result; +} + +void +tracking_free(void *ptr) +{ + if (!ptr) { + return; + } + std::lock_guard lock(allocation_tracker.mutex); + if (allocation_tracker.live.erase(ptr) == 0) { + allocation_tracker.invalid_frees++; + return; + } + std::free(ptr); +} + +std::vector +read_binary(const char *path) +{ + std::ifstream stream(path, std::ios::binary); + EXPECT_TRUE(stream.good()) << path; + return std::vector(std::istreambuf_iterator(stream), {}); +} + +LoadArgs +component_load_args() +{ + static char component_name[] = "parser-cleanup-test"; + LoadArgs args = {}; + args.name = component_name; + args.is_component = true; + return args; +} + +bool +read_u32_leb(const std::vector &bytes, size_t *offset, uint32_t *value) +{ + uint32_t result = 0; + uint32_t shift = 0; + while (*offset < bytes.size() && shift < 35) { + uint8_t byte = bytes[(*offset)++]; + result |= static_cast(byte & 0x7f) << shift; + if ((byte & 0x80) == 0) { + *value = result; + return true; + } + shift += 7; + } + return false; +} + +void +append_u32_leb(std::vector *bytes, uint32_t value) +{ + do { + uint8_t byte = static_cast(value & 0x7f); + value >>= 7; + if (value) { + byte |= 0x80; + } + bytes->push_back(byte); + } while (value); +} + +struct SectionSlice { + uint8_t id; + const uint8_t *payload; + uint32_t payload_size; +}; + +std::vector +top_level_sections(const std::vector &component) +{ + std::vector sections; + size_t offset = 8; + while (offset < component.size()) { + uint8_t id = component[offset++]; + uint32_t payload_size = 0; + if (!read_u32_leb(component, &offset, &payload_size) + || payload_size > component.size() - offset) { + ADD_FAILURE() << "valid test component has malformed section table"; + return {}; + } + sections.push_back({ id, component.data() + offset, payload_size }); + offset += payload_size; + } + return sections; +} + +std::vector +single_section_component(const std::vector &source, + const SectionSlice §ion, uint32_t prefix_size) +{ + std::vector result(source.begin(), source.begin() + 8); + result.push_back(section.id); + append_u32_leb(&result, prefix_size); + result.insert(result.end(), section.payload, section.payload + prefix_size); + return result; +} + +void +load_and_release_if_valid(std::vector *bytes) +{ + char error[256] = {}; + LoadArgs args = component_load_args(); + WASMComponent *component = + wasm_component_load(bytes->data(), static_cast(bytes->size()), + &args, error, sizeof(error)); + if (component) { + wasm_component_unload(component); + } + else { + EXPECT_NE(error[0], '\0'); + } +} + +TEST(ComponentParserFailureCleanupTest, + TruncatedSectionPayloadsAreAlwaysSafelyReleasable) +{ + RuntimeInitArgs init_args = {}; + init_args.mem_alloc_type = Alloc_With_Allocator; + init_args.mem_alloc_option.allocator.malloc_func = + reinterpret_cast(tracking_malloc); + init_args.mem_alloc_option.allocator.realloc_func = + reinterpret_cast(tracking_realloc); + init_args.mem_alloc_option.allocator.free_func = + reinterpret_cast(tracking_free); + allocation_tracker.attempt = 0; + allocation_tracker.fail_at = kNoFailure; + allocation_tracker.invalid_frees = 0; + allocation_tracker.injected_failure = false; + allocation_tracker.live.clear(); + ASSERT_TRUE(wasm_runtime_full_init(&init_args)); + const size_t runtime_baseline = allocation_tracker.live.size(); + + std::vector source = read_binary("add.wasm"); + ASSERT_GE(source.size(), 8u); + std::set tested_ids; + for (const SectionSlice §ion : top_level_sections(source)) { + if (!tested_ids.insert(section.id).second) { + continue; + } + + std::set cuts; + uint32_t dense_end = std::min(section.payload_size, 64); + for (uint32_t cut = 0; cut <= dense_end; cut++) { + cuts.insert(cut); + } + for (uint32_t cut = 128; cut < section.payload_size; cut *= 2) { + cuts.insert(cut); + if (cut > UINT32_MAX / 2) { + break; + } + } + if (section.payload_size > 0) { + cuts.insert(section.payload_size / 4); + cuts.insert(section.payload_size / 2); + cuts.insert(section.payload_size * 3 / 4); + cuts.insert(section.payload_size - 1); + } + + for (uint32_t cut : cuts) { + if (cut >= section.payload_size) { + continue; + } + std::vector truncated = + single_section_component(source, section, cut); + load_and_release_if_valid(&truncated); + ASSERT_EQ(allocation_tracker.invalid_frees, 0u) + << "section " << section.id << ", cut " << cut; + ASSERT_EQ(allocation_tracker.live.size(), runtime_baseline) + << "section " << section.id << ", cut " << cut; + } + } + + const std::vector>> synthetic = { + { WASM_COMP_SECTION_CORE_TYPE, { 1, 0x50, 0 } }, + { WASM_COMP_SECTION_START, { 0, 2, 0, 1, 0 } }, + { WASM_COMP_SECTION_IMPORTS, { 1, 0, 1, 'x', 1, 0 } }, + { WASM_COMP_SECTION_VALUES, { 1, WASM_COMP_PRIMVAL_U8, 1, 42 } }, + { WASM_COMP_SECTION_TYPE, + { 1, WASM_COMP_DEF_VAL_RESULT, WASM_COMP_OPTIONAL_FALSE, + WASM_COMP_OPTIONAL_FALSE } }, + { WASM_COMP_SECTION_TYPE, + { 1, WASM_COMP_DEF_VAL_STREAM, WASM_COMP_OPTIONAL_FALSE } }, + { WASM_COMP_SECTION_TYPE, + { 1, WASM_COMP_DEF_VAL_FUTURE, WASM_COMP_OPTIONAL_FALSE } }, + { WASM_COMP_SECTION_TYPE, + { 1, WASM_COMP_RESOURCE_TYPE_SYNC, WASM_COMP_RESOURCE_REP_I32, + WASM_COMP_OPTIONAL_FALSE } }, + { WASM_COMP_SECTION_TYPE, + { 1, WASM_COMP_RESOURCE_TYPE_ASYNC, WASM_COMP_RESOURCE_REP_I32, 0, + WASM_COMP_OPTIONAL_FALSE } }, + { WASM_COMP_SECTION_TYPE, { 1, WASM_COMP_COMPONENT_TYPE, 0 } }, + { WASM_COMP_SECTION_TYPE, { 1, WASM_COMP_INSTANCE_TYPE, 0 } }, + /* Fail after allocating the current record field/case. */ + { WASM_COMP_SECTION_TYPE, { 1, WASM_COMP_DEF_VAL_RECORD, 1, 1, 'x' } }, + { WASM_COMP_SECTION_TYPE, + { 1, WASM_COMP_DEF_VAL_VARIANT, 1, 1, 'x', + WASM_COMP_OPTIONAL_TRUE } }, + }; + for (const auto &[id, payload] : synthetic) { + SectionSlice section = { id, payload.data(), + static_cast(payload.size()) }; + for (uint32_t cut = 0; cut <= section.payload_size; cut++) { + std::vector truncated = + single_section_component(source, section, cut); + load_and_release_if_valid(&truncated); + ASSERT_EQ(allocation_tracker.invalid_frees, 0u) + << "synthetic section " << section.id << ", cut " << cut; + ASSERT_EQ(allocation_tracker.live.size(), runtime_baseline) + << "synthetic section " << section.id << ", cut " << cut; + } + } + + wasm_runtime_destroy(); + EXPECT_EQ(allocation_tracker.invalid_frees, 0u); + EXPECT_TRUE(allocation_tracker.live.empty()); +} + +TEST(ComponentParserFailureCleanupTest, + ScalarCoreTypeParsersRejectTruncatedInput) +{ + RuntimeInitArgs init_args = {}; + init_args.mem_alloc_type = Alloc_With_Allocator; + init_args.mem_alloc_option.allocator.malloc_func = + reinterpret_cast(tracking_malloc); + init_args.mem_alloc_option.allocator.realloc_func = + reinterpret_cast(tracking_realloc); + init_args.mem_alloc_option.allocator.free_func = + reinterpret_cast(tracking_free); + allocation_tracker.attempt = 0; + allocation_tracker.fail_at = kNoFailure; + allocation_tracker.invalid_frees = 0; + allocation_tracker.injected_failure = false; + allocation_tracker.live.clear(); + ASSERT_TRUE(wasm_runtime_full_init(&init_args)); + const size_t runtime_baseline = allocation_tracker.live.size(); + + uint8_t empty_storage = 0; + const uint8_t *p = &empty_storage; + const uint8_t *end = p; + char error[256] = {}; + WASMComponentCoreLimits limits = {}; + EXPECT_FALSE(parse_core_limits(&p, end, &limits, error, sizeof(error))); + EXPECT_NE(error[0], '\0'); + + p = &empty_storage; + error[0] = '\0'; + WASMComponentCoreValType val_type = {}; + EXPECT_FALSE(parse_core_valtype(&p, end, &val_type, error, sizeof(error))); + EXPECT_NE(error[0], '\0'); + + const uint8_t truncated_heap_type[] = { 0x63 }; + p = truncated_heap_type; + error[0] = '\0'; + EXPECT_FALSE(parse_core_valtype(&p, truncated_heap_type + 1, &val_type, + error, sizeof(error))); + EXPECT_NE(error[0], '\0'); + + const std::vector> import_desc_cases = { + {}, + { WASM_CORE_IMPORTDESC_TABLE }, + { WASM_CORE_IMPORTDESC_TABLE, WASM_CORE_REFTYPE_FUNC_REF }, + { WASM_CORE_IMPORTDESC_GLOBAL, WASM_CORE_NUM_TYPE_I32 }, + }; + for (const std::vector &bytes : import_desc_cases) { + p = bytes.empty() ? &empty_storage : bytes.data(); + end = p + bytes.size(); + error[0] = '\0'; + WASMComponentCoreImportDesc import_desc = {}; + EXPECT_FALSE(parse_core_import_desc(&p, end, &import_desc, error, + sizeof(error))); + EXPECT_NE(error[0], '\0'); + EXPECT_EQ(allocation_tracker.invalid_frees, 0u); + EXPECT_EQ(allocation_tracker.live.size(), runtime_baseline); + } + + p = &empty_storage; + end = p; + error[0] = '\0'; + WASMComponentCoreAliasTarget alias_target = {}; + EXPECT_FALSE( + parse_alias_target(&p, end, &alias_target, error, sizeof(error))); + EXPECT_NE(error[0], '\0'); + + p = &empty_storage; + error[0] = '\0'; + WASMComponentCoreModuleDecl module_decl = {}; + EXPECT_FALSE( + parse_core_module_decl(&p, end, &module_decl, error, sizeof(error))); + EXPECT_NE(error[0], '\0'); + EXPECT_EQ(allocation_tracker.invalid_frees, 0u); + EXPECT_EQ(allocation_tracker.live.size(), runtime_baseline); + + wasm_runtime_destroy(); + EXPECT_EQ(allocation_tracker.invalid_frees, 0u); + EXPECT_TRUE(allocation_tracker.live.empty()); +} + +TEST(ComponentParserFailureCleanupTest, PublicLoadPreservesParserDiagnostic) +{ + RuntimeInitArgs init_args = {}; + init_args.mem_alloc_type = Alloc_With_System_Allocator; + ASSERT_TRUE(wasm_runtime_full_init(&init_args)); + + std::vector malformed = read_binary("add.wasm"); + ASSERT_GE(malformed.size(), 8u); + malformed.resize(8); + malformed.push_back(WASM_COMP_SECTION_TYPE); + append_u32_leb(&malformed, 10); + malformed.push_back(0); + + char error[256] = {}; + LoadArgs args = component_load_args(); + EXPECT_EQ(wasm_component_load(malformed.data(), malformed.size(), &args, + error, sizeof(error)), + nullptr); + EXPECT_NE(std::string(error).find("payload exceeds input"), + std::string::npos) + << error; + + wasm_runtime_destroy(); +} + +TEST(ComponentParserFailureCleanupTest, + InstanceSectionsReportTheirActualConsumedLength) +{ + RuntimeInitArgs init_args = {}; + init_args.mem_alloc_type = Alloc_With_System_Allocator; + ASSERT_TRUE(wasm_runtime_full_init(&init_args)); + + const std::vector source = read_binary("add.wasm"); + ASSERT_GE(source.size(), 8u); + for (uint8_t section_id : + { WASM_COMP_SECTION_CORE_INSTANCE, WASM_COMP_SECTION_INSTANCES }) { + const std::vector payload = { 0, 0 }; + const SectionSlice section = { + section_id, + payload.data(), + static_cast(payload.size()), + }; + std::vector malformed = single_section_component( + source, section, static_cast(payload.size())); + char error[256] = {}; + LoadArgs args = component_load_args(); + WASMComponent *component = wasm_component_load( + malformed.data(), static_cast(malformed.size()), &args, + error, sizeof(error)); + EXPECT_EQ(component, nullptr); + if (component) { + wasm_component_unload(component); + } + EXPECT_NE(std::string(error).find("consumed 1 of 2 bytes"), + std::string::npos) + << error; + } + + wasm_runtime_destroy(); +} + +TEST(ComponentParserFailureCleanupTest, + OversizedVectorCountsAreRejectedBeforeArrayAllocation) +{ + RuntimeInitArgs init_args = {}; + init_args.mem_alloc_type = Alloc_With_Allocator; + init_args.mem_alloc_option.allocator.malloc_func = + reinterpret_cast(tracking_malloc); + init_args.mem_alloc_option.allocator.realloc_func = + reinterpret_cast(tracking_realloc); + init_args.mem_alloc_option.allocator.free_func = + reinterpret_cast(tracking_free); + allocation_tracker.attempt = 0; + allocation_tracker.fail_at = kNoFailure; + allocation_tracker.invalid_frees = 0; + allocation_tracker.injected_failure = false; + allocation_tracker.live.clear(); + ASSERT_TRUE(wasm_runtime_full_init(&init_args)); + const size_t runtime_baseline = allocation_tracker.live.size(); + + const std::vector source = read_binary("add.wasm"); + ASSERT_GE(source.size(), 8u); + char direct_error[256] = {}; + EXPECT_EQ(wasm_component_checked_calloc(0x10000001u, 16, nullptr, nullptr, + 0, "direct oversized regression", + direct_error, sizeof(direct_error)), + nullptr); + EXPECT_NE(std::string(direct_error).find("too large"), std::string::npos) + << direct_error; + EXPECT_EQ(allocation_tracker.live.size(), runtime_baseline); + + struct OversizedCountCase { + const char *description; + uint8_t section_id; + std::vector prefix; + }; + const std::vector cases = { + { "core instances", WASM_COMP_SECTION_CORE_INSTANCE, {} }, + { "core types", WASM_COMP_SECTION_CORE_TYPE, {} }, + { "component instances", WASM_COMP_SECTION_INSTANCES, {} }, + { "aliases", WASM_COMP_SECTION_ALIASES, {} }, + { "types", WASM_COMP_SECTION_TYPE, {} }, + { "canonical definitions", WASM_COMP_SECTION_CANONS, {} }, + { "start arguments", WASM_COMP_SECTION_START, { 0 } }, + { "imports", WASM_COMP_SECTION_IMPORTS, {} }, + { "exports", WASM_COMP_SECTION_EXPORTS, {} }, + { "values", WASM_COMP_SECTION_VALUES, {} }, + { "core instantiate arguments", + WASM_COMP_SECTION_CORE_INSTANCE, + { 1, WASM_COMP_INSTANCE_EXPRESSION_WITH_ARGS, 0 } }, + { "core inline exports", + WASM_COMP_SECTION_CORE_INSTANCE, + { 1, WASM_COMP_INSTANCE_EXPRESSION_WITHOUT_ARGS } }, + { "component instantiate arguments", + WASM_COMP_SECTION_INSTANCES, + { 1, WASM_COMP_INSTANCE_EXPRESSION_WITH_ARGS, 0 } }, + { "component inline exports", + WASM_COMP_SECTION_INSTANCES, + { 1, WASM_COMP_INSTANCE_EXPRESSION_WITHOUT_ARGS } }, + { "core module declarations", + WASM_COMP_SECTION_CORE_TYPE, + { 1, 0x50 } }, + { "record fields", + WASM_COMP_SECTION_TYPE, + { 1, WASM_COMP_DEF_VAL_RECORD } }, + { "variant cases", + WASM_COMP_SECTION_TYPE, + { 1, WASM_COMP_DEF_VAL_VARIANT } }, + { "tuple elements", + WASM_COMP_SECTION_TYPE, + { 1, WASM_COMP_DEF_VAL_TUPLE } }, + { "enum labels", + WASM_COMP_SECTION_TYPE, + { 1, WASM_COMP_DEF_VAL_ENUM } }, + { "function parameters", + WASM_COMP_SECTION_TYPE, + { 1, WASM_COMP_FUNC_TYPE } }, + { "component type declarations", + WASM_COMP_SECTION_TYPE, + { 1, WASM_COMP_COMPONENT_TYPE } }, + { "component instance declarations", + WASM_COMP_SECTION_TYPE, + { 1, WASM_COMP_INSTANCE_TYPE } }, + { "canonical options", + WASM_COMP_SECTION_CANONS, + { 1, WASM_COMP_CANON_LIFT, 0, 0 } }, + }; + + for (const OversizedCountCase &test_case : cases) { + std::vector payload = test_case.prefix; + append_u32_leb(&payload, 0x10000001u); + SectionSlice section = { + test_case.section_id, + payload.data(), + static_cast(payload.size()), + }; + std::vector malformed = single_section_component( + source, section, static_cast(payload.size())); + char error[256] = {}; + LoadArgs args = component_load_args(); + WASMComponent *component = wasm_component_load( + malformed.data(), static_cast(malformed.size()), &args, + error, sizeof(error)); + + EXPECT_EQ(component, nullptr) << test_case.description; + if (component) { + wasm_component_unload(component); + } + EXPECT_NE(std::string(error).find("exceeds remaining payload"), + std::string::npos) + << test_case.description << ": " << error; + EXPECT_EQ(allocation_tracker.invalid_frees, 0u) + << test_case.description; + EXPECT_EQ(allocation_tracker.live.size(), runtime_baseline) + << test_case.description; + } + + wasm_runtime_destroy(); + EXPECT_EQ(allocation_tracker.invalid_frees, 0u); + EXPECT_TRUE(allocation_tracker.live.empty()); +} + +TEST(ComponentParserFailureCleanupTest, + FlagsAndEnumLabelAllocationFailuresPreserveArrayOwnership) +{ + RuntimeInitArgs init_args = {}; + init_args.mem_alloc_type = Alloc_With_Allocator; + init_args.mem_alloc_option.allocator.malloc_func = + reinterpret_cast(tracking_malloc); + init_args.mem_alloc_option.allocator.realloc_func = + reinterpret_cast(tracking_realloc); + init_args.mem_alloc_option.allocator.free_func = + reinterpret_cast(tracking_free); + allocation_tracker.attempt = 0; + allocation_tracker.fail_at = kNoFailure; + allocation_tracker.invalid_frees = 0; + allocation_tracker.injected_failure = false; + allocation_tracker.live.clear(); + ASSERT_TRUE(wasm_runtime_full_init(&init_args)); + const size_t runtime_baseline = allocation_tracker.live.size(); + + const std::vector source = read_binary("add.wasm"); + ASSERT_GE(source.size(), 8u); + struct LabelTypeCase { + const char *description; + uint8_t tag; + }; + const LabelTypeCase cases[] = { + { "flags", WASM_COMP_DEF_VAL_FLAGS }, + { "enum", WASM_COMP_DEF_VAL_ENUM }, + }; + + for (const LabelTypeCase &test_case : cases) { + const std::vector payload = { 1, test_case.tag, 1, 1, 'x' }; + const SectionSlice section = { + WASM_COMP_SECTION_TYPE, + payload.data(), + static_cast(payload.size()), + }; + const std::vector pristine = single_section_component( + source, section, static_cast(payload.size())); + + std::vector bytes = pristine; + LoadArgs args = component_load_args(); + char error[256] = {}; + allocation_tracker.attempt = 0; + WASMComponent *component = wasm_component_load( + bytes.data(), static_cast(bytes.size()), &args, error, + sizeof(error)); + ASSERT_NE(component, nullptr) << test_case.description << ": " << error; + const size_t successful_load_allocations = allocation_tracker.attempt; + wasm_component_unload(component); + ASSERT_GE(successful_load_allocations, 6u) << test_case.description; + ASSERT_EQ(allocation_tracker.invalid_frees, 0u) + << test_case.description; + ASSERT_EQ(allocation_tracker.live.size(), runtime_baseline) + << test_case.description; + + for (size_t fail_at = 0; fail_at < successful_load_allocations; + fail_at++) { + bytes = pristine; + allocation_tracker.attempt = 0; + allocation_tracker.fail_at = fail_at; + allocation_tracker.injected_failure = false; + error[0] = '\0'; + + component = wasm_component_load(bytes.data(), + static_cast(bytes.size()), + &args, error, sizeof(error)); + allocation_tracker.fail_at = kNoFailure; + EXPECT_TRUE(allocation_tracker.injected_failure) + << test_case.description << ", failure index " << fail_at; + EXPECT_EQ(component, nullptr) + << test_case.description << ", failure index " << fail_at; + if (component) { + wasm_component_unload(component); + } + EXPECT_NE(error[0], '\0') + << test_case.description << ", failure index " << fail_at; + ASSERT_EQ(allocation_tracker.invalid_frees, 0u) + << test_case.description << ", failure index " << fail_at; + ASSERT_EQ(allocation_tracker.live.size(), runtime_baseline) + << test_case.description << ", failure index " << fail_at; + } + } + + wasm_runtime_destroy(); + EXPECT_EQ(allocation_tracker.invalid_frees, 0u); + EXPECT_TRUE(allocation_tracker.live.empty()); +} + +TEST(ComponentParserFailureCleanupTest, + ComponentInstanceAllocationRejectsOverflowingLayout) +{ + RuntimeInitArgs init_args = {}; + init_args.mem_alloc_type = Alloc_With_System_Allocator; + ASSERT_TRUE(wasm_runtime_full_init(&init_args)); + + WASMComponentIndexCount counts = {}; + counts.functions = UINT32_MAX; + char error[256] = {}; + EXPECT_EQ(wasm_component_instance_allocate(&counts, error, sizeof(error)), + nullptr); + EXPECT_NE(std::string(error).find("too large"), std::string::npos) << error; + + WASMComponentLabelValType dummy_field = {}; + WASMComponentRecordType record = {}; + record.count = UINT32_MAX; + record.fields = &dummy_field; + WASMComponentDefValType def_val = {}; + def_val.tag = WASM_COMP_DEF_VAL_RECORD; + def_val.def_val.record = &record; + uint64 def_val_size = 0; + ASSERT_TRUE(wasm_get_def_val_type_size(&def_val, &def_val_size)); + EXPECT_GT(def_val_size, UINT32_MAX); + + WASMComponentParamList params = {}; + params.count = UINT32_MAX; + params.params = &dummy_field; + WASMComponentResultList results = {}; + WASMComponentFuncType func_type = {}; + func_type.params = ¶ms; + func_type.results = &results; + uint64 func_type_size = 0; + ASSERT_TRUE(wasm_get_func_type_size(&func_type, &func_type_size)); + EXPECT_GT(func_type_size, UINT32_MAX); + + WASMComponentTypes nested_type = {}; + nested_type.tag = WASM_COMP_DEF_TYPE; + nested_type.type.def_val_type = &def_val; + WASMComponentInstDecl instance_decl = {}; + instance_decl.tag = WASM_COMP_COMPONENT_DECL_INSTANCE_TYPE; + instance_decl.decl.type = &nested_type; + WASMComponentInstType instance_type = {}; + instance_type.count = 1; + instance_type.instance_decls = &instance_decl; + WASMComponentInstanceDeclTypeSize instance_counts = {}; + uint64 instance_size = 0; + ASSERT_TRUE(wasm_get_inst_decl_size(&instance_type, &instance_counts, + &instance_size)); + EXPECT_GT(instance_size, UINT32_MAX); + EXPECT_GT(instance_counts.types_size, UINT32_MAX); + + counts = {}; + counts.types_total_size = UINT64_MAX; + error[0] = '\0'; + EXPECT_EQ(wasm_component_instance_allocate(&counts, error, sizeof(error)), + nullptr); + EXPECT_NE(std::string(error).find("too large"), std::string::npos) << error; + + wasm_runtime_destroy(); +} + +TEST(ComponentParserFailureCleanupTest, + EveryInjectedAllocationFailureReturnsToTheRuntimeBaseline) +{ + RuntimeInitArgs init_args = {}; + init_args.mem_alloc_type = Alloc_With_Allocator; + init_args.mem_alloc_option.allocator.malloc_func = + reinterpret_cast(tracking_malloc); + init_args.mem_alloc_option.allocator.realloc_func = + reinterpret_cast(tracking_realloc); + init_args.mem_alloc_option.allocator.free_func = + reinterpret_cast(tracking_free); + + allocation_tracker.attempt = 0; + allocation_tracker.fail_at = kNoFailure; + allocation_tracker.invalid_frees = 0; + allocation_tracker.injected_failure = false; + allocation_tracker.live.clear(); + ASSERT_TRUE(wasm_runtime_full_init(&init_args)); + const size_t runtime_baseline = allocation_tracker.live.size(); + + const std::vector pristine_source = read_binary("add.wasm"); + ASSERT_FALSE(pristine_source.empty()); + std::vector source = pristine_source; + LoadArgs args = component_load_args(); + char error[256] = {}; + + allocation_tracker.attempt = 0; + WASMComponent *component = + wasm_component_load(source.data(), static_cast(source.size()), + &args, error, sizeof(error)); + ASSERT_NE(component, nullptr) << error; + wasm_component_unload(component); + ASSERT_EQ(allocation_tracker.invalid_frees, 0u); + ASSERT_EQ(allocation_tracker.live.size(), runtime_baseline); + + /* The first core-module load can initialize process-wide parser caches. + * Measure the steady-state path that each injected iteration follows. */ + source = pristine_source; + allocation_tracker.attempt = 0; + component = + wasm_component_load(source.data(), static_cast(source.size()), + &args, error, sizeof(error)); + ASSERT_NE(component, nullptr) << error; + const size_t successful_load_allocations = allocation_tracker.attempt; + wasm_component_unload(component); + ASSERT_EQ(allocation_tracker.invalid_frees, 0u); + ASSERT_EQ(allocation_tracker.live.size(), runtime_baseline); + + for (size_t fail_at = 0; fail_at < successful_load_allocations; fail_at++) { + source = pristine_source; + allocation_tracker.attempt = 0; + allocation_tracker.fail_at = fail_at; + allocation_tracker.injected_failure = false; + error[0] = '\0'; + + component = wasm_component_load(source.data(), + static_cast(source.size()), + &args, error, sizeof(error)); + allocation_tracker.fail_at = kNoFailure; + if (component) { + wasm_component_unload(component); + } + else { + EXPECT_NE(error[0], '\0') << "failure index " << fail_at; + } + + ASSERT_TRUE(allocation_tracker.injected_failure) + << "failure index " << fail_at; + ASSERT_EQ(allocation_tracker.invalid_frees, 0u) + << "failure index " << fail_at; + ASSERT_EQ(allocation_tracker.live.size(), runtime_baseline) + << "failure index " << fail_at; + } + + wasm_runtime_destroy(); + EXPECT_EQ(allocation_tracker.invalid_frees, 0u); + EXPECT_TRUE(allocation_tracker.live.empty()); +} + +} // namespace diff --git a/tests/unit/component/test_resource_table.cc b/tests/unit/component/test_resource_table.cc index 4fa75b3e39..e335823c84 100644 --- a/tests/unit/component/test_resource_table.cc +++ b/tests/unit/component/test_resource_table.cc @@ -4,6 +4,8 @@ */ #include +#include +#include #include #include #include @@ -13,22 +15,99 @@ extern "C" { #include "wasm_component_resource.h" #include "wasm_component_resource_table.h" +#include "wasm_component_host_resource.h" +#include "wasm_component_canon.h" +#include "wasm_component_task.h" +#include "wasm_component_canonical.h" #include "wasm_runtime_common.h" #include "wasm_component.h" #include "wasm_component_runtime.h" } +namespace { + +int allocation_fail_after = -1; +std::atomic host_resource_dtor_count{ 0 }; + +struct CustomDropObservation { + uint32_t count; + uint32_t representation; +}; + +bool +observe_custom_resource_drop(void *attachment, uint32_t representation) +{ + CustomDropObservation *observation = + static_cast(attachment); + if (!observation) { + return false; + } + observation->count++; + observation->representation = representation; + return true; +} + +void * +failure_injecting_malloc(unsigned int size) +{ + if (allocation_fail_after == 0) { + return nullptr; + } + if (allocation_fail_after > 0) { + allocation_fail_after--; + } + return std::malloc(size); +} + +void * +failure_injecting_realloc(void *ptr, unsigned int size) +{ + return std::realloc(ptr, size); +} + +void +failure_injecting_free(void *ptr) +{ + std::free(ptr); +} + +void +count_host_resource_dtor(void *) +{ + host_resource_dtor_count.fetch_add(1, std::memory_order_relaxed); +} + +WASMComponentResourceInstance * +test_resource_type() +{ + static WASMComponentResourceInstance resource_type = { + .name = (char *)"test_resource", + .interface_name = (char *)"test_interface", + .impl = NULL, + .drop_method = NULL, + .new_method = NULL, + .rep_method = NULL, + .dtor_method = NULL, + .ctor_method = NULL, + }; + return &resource_type; +} + +} // namespace + class ResourceTableTest : public testing::Test { public: - ResourceTableTest() : table_(nullptr) {} + ResourceTableTest() + : table_(nullptr) + { + } ~ResourceTableTest() {} - virtual void SetUp() { - wasm_runtime_init(); - } + virtual void SetUp() { wasm_runtime_init(); } - virtual void TearDown() { + virtual void TearDown() + { if (table_) { wasm_component_table_destroy(table_); table_ = nullptr; @@ -40,10 +119,11 @@ class ResourceTableTest : public testing::Test WASMComponentResourceTable *table_; // Helper function to create a test resource handle - WASMResourceHandle* createTestResource(uint32_t rep = 42, bool own = true) { + WASMResourceHandle *createTestResource(uint32_t rep = 42, bool own = true) + { static WASMComponentResourceInstance dummy_rt = { - .name = (char*)"test_resource", - .interface_name = (char*)"test_interface", + .name = (char *)"test_resource", + .interface_name = (char *)"test_interface", .impl = NULL, .drop_method = NULL, .new_method = NULL, @@ -60,7 +140,7 @@ class ResourceTableTest : public testing::Test TEST_F(ResourceTableTest, ResourceHandle_CreateDestroy) { uint32_t test_rep = 456; - WASMResourceHandle* handle = createTestResource(test_rep, true); + WASMResourceHandle *handle = createTestResource(test_rep, true); ASSERT_NE(handle, nullptr); EXPECT_TRUE(handle->own); @@ -74,8 +154,8 @@ TEST_F(ResourceTableTest, ResourceHandle_CreateDestroy) TEST_F(ResourceTableTest, ResourceHandle_ConvenienceWrapper) { static WASMComponentResourceInstance dummy_rt = { - .name = (char*)"test_resource", - .interface_name = (char*)"test_interface", + .name = (char *)"test_resource", + .interface_name = (char *)"test_interface", .impl = NULL, .drop_method = NULL, .new_method = NULL, @@ -84,7 +164,7 @@ TEST_F(ResourceTableTest, ResourceHandle_ConvenienceWrapper) .ctor_method = NULL }; - WASMResourceHandle* handle = createTestResource(123, true); + WASMResourceHandle *handle = createTestResource(123, true); ASSERT_NE(handle, nullptr); EXPECT_EQ(handle->rep, 123u); @@ -93,8 +173,10 @@ TEST_F(ResourceTableTest, ResourceHandle_ConvenienceWrapper) wasm_destroy_resource_handle(handle); // Test guard conditions - EXPECT_EQ(wasm_create_resource_handle(nullptr, 42, true), nullptr); // null rt - EXPECT_EQ(wasm_create_resource_handle(&dummy_rt, 0, true), nullptr); // zero rep (invalid) + EXPECT_EQ(wasm_create_resource_handle(nullptr, 42, true), + nullptr); // null rt + EXPECT_EQ(wasm_create_resource_handle(&dummy_rt, 0, true), + nullptr); // zero rep (invalid) // Test wrapper with null handle EXPECT_EQ(wasm_resource_handle_get_rep_i32(nullptr), 0u); @@ -120,18 +202,21 @@ TEST_F(ResourceTableTest, Table_AddRemoveSingle) ASSERT_NE(table_, nullptr); // Create and add a resource - WASMResourceHandle* handle = createTestResource(); + WASMResourceHandle *handle = createTestResource(); ASSERT_NE(handle, nullptr); uint32_t index; - bool result = wasm_component_table_add(table_, handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &index); + bool result = wasm_component_table_add( + table_, handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &index); EXPECT_TRUE(result); EXPECT_EQ(index, 1u); // First valid index should be 1 EXPECT_EQ(table_->next_index, 2u); // Retrieve the resource - WASMResourceHandle* retrieved = (WASMResourceHandle*)wasm_component_table_get(table_, index, WASM_TABLE_ELEM_RESOURCE_HANDLE); + WASMResourceHandle *retrieved = + (WASMResourceHandle *)wasm_component_table_get( + table_, index, WASM_TABLE_ELEM_RESOURCE_HANDLE); EXPECT_EQ(retrieved, handle); // Remove the resource @@ -140,7 +225,8 @@ TEST_F(ResourceTableTest, Table_AddRemoveSingle) EXPECT_EQ(table_->free_count, 1u); // Verify it's gone - retrieved = (WASMResourceHandle*)wasm_component_table_get(table_, index, WASM_TABLE_ELEM_RESOURCE_HANDLE); + retrieved = (WASMResourceHandle *)wasm_component_table_get( + table_, index, WASM_TABLE_ELEM_RESOURCE_HANDLE); EXPECT_EQ(retrieved, nullptr); } @@ -154,11 +240,12 @@ TEST_F(ResourceTableTest, Table_FreeListReuse) std::vector indices; for (int i = 0; i < 3; i++) { - WASMResourceHandle* handle = createTestResource(); + WASMResourceHandle *handle = createTestResource(); ASSERT_NE(handle, nullptr); uint32_t index; - bool result = wasm_component_table_add(table_, handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &index); + bool result = wasm_component_table_add( + table_, handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &index); EXPECT_TRUE(result); EXPECT_EQ(index, static_cast(i + 1)); // Should be 1, 2, 3 indices.push_back(index); @@ -177,17 +264,20 @@ TEST_F(ResourceTableTest, Table_FreeListReuse) EXPECT_EQ(table_->free_list[0], removed_index); // Add a new resource - it should reuse the freed index - WASMResourceHandle* new_handle = createTestResource(); + WASMResourceHandle *new_handle = createTestResource(); ASSERT_NE(new_handle, nullptr); uint32_t new_index; - result = wasm_component_table_add(table_, new_handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &new_index); + result = wasm_component_table_add( + table_, new_handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &new_index); EXPECT_TRUE(result); EXPECT_EQ(new_index, removed_index); // Should reuse the freed index - EXPECT_EQ(table_->free_count, 0u); // Free list should be empty again + EXPECT_EQ(table_->free_count, 0u); // Free list should be empty again // Verify the new resource is accessible - WASMResourceHandle* retrieved = (WASMResourceHandle*)wasm_component_table_get(table_, new_index, WASM_TABLE_ELEM_RESOURCE_HANDLE); + WASMResourceHandle *retrieved = + (WASMResourceHandle *)wasm_component_table_get( + table_, new_index, WASM_TABLE_ELEM_RESOURCE_HANDLE); EXPECT_EQ(retrieved, new_handle); } @@ -205,11 +295,12 @@ TEST_F(ResourceTableTest, Table_Resize) // index 0 is reserved, so it can initially fit 1 resource // Adding 2+ resources should trigger resize for (int i = 0; i < 4; i++) { - WASMResourceHandle* handle = createTestResource(); + WASMResourceHandle *handle = createTestResource(); ASSERT_NE(handle, nullptr); uint32_t index; - bool result = wasm_component_table_add(table_, handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &index); + bool result = wasm_component_table_add( + table_, handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &index); EXPECT_TRUE(result); indices.push_back(index); @@ -219,12 +310,16 @@ TEST_F(ResourceTableTest, Table_Resize) } } - EXPECT_GT(table_->array_size, 2u); // Final size should be larger than initial - EXPECT_EQ(table_->next_index, 5u); // Should have 4 resources at indices 1,2,3,4 + EXPECT_GT(table_->array_size, + 2u); // Final size should be larger than initial + EXPECT_EQ(table_->next_index, + 5u); // Should have 4 resources at indices 1,2,3,4 // Verify all resources are still accessible after resize for (size_t i = 0; i < indices.size(); i++) { - WASMResourceHandle* retrieved = (WASMResourceHandle*)wasm_component_table_get(table_, indices[i], WASM_TABLE_ELEM_RESOURCE_HANDLE); + WASMResourceHandle *retrieved = + (WASMResourceHandle *)wasm_component_table_get( + table_, indices[i], WASM_TABLE_ELEM_RESOURCE_HANDLE); EXPECT_NE(retrieved, nullptr); // Just check it exists } @@ -235,13 +330,480 @@ TEST_F(ResourceTableTest, Table_Resize) EXPECT_EQ(table_->free_count, 1u); // Add a new resource - should reuse the freed slot - WASMResourceHandle* new_handle = createTestResource(); + WASMResourceHandle *new_handle = createTestResource(); uint32_t new_index; - result = wasm_component_table_add(table_, new_handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &new_index); + result = wasm_component_table_add( + table_, new_handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &new_index); EXPECT_TRUE(result); EXPECT_EQ(new_index, removed_index); // Should reuse freed index } +class ResourceTableAllocationFailureTest : public testing::Test +{ + public: + void SetUp() override + { + RuntimeInitArgs init_args = {}; + allocation_fail_after = -1; + init_args.mem_alloc_type = Alloc_With_Allocator; + init_args.mem_alloc_option.allocator.malloc_func = + (void *)failure_injecting_malloc; + init_args.mem_alloc_option.allocator.realloc_func = + (void *)failure_injecting_realloc; + init_args.mem_alloc_option.allocator.free_func = + (void *)failure_injecting_free; + ASSERT_TRUE(wasm_runtime_full_init(&init_args)); + } + + void TearDown() override + { + allocation_fail_after = -1; + if (table_) { + wasm_component_table_destroy(table_); + } + wasm_runtime_destroy(); + } + + protected: + WASMComponentResourceTable *table_ = nullptr; +}; + +TEST_F(ResourceTableAllocationFailureTest, + ResizeFailureLeavesBothOldBuffersAndContentsIntact) +{ + table_ = wasm_component_table_init(2, 100); + ASSERT_NE(table_, nullptr); + + WASMResourceHandle *first = + wasm_create_resource_handle(test_resource_type(), 100, true); + ASSERT_NE(first, nullptr); + uint32_t first_index = 0; + ASSERT_TRUE(wasm_component_table_add( + table_, first, WASM_TABLE_ELEM_RESOURCE_HANDLE, &first_index)); + + WASMResourceHandle *second = + wasm_create_resource_handle(test_resource_type(), 200, true); + ASSERT_NE(second, nullptr); + + WASMTableElement **old_array = table_->array; + uint32_t *old_free_list = table_->free_list; + uint32_t old_size = table_->array_size; + uint32_t old_next_index = table_->next_index; + + /* table_add allocates its element wrapper, then resize allocates the new + * element array and free list. Fail the latter allocation, after a new + * element array has already been allocated. */ + allocation_fail_after = 2; + uint32_t second_index = 0; + EXPECT_FALSE(wasm_component_table_add( + table_, second, WASM_TABLE_ELEM_RESOURCE_HANDLE, &second_index)); + allocation_fail_after = -1; + + EXPECT_EQ(table_->array, old_array); + EXPECT_EQ(table_->free_list, old_free_list); + EXPECT_EQ(table_->array_size, old_size); + EXPECT_EQ(table_->next_index, old_next_index); + EXPECT_EQ(wasm_component_table_get(table_, first_index, + WASM_TABLE_ELEM_RESOURCE_HANDLE), + first); + + wasm_destroy_resource_handle(second); + + WASMResourceHandle *third = + wasm_create_resource_handle(test_resource_type(), 300, true); + ASSERT_NE(third, nullptr); + uint32_t third_index = 0; + EXPECT_TRUE(wasm_component_table_add( + table_, third, WASM_TABLE_ELEM_RESOURCE_HANDLE, &third_index)); + EXPECT_EQ(third_index, 2u); +} + +class ResourceDropTest : public testing::Test +{ + public: + void SetUp() override + { + runtime_initialized_ = wasm_runtime_init(); + ASSERT_TRUE(runtime_initialized_); + ASSERT_TRUE(instantiate_host_resource_table()); + host_table_ = get_global_host_resource_table(); + ASSERT_NE(host_table_, nullptr); + + memset(&wasi_resource_type_, 0, sizeof(wasi_resource_type_)); + wasi_resource_type_.name = (char *)"test-host-resource"; + wasi_resource_type_.interface_name = (char *)"wasi:test/resource"; + wasi_resource_type_.is_builtin_wasi = true; + + table_ = wasm_component_table_init(4, 100); + ASSERT_NE(table_, nullptr); + host_resource_dtor_count.store(0, std::memory_order_relaxed); + } + + void TearDown() override + { + if (table_) { + wasm_component_table_destroy(table_); + table_ = nullptr; + } + if (get_global_host_resource_table()) { + destroy_host_resource_table(); + } + if (runtime_initialized_) { + wasm_runtime_destroy(); + } + } + + protected: + uint32_t addHostResource() + { + HostResource *resource = + host_resource_create(WASI_P2_TCP_SOCKET, sizeof(uint32_t)); + EXPECT_NE(resource, nullptr); + if (!resource) { + return 0; + } + + host_resource_set_dtor(resource, count_host_resource_dtor); + uint32_t id = host_resource_table_add(host_table_, resource); + EXPECT_NE(id, 0u); + if (id == 0) { + destroy_host_resource(resource); + } + return id; + } + + uint32_t addHandle(uint32_t rep, bool own) + { + WASMResourceHandle *handle = + wasm_create_resource_handle(&wasi_resource_type_, rep, own); + EXPECT_NE(handle, nullptr); + if (!handle) { + return 0; + } + + uint32_t index = 0; + if (!wasm_component_table_add( + table_, handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &index)) { + wasm_destroy_resource_handle(handle); + ADD_FAILURE() << "failed to add resource handle"; + return 0; + } + return index; + } + + bool runtime_initialized_ = false; + HostResourceTable *host_table_ = nullptr; + WASMComponentResourceTable *table_ = nullptr; + WASMComponentResourceInstance wasi_resource_type_ = {}; +}; + +TEST_F(ResourceDropTest, ExplicitDropDestroysOwnedRepresentationExactlyOnce) +{ + uint32_t rep = addHostResource(); + ASSERT_NE(rep, 0u); + uint32_t index = addHandle(rep, true); + ASSERT_NE(index, 0u); + + EXPECT_TRUE(wasm_component_table_drop_resource(table_, index)); + EXPECT_EQ(host_resource_dtor_count.load(std::memory_order_relaxed), 1u); + EXPECT_EQ(host_resource_table_get(host_table_, rep), nullptr); + + EXPECT_FALSE(wasm_component_table_drop_resource(table_, index)); + EXPECT_EQ(host_resource_dtor_count.load(std::memory_order_relaxed), 1u); +} + +TEST_F(ResourceDropTest, FailedTeardownStillConsumesHandleExactlyOnce) +{ + uint32_t missing_rep = host_resource_table_get_next_id(WASI_P2_TCP_SOCKET); + ASSERT_NE(missing_rep, 0u); + uint32_t index = addHandle(missing_rep, true); + ASSERT_NE(index, 0u); + + EXPECT_FALSE(wasm_component_table_drop_resource(table_, index)); + EXPECT_EQ(wasm_component_table_get(table_, index, + WASM_TABLE_ELEM_RESOURCE_HANDLE), + nullptr); + EXPECT_FALSE(wasm_component_table_drop_resource(table_, index)); + EXPECT_EQ(host_resource_dtor_count.load(std::memory_order_relaxed), 0u); +} + +TEST_F(ResourceDropTest, RemoveTransfersOwnershipWithoutDroppingRepresentation) +{ + uint32_t rep = addHostResource(); + ASSERT_NE(rep, 0u); + uint32_t index = addHandle(rep, true); + ASSERT_NE(index, 0u); + + EXPECT_TRUE(wasm_component_table_remove(table_, index)); + EXPECT_EQ(host_resource_dtor_count.load(std::memory_order_relaxed), 0u); + EXPECT_NE(host_resource_table_get(host_table_, rep), nullptr); + + EXPECT_EQ(host_resource_table_delete(host_table_, rep), 1u); + EXPECT_EQ(host_resource_dtor_count.load(std::memory_order_relaxed), 1u); +} + +TEST_F(ResourceDropTest, LiftOwnRejectsDifferentNominalResourceType) +{ + WASMComponentResourceInstance other_resource_type = {}; + WASMComponentResourceHandleInstance expected_handle_type = {}; + WASMComponentInstance component_instance = {}; + LiftLowerContext context = {}; + wit_value_t lifted = nullptr; + uint32_t rep = addHostResource(); + ASSERT_NE(rep, 0u); + uint32_t index = addHandle(rep, true); + ASSERT_NE(index, 0u); + + other_resource_type.name = (char *)"different-host-resource"; + other_resource_type.interface_name = (char *)"wasi:test/other-resource"; + other_resource_type.is_builtin_wasi = true; + expected_handle_type.resource = &other_resource_type; + component_instance.table = table_; + context.inst = &component_instance; + + EXPECT_FALSE(lift_own(&context, index, &expected_handle_type, &lifted)); + EXPECT_EQ(lifted, nullptr); + EXPECT_NE(wasm_component_table_get(table_, index, + WASM_TABLE_ELEM_RESOURCE_HANDLE), + nullptr); + EXPECT_NE(host_resource_table_get(host_table_, rep), nullptr); + + EXPECT_TRUE(wasm_component_table_drop_resource(table_, index)); + EXPECT_EQ(host_resource_dtor_count.load(std::memory_order_relaxed), 1u); +} + +TEST_F(ResourceDropTest, TableDestroyDropsOwnedButNotBorrowedRepresentations) +{ + uint32_t owned_rep = addHostResource(); + uint32_t borrowed_rep = addHostResource(); + ASSERT_NE(owned_rep, 0u); + ASSERT_NE(borrowed_rep, 0u); + ASSERT_NE(addHandle(owned_rep, true), 0u); + ASSERT_NE(addHandle(borrowed_rep, false), 0u); + + wasm_component_table_destroy(table_); + table_ = nullptr; + + EXPECT_EQ(host_resource_dtor_count.load(std::memory_order_relaxed), 1u); + EXPECT_EQ(host_resource_table_get(host_table_, owned_rep), nullptr); + EXPECT_NE(host_resource_table_get(host_table_, borrowed_rep), nullptr); + + EXPECT_EQ(host_resource_table_delete(host_table_, borrowed_rep), 1u); + EXPECT_EQ(host_resource_dtor_count.load(std::memory_order_relaxed), 2u); +} + +TEST_F(ResourceDropTest, CanonicalBorrowDropUpdatesTaskWithoutDestroyingRep) +{ + uint32_t rep = addHostResource(); + ASSERT_NE(rep, 0u); + uint32_t index = addHandle(rep, false); + ASSERT_NE(index, 0u); + + Task task = {}; + task.num_borrows = 1; + WASMResourceHandle *handle = (WASMResourceHandle *)wasm_component_table_get( + table_, index, WASM_TABLE_ELEM_RESOURCE_HANDLE); + ASSERT_NE(handle, nullptr); + handle->borrow_scope = &task; + + WASMComponentInstance component_instance = {}; + component_instance.table = table_; + component_instance.may_leave = true; + + EXPECT_TRUE( + canon_resource_drop(&wasi_resource_type_, &component_instance, index)); + EXPECT_EQ(task.num_borrows, 0u); + EXPECT_NE(host_resource_table_get(host_table_, rep), nullptr); + EXPECT_EQ(host_resource_dtor_count.load(std::memory_order_relaxed), 0u); + + EXPECT_EQ(host_resource_table_delete(host_table_, rep), 1u); + EXPECT_EQ(host_resource_dtor_count.load(std::memory_order_relaxed), 1u); +} + +TEST_F(ResourceDropTest, ComponentDeinstantiateDropsOwnedResources) +{ + uint32_t rep = addHostResource(); + ASSERT_NE(rep, 0u); + ASSERT_NE(addHandle(rep, true), 0u); + + WASMComponentInstance *component_instance = + (WASMComponentInstance *)wasm_runtime_malloc( + sizeof(WASMComponentInstance)); + ASSERT_NE(component_instance, nullptr); + memset(component_instance, 0, sizeof(WASMComponentInstance)); + component_instance->table = table_; + table_ = nullptr; + + wasm_component_deinstantiate(component_instance); + + EXPECT_EQ(host_resource_dtor_count.load(std::memory_order_relaxed), 1u); + EXPECT_EQ(host_resource_table_get(host_table_, rep), nullptr); +} + +TEST_F(ResourceDropTest, CustomImportedResourceUsesPerInstanceDropCallback) +{ + WASMComponentResourceInstance custom_resource = {}; + custom_resource.name = (char *)"audio-node"; + custom_resource.interface_name = + (char *)"rebeckerspecialties:web-audio/graph@0.1.0"; + custom_resource.is_host = true; + + WASMComponentTypeInstance resource_type = {}; + resource_type.type = COMPONENT_VAL_TYPE_RESOURCE_SYNC; + resource_type.type_specific.resource = &custom_resource; + WASMComponentTypeInstance *types[] = { &resource_type }; + + WASMComponentInstance component_instance = {}; + component_instance.types = types; + component_instance.types_count = 1; + component_instance.table = table_; + component_instance.may_leave = true; + + CustomDropObservation observation = {}; + EXPECT_FALSE(wasm_component_set_host_resource_drop_callback( + &component_instance, "rebeckerspecialties:web-audio/graph@0.2.0", + "audio-node", observe_custom_resource_drop, &observation)); + ASSERT_TRUE(wasm_component_set_host_resource_drop_callback( + &component_instance, "rebeckerspecialties:web-audio/graph@0.1.0", + "audio-node", observe_custom_resource_drop, &observation)); + + WASMResourceHandle *handle = + wasm_create_resource_handle(&custom_resource, 0x1234, true); + ASSERT_NE(handle, nullptr); + uint32_t index = 0; + ASSERT_TRUE(wasm_component_table_add( + table_, handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &index)); + + EXPECT_TRUE( + canon_resource_drop(&custom_resource, &component_instance, index)); + EXPECT_EQ(observation.count, 1u); + EXPECT_EQ(observation.representation, 0x1234u); + EXPECT_FALSE(wasm_component_table_drop_resource(table_, index)); + EXPECT_EQ(observation.count, 1u); +} + +TEST_F(ResourceDropTest, PreInstantiationDropRegistrationIsExactAndOwned) +{ + WASMComponent component = {}; + wasm_component_host_resource_drop_callback_t callback = nullptr; + + ASSERT_TRUE(wasm_component_register_host_resource_drop_callback( + &component, "rebeckerspecialties:web-audio/graph@0.1.0", "audio-node", + observe_custom_resource_drop)); + EXPECT_FALSE(wasm_component_find_host_resource_drop_callback( + &component, "rebeckerspecialties:web-audio/graph@0.2.0", "audio-node", + &callback)); + EXPECT_FALSE(wasm_component_find_host_resource_drop_callback( + &component, "rebeckerspecialties:web-audio/graph@0.1.0", "gain-node", + &callback)); + ASSERT_TRUE(wasm_component_find_host_resource_drop_callback( + &component, "rebeckerspecialties:web-audio/graph@0.1.0", "audio-node", + &callback)); + EXPECT_EQ(callback, observe_custom_resource_drop); + + wasm_component_free(&component); + EXPECT_EQ(component.host_resource_drops, nullptr); + EXPECT_EQ(component.host_resource_drop_count, 0u); +} + +TEST_F(ResourceDropTest, ComponentTeardownDropsCustomOwnedResourceExactlyOnce) +{ + WASMComponentResourceInstance custom_resource = {}; + custom_resource.name = (char *)"audio-node"; + custom_resource.interface_name = + (char *)"rebeckerspecialties:web-audio/graph@0.1.0"; + custom_resource.is_host = true; + + WASMComponentTypeInstance resource_type = {}; + resource_type.type = COMPONENT_VAL_TYPE_RESOURCE_SYNC; + resource_type.type_specific.resource = &custom_resource; + WASMComponentTypeInstance *types[] = { &resource_type }; + + WASMComponentInstance *component_instance = + (WASMComponentInstance *)wasm_runtime_malloc( + sizeof(WASMComponentInstance)); + ASSERT_NE(component_instance, nullptr); + memset(component_instance, 0, sizeof(*component_instance)); + component_instance->types = types; + component_instance->types_count = 1; + component_instance->table = table_; + table_ = nullptr; + + CustomDropObservation observation = {}; + ASSERT_TRUE(wasm_component_set_host_resource_drop_callback( + component_instance, "rebeckerspecialties:web-audio/graph@0.1.0", + "audio-node", observe_custom_resource_drop, &observation)); + + WASMResourceHandle *handle = + wasm_create_resource_handle(&custom_resource, 77, true); + ASSERT_NE(handle, nullptr); + uint32_t index = 0; + ASSERT_TRUE(wasm_component_table_add(component_instance->table, handle, + WASM_TABLE_ELEM_RESOURCE_HANDLE, + &index)); + + wasm_component_deinstantiate(component_instance); + EXPECT_EQ(observation.count, 1u); + EXPECT_EQ(observation.representation, 77u); +} + +TEST_F(ResourceDropTest, BorrowedCustomResourceNeverInvokesOwnerDrop) +{ + WASMComponentResourceInstance custom_resource = {}; + custom_resource.name = (char *)"audio-node"; + custom_resource.interface_name = + (char *)"rebeckerspecialties:web-audio/graph@0.1.0"; + custom_resource.is_host = true; + + WASMComponentTypeInstance resource_type = {}; + resource_type.type = COMPONENT_VAL_TYPE_RESOURCE_SYNC; + resource_type.type_specific.resource = &custom_resource; + WASMComponentTypeInstance *types[] = { &resource_type }; + + WASMComponentInstance component_instance = {}; + component_instance.types = types; + component_instance.types_count = 1; + component_instance.table = table_; + + CustomDropObservation observation = {}; + ASSERT_TRUE(wasm_component_set_host_resource_drop_callback( + &component_instance, "rebeckerspecialties:web-audio/graph@0.1.0", + "audio-node", observe_custom_resource_drop, &observation)); + + WASMResourceHandle *handle = + wasm_create_resource_handle(&custom_resource, 91, false); + ASSERT_NE(handle, nullptr); + uint32_t index = 0; + ASSERT_TRUE(wasm_component_table_add( + table_, handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &index)); + + wasm_component_table_destroy(table_); + table_ = nullptr; + EXPECT_EQ(observation.count, 0u); +} + +TEST_F(ResourceDropTest, CustomImportedResourceWithoutCallbackFailsClosed) +{ + WASMComponentResourceInstance custom_resource = {}; + custom_resource.name = (char *)"gpu-buffer"; + custom_resource.interface_name = + (char *)"rebeckerspecialties:webgpu-p2/gpu@0.1.0"; + custom_resource.is_host = true; + + WASMResourceHandle *handle = + wasm_create_resource_handle(&custom_resource, 9, true); + ASSERT_NE(handle, nullptr); + uint32_t index = 0; + ASSERT_TRUE(wasm_component_table_add( + table_, handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, &index)); + + EXPECT_FALSE(wasm_component_table_drop_resource(table_, index)); + EXPECT_EQ(wasm_component_table_get(table_, index, + WASM_TABLE_ELEM_RESOURCE_HANDLE), + nullptr); + EXPECT_FALSE(wasm_component_table_drop_resource(table_, index)); +} + // Test edge cases and error conditions TEST_F(ResourceTableTest, Table_EdgeCases) { @@ -250,13 +812,15 @@ TEST_F(ResourceTableTest, Table_EdgeCases) // Test adding NULL handle uint32_t index; - bool result = wasm_component_table_add(table_, nullptr, WASM_TABLE_ELEM_RESOURCE_HANDLE, &index); + bool result = wasm_component_table_add( + table_, nullptr, WASM_TABLE_ELEM_RESOURCE_HANDLE, &index); EXPECT_FALSE(result); // Test adding with NULL out_index - WASMResourceHandle* handle = createTestResource(); + WASMResourceHandle *handle = createTestResource(); ASSERT_NE(handle, nullptr); - result = wasm_component_table_add(table_, handle, WASM_TABLE_ELEM_RESOURCE_HANDLE, nullptr); + result = wasm_component_table_add(table_, handle, + WASM_TABLE_ELEM_RESOURCE_HANDLE, nullptr); EXPECT_FALSE(result); wasm_destroy_resource_handle(handle); // Clean up since add failed @@ -268,14 +832,17 @@ TEST_F(ResourceTableTest, Table_EdgeCases) EXPECT_FALSE(result); // Test getting from invalid indices - WASMResourceHandle* retrieved = (WASMResourceHandle*)wasm_component_table_get(table_, 0, WASM_TABLE_ELEM_RESOURCE_HANDLE); + WASMResourceHandle *retrieved = + (WASMResourceHandle *)wasm_component_table_get( + table_, 0, WASM_TABLE_ELEM_RESOURCE_HANDLE); EXPECT_EQ(retrieved, nullptr); - retrieved = (WASMResourceHandle*)wasm_component_table_get(table_, 999, WASM_TABLE_ELEM_RESOURCE_HANDLE); + retrieved = (WASMResourceHandle *)wasm_component_table_get( + table_, 999, WASM_TABLE_ELEM_RESOURCE_HANDLE); EXPECT_EQ(retrieved, nullptr); // Test initialization with invalid parameters - WASMComponentResourceTable* bad_table = wasm_component_table_init(0, 50); + WASMComponentResourceTable *bad_table = wasm_component_table_init(0, 50); EXPECT_EQ(bad_table, nullptr); bad_table = wasm_component_table_init(4, 0); diff --git a/tests/unit/component/test_wasi_configuration.cc b/tests/unit/component/test_wasi_configuration.cc new file mode 100644 index 0000000000..bd1d17a13f --- /dev/null +++ b/tests/unit/component/test_wasi_configuration.cc @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2026 Matt Hargett. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include + +#include "helpers.h" + +extern "C" { +#include "wasm_runtime_common.h" +#include "../../../product-mini/platforms/common/libc_wasi.h" +} + +namespace { + +class ComponentWasiConfigurationTest : public testing::Test +{ + protected: + ComponentHelper helper; + + void SetUp() override { helper.do_setup(); } + void TearDown() override { helper.do_teardown(); } +}; + +TEST_F(ComponentWasiConfigurationTest, ComponentDefaultsAndSettersAreExplicit) +{ + ASSERT_TRUE(helper.runtime_init); + ASSERT_TRUE(helper.read_wasm_file("add.wasm")); + ASSERT_TRUE(helper.load_component()); + + EXPECT_FALSE(helper.component->wasi_args.set_by_user); + EXPECT_EQ(helper.component->wasi_args.stdio[0], os_invalid_raw_handle()); + EXPECT_EQ(helper.component->wasi_args.stdio[1], os_invalid_raw_handle()); + EXPECT_EQ(helper.component->wasi_args.stdio[2], os_invalid_raw_handle()); + + WASMComponent args_component = {}; + wasi_args_set_defaults(&args_component.wasi_args); + char arg[] = "guest"; + char *argv[] = { arg }; + wasm_component_runtime_set_wasi_args(&args_component, nullptr, 0, nullptr, + 0, nullptr, 0, argv, 1); + EXPECT_TRUE(args_component.wasi_args.set_by_user); + EXPECT_TRUE(args_component.import_wasi_api); + + WASMComponent addr_component = {}; + wasi_args_set_defaults(&addr_component.wasi_args); + const char *addr_pool[] = { "127.0.0.1/32" }; + wasm_component_runtime_set_wasi_addr_pool(&addr_component, addr_pool, 1); + EXPECT_TRUE(addr_component.wasi_args.set_by_user); + + WASMComponent lookup_component = {}; + wasi_args_set_defaults(&lookup_component.wasi_args); + const char *lookup_pool[] = { "localhost" }; + wasm_component_runtime_set_wasi_ns_lookup_pool(&lookup_component, + lookup_pool, 1); + EXPECT_TRUE(lookup_component.wasi_args.set_by_user); + + WASMComponent options_component = {}; + wasi_args_set_defaults(&options_component.wasi_args); + libc_wasi_options_t options = {}; + wasm_component_runtime_set_wasi_options(&options_component, &options); + EXPECT_TRUE(options_component.wasi_args.set_by_user); +} + +TEST_F(ComponentWasiConfigurationTest, + ExplicitComponentArgumentsEnvironmentAndPreopenReachContext) +{ + ASSERT_TRUE(helper.runtime_init); + ASSERT_TRUE(helper.read_wasm_file("add.wasm")); + ASSERT_TRUE(helper.load_component()); + + const char *dirs[] = { "." }; + const char *env[] = { "KEY=value" }; + char arg0[] = "guest"; + char arg1[] = "arg1"; + char *argv[] = { arg0, arg1 }; + wasm_component_runtime_set_wasi_args(helper.component, dirs, 1, nullptr, 0, + env, 1, argv, 2); + + ASSERT_TRUE(helper.instantiate_component()) << helper.error_buf; + WASIContext *wasi_ctx = helper.component_inst->wasi_ctx; + ASSERT_NE(wasi_ctx, nullptr); + ASSERT_NE(wasi_ctx->argv_list, nullptr); + ASSERT_NE(wasi_ctx->env_list, nullptr); + EXPECT_STREQ(wasi_ctx->argv_list[0], "guest"); + EXPECT_STREQ(wasi_ctx->argv_list[1], "arg1"); + EXPECT_EQ(wasi_ctx->argv_list[2], nullptr); + EXPECT_STREQ(wasi_ctx->env_list[0], "KEY=value"); + EXPECT_EQ(wasi_ctx->env_list[1], nullptr); + ASSERT_NE(wasi_ctx->prestats, nullptr); + ASSERT_GT(wasi_ctx->prestats->size, 3u); + ASSERT_NE(wasi_ctx->prestats->prestats[3].dir, nullptr); + EXPECT_STREQ(wasi_ctx->prestats->prestats[3].dir, "."); +} + +TEST_F(ComponentWasiConfigurationTest, + MixedComponentAndInstantiationConfigurationIsRejected) +{ + ASSERT_TRUE(helper.runtime_init); + ASSERT_TRUE(helper.read_wasm_file("add.wasm")); + ASSERT_TRUE(helper.load_component()); + + char component_arg[] = "component"; + char *component_argv[] = { component_arg }; + wasm_component_runtime_set_wasi_args(helper.component, nullptr, 0, nullptr, + 0, nullptr, 0, component_argv, 1); + + struct InstantiationArgs2 *args = nullptr; + ASSERT_TRUE(wasm_runtime_instantiation_args_create(&args)); + char instance_arg[] = "instance"; + char *instance_argv[] = { instance_arg }; + wasm_runtime_instantiation_args_set_wasi_arg(args, instance_argv, 1); + WASMComponentInstance *instance = wasm_component_instantiate_ex2( + helper.component, args, helper.error_buf, sizeof(helper.error_buf)); + wasm_runtime_instantiation_args_destroy(args); + + EXPECT_EQ(instance, nullptr); + if (instance) { + wasm_component_deinstantiate(instance); + } + EXPECT_NE( + strstr(helper.error_buf, "both the component and InstantiationArgs2"), + nullptr) + << helper.error_buf; +} + +} // namespace diff --git a/tests/unit/component/wasm-apps/custom_data_update.wasm b/tests/unit/component/wasm-apps/custom_data_update.wasm new file mode 100644 index 0000000000..2d5d103bbf Binary files /dev/null and b/tests/unit/component/wasm-apps/custom_data_update.wasm differ diff --git a/tests/unit/component/wasm-apps/custom_data_update.wat b/tests/unit/component/wasm-apps/custom_data_update.wat new file mode 100644 index 0000000000..beab9f6955 --- /dev/null +++ b/tests/unit/component/wasm-apps/custom_data_update.wat @@ -0,0 +1,28 @@ +;; Copyright (C) 2026 Matt Hargett. All rights reserved. +;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +(component + (type $observe-type (func (result u32))) + (type $host-type + (instance + (export "item" (type (sub resource))) + (export "observe" (func (type $observe-type))))) + (import "test:context/host@1.0.0" + (instance $host (type $host-type))) + + (alias export $host "observe" (func $observe)) + (core func $lowered-observe (canon lower (func $observe))) + (core instance $lowered-host + (export "observe" (func $lowered-observe))) + + (core module $guest + (import "host" "observe" (func $observe (result i32))) + (func (export "call") (result i32) + call $observe)) + (core instance $guest-instance + (instantiate $guest + (with "host" (instance $lowered-host)))) + + (alias core export $guest-instance "call" (core func $call)) + (func $lifted-call (type $observe-type) (canon lift (core func $call))) + (export "call" (func $lifted-call))) diff --git a/tests/unit/component/wasm-apps/exact_newer_wasi_host.wasm b/tests/unit/component/wasm-apps/exact_newer_wasi_host.wasm new file mode 100644 index 0000000000..ca165d0123 Binary files /dev/null and b/tests/unit/component/wasm-apps/exact_newer_wasi_host.wasm differ diff --git a/tests/unit/component/wasm-apps/exact_newer_wasi_host.wat b/tests/unit/component/wasm-apps/exact_newer_wasi_host.wat new file mode 100644 index 0000000000..05ee79b9aa --- /dev/null +++ b/tests/unit/component/wasm-apps/exact_newer_wasi_host.wat @@ -0,0 +1,10 @@ +;; Copyright (C) 2026 Matt Hargett. All rights reserved. +;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +(component + (type $random-host + (instance + (type $get-random-u64 (func (result u64))) + (export "get-random-u64" (func (type $get-random-u64))))) + (import "wasi:random/random@0.3.0" + (instance (type $random-host)))) diff --git a/tests/unit/component/wasm-apps/large_shared_host_aliases.wasm b/tests/unit/component/wasm-apps/large_shared_host_aliases.wasm new file mode 100644 index 0000000000..ecc5996d12 Binary files /dev/null and b/tests/unit/component/wasm-apps/large_shared_host_aliases.wasm differ diff --git a/tests/unit/component/wasm-apps/large_shared_host_aliases.wat b/tests/unit/component/wasm-apps/large_shared_host_aliases.wat new file mode 100644 index 0000000000..d0ef59c27f --- /dev/null +++ b/tests/unit/component/wasm-apps/large_shared_host_aliases.wat @@ -0,0 +1,76 @@ +;; Copyright (C) 2026 Matt Hargett. All rights reserved. +;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +(component + (type $host-interface + (instance + (export "item" (type (sub resource))) + (export "item-alias-00" (type (eq 0))) + (export "item-alias-01" (type (eq 0))) + (export "item-alias-02" (type (eq 0))) + (export "item-alias-03" (type (eq 0))) + (export "item-alias-04" (type (eq 0))) + (export "item-alias-05" (type (eq 0))) + (export "item-alias-06" (type (eq 0))) + (export "item-alias-07" (type (eq 0))) + (export "item-alias-08" (type (eq 0))) + (export "item-alias-09" (type (eq 0))) + (export "item-alias-10" (type (eq 0))) + (export "item-alias-11" (type (eq 0))) + (export "item-alias-12" (type (eq 0))) + (export "item-alias-13" (type (eq 0))) + (export "item-alias-14" (type (eq 0))) + (export "item-alias-15" (type (eq 0))) + (export "item-alias-16" (type (eq 0))) + (export "item-alias-17" (type (eq 0))) + (export "item-alias-18" (type (eq 0))) + (export "item-alias-19" (type (eq 0))) + (export "item-alias-20" (type (eq 0))) + (export "item-alias-21" (type (eq 0))) + (export "item-alias-22" (type (eq 0))) + (export "item-alias-23" (type (eq 0))) + (export "item-alias-24" (type (eq 0))) + (export "item-alias-25" (type (eq 0))) + (export "item-alias-26" (type (eq 0))) + (export "item-alias-27" (type (eq 0))) + (export "item-alias-28" (type (eq 0))) + (export "item-alias-29" (type (eq 0))) + (export "item-alias-30" (type (eq 0))) + (export "item-alias-31" (type (eq 0))) + (export "item-alias-32" (type (eq 0))) + (export "item-alias-33" (type (eq 0))) + (export "item-alias-34" (type (eq 0))) + (export "item-alias-35" (type (eq 0))) + (export "item-alias-36" (type (eq 0))) + (export "item-alias-37" (type (eq 0))) + (export "item-alias-38" (type (eq 0))) + (export "item-alias-39" (type (eq 0))) + (export "item-alias-40" (type (eq 0))) + (export "item-alias-41" (type (eq 0))) + (export "item-alias-42" (type (eq 0))) + (export "item-alias-43" (type (eq 0))) + (export "item-alias-44" (type (eq 0))) + (export "item-alias-45" (type (eq 0))) + (export "item-alias-46" (type (eq 0))) + (export "item-alias-47" (type (eq 0))) + (export "item-alias-48" (type (eq 0))) + (export "item-alias-49" (type (eq 0))) + (export "item-alias-50" (type (eq 0))) + (export "item-alias-51" (type (eq 0))) + (export "item-alias-52" (type (eq 0))) + (export "item-alias-53" (type (eq 0))) + (export "item-alias-54" (type (eq 0))) + (export "item-alias-55" (type (eq 0))) + (export "item-alias-56" (type (eq 0))) + (export "item-alias-57" (type (eq 0))) + (export "item-alias-58" (type (eq 0))) + (export "item-alias-59" (type (eq 0))) + (export "item-alias-60" (type (eq 0))) + (export "item-alias-61" (type (eq 0))) + (export "item-alias-62" (type (eq 0))) + (export "item-alias-63" (type (eq 0))) + (type $ping-type (func)) + (export "ping" (func (type $ping-type))))) + + (import "test:alias/large@1.0.0" + (instance (type $host-interface)))) diff --git a/tests/unit/component/wasm-apps/shared_host_instance_type.wasm b/tests/unit/component/wasm-apps/shared_host_instance_type.wasm new file mode 100644 index 0000000000..a241d20ac4 Binary files /dev/null and b/tests/unit/component/wasm-apps/shared_host_instance_type.wasm differ diff --git a/tests/unit/component/wasm-apps/shared_host_instance_type.wat b/tests/unit/component/wasm-apps/shared_host_instance_type.wat new file mode 100644 index 0000000000..e4edea564f --- /dev/null +++ b/tests/unit/component/wasm-apps/shared_host_instance_type.wat @@ -0,0 +1,15 @@ +;; Copyright (C) 2026 Matt Hargett. All rights reserved. +;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +(component + (type $host-interface + (instance + (export "item" (type (sub resource))) + (type $use-type + (func (param "item" (borrow 0)))) + (export "use-item" (func (type $use-type))))) + + (import "test:alias/first@1.0.0" + (instance (type $host-interface))) + (import "test:alias/second@1.0.0" + (instance (type $host-interface)))) diff --git a/tests/unit/libc-wasi-p2/CMakeLists.txt b/tests/unit/libc-wasi-p2/CMakeLists.txt index ef23f0da74..1c8386ef1a 100644 --- a/tests/unit/libc-wasi-p2/CMakeLists.txt +++ b/tests/unit/libc-wasi-p2/CMakeLists.txt @@ -52,6 +52,17 @@ target_include_directories(libc-wasi-p2 PRIVATE ${LIBC_WASI_P2_INCLUDE_DIRS}) find_package(Threads REQUIRED) target_link_libraries (libc-wasi-p2 ${LLVM_AVAILABLE_LIBS} ${WAMR_TEST_OPENSSL_LIBS} gtest_main Threads::Threads) -target_compile_definitions(libc-wasi-p2 PRIVATE WAMR_BUILD_COMPONENT_MODEL=1) +target_compile_definitions(libc-wasi-p2 PRIVATE + WAMR_BUILD_COMPONENT_MODEL=1 + WAMR_UNIT_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" +) -gtest_discover_tests(libc-wasi-p2) \ No newline at end of file +gtest_discover_tests(libc-wasi-p2 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} +) + +add_custom_command(TARGET libc-wasi-p2 POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/wasm-apps/*.wasm + ${CMAKE_CURRENT_BINARY_DIR}/ +) diff --git a/tests/unit/libc-wasi-p2/test_libc_wasi_p2_wrapper.cc b/tests/unit/libc-wasi-p2/test_libc_wasi_p2_wrapper.cc index c417010bf9..dd0bee4e9c 100644 --- a/tests/unit/libc-wasi-p2/test_libc_wasi_p2_wrapper.cc +++ b/tests/unit/libc-wasi-p2/test_libc_wasi_p2_wrapper.cc @@ -23,6 +23,61 @@ class WasiP2WrapperTest : public testing::Test { std::unique_ptr> runtime_; }; +class ScopedWasiP2ModuleVersion +{ + public: + ScopedWasiP2ModuleVersion(wasi_p2_module_t *module, const char *version) + : module_(module) + , original_version_(module->version) + { + module_->version = version; + } + + ~ScopedWasiP2ModuleVersion() { module_->version = original_version_; } + + private: + wasi_p2_module_t *module_; + const char *original_version_; +}; + +TEST_F(WasiP2WrapperTest, VersionCompatibilityUsesRuntimePatchAsUpperBound) +{ + wasi_p2_module_t *modules = NULL; + uint32_t module_count = get_libc_wasi_p2_export_apis(&modules); + wasi_p2_module_t *io_poll_module = NULL; + + for (uint32_t i = 0; i < module_count; i++) { + if (strcmp(modules[i].module_name, "wasi:io/poll") == 0) { + io_poll_module = &modules[i]; + break; + } + } + + ASSERT_NE(io_poll_module, nullptr); + ScopedWasiP2ModuleVersion version(io_poll_module, "0.2.6"); + + EXPECT_TRUE(wasm_check_wasi_p2_version("wasi:io/poll")); + EXPECT_TRUE(wasm_check_wasi_p2_version("wasi:io/poll@0.2.0")); + EXPECT_TRUE(wasm_check_wasi_p2_version("wasi:io/poll@0.2.3")); + EXPECT_TRUE(wasm_check_wasi_p2_version("wasi:io/poll@0.2.6")); + EXPECT_FALSE(wasm_check_wasi_p2_version("wasi:io/poll@0.2.7")); + EXPECT_FALSE(wasm_check_wasi_p2_version("wasi:io/poll@0.1.6")); + EXPECT_FALSE(wasm_check_wasi_p2_version("wasi:io/poll@1.2.6")); + EXPECT_FALSE(wasm_check_wasi_p2_version("wasi:io/poll@0.2")); + EXPECT_FALSE(wasm_check_wasi_p2_version("wasi:io/poll@0.2.x")); +} + +TEST_F(WasiP2WrapperTest, BuiltInModulesDeclareImplementedPackageVersion) +{ + wasi_p2_module_t *modules = NULL; + uint32_t module_count = get_libc_wasi_p2_export_apis(&modules); + + ASSERT_GT(module_count, 0u); + for (uint32_t i = 0; i < module_count; i++) { + EXPECT_STREQ(modules[i].version, "0.2.6") << modules[i].module_name; + } +} + // Test to verify that all exported WASI P2 symbols can be resolved. TEST_F(WasiP2WrapperTest, LookupAllWasiP2Symbols) { @@ -55,11 +110,33 @@ TEST_F(WasiP2WrapperTest, LookupAllWasiP2Symbols) // Test that a symbol in a non-existent module is not resolved. void *func_ptr = wasm_native_resolve_symbol( - "wasi:non-existent/interface@0.2.3", "non-existent", nullptr, nullptr, + "wasi:non-existent/interface@0.2.6", "non-existent", nullptr, nullptr, nullptr, nullptr); EXPECT_EQ(func_ptr, nullptr); } +TEST_F(WasiP2WrapperTest, BuiltinResourceRegistryIsExactAndVersioned) +{ + EXPECT_TRUE(wasm_native_has_builtin_wasi_p2_resource( + "wasi:sockets/network@0.2.6", "network")); + EXPECT_TRUE(wasm_native_has_builtin_wasi_p2_resource( + "wasi:filesystem/types@0.2.6", "descriptor")); + + EXPECT_FALSE(wasm_native_has_builtin_wasi_p2_resource( + "wasi:sockets/network@0.2.6", "unknown")); + EXPECT_FALSE(wasm_native_has_builtin_wasi_p2_resource( + "wasi:unknown/network@0.2.6", "network")); + EXPECT_FALSE(wasm_native_has_builtin_wasi_p2_resource( + "wasi:sockets/network", "network")); + EXPECT_FALSE(wasm_native_has_builtin_wasi_p2_resource( + "wasi:sockets/network@0.2.7", "network")); + EXPECT_FALSE(wasm_native_has_builtin_wasi_p2_resource( + "wasi:sockets/network@0.3.0", "network")); + EXPECT_FALSE(wasm_native_has_builtin_wasi_p2_resource(nullptr, "network")); + EXPECT_FALSE(wasm_native_has_builtin_wasi_p2_resource( + "wasi:sockets/network@0.2.6", nullptr)); +} + // Test to ensure that WASI P2 symbols are not resolved in the WASI P1 namespace. TEST_F(WasiP2WrapperTest, LookupWasiP2SymbolsInWasiP1NamespaceShouldFail) { @@ -87,7 +164,7 @@ TEST_F(WasiP2WrapperTest, LookupWasiP2SymbolsInWasiP1NamespaceShouldFail) // Test the registration and unregistration of all WASI P2 modules. TEST_F(WasiP2WrapperTest, RegisterUnregisterAllModules) { - const char *module_name = "wasi:clocks/wall-clock@0.2.3"; + const char *module_name = "wasi:clocks/wall-clock@0.2.6"; const char *symbol_name = "now"; // Unregister all modules. @@ -110,9 +187,9 @@ TEST_F(WasiP2WrapperTest, RegisterUnregisterAllModules) // Test the registration and unregistration of a single WASI P2 module. TEST_F(WasiP2WrapperTest, RegisterUnregisterSingleModule) { - const char *module_to_test = "wasi:clocks/monotonic-clock@0.2.3"; + const char *module_to_test = "wasi:clocks/monotonic-clock@0.2.6"; const char *symbol_to_test = "now"; - const char *another_module = "wasi:clocks/wall-clock@0.2.3"; + const char *another_module = "wasi:clocks/wall-clock@0.2.6"; const char *another_symbol = "now"; // Ensure they are registered first @@ -143,7 +220,7 @@ TEST_F(WasiP2WrapperTest, RegisterUnregisterSingleModule) // Test the registration and unregistration of a single function within a WASI P2 module. TEST_F(WasiP2WrapperTest, RegisterUnregisterSingleFunction) { - const char *module_name = "wasi:random/random@0.2.3"; + const char *module_name = "wasi:random/random@0.2.6"; const char *func_to_test = "get-random-bytes"; const char *another_func = "get-random-u64"; @@ -180,4 +257,4 @@ TEST_F(WasiP2WrapperTest, RegisterUnregisterSingleFunction) // Re-register the whole module so other tests are not affected. EXPECT_TRUE(wasm_native_register_wasi_p2_module(module_name)); -} \ No newline at end of file +} diff --git a/tests/unit/libc-wasi-p2/test_sandboxed_env.cc b/tests/unit/libc-wasi-p2/test_sandboxed_env.cc index 8ec3717827..116fd9093c 100644 --- a/tests/unit/libc-wasi-p2/test_sandboxed_env.cc +++ b/tests/unit/libc-wasi-p2/test_sandboxed_env.cc @@ -36,7 +36,7 @@ class CanonSandboxedEnv : public testing::Test bool runtime_init = false; WASMComponentInstance *comp_instance; - libc_wasi_parse_context_t parse_ctx; + libc_wasi_parse_context_t parse_ctx = {}; // WASIContext *wasi_ctx; char test_dir[128]; diff --git a/tests/unit/libc-wasi-p2/test_wasi_p2_cli.cc b/tests/unit/libc-wasi-p2/test_wasi_p2_cli.cc index 6441e39144..f80cc855e4 100644 --- a/tests/unit/libc-wasi-p2/test_wasi_p2_cli.cc +++ b/tests/unit/libc-wasi-p2/test_wasi_p2_cli.cc @@ -14,6 +14,7 @@ extern "C" { #include "wasi_p2_cli.h" +extern char **environ; } // The set_wasi_p2_args function is not in a public header, @@ -156,4 +157,4 @@ TEST_F(WasiP2CliTest, Environment_InitialCwd) ASSERT_STREQ(cwd, expected_cwd); free(cwd); -} \ No newline at end of file +} diff --git a/tests/unit/libc-wasi-p2/test_wasi_p2_cli_wrapper.cc b/tests/unit/libc-wasi-p2/test_wasi_p2_cli_wrapper.cc index 8cde711681..e18bb109d6 100644 --- a/tests/unit/libc-wasi-p2/test_wasi_p2_cli_wrapper.cc +++ b/tests/unit/libc-wasi-p2/test_wasi_p2_cli_wrapper.cc @@ -30,7 +30,7 @@ class WasiP2CliWrapperTest : public testing::Test bool runtime_init = false; WASMComponentInstance *comp_instance; - libc_wasi_parse_context_t parse_ctx; + libc_wasi_parse_context_t parse_ctx = {}; char *argv[2] = {(char *)"arg1", (char *)"arg2"}; @@ -247,4 +247,4 @@ TEST_F(WasiP2CliWrapperTest, test_call_exit) ASSERT_TRUE(WIFEXITED(status)); ASSERT_EQ(WEXITSTATUS(status), 0); } -} \ No newline at end of file +} diff --git a/tests/unit/libc-wasi-p2/test_wasi_p2_clocks_wrapper.cc b/tests/unit/libc-wasi-p2/test_wasi_p2_clocks_wrapper.cc index fcca6f771a..f04c0c294f 100644 --- a/tests/unit/libc-wasi-p2/test_wasi_p2_clocks_wrapper.cc +++ b/tests/unit/libc-wasi-p2/test_wasi_p2_clocks_wrapper.cc @@ -31,7 +31,7 @@ class WasiP2ClocksWrapperTest : public testing::Test bool runtime_init = false; WASMComponentInstance *comp_instance; - libc_wasi_parse_context_t parse_ctx; + libc_wasi_parse_context_t parse_ctx = {}; virtual void SetUp() { printf("Starting setup\n"); @@ -158,4 +158,4 @@ TEST_F(WasiP2ClocksWrapperTest, test_call_wall_clock_resolution) ASSERT_TRUE(loaded_value->value.record_value.fields[0].value); ASSERT_STREQ(loaded_value->value.record_value.fields[1].key, "nanoseconds"); ASSERT_TRUE(loaded_value->value.record_value.fields[1].value); -} \ No newline at end of file +} diff --git a/tests/unit/libc-wasi-p2/test_wasi_p2_filesystem.cc b/tests/unit/libc-wasi-p2/test_wasi_p2_filesystem.cc index 2da9735b77..6ff2839070 100644 --- a/tests/unit/libc-wasi-p2/test_wasi_p2_filesystem.cc +++ b/tests/unit/libc-wasi-p2/test_wasi_p2_filesystem.cc @@ -228,6 +228,7 @@ TEST_F(WasiP2FilesystemTest, Filesystem_ReadDirectoryWithMixedTypes) { WASI_DESCRIPTOR_TYPE_SYMBOLIC_LINK); ASSERT_EQ(found_entries["fifo_file"], WASI_DESCRIPTOR_TYPE_FIFO); + directory_entry_stream_dtor(&stream); close(dir_fd); } @@ -677,6 +678,7 @@ TEST_F(WasiP2FilesystemTest, Filesystem_ReadDirectory) { ASSERT_EQ(err, WASI_ERROR_CODE_SUCCESS); ASSERT_FALSE(is_some); + directory_entry_stream_dtor(&stream); close(dir_fd); } @@ -952,4 +954,4 @@ TEST_F(WasiP2FilesystemTest, Filesystem_OpenAtWithPermissions) { ASSERT_EQ(wasi_filesystem_close(file_fd), WASI_ERROR_CODE_SUCCESS); close(dir_fd); -} \ No newline at end of file +} diff --git a/tests/unit/libc-wasi-p2/test_wasi_p2_filesystem_wrapper.cc b/tests/unit/libc-wasi-p2/test_wasi_p2_filesystem_wrapper.cc index 02e26fdf1a..0293be6812 100644 --- a/tests/unit/libc-wasi-p2/test_wasi_p2_filesystem_wrapper.cc +++ b/tests/unit/libc-wasi-p2/test_wasi_p2_filesystem_wrapper.cc @@ -25,7 +25,7 @@ class WasiP2FilesystemWrapperTest : public testing::Test ~WasiP2FilesystemWrapperTest() {} RuntimeInitArgs init_args; unsigned char *component_raw = NULL; - libc_wasi_parse_context_t parse_ctx; + libc_wasi_parse_context_t parse_ctx = {}; char error_buf[128]; char global_heap_buf[HEAP_SIZE]; // 100 MB @@ -34,7 +34,7 @@ class WasiP2FilesystemWrapperTest : public testing::Test WASMComponentInstance *comp_instance; WASIContext *wasi_ctx; - char test_dir[128]; + char test_dir[PATH_MAX] = WAMR_UNIT_TEST_SOURCE_DIR "/test_dir"; int32_t dir_fd; @@ -88,8 +88,6 @@ class WasiP2FilesystemWrapperTest : public testing::Test strcat(created_link, "/test_file_2.txt"); unlink(created_link); - close(dir_fd); - printf("Ending teardown\n"); } @@ -97,42 +95,16 @@ class WasiP2FilesystemWrapperTest : public testing::Test return load_component_from_candidates_internal(file_name, "libc-wasi-p2"); } - void get_test_dir() { - char cwd[PATH_MAX]; - getcwd(cwd, sizeof(cwd)); - const char *substr = "wasm-micro-runtime"; - char *pos = strstr(cwd, substr); - if (!pos) { - printf("Could not find 'wasm-micro-runtime' in cwd\n"); - return; - } - size_t prefix_len = (pos - cwd) + strlen(substr); - test_dir[0] = '\0'; - strncat(test_dir, cwd, prefix_len); - strcat(test_dir, "/tests/unit/libc-wasi-p2/test_dir"); - } - - void init_prestats() { - - get_test_dir(); - - WASIContext *wasi_ctx = wasm_runtime_get_wasi_ctx((WASMModuleInstanceCommon *)comp_instance->core_module_instances[0]); - wasi_ctx->prestats = (struct fd_prestats *)wasm_runtime_malloc( - sizeof(struct fd_prestats)); - ASSERT_NE(wasi_ctx->prestats, nullptr); - memset(wasi_ctx->prestats, 0, sizeof(struct fd_prestats)); - // one preopen - wasi_ctx->prestats->size = 10; - wasi_ctx->prestats->prestats = (struct fd_prestat *)wasm_runtime_malloc( - 10 * sizeof(struct fd_prestat)); - ASSERT_NE(wasi_ctx->prestats->prestats, nullptr); - memset(wasi_ctx->prestats->prestats, 0, 10 * sizeof(struct fd_prestat)); - dir_fd = open(test_dir, O_RDONLY | O_DIRECTORY); - - ASSERT_TRUE(dir_fd < (int32_t)wasi_ctx->prestats->size ); - wasi_ctx->prestats->prestats[dir_fd].dir = test_dir; - ASSERT_NE(wasi_ctx->prestats->prestats[dir_fd].dir[0], '\0'); - fd_table_insert_existing(wasi_ctx->curfds, dir_fd, dir_fd, false); + void init_prestats() + { + WASIContext *wasi_ctx = wasm_runtime_get_wasi_ctx( + (WASMModuleInstanceCommon *) + comp_instance->core_module_instances[0]); + dir_fd = open(test_dir, O_RDONLY | O_DIRECTORY); + ASSERT_NE(dir_fd, -1) << strerror(errno); + ASSERT_TRUE(fd_prestats_insert(wasi_ctx->prestats, test_dir, dir_fd)); + ASSERT_TRUE( + fd_table_insert_existing(wasi_ctx->curfds, dir_fd, dir_fd, false)); } void test_function_execution(const char *binary_name, const char *func_name) { @@ -707,4 +679,4 @@ TEST_F(WasiP2FilesystemWrapperTest, test_call_fs_error_code) ASSERT_TRUE(load_result); ASSERT_NE(loaded_value, nullptr); ASSERT_TRUE(loaded_value->value.option_value.optional_elem); -} \ No newline at end of file +} diff --git a/tests/unit/libc-wasi-p2/test_wasi_p2_io_wrapper.cc b/tests/unit/libc-wasi-p2/test_wasi_p2_io_wrapper.cc index d146fde606..1c93730f16 100644 --- a/tests/unit/libc-wasi-p2/test_wasi_p2_io_wrapper.cc +++ b/tests/unit/libc-wasi-p2/test_wasi_p2_io_wrapper.cc @@ -25,7 +25,7 @@ class WasiP2IoWrapperTest : public testing::Test ~WasiP2IoWrapperTest() {} RuntimeInitArgs init_args; unsigned char *component_raw = NULL; - libc_wasi_parse_context_t parse_ctx; + libc_wasi_parse_context_t parse_ctx = {}; char error_buf[128]; char global_heap_buf[HEAP_SIZE]; // 100 MB @@ -50,16 +50,9 @@ class WasiP2IoWrapperTest : public testing::Test bool success = instantiate_host_resource_table(); ASSERT_TRUE(success); - char cwd[PATH_MAX]; - getcwd(cwd, sizeof(cwd)); - const char *substr = "wasm-micro-runtime"; - char *pos = strstr(cwd, substr); - ASSERT_TRUE(pos != NULL) << "Could not find 'wasm-micro-runtime' in cwd"; - size_t prefix_len = (pos - cwd) + strlen(substr); - char path[PATH_MAX] = ""; - strncat(path, cwd, prefix_len); - strcat(path, "/tests/unit/libc-wasi-p2/test_dir/test_input.txt"); - + char path[PATH_MAX] = + WAMR_UNIT_TEST_SOURCE_DIR "/test_dir/test_input.txt"; + FILE *input_file = freopen(path, "r", stdin); ASSERT_TRUE(input_file); @@ -348,16 +341,8 @@ TEST_F(WasiP2IoWrapperTest, test_blocking_splice) TEST_F(WasiP2IoWrapperTest, test_to_debug_string) { // redirect stdin to write only file in order to cause input-stream.read to return last-operation-failed and generate an error resource - char cwd[PATH_MAX]; - getcwd(cwd, sizeof(cwd)); - const char *substr = "wasm-micro-runtime"; - char *pos = strstr(cwd, substr); - ASSERT_TRUE(pos != NULL) << "Could not find 'wasm-micro-runtime' in cwd"; - size_t prefix_len = (pos - cwd) + strlen(substr); - char path[PATH_MAX] = ""; - strncat(path, cwd, prefix_len); - strcat(path, "/tests/unit/libc-wasi-p2/test_dir/test_output.txt"); - + char path[PATH_MAX] = WAMR_UNIT_TEST_SOURCE_DIR "/test_dir/test_output.txt"; + FILE *input_file = freopen(path, "w", stdin); ASSERT_TRUE(input_file); ASSERT_TRUE(wasm_component_application_execute_func(comp_instance, (char *)"call-to-debug-string()")); @@ -403,4 +388,4 @@ TEST_F(WasiP2IoWrapperTest, test_poll) ASSERT_TRUE(load_result); ASSERT_NE(loaded_value, nullptr); ASSERT_FALSE(loaded_value->value.result_value.is_err); -} \ No newline at end of file +} diff --git a/tests/unit/libc-wasi-p2/test_wasi_p2_random.cc b/tests/unit/libc-wasi-p2/test_wasi_p2_random.cc index 62f6b5e705..202e1bc56f 100644 --- a/tests/unit/libc-wasi-p2/test_wasi_p2_random.cc +++ b/tests/unit/libc-wasi-p2/test_wasi_p2_random.cc @@ -89,6 +89,7 @@ TEST_F(WasiP2RandomTest, wasi_random_get_random_bytes_different_sizes) { // Test with size 0 wasi_random_get_random_bytes(0, &ret); ASSERT_EQ(ret.buf_len, 0); + ASSERT_EQ(ret.buf, nullptr); // Test with size 1 wasi_random_get_random_bytes(1, &ret); @@ -144,6 +145,7 @@ TEST_F(WasiP2RandomTest, wasi_random_get_insecure_random_bytes_different_sizes) // Test with size 0 wasi_random_get_insecure_random_bytes(0, &ret); ASSERT_EQ(ret.buf_len, 0); + ASSERT_EQ(ret.buf, nullptr); // Test with size 1 wasi_random_get_insecure_random_bytes(1, &ret); diff --git a/tests/unit/libc-wasi-p2/test_wasi_p2_random_wrapper.cc b/tests/unit/libc-wasi-p2/test_wasi_p2_random_wrapper.cc index 3722e1931e..9baf4711ce 100644 --- a/tests/unit/libc-wasi-p2/test_wasi_p2_random_wrapper.cc +++ b/tests/unit/libc-wasi-p2/test_wasi_p2_random_wrapper.cc @@ -32,7 +32,7 @@ class WasiP2RandomWrapperTest : public testing::Test bool runtime_init = false; WASMComponentInstance *comp_instance; - libc_wasi_parse_context_t parse_ctx; + libc_wasi_parse_context_t parse_ctx = {}; virtual void SetUp() { printf("Starting setup\n"); diff --git a/tests/unit/libc-wasi-p2/test_wasi_p2_sockets.cc b/tests/unit/libc-wasi-p2/test_wasi_p2_sockets.cc index bfdfd36d5a..7f0e72b022 100644 --- a/tests/unit/libc-wasi-p2/test_wasi_p2_sockets.cc +++ b/tests/unit/libc-wasi-p2/test_wasi_p2_sockets.cc @@ -170,6 +170,11 @@ TEST_F(WasiP2SocketsTest, Tcp_FullLifecycle) { wasi_sockets_tcp_finish_connect(client_sock, &client_input, &client_output, &err); ASSERT_EQ(err, WASI_ERROR_CODE_SUCCESS); + ASSERT_NE(client_input, client_sock); + ASSERT_NE(client_output, client_sock); + ASSERT_NE(client_input, client_output); + ASSERT_NE(fcntl(client_input, F_GETFD) & FD_CLOEXEC, 0); + ASSERT_NE(fcntl(client_output, F_GETFD) & FD_CLOEXEC, 0); // Poll for accept ASSERT_NE(listen_sock, -1); @@ -182,12 +187,20 @@ TEST_F(WasiP2SocketsTest, Tcp_FullLifecycle) { &server_output, &err); ASSERT_EQ(err, WASI_ERROR_CODE_SUCCESS); ASSERT_NE(server_sock, -1); + ASSERT_NE(server_input, server_sock); + ASSERT_NE(server_output, server_sock); + ASSERT_NE(server_input, server_output); + ASSERT_NE(fcntl(server_input, F_GETFD) & FD_CLOEXEC, 0); + ASSERT_NE(fcntl(server_output, F_GETFD) & FD_CLOEXEC, 0); // Get remote address wasi_ip_socket_address_t remote_addr; wasi_sockets_tcp_remote_address(server_sock, &remote_addr, &err); ASSERT_EQ(err, WASI_ERROR_CODE_SUCCESS); + close(client_sock); + close(server_sock); + // Send/Recv char send_buf[] = "hello"; char recv_buf[16]; @@ -197,8 +210,9 @@ TEST_F(WasiP2SocketsTest, Tcp_FullLifecycle) { ASSERT_EQ(nwritten, sizeof(send_buf)); // Poll for server socket readability - ASSERT_NE(server_sock, -1); - struct pollfd pfd_server = { .fd = server_sock, .events = POLLIN, .revents = 0}; + struct pollfd pfd_server = { .fd = server_input, + .events = POLLIN, + .revents = 0 }; ret = poll(&pfd_server, 1, 1000); ASSERT_GT(ret, 0); @@ -206,12 +220,10 @@ TEST_F(WasiP2SocketsTest, Tcp_FullLifecycle) { ASSERT_EQ(nread, sizeof(send_buf)); ASSERT_STREQ(send_buf, recv_buf); - // Shutdown - err = wasi_sockets_tcp_shutdown(client_sock, WASI_SHUTDOWN_TYPE_BOTH); - ASSERT_EQ(err, WASI_ERROR_CODE_SUCCESS); - - close(client_sock); - close(server_sock); + close(client_input); + close(client_output); + close(server_input); + close(server_output); close(listen_sock); } @@ -886,10 +898,13 @@ TEST_F(WasiP2SocketsTest, Udp_Stream) { ASSERT_NE(output_stream, (wasi_outgoing_datagram_stream_t)-1); ASSERT_NE(input_stream, sock); ASSERT_NE(output_stream, sock); + ASSERT_NE(input_stream, output_stream); + ASSERT_NE(fcntl(input_stream, F_GETFD) & FD_CLOEXEC, 0); + ASSERT_NE(fcntl(output_stream, F_GETFD) & FD_CLOEXEC, 0); + close(sock); close(input_stream); close(output_stream); - close(sock); } // Test: wasi:sockets/tcp.start-bind with an invalid socket @@ -1090,4 +1105,4 @@ TEST_F(WasiP2SocketsTest, Udp_StartBindAddressInUse) { close(sock1); close(sock2); -} \ No newline at end of file +} diff --git a/tests/unit/libc-wasi-p2/test_wasi_p2_sockets_wrapper.cc b/tests/unit/libc-wasi-p2/test_wasi_p2_sockets_wrapper.cc index 04dc7b7f79..0161e07d68 100644 --- a/tests/unit/libc-wasi-p2/test_wasi_p2_sockets_wrapper.cc +++ b/tests/unit/libc-wasi-p2/test_wasi_p2_sockets_wrapper.cc @@ -25,7 +25,7 @@ class WasiP2SocketsWrapperTest : public testing::Test ~WasiP2SocketsWrapperTest() {} RuntimeInitArgs init_args; unsigned char *component_raw = NULL; - libc_wasi_parse_context_t parse_ctx; + libc_wasi_parse_context_t parse_ctx = {}; char error_buf[128]; char global_heap_buf[HEAP_SIZE]; // 100 MB @@ -110,7 +110,8 @@ TEST_F(WasiP2SocketsWrapperTest, test_call_create_udp_socket) { ASSERT_TRUE(wasm_component_application_execute_func(comp_instance, (char *)"call-create-udp-socket()")); - WASMComponentTypeInstance *ret_type = comp_instance->functions[10]->func_type->results->result; + WASMComponentTypeInstance *ret_type = + comp_instance->functions[9]->func_type->results->result; LiftLowerContext cx; cx.canonical_opts = comp_instance->core_functions[54]->canon_options; cx.inst = comp_instance; @@ -446,8 +447,9 @@ TEST_F(WasiP2SocketsWrapperTest, test_call_tcp_finish_bind) TEST_F(WasiP2SocketsWrapperTest, test_call_tcp_start_connect) { ASSERT_TRUE(wasm_component_application_execute_func(comp_instance, (char *)"call-tcp-start-connect()")); - - WASMComponentTypeInstance *ret_type = comp_instance->functions[27]->func_type->results->result; + + WASMComponentTypeInstance *ret_type = + comp_instance->functions[27]->func_type->results->result; LiftLowerContext cx; cx.canonical_opts = comp_instance->core_functions[71]->canon_options; cx.inst = comp_instance; @@ -911,4 +913,3 @@ TEST_F(WasiP2SocketsWrapperTest, test_call_udp_lookup_resolve_next_address) ASSERT_NE(loaded_value, nullptr); ASSERT_FALSE(loaded_value->value.result_value.is_err); } - diff --git a/tests/unit/linear-memory-aot/build_aot.sh b/tests/unit/linear-memory-aot/build_aot.sh index e0a3e66be4..73fc2a7979 100755 --- a/tests/unit/linear-memory-aot/build_aot.sh +++ b/tests/unit/linear-memory-aot/build_aot.sh @@ -14,7 +14,9 @@ file_names=("mem_grow_out_of_bounds_01" "mem_grow_out_of_bounds_02" WORKDIR="$PWD" WAMRC_ROOT_DIR="${WORKDIR}/../../../wamr-compiler" WAMRC="${WAMRC_ROOT_DIR}/build/wamrc" -WAST2WASM="$(command -v wat2wasm)" || { echo "wat2wasm not found"; exit 1; } +# CI installs wabt under /opt/wabt (not on PATH); prefer it, fall back to PATH. +WAST2WASM="/opt/wabt/bin/wat2wasm" +if [ ! -x "$WAST2WASM" ]; then WAST2WASM="$(command -v wat2wasm)"; fi # build wamrc if not exist if [ ! -s "$WAMRC" ]; then diff --git a/tests/unit/relaxed-simd/CMakeLists.txt b/tests/unit/relaxed-simd/CMakeLists.txt new file mode 100644 index 0000000000..7c722b4d87 --- /dev/null +++ b/tests/unit/relaxed-simd/CMakeLists.txt @@ -0,0 +1,42 @@ +# Copyright (C) 2026 Intel Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required(VERSION 3.14) + +project (test-relaxed-simd) + +add_definitions (-DRUN_ON_LINUX) + +add_definitions (-Dattr_container_malloc=malloc) +add_definitions (-Dattr_container_free=free) + +set (WAMR_BUILD_AOT 0) +set (WAMR_BUILD_INTERP 1) +set (WAMR_BUILD_FAST_INTERP 1) +set (WAMR_BUILD_JIT 0) +set (WAMR_BUILD_LIBC_WASI 0) +set (WAMR_BUILD_APP_FRAMEWORK 0) +set (WAMR_BUILD_SIMD 1) +set (WAMR_BUILD_RELAXED_SIMD 1) +set (WAMR_BUILD_BULK_MEMORY 1) +set (WAMR_BUILD_REF_TYPES 1) + +include (../unit_common.cmake) + +include_directories (${CMAKE_CURRENT_SOURCE_DIR}) +include_directories (${IWASM_DIR}/interpreter) + +file (GLOB_RECURSE source_all ${CMAKE_CURRENT_SOURCE_DIR}/*.cc) + +set (UNIT_SOURCE ${source_all}) + +set (unit_test_sources + ${UNIT_SOURCE} + ${WAMR_RUNTIME_LIB_SOURCE} +) + +add_executable (relaxed_simd_test ${unit_test_sources}) + +target_link_libraries (relaxed_simd_test gtest_main) + +gtest_discover_tests(relaxed_simd_test) diff --git a/tests/unit/relaxed-simd/relaxed_simd_test.cc b/tests/unit/relaxed-simd/relaxed_simd_test.cc new file mode 100644 index 0000000000..176c5569fa --- /dev/null +++ b/tests/unit/relaxed-simd/relaxed_simd_test.cc @@ -0,0 +1,644 @@ +/* + * Copyright (C) 2026 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +/* Gtest coverage for the fast-interp relaxed-SIMD opcode lowering + * gated by `WAMR_BUILD_RELAXED_SIMD=1`. Two angles: + * + * 1. Load-time validation — a module containing a relaxed-SIMD + * opcode loads cleanly (the loader's prepare_bytecode SIMD + * switch recognizes 0x100..0x113). Without commit 1 of the + * patch series the loader would reject with + * `"invalid opcode 0xfd 100"`. + * + * 2. Runtime dispatch — calling a function that executes + * `f32x4.relaxed_madd` returns the FMA-rounded result. The + * result encoding (4×i32 bit pattern packed into the low i64 + * of the v128 via `i64x2.extract_lane 0`) is bit-identical + * across aarch64/x86-64 because the inputs are exact under + * both single-rounded (hardware FMA) and double-rounded + * (split mul+add) semantics — every multiplication and + * addition is exactly representable in f32. + */ + +#include "gtest/gtest.h" +#include "wasm_runtime_common.h" +#include "bh_platform.h" + +class RelaxedSimdTest : public testing::Test +{ + protected: + virtual void SetUp() + { + memset(&init_args, 0, sizeof(RuntimeInitArgs)); + init_args.mem_alloc_type = Alloc_With_Pool; + init_args.mem_alloc_option.pool.heap_buf = global_heap_buf; + init_args.mem_alloc_option.pool.heap_size = sizeof(global_heap_buf); + ASSERT_EQ(wasm_runtime_full_init(&init_args), true); + } + + virtual void TearDown() { wasm_runtime_destroy(); } + + public: + char global_heap_buf[512 * 1024]; + RuntimeInitArgs init_args; + char error_buf[256]; +}; + +/* + * Minimal wasm module that exports a single `madd` function: + * + * (module + * (func (export "madd") (result i64) + * v128.const f32x4 1 2 3 4 + * v128.const f32x4 10 20 30 40 + * v128.const f32x4 100 200 300 400 + * f32x4.relaxed_madd ;; opcode 0xfd 0x85 0x02 (= 0x105) + * i64x2.extract_lane 0)) + * + * Bytes below are the raw output of `wasm-tools parse` on that WAT, + * inlined so the test has no wabt / wat-runtime dependency at run. + */ +static const uint8_t MADD_WASM[] = { + 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60, + 0x00, 0x01, 0x7E, 0x03, 0x02, 0x01, 0x00, 0x07, 0x08, 0x01, 0x04, 0x6D, + 0x61, 0x64, 0x64, 0x00, 0x00, 0x0A, 0x40, 0x01, 0x3E, 0x00, 0xFD, 0x0C, + 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, + 0x00, 0x00, 0x80, 0x40, 0xFD, 0x0C, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, + 0xA0, 0x41, 0x00, 0x00, 0xF0, 0x41, 0x00, 0x00, 0x20, 0x42, 0xFD, 0x0C, + 0x00, 0x00, 0xC8, 0x42, 0x00, 0x00, 0x48, 0x43, 0x00, 0x00, 0x96, 0x43, + 0x00, 0x00, 0xC8, 0x43, 0xFD, 0x85, 0x02, 0xFD, 0x1D, 0x00, 0x0B +}; + +TEST_F(RelaxedSimdTest, load_module_with_relaxed_madd) +{ + char err[128] = { 0 }; + /* The runtime API expects a mutable buffer (modifies in + * place during load); copy into a heap buffer first. */ + uint8_t buf[sizeof(MADD_WASM)]; + memcpy(buf, MADD_WASM, sizeof(MADD_WASM)); + + wasm_module_t module = wasm_runtime_load(buf, (uint32_t)sizeof(buf), err, + (uint32_t)sizeof(err)); + ASSERT_NE(module, nullptr) + << "load failed: " << err + << " — make sure WAMR_BUILD_RELAXED_SIMD=1 is set"; + wasm_runtime_unload(module); +} + +TEST_F(RelaxedSimdTest, invoke_relaxed_madd_returns_fma_result) +{ + char err[128] = { 0 }; + uint8_t buf[sizeof(MADD_WASM)]; + memcpy(buf, MADD_WASM, sizeof(MADD_WASM)); + + wasm_module_t module = wasm_runtime_load(buf, (uint32_t)sizeof(buf), err, + (uint32_t)sizeof(err)); + ASSERT_NE(module, nullptr) << "load failed: " << err; + + wasm_module_inst_t inst = wasm_runtime_instantiate( + module, 32768u, 32768u, err, (uint32_t)sizeof(err)); + ASSERT_NE(inst, nullptr) << "instantiate failed: " << err; + + wasm_function_inst_t func = wasm_runtime_lookup_function(inst, "madd"); + ASSERT_NE(func, nullptr) << "export `madd` not found"; + + wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 32768u); + ASSERT_NE(env, nullptr); + + uint32_t argv[2] = { 0, 0 }; + bool ok = wasm_runtime_call_wasm(env, func, 0, argv); + EXPECT_TRUE(ok) << "call_wasm failed: " << wasm_runtime_get_exception(inst); + + /* + * Expected: f32x4.relaxed_madd((1,2,3,4), (10,20,30,40), + * (100,200,300,400)) + * = (1*10+100, 2*20+200, 3*30+300, 4*40+400) + * = (110, 240, 390, 560) + * + * As bit patterns: + * f32(110) = 0x42DC0000 + * f32(240) = 0x43700000 + * f32(390) = 0x43C30000 + * f32(560) = 0x440C0000 + * + * i64x2.extract_lane 0 packs lanes 0,1 of the v128 into the + * low i64: + * high i32 (argv[1]) = lane 1 = 0x43700000 + * low i32 (argv[0]) = lane 0 = 0x42DC0000 + * + * (Both single-rounded FMA hardware and split mul+add + * produce the same bit pattern here — every product and sum + * is exactly representable in f32.) + */ + EXPECT_EQ(argv[0], 0x42DC0000u); + EXPECT_EQ(argv[1], 0x43700000u); + + wasm_runtime_destroy_exec_env(env); + wasm_runtime_deinstantiate(inst); + wasm_runtime_unload(module); +} + +/* + * Regression test for the i16-intermediate truncation bug in + * `i32x4.relaxed_dot_i8x16_i7x16_add_s` flagged by the chatgpt- + * codex-connector code review on PR #3 (commit "fast-interp: + * i32x4.relaxed_dot_i8x16_i7x16_add_s preserve i16 intermediate"). + * + * (module + * (func (export "dot_add_i16_overflow") (result i64) + * v128.const i8x16 -128 -128 -128 -128 -128 -128 -128 -128 + * -128 -128 -128 -128 -128 -128 -128 -128 + * v128.const i8x16 -128 -128 -128 -128 -128 -128 -128 -128 + * -128 -128 -128 -128 -128 -128 -128 -128 + * v128.const i32x4 0 0 0 0 + * i32x4.relaxed_dot_i8x16_i7x16_add_s + * i64x2.extract_lane 0)) + * + * With a = b = 0x80 (i8 = -128) in all 16 bytes and c = 0, the + * spec-allowed result set is {-65536, -1, 65534} per lane (the + * three possible wrap/saturate combinations of the two pair + * sums). The pre-fix direct-sum impl produced 65536 — outside + * that set. The fix preserves the i16 truncation between the + * pair sum and the extadd_pairwise, producing -65536 per lane. + * + * low i64 = (lane1 << 32) | lane0 = 0xffff0000_ffff0000 + */ +static const uint8_t DOT_ADD_OVERFLOW_WASM[] = { + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60, + 0x00, 0x01, 0x7e, 0x03, 0x02, 0x01, 0x00, 0x07, 0x18, 0x01, 0x14, 0x64, + 0x6f, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x31, 0x36, 0x5f, 0x6f, + 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x00, 0x00, 0x0a, 0x40, 0x01, + 0x3e, 0x00, 0xfd, 0x0c, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xfd, 0x0c, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0xfd, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x93, 0x02, 0xfd, + 0x1d, 0x00, 0x0b +}; + +TEST_F(RelaxedSimdTest, dot_add_i16_intermediate_overflow_regression) +{ + char err[128] = { 0 }; + uint8_t buf[sizeof(DOT_ADD_OVERFLOW_WASM)]; + memcpy(buf, DOT_ADD_OVERFLOW_WASM, sizeof(DOT_ADD_OVERFLOW_WASM)); + + wasm_module_t module = wasm_runtime_load(buf, (uint32_t)sizeof(buf), err, + (uint32_t)sizeof(err)); + ASSERT_NE(module, nullptr) << "load failed: " << err; + + wasm_module_inst_t inst = wasm_runtime_instantiate( + module, 32768u, 32768u, err, (uint32_t)sizeof(err)); + ASSERT_NE(inst, nullptr) << "instantiate failed: " << err; + + wasm_function_inst_t func = + wasm_runtime_lookup_function(inst, "dot_add_i16_overflow"); + ASSERT_NE(func, nullptr) << "export `dot_add_i16_overflow` not found"; + + wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 32768u); + ASSERT_NE(env, nullptr); + + uint32_t argv[2] = { 0, 0 }; + bool ok = wasm_runtime_call_wasm(env, func, 0, argv); + EXPECT_TRUE(ok) << "call_wasm failed: " << wasm_runtime_get_exception(inst); + + /* Per-lane result: -65536 = 0xffff0000 (i32). i64x2.extract_lane 0 + * packs lanes 0 and 1, both = 0xffff0000: + * argv[0] (low i32) = 0xffff0000 + * argv[1] (high i32) = 0xffff0000 + * If anyone refactors the impl back to direct-sum, both lanes + * will be 0x00010000 (= 65536) and this test will fail. */ + EXPECT_EQ(argv[0], 0xffff0000u); + EXPECT_EQ(argv[1], 0xffff0000u); + + wasm_runtime_destroy_exec_env(env); + wasm_runtime_deinstantiate(inst); + wasm_runtime_unload(module); +} + +/* + * Pinning test for `i16x8.relaxed_dot_i8x16_i7x16_s` at the same + * i16-intermediate overflow boundary. The current impl correctly + * truncates to i16 via `result.i16x8[lane] = (int16)sum` on + * wasm_interp_fast.c:8103. Same input pattern (a = b = 0x80 + * everywhere); each i16 lane = (int16)32768 = -32768 = 0x8000. + * + * low i64 = four i16 lanes packed little-endian + * = 0x8000_8000_8000_8000 + * + * If a future refactor drops the (int16) cast in the sibling + * op, this test fires before the bug ships. + * + * (module + * (func (export "dot_s_i16_overflow_pin") (result i64) + * v128.const i8x16 -128 ... (16x) + * v128.const i8x16 -128 ... (16x) + * i16x8.relaxed_dot_i8x16_i7x16_s + * i64x2.extract_lane 0)) + */ +static const uint8_t DOT_S_PIN_WASM[] = { + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60, + 0x00, 0x01, 0x7e, 0x03, 0x02, 0x01, 0x00, 0x07, 0x1a, 0x01, 0x16, 0x64, + 0x6f, 0x74, 0x5f, 0x73, 0x5f, 0x69, 0x31, 0x36, 0x5f, 0x6f, 0x76, 0x65, + 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x70, 0x69, 0x6e, 0x00, 0x00, 0x0a, + 0x2e, 0x01, 0x2c, 0x00, 0xfd, 0x0c, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xfd, 0x0c, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0xfd, 0x92, 0x02, 0xfd, 0x1d, 0x00, 0x0b +}; + +TEST_F(RelaxedSimdTest, dot_s_i16_overflow_pin_sibling_op) +{ + char err[128] = { 0 }; + uint8_t buf[sizeof(DOT_S_PIN_WASM)]; + memcpy(buf, DOT_S_PIN_WASM, sizeof(DOT_S_PIN_WASM)); + + wasm_module_t module = wasm_runtime_load(buf, (uint32_t)sizeof(buf), err, + (uint32_t)sizeof(err)); + ASSERT_NE(module, nullptr) << "load failed: " << err; + + wasm_module_inst_t inst = wasm_runtime_instantiate( + module, 32768u, 32768u, err, (uint32_t)sizeof(err)); + ASSERT_NE(inst, nullptr) << "instantiate failed: " << err; + + wasm_function_inst_t func = + wasm_runtime_lookup_function(inst, "dot_s_i16_overflow_pin"); + ASSERT_NE(func, nullptr) << "export `dot_s_i16_overflow_pin` not found"; + + wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 32768u); + ASSERT_NE(env, nullptr); + + uint32_t argv[2] = { 0, 0 }; + bool ok = wasm_runtime_call_wasm(env, func, 0, argv); + EXPECT_TRUE(ok) << "call_wasm failed: " << wasm_runtime_get_exception(inst); + + /* low i64 = four packed i16 lanes, all = (int16)32768 = -32768 + * = 0x8000_8000_8000_8000 + * argv[0] (low i32) = 0x80008000 + * argv[1] (high i32) = 0x80008000 */ + EXPECT_EQ(argv[0], 0x80008000u); + EXPECT_EQ(argv[1], 0x80008000u); + + wasm_runtime_destroy_exec_env(env); + wasm_runtime_deinstantiate(inst); + wasm_runtime_unload(module); +} + +/* + * Spec-allowed-set test for `i16x8.relaxed_q15mulr_s` at the + * INT16_MIN * INT16_MIN overflow boundary. + * + * (module + * (func (export "q15mulr_int16_min_squared") (result i64) + * v128.const i16x8 -32768 0 0 0 0 0 0 0 + * v128.const i16x8 -32768 0 0 0 0 0 0 0 + * i16x8.relaxed_q15mulr_s + * i64x2.extract_lane 0)) + * + * Q15 multiply-with-rounding: lane = sat_s((a*b + 0x4000) >> 15). + * For a = b = INT16_MIN: + * a*b = (-32768)*(-32768) = 0x40000000 + * + 0x4000 = 0x40004000 + * >> 15 = 0x8000 = 32768 (overflows i16) + * sat_s = 32767 = 0x7fff (saturate, IEEE/x86 PMULHRSW) + * wrap = (int16)32768 = 0x8000 (truncate, spec-allowed) + * + * The spec's relaxed clause permits either lowering, so the lane-0 + * value must be 0x7fff OR 0x8000. Lanes 1..7 are 0 (deterministic). + * Encoded as the low i64 (i64x2.extract_lane 0) the spec-allowed + * set is { 0x0000_0000_0000_7fff, 0x0000_0000_0000_8000 }. + * + * WAMR's hand-rolled lowering picks saturate (0x7fff); this test + * pins the choice via membership rather than exact equality, so a + * future switch to wrap (spec-allowed) does not break the test. + */ +static const uint8_t Q15MULR_OVERFLOW_WASM[] = { + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, + 0x60, 0x00, 0x01, 0x7e, 0x03, 0x02, 0x01, 0x00, 0x07, 0x1d, 0x01, + 0x19, 0x71, 0x31, 0x35, 0x6d, 0x75, 0x6c, 0x72, 0x5f, 0x69, 0x6e, + 0x74, 0x31, 0x36, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x71, 0x75, + 0x61, 0x72, 0x65, 0x64, 0x00, 0x00, 0x0a, 0x2e, 0x01, 0x2c, 0x00, + 0xfd, 0x0c, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x0c, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfd, 0x91, 0x02, 0xfd, 0x1d, 0x00, 0x0b +}; + +TEST_F(RelaxedSimdTest, q15mulr_int16_min_squared_either_sat_or_wrap) +{ + char err[128] = { 0 }; + uint8_t buf[sizeof(Q15MULR_OVERFLOW_WASM)]; + memcpy(buf, Q15MULR_OVERFLOW_WASM, sizeof(Q15MULR_OVERFLOW_WASM)); + + wasm_module_t module = wasm_runtime_load(buf, (uint32_t)sizeof(buf), err, + (uint32_t)sizeof(err)); + ASSERT_NE(module, nullptr) << "load failed: " << err; + + wasm_module_inst_t inst = wasm_runtime_instantiate( + module, 32768u, 32768u, err, (uint32_t)sizeof(err)); + ASSERT_NE(inst, nullptr) << "instantiate failed: " << err; + + wasm_function_inst_t func = + wasm_runtime_lookup_function(inst, "q15mulr_int16_min_squared"); + ASSERT_NE(func, nullptr) << "export `q15mulr_int16_min_squared` not found"; + + wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 32768u); + ASSERT_NE(env, nullptr); + + uint32_t argv[2] = { 0, 0 }; + bool ok = wasm_runtime_call_wasm(env, func, 0, argv); + EXPECT_TRUE(ok) << "call_wasm failed: " << wasm_runtime_get_exception(inst); + + /* Lanes 1..3 must be 0 (deterministic). Encoded in argv: lanes + * 1..3 occupy bits 16..63 of the 64-bit packed result. + * argv[0] (low i32) = (lane1 << 16) | lane0 + * argv[1] (high i32) = (lane3 << 16) | lane2 */ + EXPECT_EQ(argv[1], 0u) << "lanes 2,3 must be zero"; + EXPECT_EQ((argv[0] >> 16) & 0xffffu, 0u) << "lane 1 must be zero"; + + /* Lane 0 = low 16 bits of argv[0]: either 0x7fff (sat) or + * 0x8000 (wrap). Both spec-conformant per the relaxed-SIMD + * implementation-defined clause for q15mulr_s. */ + uint32_t lane0 = argv[0] & 0xffffu; + EXPECT_TRUE(lane0 == 0x7fffu || lane0 == 0x8000u) + << "lane 0 = 0x" << std::hex << lane0 + << ", expected 0x7fff (saturate) or 0x8000 (wrap)"; + + wasm_runtime_destroy_exec_env(env); + wasm_runtime_deinstantiate(inst); + wasm_runtime_unload(module); +} + +/* + * Spec-allowed-set test for `f32x4.relaxed_madd` at the + * (Inf * 0 + c) invalid-multiply boundary. + * + * (module + * (func (export "madd_inf_times_zero_lo") (result i64) + * v128.const f32x4 inf inf inf inf + * v128.const f32x4 0 0 0 0 + * v128.const f32x4 1.0 2.0 3.0 4.0 + * f32x4.relaxed_madd + * i64x2.extract_lane 0) + * (func (export "madd_inf_times_zero_hi") (result i64) ;; lane 1) + * + * IEEE 754 §7.2: Inf × 0 is an invalid operation and produces NaN + * (regardless of the subsequent add of `c`). Both fused-multiply- + * add (`fma(Inf, 0, c)`) and unfused (`Inf * 0 + c`) lowerings of + * relaxed_madd produce a NaN here — so the choice between them + * doesn't affect the *kind* of result, only its specific bit + * pattern. The relaxed-SIMD spec leaves the NaN bit pattern + * implementation-defined, so the test checks the IEEE-754 NaN + * predicate (exponent all-ones, fraction non-zero) per lane + * rather than an exact bit pattern. + * + * This case is the relevant adversarial input for "do we + * propagate NaN through the FMA path correctly when one of the + * inputs is +Inf and another is +0?" — exactly the kind of + * boundary the spec test set doesn't explicitly cover. + */ +static const uint8_t MADD_INF_TIMES_ZERO_WASM[] = { + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60, + 0x00, 0x01, 0x7e, 0x03, 0x03, 0x02, 0x00, 0x00, 0x07, 0x33, 0x02, 0x16, + 0x6d, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x66, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x5f, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x6c, 0x6f, 0x00, 0x00, + 0x16, 0x6d, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x66, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x5f, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x68, 0x69, 0x00, + 0x01, 0x0a, 0x7f, 0x02, 0x3e, 0x00, 0xfd, 0x0c, 0x00, 0x00, 0x80, 0x7f, + 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x80, 0x7f, + 0xfd, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x0c, 0x00, 0x00, 0x80, 0x3f, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, + 0xfd, 0x85, 0x02, 0xfd, 0x1d, 0x00, 0x0b, 0x3e, 0x00, 0xfd, 0x0c, 0x00, + 0x00, 0x80, 0x7f, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x80, 0x7f, 0x00, + 0x00, 0x80, 0x7f, 0xfd, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x0c, 0x00, + 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, + 0x00, 0x80, 0x40, 0xfd, 0x85, 0x02, 0xfd, 0x1d, 0x01, 0x0b +}; + +/* Helper: true iff the f32 bit pattern is any NaN + * (exponent = 0xff, fraction != 0). */ +static bool +f32_bits_are_nan(uint32_t bits) +{ + uint32_t exp = (bits >> 23) & 0xff; + uint32_t frac = bits & 0x7fffff; + return exp == 0xff && frac != 0u; +} + +TEST_F(RelaxedSimdTest, madd_inf_times_zero_propagates_nan) +{ + char err[128] = { 0 }; + uint8_t buf[sizeof(MADD_INF_TIMES_ZERO_WASM)]; + memcpy(buf, MADD_INF_TIMES_ZERO_WASM, sizeof(MADD_INF_TIMES_ZERO_WASM)); + + wasm_module_t module = wasm_runtime_load(buf, (uint32_t)sizeof(buf), err, + (uint32_t)sizeof(err)); + ASSERT_NE(module, nullptr) << "load failed: " << err; + + wasm_module_inst_t inst = wasm_runtime_instantiate( + module, 32768u, 32768u, err, (uint32_t)sizeof(err)); + ASSERT_NE(inst, nullptr) << "instantiate failed: " << err; + + wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 32768u); + ASSERT_NE(env, nullptr); + + /* Call the lo half (lanes 0,1) then the hi half (lanes 2,3); + * each call returns one i64 packing two f32 lanes: + * argv[0] = lane2k bits, argv[1] = lane2k+1 bits */ + for (uint32_t half = 0; half < 2; half++) { + const char *name = + half == 0 ? "madd_inf_times_zero_lo" : "madd_inf_times_zero_hi"; + wasm_function_inst_t func = wasm_runtime_lookup_function(inst, name); + ASSERT_NE(func, nullptr) << "export `" << name << "` not found"; + + uint32_t argv[2] = { 0, 0 }; + bool ok = wasm_runtime_call_wasm(env, func, 0, argv); + EXPECT_TRUE(ok) << "call_wasm `" << name + << "` failed: " << wasm_runtime_get_exception(inst); + + EXPECT_TRUE(f32_bits_are_nan(argv[0])) + << name << " lane " << (2 * half) << " not NaN: bits = 0x" + << std::hex << argv[0]; + EXPECT_TRUE(f32_bits_are_nan(argv[1])) + << name << " lane " << (2 * half + 1) << " not NaN: bits = 0x" + << std::hex << argv[1]; + } + + wasm_runtime_destroy_exec_env(env); + wasm_runtime_deinstantiate(inst); + wasm_runtime_unload(module); +} + +/* + * Regression test for `i32x4.relaxed_trunc_f32x4_s` truncating (not + * rounding) for in-range lanes. + * + * (module + * (func (export "trunc_lo") (result i64) + * v128.const f32x4 1.9 -1.9 2.5 -2.5 + * i32x4.relaxed_trunc_f32x4_s + * i64x2.extract_lane 0) + * (func (export "trunc_hi") (result i64) ;; lanes 2,3 + * ... i64x2.extract_lane 1)) + * + * relaxed_trunc must agree with the non-relaxed truncation for + * in-range inputs, i.e. round toward zero: trunc(1.9) = 1, + * trunc(-1.9) = -1, trunc(2.5) = 2, trunc(-2.5) = -2. + * + * SIMDe's simde_wasm_i32x4_relaxed_trunc_f32x4 lowers to SSE2 + * _mm_cvtps_epi32, which ROUNDS to nearest (1.9 -> 2, 2.5 -> 2), + * so before the fix this returned (2, -2, 2, -2) on x86. The fix + * routes the case to the truncating saturating helper, which + * rounds toward zero on every backend. + * + * trunc_lo: argv[0] (lane0) = 1 = 0x00000001 + * argv[1] (lane1) = -1 = 0xffffffff + * trunc_hi: argv[0] (lane2) = 2 = 0x00000002 + * argv[1] (lane3) = -2 = 0xfffffffe + */ +static const uint8_t RELAXED_TRUNC_WASM[] = { + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60, + 0x00, 0x01, 0x7e, 0x03, 0x03, 0x02, 0x00, 0x00, 0x07, 0x17, 0x02, 0x08, + 0x74, 0x72, 0x75, 0x6e, 0x63, 0x5f, 0x6c, 0x6f, 0x00, 0x00, 0x08, 0x74, + 0x72, 0x75, 0x6e, 0x63, 0x5f, 0x68, 0x69, 0x00, 0x01, 0x0a, 0x37, 0x02, + 0x1a, 0x00, 0xfd, 0x0c, 0x33, 0x33, 0xf3, 0x3f, 0x33, 0x33, 0xf3, 0xbf, + 0x00, 0x00, 0x20, 0x40, 0x00, 0x00, 0x20, 0xc0, 0xfd, 0x81, 0x02, 0xfd, + 0x1d, 0x00, 0x0b, 0x1a, 0x00, 0xfd, 0x0c, 0x33, 0x33, 0xf3, 0x3f, 0x33, + 0x33, 0xf3, 0xbf, 0x00, 0x00, 0x20, 0x40, 0x00, 0x00, 0x20, 0xc0, 0xfd, + 0x81, 0x02, 0xfd, 0x1d, 0x01, 0x0b +}; + +TEST_F(RelaxedSimdTest, relaxed_trunc_f32x4_s_truncates_in_range) +{ + char err[128] = { 0 }; + uint8_t buf[sizeof(RELAXED_TRUNC_WASM)]; + memcpy(buf, RELAXED_TRUNC_WASM, sizeof(RELAXED_TRUNC_WASM)); + + wasm_module_t module = wasm_runtime_load(buf, (uint32_t)sizeof(buf), err, + (uint32_t)sizeof(err)); + ASSERT_NE(module, nullptr) << "load failed: " << err; + + wasm_module_inst_t inst = wasm_runtime_instantiate( + module, 32768u, 32768u, err, (uint32_t)sizeof(err)); + ASSERT_NE(inst, nullptr) << "instantiate failed: " << err; + + wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 32768u); + ASSERT_NE(env, nullptr); + + /* trunc_lo returns lanes 0,1; trunc_hi returns lanes 2,3. */ + { + wasm_function_inst_t func = + wasm_runtime_lookup_function(inst, "trunc_lo"); + ASSERT_NE(func, nullptr) << "export `trunc_lo` not found"; + uint32_t argv[2] = { 0, 0 }; + bool ok = wasm_runtime_call_wasm(env, func, 0, argv); + EXPECT_TRUE(ok) << "call_wasm `trunc_lo` failed: " + << wasm_runtime_get_exception(inst); + EXPECT_EQ(argv[0], 0x00000001u) << "trunc(1.9) must be 1, not 2"; + EXPECT_EQ(argv[1], 0xffffffffu) << "trunc(-1.9) must be -1, not -2"; + } + { + wasm_function_inst_t func = + wasm_runtime_lookup_function(inst, "trunc_hi"); + ASSERT_NE(func, nullptr) << "export `trunc_hi` not found"; + uint32_t argv[2] = { 0, 0 }; + bool ok = wasm_runtime_call_wasm(env, func, 0, argv); + EXPECT_TRUE(ok) << "call_wasm `trunc_hi` failed: " + << wasm_runtime_get_exception(inst); + EXPECT_EQ(argv[0], 0x00000002u) << "trunc(2.5) must be 2"; + EXPECT_EQ(argv[1], 0xfffffffeu) << "trunc(-2.5) must be -2"; + } + + wasm_runtime_destroy_exec_env(env); + wasm_runtime_deinstantiate(inst); + wasm_runtime_unload(module); +} + +/* + * Regression test for `i8x16.relaxed_swizzle` zeroing lanes whose + * source index byte has the high bit set (>= 0x80), as required by + * the relaxed-SIMD spec. + * + * a = i8x16 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 + * s = i8x16 0x80 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + * result = relaxed_swizzle(a, s) + * + * Lane 0 has index 0x80 (high bit set), so result lane 0 MUST be 0. + * Lanes 1..15 select a[s[i]] = a[i-1], i.e. 10..24. + * result bytes = 0 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + * + * SIMDe's NEON (vtbl2_s8) and SSSE3 (pshufb) backends already zero + * high-bit lanes, but its v0.8.2 SCALAR fallback computes + * a[s[i] & 15], so 0x80 wrongly returned a[0] = 10. The fix hand- + * emulates the lane loop so all backends zero on the high bit. + * + * swizzle_lo (bytes 0..7): + * argv[0] = 0x0c0b0a00, argv[1] = 0x100f0e0d + * swizzle_hi (bytes 8..15): + * argv[0] = 0x14131211, argv[1] = 0x18171615 + */ +static const uint8_t RELAXED_SWIZZLE_WASM[] = { + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60, + 0x00, 0x01, 0x7e, 0x03, 0x03, 0x02, 0x00, 0x00, 0x07, 0x1b, 0x02, 0x0a, + 0x73, 0x77, 0x69, 0x7a, 0x7a, 0x6c, 0x65, 0x5f, 0x6c, 0x6f, 0x00, 0x00, + 0x0a, 0x73, 0x77, 0x69, 0x7a, 0x7a, 0x6c, 0x65, 0x5f, 0x68, 0x69, 0x00, + 0x01, 0x0a, 0x5b, 0x02, 0x2c, 0x00, 0xfd, 0x0c, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, + 0xfd, 0x0c, 0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xfd, 0x80, 0x02, 0xfd, 0x1d, 0x00, + 0x0b, 0x2c, 0x00, 0xfd, 0x0c, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xfd, 0x0c, 0x80, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0xfd, 0x80, 0x02, 0xfd, 0x1d, 0x01, 0x0b +}; + +TEST_F(RelaxedSimdTest, relaxed_swizzle_zeroes_high_bit_index) +{ + char err[128] = { 0 }; + uint8_t buf[sizeof(RELAXED_SWIZZLE_WASM)]; + memcpy(buf, RELAXED_SWIZZLE_WASM, sizeof(RELAXED_SWIZZLE_WASM)); + + wasm_module_t module = wasm_runtime_load(buf, (uint32_t)sizeof(buf), err, + (uint32_t)sizeof(err)); + ASSERT_NE(module, nullptr) << "load failed: " << err; + + wasm_module_inst_t inst = wasm_runtime_instantiate( + module, 32768u, 32768u, err, (uint32_t)sizeof(err)); + ASSERT_NE(inst, nullptr) << "instantiate failed: " << err; + + wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 32768u); + ASSERT_NE(env, nullptr); + + { + wasm_function_inst_t func = + wasm_runtime_lookup_function(inst, "swizzle_lo"); + ASSERT_NE(func, nullptr) << "export `swizzle_lo` not found"; + uint32_t argv[2] = { 0, 0 }; + bool ok = wasm_runtime_call_wasm(env, func, 0, argv); + EXPECT_TRUE(ok) << "call_wasm `swizzle_lo` failed: " + << wasm_runtime_get_exception(inst); + /* Low byte of argv[0] is result lane 0 (index 0x80 -> 0). */ + EXPECT_EQ(argv[0] & 0xffu, 0u) + << "high-bit index 0x80 must select 0, got " << (argv[0] & 0xffu); + EXPECT_EQ(argv[0], 0x0c0b0a00u); + EXPECT_EQ(argv[1], 0x100f0e0du); + } + { + wasm_function_inst_t func = + wasm_runtime_lookup_function(inst, "swizzle_hi"); + ASSERT_NE(func, nullptr) << "export `swizzle_hi` not found"; + uint32_t argv[2] = { 0, 0 }; + bool ok = wasm_runtime_call_wasm(env, func, 0, argv); + EXPECT_TRUE(ok) << "call_wasm `swizzle_hi` failed: " + << wasm_runtime_get_exception(inst); + EXPECT_EQ(argv[0], 0x14131211u); + EXPECT_EQ(argv[1], 0x18171615u); + } + + wasm_runtime_destroy_exec_env(env); + wasm_runtime_deinstantiate(inst); + wasm_runtime_unload(module); +} diff --git a/tests/unit/runtime-common/build_aot.sh b/tests/unit/runtime-common/build_aot.sh index e8f9f7d7d1..aad2b9fac9 100755 --- a/tests/unit/runtime-common/build_aot.sh +++ b/tests/unit/runtime-common/build_aot.sh @@ -11,7 +11,9 @@ file_names=("main") WORKDIR="$PWD" WAMRC_ROOT_DIR="${WORKDIR}/../../../wamr-compiler" WAMRC="${WAMRC_ROOT_DIR}/build/wamrc" -WAST2WASM="$(command -v wat2wasm)" || { echo "wat2wasm not found"; exit 1; } +# CI installs wabt under /opt/wabt (not on PATH); prefer it, fall back to PATH. +WAST2WASM="/opt/wabt/bin/wat2wasm" +if [ ! -x "$WAST2WASM" ]; then WAST2WASM="$(command -v wat2wasm)"; fi # build wamrc if not exist if [ ! -s "$WAMRC" ]; then diff --git a/tests/unit/runtime-common/wasm_runtime_common_test.cc b/tests/unit/runtime-common/wasm_runtime_common_test.cc index 3e2fb1429a..99eb41790e 100644 --- a/tests/unit/runtime-common/wasm_runtime_common_test.cc +++ b/tests/unit/runtime-common/wasm_runtime_common_test.cc @@ -145,6 +145,11 @@ TEST_F(wasm_runtime_common_test_suite, wasm_runtime_unregister_module) wasm_runtime_unregister_module(nullptr); } +TEST_F(wasm_runtime_common_test_suite, wasm_runtime_unload_null) +{ + wasm_runtime_unload(nullptr); +} + TEST_F(wasm_runtime_common_test_suite, wasm_runtime_find_module_registered) { EXPECT_EQ(nullptr, wasm_runtime_find_module_registered("module_test")); diff --git a/tests/unit/runtime-common/wasm_runtime_module_lifetime_test.cc b/tests/unit/runtime-common/wasm_runtime_module_lifetime_test.cc new file mode 100644 index 0000000000..2dfa3f7628 --- /dev/null +++ b/tests/unit/runtime-common/wasm_runtime_module_lifetime_test.cc @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2026 Bytecode Alliance and contributors. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include + +#include "gtest/gtest.h" +#include "wasm_export.h" + +static const uint8_t dependency_wasm_template[] = { + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, + 0x01, 0x60, 0x00, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, + 0x09, 0x01, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x00, 0x00, + 0x0a, 0x06, 0x01, 0x04, 0x00, 0x41, 0x07, 0x0b, +}; + +static const uint8_t parent_wasm_template[] = { + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60, + 0x00, 0x01, 0x7f, 0x02, 0x14, 0x01, 0x0a, 0x64, 0x65, 0x70, 0x65, 0x6e, + 0x64, 0x65, 0x6e, 0x63, 0x79, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x00, + 0x00, 0x03, 0x02, 0x01, 0x00, 0x07, 0x07, 0x01, 0x03, 0x72, 0x75, 0x6e, + 0x00, 0x01, 0x0a, 0x06, 0x01, 0x04, 0x00, 0x10, 0x00, 0x0b, +}; + +static uint8_t dependency_wasm[sizeof(dependency_wasm_template)]; +static uint8_t parent_wasm[sizeof(parent_wasm_template)]; +static wasm_module_t tracked_parent; +static wasm_module_t tracked_dependency; +static bool tracked_parent_freed; +static bool tracked_dependency_freed; +static uint8_t *reader_buffer; +static bool reader_buffer_destroyed; + +static void * +tracking_malloc(unsigned int size) +{ + return malloc(size); +} + +static void * +tracking_realloc(void *ptr, unsigned int size) +{ + return realloc(ptr, size); +} + +static void +tracking_free(void *ptr) +{ + if (ptr == tracked_parent) { + tracked_parent_freed = true; + } + if (ptr == tracked_dependency) { + tracked_dependency_freed = true; + } + free(ptr); +} + +static bool +module_reader_callback(package_type_t module_type, const char *module_name, + uint8_t **p_buffer, uint32_t *p_size) +{ + if (module_type != Wasm_Module_Bytecode + || strcmp(module_name, "dependency") != 0) { + return false; + } + + reader_buffer = (uint8_t *)malloc(sizeof(dependency_wasm_template)); + if (!reader_buffer) { + return false; + } + memcpy(reader_buffer, dependency_wasm_template, + sizeof(dependency_wasm_template)); + *p_buffer = reader_buffer; + *p_size = (uint32_t)sizeof(dependency_wasm_template); + return true; +} + +static void +module_destroyer_callback(uint8_t *buffer, uint32_t size) +{ + (void)size; + if (buffer == reader_buffer) { + reader_buffer_destroyed = true; + free(reader_buffer); + reader_buffer = nullptr; + } +} + +class wasm_runtime_module_lifetime_test_suite : public testing::Test +{ + protected: + void SetUp() override + { + RuntimeInitArgs init_args = {}; + + memcpy(parent_wasm, parent_wasm_template, sizeof(parent_wasm)); + memcpy(dependency_wasm, dependency_wasm_template, + sizeof(dependency_wasm)); + tracked_parent = nullptr; + tracked_dependency = nullptr; + tracked_parent_freed = false; + tracked_dependency_freed = false; + reader_buffer = nullptr; + reader_buffer_destroyed = false; + + init_args.mem_alloc_type = Alloc_With_Allocator; + init_args.mem_alloc_option.allocator.malloc_func = + (void *)tracking_malloc; + init_args.mem_alloc_option.allocator.realloc_func = + (void *)tracking_realloc; + init_args.mem_alloc_option.allocator.free_func = (void *)tracking_free; + ASSERT_TRUE(wasm_runtime_full_init(&init_args)); + runtime_initialized = true; + wasm_runtime_set_module_reader(module_reader_callback, + module_destroyer_callback); + } + + void TearDown() override + { + if (runtime_initialized) { + wasm_runtime_destroy(); + } + if (reader_buffer) { + free(reader_buffer); + reader_buffer = nullptr; + } + tracked_parent = nullptr; + tracked_dependency = nullptr; + } + + bool runtime_initialized = false; +}; + +TEST_F(wasm_runtime_module_lifetime_test_suite, + unregister_then_unload_parent_before_dependency) +{ + char error_buf[128] = {}; + + tracked_dependency = wasm_runtime_load( + dependency_wasm, sizeof(dependency_wasm), error_buf, sizeof(error_buf)); + ASSERT_NE(tracked_dependency, nullptr); + ASSERT_TRUE(wasm_runtime_register_module("dependency", tracked_dependency, + error_buf, sizeof(error_buf))) + << error_buf; + tracked_parent = wasm_runtime_load(parent_wasm, sizeof(parent_wasm), + error_buf, sizeof(error_buf)); + ASSERT_NE(tracked_parent, nullptr) << error_buf; + ASSERT_TRUE(wasm_runtime_register_module("parent", tracked_parent, + error_buf, sizeof(error_buf))) + << error_buf; + + wasm_runtime_unload(tracked_parent); + wasm_runtime_unload(tracked_dependency); + EXPECT_FALSE(tracked_parent_freed); + EXPECT_FALSE(tracked_dependency_freed); + EXPECT_EQ(wasm_runtime_find_module_registered("parent"), tracked_parent); + EXPECT_EQ(wasm_runtime_find_module_registered("dependency"), + tracked_dependency); + + EXPECT_TRUE(wasm_runtime_unregister_module(tracked_parent)); + wasm_runtime_unload(tracked_parent); + EXPECT_TRUE(tracked_parent_freed); + EXPECT_EQ(wasm_runtime_find_module_registered("parent"), nullptr); + EXPECT_EQ(wasm_runtime_find_module_registered("dependency"), + tracked_dependency); + + EXPECT_TRUE(wasm_runtime_unregister_module(tracked_dependency)); + wasm_runtime_unload(tracked_dependency); + EXPECT_TRUE(tracked_dependency_freed); + EXPECT_EQ(wasm_runtime_find_module_registered("dependency"), nullptr); +} + +TEST_F(wasm_runtime_module_lifetime_test_suite, + reader_owned_dependency_remains_registered_until_runtime_destroy) +{ + char error_buf[128] = {}; + + tracked_parent = wasm_runtime_load(parent_wasm, sizeof(parent_wasm), + error_buf, sizeof(error_buf)); + ASSERT_NE(tracked_parent, nullptr) << error_buf; + tracked_dependency = wasm_runtime_find_module_registered("dependency"); + ASSERT_NE(tracked_dependency, nullptr); + ASSERT_TRUE(wasm_runtime_register_module("parent", tracked_parent, + error_buf, sizeof(error_buf))) + << error_buf; + + EXPECT_FALSE(wasm_runtime_unregister_module(tracked_dependency)); + wasm_runtime_unload(tracked_dependency); + EXPECT_FALSE(tracked_dependency_freed); + EXPECT_FALSE(reader_buffer_destroyed); + EXPECT_EQ(wasm_runtime_find_module_registered("dependency"), + tracked_dependency); + + EXPECT_TRUE(wasm_runtime_unregister_module(tracked_parent)); + wasm_runtime_unload(tracked_parent); + EXPECT_TRUE(tracked_parent_freed); + EXPECT_EQ(wasm_runtime_find_module_registered("dependency"), + tracked_dependency); + + wasm_runtime_destroy(); + runtime_initialized = false; + EXPECT_TRUE(tracked_dependency_freed); + EXPECT_TRUE(reader_buffer_destroyed); +} diff --git a/tests/unit/unsupported-features/CMakeLists.txt b/tests/unit/unsupported-features/CMakeLists.txt index d585e8ff8e..8ceab47c7d 100644 --- a/tests/unit/unsupported-features/CMakeLists.txt +++ b/tests/unit/unsupported-features/CMakeLists.txt @@ -36,7 +36,6 @@ endfunction() # List of unsupported feature tests set(UNSUPPORTED_FEATURE_TESTS "exce_handling_aot -DWAMR_BUILD_EXCE_HANDLING=1 -DWAMR_BUILD_AOT=1" - "exce_handling_fast_interp -DWAMR_BUILD_EXCE_HANDLING=1 -DWAMR_BUILD_INTERP=1 -DWAMR_BUILD_FAST_INTERP=1" "exce_handling_fast_jit -DWAMR_BUILD_EXCE_HANDLING=1 -DWAMR_BUILD_FAST_JIT=1" "exce_handling_llvm_jit -DWAMR_BUILD_EXCE_HANDLING=1 -DWAMR_BUILD_JIT=1" "gc_fast_jit -DWAMR_BUILD_GC=1 -DWAMR_BUILD_FAST_JIT=1" diff --git a/tests/wamr-test-suites/test_wamr.sh b/tests/wamr-test-suites/test_wamr.sh index 84ec1d8ec9..65f3fef504 100755 --- a/tests/wamr-test-suites/test_wamr.sh +++ b/tests/wamr-test-suites/test_wamr.sh @@ -1047,8 +1047,9 @@ function do_execute_in_running_mode() # keep alpha order if [[ ${ENABLE_EH} -eq 1 ]]; then - if [[ "${RUNNING_MODE}" != "classic-interp" ]]; then - echo "support exception handling in classic-interp" + if [[ "${RUNNING_MODE}" != "classic-interp" \ + && "${RUNNING_MODE}" != "fast-interp" ]]; then + echo "support exception handling in classic-interp and fast-interp" return 0; fi fi diff --git a/tests/wamr-test-suites/wasi-test-script/run_wasi_tests.sh b/tests/wamr-test-suites/wasi-test-script/run_wasi_tests.sh index 9ea733c3ff..341e01a535 100755 --- a/tests/wamr-test-suites/wasi-test-script/run_wasi_tests.sh +++ b/tests/wamr-test-suites/wasi-test-script/run_wasi_tests.sh @@ -76,7 +76,7 @@ run_aot_tests () { fi echo "Compiling $test_wasm to $test_aot" - ${WAMRC_CMD} --enable-multi-thread ${target_option} \ + ${WAMRC_CMD} --enable-multi-thread "${target_options[@]}" \ -o ${test_aot} ${test_wasm} echo "Running $test_aot" @@ -140,9 +140,14 @@ if [[ $MODE != "aot" ]];then deactivate else - target_option="" + target_options=() if [[ $TARGET == "X86_32" ]];then - target_option="--target=i386" + target_options=(--target=i386) + elif [[ $TARGET == "X86_64" ]];then + # Keep generated AOT artifacts independent of the hosted runner CPU. + # In particular, znver4 auto-detection enables AVX-512 code paths that + # are not reliable on every runner execution environment. + target_options=(--target=x86_64 --cpu=skylake) fi exit_code=0