diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..093a5dfe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: "\U0001F41E Bug Report" +about: "If something isn't working as expected \U0001F914." +title: '' +labels: 'i: bug, i: needs triage' +assignees: '' + +--- + +**What steps will reproduce the bug?** +(please provide small code example that reproduces the issue when possible) + +1. step 1 +2. step 2 +3. ... + +**What happens?** + +... + +**What did you expect to happen instead?** + +... + +**How did you install ristretto `java`?** + + + +### Information about your environment + +* ristretto version: [REQUIRED] (e.g. output of `java --version` preferred: "java/0.8.0 Mac-OS/15.0.1/arm64") +* Runtime Version: [REQUIRED] (e.g. 21.0.4.7.1) +* Operating system: [REQUIRED] diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..3ba13e0c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..b06cc4cc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: "\U00002728 Feature Request" +about: "I have a suggestion (and may want to implement it \U0001F642)!" +title: '' +labels: 'i: enhancement, i: needs triage' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54d12c8b..1523462e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,8 +28,8 @@ jobs: - macos-arm64 - macos-x64 - windows-x64 - - wasm32-unknown - - wasm32-wasi + #- wasm32-unknown + #- wasm32-wasi include: - platform: linux-x64 @@ -44,12 +44,12 @@ jobs: - platform: windows-x64 os: windows-2022 target: x86_64-pc-windows-msvc - - platform: wasm32-unknown - os: ubuntu-22.04 - target: wasm32-unknown-unknown - - platform: wasm32-wasi - os: ubuntu-22.04 - target: wasm32-wasip1-threads + # - platform: wasm32-unknown + # os: ubuntu-22.04 + # target: wasm32-unknown-unknown + # - platform: wasm32-wasi + # os: ubuntu-22.04 + # target: wasm32-wasip1-threads steps: - name: Checkout source code @@ -58,17 +58,16 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@master with: - components: 'llvm-tools-preview' targets: ${{ matrix.target }} toolchain: stable - - name: Install grcov + - name: Install cargo-llvm-cov uses: taiki-e/install-action@main with: - tool: grcov + tool: cargo-llvm-cov - name: Build - if: ${{ startsWith(matrix.platform, 'wasm32-') }} + if: ${{ startsWith(matrix.platform, 'wasm32-') || startsWith(matrix.platform, 'windows-')}} env: CARGO_TERM_COLOR: always GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} @@ -77,37 +76,33 @@ jobs: cargo build --target ${{ matrix.target }} - name: Tests - if: ${{ !startsWith(matrix.platform, 'wasm32-') }} + if: ${{ !startsWith(matrix.os, 'ubuntu-') && !startsWith(matrix.platform, 'wasm32-') && !startsWith(matrix.platform, 'windows-') }} env: CARGO_TERM_COLOR: always GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - LLVM_PROFILE_FILE: ristretto-%p-%m.profraw + RUST_BACKTRACE: 1 RUST_LOG: info - RUSTFLAGS: -Cinstrument-coverage - RUSTDOCFLAGS: -Cinstrument-coverage + RUST_LOG_SPAN_EVENTS: full run: | cargo test --workspace --all-features - du -h ~/.ristretto - - name: Produce coverage info - if: ${{ startsWith(matrix.platform, 'linux-') }} + - name: Tests + if: ${{ startsWith(matrix.os, 'ubuntu-') }} + env: + CARGO_TERM_COLOR: always + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + RUST_BACKTRACE: 1 + RUST_LOG: info + RUST_LOG_SPAN_EVENTS: full run: | - grcov $(find . -name "ristretto-*.profraw" -print) \ - -s . \ - --branch \ - --ignore-not-existing \ - --ignore='target/*' \ - --ignore='benches/*' \ - --ignore='/*' \ - --binary-path ./target/debug/ \ - --excl-line='#\[derive' \ - -t lcov \ - -o lcov.info + cargo llvm-cov --workspace --lcov --output-path lcov.info - name: Upload to codecov.io - if: ${{ startsWith(matrix.platform, 'linux-') }} + if: ${{ startsWith(matrix.os, 'ubuntu-') }} uses: codecov/codecov-action@v4 with: files: lcov.info fail_ci_if_error: true - token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 12f23f9d..098123ba 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -17,6 +17,8 @@ jobs: fuzz: name: ${{ matrix.target }} runs-on: ubuntu-latest + env: + RUSTUP_TOOLCHAIN: nightly strategy: fail-fast: false matrix: diff --git a/.github/workflows/release-post-announce.yml b/.github/workflows/release-post-announce.yml new file mode 100644 index 00000000..2daa6c6d --- /dev/null +++ b/.github/workflows/release-post-announce.yml @@ -0,0 +1,109 @@ +name: Publish + +on: + # Defining workflow_call means that this workflow can be called from + # your main workflow job + workflow_call: + # cargo-dist exposes the plan from the plan step, as a JSON string, + # to your job if it needs it + inputs: + plan: + required: true + type: string + +jobs: + upload-assets: + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - { target: aarch64-apple-darwin, os: macos-14 } + # - { target: aarch64-linux-android, os: ubuntu-latest } + - { target: aarch64-unknown-linux-gnu, os: ubuntu-latest } + - { target: aarch64-unknown-linux-musl, os: ubuntu-latest } + # - { target: arm-linux-androideabi, os: ubuntu-latest } + - { target: arm-unknown-linux-gnueabi, os: ubuntu-latest } + # - { target: arm-unknown-linux-gnueabihf, os: ubuntu-latest } + # - { target: arm-unknown-linux-musleabi, os: ubuntu-latest } + # - { target: arm-unknown-linux-musleabihf, os: ubuntu-latest } + # - { target: armv5te-unknown-linux-gnueabi, os: ubuntu-latest } + # - { target: armv5te-unknown-linux-musleabi, os: ubuntu-latest } + # - { target: armv7-linux-androideabi, os: ubuntu-latest } + - { target: armv7-unknown-linux-gnueabi, os: ubuntu-latest } + - { target: armv7-unknown-linux-gnueabihf, os: ubuntu-latest } + - { target: armv7-unknown-linux-musleabi, os: ubuntu-latest } + - { target: armv7-unknown-linux-musleabihf, os: ubuntu-latest } + # - { target: i586-unknown-linux-gnu, os: ubuntu-latest } + # - { target: i586-unknown-linux-musl, os: ubuntu-latest } + # - { target: i686-unknown-freebsd, os: ubuntu-latest } + # - { target: i686-linux-android, os: ubuntu-latest } + # - { target: i686-pc-windows-gnu, os: windows-latest } + - { target: i686-unknown-linux-gnu, os: ubuntu-latest } + # - { target: mips-unknown-linux-gnu, os: ubuntu-latest } + # - { target: mips-unknown-linux-musl, os: ubuntu-latest } + # - { target: mips64-unknown-linux-gnuabi64, os: ubuntu-latest } + # - { target: mips64-unknown-linux-muslabi64, os: ubuntu-latest } + # - { target: mips64el-unknown-linux-gnuabi64, os: ubuntu-latest } + # - { target: mips64el-unknown-linux-muslabi64, os: ubuntu-latest } + # - { target: mipsel-unknown-linux-gnu, os: ubuntu-latest } + # - { target: mipsel-unknown-linux-musl, os: ubuntu-latest } + # - { target: powerpc-unknown-linux-gnu, os: ubuntu-latest } + # - { target: powerpc64-unknown-linux-gnu, os: ubuntu-latest } + - { target: powerpc64le-unknown-linux-gnu, os: ubuntu-latest } + # - { target: riscv64gc-unknown-linux-gnu, os: ubuntu-latest } + - { target: s390x-unknown-linux-gnu, os: ubuntu-latest } + # - { target: sparc64-unknown-linux-gnu, os: ubuntu-latest } + # - { target: sparcv9-sun-solaris, os: ubuntu-latest } + # - { target: thumbv6m-none-eabi, os: ubuntu-latest } + # - { target: thumbv7em-none-eabi, os: ubuntu-latest } + # - { target: thumbv7em-none-eabihf, os: ubuntu-latest } + # - { target: thumbv7m-none-eabi, os: ubuntu-latest } + # - { target: thumbv7neon-linux-androideabi, os: ubuntu-latest } + # - { target: thumbv7neon-unknown-linux-gnueabihf, os: ubuntu-latest } + # - { target: thumbv8m.base-none-eabi, os: ubuntu-latest } + # - { target: thumbv8m.main-none-eabi, os: ubuntu-latest } + # - { target: thumbv8m.main-none-eabihf, os: ubuntu-latest } + # - { target: wasm32-unknown-emscripten, os: ubuntu-latest } + - { target: x86_64-apple-darwin, os: macos-12 } + # - { target: x86_64-linux-android, os: ubuntu-latest } + # - { target: x86_64-pc-windows-gnu, os: windows-latest } + # - { target: x86_64-pc-windows-msvc, os: windows-latest } + # - { target: x86_64-sun-solaris, os: ubuntu-latest } + # - { target: x86_64-unknown-freebsd, os: ubuntu-latest } + # - { target: x86_64-unknown-dragonfly, os: ubuntu-latest } + # - { target: x86_64-unknown-illumos, os: ubuntu-latest } + - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest } + - { target: x86_64-unknown-linux-musl, os: ubuntu-latest } + # - { target: x86_64-unknown-netbsd, os: ubuntu-latest } + runs-on: ${{ matrix.os }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Install cross-compilation tools + if: ${{ startsWith(matrix.os, 'ubuntu') }} + uses: taiki-e/setup-cross-toolchain-action@v1 + with: + target: ${{ matrix.target }} + + - uses: taiki-e/upload-rust-binary-action@v1 + with: + bin: java + # (optional) Comma-separated list of algorithms to be used for checksum. + # [default value: ] + # [possible values: sha256, sha512, sha1, or md5] + checksum: sha256 + # (optional) On which platform to distribute the `.tar.gz` file. + # [default value: unix] + # [possible values: all, unix, windows, none] + tar: all + # (optional) On which platform to distribute the `.zip` file. + # [default value: windows] + # [possible values: all, unix, windows, none] + zip: windows + # (optional) Target triple, default is host triple. + target: ${{ matrix.target }} + env: + # (required) + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..629b7db1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,342 @@ +# This file was autogenerated by cargo-dist: https://opensource.axo.dev/cargo-dist/ +# +# Copyright 2022-2024, axodotdev +# SPDX-License-Identifier: MIT or Apache-2.0 +# +# CI that: +# +# * checks for a Git Tag that looks like a release +# * builds artifacts with cargo-dist (archives, installers, hashes) +# * uploads those artifacts to temporary workflow zip +# * on success, uploads the artifacts to a GitHub Release +# +# Note that the GitHub Release will be created with a generated +# title/body based on your changelogs. + +name: Release +permissions: + "contents": "write" + +# This task will run whenever you push a git tag that looks like a version +# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. +# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where +# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION +# must be a Cargo-style SemVer Version (must have at least major.minor.patch). +# +# If PACKAGE_NAME is specified, then the announcement will be for that +# package (erroring out if it doesn't have the given version or isn't cargo-dist-able). +# +# If PACKAGE_NAME isn't specified, then the announcement will be for all +# (cargo-dist-able) packages in the workspace with that version (this mode is +# intended for workspaces with only one dist-able package, or with all dist-able +# packages versioned/released in lockstep). +# +# If you push multiple tags at once, separate instances of this workflow will +# spin up, creating an independent announcement for each one. However, GitHub +# will hard limit this to 3 tags per commit, as it will assume more tags is a +# mistake. +# +# If there's a prerelease-style suffix to the version, then the release(s) +# will be marked as a prerelease. +on: + pull_request: + push: + tags: + - '**[0-9]+.[0-9]+.[0-9]+*' + +jobs: + # Run 'cargo dist plan' (or host) to determine what tasks we need to do + plan: + runs-on: "ubuntu-20.04" + outputs: + val: ${{ steps.plan.outputs.manifest }} + tag: ${{ !github.event.pull_request && github.ref_name || '' }} + tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} + publishing: ${{ !github.event.pull_request }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install cargo-dist + # we specify bash to get pipefail; it guards against the `curl` command + # failing. otherwise `sh` won't catch that `curl` returned non-0 + shell: bash + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.22.1/cargo-dist-installer.sh | sh" + - name: Cache cargo-dist + uses: actions/upload-artifact@v4 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/cargo-dist + # sure would be cool if github gave us proper conditionals... + # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible + # functionality based on whether this is a pull_request, and whether it's from a fork. + # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* + # but also really annoying to build CI around when it needs secrets to work right.) + - id: plan + run: | + cargo dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json + echo "cargo dist ran successfully" + cat plan-dist-manifest.json + echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v4 + with: + name: artifacts-plan-dist-manifest + path: plan-dist-manifest.json + + # Build and packages all the platform-specific things + build-local-artifacts: + name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) + # Let the initial task tell us to not run (currently very blunt) + needs: + - plan + if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} + strategy: + fail-fast: false + # Target platforms/runners are computed by cargo-dist in create-release. + # Each member of the matrix has the following arguments: + # + # - runner: the github runner + # - dist-args: cli flags to pass to cargo dist + # - install-dist: expression to run to install cargo-dist on the runner + # + # Typically there will be: + # - 1 "global" task that builds universal installers + # - N "local" tasks that build each platform's binaries and platform-specific installers + matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} + runs-on: ${{ matrix.runner }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json + steps: + - name: enable windows longpaths + run: | + git config --global core.longpaths true + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: swatinem/rust-cache@v2 + with: + key: ${{ join(matrix.targets, '-') }} + cache-provider: ${{ matrix.cache_provider }} + - name: Install cargo-dist + run: ${{ matrix.install_dist }} + # Get the dist-manifest + - name: Fetch local artifacts + uses: actions/download-artifact@v4 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - name: Install dependencies + run: | + ${{ matrix.packages_install }} + - name: Build artifacts + run: | + # Actually do builds and make zips and whatnot + cargo dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json + echo "cargo dist ran successfully" + - id: cargo-dist + name: Post-build + # We force bash here just because github makes it really hard to get values up + # to "real" actions without writing to env-vars, and writing to env-vars has + # inconsistent syntax between shell and powershell. + shell: bash + run: | + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v4 + with: + name: artifacts-build-local-${{ join(matrix.targets, '_') }} + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + + # Build and package all the platform-agnostic(ish) things + build-global-artifacts: + needs: + - plan + - build-local-artifacts + runs-on: "ubuntu-20.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install cached cargo-dist + uses: actions/download-artifact@v4 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/ + - run: chmod +x ~/.cargo/bin/cargo-dist + # Get all the local artifacts for the global tasks to use (for e.g. checksums) + - name: Fetch local artifacts + uses: actions/download-artifact@v4 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - id: cargo-dist + shell: bash + run: | + cargo dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json + echo "cargo dist ran successfully" + + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v4 + with: + name: artifacts-build-global + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + # Determines if we should publish/announce + host: + needs: + - plan + - build-local-artifacts + - build-global-artifacts + # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) + if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + runs-on: "ubuntu-20.04" + outputs: + val: ${{ steps.host.outputs.manifest }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install cached cargo-dist + uses: actions/download-artifact@v4 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/ + - run: chmod +x ~/.cargo/bin/cargo-dist + # Fetch artifacts from scratch-storage + - name: Fetch artifacts + uses: actions/download-artifact@v4 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - id: host + shell: bash + run: | + cargo dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json + echo "artifacts uploaded and released successfully" + cat dist-manifest.json + echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v4 + with: + # Overwrite the previous copy + name: artifacts-dist-manifest + path: dist-manifest.json + # Create a GitHub Release while uploading all files to it + - name: "Download GitHub Artifacts" + uses: actions/download-artifact@v4 + with: + pattern: artifacts-* + path: artifacts + merge-multiple: true + - name: Cleanup + run: | + # Remove the granular manifests + rm -f artifacts/*-dist-manifest.json + - name: Create GitHub Release + env: + PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" + ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" + ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" + RELEASE_COMMIT: "${{ github.sha }}" + run: | + # Write and read notes from a file to avoid quoting breaking things + echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt + + gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* + + publish-homebrew-formula: + needs: + - plan + - host + runs-on: "ubuntu-20.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PLAN: ${{ needs.plan.outputs.val }} + GITHUB_USER: "axo bot" + GITHUB_EMAIL: "admin+bot@axo.dev" + if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} + steps: + - uses: actions/checkout@v4 + with: + repository: "theseus-rs/homebrew-tap" + token: ${{ secrets.HOMEBREW_TAP_TOKEN }} + # So we have access to the formula + - name: Fetch homebrew formulae + uses: actions/download-artifact@v4 + with: + pattern: artifacts-* + path: Formula/ + merge-multiple: true + # This is extra complex because you can make your Formula name not match your app name + # so we need to find releases with a *.rb file, and publish with that filename. + - name: Commit formula files + run: | + git config --global user.name "${GITHUB_USER}" + git config --global user.email "${GITHUB_EMAIL}" + + for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith(".rb")] | any)'); do + filename=$(echo "$release" | jq '.artifacts[] | select(endswith(".rb"))' --raw-output) + name=$(echo "$filename" | sed "s/\.rb$//") + version=$(echo "$release" | jq .app_version --raw-output) + + export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH" + brew update + # We avoid reformatting user-provided data such as the app description and homepage. + brew style --except-cops FormulaAudit/Homepage,FormulaAudit/Desc,FormulaAuditStrict --fix "Formula/${filename}" || true + + git add "Formula/${filename}" + git commit -m "${name} ${version}" + done + git push + + announce: + needs: + - plan + - host + - publish-homebrew-formula + # use "always() && ..." to allow us to wait for all publish jobs while + # still allowing individual publish jobs to skip themselves (for prereleases). + # "host" however must run to completion, no skipping allowed! + if: ${{ always() && needs.host.result == 'success' && (needs.publish-homebrew-formula.result == 'skipped' || needs.publish-homebrew-formula.result == 'success') }} + runs-on: "ubuntu-20.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + custom-release-post-announce: + needs: + - plan + - announce + uses: ./.github/workflows/release-post-announce.yml + with: + plan: ${{ needs.plan.outputs.val }} + secrets: inherit diff --git a/.gitignore b/.gitignore index ce3496ee..915f4446 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/ +.run/ /fuzz/artifacts/ /fuzz/corpus/ /fuzz/target/ diff --git a/Cargo.lock b/Cargo.lock index bd26aa39..8b421649 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,19 +4,13 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -89,9 +83,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "arbitrary" @@ -110,23 +104,23 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -155,9 +149,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cast" @@ -167,9 +161,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.13" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "shlex", ] @@ -209,21 +203,36 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.16" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -237,7 +246,6 @@ name = "class_loader" version = "0.7.0" dependencies = [ "ristretto_classloader", - "tokio", ] [[package]] @@ -305,6 +313,25 @@ dependencies = [ "itertools", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -328,6 +355,27 @@ dependencies = [ "syn", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -354,27 +402,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "env_filter" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" -dependencies = [ - "log", -] - -[[package]] -name = "env_logger" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "log", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -393,15 +420,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "filetime" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", @@ -411,12 +438,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.32" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -451,41 +478,52 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", + "futures-io", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -501,9 +539,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" @@ -536,9 +574,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -597,9 +641,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "hyper" @@ -623,9 +667,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http", @@ -656,9 +700,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -669,7 +713,6 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] @@ -686,9 +729,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", @@ -702,9 +745,9 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is-terminal" @@ -740,9 +783,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "0cb94a0ffd3f3ee755c20f7d8752f45cac88605a4dcf808abcff72873296ec7b" dependencies = [ "wasm-bindgen", ] @@ -755,9 +798,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libredox" @@ -809,15 +852,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -856,6 +890,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -877,18 +920,18 @@ dependencies = [ [[package]] name = "object" -version = "0.36.3" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" @@ -941,36 +984,33 @@ dependencies = [ ] [[package]] -name = "overload" -version = "0.1.1" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] -name = "percent-encoding" -version = "2.3.1" +name = "os_info" +version = "3.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +dependencies = [ + "log", + "serde", + "windows-sys 0.52.0", +] [[package]] -name = "pin-project" -version = "1.1.5" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] -name = "pin-project-internal" -version = "1.1.5" +name = "percent-encoding" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" @@ -986,28 +1026,48 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "read_class" version = "0.7.0" @@ -1018,23 +1078,34 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -1048,13 +1119,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -1065,19 +1136,20 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2", @@ -1140,9 +1212,7 @@ dependencies = [ "indoc", "reqwest", "tar", - "test-log", "thiserror", - "tokio", "zip", ] @@ -1161,19 +1231,44 @@ dependencies = [ "serde_plain", "tar", "tempfile", - "test-log", "thiserror", - "tokio", "tracing", "zip", ] +[[package]] +name = "ristretto_cli" +version = "0.7.0" +dependencies = [ + "clap", + "os_info", + "ristretto_vm", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ristretto_vm" +version = "0.7.0" +dependencies = [ + "criterion", + "dirs", + "indexmap", + "os_info", + "ristretto_classfile", + "ristretto_classloader", + "sys-locale", + "sysinfo", + "thiserror", + "tracing", + "whoami", +] + [[package]] name = "runtime_class_loader" version = "0.7.0" dependencies = [ "ristretto_classloader", - "tokio", ] [[package]] @@ -1184,9 +1279,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags", "errno", @@ -1197,9 +1292,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "once_cell", "rustls-pki-types", @@ -1210,25 +1305,24 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" -version = "0.102.6" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -1252,11 +1346,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1274,9 +1368,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -1284,18 +1378,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -1304,9 +1398,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -1387,6 +1481,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -1395,9 +1495,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.75" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -1413,6 +1513,29 @@ dependencies = [ "futures-core", ] +[[package]] +name = "sys-locale" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0" +dependencies = [ + "libc", +] + +[[package]] +name = "sysinfo" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ae3f4f7d64646c46c4cae4e3f01d1c5d255c7406fdd7c7f999a94e488791" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows", +] + [[package]] name = "system-configuration" version = "0.6.1" @@ -1436,9 +1559,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +checksum = "4ff6c40d3aedb5e06b57c6f669ad17ab063dd1e63d977c6a88e7f4dfa4f04020" dependencies = [ "filetime", "libc", @@ -1447,9 +1570,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -1458,42 +1581,20 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "test-log" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" -dependencies = [ - "env_logger", - "test-log-macros", - "tracing-subscriber", -] - -[[package]] -name = "test-log-macros" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", @@ -1537,9 +1638,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -1547,21 +1648,9 @@ dependencies = [ "mio", "pin-project-lite", "socket2", - "tokio-macros", "windows-sys 0.52.0", ] -[[package]] -name = "tokio-macros" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -1585,9 +1674,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -1596,27 +1685,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - [[package]] name = "tower-service" version = "0.3.3" @@ -1677,6 +1745,7 @@ dependencies = [ "once_cell", "regex", "sharded-slab", + "smallvec", "thread_local", "tracing", "tracing-core", @@ -1691,21 +1760,21 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -1770,11 +1839,17 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "ef073ced962d62984fb38a36e5fdc1a2b23c9e0e1fa0689bb97afa4202ef6887" dependencies = [ "cfg-if", "once_cell", @@ -1783,9 +1858,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "c4bfab14ef75323f4eb75fa52ee0a3fb59611977fd3240da19b2cf36ff85030e" dependencies = [ "bumpalo", "log", @@ -1798,9 +1873,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "65471f79c1022ffa5291d33520cbbb53b7687b01c2f8e83b57d102eed7ed479d" dependencies = [ "cfg-if", "js-sys", @@ -1810,9 +1885,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "a7bec9830f60924d9ceb3ef99d55c155be8afa76954edffbb5936ff4509474e7" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1820,9 +1895,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "4c74f6e152a76a2ad448e223b0fc0b6b5747649c3d769cc6bf45737bf97d0ed6" dependencies = [ "proc-macro2", "quote", @@ -1833,20 +1908,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "a42f6c679374623f295a8623adfe63d9284091245c3504bde47c17a3ce2777d9" [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "44188d185b5bdcae1052d08bcbcf9091a5524038d4572cc4f4f2bb9d5554ddd9" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall", + "wasite", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1878,15 +1964,68 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-registry" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-strings", - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -1895,7 +2034,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1904,8 +2043,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", - "windows-targets", + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -1914,7 +2062,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1923,7 +2071,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -1932,28 +2095,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1966,24 +2147,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 4736466e..3e29af36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,8 @@ default-members = [ members = [ "examples/*", "ristretto_classfile", - "ristretto_classloader", + "ristretto_classloader", "ristretto_cli", + "ristretto_vm", ] resolver = "2" @@ -20,23 +21,28 @@ repository = "https://github.com/theseus-rs/ristretto" version = "0.7.0" [workspace.dependencies] -anyhow = "1.0.44" +anyhow = "1.0.88" bitflags = "2.6.0" byteorder = "1.5.0" +clap = "4.5.20" criterion = { version = "0.5.1", default-features = false } -flate2 = "1.0.32" +dirs = "5.0.1" +flate2 = "1.0.33" home = "0.5.9" indoc = "2.0.5" -indexmap = "2.4.0" +indexmap = "2.6.0" +os_info = "3.8.2" reqwest = "0.12.7" -serde = "1.0.207" +serde = "1.0.210" serde_plain = "1.0.2" -tar = "0.4.41" -tempfile = "3.11.0" -test-log = { version = "0.2.16", features = ["trace"] } -thiserror = "1.0.63" -tokio = { version = "1.39.3", default-features = false, features = ["macros", "rt", "sync"] } +sysinfo = "0.32.0" +sys-locale = "0.3.1" +tar = "0.4.42" +tempfile = "3.13.0" +thiserror = "1.0.64" tracing = "0.1.40" +tracing-subscriber = "0.3.18" +whoami = "1.5.2" zip = { version = "2.2.0", default-features = false, features = ["deflate"] } [workspace.metadata.release] @@ -44,9 +50,36 @@ shared-version = true dependent-version = "upgrade" tag-name = "v{{version}}" +# Config for 'cargo dist' +[workspace.metadata.dist] +# The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) +cargo-dist-version = "0.22.1" +# CI backends to support +ci = "github" +# The installers to generate for each app +installers = ["shell", "powershell", "homebrew", "msi"] +# Post-announce jobs to run in CI +post-announce-jobs = ["./release-post-announce"] +# A GitHub repo to push Homebrew formulas to +tap = "theseus-rs/homebrew-tap" +# Target platforms to build apps for (Rust target-triple syntax) +targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"] +# Path that installers should place binaries in +install-path = "CARGO_HOME" +# Publish jobs to run in CI +publish-jobs = ["homebrew"] +# Which actions to run on pull requests +pr-run-mode = "upload" +# Whether to install an updater program +install-updater = true + [profile.release] codegen-units = 1 lto = true opt-level = "z" panic = "abort" -strip = true + +# The profile that 'cargo dist' will build with +[profile.dist] +inherits = "release" +lto = "thin" diff --git a/README.md b/README.md index f668b316..4a1b16df 100644 --- a/README.md +++ b/README.md @@ -10,57 +10,50 @@ [![License](https://img.shields.io/crates/l/ristretto_classfile)](https://github.com/theseus-rs/ristretto#license) [![Semantic Versioning](https://img.shields.io/badge/%E2%9A%99%EF%B8%8F_SemVer-2.0.0-blue)](https://semver.org/spec/v2.0.0.html) -Crates for the [JVM Specification](https://docs.oracle.com/javase/specs/jvms/se22/html/) +[JVM](https://docs.oracle.com/javase/specs/jvms/se23/html/) implementation that does not use a garbage collector and +allocates / deallocates memory in a deterministic way. + +**This is a work in progress and is not ready for production use.** ## Getting Started -Crates for the [JVM Specification](https://docs.oracle.com/javase/specs/jvms/se22/html/) +`ristretto` java can be installed using the following methods: -### Features +### Linux / MacOS -* loading classes from any version of [AWS Corretto](https://github.com/corretto) -* load classes from directories, jars, modules -* url classes loading from jars and modules -* reading, writing, verifying classes for any version of Java version up to 24 -* verification of class files is supported, but is still a work in progress. - -# Examples - -## Load a class from the Java runtime - -```rust -use ristretto_classloader::{runtime, ClassLoader, Result}; -use std::sync::Arc; - -#[tokio::main] -async fn main() -> Result<()> { - let (version, class_loader) = runtime::class_loader("21").await?; - let class_name = "java.util.HashMap"; - println!("Loading {class_name} from Java runtime {version}"); - let class = ClassLoader::load_class(&Arc::new(class_loader), class_name).await?; - println!("{class:?}"); - Ok(()) -} +```shell +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/theseus-rs/ristretto/releases/latest/download/ristretto_cli-installer.sh | sh ``` -## Create a class file - -```rust -use ristretto_classfile::{ClassFile, ConstantPool, Result, Version}; - -fn main() -> Result<()> { - let mut constant_pool = ConstantPool::default(); - let this_class = constant_pool.add_class("Foo")?; - let class_file = ClassFile { - version: Version::Java21 { minor: 0 }, - constant_pool, - this_class, - ..Default::default() - }; - class_file.verify() -} +### Windows + +```shell +irm https://github.com/theseus-rs/ristretto/releases/latest/download/ristretto_cli-installer.ps1 | iex ``` +For more information, and additional installations instructions (cargo, homebrew, msi), +visit the [ristretto](https://theseus-rs.github.io/ristretto/ristretto_cli/) site. + +### Features + +- Deterministic memory allocation / deallocation +- No garbage collector +- Loading classes from any version of [AWS Corretto](https://github.com/corretto) +- Load classes from directories, jars, modules +- Url class loading from jars and modules +- Reading, writing, verifying classes for any version of Java version up to 24 +- Verification of class files is supported, but is still a work in progress. + +# Limitations + +- The instructions Athrow, Multianewarray and Invokedynamic are not implemented +- Exceptions are not implemented +- Threading is not implemented +- Numerous JDK native methods are not implemented +- JNI is not implemented +- JIT is not implemented +- No Security manager; see: [JEP 411](https://openjdk.org/jeps/411) + ## Safety These crates use `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% safe Rust. diff --git a/classes/Annotations.class b/classes/Annotations.class index 52c66991..be7b5164 100644 Binary files a/classes/Annotations.class and b/classes/Annotations.class differ diff --git a/classes/Child.class b/classes/Child.class new file mode 100644 index 00000000..0b75fb74 Binary files /dev/null and b/classes/Child.class differ diff --git a/classes/Child.java b/classes/Child.java new file mode 100644 index 00000000..660234eb --- /dev/null +++ b/classes/Child.java @@ -0,0 +1,3 @@ +public class Child extends Parent { + public int three = 300; +} diff --git a/classes/Constants.class b/classes/Constants.class index 6a99bc9d..0bd83183 100644 Binary files a/classes/Constants.class and b/classes/Constants.class differ diff --git a/classes/Constants.java b/classes/Constants.java index 17c9b1da..25753c9b 100644 --- a/classes/Constants.java +++ b/classes/Constants.java @@ -19,4 +19,9 @@ public class Constants { public static final String[] STRING_VALUES = {"14"}; public static final Object OBJECT_VALUE = new Object(); public static final Object[] OBJECT_VALUES = {new Object()}; + // TODO: uncomment the following lines when the multiarray instruction is implemented +// public static final int[][] MULTI_DIMENSIONAL_TWO = new int[1][2]; +// public static final int[][][] MULTI_DIMENSIONAL_THREE = new int[1][2][3]; +// public static final int[][][][] MULTI_DIMENSIONAL_FOUR = new int[1][2][3][4]; +// public static final int[][][][][] MULTI_DIMENSIONAL_FIVE = new int[1][2][3][4][5]; } diff --git a/classes/Expressions.class b/classes/Expressions.class index fd833a6b..4b646a0b 100644 Binary files a/classes/Expressions.class and b/classes/Expressions.class differ diff --git a/classes/Expressions.java b/classes/Expressions.java index 459724c5..018b4260 100644 --- a/classes/Expressions.java +++ b/classes/Expressions.java @@ -40,4 +40,8 @@ public static void main(String[] args) { System.out.println("default"); } } + + public static int add(int a, int b) { + return a + b; + } } diff --git a/classes/GrandParent.class b/classes/GrandParent.class new file mode 100644 index 00000000..7b0773e9 Binary files /dev/null and b/classes/GrandParent.class differ diff --git a/classes/GrandParent.java b/classes/GrandParent.java new file mode 100644 index 00000000..ac401a57 --- /dev/null +++ b/classes/GrandParent.java @@ -0,0 +1,4 @@ +public class GrandParent { + public int zero = 0; + public int one = -100; +} diff --git a/classes/HelloWorld.class b/classes/HelloWorld.class index 43e601fe..3c16eb11 100644 Binary files a/classes/HelloWorld.class and b/classes/HelloWorld.class differ diff --git a/classes/Minimum.class b/classes/Minimum.class index 784b3b27..953607d5 100644 Binary files a/classes/Minimum.class and b/classes/Minimum.class differ diff --git a/classes/Parent.class b/classes/Parent.class new file mode 100644 index 00000000..df87a2c7 Binary files /dev/null and b/classes/Parent.class differ diff --git a/classes/Parent.java b/classes/Parent.java new file mode 100644 index 00000000..5bbc3dbb --- /dev/null +++ b/classes/Parent.java @@ -0,0 +1,4 @@ +public class Parent extends GrandParent { + public int one = 100; + public int two = 200; +} diff --git a/classes/Simple.class b/classes/Simple.class index 86db8ad0..3785166c 100644 Binary files a/classes/Simple.class and b/classes/Simple.class differ diff --git a/classes/Simple.java b/classes/Simple.java index 2955f483..8504f289 100644 --- a/classes/Simple.java +++ b/classes/Simple.java @@ -13,6 +13,12 @@ public class Simple implements SimpleInterface { public static final short SHORT = Short.MAX_VALUE; public static final String STRING = "foo"; + private static int ANSWER; + + static { + ANSWER = 6 * 7; + } + @Deprecated public int publicValue; protected int protectedValue; @@ -27,6 +33,10 @@ public Simple() { this.privateValue = 3; } + public static int getAnswer() { + return ANSWER; + } + @Deprecated public int getPublicValue() { return publicValue; diff --git a/classes/SimpleInterface.class b/classes/SimpleInterface.class index a8d50496..bd1da6a0 100644 Binary files a/classes/SimpleInterface.class and b/classes/SimpleInterface.class differ diff --git a/classes/classes.jar b/classes/classes.jar index 4446d560..e3e5a355 100644 Binary files a/classes/classes.jar and b/classes/classes.jar differ diff --git a/classes/compile.sh b/classes/compile.sh index ec8ed010..ae26a082 100755 --- a/classes/compile.sh +++ b/classes/compile.sh @@ -1,3 +1,3 @@ #!/usr/bin/env sh -javac -source 21 -target 21 *.java +javac -source 8 -target 8 *.java jar --create --verbose --file classes.jar --main-class HelloWorld *.class diff --git a/deny.toml b/deny.toml index 212f2266..994729f9 100644 --- a/deny.toml +++ b/deny.toml @@ -19,6 +19,7 @@ allow = [ "BSD-3-Clause", "BSL-1.0", "MIT", + "MPL-2.0", "Unicode-DFS-2016", "Unlicense", ] diff --git a/examples/class_loader/Cargo.toml b/examples/class_loader/Cargo.toml index d6b0a22c..08cf1ed0 100644 --- a/examples/class_loader/Cargo.toml +++ b/examples/class_loader/Cargo.toml @@ -7,4 +7,3 @@ version.workspace = true [dependencies] ristretto_classloader = { path = "../../ristretto_classloader" } -tokio = { workspace = true, features = ["rt-multi-thread"] } diff --git a/examples/class_loader/src/main.rs b/examples/class_loader/src/main.rs index 36bb77d3..c9920cc2 100644 --- a/examples/class_loader/src/main.rs +++ b/examples/class_loader/src/main.rs @@ -4,11 +4,10 @@ use ristretto_classloader::{ClassLoader, ClassPath, Result}; /// Example that uses a class loader to load a class. -#[tokio::main] -async fn main() -> Result<()> { +fn main() -> Result<()> { let class_path = ClassPath::from("classes"); let class_loader = ClassLoader::new("example", class_path); - let class = class_loader.load("HelloWorld").await?; + let class = class_loader.load("HelloWorld")?; println!("{class:?}"); Ok(()) } diff --git a/examples/runtime_class_loader/Cargo.toml b/examples/runtime_class_loader/Cargo.toml index 6a6126cb..2b141b63 100644 --- a/examples/runtime_class_loader/Cargo.toml +++ b/examples/runtime_class_loader/Cargo.toml @@ -7,4 +7,3 @@ version.workspace = true [dependencies] ristretto_classloader = { path = "../../ristretto_classloader" } -tokio = { workspace = true, features = ["rt-multi-thread"] } diff --git a/examples/runtime_class_loader/src/main.rs b/examples/runtime_class_loader/src/main.rs index 59c24f62..e97bb966 100644 --- a/examples/runtime_class_loader/src/main.rs +++ b/examples/runtime_class_loader/src/main.rs @@ -4,12 +4,11 @@ use ristretto_classloader::{runtime, Result}; /// Example that loads a class from the Java runtime. -#[tokio::main] -async fn main() -> Result<()> { - let (version, class_loader) = runtime::class_loader("21").await?; - let class_name = "java.util.HashMap"; +fn main() -> Result<()> { + let (version, class_loader) = runtime::class_loader("21")?; + let class_name = "java/util/HashMap"; println!("Loading {class_name} from Java runtime {version}"); - let class = class_loader.load(class_name).await?; + let class = class_loader.load(class_name)?; println!("{class:?}"); Ok(()) } diff --git a/examples/write_class/src/main.rs b/examples/write_class/src/main.rs index 6e12ae95..f57d0900 100644 --- a/examples/write_class/src/main.rs +++ b/examples/write_class/src/main.rs @@ -16,7 +16,6 @@ use std::fs; /// } /// } /// ``` -#[allow(clippy::too_many_lines)] fn main() -> Result<()> { let mut constant_pool = ConstantPool::default(); let super_class = constant_pool.add_class("java/lang/Object")?; diff --git a/ristretto_classfile/Cargo.toml b/ristretto_classfile/Cargo.toml index 53601cb7..07964b7b 100644 --- a/ristretto_classfile/Cargo.toml +++ b/ristretto_classfile/Cargo.toml @@ -20,10 +20,8 @@ anyhow = { workspace = true } criterion = { workspace = true } flate2 = { workspace = true } indoc = { workspace = true } -reqwest = { workspace = true } +reqwest = { workspace = true, features = ["blocking"] } tar = { workspace = true } -test-log = { workspace = true } -tokio = { workspace = true } zip = { workspace = true } [[bench]] diff --git a/ristretto_classfile/README.md b/ristretto_classfile/README.md index 02a51340..0d52cf5f 100644 --- a/ristretto_classfile/README.md +++ b/ristretto_classfile/README.md @@ -10,7 +10,7 @@ ## Getting Started -Implementation of the [JVM Class File Format](https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-4.html) that +Implementation of the [JVM Class File Format](https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-4.html) that is used to read, write and verify Java classes. Supports reading and writing class files for any version of Java version up to 24. Verification of class files is diff --git a/ristretto_classfile/src/attributes/annotation.rs b/ristretto_classfile/src/attributes/annotation.rs index b0526a5d..53e23fe7 100644 --- a/ristretto_classfile/src/attributes/annotation.rs +++ b/ristretto_classfile/src/attributes/annotation.rs @@ -6,7 +6,7 @@ use std::io::Cursor; /// Implementation of Annotation. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct Annotation { pub type_index: u16, @@ -65,7 +65,7 @@ mod test { use super::*; use crate::attributes::AnnotationElement; - #[test_log::test] + #[test] fn test_to_string() { let annotation_value_pair = AnnotationValuePair { name_index: 1, @@ -84,7 +84,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let annotation_value_pair = AnnotationValuePair { name_index: 1, diff --git a/ristretto_classfile/src/attributes/annotation_element.rs b/ristretto_classfile/src/attributes/annotation_element.rs index fb469508..f8283d51 100644 --- a/ristretto_classfile/src/attributes/annotation_element.rs +++ b/ristretto_classfile/src/attributes/annotation_element.rs @@ -7,7 +7,7 @@ use std::io::Cursor; /// Implementation of `AnnotationElement`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub enum AnnotationElement { Byte { @@ -233,7 +233,7 @@ mod test { use super::*; use crate::attributes::annotation_value_pair::AnnotationValuePair; - #[test_log::test] + #[test] fn test_invalid_tag() { let mut bytes = Cursor::new(vec![0]); assert_eq!( @@ -253,7 +253,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_byte() -> Result<()> { let element = AnnotationElement::Byte { const_value_index: 42, @@ -264,7 +264,7 @@ mod test { test_element(&element, &expected_bytes, b'B') } - #[test_log::test] + #[test] fn test_char() -> Result<()> { let element = AnnotationElement::Char { const_value_index: 42, @@ -275,7 +275,7 @@ mod test { test_element(&element, &expected_bytes, b'C') } - #[test_log::test] + #[test] fn test_double() -> Result<()> { let element = AnnotationElement::Double { const_value_index: 42, @@ -286,7 +286,7 @@ mod test { test_element(&element, &expected_bytes, b'D') } - #[test_log::test] + #[test] fn test_float() -> Result<()> { let element = AnnotationElement::Float { const_value_index: 42, @@ -297,7 +297,7 @@ mod test { test_element(&element, &expected_bytes, b'F') } - #[test_log::test] + #[test] fn test_int() -> Result<()> { let element = AnnotationElement::Int { const_value_index: 42, @@ -308,7 +308,7 @@ mod test { test_element(&element, &expected_bytes, b'I') } - #[test_log::test] + #[test] fn test_long() -> Result<()> { let element = AnnotationElement::Long { const_value_index: 42, @@ -319,7 +319,7 @@ mod test { test_element(&element, &expected_bytes, b'J') } - #[test_log::test] + #[test] fn test_short() -> Result<()> { let element = AnnotationElement::Short { const_value_index: 42, @@ -330,7 +330,7 @@ mod test { test_element(&element, &expected_bytes, b'S') } - #[test_log::test] + #[test] fn test_boolean() -> Result<()> { let element = AnnotationElement::Boolean { const_value_index: 42, @@ -341,7 +341,7 @@ mod test { test_element(&element, &expected_bytes, b'Z') } - #[test_log::test] + #[test] fn test_string() -> Result<()> { let element = AnnotationElement::String { const_value_index: 42, @@ -352,7 +352,7 @@ mod test { test_element(&element, &expected_bytes, b's') } - #[test_log::test] + #[test] fn test_enum() -> Result<()> { let element = AnnotationElement::Enum { type_name_index: 3, @@ -367,7 +367,7 @@ mod test { test_element(&element, &expected_bytes, b'e') } - #[test_log::test] + #[test] fn test_class() -> Result<()> { let element = AnnotationElement::Class { class_info_index: 42, @@ -378,7 +378,7 @@ mod test { test_element(&element, &expected_bytes, b'c') } - #[test_log::test] + #[test] fn test_annotation() -> Result<()> { let element = AnnotationValuePair { name_index: 1, @@ -397,7 +397,7 @@ mod test { test_element(&element, &expected_bytes, b'@') } - #[test_log::test] + #[test] fn test_array() -> Result<()> { let element = AnnotationValuePair { name_index: 1, diff --git a/ristretto_classfile/src/attributes/annotation_value_pair.rs b/ristretto_classfile/src/attributes/annotation_value_pair.rs index 4d521245..70e64e3a 100644 --- a/ristretto_classfile/src/attributes/annotation_value_pair.rs +++ b/ristretto_classfile/src/attributes/annotation_value_pair.rs @@ -6,7 +6,7 @@ use std::io::Cursor; /// Implementation of an annotation value pair. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct AnnotationValuePair { pub name_index: u16, @@ -46,7 +46,7 @@ impl fmt::Display for AnnotationValuePair { mod test { use super::*; - #[test_log::test] + #[test] fn test_to_string() { let annotation_value_pair = AnnotationValuePair { name_index: 1, @@ -61,7 +61,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let annotation_value_pair = AnnotationValuePair { name_index: 1, diff --git a/ristretto_classfile/src/attributes/array_type.rs b/ristretto_classfile/src/attributes/array_type.rs index 3b0ff371..65492ce7 100644 --- a/ristretto_classfile/src/attributes/array_type.rs +++ b/ristretto_classfile/src/attributes/array_type.rs @@ -86,7 +86,7 @@ mod test { use super::*; use std::io::Read; - #[test_log::test] + #[test] fn test_invalid_code() { let mut bytes = Cursor::new(vec![0]); assert_eq!( @@ -110,49 +110,49 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_boolean() -> Result<()> { assert_eq!("boolean", ArrayType::Boolean.to_string()); test_array_type(&ArrayType::Boolean, 4) } - #[test_log::test] + #[test] fn test_char() -> Result<()> { assert_eq!("char", ArrayType::Char.to_string()); test_array_type(&ArrayType::Char, 5) } - #[test_log::test] + #[test] fn test_float() -> Result<()> { assert_eq!("float", ArrayType::Float.to_string()); test_array_type(&ArrayType::Float, 6) } - #[test_log::test] + #[test] fn test_double() -> Result<()> { assert_eq!("double", ArrayType::Double.to_string()); test_array_type(&ArrayType::Double, 7) } - #[test_log::test] + #[test] fn test_byte() -> Result<()> { assert_eq!("byte", ArrayType::Byte.to_string()); test_array_type(&ArrayType::Byte, 8) } - #[test_log::test] + #[test] fn test_short() -> Result<()> { assert_eq!("short", ArrayType::Short.to_string()); test_array_type(&ArrayType::Short, 9) } - #[test_log::test] + #[test] fn test_int() -> Result<()> { assert_eq!("int", ArrayType::Int.to_string()); test_array_type(&ArrayType::Int, 10) } - #[test_log::test] + #[test] fn test_long() -> Result<()> { assert_eq!("long", ArrayType::Long.to_string()); test_array_type(&ArrayType::Long, 11) diff --git a/ristretto_classfile/src/attributes/attribute.rs b/ristretto_classfile/src/attributes/attribute.rs index 88b582c8..0d677e36 100644 --- a/ristretto_classfile/src/attributes/attribute.rs +++ b/ristretto_classfile/src/attributes/attribute.rs @@ -32,7 +32,7 @@ const VERSION_61_0: Version = Version::Java17 { minor: 0 }; /// Attribute. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub enum Attribute { ConstantValue { @@ -215,7 +215,7 @@ impl Attribute { } /// Check if the Attribute is valid for the given version. - #[allow(clippy::match_same_arms)] + #[expect(clippy::match_same_arms)] #[must_use] pub fn valid_for_version(&self, version: &Version) -> bool { match self { @@ -258,7 +258,7 @@ impl Attribute { /// # Errors /// - If the attribute name index is invalid. /// - If the attribute length is invalid. - #[allow(clippy::too_many_lines)] + #[expect(clippy::too_many_lines)] pub fn from_bytes( constant_pool: &ConstantPool, bytes: &mut Cursor>, @@ -652,8 +652,8 @@ impl Attribute { /// /// # Errors /// If there is an issue serializing an attribute - #[allow(clippy::too_many_lines)] - #[allow(clippy::match_same_arms)] + #[expect(clippy::too_many_lines)] + #[expect(clippy::match_same_arms)] pub fn to_bytes(&self, bytes: &mut Vec) -> Result<()> { let (name_index, info) = match self { Attribute::ConstantValue { @@ -1094,7 +1094,7 @@ mod test { use crate::method_access_flags::MethodAccessFlags; use indoc::indoc; - #[test_log::test] + #[test] fn test_invalid_attribute_name_index_error() { let expected_bytes = [0, 1, 0, 0, 0, 0]; @@ -1142,12 +1142,12 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_constant_value_from_bytes_error() -> Result<()> { test_invalid_attribute_from_bytes_error("ConstantValue") } - #[test_log::test] + #[test] fn test_constant_value() -> Result<()> { let attribute = Attribute::ConstantValue { name_index: 1, @@ -1158,7 +1158,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_code() -> Result<()> { let constant = Attribute::ConstantValue { name_index: 2, @@ -1210,7 +1210,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_stack_map_table() -> Result<()> { let attribute = Attribute::StackMapTable { name_index: 1, @@ -1228,7 +1228,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_50_0) } - #[test_log::test] + #[test] fn test_exceptions() -> Result<()> { let attribute = Attribute::Exceptions { name_index: 1, @@ -1243,7 +1243,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_inner_classes() -> Result<()> { let inner_class = InnerClass { class_info_index: 1, @@ -1264,12 +1264,12 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_enclosing_method_from_bytes_error() -> Result<()> { test_invalid_attribute_from_bytes_error("EnclosingMethod") } - #[test_log::test] + #[test] fn test_enclosing_method() -> Result<()> { let attribute = Attribute::EnclosingMethod { name_index: 1, @@ -1285,12 +1285,12 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_49_0) } - #[test_log::test] + #[test] fn test_synthetic_from_bytes_error() -> Result<()> { test_invalid_attribute_from_bytes_error("Synthetic") } - #[test_log::test] + #[test] fn test_synthetic() -> Result<()> { let attribute = Attribute::Synthetic { name_index: 1 }; let expected_bytes = [0, 1, 0, 0, 0, 0]; @@ -1299,12 +1299,12 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_signature_from_bytes_error() -> Result<()> { test_invalid_attribute_from_bytes_error("Signature") } - #[test_log::test] + #[test] fn test_signature() -> Result<()> { let attribute = Attribute::Signature { name_index: 1, @@ -1319,12 +1319,12 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_49_0) } - #[test_log::test] + #[test] fn test_source_file_from_bytes_error() -> Result<()> { test_invalid_attribute_from_bytes_error("SourceFile") } - #[test_log::test] + #[test] fn test_source_file() -> Result<()> { let attribute = Attribute::SourceFile { name_index: 1, @@ -1339,7 +1339,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_source_debug_extension() -> Result<()> { let attribute = Attribute::SourceDebugExtension { name_index: 1, @@ -1354,7 +1354,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_49_0) } - #[test_log::test] + #[test] fn test_line_number_table() -> Result<()> { let attribute = Attribute::LineNumberTable { name_index: 1, @@ -1373,7 +1373,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_locale_variable_table() -> Result<()> { let variable = LocalVariableTable { start_pc: 1, @@ -1395,7 +1395,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_49_0) } - #[test_log::test] + #[test] fn test_local_variable_type_table() -> Result<()> { let variable_type = LocalVariableTypeTable { start_pc: 1, @@ -1417,12 +1417,12 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_deprecated_from_bytes_error() -> Result<()> { test_invalid_attribute_from_bytes_error("Deprecated") } - #[test_log::test] + #[test] fn test_deprecated() -> Result<()> { let attribute = Attribute::Deprecated { name_index: 1 }; let expected_bytes = [0, 1, 0, 0, 0, 0]; @@ -1431,7 +1431,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_runtime_visible_annotations() -> Result<()> { let attribute = Attribute::RuntimeVisibleAnnotations { name_index: 1, @@ -1454,7 +1454,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_49_0) } - #[test_log::test] + #[test] fn test_runtime_invisible_annotations() -> Result<()> { let attribute = Attribute::RuntimeInvisibleAnnotations { name_index: 1, @@ -1477,7 +1477,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_49_0) } - #[test_log::test] + #[test] fn test_runtime_visible_parameter_annotations() -> Result<()> { let annotation_value_pair = AnnotationValuePair { name_index: 1, @@ -1505,7 +1505,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_49_0) } - #[test_log::test] + #[test] fn test_runtime_invisible_parameter_annotations() -> Result<()> { let annotation_value_pair = AnnotationValuePair { name_index: 1, @@ -1533,7 +1533,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_49_0) } - #[test_log::test] + #[test] fn test_runtime_visible_type_annotations() -> Result<()> { let element = AnnotationValuePair { name_index: 1, @@ -1565,7 +1565,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_52_0) } - #[test_log::test] + #[test] fn test_runtime_invisible_type_annotations() -> Result<()> { let element = AnnotationValuePair { name_index: 1, @@ -1597,7 +1597,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_52_0) } - #[test_log::test] + #[test] fn test_annotation_default() -> Result<()> { let attribute = Attribute::AnnotationDefault { name_index: 1, @@ -1614,7 +1614,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_49_0) } - #[test_log::test] + #[test] fn test_bootstrap_methods() -> Result<()> { let method = BootstrapMethod { bootstrap_method_ref: 3, @@ -1633,7 +1633,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_51_0) } - #[test_log::test] + #[test] fn test_method_parameters() -> Result<()> { let parameter = MethodParameter { name_index: 2, @@ -1652,7 +1652,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_52_0) } - #[test_log::test] + #[test] fn test_module() -> Result<()> { let attribute = Attribute::Module { name_index: 1, @@ -1692,7 +1692,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_53_0) } - #[test_log::test] + #[test] fn test_module_packages() -> Result<()> { let attribute = Attribute::ModulePackages { name_index: 1, @@ -1707,12 +1707,12 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_53_0) } - #[test_log::test] + #[test] fn test_module_main_class_from_bytes_error() -> Result<()> { test_invalid_attribute_from_bytes_error("ModuleMainClass") } - #[test_log::test] + #[test] fn test_module_main_class() -> Result<()> { let attribute = Attribute::ModuleMainClass { name_index: 1, @@ -1727,12 +1727,12 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_53_0) } - #[test_log::test] + #[test] fn test_nest_host_from_bytes_error() -> Result<()> { test_invalid_attribute_from_bytes_error("NestHost") } - #[test_log::test] + #[test] fn test_nest_host() -> Result<()> { let attribute = Attribute::NestHost { name_index: 1, @@ -1747,7 +1747,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_55_0) } - #[test_log::test] + #[test] fn test_nest_members() -> Result<()> { let attribute = Attribute::NestMembers { name_index: 1, @@ -1762,7 +1762,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_55_0) } - #[test_log::test] + #[test] fn test_record() -> Result<()> { let constant = Attribute::ConstantValue { name_index: 1, @@ -1806,7 +1806,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_permitted_subclasses() -> Result<()> { let attribute = Attribute::PermittedSubclasses { name_index: 1, @@ -1821,7 +1821,7 @@ mod test { test_attribute(&attribute, &expected_bytes, &VERSION_61_0) } - #[test_log::test] + #[test] fn test_unknown() -> Result<()> { let attribute = Attribute::Unknown { name_index: 1, diff --git a/ristretto_classfile/src/attributes/bootstrap_method.rs b/ristretto_classfile/src/attributes/bootstrap_method.rs index e555d630..5cf6a95b 100644 --- a/ristretto_classfile/src/attributes/bootstrap_method.rs +++ b/ristretto_classfile/src/attributes/bootstrap_method.rs @@ -5,7 +5,7 @@ use std::io::Cursor; /// Implementation of `BootstrapMethod`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct BootstrapMethod { pub bootstrap_method_ref: u16, @@ -61,7 +61,7 @@ impl fmt::Display for BootstrapMethod { mod test { use super::*; - #[test_log::test] + #[test] fn test_to_string() { let bootstrap_method = BootstrapMethod { bootstrap_method_ref: 3, @@ -73,7 +73,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let bootstrap_method = BootstrapMethod { bootstrap_method_ref: 3, diff --git a/ristretto_classfile/src/attributes/code_exception.rs b/ristretto_classfile/src/attributes/code_exception.rs index b99e1c99..fca0af78 100644 --- a/ristretto_classfile/src/attributes/code_exception.rs +++ b/ristretto_classfile/src/attributes/code_exception.rs @@ -5,7 +5,7 @@ use std::io::Cursor; /// Implementation of `CodeException`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct CodeException { pub start_pc: u16, @@ -60,7 +60,7 @@ impl fmt::Display for CodeException { mod test { use super::*; - #[test_log::test] + #[test] fn test_to_string() { let code_exception = CodeException { start_pc: 1, @@ -74,7 +74,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let bootstrap_method = CodeException { start_pc: 1, diff --git a/ristretto_classfile/src/attributes/exports.rs b/ristretto_classfile/src/attributes/exports.rs index d415d48e..f0f7e4e9 100644 --- a/ristretto_classfile/src/attributes/exports.rs +++ b/ristretto_classfile/src/attributes/exports.rs @@ -6,7 +6,7 @@ use std::io::Cursor; /// Implementation of `Exports`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct Exports { pub index: u16, @@ -66,7 +66,7 @@ impl fmt::Display for Exports { mod test { use super::*; - #[test_log::test] + #[test] fn test_to_string() { let exports = Exports { index: 1, @@ -79,7 +79,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let exports = Exports { index: 1, diff --git a/ristretto_classfile/src/attributes/exports_flags.rs b/ristretto_classfile/src/attributes/exports_flags.rs index 7b69359f..2cd6bc35 100644 --- a/ristretto_classfile/src/attributes/exports_flags.rs +++ b/ristretto_classfile/src/attributes/exports_flags.rs @@ -7,7 +7,7 @@ use std::io::Cursor; bitflags! { /// Exports flags. /// - /// See: + /// See: #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ExportsFlags: u16 { /// Indicates that this export was not explicitly or implicitly declared in the source of @@ -63,12 +63,12 @@ impl fmt::Display for ExportsFlags { mod test { use super::*; - #[test_log::test] + #[test] fn test_default() { assert_eq!(ExportsFlags::empty(), ExportsFlags::default()); } - #[test_log::test] + #[test] fn test_all_access_flags() { let access_flags: u16 = u16::MAX; let mut bytes = Cursor::new(access_flags.to_be_bytes().to_vec()); @@ -78,7 +78,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_access_flags() -> Result<()> { let access_flags = ExportsFlags::MANDATED; let mut bytes = Vec::new(); @@ -88,7 +88,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_to_string() { assert_eq!( "(0x1000) ACC_SYNTHETIC", diff --git a/ristretto_classfile/src/attributes/inner_class.rs b/ristretto_classfile/src/attributes/inner_class.rs index 24610b40..1fe9aa49 100644 --- a/ristretto_classfile/src/attributes/inner_class.rs +++ b/ristretto_classfile/src/attributes/inner_class.rs @@ -6,7 +6,7 @@ use std::io::Cursor; /// Implementation of `InnerClass`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct InnerClass { pub class_info_index: u16, @@ -61,7 +61,7 @@ impl fmt::Display for InnerClass { mod test { use super::*; - #[test_log::test] + #[test] fn test_to_string() { let inner_class = InnerClass { class_info_index: 1, @@ -75,7 +75,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let inner_class = InnerClass { class_info_index: 1, diff --git a/ristretto_classfile/src/attributes/instruction.rs b/ristretto_classfile/src/attributes/instruction.rs index 9396ff55..826010c3 100644 --- a/ristretto_classfile/src/attributes/instruction.rs +++ b/ristretto_classfile/src/attributes/instruction.rs @@ -1,6 +1,7 @@ use crate::attributes::ArrayType; use crate::error::Error::InvalidInstruction; use crate::error::Result; +use crate::ConstantPool; use crate::Error::InvalidWideInstruction; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use indexmap::IndexMap; @@ -9,8 +10,8 @@ use std::io::Cursor; /// Implementation of `Instruction`. /// -/// See: -#[allow(non_camel_case_types)] +/// See: +#[expect(non_camel_case_types)] #[derive(Clone, Debug, PartialEq)] pub enum Instruction { Nop, @@ -225,7 +226,7 @@ pub enum Instruction { Jsr_w(i32), // Breakpoint, Impdep1 and Impdep2 instructions are reserved for debugging and implementation // dependent operations. - // See: https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-6.html#jvms-6.2 + // See: https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-6.html#jvms-6.2 Breakpoint, Impdep1, Impdep2, @@ -248,8 +249,8 @@ pub enum Instruction { impl Instruction { /// Return the code for the instruction element. - #[allow(clippy::match_same_arms)] - #[allow(clippy::too_many_lines)] + #[expect(clippy::match_same_arms)] + #[expect(clippy::too_many_lines)] #[must_use] pub fn code(&self) -> u8 { match self { @@ -478,7 +479,7 @@ impl Instruction { /// /// # Errors /// Returns an error if the instruction is invalid. - #[allow(clippy::too_many_lines)] + #[expect(clippy::too_many_lines)] pub fn from_bytes(bytes: &mut Cursor>) -> Result { let current_position = i32::try_from(bytes.position())?; let code = bytes.read_u8()?; @@ -784,8 +785,7 @@ impl Instruction { /// /// # Errors /// If an instruction cannot be serialized to bytes. - #[allow(clippy::match_same_arms)] - #[allow(clippy::too_many_lines)] + #[expect(clippy::too_many_lines)] pub fn to_bytes(&self, bytes: &mut Cursor>) -> Result<()> { bytes.write_u8(self.code())?; @@ -957,10 +957,42 @@ impl Instruction { bytes.write_i16::(offset)?; Ok(()) } + + /// Get a formatted string representation of the instruction. + /// + /// # Errors + /// Returns an error if the constant pool index is invalid. + pub fn to_formatted_string(&self, constant_pool: &ConstantPool) -> Result { + let value = match self { + Instruction::Ldc(index) => { + let index = u16::from(*index); + let detail = constant_pool.try_get_formatted_string(index)?; + format!("{self} // {detail}") + } + Instruction::Ldc_w(index) + | Instruction::Ldc2_w(index) + | Instruction::Getstatic(index) + | Instruction::Putstatic(index) + | Instruction::Getfield(index) + | Instruction::Putfield(index) + | Instruction::Invokevirtual(index) + | Instruction::Invokespecial(index) + | Instruction::Invokestatic(index) + | Instruction::Invokeinterface(index, _) + | Instruction::Invokedynamic(index) + | Instruction::Checkcast(index) + | Instruction::Instanceof(index) => { + let detail = constant_pool.try_get_formatted_string(*index)?; + format!("{self} // {detail}") + } + _ => self.to_string(), + }; + Ok(value) + } } impl fmt::Display for Instruction { - #[allow(clippy::too_many_lines)] + #[expect(clippy::too_many_lines)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Instruction::Nop => write!(f, "nop"), @@ -1218,7 +1250,7 @@ mod test { use indoc::indoc; use std::io::Read; - #[test_log::test] + #[test] fn test_invalid_instructions() -> Result<()> { for code in 203..253 { let mut bytes = Vec::new(); @@ -1246,7 +1278,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_nop() -> Result<()> { let instruction = Instruction::Nop; let code = 0; @@ -1256,7 +1288,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_aconst_null() -> Result<()> { let instruction = Instruction::Aconst_null; let code = 1; @@ -1266,7 +1298,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iconst_m1() -> Result<()> { let instruction = Instruction::Iconst_m1; let code = 2; @@ -1276,7 +1308,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iconst_0() -> Result<()> { let instruction = Instruction::Iconst_0; let code = 3; @@ -1286,7 +1318,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iconst_1() -> Result<()> { let instruction = Instruction::Iconst_1; let code = 4; @@ -1296,7 +1328,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iconst_2() -> Result<()> { let instruction = Instruction::Iconst_2; let code = 5; @@ -1306,7 +1338,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iconst_3() -> Result<()> { let instruction = Instruction::Iconst_3; let code = 6; @@ -1316,7 +1348,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iconst_4() -> Result<()> { let instruction = Instruction::Iconst_4; let code = 7; @@ -1326,7 +1358,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iconst_5() -> Result<()> { let instruction = Instruction::Iconst_5; let code = 8; @@ -1336,7 +1368,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lconst_0() -> Result<()> { let instruction = Instruction::Lconst_0; let code = 9; @@ -1346,7 +1378,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lconst_1() -> Result<()> { let instruction = Instruction::Lconst_1; let code = 10; @@ -1356,7 +1388,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fconst_0() -> Result<()> { let instruction = Instruction::Fconst_0; let code = 11; @@ -1366,7 +1398,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fconst_1() -> Result<()> { let instruction = Instruction::Fconst_1; let code = 12; @@ -1376,7 +1408,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fconst_2() -> Result<()> { let instruction = Instruction::Fconst_2; let code = 13; @@ -1386,7 +1418,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dconst_0() -> Result<()> { let instruction = Instruction::Dconst_0; let code = 14; @@ -1396,7 +1428,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dconst_1() -> Result<()> { let instruction = Instruction::Dconst_1; let code = 15; @@ -1406,7 +1438,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_bipush() -> Result<()> { let instruction = Instruction::Bipush(42); let code = 16; @@ -1416,7 +1448,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_sipush() -> Result<()> { let instruction = Instruction::Sipush(42); let code = 17; @@ -1426,7 +1458,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ldc() -> Result<()> { let instruction = Instruction::Ldc(42); let code = 18; @@ -1436,7 +1468,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ldc_w() -> Result<()> { let instruction = Instruction::Ldc_w(42); let code = 19; @@ -1446,7 +1478,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ldc2_w() -> Result<()> { let instruction = Instruction::Ldc2_w(42); let code = 20; @@ -1456,7 +1488,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iload() -> Result<()> { let instruction = Instruction::Iload(42); let code = 21; @@ -1466,7 +1498,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lload() -> Result<()> { let instruction = Instruction::Lload(42); let code = 22; @@ -1476,7 +1508,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fload() -> Result<()> { let instruction = Instruction::Fload(42); let code = 23; @@ -1486,7 +1518,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dload() -> Result<()> { let instruction = Instruction::Dload(42); let code = 24; @@ -1496,7 +1528,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_aload() -> Result<()> { let instruction = Instruction::Aload(42); let code = 25; @@ -1506,7 +1538,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iload_0() -> Result<()> { let instruction = Instruction::Iload_0; let code = 26; @@ -1516,7 +1548,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iload_1() -> Result<()> { let instruction = Instruction::Iload_1; let code = 27; @@ -1526,7 +1558,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iload_2() -> Result<()> { let instruction = Instruction::Iload_2; let code = 28; @@ -1536,7 +1568,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iload_3() -> Result<()> { let instruction = Instruction::Iload_3; let code = 29; @@ -1546,7 +1578,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lload_0() -> Result<()> { let instruction = Instruction::Lload_0; let code = 30; @@ -1556,7 +1588,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lload_1() -> Result<()> { let instruction = Instruction::Lload_1; let code = 31; @@ -1566,7 +1598,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lload_2() -> Result<()> { let instruction = Instruction::Lload_2; let code = 32; @@ -1576,7 +1608,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lload_3() -> Result<()> { let instruction = Instruction::Lload_3; let code = 33; @@ -1586,7 +1618,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fload_0() -> Result<()> { let instruction = Instruction::Fload_0; let code = 34; @@ -1596,7 +1628,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fload_1() -> Result<()> { let instruction = Instruction::Fload_1; let code = 35; @@ -1606,7 +1638,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fload_2() -> Result<()> { let instruction = Instruction::Fload_2; let code = 36; @@ -1616,7 +1648,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fload_3() -> Result<()> { let instruction = Instruction::Fload_3; let code = 37; @@ -1626,7 +1658,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dload_0() -> Result<()> { let instruction = Instruction::Dload_0; let code = 38; @@ -1636,7 +1668,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dload_1() -> Result<()> { let instruction = Instruction::Dload_1; let code = 39; @@ -1646,7 +1678,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dload_2() -> Result<()> { let instruction = Instruction::Dload_2; let code = 40; @@ -1656,7 +1688,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dload_3() -> Result<()> { let instruction = Instruction::Dload_3; let code = 41; @@ -1666,7 +1698,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_aload_0() -> Result<()> { let instruction = Instruction::Aload_0; let code = 42; @@ -1676,7 +1708,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_aload_1() -> Result<()> { let instruction = Instruction::Aload_1; let code = 43; @@ -1686,7 +1718,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_aload_2() -> Result<()> { let instruction = Instruction::Aload_2; let code = 44; @@ -1696,7 +1728,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_aload_3() -> Result<()> { let instruction = Instruction::Aload_3; let code = 45; @@ -1706,7 +1738,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iaload() -> Result<()> { let instruction = Instruction::Iaload; let code = 46; @@ -1716,7 +1748,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_laload() -> Result<()> { let instruction = Instruction::Laload; let code = 47; @@ -1726,7 +1758,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_faload() -> Result<()> { let instruction = Instruction::Faload; let code = 48; @@ -1736,7 +1768,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_daload() -> Result<()> { let instruction = Instruction::Daload; let code = 49; @@ -1746,7 +1778,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_aaload() -> Result<()> { let instruction = Instruction::Aaload; let code = 50; @@ -1756,7 +1788,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_baload() -> Result<()> { let instruction = Instruction::Baload; let code = 51; @@ -1766,7 +1798,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_caload() -> Result<()> { let instruction = Instruction::Caload; let code = 52; @@ -1776,7 +1808,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_saload() -> Result<()> { let instruction = Instruction::Saload; let code = 53; @@ -1786,7 +1818,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_istore() -> Result<()> { let instruction = Instruction::Istore(42); let code = 54; @@ -1796,7 +1828,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lstore() -> Result<()> { let instruction = Instruction::Lstore(42); let code = 55; @@ -1806,7 +1838,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fstore() -> Result<()> { let instruction = Instruction::Fstore(42); let code = 56; @@ -1816,7 +1848,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dstore() -> Result<()> { let instruction = Instruction::Dstore(42); let code = 57; @@ -1826,7 +1858,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_astore() -> Result<()> { let instruction = Instruction::Astore(42); let code = 58; @@ -1836,7 +1868,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_istore_0() -> Result<()> { let instruction = Instruction::Istore_0; let code = 59; @@ -1846,7 +1878,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_istore_1() -> Result<()> { let instruction = Instruction::Istore_1; let code = 60; @@ -1856,7 +1888,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_istore_2() -> Result<()> { let instruction = Instruction::Istore_2; let code = 61; @@ -1866,7 +1898,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_istore_3() -> Result<()> { let instruction = Instruction::Istore_3; let code = 62; @@ -1876,7 +1908,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lstore_0() -> Result<()> { let instruction = Instruction::Lstore_0; let code = 63; @@ -1886,7 +1918,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lstore_1() -> Result<()> { let instruction = Instruction::Lstore_1; let code = 64; @@ -1896,7 +1928,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lstore_2() -> Result<()> { let instruction = Instruction::Lstore_2; let code = 65; @@ -1906,7 +1938,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lstore_3() -> Result<()> { let instruction = Instruction::Lstore_3; let code = 66; @@ -1916,7 +1948,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fstore_0() -> Result<()> { let instruction = Instruction::Fstore_0; let code = 67; @@ -1926,7 +1958,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fstore_1() -> Result<()> { let instruction = Instruction::Fstore_1; let code = 68; @@ -1936,7 +1968,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fstore_2() -> Result<()> { let instruction = Instruction::Fstore_2; let code = 69; @@ -1946,7 +1978,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fstore_3() -> Result<()> { let instruction = Instruction::Fstore_3; let code = 70; @@ -1956,7 +1988,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dstore_0() -> Result<()> { let instruction = Instruction::Dstore_0; let code = 71; @@ -1966,7 +1998,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dstore_1() -> Result<()> { let instruction = Instruction::Dstore_1; let code = 72; @@ -1976,7 +2008,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dstore_2() -> Result<()> { let instruction = Instruction::Dstore_2; let code = 73; @@ -1986,7 +2018,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dstore_3() -> Result<()> { let instruction = Instruction::Dstore_3; let code = 74; @@ -1996,7 +2028,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_astore_0() -> Result<()> { let instruction = Instruction::Astore_0; let code = 75; @@ -2006,7 +2038,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_astore_1() -> Result<()> { let instruction = Instruction::Astore_1; let code = 76; @@ -2016,7 +2048,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_astore_2() -> Result<()> { let instruction = Instruction::Astore_2; let code = 77; @@ -2026,7 +2058,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_astore_3() -> Result<()> { let instruction = Instruction::Astore_3; let code = 78; @@ -2036,7 +2068,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iastore() -> Result<()> { let instruction = Instruction::Iastore; let code = 79; @@ -2046,7 +2078,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lastore() -> Result<()> { let instruction = Instruction::Lastore; let code = 80; @@ -2056,7 +2088,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fastore() -> Result<()> { let instruction = Instruction::Fastore; let code = 81; @@ -2066,7 +2098,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dastore() -> Result<()> { let instruction = Instruction::Dastore; let code = 82; @@ -2076,7 +2108,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_aastore() -> Result<()> { let instruction = Instruction::Aastore; let code = 83; @@ -2086,7 +2118,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_bastore() -> Result<()> { let instruction = Instruction::Bastore; let code = 84; @@ -2096,7 +2128,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_castore() -> Result<()> { let instruction = Instruction::Castore; let code = 85; @@ -2106,7 +2138,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_sastore() -> Result<()> { let instruction = Instruction::Sastore; let code = 86; @@ -2116,7 +2148,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_pop() -> Result<()> { let instruction = Instruction::Pop; let code = 87; @@ -2126,7 +2158,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_pop2() -> Result<()> { let instruction = Instruction::Pop2; let code = 88; @@ -2136,7 +2168,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dup() -> Result<()> { let instruction = Instruction::Dup; let code = 89; @@ -2146,7 +2178,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dup_x1() -> Result<()> { let instruction = Instruction::Dup_x1; let code = 90; @@ -2156,7 +2188,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dup_x2() -> Result<()> { let instruction = Instruction::Dup_x2; let code = 91; @@ -2166,7 +2198,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dup2() -> Result<()> { let instruction = Instruction::Dup2; let code = 92; @@ -2176,7 +2208,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dup2_x1() -> Result<()> { let instruction = Instruction::Dup2_x1; let code = 93; @@ -2186,7 +2218,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dup2_x2() -> Result<()> { let instruction = Instruction::Dup2_x2; let code = 94; @@ -2196,7 +2228,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_swap() -> Result<()> { let instruction = Instruction::Swap; let code = 95; @@ -2206,7 +2238,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iadd() -> Result<()> { let instruction = Instruction::Iadd; let code = 96; @@ -2216,7 +2248,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ladd() -> Result<()> { let instruction = Instruction::Ladd; let code = 97; @@ -2226,7 +2258,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fadd() -> Result<()> { let instruction = Instruction::Fadd; let code = 98; @@ -2236,7 +2268,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dadd() -> Result<()> { let instruction = Instruction::Dadd; let code = 99; @@ -2246,7 +2278,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_isub() -> Result<()> { let instruction = Instruction::Isub; let code = 100; @@ -2256,7 +2288,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lsub() -> Result<()> { let instruction = Instruction::Lsub; let code = 101; @@ -2266,7 +2298,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fsub() -> Result<()> { let instruction = Instruction::Fsub; let code = 102; @@ -2276,7 +2308,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dsub() -> Result<()> { let instruction = Instruction::Dsub; let code = 103; @@ -2286,7 +2318,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_imul() -> Result<()> { let instruction = Instruction::Imul; let code = 104; @@ -2296,7 +2328,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lmul() -> Result<()> { let instruction = Instruction::Lmul; let code = 105; @@ -2306,7 +2338,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fmul() -> Result<()> { let instruction = Instruction::Fmul; let code = 106; @@ -2316,7 +2348,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dmul() -> Result<()> { let instruction = Instruction::Dmul; let code = 107; @@ -2326,7 +2358,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_idiv() -> Result<()> { let instruction = Instruction::Idiv; let code = 108; @@ -2336,7 +2368,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ldiv() -> Result<()> { let instruction = Instruction::Ldiv; let code = 109; @@ -2346,7 +2378,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fdiv() -> Result<()> { let instruction = Instruction::Fdiv; let code = 110; @@ -2356,7 +2388,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ddiv() -> Result<()> { let instruction = Instruction::Ddiv; let code = 111; @@ -2366,7 +2398,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_irem() -> Result<()> { let instruction = Instruction::Irem; let code = 112; @@ -2376,7 +2408,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lrem() -> Result<()> { let instruction = Instruction::Lrem; let code = 113; @@ -2386,7 +2418,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_frem() -> Result<()> { let instruction = Instruction::Frem; let code = 114; @@ -2396,7 +2428,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_drem() -> Result<()> { let instruction = Instruction::Drem; let code = 115; @@ -2406,7 +2438,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ineg() -> Result<()> { let instruction = Instruction::Ineg; let code = 116; @@ -2416,7 +2448,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lneg() -> Result<()> { let instruction = Instruction::Lneg; let code = 117; @@ -2426,7 +2458,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fneg() -> Result<()> { let instruction = Instruction::Fneg; let code = 118; @@ -2436,7 +2468,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dneg() -> Result<()> { let instruction = Instruction::Dneg; let code = 119; @@ -2446,7 +2478,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ishl() -> Result<()> { let instruction = Instruction::Ishl; let code = 120; @@ -2456,7 +2488,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lshl() -> Result<()> { let instruction = Instruction::Lshl; let code = 121; @@ -2466,7 +2498,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ishr() -> Result<()> { let instruction = Instruction::Ishr; let code = 122; @@ -2476,7 +2508,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lshr() -> Result<()> { let instruction = Instruction::Lshr; let code = 123; @@ -2486,7 +2518,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iushr() -> Result<()> { let instruction = Instruction::Iushr; let code = 124; @@ -2496,7 +2528,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lushr() -> Result<()> { let instruction = Instruction::Lushr; let code = 125; @@ -2506,7 +2538,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iand() -> Result<()> { let instruction = Instruction::Iand; let code = 126; @@ -2516,7 +2548,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_land() -> Result<()> { let instruction = Instruction::Land; let code = 127; @@ -2526,7 +2558,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ior() -> Result<()> { let instruction = Instruction::Ior; let code = 128; @@ -2536,7 +2568,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lor() -> Result<()> { let instruction = Instruction::Lor; let code = 129; @@ -2546,7 +2578,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ixor() -> Result<()> { let instruction = Instruction::Ixor; let code = 130; @@ -2556,7 +2588,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lxor() -> Result<()> { let instruction = Instruction::Lxor; let code = 131; @@ -2566,7 +2598,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iinc() -> Result<()> { let instruction = Instruction::Iinc(42, 3); let code = 132; @@ -2576,7 +2608,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_i2l() -> Result<()> { let instruction = Instruction::I2l; let code = 133; @@ -2586,7 +2618,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_i2f() -> Result<()> { let instruction = Instruction::I2f; let code = 134; @@ -2596,7 +2628,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_i2d() -> Result<()> { let instruction = Instruction::I2d; let code = 135; @@ -2606,7 +2638,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_l2i() -> Result<()> { let instruction = Instruction::L2i; let code = 136; @@ -2616,7 +2648,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_l2f() -> Result<()> { let instruction = Instruction::L2f; let code = 137; @@ -2626,7 +2658,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_l2d() -> Result<()> { let instruction = Instruction::L2d; let code = 138; @@ -2636,7 +2668,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_f2i() -> Result<()> { let instruction = Instruction::F2i; let code = 139; @@ -2646,7 +2678,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_f2l() -> Result<()> { let instruction = Instruction::F2l; let code = 140; @@ -2656,7 +2688,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_f2d() -> Result<()> { let instruction = Instruction::F2d; let code = 141; @@ -2666,7 +2698,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_d2i() -> Result<()> { let instruction = Instruction::D2i; let code = 142; @@ -2676,7 +2708,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_d2l() -> Result<()> { let instruction = Instruction::D2l; let code = 143; @@ -2686,7 +2718,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_d2f() -> Result<()> { let instruction = Instruction::D2f; let code = 144; @@ -2696,7 +2728,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_i2b() -> Result<()> { let instruction = Instruction::I2b; let code = 145; @@ -2706,7 +2738,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_i2c() -> Result<()> { let instruction = Instruction::I2c; let code = 146; @@ -2716,7 +2748,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_i2s() -> Result<()> { let instruction = Instruction::I2s; let code = 147; @@ -2726,7 +2758,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lcmp() -> Result<()> { let instruction = Instruction::Lcmp; let code = 148; @@ -2736,7 +2768,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fcmpl() -> Result<()> { let instruction = Instruction::Fcmpl; let code = 149; @@ -2746,7 +2778,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_fcmpg() -> Result<()> { let instruction = Instruction::Fcmpg; let code = 150; @@ -2756,7 +2788,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dcmpl() -> Result<()> { let instruction = Instruction::Dcmpl; let code = 151; @@ -2766,7 +2798,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dcmpg() -> Result<()> { let instruction = Instruction::Dcmpg; let code = 152; @@ -2776,7 +2808,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ifeq() -> Result<()> { let instruction = Instruction::Ifeq(42); let code = 153; @@ -2786,7 +2818,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ifne() -> Result<()> { let instruction = Instruction::Ifne(42); let code = 154; @@ -2796,7 +2828,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_iflt() -> Result<()> { let instruction = Instruction::Iflt(42); let code = 155; @@ -2806,7 +2838,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ifge() -> Result<()> { let instruction = Instruction::Ifge(42); let code = 156; @@ -2816,7 +2848,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ifgt() -> Result<()> { let instruction = Instruction::Ifgt(42); let code = 157; @@ -2826,7 +2858,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ifle() -> Result<()> { let instruction = Instruction::Ifle(42); let code = 158; @@ -2836,7 +2868,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_if_icmpeq() -> Result<()> { let instruction = Instruction::If_icmpeq(42); let code = 159; @@ -2846,7 +2878,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_if_icmpne() -> Result<()> { let instruction = Instruction::If_icmpne(42); let code = 160; @@ -2856,7 +2888,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_if_icmplt() -> Result<()> { let instruction = Instruction::If_icmplt(42); let code = 161; @@ -2866,7 +2898,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_if_icmpge() -> Result<()> { let instruction = Instruction::If_icmpge(42); let code = 162; @@ -2876,7 +2908,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_if_icmpgt() -> Result<()> { let instruction = Instruction::If_icmpgt(42); let code = 163; @@ -2886,7 +2918,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_if_icmple() -> Result<()> { let instruction = Instruction::If_icmple(42); let code = 164; @@ -2896,7 +2928,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_if_acmpeq() -> Result<()> { let instruction = Instruction::If_acmpeq(42); let code = 165; @@ -2906,7 +2938,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_if_acmpne() -> Result<()> { let instruction = Instruction::If_acmpne(42); let code = 166; @@ -2916,7 +2948,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_goto() -> Result<()> { let instruction = Instruction::Goto(42); let code = 167; @@ -2926,7 +2958,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_jsr() -> Result<()> { let instruction = Instruction::Jsr(42); let code = 168; @@ -2936,7 +2968,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ret() -> Result<()> { let instruction = Instruction::Ret(42); let code = 169; @@ -2946,7 +2978,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_tableswitch() -> Result<()> { let instruction = Instruction::Tableswitch { default: 42, @@ -2969,7 +3001,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lookupswitch() -> Result<()> { let instruction = Instruction::Lookupswitch { default: 42, @@ -2989,7 +3021,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ireturn() -> Result<()> { let instruction = Instruction::Ireturn; let code = 172; @@ -2999,7 +3031,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_lreturn() -> Result<()> { let instruction = Instruction::Lreturn; let code = 173; @@ -3009,7 +3041,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_freturn() -> Result<()> { let instruction = Instruction::Freturn; let code = 174; @@ -3019,7 +3051,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_dreturn() -> Result<()> { let instruction = Instruction::Dreturn; let code = 175; @@ -3029,7 +3061,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_areturn() -> Result<()> { let instruction = Instruction::Areturn; let code = 176; @@ -3039,7 +3071,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_return() -> Result<()> { let instruction = Instruction::Return; let code = 177; @@ -3049,7 +3081,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_getstatic() -> Result<()> { let instruction = Instruction::Getstatic(42); let code = 178; @@ -3059,7 +3091,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_putstatic() -> Result<()> { let instruction = Instruction::Putstatic(42); let code = 179; @@ -3069,7 +3101,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_getfield() -> Result<()> { let instruction = Instruction::Getfield(42); let code = 180; @@ -3079,7 +3111,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_putfield() -> Result<()> { let instruction = Instruction::Putfield(42); let code = 181; @@ -3089,7 +3121,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_invokevirtual() -> Result<()> { let instruction = Instruction::Invokevirtual(42); let code = 182; @@ -3099,7 +3131,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_invokespecial() -> Result<()> { let instruction = Instruction::Invokespecial(42); let code = 183; @@ -3109,7 +3141,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_invokestatic() -> Result<()> { let instruction = Instruction::Invokestatic(42); let code = 184; @@ -3119,7 +3151,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_invokeinterface() -> Result<()> { let instruction = Instruction::Invokeinterface(42, 3); let code = 185; @@ -3129,7 +3161,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_invokeinterface_error() { let bytes: [u8; 5] = [185, 0, 42, 3, 1]; let mut cursor = Cursor::new(bytes.to_vec()); @@ -3139,7 +3171,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_invokedynamic() -> Result<()> { let instruction = Instruction::Invokedynamic(42); let code = 186; @@ -3149,7 +3181,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_new() -> Result<()> { let instruction = Instruction::New(42); let code = 187; @@ -3159,7 +3191,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_newarray() -> Result<()> { let instruction = Instruction::Newarray(ArrayType::Boolean); let code = 188; @@ -3169,7 +3201,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_anewarray() -> Result<()> { let instruction = Instruction::Anewarray(42); let code = 189; @@ -3179,7 +3211,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_arraylength() -> Result<()> { let instruction = Instruction::Arraylength; let code = 190; @@ -3189,7 +3221,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_athrow() -> Result<()> { let instruction = Instruction::Athrow; let code = 191; @@ -3199,7 +3231,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_checkcast() -> Result<()> { let instruction = Instruction::Checkcast(42); let code = 192; @@ -3209,7 +3241,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_instanceof() -> Result<()> { let instruction = Instruction::Instanceof(42); let code = 193; @@ -3219,7 +3251,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_monitorenter() -> Result<()> { let instruction = Instruction::Monitorenter; let code = 194; @@ -3229,7 +3261,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_monitorexit() -> Result<()> { let instruction = Instruction::Monitorexit; let code = 195; @@ -3239,13 +3271,13 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_wide() { assert_eq!(196, Instruction::Wide.code()); assert_eq!("wide", Instruction::Wide.to_string()); } - #[test_log::test] + #[test] fn test_multianewarray() -> Result<()> { let instruction = Instruction::Multianewarray(42, 3); let code = 197; @@ -3255,7 +3287,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ifnull() -> Result<()> { let instruction = Instruction::Ifnull(42); let code = 198; @@ -3265,7 +3297,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_ifnonnull() -> Result<()> { let instruction = Instruction::Ifnonnull(42); let code = 199; @@ -3275,7 +3307,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_goto_w() -> Result<()> { let instruction = Instruction::Goto_w(42); let code = 200; @@ -3285,7 +3317,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_jsr_w() -> Result<()> { let instruction = Instruction::Jsr_w(42); let code = 201; @@ -3295,7 +3327,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_breakpoint() -> Result<()> { let instruction = Instruction::Breakpoint; let code = 202; @@ -3305,7 +3337,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_impdep1() -> Result<()> { let instruction = Instruction::Impdep1; let code = 254; @@ -3315,7 +3347,7 @@ mod test { test_instruction(&instruction, &expected_bytes, code) } - #[test_log::test] + #[test] fn test_impdep2() -> Result<()> { let instruction = Instruction::Impdep2; let code = 255; @@ -3327,7 +3359,7 @@ mod test { // Wide instructions - #[test_log::test] + #[test] fn test_iload_w() -> Result<()> { let instruction = Instruction::Iload_w(42); let wide_code = 196; @@ -3338,7 +3370,7 @@ mod test { test_instruction(&instruction, &expected_bytes, wide_code) } - #[test_log::test] + #[test] fn test_lload_w() -> Result<()> { let instruction = Instruction::Lload_w(42); let wide_code = 196; @@ -3349,7 +3381,7 @@ mod test { test_instruction(&instruction, &expected_bytes, wide_code) } - #[test_log::test] + #[test] fn test_fload_w() -> Result<()> { let instruction = Instruction::Fload_w(42); let wide_code = 196; @@ -3360,7 +3392,7 @@ mod test { test_instruction(&instruction, &expected_bytes, wide_code) } - #[test_log::test] + #[test] fn test_dload_w() -> Result<()> { let instruction = Instruction::Dload_w(42); let wide_code = 196; @@ -3371,7 +3403,7 @@ mod test { test_instruction(&instruction, &expected_bytes, wide_code) } - #[test_log::test] + #[test] fn test_aload_w() -> Result<()> { let instruction = Instruction::Aload_w(42); let wide_code = 196; @@ -3382,7 +3414,7 @@ mod test { test_instruction(&instruction, &expected_bytes, wide_code) } - #[test_log::test] + #[test] fn test_istore_w() -> Result<()> { let instruction = Instruction::Istore_w(42); let wide_code = 196; @@ -3393,7 +3425,7 @@ mod test { test_instruction(&instruction, &expected_bytes, wide_code) } - #[test_log::test] + #[test] fn test_lstore_w() -> Result<()> { let instruction = Instruction::Lstore_w(42); let wide_code = 196; @@ -3404,7 +3436,7 @@ mod test { test_instruction(&instruction, &expected_bytes, wide_code) } - #[test_log::test] + #[test] fn test_fstore_w() -> Result<()> { let instruction = Instruction::Fstore_w(42); let wide_code = 196; @@ -3415,7 +3447,7 @@ mod test { test_instruction(&instruction, &expected_bytes, wide_code) } - #[test_log::test] + #[test] fn test_dstore_w() -> Result<()> { let instruction = Instruction::Dstore_w(42); let wide_code = 196; @@ -3426,7 +3458,7 @@ mod test { test_instruction(&instruction, &expected_bytes, wide_code) } - #[test_log::test] + #[test] fn test_astore_w() -> Result<()> { let instruction = Instruction::Astore_w(42); let wide_code = 196; @@ -3437,7 +3469,7 @@ mod test { test_instruction(&instruction, &expected_bytes, wide_code) } - #[test_log::test] + #[test] fn test_iinc_w() -> Result<()> { let instruction = Instruction::Iinc_w(42, 3); let wide_code = 196; @@ -3448,7 +3480,7 @@ mod test { test_instruction(&instruction, &expected_bytes, wide_code) } - #[test_log::test] + #[test] fn test_ret_w() -> Result<()> { let instruction = Instruction::Ret_w(42); let wide_code = 196; @@ -3459,7 +3491,7 @@ mod test { test_instruction(&instruction, &expected_bytes, wide_code) } - #[test_log::test] + #[test] fn test_wide_error() { let bytes: [u8; 4] = [196, 0, 1, 2]; let mut cursor = Cursor::new(bytes.to_vec()); diff --git a/ristretto_classfile/src/attributes/instruction_utils.rs b/ristretto_classfile/src/attributes/instruction_utils.rs index 6b233694..42025c3d 100644 --- a/ristretto_classfile/src/attributes/instruction_utils.rs +++ b/ristretto_classfile/src/attributes/instruction_utils.rs @@ -8,7 +8,6 @@ use std::io::Cursor; /// idiomatic way to represent the instructions, but the JVM uses a byte representation. This /// function converts the instruction enums to a byte representation and adjusts offsets where /// necessary. -#[allow(clippy::too_many_lines)] pub(crate) fn to_bytes(instructions: &[Instruction]) -> Result> { let mut bytes = Cursor::new(Vec::new()); let mut instruction_to_byte_map = HashMap::new(); @@ -48,7 +47,7 @@ pub(crate) fn to_bytes(instructions: &[Instruction]) -> Result> { Instruction::Goto_w(ref mut offset) | Instruction::Jsr_w(ref mut offset) => { // Note the map may need to be updated to use 32-bit offsets if/when the JVM spec // is updated to support 32-bit offsets for goto_w and jsr_w. - // See: https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-6.html#jvms-6.5.goto_w + // See: https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-6.html#jvms-6.5.goto_w let map_offset = u16::try_from(*offset)?; *offset = i32::from( *instruction_to_byte_map @@ -116,7 +115,6 @@ pub(crate) fn to_bytes(instructions: &[Instruction]) -> Result> { /// Converts a vector of bytes to a vector of instructions. Using the instruction enum is a more /// idiomatic way to represent the instructions, but the JVM uses a byte representation. This /// function converts bytes to instruction enums and adjusts offsets where necessary. -#[allow(clippy::too_many_lines)] pub(crate) fn from_bytes(bytes: &mut Cursor>) -> Result> { let mut instructions = Vec::new(); let mut byte_to_instruction_map = HashMap::new(); @@ -157,7 +155,7 @@ pub(crate) fn from_bytes(bytes: &mut Cursor>) -> Result Instruction::Goto_w(ref mut offset) | Instruction::Jsr_w(ref mut offset) => { // Note the map may need to be updated to use 32-bit offsets if/when the JVM spec // is updated to support 32-bit offsets for goto_w and jsr_w. - // See: https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-6.html#jvms-6.5.goto_w + // See: https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-6.html#jvms-6.5.goto_w let map_offset = u16::try_from(*offset)?; *offset = i32::from( *byte_to_instruction_map @@ -225,7 +223,7 @@ mod tests { use super::*; use indexmap::IndexMap; - #[test_log::test] + #[test] fn test_to_bytes() -> Result<()> { let instructions = vec![ Instruction::Iconst_0, @@ -244,14 +242,14 @@ mod tests { Ok(()) } - #[test_log::test] + #[test] fn test_to_bytes_invalid() { let instructions = vec![Instruction::Iflt(42)]; let result = to_bytes(&instructions); assert!(matches!(result, Err(InvalidInstructionOffset(_)))); } - #[test_log::test] + #[test] fn test_to_bytes_invalid_table_switch_default_offset() { let instructions = vec![Instruction::Tableswitch { default: 42, @@ -263,7 +261,7 @@ mod tests { assert!(matches!(result, Err(InvalidInstructionOffset(_)))); } - #[test_log::test] + #[test] fn test_to_bytes_invalid_table_switch_offset() { let instructions = vec![ Instruction::Nop, @@ -278,7 +276,7 @@ mod tests { assert!(matches!(result, Err(InvalidInstructionOffset(_)))); } - #[test_log::test] + #[test] fn test_to_bytes_invalid_lookup_switch_default_offset() { let instructions = vec![Instruction::Lookupswitch { default: 42, @@ -288,7 +286,7 @@ mod tests { assert!(matches!(result, Err(InvalidInstructionOffset(_)))); } - #[test_log::test] + #[test] fn test_to_bytes_invalid_lookup_switch_pairs_offset() { let instructions = vec![ Instruction::Nop, @@ -301,7 +299,7 @@ mod tests { assert!(matches!(result, Err(InvalidInstructionOffset(_)))); } - #[test_log::test] + #[test] fn test_from_bytes() -> Result<()> { let instructions = vec![ Instruction::Iconst_0, @@ -323,7 +321,7 @@ mod tests { Ok(()) } - #[test_log::test] + #[test] fn test_from_bytes_invalid() { let bytes = vec![155, 0, 42]; let mut cursor = Cursor::new(bytes); @@ -331,7 +329,7 @@ mod tests { assert!(matches!(result, Err(InvalidInstructionOffset(_)))); } - #[test_log::test] + #[test] fn test_from_bytes_invalid_table_switch_default_offset() { let bytes = vec![ 170, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, @@ -341,7 +339,7 @@ mod tests { assert!(matches!(result, Err(InvalidInstructionOffset(_)))); } - #[test_log::test] + #[test] fn test_from_bytes_invalid_table_switch_offset() { let bytes = vec![ 0, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, @@ -351,7 +349,7 @@ mod tests { assert!(matches!(result, Err(InvalidInstructionOffset(_)))); } - #[test_log::test] + #[test] fn test_from_bytes_invalid_lookup_switch_default_offset() { let bytes = vec![ 171, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, @@ -361,7 +359,7 @@ mod tests { assert!(matches!(result, Err(InvalidInstructionOffset(_)))); } - #[test_log::test] + #[test] fn test_from_bytes_invalid_lookup_switch_offset() { let bytes = vec![0, 171, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2]; let mut cursor = Cursor::new(bytes); @@ -381,97 +379,97 @@ mod tests { Ok(()) } - #[test_log::test] + #[test] fn test_ifeq() -> Result<()> { test_instruction(Instruction::Ifeq(0)) } - #[test_log::test] + #[test] fn test_ifne() -> Result<()> { test_instruction(Instruction::Ifne(0)) } - #[test_log::test] + #[test] fn test_iflt() -> Result<()> { test_instruction(Instruction::Iflt(0)) } - #[test_log::test] + #[test] fn test_ifge() -> Result<()> { test_instruction(Instruction::Ifge(0)) } - #[test_log::test] + #[test] fn test_ifgt() -> Result<()> { test_instruction(Instruction::Ifgt(0)) } - #[test_log::test] + #[test] fn test_ifle() -> Result<()> { test_instruction(Instruction::Ifle(0)) } - #[test_log::test] + #[test] fn test_if_icmpeq() -> Result<()> { test_instruction(Instruction::If_icmpeq(0)) } - #[test_log::test] + #[test] fn test_if_icmpne() -> Result<()> { test_instruction(Instruction::If_icmpne(0)) } - #[test_log::test] + #[test] fn test_if_icmplt() -> Result<()> { test_instruction(Instruction::If_icmplt(0)) } - #[test_log::test] + #[test] fn test_if_icmpge() -> Result<()> { test_instruction(Instruction::If_icmpge(0)) } - #[test_log::test] + #[test] fn test_if_icmpgt() -> Result<()> { test_instruction(Instruction::If_icmpgt(0)) } - #[test_log::test] + #[test] fn test_if_icmple() -> Result<()> { test_instruction(Instruction::If_icmple(0)) } - #[test_log::test] + #[test] fn test_if_acmpeq() -> Result<()> { test_instruction(Instruction::If_acmpeq(0)) } - #[test_log::test] + #[test] fn test_if_acmpne() -> Result<()> { test_instruction(Instruction::If_acmpne(0)) } - #[test_log::test] + #[test] fn test_goto() -> Result<()> { test_instruction(Instruction::Goto(0)) } - #[test_log::test] + #[test] fn test_jsr() -> Result<()> { test_instruction(Instruction::Jsr(0)) } - #[test_log::test] + #[test] fn test_ifnull() -> Result<()> { test_instruction(Instruction::Ifnull(0)) } - #[test_log::test] + #[test] fn test_ifnonnull() -> Result<()> { test_instruction(Instruction::Ifnonnull(0)) } - #[test_log::test] + #[test] fn test_goto_w() -> Result<()> { let instruction = Instruction::Goto_w(1); let expected_bytes = [instruction.code(), 0, 0, 0, 5, Instruction::Nop.code()]; @@ -485,7 +483,7 @@ mod tests { Ok(()) } - #[test_log::test] + #[test] fn test_jsr_w() -> Result<()> { let instruction = Instruction::Jsr_w(1); let expected_bytes = [instruction.code(), 0, 0, 0, 5, Instruction::Nop.code()]; @@ -499,7 +497,7 @@ mod tests { Ok(()) } - #[test_log::test] + #[test] fn test_tableswitch() -> Result<()> { let instruction = Instruction::Tableswitch { default: 3, @@ -551,7 +549,7 @@ mod tests { Ok(()) } - #[test_log::test] + #[test] fn test_lookupswitch() -> Result<()> { let instruction = Instruction::Lookupswitch { default: 3, diff --git a/ristretto_classfile/src/attributes/line_number.rs b/ristretto_classfile/src/attributes/line_number.rs index 6926af72..c4eb4524 100644 --- a/ristretto_classfile/src/attributes/line_number.rs +++ b/ristretto_classfile/src/attributes/line_number.rs @@ -5,7 +5,7 @@ use std::io::Cursor; /// Implementation of `LineNumber`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct LineNumber { pub start_pc: u16, @@ -46,7 +46,7 @@ impl LineNumber { mod test { use super::*; - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let line_number = LineNumber { start_pc: 1, diff --git a/ristretto_classfile/src/attributes/local_variable_table.rs b/ristretto_classfile/src/attributes/local_variable_table.rs index 53aa0f47..f6280366 100644 --- a/ristretto_classfile/src/attributes/local_variable_table.rs +++ b/ristretto_classfile/src/attributes/local_variable_table.rs @@ -5,7 +5,7 @@ use std::io::Cursor; /// Implementation of `LocalVariableTable`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct LocalVariableTable { pub start_pc: u16, @@ -65,7 +65,7 @@ impl fmt::Display for LocalVariableTable { mod test { use super::*; - #[test_log::test] + #[test] fn test_to_string() { let local_variable_table = LocalVariableTable { start_pc: 1, @@ -81,7 +81,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let local_variable_table = LocalVariableTable { start_pc: 1, diff --git a/ristretto_classfile/src/attributes/local_variable_target.rs b/ristretto_classfile/src/attributes/local_variable_target.rs index a6fb1891..a1fe1aab 100644 --- a/ristretto_classfile/src/attributes/local_variable_target.rs +++ b/ristretto_classfile/src/attributes/local_variable_target.rs @@ -5,7 +5,7 @@ use std::io::Cursor; /// Implementation of `LocalVariableTarget`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct LocalVariableTarget { pub start_pc: u16, @@ -57,7 +57,7 @@ impl fmt::Display for LocalVariableTarget { mod test { use super::*; - #[test_log::test] + #[test] fn test_display() { let local_variable_target = LocalVariableTarget { start_pc: 1, @@ -70,7 +70,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let local_variable_target = LocalVariableTarget { start_pc: 1, diff --git a/ristretto_classfile/src/attributes/local_variable_type_table.rs b/ristretto_classfile/src/attributes/local_variable_type_table.rs index 761eea26..cc3464b3 100644 --- a/ristretto_classfile/src/attributes/local_variable_type_table.rs +++ b/ristretto_classfile/src/attributes/local_variable_type_table.rs @@ -5,7 +5,7 @@ use std::io::Cursor; /// Implementation of `LocalVariableTypeTable`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct LocalVariableTypeTable { pub start_pc: u16, @@ -65,7 +65,7 @@ impl fmt::Display for LocalVariableTypeTable { mod test { use super::*; - #[test_log::test] + #[test] fn test_display() { let local_variable_type_table = LocalVariableTypeTable { start_pc: 1, @@ -80,7 +80,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let local_variable_type_table = LocalVariableTypeTable { start_pc: 1, diff --git a/ristretto_classfile/src/attributes/method_parameter.rs b/ristretto_classfile/src/attributes/method_parameter.rs index 849e31b8..0442cb1e 100644 --- a/ristretto_classfile/src/attributes/method_parameter.rs +++ b/ristretto_classfile/src/attributes/method_parameter.rs @@ -6,7 +6,7 @@ use std::io::Cursor; /// Implementation of `MethodParameter`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct MethodParameter { pub name_index: u16, @@ -52,7 +52,7 @@ impl fmt::Display for MethodParameter { mod test { use super::*; - #[test_log::test] + #[test] fn test_to_string() { let method_parameter = MethodParameter { name_index: 3, @@ -64,7 +64,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let method_parameter = MethodParameter { name_index: 3, diff --git a/ristretto_classfile/src/attributes/module_access_flags.rs b/ristretto_classfile/src/attributes/module_access_flags.rs index 99195fad..96417299 100644 --- a/ristretto_classfile/src/attributes/module_access_flags.rs +++ b/ristretto_classfile/src/attributes/module_access_flags.rs @@ -7,7 +7,7 @@ use std::io::Cursor; bitflags! { /// Module access flags. /// - /// See: + /// See: #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ModuleAccessFlags: u16 { /// Indicates that this module is open. @@ -66,12 +66,12 @@ impl fmt::Display for ModuleAccessFlags { mod test { use super::*; - #[test_log::test] + #[test] fn test_default() { assert_eq!(ModuleAccessFlags::empty(), ModuleAccessFlags::default()); } - #[test_log::test] + #[test] fn test_all_access_flags() { let access_flags: u16 = u16::MAX; let mut bytes = Cursor::new(access_flags.to_be_bytes().to_vec()); @@ -83,7 +83,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_access_flags() -> Result<()> { let access_flags = ModuleAccessFlags::OPEN; let mut bytes = Vec::new(); @@ -93,7 +93,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_to_string() { assert_eq!("(0x0020) ACC_OPEN", ModuleAccessFlags::OPEN.to_string()); assert_eq!( diff --git a/ristretto_classfile/src/attributes/nested_class_access_flags.rs b/ristretto_classfile/src/attributes/nested_class_access_flags.rs index 945489e1..e5467279 100644 --- a/ristretto_classfile/src/attributes/nested_class_access_flags.rs +++ b/ristretto_classfile/src/attributes/nested_class_access_flags.rs @@ -7,7 +7,7 @@ use std::io::Cursor; bitflags! { /// Nest class access flags. /// - /// See: + /// See: #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct NestedClassAccessFlags: u16 { /// Declared public; may be accessed from outside its package. @@ -54,8 +54,6 @@ impl NestedClassAccessFlags { /// /// # Errors /// Should not occur; reserved for future use. - #[allow(clippy::trivially_copy_pass_by_ref)] - #[allow(clippy::wrong_self_convention)] pub fn to_bytes(&self, bytes: &mut Vec) -> Result<()> { bytes.write_u16::(self.bits())?; Ok(()) @@ -103,7 +101,7 @@ impl fmt::Display for NestedClassAccessFlags { mod test { use super::*; - #[test_log::test] + #[test] fn test_default() { assert_eq!( NestedClassAccessFlags::empty(), @@ -111,7 +109,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_all_access_flags() { let access_flags: u16 = u16::MAX; let mut bytes = Cursor::new(access_flags.to_be_bytes().to_vec()); @@ -130,7 +128,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_access_flags() -> Result<()> { let access_flags = NestedClassAccessFlags::PUBLIC | NestedClassAccessFlags::FINAL; let mut bytes = Vec::new(); @@ -143,7 +141,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_to_string() { assert_eq!( "(0x0001) ACC_PUBLIC", diff --git a/ristretto_classfile/src/attributes/opens.rs b/ristretto_classfile/src/attributes/opens.rs index acd59828..c591436f 100644 --- a/ristretto_classfile/src/attributes/opens.rs +++ b/ristretto_classfile/src/attributes/opens.rs @@ -6,7 +6,7 @@ use std::io::Cursor; /// Implementation of `Opens`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct Opens { pub index: u16, @@ -66,7 +66,7 @@ impl fmt::Display for Opens { mod test { use super::*; - #[test_log::test] + #[test] fn test_display() { let opens = Opens { index: 1, @@ -79,7 +79,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let opens = Opens { index: 1, diff --git a/ristretto_classfile/src/attributes/opens_flags.rs b/ristretto_classfile/src/attributes/opens_flags.rs index bdd85e6e..7c1b88a5 100644 --- a/ristretto_classfile/src/attributes/opens_flags.rs +++ b/ristretto_classfile/src/attributes/opens_flags.rs @@ -7,7 +7,7 @@ use std::io::Cursor; bitflags! { /// Opens flags. /// - /// See: + /// See: #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct OpensFlags: u16 { /// Indicates that this opening was not explicitly or implicitly declared in the source of @@ -63,12 +63,12 @@ impl fmt::Display for OpensFlags { mod test { use super::*; - #[test_log::test] + #[test] fn test_default() { assert_eq!(OpensFlags::empty(), OpensFlags::default()); } - #[test_log::test] + #[test] fn test_all_access_flags() { let access_flags: u16 = u16::MAX; let mut bytes = Cursor::new(access_flags.to_be_bytes().to_vec()); @@ -78,7 +78,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_access_flags() -> Result<()> { let access_flags = OpensFlags::MANDATED; let mut bytes = Vec::new(); @@ -88,7 +88,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_to_string() { assert_eq!("(0x1000) ACC_SYNTHETIC", OpensFlags::SYNTHETIC.to_string()); assert_eq!("(0x8000) ACC_MANDATED", OpensFlags::MANDATED.to_string()); diff --git a/ristretto_classfile/src/attributes/parameter_annotation.rs b/ristretto_classfile/src/attributes/parameter_annotation.rs index e3ee0bef..7fe58bc8 100644 --- a/ristretto_classfile/src/attributes/parameter_annotation.rs +++ b/ristretto_classfile/src/attributes/parameter_annotation.rs @@ -6,7 +6,7 @@ use std::io::Cursor; /// Implementation of a parameter annotation. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct ParameterAnnotation { pub annotations: Vec, @@ -57,7 +57,7 @@ mod test { use super::*; use crate::attributes::{AnnotationElement, AnnotationValuePair}; - #[test_log::test] + #[test] fn test_to_string() { let annotation_value_pair = AnnotationValuePair { name_index: 1, @@ -78,7 +78,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let annotation_value_pair = AnnotationValuePair { name_index: 1, diff --git a/ristretto_classfile/src/attributes/provides.rs b/ristretto_classfile/src/attributes/provides.rs index d1670cf5..a26d423b 100644 --- a/ristretto_classfile/src/attributes/provides.rs +++ b/ristretto_classfile/src/attributes/provides.rs @@ -5,7 +5,7 @@ use std::io::Cursor; /// Implementation of `Provides`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct Provides { pub index: u16, @@ -59,7 +59,7 @@ impl fmt::Display for Provides { mod test { use super::*; - #[test_log::test] + #[test] fn test_to_string() { let provides = Provides { index: 1, @@ -68,7 +68,7 @@ mod test { assert_eq!("Provides[index=1, with_index=[2]]", provides.to_string()); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let provides = Provides { index: 1, diff --git a/ristretto_classfile/src/attributes/record.rs b/ristretto_classfile/src/attributes/record.rs index 19facac0..f6955d80 100644 --- a/ristretto_classfile/src/attributes/record.rs +++ b/ristretto_classfile/src/attributes/record.rs @@ -7,7 +7,7 @@ use std::io::Cursor; /// Implementation of `Record`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct Record { pub name_index: u16, @@ -70,7 +70,7 @@ impl fmt::Display for Record { mod test { use super::*; - #[test_log::test] + #[test] fn test_to_string() { let attribute = Attribute::ConstantValue { name_index: 1, @@ -87,7 +87,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let attribute = Attribute::ConstantValue { name_index: 1, diff --git a/ristretto_classfile/src/attributes/requires.rs b/ristretto_classfile/src/attributes/requires.rs index bcada731..eb57eaf8 100644 --- a/ristretto_classfile/src/attributes/requires.rs +++ b/ristretto_classfile/src/attributes/requires.rs @@ -6,7 +6,7 @@ use std::io::Cursor; /// Implementation of `Requires`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct Requires { pub index: u16, @@ -57,7 +57,7 @@ impl fmt::Display for Requires { mod test { use super::*; - #[test_log::test] + #[test] fn test_to_string() { let requires = Requires { index: 1, @@ -70,7 +70,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let requires = Requires { index: 1, diff --git a/ristretto_classfile/src/attributes/requires_flags.rs b/ristretto_classfile/src/attributes/requires_flags.rs index 389af26e..6d83673e 100644 --- a/ristretto_classfile/src/attributes/requires_flags.rs +++ b/ristretto_classfile/src/attributes/requires_flags.rs @@ -7,7 +7,7 @@ use std::io::Cursor; bitflags! { /// Requires flags. /// - /// See: + /// See: #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct RequiresFlags: u16 { /// Indicates that any module which depends on the current module, implicitly declares a @@ -75,12 +75,12 @@ impl fmt::Display for RequiresFlags { mod test { use super::*; - #[test_log::test] + #[test] fn test_default() { assert_eq!(RequiresFlags::empty(), RequiresFlags::default()); } - #[test_log::test] + #[test] fn test_all_access_flags() { let access_flags: u16 = u16::MAX; let mut bytes = Cursor::new(access_flags.to_be_bytes().to_vec()); @@ -93,7 +93,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_access_flags() -> Result<()> { let access_flags = RequiresFlags::TRANSITIVE; let mut bytes = Vec::new(); @@ -103,7 +103,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_to_string() { assert_eq!( "(0x0020) ACC_TRANSITIVE", diff --git a/ristretto_classfile/src/attributes/stack_frame.rs b/ristretto_classfile/src/attributes/stack_frame.rs index 434dd55a..1dc6c55a 100644 --- a/ristretto_classfile/src/attributes/stack_frame.rs +++ b/ristretto_classfile/src/attributes/stack_frame.rs @@ -7,8 +7,7 @@ use std::io::Cursor; /// Implementation of `StackFrame`. /// -/// See: -#[allow(non_camel_case_types)] +/// See: #[derive(Clone, Debug, PartialEq)] pub enum StackFrame { SameFrame { @@ -140,7 +139,6 @@ impl StackFrame { /// # Errors /// - If the number of locals or stack items exceeds 65,534. /// - If a stack frame fails to serialize. - #[allow(clippy::match_same_arms)] pub fn to_bytes(&self, bytes: &mut Vec) -> Result<()> { match self { StackFrame::SameFrame { frame_type } => { @@ -303,7 +301,7 @@ mod test { use super::*; use indoc::indoc; - #[test_log::test] + #[test] fn test_invalid_stack_frame() -> Result<()> { let mut bytes = Vec::new(); let frame_type = 128; @@ -326,7 +324,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_same_frame() -> Result<()> { let frame_type = 0; let stack_frame = StackFrame::SameFrame { frame_type: 0 }; @@ -340,7 +338,7 @@ mod test { test_stack_frame(&stack_frame, &expected_bytes) } - #[test_log::test] + #[test] fn test_same_locales_1_stack_item_frame() -> Result<()> { let frame_type = 64; let stack_frame = StackFrame::SameLocals1StackItemFrame { @@ -359,7 +357,7 @@ mod test { test_stack_frame(&stack_frame, &expected_bytes) } - #[test_log::test] + #[test] fn test_same_locales_1_stack_item_frame_extended() -> Result<()> { let frame_type = 247; let stack_frame = StackFrame::SameLocals1StackItemFrameExtended { @@ -380,7 +378,7 @@ mod test { test_stack_frame(&stack_frame, &expected_bytes) } - #[test_log::test] + #[test] fn test_chop_frame() -> Result<()> { let frame_type = 248; let stack_frame = StackFrame::ChopFrame { @@ -399,7 +397,7 @@ mod test { test_stack_frame(&stack_frame, &expected_bytes) } - #[test_log::test] + #[test] fn test_same_frame_extended() -> Result<()> { let frame_type = 251; let stack_frame = StackFrame::SameFrameExtended { @@ -418,7 +416,7 @@ mod test { test_stack_frame(&stack_frame, &expected_bytes) } - #[test_log::test] + #[test] fn test_append_frame() -> Result<()> { let frame_type = 252; let stack_frame = StackFrame::AppendFrame { @@ -439,7 +437,7 @@ mod test { test_stack_frame(&stack_frame, &expected_bytes) } - #[test_log::test] + #[test] fn test_full_frame() -> Result<()> { let frame_type = 255; let stack_frame = StackFrame::FullFrame { diff --git a/ristretto_classfile/src/attributes/target_path.rs b/ristretto_classfile/src/attributes/target_path.rs index b48d36b8..073e2c87 100644 --- a/ristretto_classfile/src/attributes/target_path.rs +++ b/ristretto_classfile/src/attributes/target_path.rs @@ -5,7 +5,7 @@ use std::io::Cursor; /// Implementation of `TargetPath`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct TargetPath { pub type_path_kind: u8, @@ -53,7 +53,7 @@ impl fmt::Display for TargetPath { mod test { use super::*; - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let target_path = TargetPath { type_path_kind: 1, diff --git a/ristretto_classfile/src/attributes/target_type.rs b/ristretto_classfile/src/attributes/target_type.rs index 323f561d..607ba646 100644 --- a/ristretto_classfile/src/attributes/target_type.rs +++ b/ristretto_classfile/src/attributes/target_type.rs @@ -7,7 +7,7 @@ use std::io::Cursor; /// Implementation of `TargetType`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub enum TargetType { TypeParameter { @@ -331,7 +331,7 @@ impl fmt::Display for TargetType { mod test { use super::*; - #[test_log::test] + #[test] fn test_invalid_code() { let mut bytes = Cursor::new(vec![255]); assert_eq!( @@ -350,7 +350,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_type_parameter() -> Result<()> { let target_type = TargetType::TypeParameter { target_type: 0, @@ -364,7 +364,7 @@ mod test { test_array_type(&target_type, &[0, 42]) } - #[test_log::test] + #[test] fn test_super_type() -> Result<()> { let target_type = TargetType::SuperType { target_type: 16, @@ -378,7 +378,7 @@ mod test { test_array_type(&target_type, &[16, 0, 42]) } - #[test_log::test] + #[test] fn test_type_parameter_bound() -> Result<()> { let target_type = TargetType::TypeParameterBound { target_type: 17, @@ -393,7 +393,7 @@ mod test { test_array_type(&target_type, &[17, 1, 42]) } - #[test_log::test] + #[test] fn test_empty() -> Result<()> { let target_type = TargetType::Empty { target_type: 19 }; @@ -401,7 +401,7 @@ mod test { test_array_type(&target_type, &[19]) } - #[test_log::test] + #[test] fn test_formal_parameter() -> Result<()> { let target_type = TargetType::FormalParameter { target_type: 22, @@ -415,7 +415,7 @@ mod test { test_array_type(&target_type, &[22, 42]) } - #[test_log::test] + #[test] fn test_throws() -> Result<()> { let target_type = TargetType::Throws { target_type: 23, @@ -429,7 +429,7 @@ mod test { test_array_type(&target_type, &[23, 0, 42]) } - #[test_log::test] + #[test] fn test_local_var() -> Result<()> { let local_variable_targets = vec![LocalVariableTarget { start_pc: 1, @@ -445,7 +445,7 @@ mod test { test_array_type(&target_type, &[64, 0, 1, 0, 1, 0, 2, 0, 3]) } - #[test_log::test] + #[test] fn test_catch() -> Result<()> { let target_type = TargetType::Catch { target_type: 66, @@ -459,7 +459,7 @@ mod test { test_array_type(&target_type, &[66, 0, 42]) } - #[test_log::test] + #[test] fn test_offset() -> Result<()> { let target_type = TargetType::Offset { target_type: 67, @@ -470,7 +470,7 @@ mod test { test_array_type(&target_type, &[67, 0, 42]) } - #[test_log::test] + #[test] fn test_type_argument() -> Result<()> { let target_type = TargetType::TypeArgument { target_type: 71, diff --git a/ristretto_classfile/src/attributes/type_annotation.rs b/ristretto_classfile/src/attributes/type_annotation.rs index 122570d3..3d7492ca 100644 --- a/ristretto_classfile/src/attributes/type_annotation.rs +++ b/ristretto_classfile/src/attributes/type_annotation.rs @@ -6,7 +6,7 @@ use std::io::Cursor; /// Implementation of a type annotation. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct TypeAnnotation { pub target_type: TargetType, @@ -89,7 +89,7 @@ mod test { use super::*; use crate::attributes::{AnnotationElement, AnnotationValuePair}; - #[test_log::test] + #[test] fn test_to_string() { let element = AnnotationValuePair { name_index: 1, @@ -112,7 +112,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let element = AnnotationValuePair { name_index: 1, diff --git a/ristretto_classfile/src/attributes/verification_type.rs b/ristretto_classfile/src/attributes/verification_type.rs index 2625a95c..daf187b3 100644 --- a/ristretto_classfile/src/attributes/verification_type.rs +++ b/ristretto_classfile/src/attributes/verification_type.rs @@ -6,8 +6,7 @@ use std::io::Cursor; /// Implementation of `VerificationType`. /// -/// See: -#[allow(non_camel_case_types)] +/// See: #[derive(Clone, Debug, PartialEq)] pub enum VerificationType { Top, @@ -101,7 +100,7 @@ impl fmt::Display for VerificationType { mod test { use super::*; - #[test_log::test] + #[test] fn test_invalid_verification_type() -> Result<()> { let mut bytes = Vec::new(); let tag = u8::MAX; @@ -132,7 +131,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_top() -> Result<()> { let verification_type = VerificationType::Top; let tag = 0; @@ -142,7 +141,7 @@ mod test { test_verification_type(&verification_type, &expected_bytes, tag) } - #[test_log::test] + #[test] fn test_integer() -> Result<()> { let verification_type = VerificationType::Integer; let tag = 1; @@ -152,7 +151,7 @@ mod test { test_verification_type(&verification_type, &expected_bytes, tag) } - #[test_log::test] + #[test] fn test_float() -> Result<()> { let verification_type = VerificationType::Float; let tag = 2; @@ -162,7 +161,7 @@ mod test { test_verification_type(&verification_type, &expected_bytes, tag) } - #[test_log::test] + #[test] fn test_double() -> Result<()> { let verification_type = VerificationType::Double; let tag = 3; @@ -172,7 +171,7 @@ mod test { test_verification_type(&verification_type, &expected_bytes, tag) } - #[test_log::test] + #[test] fn test_long() -> Result<()> { let verification_type = VerificationType::Long; let tag = 4; @@ -182,7 +181,7 @@ mod test { test_verification_type(&verification_type, &expected_bytes, tag) } - #[test_log::test] + #[test] fn test_null() -> Result<()> { let verification_type = VerificationType::Null; let tag = 5; @@ -192,7 +191,7 @@ mod test { test_verification_type(&verification_type, &expected_bytes, tag) } - #[test_log::test] + #[test] fn test_uninitialized_this() -> Result<()> { let verification_type = VerificationType::UninitializedThis; let tag = 6; @@ -202,7 +201,7 @@ mod test { test_verification_type(&verification_type, &expected_bytes, tag) } - #[test_log::test] + #[test] fn test_object() -> Result<()> { let verification_type = VerificationType::Object { cpool_index: 42 }; let tag = 7; @@ -212,7 +211,7 @@ mod test { test_verification_type(&verification_type, &expected_bytes, tag) } - #[test_log::test] + #[test] fn test_uninitialized() -> Result<()> { let verification_type = VerificationType::Uninitialized { offset: 42 }; let tag = 8; diff --git a/ristretto_classfile/src/base_type.rs b/ristretto_classfile/src/base_type.rs index 7fcc34be..be211404 100644 --- a/ristretto_classfile/src/base_type.rs +++ b/ristretto_classfile/src/base_type.rs @@ -4,7 +4,7 @@ use std::fmt; /// Implementation of `BaseType`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub enum BaseType { Byte, @@ -73,12 +73,12 @@ impl fmt::Display for BaseType { mod test { use super::*; - #[test_log::test] + #[test] fn test_invalid_code() { assert_eq!(Err(InvalidBaseTypeCode('0')), BaseType::parse('0')); } - #[test_log::test] + #[test] fn test_byte() -> Result<()> { assert_eq!(BaseType::Byte.code(), 'B'); assert_eq!(BaseType::Byte, BaseType::parse('B')?); @@ -86,7 +86,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_char() -> Result<()> { assert_eq!(BaseType::Char.code(), 'C'); assert_eq!(BaseType::Char, BaseType::parse('C')?); @@ -94,7 +94,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_double() -> Result<()> { assert_eq!(BaseType::Double.code(), 'D'); assert_eq!(BaseType::Double, BaseType::parse('D')?); @@ -102,7 +102,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_float() -> Result<()> { assert_eq!(BaseType::Float.code(), 'F'); assert_eq!(BaseType::Float, BaseType::parse('F')?); @@ -110,7 +110,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_int() -> Result<()> { assert_eq!(BaseType::Int.code(), 'I'); assert_eq!(BaseType::Int, BaseType::parse('I')?); @@ -118,7 +118,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_long() -> Result<()> { assert_eq!(BaseType::Long.code(), 'J'); assert_eq!(BaseType::Long, BaseType::parse('J')?); @@ -126,7 +126,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_short() -> Result<()> { assert_eq!(BaseType::Short.code(), 'S'); assert_eq!(BaseType::Short, BaseType::parse('S')?); @@ -134,7 +134,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_boolean() -> Result<()> { assert_eq!(BaseType::Boolean.code(), 'Z'); assert_eq!(BaseType::Boolean, BaseType::parse('Z')?); diff --git a/ristretto_classfile/src/class_access_flags.rs b/ristretto_classfile/src/class_access_flags.rs index b87e279c..14c55517 100644 --- a/ristretto_classfile/src/class_access_flags.rs +++ b/ristretto_classfile/src/class_access_flags.rs @@ -7,7 +7,7 @@ use std::io::Cursor; bitflags! { /// Class access flags. /// - /// See: + /// See: #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ClassAccessFlags: u16 { /// Declared public; may be accessed from outside its package. @@ -50,7 +50,6 @@ impl ClassAccessFlags { /// Get the Class Access Flags as a string of code. #[must_use] - #[allow(clippy::trivially_copy_pass_by_ref)] pub fn as_code(&self) -> String { let mut modifiers = Vec::new(); if self.contains(ClassAccessFlags::PUBLIC) { @@ -128,12 +127,12 @@ impl fmt::Display for ClassAccessFlags { mod test { use super::*; - #[test_log::test] + #[test] fn test_default() { assert_eq!(ClassAccessFlags::empty(), ClassAccessFlags::default()); } - #[test_log::test] + #[test] fn test_all_access_flags() { let access_flags: u16 = u16::MAX; let mut bytes = Cursor::new(access_flags.to_be_bytes().to_vec()); @@ -151,7 +150,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_access_flags() -> Result<()> { let access_flags = ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL; let mut bytes = Vec::new(); @@ -161,7 +160,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_as_code() { assert_eq!("public class", ClassAccessFlags::PUBLIC.as_code()); assert_eq!("final class", ClassAccessFlags::FINAL.as_code()); @@ -174,7 +173,7 @@ mod test { assert_eq!("module", ClassAccessFlags::MODULE.as_code()); } - #[test_log::test] + #[test] fn test_to_string() { assert_eq!("(0x0001) ACC_PUBLIC", ClassAccessFlags::PUBLIC.to_string()); assert_eq!("(0x0010) ACC_FINAL", ClassAccessFlags::FINAL.to_string()); diff --git a/ristretto_classfile/src/class_file.rs b/ristretto_classfile/src/class_file.rs index 32829eac..34c253a4 100644 --- a/ristretto_classfile/src/class_file.rs +++ b/ristretto_classfile/src/class_file.rs @@ -16,7 +16,7 @@ const MAGIC: u32 = 0xCAFE_BABE; /// `ClassFile` represents the content of a Java .class file. /// -/// See: +/// See: #[derive(Clone, Debug, Default, PartialEq)] pub struct ClassFile { pub version: Version, @@ -31,7 +31,7 @@ pub struct ClassFile { } impl ClassFile { - /// Get the class name. + /// Get the fully qualified class name. /// /// # Errors /// Returns an error if the class name is not found. @@ -223,7 +223,7 @@ mod test { use crate::Error::{InvalidConstantPoolIndexType, IoError}; use indoc::indoc; - #[test_log::test] + #[test] fn test_invalid_magic() { let invalid_magic: u32 = 0x0102_0304; let mut bytes = Cursor::new(invalid_magic.to_be_bytes().to_vec()); @@ -233,7 +233,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_class_name() -> Result<()> { let class_bytes = include_bytes!("../../classes/Simple.class"); let expected_bytes = class_bytes.to_vec(); @@ -243,7 +243,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_class_name_invalid_constant_pool() -> Result<()> { let mut constant_pool = ConstantPool::default(); let utf8_index = constant_pool.add_utf8("Test")?; @@ -260,7 +260,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_verify() -> Result<()> { let class_bytes = include_bytes!("../../classes/Simple.class"); let expected_bytes = class_bytes.to_vec(); @@ -270,13 +270,14 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_verify_error() -> Result<()> { let mut constant_pool = ConstantPool::default(); let this_class = constant_pool.add_class("Test")?; // Add an invalid constant to trigger a verification error. constant_pool.push(Constant::Class(u16::MAX)); let class_file = ClassFile { + version: Version::Java21 { minor: 0 }, constant_pool: constant_pool.clone(), this_class, ..Default::default() @@ -292,7 +293,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_minimum_to_string() -> Result<()> { let class_bytes = include_bytes!("../../classes/Minimum.class"); let expected_bytes = class_bytes.to_vec(); @@ -300,7 +301,7 @@ mod test { let expected = indoc! {r" public class Minimum minor version: 0 - major version: 65 (Java 21) + major version: 52 (Java 8) flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #7 super_class: #2 @@ -339,13 +340,13 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_minimum_serialization() -> Result<()> { let class_bytes = include_bytes!("../../classes/Minimum.class"); let expected_bytes = class_bytes.to_vec(); let class_file = ClassFile::from_bytes(&mut Cursor::new(expected_bytes.clone()))?; - assert_eq!(Version::Java21 { minor: 0 }, class_file.version); + assert_eq!(Version::Java8 { minor: 0 }, class_file.version); assert_eq!( ClassAccessFlags::PUBLIC | ClassAccessFlags::SUPER, class_file.access_flags @@ -358,13 +359,13 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_simple_serialization() -> Result<()> { let class_bytes = include_bytes!("../../classes/Simple.class"); let expected_bytes = class_bytes.to_vec(); let class_file = ClassFile::from_bytes(&mut Cursor::new(expected_bytes.clone()))?; - assert_eq!(Version::Java21 { minor: 0 }, class_file.version); + assert_eq!(Version::Java8 { minor: 0 }, class_file.version); assert_eq!( ClassAccessFlags::PUBLIC | ClassAccessFlags::SUPER, class_file.access_flags @@ -377,7 +378,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_from_bytes_invalid() { let bytes = vec![ 202, 254, 186, 190, 254, 0, 0, 48, 0, 0, 160, 93, 37, 0, 212, 186, diff --git a/ristretto_classfile/src/constant.rs b/ristretto_classfile/src/constant.rs index 0a933cd5..d5e8a13e 100644 --- a/ristretto_classfile/src/constant.rs +++ b/ristretto_classfile/src/constant.rs @@ -15,7 +15,7 @@ const VERSION_55_0: Version = Version::Java11 { minor: 0 }; /// Constant /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub enum Constant { Utf8(String), @@ -156,7 +156,6 @@ impl Constant { /// /// # Errors /// If a UTF-8 string is more than 65535 bytes long. - #[allow(clippy::match_same_arms)] pub fn to_bytes(&self, bytes: &mut Vec) -> Result<()> { bytes.write_u8(self.tag())?; @@ -293,7 +292,7 @@ impl fmt::Display for Constant { mod test { use super::*; - #[test_log::test] + #[test] fn test_invalid_tag() { let mut bytes = Cursor::new(vec![0]); assert_eq!(Err(InvalidConstantTag(0)), Constant::from_bytes(&mut bytes)); @@ -317,7 +316,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_utf8() -> Result<()> { let constant = Constant::Utf8("foo".to_string()); let expected_bytes = [1, 0, 3, 102, 111, 111]; @@ -326,7 +325,7 @@ mod test { test_constant(&constant, &expected_bytes, 1, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_integer() -> Result<()> { let constant = Constant::Integer(42); let expected_bytes = [3, 0, 0, 0, 42]; @@ -335,7 +334,7 @@ mod test { test_constant(&constant, &expected_bytes, 3, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_float() -> Result<()> { let constant = Constant::Float(std::f32::consts::PI); let expected_bytes = [4, 64, 73, 15, 219]; @@ -344,7 +343,7 @@ mod test { test_constant(&constant, &expected_bytes, 4, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_long() -> Result<()> { let constant = Constant::Long(1_234_567_890); let expected_bytes = [5, 0, 0, 0, 0, 73, 150, 2, 210]; @@ -353,7 +352,7 @@ mod test { test_constant(&constant, &expected_bytes, 5, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_double() -> Result<()> { let constant = Constant::Double(std::f64::consts::PI); let expected_bytes = [6, 64, 9, 33, 251, 84, 68, 45, 24]; @@ -362,7 +361,7 @@ mod test { test_constant(&constant, &expected_bytes, 6, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_class() -> Result<()> { let constant = Constant::Class(1); let expected_bytes = [7, 0, 1]; @@ -371,7 +370,7 @@ mod test { test_constant(&constant, &expected_bytes, 7, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_string() -> Result<()> { let constant = Constant::String(1); let expected_bytes = [8, 0, 1]; @@ -380,7 +379,7 @@ mod test { test_constant(&constant, &expected_bytes, 8, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_field_ref() -> Result<()> { let constant = Constant::FieldRef { class_index: 1, @@ -392,7 +391,7 @@ mod test { test_constant(&constant, &expected_bytes, 9, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_method_ref() -> Result<()> { let constant = Constant::MethodRef { class_index: 1, @@ -404,7 +403,7 @@ mod test { test_constant(&constant, &expected_bytes, 10, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_interface_method_ref() -> Result<()> { let constant = Constant::InterfaceMethodRef { class_index: 1, @@ -416,7 +415,7 @@ mod test { test_constant(&constant, &expected_bytes, 11, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_name_and_type() -> Result<()> { let constant = Constant::NameAndType { name_index: 1, @@ -428,7 +427,7 @@ mod test { test_constant(&constant, &expected_bytes, 12, &VERSION_45_3) } - #[test_log::test] + #[test] fn test_method_handle() -> Result<()> { let constant = Constant::MethodHandle { reference_kind: ReferenceKind::GetField, @@ -440,7 +439,7 @@ mod test { test_constant(&constant, &expected_bytes, 15, &VERSION_51_0) } - #[test_log::test] + #[test] fn test_method_type() -> Result<()> { let constant = Constant::MethodType(1); let expected_bytes = [16, 0, 1]; @@ -449,7 +448,7 @@ mod test { test_constant(&constant, &expected_bytes, 16, &VERSION_51_0) } - #[test_log::test] + #[test] fn test_dynamic() -> Result<()> { let constant = Constant::Dynamic { bootstrap_method_attr_index: 1, @@ -461,7 +460,7 @@ mod test { test_constant(&constant, &expected_bytes, 17, &VERSION_55_0) } - #[test_log::test] + #[test] fn test_invoke_dynamic() -> Result<()> { let constant = Constant::InvokeDynamic { bootstrap_method_attr_index: 1, @@ -473,7 +472,7 @@ mod test { test_constant(&constant, &expected_bytes, 18, &VERSION_51_0) } - #[test_log::test] + #[test] fn test_module() -> Result<()> { let constant = Constant::Module(1); let expected_bytes = [19, 0, 1]; @@ -482,7 +481,7 @@ mod test { test_constant(&constant, &expected_bytes, 19, &VERSION_55_0) } - #[test_log::test] + #[test] fn test_package() -> Result<()> { let constant = Constant::Package(1); let expected_bytes = [20, 0, 1]; diff --git a/ristretto_classfile/src/constant_pool.rs b/ristretto_classfile/src/constant_pool.rs index def94526..eb067bde 100644 --- a/ristretto_classfile/src/constant_pool.rs +++ b/ristretto_classfile/src/constant_pool.rs @@ -8,7 +8,7 @@ use std::{fmt, io}; /// Constant pool. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct ConstantPool { constants: Vec, @@ -48,7 +48,7 @@ impl ConstantPool { /// Get a constant from the pool by index; indexes are 1-based. /// Returns None if the index is out of bounds. - /// See: + /// See: #[must_use] pub fn get(&self, index: u16) -> Option<&Constant> { match self.try_get(index) { @@ -59,7 +59,7 @@ impl ConstantPool { /// Get a constant from the pool by index; indexes are 1-based. /// Returns an error if the index is out of bounds. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds. @@ -136,14 +136,13 @@ impl ConstantPool { /// Get a UTF-8 constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a UTF-8 constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a UTF-8 constant. pub fn try_get_utf8(&self, index: u16) -> Result<&String> { - match self.try_get(index) { - Ok(Constant::Utf8(value)) => Ok(value), - Err(_) => Err(InvalidConstantPoolIndex(index)), + match self.try_get(index)? { + Constant::Utf8(value) => Ok(value), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -158,14 +157,13 @@ impl ConstantPool { /// Get an integer constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not an integer constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not an integer constant. pub fn try_get_integer(&self, index: u16) -> Result<&i32> { - match self.try_get(index) { - Ok(Constant::Integer(value)) => Ok(value), - Err(_) => Err(InvalidConstantPoolIndex(index)), + match self.try_get(index)? { + Constant::Integer(value) => Ok(value), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -180,14 +178,13 @@ impl ConstantPool { /// Get a float constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a float constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a float constant. pub fn try_get_float(&self, index: u16) -> Result<&f32> { - match self.try_get(index) { - Ok(Constant::Float(value)) => Ok(value), - Err(_) => Err(InvalidConstantPoolIndex(index)), + match self.try_get(index)? { + Constant::Float(value) => Ok(value), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -202,14 +199,13 @@ impl ConstantPool { /// Get a long constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a long constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a long constant. pub fn try_get_long(&self, index: u16) -> Result<&i64> { - match self.try_get(index) { - Ok(Constant::Long(value)) => Ok(value), - Err(_) => Err(InvalidConstantPoolIndex(index)), + match self.try_get(index)? { + Constant::Long(value) => Ok(value), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -224,14 +220,13 @@ impl ConstantPool { /// Get a double constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a double constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a double constant. pub fn try_get_double(&self, index: u16) -> Result<&f64> { - match self.try_get(index) { - Ok(Constant::Double(value)) => Ok(value), - Err(_) => Err(InvalidConstantPoolIndex(index)), + match self.try_get(index)? { + Constant::Double(value) => Ok(value), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -247,14 +242,13 @@ impl ConstantPool { /// Get a class constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a class constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a class constant. pub fn try_get_class(&self, index: u16) -> Result<&String> { - match self.try_get(index) { - Ok(Constant::Class(value)) => self.try_get_utf8(*value), - Err(_) => Err(InvalidConstantPoolIndex(index)), + match self.try_get(index)? { + Constant::Class(utf8_index) => self.try_get_utf8(*utf8_index), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -270,14 +264,13 @@ impl ConstantPool { /// Get a string constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a string constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a string constant. pub fn try_get_string(&self, index: u16) -> Result<&String> { - match self.try_get(index) { - Ok(Constant::String(value)) => self.try_get_utf8(*value), - Err(_) => Err(InvalidConstantPoolIndex(index)), + match self.try_get(index)? { + Constant::String(value) => self.try_get_utf8(*value), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -301,17 +294,16 @@ impl ConstantPool { /// Get a field constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a field constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a field constant. pub fn try_get_field_ref(&self, index: u16) -> Result<(&u16, &u16)> { - match self.try_get(index) { - Ok(Constant::FieldRef { + match self.try_get(index)? { + Constant::FieldRef { class_index, name_and_type_index, - }) => Ok((class_index, name_and_type_index)), - Err(_) => Err(InvalidConstantPoolIndex(index)), + } => Ok((class_index, name_and_type_index)), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -335,17 +327,16 @@ impl ConstantPool { /// Get a method constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a method constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a method constant. pub fn try_get_method_ref(&self, index: u16) -> Result<(&u16, &u16)> { - match self.try_get(index) { - Ok(Constant::MethodRef { + match self.try_get(index)? { + Constant::MethodRef { class_index, name_and_type_index, - }) => Ok((class_index, name_and_type_index)), - Err(_) => Err(InvalidConstantPoolIndex(index)), + } => Ok((class_index, name_and_type_index)), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -369,18 +360,17 @@ impl ConstantPool { /// Get an interface method constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not an interface method constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not an interface method /// constant. pub fn try_get_interface_method_ref(&self, index: u16) -> Result<(&u16, &u16)> { - match self.try_get(index) { - Ok(Constant::InterfaceMethodRef { + match self.try_get(index)? { + Constant::InterfaceMethodRef { class_index, name_and_type_index, - }) => Ok((class_index, name_and_type_index)), - Err(_) => Err(InvalidConstantPoolIndex(index)), + } => Ok((class_index, name_and_type_index)), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -400,18 +390,17 @@ impl ConstantPool { /// Get a name and type constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a name and type constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a name and type /// constant. pub fn try_get_name_and_type(&self, index: u16) -> Result<(&u16, &u16)> { - match self.try_get(index) { - Ok(Constant::NameAndType { + match self.try_get(index)? { + Constant::NameAndType { name_index, descriptor_index, - }) => Ok((name_index, descriptor_index)), - Err(_) => Err(InvalidConstantPoolIndex(index)), + } => Ok((name_index, descriptor_index)), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -433,18 +422,17 @@ impl ConstantPool { /// Get a method handle constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a method handle constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a method handle /// constant. pub fn try_get_method_handle(&self, index: u16) -> Result<(&ReferenceKind, &u16)> { - match self.try_get(index) { - Ok(Constant::MethodHandle { + match self.try_get(index)? { + Constant::MethodHandle { reference_kind, reference_index, - }) => Ok((reference_kind, reference_index)), - Err(_) => Err(InvalidConstantPoolIndex(index)), + } => Ok((reference_kind, reference_index)), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -460,15 +448,14 @@ impl ConstantPool { /// Get a method type constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a method type constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a method type /// constant. pub fn try_get_method_type(&self, index: u16) -> Result<&u16> { - match self.try_get(index) { - Ok(Constant::MethodType(value)) => Ok(value), - Err(_) => Err(InvalidConstantPoolIndex(index)), + match self.try_get(index)? { + Constant::MethodType(name_and_type_index) => Ok(name_and_type_index), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -492,17 +479,16 @@ impl ConstantPool { /// Get a dynamic constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a dynamic constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a dynamic constant. pub fn try_get_dynamic(&self, index: u16) -> Result<(&u16, &u16)> { - match self.try_get(index) { - Ok(Constant::Dynamic { + match self.try_get(index)? { + Constant::Dynamic { bootstrap_method_attr_index, name_and_type_index, - }) => Ok((bootstrap_method_attr_index, name_and_type_index)), - Err(_) => Err(InvalidConstantPoolIndex(index)), + } => Ok((bootstrap_method_attr_index, name_and_type_index)), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -526,18 +512,17 @@ impl ConstantPool { /// Get an invoke dynamic constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not an invoke dynamic constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not an invoke /// dynamic constant. pub fn try_get_invoke_dynamic(&self, index: u16) -> Result<(&u16, &u16)> { - match self.try_get(index) { - Ok(Constant::InvokeDynamic { + match self.try_get(index)? { + Constant::InvokeDynamic { bootstrap_method_attr_index, name_and_type_index, - }) => Ok((bootstrap_method_attr_index, name_and_type_index)), - Err(_) => Err(InvalidConstantPoolIndex(index)), + } => Ok((bootstrap_method_attr_index, name_and_type_index)), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -553,14 +538,13 @@ impl ConstantPool { /// Get a module constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a module constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a module constant. pub fn try_get_module(&self, index: u16) -> Result<&String> { - match self.try_get(index) { - Ok(Constant::Module(value)) => self.try_get_utf8(*value), - Err(_) => Err(InvalidConstantPoolIndex(index)), + match self.try_get(index)? { + Constant::Module(name_index) => self.try_get_utf8(*name_index), _ => Err(InvalidConstantPoolIndexType(index)), } } @@ -576,17 +560,113 @@ impl ConstantPool { /// Get a package constant from the pool by index; indexes are 1-based. /// Returns an error if the constant is not a package constant. - /// See: + /// See: /// /// # Errors /// Returns an error if the index is out of bounds or the constant is not a package constant. pub fn try_get_package(&self, index: u16) -> Result<&String> { - match self.try_get(index) { - Ok(Constant::Package(value)) => self.try_get_utf8(*value), - Err(_) => Err(InvalidConstantPoolIndex(index)), + match self.try_get(index)? { + Constant::Package(name_index) => self.try_get_utf8(*name_index), _ => Err(InvalidConstantPoolIndexType(index)), } } + + /// Get a formatted string constant from the pool by index; indexes are 1-based. + /// + /// # Errors + /// Returns an error if the index is out of bounds + pub fn try_get_formatted_string(&self, index: u16) -> Result { + let value = match self.try_get(index)? { + Constant::Utf8(value) => value.to_string(), + Constant::Integer(integer) => format!("{integer}"), + Constant::Float(float) => format!("{float}"), + Constant::Long(long) => format!("{long}"), + Constant::Double(double) => format!("{double}"), + Constant::Class(utf8_index) => { + format!("Class {}", self.try_get_utf8(*utf8_index)?) + } + Constant::String(utf8_index) => { + format!("String {}", self.try_get_utf8(*utf8_index)?) + } + Constant::FieldRef { + class_index, + name_and_type_index, + } => { + let (name_index, _descriptor_index) = + self.try_get_name_and_type(*name_and_type_index)?; + let class_name = self.try_get_class(*class_index)?; + let field_name = self.try_get_utf8(*name_index)?; + format!("Field {class_name}.{field_name}") + } + Constant::MethodRef { + class_index, + name_and_type_index, + } => { + let class_name = self.try_get_class(*class_index)?; + let (name_index, descriptor_index) = + self.try_get_name_and_type(*name_and_type_index)?; + let method_name = self.try_get_utf8(*name_index)?; + let method_descriptor = self.try_get_utf8(*descriptor_index)?; + format!("Method {class_name}.{method_name}{method_descriptor}") + } + Constant::InterfaceMethodRef { + class_index, + name_and_type_index, + } => { + let class_name = self.try_get_class(*class_index)?; + let (name_index, descriptor_index) = + self.try_get_name_and_type(*name_and_type_index)?; + let method_name = self.try_get_utf8(*name_index)?; + let method_descriptor = self.try_get_utf8(*descriptor_index)?; + format!("Interface method {class_name}.{method_name}{method_descriptor}") + } + Constant::NameAndType { + name_index, + descriptor_index, + } => { + let name = self.try_get_utf8(*name_index)?; + let descriptor = self.try_get_utf8(*descriptor_index)?; + format!("Name {name}, Descriptor {descriptor}") + } + Constant::MethodHandle { + reference_kind, + reference_index, + } => { + let reference = self.try_get_formatted_string(*reference_index)?; + format!("Method handle {reference_kind} {reference}") + } + Constant::MethodType(name_and_type_index) => { + let (name_index, descriptor_index) = + self.try_get_name_and_type(*name_and_type_index)?; + let method_name = self.try_get_utf8(*name_index)?; + let method_descriptor = self.try_get_utf8(*descriptor_index)?; + format!("Method type{method_name}{method_descriptor}") + } + Constant::Dynamic { + bootstrap_method_attr_index, + name_and_type_index, + } => { + let method = self.try_get_formatted_string(*bootstrap_method_attr_index)?; + let name_and_type = self.try_get_formatted_string(*name_and_type_index)?; + format!("Dynamic Bootstrap {method}, {name_and_type}") + } + Constant::InvokeDynamic { + bootstrap_method_attr_index, + name_and_type_index, + } => { + let method = self.try_get_formatted_string(*bootstrap_method_attr_index)?; + let name_and_type = self.try_get_formatted_string(*name_and_type_index)?; + format!("Dynamic Bootstrap {method}, {name_and_type}") + } + Constant::Module(name_index) => { + format!("Package {}", self.try_get_utf8(*name_index)?) + } + Constant::Package(name_index) => { + format!("Package {}", self.try_get_utf8(*name_index)?) + } + }; + Ok(value) + } } impl Default for ConstantPool { @@ -597,7 +677,7 @@ impl Default for ConstantPool { /// All 8 byte constants (long and double) take up two entries in the constant pool; a placeholder /// is used to facilitate efficient indexed access of constants in the pool. See the JVM spec for: -/// +/// #[derive(Clone, Debug, PartialEq)] enum ConstantEntry { Constant(Constant), @@ -613,7 +693,7 @@ impl fmt::Display for ConstantEntry { } } -#[allow(clippy::module_name_repetitions)] +#[expect(clippy::module_name_repetitions)] pub struct ConstantPoolIterator<'a> { constant_pool: &'a ConstantPool, index: usize, @@ -682,7 +762,7 @@ mod test { use crate::Error::IoError; use std::fmt::Debug; - #[test_log::test] + #[test] fn test_constant_pool_entry_to_string() { assert_eq!( "Integer 42", @@ -691,13 +771,13 @@ mod test { assert_eq!("", ConstantEntry::Placeholder.to_string()); } - #[test_log::test] + #[test] fn test_get_zero_none() { let constant_pool = ConstantPool::default(); assert!(constant_pool.get(0).is_none()); } - #[test_log::test] + #[test] fn test_get() { let mut constant_pool = ConstantPool::default(); assert!(constant_pool.get(1).is_none()); @@ -705,13 +785,13 @@ mod test { assert!(constant_pool.get(1).is_some()); } - #[test_log::test] + #[test] fn test_try_get_zero_error() { let constant_pool = ConstantPool::default(); assert_eq!(Err(InvalidConstantPoolIndex(0)), constant_pool.try_get(0)); } - #[test_log::test] + #[test] fn test_try_get() { let mut constant_pool = ConstantPool::default(); assert!(constant_pool.try_get(1).is_err()); @@ -719,7 +799,7 @@ mod test { assert!(constant_pool.try_get(1).is_ok()); } - #[test_log::test] + #[test] fn test_utf8() { let mut constant_pool = ConstantPool::default(); constant_pool.push(Constant::Utf8("foo".to_string())); @@ -727,7 +807,7 @@ mod test { assert_eq!(1, constant_pool.len()); } - #[test_log::test] + #[test] fn test_integer() { let mut constant_pool = ConstantPool::default(); constant_pool.push(Constant::Integer(42)); @@ -735,7 +815,7 @@ mod test { assert_eq!(1, constant_pool.len()); } - #[test_log::test] + #[test] fn test_long() { let mut constant_pool = ConstantPool::default(); constant_pool.push(Constant::Long(1_234_567_890)); @@ -744,7 +824,7 @@ mod test { assert_eq!(2, constant_pool.len()); } - #[test_log::test] + #[test] fn test_len() { let mut constant_pool = ConstantPool::default(); assert_eq!(0, constant_pool.len()); @@ -752,7 +832,7 @@ mod test { assert_eq!(1, constant_pool.len()); } - #[test_log::test] + #[test] fn test_is_empty() { let mut constant_pool = ConstantPool::default(); assert!(constant_pool.is_empty()); @@ -760,7 +840,7 @@ mod test { assert!(!constant_pool.is_empty()); } - #[test_log::test] + #[test] fn test_iter() { let mut constant_pool = ConstantPool::default(); constant_pool.push(Constant::Utf8("foo".to_string())); @@ -773,7 +853,7 @@ mod test { assert_eq!(None, iter.next()); } - #[test_log::test] + #[test] fn test_into_iter() { let mut constant_pool = ConstantPool::default(); constant_pool.push(Constant::Utf8("foo".to_string())); @@ -782,7 +862,7 @@ mod test { } } - #[test_log::test] + #[test] fn test_double() { let mut constant_pool = ConstantPool::default(); constant_pool.push(Constant::Double(std::f64::consts::PI)); @@ -791,7 +871,7 @@ mod test { assert_eq!(2, constant_pool.len()); } - #[test_log::test] + #[test] fn test_to_string() { let mut constant_pool = ConstantPool::default(); constant_pool.push(Constant::Utf8("foo".to_string())); @@ -801,7 +881,7 @@ mod test { assert_eq!(expected, constant_pool.to_string()); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let mut constant_pool = ConstantPool::default(); let integer_constant = Constant::Integer(42); @@ -850,7 +930,7 @@ mod test { assert!(f(&constant_pool, 2).is_ok()); } - #[test_log::test] + #[test] fn test_add_utf8() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_utf8("foo")?; @@ -862,7 +942,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_utf8() { test_try_get_constant( ConstantPool::try_get_utf8, @@ -870,7 +950,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_add_integer() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_integer(42)?; @@ -879,12 +959,12 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_integer() { test_try_get_constant(ConstantPool::try_get_integer, Constant::Integer(42)); } - #[test_log::test] + #[test] fn test_add_float() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_float(std::f32::consts::PI)?; @@ -896,7 +976,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_float() { test_try_get_constant( ConstantPool::try_get_float, @@ -904,7 +984,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_add_long() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_long(i64::MAX)?; @@ -913,12 +993,12 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_long() { test_try_get_constant(ConstantPool::try_get_long, Constant::Long(i64::MAX)); } - #[test_log::test] + #[test] fn test_add_double() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_double(std::f64::consts::PI)?; @@ -930,7 +1010,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_double() { test_try_get_constant( ConstantPool::try_get_double, @@ -938,7 +1018,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_add_class() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_class("java/lang/Object")?; @@ -947,12 +1027,22 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_class() { test_try_get_constant(ConstantPool::try_get_class, Constant::Class(1)); } - #[test_log::test] + #[test] + fn test_try_get_class_name() -> Result<()> { + let mut constant_pool = ConstantPool::default(); + constant_pool.push(Constant::Utf8("java/lang/Object".to_string())); + constant_pool.push(Constant::Class(1)); + let class_name = constant_pool.try_get_class(2)?; + assert_eq!("java/lang/Object", class_name); + Ok(()) + } + + #[test] fn test_add_string() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_string("foo")?; @@ -961,12 +1051,12 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_string() { test_try_get_constant(ConstantPool::try_get_string, Constant::String(1)); } - #[test_log::test] + #[test] fn test_add_field_ref() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_field_ref(1, "out", "Ljava/io/PrintStream;")?; @@ -981,7 +1071,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_field_ref() { test_try_get_constant_tuple( ConstantPool::try_get_field_ref, @@ -992,7 +1082,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_add_method_ref() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_method_ref(1, "println", "(Ljava/lang/String;)V")?; @@ -1007,7 +1097,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_method_ref() { test_try_get_constant_tuple( ConstantPool::try_get_method_ref, @@ -1018,7 +1108,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_add_interface_method_ref() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = @@ -1034,7 +1124,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_interface_method_ref() { test_try_get_constant_tuple( ConstantPool::try_get_interface_method_ref, @@ -1045,7 +1135,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_add_name_and_type() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_name_and_type("name", "type")?; @@ -1060,7 +1150,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_name_and_type() { test_try_get_constant_tuple( ConstantPool::try_get_name_and_type, @@ -1071,7 +1161,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_add_method_handle() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_method_handle(ReferenceKind::GetField, 1)?; @@ -1086,7 +1176,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_method_handle() { test_try_get_constant_tuple( ConstantPool::try_get_method_handle, @@ -1097,7 +1187,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_add_method_type() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_method_type("()V")?; @@ -1106,12 +1196,12 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_method_type() { test_try_get_constant(ConstantPool::try_get_method_type, Constant::MethodType(1)); } - #[test_log::test] + #[test] fn test_add_dynamic() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_dynamic(1, "name", "type")?; @@ -1126,7 +1216,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_dynamic() { test_try_get_constant_tuple( ConstantPool::try_get_dynamic, @@ -1137,7 +1227,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_add_invoke_dynamic() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_invoke_dynamic(1, "name", "type")?; @@ -1152,7 +1242,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_invoke_dynamic() { test_try_get_constant_tuple( ConstantPool::try_get_invoke_dynamic, @@ -1163,7 +1253,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_add_module() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_module("module")?; @@ -1172,12 +1262,12 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_module() { test_try_get_constant(ConstantPool::try_get_module, Constant::Module(1)); } - #[test_log::test] + #[test] fn test_add_package() -> Result<()> { let mut constant_pool = ConstantPool::default(); let index = constant_pool.add_package("package")?; @@ -1186,12 +1276,12 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_try_get_package() { test_try_get_constant(ConstantPool::try_get_package, Constant::Package(1)); } - #[test_log::test] + #[test] fn test_from_bytes_invalid_tag() { let mut bytes = Cursor::new(vec![0, 0, 10]); assert_eq!( diff --git a/ristretto_classfile/src/display.rs b/ristretto_classfile/src/display.rs index fd55bdb5..fbd1e0fc 100644 --- a/ristretto_classfile/src/display.rs +++ b/ristretto_classfile/src/display.rs @@ -11,7 +11,7 @@ pub fn indent_lines(input: &str, indent: &str) -> String { mod test { use super::*; - #[test_log::test] + #[test] fn test_indent_lines() { let input = "hello\nworld".to_string(); let indent = " "; diff --git a/ristretto_classfile/src/error.rs b/ristretto_classfile/src/error.rs index c4ff6cf5..2cc118b3 100644 --- a/ristretto_classfile/src/error.rs +++ b/ristretto_classfile/src/error.rs @@ -114,7 +114,7 @@ impl From for Error { mod test { use super::*; - #[test_log::test] + #[test] fn test_from_utf8_error() { let invalid_utf8: Vec = vec![0, 159, 146, 150]; let utf8_error = String::from_utf8(invalid_utf8).expect_err("expected FromUtf8Error"); @@ -125,7 +125,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_io_error() { let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"); let error = Error::from(io_error); diff --git a/ristretto_classfile/src/field.rs b/ristretto_classfile/src/field.rs index e323c533..8badf73b 100644 --- a/ristretto_classfile/src/field.rs +++ b/ristretto_classfile/src/field.rs @@ -10,7 +10,7 @@ use std::io::Cursor; /// Field. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub struct Field { pub access_flags: FieldAccessFlags, @@ -90,7 +90,7 @@ mod test { use crate::BaseType; use indoc::indoc; - #[test_log::test] + #[test] fn test_to_string() -> Result<()> { let mut constant_pool = ConstantPool::default(); constant_pool.add_utf8("ConstantValue")?; @@ -117,7 +117,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_field() -> Result<()> { let mut constant_pool = ConstantPool::default(); constant_pool.add_utf8("ConstantValue")?; diff --git a/ristretto_classfile/src/field_access_flags.rs b/ristretto_classfile/src/field_access_flags.rs index 731c289a..1d998a52 100644 --- a/ristretto_classfile/src/field_access_flags.rs +++ b/ristretto_classfile/src/field_access_flags.rs @@ -7,7 +7,7 @@ use std::io::Cursor; bitflags! { /// Field access flags. /// - /// See: + /// See: #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct FieldAccessFlags: u16 { /// Declared public; may be accessed from outside its package. @@ -59,7 +59,6 @@ impl FieldAccessFlags { /// Get the Field Access Flags as a string of code. #[must_use] - #[allow(clippy::trivially_copy_pass_by_ref)] pub fn as_code(&self) -> String { let mut modifiers = Vec::new(); if self.contains(FieldAccessFlags::PUBLIC) { @@ -132,12 +131,12 @@ impl fmt::Display for FieldAccessFlags { mod test { use super::*; - #[test_log::test] + #[test] fn test_default() { assert_eq!(FieldAccessFlags::empty(), FieldAccessFlags::default()); } - #[test_log::test] + #[test] fn test_all_access_flags() { let access_flags: u16 = u16::MAX; let mut bytes = Cursor::new(access_flags.to_be_bytes().to_vec()); @@ -155,7 +154,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_access_flags() -> Result<()> { let access_flags = FieldAccessFlags::PUBLIC | FieldAccessFlags::FINAL; let mut bytes = Vec::new(); @@ -165,7 +164,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_as_code() { assert_eq!("public", FieldAccessFlags::PUBLIC.as_code()); assert_eq!("private", FieldAccessFlags::PRIVATE.as_code()); @@ -181,7 +180,7 @@ mod test { assert_eq!("public static final", access_flags.as_code()); } - #[test_log::test] + #[test] fn test_to_string() { assert_eq!("(0x0001) ACC_PUBLIC", FieldAccessFlags::PUBLIC.to_string()); assert_eq!( diff --git a/ristretto_classfile/src/field_type.rs b/ristretto_classfile/src/field_type.rs index f3562080..243981bc 100644 --- a/ristretto_classfile/src/field_type.rs +++ b/ristretto_classfile/src/field_type.rs @@ -5,7 +5,7 @@ use std::{fmt, io}; /// Implementation of `FieldType`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub enum FieldType { Base(BaseType), @@ -78,9 +78,16 @@ impl FieldType { impl fmt::Display for FieldType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - FieldType::Base(base_type) => write!(f, "{base_type}"), - FieldType::Object(class_name) => write!(f, "L{class_name};"), - FieldType::Array(component_type) => write!(f, "[{component_type}"), + FieldType::Base(BaseType::Boolean) => write!(f, "boolean"), + FieldType::Base(BaseType::Byte) => write!(f, "byte"), + FieldType::Base(BaseType::Char) => write!(f, "char"), + FieldType::Base(BaseType::Double) => write!(f, "double"), + FieldType::Base(BaseType::Float) => write!(f, "float"), + FieldType::Base(BaseType::Int) => write!(f, "int"), + FieldType::Base(BaseType::Long) => write!(f, "long"), + FieldType::Base(BaseType::Short) => write!(f, "short"), + FieldType::Object(class_name) => write!(f, "{class_name}"), + FieldType::Array(component_type) => write!(f, "{component_type}[]"), } } } @@ -90,7 +97,7 @@ mod test { use super::*; use crate::Error::IoError; - #[test_log::test] + #[test] fn test_invalid_code() { assert_eq!( Err(InvalidFieldTypeCode('0')), @@ -107,7 +114,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_base_byte() -> Result<()> { let field_type = FieldType::Base(BaseType::Byte); @@ -115,7 +122,7 @@ mod test { test_field_type(&field_type, "B", 'B') } - #[test_log::test] + #[test] fn test_base_char() -> Result<()> { let field_type = FieldType::Base(BaseType::Char); @@ -123,7 +130,7 @@ mod test { test_field_type(&field_type, "C", 'C') } - #[test_log::test] + #[test] fn test_base_double() -> Result<()> { let field_type = FieldType::Base(BaseType::Double); @@ -131,7 +138,7 @@ mod test { test_field_type(&field_type, "D", 'D') } - #[test_log::test] + #[test] fn test_base_float() -> Result<()> { let field_type = FieldType::Base(BaseType::Float); @@ -139,7 +146,7 @@ mod test { test_field_type(&field_type, "F", 'F') } - #[test_log::test] + #[test] fn test_base_int() -> Result<()> { let field_type = FieldType::Base(BaseType::Int); @@ -147,7 +154,7 @@ mod test { test_field_type(&field_type, "I", 'I') } - #[test_log::test] + #[test] fn test_base_long() -> Result<()> { let field_type = FieldType::Base(BaseType::Long); @@ -155,7 +162,7 @@ mod test { test_field_type(&field_type, "J", 'J') } - #[test_log::test] + #[test] fn test_base_short() -> Result<()> { let field_type = FieldType::Base(BaseType::Short); @@ -163,7 +170,7 @@ mod test { test_field_type(&field_type, "S", 'S') } - #[test_log::test] + #[test] fn test_base_boolean() -> Result<()> { let field_type = FieldType::Base(BaseType::Boolean); @@ -171,15 +178,15 @@ mod test { test_field_type(&field_type, "Z", 'Z') } - #[test_log::test] + #[test] fn test_object() -> Result<()> { let field_type = FieldType::Object("Foo".to_string()); - assert_eq!("LFoo;", field_type.to_string()); + assert_eq!("Foo", field_type.to_string()); test_field_type(&field_type, "LFoo;", 'L') } - #[test_log::test] + #[test] fn test_object_no_semicolon_invalid() { let descriptor = "Lfoo".to_string(); assert_eq!( @@ -188,7 +195,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_object_no_class_name_invalid() { let descriptor = "L;".to_string(); assert_eq!( @@ -197,16 +204,16 @@ mod test { ); } - #[test_log::test] + #[test] fn test_array() -> Result<()> { let component_type = FieldType::Base(BaseType::Int); let field_type = FieldType::Array(component_type.into()); - assert_eq!("[int", field_type.to_string()); + assert_eq!("int[]", field_type.to_string()); test_field_type(&field_type, "[I", '[') } - #[test_log::test] + #[test] fn test_parse_invalid() { let descriptor = "L".to_string(); assert_eq!( diff --git a/ristretto_classfile/src/lib.rs b/ristretto_classfile/src/lib.rs index 59167370..59e5039d 100644 --- a/ristretto_classfile/src/lib.rs +++ b/ristretto_classfile/src/lib.rs @@ -7,7 +7,7 @@ //! //! ## Getting Started //! -//! Implementation of the [JVM Class File Format](https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-4.html) that +//! Implementation of the [JVM Class File Format](https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-4.html) that //! is used to read, write and verify Java classes. //! //! Supports reading and writing class files for any version of Java version up to 24. Verification @@ -33,9 +33,10 @@ //! //! ## Safety //! -//! These crates use `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% safe Rust. +//! This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% safe Rust. #![forbid(unsafe_code)] +#![forbid(clippy::allow_attributes)] #![allow(dead_code)] #![deny(clippy::pedantic)] #![deny(clippy::unwrap_in_result)] diff --git a/ristretto_classfile/src/method.rs b/ristretto_classfile/src/method.rs index 6878b4b5..c47e7213 100644 --- a/ristretto_classfile/src/method.rs +++ b/ristretto_classfile/src/method.rs @@ -9,7 +9,7 @@ use std::io::Cursor; /// Method. /// -/// See: +/// See: #[derive(Clone, Debug, Default, PartialEq)] pub struct Method { pub access_flags: MethodAccessFlags, @@ -81,7 +81,7 @@ mod test { use crate::attributes::Attribute; use indoc::indoc; - #[test_log::test] + #[test] fn test_to_string() { let attribute1 = Attribute::ConstantValue { name_index: 1, @@ -108,7 +108,7 @@ mod test { assert_eq!(expected, method.to_string()); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let mut constant_pool = ConstantPool::default(); constant_pool.add_utf8("ConstantValue")?; diff --git a/ristretto_classfile/src/method_access_flags.rs b/ristretto_classfile/src/method_access_flags.rs index 1f80a177..3bb95c23 100644 --- a/ristretto_classfile/src/method_access_flags.rs +++ b/ristretto_classfile/src/method_access_flags.rs @@ -7,7 +7,7 @@ use std::io::Cursor; bitflags! { /// Method access flags. /// - /// See: + /// See: #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct MethodAccessFlags: u16 { /// Declared public; may be accessed from outside its package. @@ -65,7 +65,6 @@ impl MethodAccessFlags { /// Get the Method Access Flags as a string of code. #[must_use] - #[allow(clippy::trivially_copy_pass_by_ref)] pub fn as_code(&self) -> String { let mut modifiers = Vec::new(); if self.contains(MethodAccessFlags::PUBLIC) { @@ -144,12 +143,12 @@ impl fmt::Display for MethodAccessFlags { mod test { use super::*; - #[test_log::test] + #[test] fn test_default() { assert_eq!(MethodAccessFlags::empty(), MethodAccessFlags::default()); } - #[test_log::test] + #[test] fn test_all_access_flags() { let access_flags: u16 = u16::MAX; let mut bytes = Cursor::new(access_flags.to_be_bytes().to_vec()); @@ -170,7 +169,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_access_flags() -> Result<()> { let access_flags = MethodAccessFlags::PUBLIC | MethodAccessFlags::FINAL; let mut bytes = Vec::new(); @@ -180,7 +179,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_as_code() { assert_eq!("public", MethodAccessFlags::PUBLIC.as_code()); assert_eq!("private", MethodAccessFlags::PRIVATE.as_code()); @@ -200,7 +199,7 @@ mod test { assert_eq!("public static final", access_flags.as_code()); } - #[test_log::test] + #[test] fn test_to_string() { assert_eq!("(0x0001) ACC_PUBLIC", MethodAccessFlags::PUBLIC.to_string()); assert_eq!( diff --git a/ristretto_classfile/src/mutf8.rs b/ristretto_classfile/src/mutf8.rs index 5232e9b7..2b775c4a 100644 --- a/ristretto_classfile/src/mutf8.rs +++ b/ristretto_classfile/src/mutf8.rs @@ -1,7 +1,7 @@ //! Functions to convert a Rust string to a Java Modified UTF-8 byte array //! and vice versa. //! -//! See: +//! See: use crate::Error::FromUtf8Error; use crate::Result; @@ -43,8 +43,7 @@ pub fn to_bytes(data: &str) -> Result> { /// /// # Errors /// Should not occur; reserved for future use. -#[allow(clippy::similar_names)] -#[allow(clippy::unnecessary_wraps)] +#[expect(clippy::similar_names)] pub fn from_bytes(bytes: &[u8]) -> Result { let mut decoded = String::with_capacity(bytes.len()); let mut i = 0; @@ -105,7 +104,7 @@ mod tests { /// Test all valid UTF-8 characters, the only two invalid characters are U+D800 and U+DFFF /// /// See: - #[test_log::test] + #[test] fn test_all_utf8_chars() -> Result<()> { for i in 0..=0x0010_FFFF { if let Some(ch) = char::from_u32(i) { @@ -127,13 +126,13 @@ mod tests { /// Test the encoding of CESU-8 character from `X11GB18030_0$Encoder.class` Java 8 rt.jar /// that fails with CESU-8 implementations. - #[test_log::test] + #[test] fn test_utf8_encoding() { let bytes = &[237, 162, 162]; assert!(from_bytes(bytes).is_ok()); } - #[test_log::test] + #[test] fn test_to_bytes() -> Result<()> { let data = "\u{0000}\u{007F}\u{0080}\u{07FF}\u{0800}\u{FFFF}\u{10000}"; let expected = vec![ @@ -149,7 +148,7 @@ mod tests { Ok(()) } - #[test_log::test] + #[test] fn test_from_bytes() -> Result<()> { let bytes = &[ 0xC0, 0x80, // '\u{0000}' @@ -166,7 +165,7 @@ mod tests { Ok(()) } - #[test_log::test] + #[test] fn test_from_bytes_invalid() { assert!(from_bytes(&[0x59, 0xd9]).is_err()); assert!(from_bytes(&[0x56, 0xe7]).is_err()); diff --git a/ristretto_classfile/src/reference_kind.rs b/ristretto_classfile/src/reference_kind.rs index deea53c8..cdd318dc 100644 --- a/ristretto_classfile/src/reference_kind.rs +++ b/ristretto_classfile/src/reference_kind.rs @@ -6,7 +6,7 @@ use std::io::Cursor; /// Implementation of the `ReferenceKind`. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq)] pub enum ReferenceKind { GetField, @@ -117,7 +117,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_get_field() -> Result<()> { let reference_kind = ReferenceKind::GetField; @@ -125,7 +125,7 @@ mod test { test_reference_kind(&reference_kind, 1) } - #[test_log::test] + #[test] fn test_get_static() -> Result<()> { let reference_kind = ReferenceKind::GetStatic; @@ -133,7 +133,7 @@ mod test { test_reference_kind(&reference_kind, 2) } - #[test_log::test] + #[test] fn test_put_field() -> Result<()> { let reference_kind = ReferenceKind::PutField; @@ -141,7 +141,7 @@ mod test { test_reference_kind(&reference_kind, 3) } - #[test_log::test] + #[test] fn test_put_static() -> Result<()> { let reference_kind = ReferenceKind::PutStatic; @@ -149,7 +149,7 @@ mod test { test_reference_kind(&reference_kind, 4) } - #[test_log::test] + #[test] fn test_invoke_virtual() -> Result<()> { let reference_kind = ReferenceKind::InvokeVirtual; @@ -157,7 +157,7 @@ mod test { test_reference_kind(&reference_kind, 5) } - #[test_log::test] + #[test] fn test_invoke_static() -> Result<()> { let reference_kind = ReferenceKind::InvokeStatic; @@ -165,7 +165,7 @@ mod test { test_reference_kind(&reference_kind, 6) } - #[test_log::test] + #[test] fn test_invoke_special() -> Result<()> { let reference_kind = ReferenceKind::InvokeSpecial; @@ -173,7 +173,7 @@ mod test { test_reference_kind(&reference_kind, 7) } - #[test_log::test] + #[test] fn test_new_invoke_special() -> Result<()> { let reference_kind = ReferenceKind::NewInvokeSpecial; @@ -181,7 +181,7 @@ mod test { test_reference_kind(&reference_kind, 8) } - #[test_log::test] + #[test] fn test_invoke_interface() -> Result<()> { let reference_kind = ReferenceKind::InvokeInterface; @@ -189,7 +189,7 @@ mod test { test_reference_kind(&reference_kind, 9) } - #[test_log::test] + #[test] fn test_from_bytes_invalid_reference_kind() { let mut bytes = Cursor::new(vec![0]); assert_eq!( diff --git a/ristretto_classfile/src/verifiers/class_access_flags.rs b/ristretto_classfile/src/verifiers/class_access_flags.rs index 59b968f2..fe4c880d 100644 --- a/ristretto_classfile/src/verifiers/class_access_flags.rs +++ b/ristretto_classfile/src/verifiers/class_access_flags.rs @@ -44,7 +44,7 @@ mod test { use crate::constant_pool::ConstantPool; use std::io::Cursor; - #[test_log::test] + #[test] fn test_verify_success() -> Result<()> { let class_bytes = include_bytes!("../../../classes/Simple.class"); let expected_bytes = class_bytes.to_vec(); @@ -71,45 +71,45 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_verify_annotation_not_interface_error() -> Result<()> { test_verify_error(ClassAccessFlags::ANNOTATION) } - #[test_log::test] + #[test] fn test_verify_interface_not_abstract_error() -> Result<()> { test_verify_error(ClassAccessFlags::INTERFACE) } - #[test_log::test] + #[test] fn test_verify_interface_is_final_error() -> Result<()> { test_verify_error( ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT | ClassAccessFlags::FINAL, ) } - #[test_log::test] + #[test] fn test_verify_interface_is_super_error() -> Result<()> { test_verify_error( ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT | ClassAccessFlags::SUPER, ) } - #[test_log::test] + #[test] fn test_verify_interface_is_enum_error() -> Result<()> { test_verify_error( ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT | ClassAccessFlags::ENUM, ) } - #[test_log::test] + #[test] fn test_verify_interface_is_module_error() -> Result<()> { test_verify_error( ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT | ClassAccessFlags::MODULE, ) } - #[test_log::test] + #[test] fn test_verify_not_abstract_and_finale_error() -> Result<()> { test_verify_error(ClassAccessFlags::ABSTRACT | ClassAccessFlags::FINAL) } diff --git a/ristretto_classfile/src/verifiers/constant_pool.rs b/ristretto_classfile/src/verifiers/constant_pool.rs index 20d74022..3c8f8c06 100644 --- a/ristretto_classfile/src/verifiers/constant_pool.rs +++ b/ristretto_classfile/src/verifiers/constant_pool.rs @@ -32,7 +32,7 @@ fn verify_version_constants(class_file: &ClassFile) -> Result<()> { } /// Verify the `ClassFile` `ConstantPool` indexes. -#[allow(clippy::too_many_lines)] +#[expect(clippy::too_many_lines)] fn verify_constant_indexes(class_file: &ClassFile) -> Result<()> { let constant_pool = &class_file.constant_pool; for (index, constant) in constant_pool.iter().enumerate() { @@ -228,7 +228,7 @@ mod test { Ok(0) } - #[test_log::test] + #[test] fn test_verify() -> Result<()> { let class_bytes = include_bytes!("../../../classes/Simple.class"); let expected_bytes = class_bytes.to_vec(); @@ -252,12 +252,12 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_version_constants_method_type() -> Result<()> { test_version_constants_error(Version::Java6 { minor: 0 }, Constant::MethodType(1)) } - #[test_log::test] + #[test] fn test_version_constants_dynamic() -> Result<()> { test_version_constants_error( Version::Java10 { minor: 0 }, @@ -268,7 +268,7 @@ mod test { ) } - #[test_log::test] + #[test] fn test_version_constants_invoke_dynamic() -> Result<()> { test_version_constants_error( Version::Java6 { minor: 0 }, @@ -279,12 +279,12 @@ mod test { ) } - #[test_log::test] + #[test] fn test_version_constants_module() -> Result<()> { test_version_constants_error(Version::Java8 { minor: 0 }, Constant::Module(1)) } - #[test_log::test] + #[test] fn test_version_constants_package() -> Result<()> { test_version_constants_error(Version::Java8 { minor: 0 }, Constant::Package(1)) } @@ -331,27 +331,27 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_indexes_class_errors() -> Result<()> { test_indexes_utf8_index_errors(&Constant::Class(u16::MAX)) } - #[test_log::test] + #[test] fn test_indexes_module_errors() -> Result<()> { test_indexes_utf8_index_errors(&Constant::Module(u16::MAX)) } - #[test_log::test] + #[test] fn test_indexes_package_errors() -> Result<()> { test_indexes_utf8_index_errors(&Constant::Package(u16::MAX)) } - #[test_log::test] + #[test] fn test_indexes_string_errors() -> Result<()> { test_indexes_utf8_index_errors(&Constant::String(u16::MAX)) } - #[test_log::test] + #[test] fn test_indexes_method_type_errors() -> Result<()> { test_indexes_utf8_index_errors(&Constant::MethodType(u16::MAX)) } @@ -402,7 +402,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_indexes_field_ref_errors() -> Result<()> { test_indexes_ref_errors(&Constant::FieldRef { class_index: 0, @@ -410,7 +410,7 @@ mod test { }) } - #[test_log::test] + #[test] fn test_indexes_method_ref_errors() -> Result<()> { test_indexes_ref_errors(&Constant::MethodRef { class_index: 0, @@ -418,7 +418,7 @@ mod test { }) } - #[test_log::test] + #[test] fn test_indexes_interface_method_ref_errors() -> Result<()> { test_indexes_ref_errors(&Constant::InterfaceMethodRef { class_index: 0, @@ -426,7 +426,7 @@ mod test { }) } - #[test_log::test] + #[test] fn test_indexes_name_and_type_errors() -> Result<()> { let class_file = &mut get_class_file()?; let integer_index = get_integer_index(class_file)?; @@ -463,7 +463,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_indexes_method_handle_errors() -> Result<()> { let class_file = &mut get_class_file()?; let integer_index = get_integer_index(class_file)?; @@ -509,7 +509,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_indexes_dynamic_errors() -> Result<()> { let class_file = &mut get_class_file()?; let bootstrap_method_index = get_bootstrap_methods_index(class_file)?; @@ -545,7 +545,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_indexes_invoke_dynamic_errors() -> Result<()> { let class_file = &mut get_class_file()?; let bootstrap_method_index = get_bootstrap_methods_index(class_file)?; diff --git a/ristretto_classfile/src/verifiers/field_access_flags.rs b/ristretto_classfile/src/verifiers/field_access_flags.rs index 5c7ef5f7..12131968 100644 --- a/ristretto_classfile/src/verifiers/field_access_flags.rs +++ b/ristretto_classfile/src/verifiers/field_access_flags.rs @@ -42,7 +42,7 @@ mod test { use crate::class_file::ClassFile; use crate::{BaseType, FieldType}; - #[test_log::test] + #[test] fn test_interface_success() { let class_file = ClassFile { access_flags: ClassAccessFlags::INTERFACE, @@ -77,7 +77,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_class_invalid_flag() { test_invalid_flag_error( FieldAccessFlags::PUBLIC | FieldAccessFlags::PROTECTED | FieldAccessFlags::PRIVATE, @@ -87,7 +87,7 @@ mod test { test_invalid_flag_error(FieldAccessFlags::PROTECTED | FieldAccessFlags::PRIVATE); } - #[test_log::test] + #[test] fn test_interface_invalid_signature() { let class_file = ClassFile { access_flags: ClassAccessFlags::INTERFACE, @@ -123,7 +123,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_interface_invalid_flag() { test_interface_invalid_flag_error(FieldAccessFlags::PRIVATE); test_interface_invalid_flag_error(FieldAccessFlags::PROTECTED); @@ -132,7 +132,7 @@ mod test { test_interface_invalid_flag_error(FieldAccessFlags::ENUM); } - #[test_log::test] + #[test] fn test_class_success() { let class_file = ClassFile::default(); let field = Field { @@ -146,7 +146,7 @@ mod test { assert_eq!(Ok(()), verify(&class_file, &field)); } - #[test_log::test] + #[test] fn test_class_final_and_volatile_error() { let class_file = ClassFile::default(); let field = Field { diff --git a/ristretto_classfile/src/verifiers/fields.rs b/ristretto_classfile/src/verifiers/fields.rs index 26da3f84..3d7cbd5d 100644 --- a/ristretto_classfile/src/verifiers/fields.rs +++ b/ristretto_classfile/src/verifiers/fields.rs @@ -57,20 +57,20 @@ mod test { (class_file, field) } - #[test_log::test] + #[test] fn test_success() { let (class_file, _field) = get_test_class_file_and_field(); assert_eq!(Ok(()), verify(&class_file)); } - #[test_log::test] + #[test] fn test_invalid_access_flag_error() { let (class_file, mut field) = get_test_class_file_and_field(); field.access_flags = FieldAccessFlags::FINAL | FieldAccessFlags::VOLATILE; assert_eq!(Ok(()), verify(&class_file)); } - #[test_log::test] + #[test] fn test_invalid_name_index() { let (class_file, mut field) = get_test_class_file_and_field(); field.name_index = u16::MAX; @@ -80,7 +80,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_invalid_name_index_type() -> Result<()> { let (mut class_file, mut field) = get_test_class_file_and_field(); let constant_pool = &mut class_file.constant_pool; @@ -93,7 +93,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_invalid_descriptor_index() { let (class_file, mut field) = get_test_class_file_and_field(); field.descriptor_index = u16::MAX; @@ -103,7 +103,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_invalid_descriptor_index_type() -> Result<()> { let (mut class_file, mut field) = get_test_class_file_and_field(); let constant_pool = &mut class_file.constant_pool; diff --git a/ristretto_classfile/src/verifiers/interfaces.rs b/ristretto_classfile/src/verifiers/interfaces.rs index b8994c50..654652a1 100644 --- a/ristretto_classfile/src/verifiers/interfaces.rs +++ b/ristretto_classfile/src/verifiers/interfaces.rs @@ -20,7 +20,7 @@ pub fn verify(class_file: &ClassFile) -> Result<()> { mod test { use super::*; - #[test_log::test] + #[test] fn test_verify_success() -> Result<()> { let mut class_file = ClassFile::default(); let constant_pool = &mut class_file.constant_pool; @@ -31,7 +31,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_verify_invalid_index() { let mut class_file = ClassFile::default(); let index = 1; @@ -40,7 +40,7 @@ mod test { assert_eq!(Err(InvalidConstantPoolIndex(index)), verify(&class_file)); } - #[test_log::test] + #[test] fn test_verify_invalid_index_type() -> Result<()> { let mut class_file = ClassFile::default(); let constant_pool = &mut class_file.constant_pool; diff --git a/ristretto_classfile/src/verifiers/method_access_flags.rs b/ristretto_classfile/src/verifiers/method_access_flags.rs index f778aa4e..aa0cad54 100644 --- a/ristretto_classfile/src/verifiers/method_access_flags.rs +++ b/ristretto_classfile/src/verifiers/method_access_flags.rs @@ -56,7 +56,7 @@ mod test { use super::*; use crate::class_file::ClassFile; - #[test_log::test] + #[test] fn test_method_success() { let class_file = ClassFile::default(); let method = Method::default(); @@ -77,7 +77,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_visibility_errors() { test_method_flag_error( MethodAccessFlags::PUBLIC | MethodAccessFlags::PROTECTED | MethodAccessFlags::PRIVATE, @@ -103,7 +103,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_interface_access_flag_errors() { test_interface_method_error(MethodAccessFlags::PROTECTED); test_interface_method_error(MethodAccessFlags::FINAL); @@ -111,7 +111,7 @@ mod test { test_interface_method_error(MethodAccessFlags::NATIVE); } - #[test_log::test] + #[test] fn test_abstract_access_flag_errors() { test_method_flag_error(MethodAccessFlags::ABSTRACT | MethodAccessFlags::PRIVATE); test_method_flag_error(MethodAccessFlags::ABSTRACT | MethodAccessFlags::STATIC); @@ -137,7 +137,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_strict_version_errors() { test_strict_version_error(Version::Java1_2 { minor: 0 }); test_strict_version_error(Version::Java1_3 { minor: 0 }); diff --git a/ristretto_classfile/src/verifiers/methods.rs b/ristretto_classfile/src/verifiers/methods.rs index c619c4a3..bfe5cf8f 100644 --- a/ristretto_classfile/src/verifiers/methods.rs +++ b/ristretto_classfile/src/verifiers/methods.rs @@ -13,7 +13,7 @@ pub fn verify(class_file: &ClassFile) -> Result<()> { verify_descriptor_index(class_file, method)?; // TODO: verify instructions match method return type: - // See: https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-6.html#jvms-6.5.ireturn + // See: https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-6.html#jvms-6.5.ireturn // TODO: verify attributes } @@ -59,20 +59,20 @@ mod test { (class_file, method) } - #[test_log::test] + #[test] fn test_success() { let (class_file, _method) = get_test_class_file_and_method(); assert_eq!(Ok(()), crate::verifiers::methods::verify(&class_file)); } - #[test_log::test] + #[test] fn test_invalid_access_flag_error() { let (class_file, mut method) = get_test_class_file_and_method(); method.access_flags = MethodAccessFlags::FINAL | MethodAccessFlags::SYNCHRONIZED; assert_eq!(Ok(()), crate::verifiers::methods::verify(&class_file)); } - #[test_log::test] + #[test] fn test_invalid_name_index() { let (class_file, mut method) = get_test_class_file_and_method(); method.name_index = u16::MAX; @@ -82,7 +82,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_invalid_name_index_type() -> Result<()> { let (mut class_file, mut method) = get_test_class_file_and_method(); let constant_pool = &mut class_file.constant_pool; @@ -95,7 +95,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_invalid_descriptor_index() { let (class_file, mut method) = get_test_class_file_and_method(); method.descriptor_index = u16::MAX; @@ -105,7 +105,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_invalid_descriptor_index_type() -> Result<()> { let (mut class_file, mut method) = get_test_class_file_and_method(); let constant_pool = &mut class_file.constant_pool; diff --git a/ristretto_classfile/src/verifiers/verifier.rs b/ristretto_classfile/src/verifiers/verifier.rs index 429ed019..19818817 100644 --- a/ristretto_classfile/src/verifiers/verifier.rs +++ b/ristretto_classfile/src/verifiers/verifier.rs @@ -53,7 +53,7 @@ mod test { use super::*; use crate::class_file::ClassFile; - #[test_log::test] + #[test] fn test_verify_this_class_success() -> Result<()> { let mut class_file = ClassFile::default(); let constant_pool = &mut class_file.constant_pool; @@ -63,7 +63,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_verify_this_class_invalid_index() { let class_file = ClassFile { this_class: u16::MAX, @@ -75,7 +75,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_verify_this_class_invalid_index_type() -> Result<()> { let mut class_file = ClassFile::default(); let constant_pool = &mut class_file.constant_pool; @@ -89,7 +89,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_verify_super_class_success() -> Result<()> { let mut class_file = ClassFile::default(); let constant_pool = &mut class_file.constant_pool; @@ -99,7 +99,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_verify_super_class_zero() { let class_file = ClassFile { super_class: 0, @@ -108,7 +108,7 @@ mod test { assert_eq!(Ok(()), verify_super_class(&class_file)); } - #[test_log::test] + #[test] fn test_verify_super_class_invalid_index() { let class_file = ClassFile { super_class: u16::MAX, @@ -120,7 +120,7 @@ mod test { ); } - #[test_log::test] + #[test] fn test_verify_super_class_invalid_index_type() -> Result<()> { let mut class_file = ClassFile::default(); let constant_pool = &mut class_file.constant_pool; @@ -134,7 +134,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_verify_super_class_interface_invalid() { let class_file = ClassFile { access_flags: ClassAccessFlags::INTERFACE, diff --git a/ristretto_classfile/src/version.rs b/ristretto_classfile/src/version.rs index 2be0b923..0fab1068 100644 --- a/ristretto_classfile/src/version.rs +++ b/ristretto_classfile/src/version.rs @@ -8,7 +8,7 @@ const JAVA_PREVIEW_MINOR_VERSION: u16 = 65535; /// Implementation of Version based on `ClassFile` format for major/minor versions. /// -/// See: +/// See: #[derive(Clone, Debug, PartialEq, PartialOrd)] pub enum Version { Java1_0_2 { minor: u16 }, @@ -142,6 +142,12 @@ impl Version { } } + /// Returns the major version for Java (e.g. 8 for Java 8). + #[must_use] + pub fn java(&self) -> u16 { + self.major() - 44 + } + /// Returns true if the current major version supports the given version. #[must_use] pub fn supports(self, version: &Version) -> bool { @@ -192,7 +198,7 @@ impl Version { impl Default for Version { fn default() -> Self { - Version::Java21 { minor: 0 } + Version::Java1_0_2 { minor: 0 } } } @@ -236,7 +242,7 @@ mod test { const MIN_MAJOR: u16 = 45; const MAX_MAJOR: u16 = 68; - #[test_log::test] + #[test] fn all_known_versions() -> Result<()> { let versions = [ Version::Java1_0_2 { minor: 0 }, @@ -277,12 +283,13 @@ mod test { assert!(version.to_string().starts_with("Java ")); assert_eq!(major, MIN_MAJOR + index); assert_eq!(version.minor(), 0); + assert_eq!(version.java(), version.major() - 44); } Ok(()) } - #[test_log::test] + #[test] fn test_from() -> Result<()> { for major in MIN_MAJOR..=MAX_MAJOR { // Test with minor version 0 @@ -295,7 +302,7 @@ mod test { Ok(()) } - #[test_log::test] + #[test] fn test_from_invalid_version() { assert_eq!( Err(InvalidVersion { @@ -313,26 +320,26 @@ mod test { ); } - #[test_log::test] + #[test] fn test_major() { let version = Version::Java21 { minor: 0 }; assert_eq!(version.major(), 65); } - #[test_log::test] + #[test] fn test_minor() { let minor = 3; let version = Version::Java11 { minor }; assert_eq!(version.minor(), minor); } - #[test_log::test] + #[test] fn test_supports() { assert!(Version::Java11 { minor: 0 }.supports(&Version::Java5_0 { minor: 0 })); assert!(!Version::Java5_0 { minor: 0 }.supports(&Version::Java11 { minor: 0 })); } - #[test_log::test] + #[test] fn test_is_preview() { assert!(!Version::Java11 { minor: 0 }.is_preview()); assert!(Version::Java21 { @@ -341,13 +348,13 @@ mod test { .is_preview()); } - #[test_log::test] + #[test] fn test_default() { let version = Version::default(); - assert_eq!(version, Version::Java21 { minor: 0 }); + assert_eq!(version, Version::Java1_0_2 { minor: 0 }); } - #[test_log::test] + #[test] fn test_serialization() -> Result<()> { let version = Version::Java21 { minor: JAVA_PREVIEW_MINOR_VERSION, diff --git a/ristretto_classfile/tests/class_tests.rs b/ristretto_classfile/tests/class_tests.rs index 8b521042..37d40fc8 100644 --- a/ristretto_classfile/tests/class_tests.rs +++ b/ristretto_classfile/tests/class_tests.rs @@ -11,27 +11,27 @@ pub fn test_class(class_bytes: &[u8]) -> Result<()> { Ok(()) } -#[test_log::test] +#[test] pub fn test_annotations() -> Result<()> { test_class(include_bytes!("../../classes/Annotations.class")) } -#[test_log::test] +#[test] pub fn test_constants() -> Result<()> { test_class(include_bytes!("../../classes/Constants.class")) } -#[test_log::test] +#[test] pub fn test_expressions() -> Result<()> { test_class(include_bytes!("../../classes/Expressions.class")) } -#[test_log::test] +#[test] pub fn test_minimum() -> Result<()> { test_class(include_bytes!("../../classes/Minimum.class")) } -#[test_log::test] +#[test] pub fn test_simple() -> Result<()> { test_class(include_bytes!("../../classes/Simple.class")) } diff --git a/ristretto_classfile/tests/jar_tests.rs b/ristretto_classfile/tests/jar_tests.rs index 15789b3c..93d60779 100644 --- a/ristretto_classfile/tests/jar_tests.rs +++ b/ristretto_classfile/tests/jar_tests.rs @@ -1,161 +1,145 @@ mod utilities; -use reqwest::Client; +use reqwest::blocking::Client; use std::error::Error; -async fn verify_jar(url: &str) -> Result<(), Box> { +fn verify_jar(url: &str) -> Result<(), Box> { let client = Client::new(); - let jar_bytes = client.get(url).send().await?.bytes().await?.to_vec(); + let jar_bytes = client.get(url).send()?.bytes()?.to_vec(); utilities::jar::verify(jar_bytes)?; Ok(()) } -#[tokio::test] -async fn test_apache_httpclient() -> Result<(), Box> { - verify_jar("https://repo1.maven.org/maven2/org/apache/httpcomponents/client5/httpclient5/5.3.1/httpclient5-5.3.1.jar").await +#[test] +fn test_apache_httpclient() -> Result<(), Box> { + verify_jar("https://repo1.maven.org/maven2/org/apache/httpcomponents/client5/httpclient5/5.3.1/httpclient5-5.3.1.jar") } -#[tokio::test] -async fn test_aws_mysql() -> Result<(), Box> { - verify_jar("https://repo1.maven.org/maven2/software/aws/rds/aws-mysql-jdbc/1.1.15/aws-mysql-jdbc-1.1.15.jar").await +#[test] +fn test_aws_mysql() -> Result<(), Box> { + verify_jar("https://repo1.maven.org/maven2/software/aws/rds/aws-mysql-jdbc/1.1.15/aws-mysql-jdbc-1.1.15.jar") } -#[tokio::test] -async fn test_cassandra() -> Result<(), Box> { - verify_jar("https://repo1.maven.org/maven2/org/apache/cassandra/cassandra-all/4.1.5/cassandra-all-4.1.5.jar").await +#[test] +fn test_cassandra() -> Result<(), Box> { + verify_jar("https://repo1.maven.org/maven2/org/apache/cassandra/cassandra-all/4.1.5/cassandra-all-4.1.5.jar") } -#[tokio::test] -async fn test_clojure() -> Result<(), Box> { - verify_jar("https://repo1.maven.org/maven2/org/clojure/clojure/1.11.3/clojure-1.11.3.jar").await +#[test] +fn test_clojure() -> Result<(), Box> { + verify_jar("https://repo1.maven.org/maven2/org/clojure/clojure/1.11.3/clojure-1.11.3.jar") } -#[tokio::test] -async fn test_commons_io() -> Result<(), Box> { +#[test] +fn test_commons_io() -> Result<(), Box> { verify_jar("https://repo1.maven.org/maven2/commons-io/commons-io/2.16.1/commons-io-2.16.1.jar") - .await } -#[tokio::test] -async fn test_commons_lang() -> Result<(), Box> { - verify_jar("https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.14.0/commons-lang3-3.14.0.jar").await +#[test] +fn test_commons_lang() -> Result<(), Box> { + verify_jar("https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.14.0/commons-lang3-3.14.0.jar") } -#[tokio::test] -async fn test_derby() -> Result<(), Box> { +#[test] +fn test_derby() -> Result<(), Box> { verify_jar( "https://repo1.maven.org/maven2/org/apache/derby/derby/10.17.1.0/derby-10.17.1.0.jar", ) - .await } -#[tokio::test] -async fn test_gson() -> Result<(), Box> { +#[test] +fn test_gson() -> Result<(), Box> { verify_jar("https://repo1.maven.org/maven2/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar") - .await } -#[tokio::test] -async fn test_jakarta_servlet() -> Result<(), Box> { +#[test] +fn test_jakarta_servlet() -> Result<(), Box> { verify_jar("https://repo1.maven.org/maven2/jakarta/servlet/jakarta.servlet-api/6.1.0/jakarta.servlet-api-6.1.0.jar") - .await } -#[tokio::test] -async fn test_jackson_databind() -> Result<(), Box> { +#[test] +fn test_jackson_databind() -> Result<(), Box> { verify_jar("https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.17.1/jackson-databind-2.17.1.jar") - .await } -#[tokio::test] -async fn test_jtds() -> Result<(), Box> { - verify_jar("https://repo1.maven.org/maven2/net/sourceforge/jtds/jtds/1.2.2/jtds-1.2.2.jar") - .await?; +#[test] +fn test_jtds() -> Result<(), Box> { + verify_jar("https://repo1.maven.org/maven2/net/sourceforge/jtds/jtds/1.2.2/jtds-1.2.2.jar")?; verify_jar("https://repo1.maven.org/maven2/net/sourceforge/jtds/jtds/1.3.1/jtds-1.3.1.jar") - .await } -#[tokio::test] -async fn test_junit() -> Result<(), Box> { +#[test] +fn test_junit() -> Result<(), Box> { verify_jar("https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-api/5.10.2/junit-jupiter-api-5.10.2.jar") - .await } -#[tokio::test] -async fn test_kotlin() -> Result<(), Box> { +#[test] +fn test_kotlin() -> Result<(), Box> { verify_jar("https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/2.0.0/kotlin-stdlib-2.0.0.jar") - .await } -#[tokio::test] -async fn test_log4j() -> Result<(), Box> { +#[test] +fn test_log4j() -> Result<(), Box> { verify_jar("https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.23.1/log4j-core-2.23.1.jar") - .await } -#[tokio::test] -async fn test_lombok() -> Result<(), Box> { +#[test] +fn test_lombok() -> Result<(), Box> { verify_jar("https://repo1.maven.org/maven2/org/projectlombok/lombok/1.18.32/lombok-1.18.32.jar") - .await } -#[tokio::test] -async fn test_mariadb() -> Result<(), Box> { - verify_jar("https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/3.4.0/mariadb-java-client-3.4.0.jar").await +#[test] +fn test_mariadb() -> Result<(), Box> { + verify_jar("https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/3.4.0/mariadb-java-client-3.4.0.jar") } -#[tokio::test] -async fn test_mockito() -> Result<(), Box> { +#[test] +fn test_mockito() -> Result<(), Box> { verify_jar( "https://repo1.maven.org/maven2/org/mockito/mockito-core/5.12.0/mockito-core-5.12.0.jar", ) - .await } -#[tokio::test] -async fn test_mysql() -> Result<(), Box> { - verify_jar("https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.4.0/mysql-connector-j-8.4.0.jar").await +#[test] +fn test_mysql() -> Result<(), Box> { + verify_jar("https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.4.0/mysql-connector-j-8.4.0.jar") } -#[tokio::test] -async fn test_ojdbc() -> Result<(), Box> { - verify_jar("https://repo1.maven.org/maven2/com/oracle/database/jdbc/ojdbc11/23.4.0.24.05/ojdbc11-23.4.0.24.05.jar").await +#[test] +fn test_ojdbc() -> Result<(), Box> { + verify_jar("https://repo1.maven.org/maven2/com/oracle/database/jdbc/ojdbc11/23.4.0.24.05/ojdbc11-23.4.0.24.05.jar") } -#[tokio::test] -async fn test_postgresql() -> Result<(), Box> { +#[test] +fn test_postgresql() -> Result<(), Box> { verify_jar( "https://repo1.maven.org/maven2/org/postgresql/postgresql/42.7.3/postgresql-42.7.3.jar", ) - .await } -#[tokio::test] -async fn test_scala3() -> Result<(), Box> { +#[test] +fn test_scala3() -> Result<(), Box> { verify_jar("https://repo1.maven.org/maven2/org/scala-lang/scala3-library_3/3.4.2/scala3-library_3-3.4.2.jar") - .await } -#[tokio::test] -async fn test_slf4j() -> Result<(), Box> { +#[test] +fn test_slf4j() -> Result<(), Box> { verify_jar("https://repo1.maven.org/maven2/org/slf4j/slf4j-api/2.0.13/slf4j-api-2.0.13.jar") - .await } -#[tokio::test] -async fn test_snowflake() -> Result<(), Box> { - verify_jar("https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc/3.16.1/snowflake-jdbc-3.16.1.jar").await +#[test] +fn test_snowflake() -> Result<(), Box> { + verify_jar("https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc/3.16.1/snowflake-jdbc-3.16.1.jar") } -#[tokio::test] -async fn test_spring_boot() -> Result<(), Box> { - verify_jar("https://repo1.maven.org/maven2/org/springframework/boot/spring-boot/3.3.0/spring-boot-3.3.0.jar").await +#[test] +fn test_spring_boot() -> Result<(), Box> { + verify_jar("https://repo1.maven.org/maven2/org/springframework/boot/spring-boot/3.3.0/spring-boot-3.3.0.jar") } -#[tokio::test] -async fn test_sqlite() -> Result<(), Box> { +#[test] +fn test_sqlite() -> Result<(), Box> { verify_jar( "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.46.0.0/sqlite-jdbc-3.46.0.0.jar", ) - .await } diff --git a/ristretto_classfile/tests/java8_runtime_test.rs b/ristretto_classfile/tests/java8_runtime_test.rs index defdda73..8f5ed798 100644 --- a/ristretto_classfile/tests/java8_runtime_test.rs +++ b/ristretto_classfile/tests/java8_runtime_test.rs @@ -2,15 +2,15 @@ mod utilities; use anyhow::{anyhow, Result}; use flate2::read::GzDecoder; -use reqwest::Client; +use reqwest::blocking::Client; use std::io::Read; use tar::Archive; -#[tokio::test] -async fn verify() -> Result<()> { +#[test] +fn verify() -> Result<()> { let url = "https://corretto.aws/downloads/latest/amazon-corretto-8-x64-linux-jdk.tar.gz"; let client = Client::new(); - let archive = client.get(url).send().await?.bytes().await?.to_vec(); + let archive = client.get(url).send()?.bytes()?.to_vec(); let jar_bytes = get_runtime_jar(archive)?; utilities::jar::verify(jar_bytes)?; diff --git a/ristretto_classloader/Cargo.toml b/ristretto_classloader/Cargo.toml index 7c5c2073..1a019233 100644 --- a/ristretto_classloader/Cargo.toml +++ b/ristretto_classloader/Cargo.toml @@ -12,7 +12,7 @@ version.workspace = true [dependencies] flate2 = { workspace = true } indexmap = { workspace = true } -reqwest = { workspace = true, features = ["json"] } +reqwest = { workspace = true, features = ["blocking", "json"] } ristretto_classfile = { path = "../ristretto_classfile", version = "0.7.0" } serde = { workspace = true, features = ["derive"] } serde_plain = { workspace = true } @@ -22,23 +22,12 @@ thiserror = { workspace = true } tracing = { workspace = true } zip = { workspace = true } -[target.'cfg(target_arch = "wasm32")'.dependencies] -tokio = { workspace = true } - [target.'cfg(not(target_arch = "wasm32"))'.dependencies] home = { workspace = true } -tokio = { workspace = true, features = ["fs"] } [dev-dependencies] criterion = { workspace = true } indoc = { workspace = true } -test-log = { workspace = true } - -[target.'cfg(target_arch = "wasm32")'.dev-dependencies] -tokio = { workspace = true } - -[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -tokio = { workspace = true, features = ["rt-multi-thread"] } [features] all = ["url"] diff --git a/ristretto_classloader/README.md b/ristretto_classloader/README.md index 22f8a163..777cea1c 100644 --- a/ristretto_classloader/README.md +++ b/ristretto_classloader/README.md @@ -10,7 +10,7 @@ ## Getting Started -Implementation of a [JVM Class Loader](https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-4.html) +Implementation of a [JVM Class Loader](https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-4.html) that is used to load Java classes. Classes can be loaded from the file system or from a URL; jar and modules are supported. A runtime Java class loader can be created from any version of [AWS Corretto](https://github.com/corretto). The runtime class loader will download and install @@ -28,14 +28,12 @@ The AWS Corretto runtime is installed in the following directory: use ristretto_classloader::{ClassLoader, ClassPath, Result}; use std::sync::Arc; -#[tokio::main] -async fn main() -> Result<()> { - #[tokio::main] - async fn main() -> Result<()> { - let (version, class_loader) = runtime::class_loader("21").await?; +fn main() -> Result<()> { + fn main() -> Result<()> { + let (version, class_loader) = runtime::class_loader("21")?; let class_name = "java.util.HashMap"; println!("Loading {class_name} from Java runtime {version}"); - let class = class_loader.load(class_name).await?; + let class = class_loader.load(class_name)?; println!("{class:?}"); Ok(()) } diff --git a/ristretto_classloader/benches/class_loader.rs b/ristretto_classloader/benches/class_loader.rs index 6a862889..99be7123 100644 --- a/ristretto_classloader/benches/class_loader.rs +++ b/ristretto_classloader/benches/class_loader.rs @@ -1,66 +1,51 @@ use criterion::{criterion_group, criterion_main, Criterion}; use ristretto_classloader::{runtime, Result}; -use tokio::runtime::Runtime; fn benchmarks(criterion: &mut Criterion) { bench_lifecycle(criterion).ok(); } fn bench_lifecycle(criterion: &mut Criterion) -> Result<()> { - let runtime = Runtime::new().unwrap(); - let (_version, class_loader) = - runtime.block_on(async { runtime::class_loader("21.0.4.7.1").await })?; + let (_version, class_loader) = runtime::class_loader("21.0.4.7.1")?; let class_loader = class_loader; criterion.bench_function("runtime_v8", |bencher| { bencher.iter(|| { - runtime.block_on(async { - runtime_class_loader("8.422.05.1").await.ok(); - }); + runtime_class_loader("8.422.05.1").ok(); }); }); criterion.bench_function("runtime_v11", |bencher| { bencher.iter(|| { - runtime.block_on(async { - runtime_class_loader("11.0.24.8.1").await.ok(); - }); + runtime_class_loader("11.0.24.8.1").ok(); }); }); criterion.bench_function("runtime_v17", |bencher| { bencher.iter(|| { - runtime.block_on(async { - runtime_class_loader("17.0.12.7.1").await.ok(); - }); + runtime_class_loader("17.0.12.7.1").ok(); }); }); criterion.bench_function("runtime_v21", |bencher| { bencher.iter(|| { - runtime.block_on(async { - runtime_class_loader("21.0.4.7.1").await.ok(); - }); + runtime_class_loader("21.0.4.7.1").ok(); }); }); criterion.bench_function("load_hash_map", |bencher| { bencher.iter(|| { - runtime.block_on(async { - let _ = class_loader.load("java.util.HashMap").await.ok(); - }); + let _ = class_loader.load("java/util/HashMap").ok(); }); }); criterion.bench_function("load_invalid_class", |bencher| { bencher.iter(|| { - runtime.block_on(async { - let _ = class_loader.load("foo").await.err(); - }); + let _ = class_loader.load("foo").err(); }); }); Ok(()) } -async fn runtime_class_loader(version: &str) -> Result<()> { - let (_runtime_version, class_loader) = runtime::class_loader(version).await?; - let _class = class_loader.load("java.lang.Object").await?; +fn runtime_class_loader(version: &str) -> Result<()> { + let (_runtime_version, class_loader) = runtime::class_loader(version)?; + let _class = class_loader.load("java.lang.Object")?; Ok(()) } diff --git a/ristretto_classloader/src/class.rs b/ristretto_classloader/src/class.rs index fb1ddf83..f53b60bb 100644 --- a/ristretto_classloader/src/class.rs +++ b/ristretto_classloader/src/class.rs @@ -1,17 +1,187 @@ -use ristretto_classfile::{ClassFile, ConstantPool}; -use std::fmt::Debug; +use crate::Error::{FieldNotFound, MethodNotFound, PoisonedLock}; +use crate::{Field, Method, Result}; +use indexmap::IndexMap; +use ristretto_classfile::attributes::Attribute; +use ristretto_classfile::{BaseType, ClassFile, ConstantPool, FieldAccessFlags}; +use std::collections::HashMap; +use std::fmt::{Debug, Display}; +use std::str::FromStr; +use std::sync::{Arc, RwLock}; /// A representation of a Java class. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Class { + name: String, + source_file: Option, class_file: ClassFile, + parent: Arc>>>, + interfaces: Arc>>>, + fields: IndexMap>, + methods: HashMap>, } impl Class { - /// Create a new class with the given class file. + /// Create a new class. #[must_use] - pub fn new(class_file: ClassFile) -> Self { - Self { class_file } + pub fn new( + name: String, + source_file: Option, + class_file: ClassFile, + parent: Option>, + interfaces: Vec>, + fields: IndexMap>, + methods: HashMap>, + ) -> Self { + Self { + name, + source_file, + class_file, + parent: Arc::new(RwLock::new(parent)), + interfaces: Arc::new(RwLock::new(interfaces)), + fields, + methods, + } + } + + /// Create a new array class. + /// + /// # Errors + /// if the class name cannot be added to the constant pool + pub fn new_array>(name: S) -> Result { + let name = name.as_ref().to_string(); + let mut constant_pool = ConstantPool::new(); + let class_index = constant_pool.add_class(name.clone())?; + let class_file = ClassFile { + constant_pool, + this_class: class_index, + ..Default::default() + }; + let methods = HashMap::new(); + Ok(Self { + name, + source_file: None, + class_file, + parent: Arc::new(RwLock::new(None)), + interfaces: Arc::new(RwLock::new(Vec::new())), + fields: IndexMap::new(), + methods, + }) + } + + /// Create a new class from the given class file. + /// + /// # Errors + /// if the class file cannot be read. + pub fn from(class_file: ClassFile) -> Result { + let name = class_file.class_name()?.clone(); + let mut source_file = None; + + for attribute in &class_file.attributes { + if let Attribute::SourceFile { + source_file_index, .. + } = attribute + { + let constant_pool = &class_file.constant_pool; + let source_file_name = constant_pool.try_get_utf8(*source_file_index)?; + source_file = Some(source_file_name.to_string()); + break; + } + } + + let mut fields = IndexMap::new(); + for class_field in &class_file.fields { + let field = Field::from(&class_file, class_field)?; + let field_name = field.name().to_string(); + fields.insert(field_name, Arc::new(field)); + } + + let mut methods = HashMap::new(); + for class_file_method in &class_file.methods { + let method = Method::from(&class_file, class_file_method)?; + let method_identifier = method.identifier(); + methods.insert(method_identifier, Arc::new(method)); + } + + Ok(Self { + name, + source_file, + class_file, + parent: Arc::new(RwLock::new(None)), + interfaces: Arc::new(RwLock::new(Vec::new())), + fields, + methods, + }) + } + + /// Get the class name. + #[must_use] + pub fn name(&self) -> &str { + &self.name + } + + /// Get the raw component name for an array class. + fn array_component_type(&self) -> &str { + let mut component_type = self.name.trim_start_matches('['); + if component_type.ends_with(';') { + component_type = component_type + .strip_prefix('L') + .unwrap_or_default() + .strip_suffix(';') + .unwrap_or_default(); + } + component_type + } + + /// Get the component name for an array class. + #[must_use] + pub fn component_type(&self) -> Option<&str> { + if !self.is_array() { + return None; + } + let component_type = self.array_component_type(); + let component_type = match component_type { + "B" => "byte", + "C" => "char", + "D" => "double", + "F" => "float", + "I" => "int", + "J" => "long", + "S" => "short", + "Z" => "boolean", + _ => component_type, + }; + Some(component_type) + } + + /// Get the class source file name. + #[must_use] + pub fn source_file(&self) -> Option<&str> { + self.source_file.as_deref() + } + + /// Determine if this class is an array + #[must_use] + pub fn is_array(&self) -> bool { + self.name.starts_with('[') + } + + /// Get the number of array dimensions + #[must_use] + pub fn array_dimensions(&self) -> usize { + self.name.chars().filter(|&c| c == '[').count() + } + + /// Determine if this class is a primitive + #[must_use] + pub fn is_primitive(&self) -> bool { + if !self.is_array() { + return false; + } + let component_type = self.array_component_type(); + let Ok(component_type) = char::from_str(component_type) else { + return false; + }; + BaseType::parse(component_type).is_ok() } /// Get the class file. @@ -20,9 +190,54 @@ impl Class { &self.class_file } - /// Get a mutable class file. - pub fn class_file_mut(&mut self) -> &mut ClassFile { - &mut self.class_file + /// Get the parent class. + /// + /// # Errors + pub fn parent(&self) -> Result>> { + let parent_guard = self + .parent + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; + match parent_guard.as_ref() { + Some(parent) => Ok(Some(parent.clone())), + None => Ok(None), + } + } + + /// Set the parent class. + /// + /// # Errors + pub fn set_parent(&self, parent: Option>) -> Result<()> { + let mut parent_guard = self + .parent + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + *parent_guard = parent; + Ok(()) + } + + /// Get the class interfaces. + /// + /// # Errors + pub fn interfaces(&self) -> Result>> { + let parent_guard = self + .interfaces + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; + let interfaces = parent_guard.clone(); + Ok(interfaces) + } + + /// Set the class interfaces. + /// + /// # Errors + pub fn set_interfaces(&self, interfaces: Vec>) -> Result<()> { + let mut interfaces_guard = self + .interfaces + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + *interfaces_guard = interfaces; + Ok(()) } /// Get the constant pool @@ -35,36 +250,426 @@ impl Class { pub fn constant_pool_mut(&mut self) -> &mut ConstantPool { &mut self.class_file.constant_pool } + + /// Get a static field by name. + /// + /// # Errors + /// if the field is not found. + pub fn static_field>(&self, name: S) -> Result> { + let name = name.as_ref(); + if let Some(field) = self.fields.get(name) { + if !field.access_flags().contains(FieldAccessFlags::STATIC) { + return Err(FieldNotFound { + class_name: self.name.to_string(), + field_name: name.to_string(), + }); + } + return Ok(field.clone()); + } + + let Some(parent) = &self.parent()? else { + return Err(FieldNotFound { + class_name: self.name.to_string(), + field_name: name.to_string(), + }); + }; + + let field = parent.static_field(name)?; + Ok(field) + } + + /// Get a list of field names in the class hierarchy. + /// + /// # Errors + /// if there is an issue accessing the parent class. + fn field_names(&self) -> Result> { + let mut field_names = Vec::new(); + let mut parent = self.parent()?; + while let Some(class) = parent { + for field_name in class.fields.keys().rev() { + field_names.insert(0, field_name.clone()); + } + parent = class.parent()?; + } + + for field_name in self.fields.keys() { + field_names.push(field_name.clone()); + } + Ok(field_names) + } + + /// Field offset by name. This is primarily used by the Unsafe class that references fields by + /// offset. + /// + /// # Errors + /// if the field is not found. + pub fn field_offset>(&self, name: S) -> Result { + let name = name.as_ref().to_string(); + let field_names = self.field_names()?; + for (offset, field_name) in field_names.iter().enumerate() { + if field_name == &name { + return Ok(offset); + } + } + Err(FieldNotFound { + class_name: self.name().to_string(), + field_name: name, + }) + } + + /// Returns the field name for an offset. This is primarily used by the Unsafe class that + /// references fields by offset. + /// + /// # Errors + /// if the field is not found. + pub fn field_name(&self, offset: usize) -> Result { + let field_names = self.field_names()?; + let Some(key) = field_names.get(offset) else { + return Err(FieldNotFound { + class_name: self.name().to_string(), + field_name: offset.to_string(), + }); + }; + + Ok(key.to_string()) + } + + /// Get the class initializer method. + #[must_use] + pub fn class_initializer(&self) -> Option> { + self.method("", "()V") + } + + /// Get an object initializer method. + #[must_use] + pub fn object_initializer(&self, descriptor: &str) -> Option> { + self.method("", descriptor) + } + + /// Get the main method. + #[must_use] + pub fn main_method(&self) -> Option> { + self.method("main", "([Ljava/lang/String;)V") + } + + /// Get a method by name and descriptor. + #[must_use] + pub fn method>(&self, name: S, descriptor: S) -> Option> { + let name = name.as_ref(); + let descriptor = descriptor.as_ref(); + let method_identifier = format!("{name}:{descriptor}"); + let method = self.methods.get(&method_identifier); + method.cloned() + } + + /// Get a method by name and descriptor. + /// + /// # Errors + /// if the method is not found. + pub fn try_get_method>(&self, name: S, descriptor: S) -> Result> { + let name = name.as_ref(); + let descriptor = descriptor.as_ref(); + let Some(method) = self.method(name, descriptor) else { + return Err(MethodNotFound { + class_name: self.name().to_string(), + method_name: name.to_string(), + method_descriptor: descriptor.to_string(), + }); + }; + Ok(method) + } + + /// Get a virtual method by name and descriptor. + /// + /// # Errors + /// if the method is not found. + pub fn try_get_virtual_method>( + &self, + name: S, + descriptor: S, + ) -> Result> { + let name = name.as_ref(); + let descriptor = descriptor.as_ref(); + if let Some(method) = self.method(name, descriptor) { + return Ok(method); + } + let Some(parent) = self.parent()? else { + return Err(MethodNotFound { + class_name: self.name().to_string(), + method_name: name.to_string(), + method_descriptor: descriptor.to_string(), + }); + }; + parent.try_get_virtual_method(name, descriptor) + } + + /// Determine if this class is assignable from the given class. + /// + /// # Errors + /// if classes or interfaces cannot be accessed. + pub fn is_assignable_from>(&self, class_name: S) -> Result { + let class_name = class_name.as_ref(); + if self.name() == class_name { + return Ok(true); + } + if let Some(parent) = self.parent()? { + if parent.is_assignable_from(class_name)? { + return Ok(true); + } + } + for interface in self.interfaces()? { + if interface.is_assignable_from(class_name)? { + return Ok(true); + } + } + Ok(false) + } +} + +impl PartialEq for Class { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + && self.class_file == other.class_file + && *self.parent.read().expect("parent") == *other.parent.read().expect("parent") + && self.fields == other.fields + && self.methods == other.methods + } +} + +impl Display for Class { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } } #[cfg(test)] mod tests { use super::*; - use crate::Result; - use indoc::indoc; + use crate::{runtime, Result}; use std::io::Cursor; - #[test_log::test(tokio::test)] - async fn test_new() -> Result<()> { + const JAVA_VERSION: &str = "21.0.4.7.1"; + + fn object_class() -> Result> { + let (_version, class_loader) = runtime::class_loader(JAVA_VERSION)?; + class_loader.load("java/lang/Object") + } + + fn string_class() -> Result> { + let (_version, class_loader) = runtime::class_loader(JAVA_VERSION)?; + let string_class = class_loader.load("java/lang/String")?; + + let object_class = object_class()?; + string_class.set_parent(Some(object_class))?; + + let serializable_class = serializable_class()?; + string_class.set_interfaces(vec![serializable_class])?; + + Ok(string_class) + } + + fn serializable_class() -> Result> { + let (_version, class_loader) = runtime::class_loader(JAVA_VERSION)?; + class_loader.load("java/io/Serializable") + } + + fn simple_class() -> Result { let bytes = include_bytes!("../../classes/Simple.class").to_vec(); let mut cursor = Cursor::new(bytes); let class_file = ClassFile::from_bytes(&mut cursor)?; - let class = Class::new(class_file); + Class::from(class_file) + } + + #[test] + fn test_new() -> Result<()> { + let class = simple_class()?; + assert_eq!("Simple", class.name()); + assert_eq!(Some("Simple.java"), class.source_file()); assert_eq!("Simple", class.class_file().class_name()?); + assert_eq!("Simple", format!("{class}")); + assert_eq!(0, class.array_dimensions()); + assert!(!class.is_array()); + assert!(!class.is_primitive()); Ok(()) } - #[test_log::test] - fn test_debug() -> Result<()> { - let bytes = include_bytes!("../../classes/Minimum.class").to_vec(); - let mut cursor = Cursor::new(bytes); - let class_file = ClassFile::from_bytes(&mut cursor)?; - let class = Class::new(class_file); - let debug = format!("{class:?}"); - assert_eq!( - debug, - indoc! {r#"Class { class_file: ClassFile { version: Java21 { minor: 0 }, constant_pool: ConstantPool { constants: [Placeholder, Constant(MethodRef { class_index: 2, name_and_type_index: 3 }), Constant(Class(4)), Constant(NameAndType { name_index: 5, descriptor_index: 6 }), Constant(Utf8("java/lang/Object")), Constant(Utf8("")), Constant(Utf8("()V")), Constant(Class(8)), Constant(Utf8("Minimum")), Constant(Utf8("Code")), Constant(Utf8("LineNumberTable")), Constant(Utf8("SourceFile")), Constant(Utf8("Minimum.java"))] }, access_flags: ClassAccessFlags(PUBLIC | SUPER), this_class: 7, super_class: 2, interfaces: [], fields: [], methods: [Method { access_flags: MethodAccessFlags(PUBLIC), name_index: 5, descriptor_index: 6, attributes: [Code { name_index: 9, max_stack: 1, max_locals: 1, code: [Aload_0, Invokespecial(1), Return], exceptions: [], attributes: [LineNumberTable { name_index: 10, line_numbers: [LineNumber { start_pc: 0, line_number: 1 }] }] }] }], attributes: [SourceFile { name_index: 11, source_file_index: 12 }] } }"#} - ); + #[test] + fn test_new_array_boolean() -> Result<()> { + let class = Class::new_array("[Z")?; + assert_eq!("[Z", class.name()); + assert_eq!(Some("boolean"), class.component_type()); + assert_eq!(1, class.array_dimensions()); + assert!(class.is_array()); + assert!(class.is_primitive()); + Ok(()) + } + + #[test] + fn test_new_array_byte() -> Result<()> { + let class = Class::new_array("[B")?; + assert_eq!("[B", class.name()); + assert_eq!(Some("byte"), class.component_type()); + assert_eq!(1, class.array_dimensions()); + assert!(class.is_array()); + assert!(class.is_primitive()); + Ok(()) + } + + #[test] + fn test_new_array_char() -> Result<()> { + let class = Class::new_array("[C")?; + assert_eq!("[C", class.name()); + assert_eq!(Some("char"), class.component_type()); + assert_eq!(1, class.array_dimensions()); + assert!(class.is_array()); + assert!(class.is_primitive()); + Ok(()) + } + + #[test] + fn test_new_array_double() -> Result<()> { + let class = Class::new_array("[D")?; + assert_eq!("[D", class.name()); + assert_eq!(Some("double"), class.component_type()); + assert_eq!(1, class.array_dimensions()); + assert!(class.is_array()); + assert!(class.is_primitive()); + Ok(()) + } + + #[test] + fn test_new_array_float() -> Result<()> { + let class = Class::new_array("[F")?; + assert_eq!("[F", class.name()); + assert_eq!(Some("float"), class.component_type()); + assert_eq!(1, class.array_dimensions()); + assert!(class.is_array()); + assert!(class.is_primitive()); + Ok(()) + } + + #[test] + fn test_new_array_int() -> Result<()> { + let class = Class::new_array("[I")?; + assert_eq!("[I", class.name()); + assert_eq!(Some("int"), class.component_type()); + assert_eq!(1, class.array_dimensions()); + assert!(class.is_array()); + assert!(class.is_primitive()); + Ok(()) + } + + #[test] + fn test_new_array_long() -> Result<()> { + let class = Class::new_array("[J")?; + assert_eq!("[J", class.name()); + assert_eq!(Some("long"), class.component_type()); + assert_eq!(1, class.array_dimensions()); + assert!(class.is_array()); + assert!(class.is_primitive()); + Ok(()) + } + + #[test] + fn test_new_array_short() -> Result<()> { + let class = Class::new_array("[S")?; + assert_eq!("[S", class.name()); + assert_eq!(Some("short"), class.component_type()); + assert_eq!(1, class.array_dimensions()); + assert!(class.is_array()); + assert!(class.is_primitive()); + Ok(()) + } + + #[test] + fn test_new_array_string() -> Result<()> { + let class = Class::new_array("[Ljava/lang/String;")?; + assert_eq!("[Ljava/lang/String;", class.name()); + assert_eq!(Some("java/lang/String"), class.component_type()); + assert_eq!(1, class.array_dimensions()); + assert!(class.is_array()); + assert!(!class.is_primitive()); + Ok(()) + } + + #[test] + fn test_new_array_multiple_dimensions() -> Result<()> { + let class = Class::new_array("[[[[[B")?; + assert_eq!("[[[[[B", class.name()); + assert_eq!(Some("byte"), class.component_type()); + assert_eq!(5, class.array_dimensions()); + assert!(class.is_array()); + assert!(class.is_primitive()); + Ok(()) + } + + #[test] + fn test_class_initializer() -> Result<()> { + let class = simple_class()?; + let method = class.class_initializer().expect("class initializer"); + assert_eq!("", method.name()); + assert_eq!("()V", method.descriptor()); + Ok(()) + } + + #[test] + fn test_object_initializer() -> Result<()> { + let class = simple_class()?; + let method = class.object_initializer("()V").expect("class initializer"); + assert_eq!("", method.name()); + assert_eq!("()V", method.descriptor()); + Ok(()) + } + + #[test] + fn test_main_method() -> Result<()> { + let class = simple_class()?; + let method = class.main_method().expect("class initializer"); + assert_eq!("main", method.name()); + assert_eq!("([Ljava/lang/String;)V", method.descriptor()); + Ok(()) + } + + #[test] + fn test_method() -> Result<()> { + let class = simple_class()?; + let name = "getPublicValue"; + let descriptor = "()I"; + let method = class.method(name, descriptor).expect("class initializer"); + assert_eq!(name, method.name()); + assert_eq!(descriptor, method.descriptor()); + Ok(()) + } + + #[test] + fn test_string_instanceof_object() -> Result<()> { + let string_class = string_class()?; + let object_class = object_class()?; + assert!(string_class.is_assignable_from(&object_class.name)?); + Ok(()) + } + + #[test] + fn test_object_instanceof_string() -> Result<()> { + let object_class = object_class()?; + let string_class = string_class()?; + assert!(!object_class.is_assignable_from(&string_class.name)?); + Ok(()) + } + + #[test] + fn test_string_instanceof_serializable() -> Result<()> { + let string_class = string_class()?; + let serializable_class = serializable_class()?; + assert!(string_class.is_assignable_from(&serializable_class.name)?); + Ok(()) + } + + #[test] + fn test_object_instanceof_serializable_class() -> Result<()> { + let object_class = object_class()?; + let serializable_class = serializable_class()?; + assert!(!object_class.is_assignable_from(&serializable_class.name)?); Ok(()) } } diff --git a/ristretto_classloader/src/class_loader.rs b/ristretto_classloader/src/class_loader.rs index 4d0aa1dd..96127815 100644 --- a/ristretto_classloader/src/class_loader.rs +++ b/ristretto_classloader/src/class_loader.rs @@ -1,19 +1,18 @@ -use crate::Error::ClassNotFound; +use crate::Error::{ClassNotFound, PoisonedLock}; use crate::{Class, ClassPath, Result}; use std::collections::HashMap; -use std::rc::Rc; -use std::sync::Arc; -use tokio::sync::RwLock; +use std::fmt::Display; +use std::sync::{Arc, RwLock}; /// Implementation of a Java class loader. /// -/// See: +/// See: #[derive(Debug)] pub struct ClassLoader { name: String, class_path: ClassPath, - parent: Option>, - classes: Arc>>, + parent: Option>, + classes: Arc>>>, } impl ClassLoader { @@ -51,7 +50,7 @@ impl ClassLoader { /// Set the parent class loader. pub fn set_parent(&mut self, parent: Option) { if let Some(parent) = parent { - self.parent = Some(Rc::new(parent)); + self.parent = Some(Arc::new(parent)); } else { self.parent = None; } @@ -61,16 +60,28 @@ impl ClassLoader { /// /// # Errors /// if the class file cannot be read. - pub async fn load>(&self, name: S) -> Result { + pub fn load>(&self, name: S) -> Result> { + self.load_with_status(name).map(|(class, _)| class) + } + + /// Load a class by name with a boolean status indicating if the class was loaded previously. + /// + /// # Errors + /// if the class file cannot be read. + pub fn load_with_status>(&self, name: S) -> Result<(Arc, bool)> { let name = name.as_ref(); { - let classes = self.classes.read().await; + let classes = self + .classes + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; if let Some(class) = classes.get(name) { - return Ok(class.clone()); + return Ok((Arc::clone(class), true)); } } - // Convert hierarchy of class loaders to a flat list. + // Convert hierarchy of class loaders to a flat list so that we can iterate over them from + // the boot class loader to the current class loader. let mut class_loader = self; let mut class_loaders = vec![class_loader]; while let Some(parent) = class_loader.parent() { @@ -78,19 +89,39 @@ impl ClassLoader { class_loaders.push(parent); } - // Iterate over class loaders in reverse order. for class_loader in class_loaders.into_iter().rev() { let class_path = class_loader.class_path(); - if let Ok(class_file) = class_path.read_class(name).await { - let class = Class::new(class_file); - let mut classes = self.classes.write().await; + if let Ok(class_file) = class_path.read_class(name) { + let mut classes = self + .classes + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + // Check if the class was loaded while waiting for the lock. + if let Some(class) = classes.get(name) { + return Ok((class.clone(), true)); + } + let class = Arc::new(Class::from(class_file)?); classes.insert(name.to_string(), class.clone()); - return Ok(class); + return Ok((class, false)); } } Err(ClassNotFound(name.to_string())) } + + /// Register a class with the class loader. + /// + /// # Errors + /// if the class cannot be registered. + pub fn register(&mut self, class: Arc) -> Result<()> { + let mut classes = self + .classes + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + let class_name = class.name().to_string(); + classes.insert(class_name, class); + Ok(()) + } } impl Clone for ClassLoader { @@ -105,6 +136,20 @@ impl Clone for ClassLoader { } } +impl Display for ClassLoader { + /// Display the class loader. + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}={}", self.name, self.class_path)?; + + let mut class_loader = self; + while let Some(parent) = class_loader.parent() { + class_loader = parent; + write!(f, "; {}={}", class_loader.name, class_loader.class_path)?; + } + Ok(()) + } +} + /// Implement equality for class loaders. impl PartialEq for ClassLoader { /// Compare class loaders by name. @@ -116,9 +161,10 @@ impl PartialEq for ClassLoader { #[cfg(test)] mod tests { use super::*; + use crate::Value; use std::path::PathBuf; - #[test_log::test] + #[test] fn test_new() { let name = "test"; let class_path = ClassPath::from("."); @@ -128,7 +174,7 @@ mod tests { assert!(class_loader.parent().is_none()); } - #[test_log::test] + #[test] fn test_equality() { let class_path1 = ClassPath::from("."); let class_loader1 = ClassLoader::new("test", class_path1); @@ -137,7 +183,7 @@ mod tests { assert_eq!(class_loader1, class_loader2); } - #[test_log::test] + #[test] fn test_inequality() { let class_path1 = ClassPath::from("."); let class_loader1 = ClassLoader::new("test1", class_path1); @@ -146,7 +192,7 @@ mod tests { assert_ne!(class_loader1, class_loader2); } - #[test_log::test] + #[test] fn test_set_parent() { let class_path1 = ClassPath::from("."); let mut class_loader1 = ClassLoader::new("test1", class_path1); @@ -156,8 +202,8 @@ mod tests { assert_eq!("test2", class_loader1.parent().expect("parent").name()); } - #[test_log::test(tokio::test)] - async fn test_load_class() -> Result<()> { + #[test] + fn test_load_class() -> Result<()> { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_directory = cargo_manifest.join("../classes"); let class_path_entries = [classes_directory.to_string_lossy().to_string()]; @@ -165,19 +211,43 @@ mod tests { let class_path = ClassPath::from(class_path_entries.join(":")); let class_loader = ClassLoader::new("test", class_path); let class_name = "HelloWorld"; - let class = class_loader.load(class_name).await?; + let class = class_loader.load(class_name)?; let class_file = class.class_file(); assert_eq!(class_name, class_file.class_name()?); // Load the same class again to test caching - let class = class_loader.load(class_name).await?; + let class = class_loader.load(class_name)?; let class_file = class.class_file(); assert_eq!(class_name, class_file.class_name()?); Ok(()) } - #[test_log::test(tokio::test)] - async fn test_load_class_parent() -> Result<()> { + #[test] + fn test_load_class_more_than_once() -> Result<()> { + let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let classes_directory = cargo_manifest.join("../classes"); + let class_path_entries = [classes_directory.to_string_lossy().to_string()]; + + let class_path = ClassPath::from(class_path_entries.join(":")); + let class_loader = ClassLoader::new("test", class_path); + let class_name = "Simple"; + + // Set a static value on the class to test class caching + let expected_value = Value::Int(21); + let class = &mut class_loader.load(class_name)?; + let answer_field = class.static_field("ANSWER")?; + answer_field.set_value(expected_value.clone())?; + + // Load the same class again and verify that the static value is still set + let class = class_loader.load(class_name)?; + let answer_field = class.static_field("ANSWER")?; + let value = answer_field.value()?; + assert_eq!(expected_value, value); + Ok(()) + } + + #[test] + fn test_load_class_parent() -> Result<()> { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_directory = cargo_manifest.join("../classes"); let class_path_entries = [classes_directory.to_string_lossy().to_string()]; @@ -187,17 +257,17 @@ mod tests { let mut class_loader = ClassLoader::new("test", foo_class_path); class_loader.set_parent(Some(boot_class_loader)); - let class = class_loader.load("HelloWorld").await?; + let class = class_loader.load("HelloWorld")?; let class_file = class.class_file(); assert_eq!("HelloWorld", class_file.class_name()?); Ok(()) } - #[test_log::test(tokio::test)] - async fn test_load_class_not_found() { + #[test] + fn test_load_class_not_found() { let class_path = ClassPath::from("."); let class_loader = ClassLoader::new("test", class_path); - let result = class_loader.load("Foo").await; + let result = class_loader.load("Foo"); assert!(matches!(result, Err(ClassNotFound(_)))); } } diff --git a/ristretto_classloader/src/class_path.rs b/ristretto_classloader/src/class_path.rs index f03056bd..174feb9e 100644 --- a/ristretto_classloader/src/class_path.rs +++ b/ristretto_classloader/src/class_path.rs @@ -3,7 +3,7 @@ use crate::Error::ClassNotFound; use crate::Result; use ristretto_classfile::ClassFile; use std::fmt::Display; -use tracing::instrument; +use tracing::{info, instrument}; /// Represents a class path. /// @@ -49,11 +49,12 @@ impl ClassPath { /// # Errors /// if the class file is not found or cannot be read. #[instrument(level = "trace", fields(name = ?name.as_ref()), skip(self))] - pub async fn read_class>(&self, name: S) -> Result { + pub fn read_class>(&self, name: S) -> Result { let name = name.as_ref(); for class_path_entry in self.iter() { - if let Ok(class_file) = class_path_entry.read_class(name).await { + if let Ok(class_file) = class_path_entry.read_class(name) { + info!("load class {name} source: {}", class_path_entry.name()); return Ok(class_file); } } @@ -90,19 +91,19 @@ mod tests { use crate::Result; use std::path::PathBuf; - #[test_log::test] + #[test] fn test_new() { let class_path = ClassPath::new(vec![ClassPathEntry::new("."), ClassPathEntry::new("..")]); assert_eq!(".:..", class_path.to_string()); } - #[test_log::test] + #[test] fn test_from() { let class_path = ClassPath::from(".:.."); assert_eq!(".:..", class_path.to_string()); } - #[test_log::test] + #[test] fn test_iter() { let class_path = ClassPath::from(".:.."); let mut iter = class_path.iter(); @@ -110,7 +111,7 @@ mod tests { assert_eq!("..", iter.next().expect("next").name()); } - #[test_log::test] + #[test] fn test_into_iter() { let class_path = ClassPath::from(".:.."); let mut iter = class_path.into_iter(); @@ -118,8 +119,8 @@ mod tests { assert_eq!("..", iter.next().expect("next").name()); } - #[test_log::test(tokio::test)] - async fn test_read_class() -> Result<()> { + #[test] + fn test_read_class() -> Result<()> { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_directory = cargo_manifest.join("../classes"); let classes_jar = cargo_manifest.join("../classes/classes.jar"); @@ -134,14 +135,13 @@ mod tests { let class_path = class_path_entries.join(":"); let class_path_entry = ClassPath::from(&class_path); - let class_file = class_path_entry.read_class("HelloWorld").await?; + let class_file = class_path_entry.read_class("HelloWorld")?; assert_eq!("HelloWorld", class_file.class_name()?); #[cfg(feature = "url")] { - let class_file = class_path_entry - .read_class("org.springframework.boot.SpringApplication") - .await?; + let class_file = + class_path_entry.read_class("org/springframework/boot/SpringApplication")?; assert_eq!( "org/springframework/boot/SpringApplication", class_file.class_name()? diff --git a/ristretto_classloader/src/class_path_entry/directory.rs b/ristretto_classloader/src/class_path_entry/directory.rs index 8d265901..539fa4fe 100644 --- a/ristretto_classloader/src/class_path_entry/directory.rs +++ b/ristretto_classloader/src/class_path_entry/directory.rs @@ -34,7 +34,7 @@ impl Directory { /// # Errors /// if the class file is not found or cannot be read. #[instrument(level = "trace", fields(name = ?name.as_ref()), skip(self))] - pub async fn read_class>(&self, name: S) -> Result { + pub fn read_class>(&self, name: S) -> Result { let name = name.as_ref(); let parts = name.split('.').collect::>(); let path = self.path.clone(); @@ -64,54 +64,52 @@ impl PartialEq for Directory { mod tests { use super::*; - #[test_log::test] + #[test] fn test_new() { let directory = Directory::new("test"); assert_eq!("test", directory.name()); } - #[test_log::test] + #[test] fn test_equality() { let directory1 = Directory::new("test"); let directory2 = Directory::new("test"); assert_eq!(directory1, directory2); } - #[test_log::test] + #[test] fn test_inequality() { let directory1 = Directory::new("test1"); let directory2 = Directory::new("test2"); assert_ne!(directory1, directory2); } - #[test_log::test(tokio::test)] - async fn test_read_class_invalid_directory() -> Result<()> { + #[test] + fn test_read_class_invalid_directory() { let directory = Directory::new("foo"); - let result = directory.read_class("HelloWorld").await; + let result = directory.read_class("HelloWorld"); assert!(matches!(result, Err(ClassNotFound(_)))); - Ok(()) } - #[test_log::test(tokio::test)] - async fn test_read_class() -> Result<()> { + #[test] + fn test_read_class() -> Result<()> { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_directory = cargo_manifest.join("../classes"); let directory = Directory::new(classes_directory.to_string_lossy()); // Read the class file twice to test caching for _ in 0..2 { - let class_file = directory.read_class("HelloWorld").await?; + let class_file = directory.read_class("HelloWorld")?; assert_eq!("HelloWorld", class_file.class_name()?); } Ok(()) } - #[test_log::test(tokio::test)] - async fn test_read_class_invalid_class_name() -> Result<()> { + #[test] + fn test_read_class_invalid_class_name() { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_directory = cargo_manifest.join("../classes"); let directory = Directory::new(classes_directory.to_string_lossy()); - let result = directory.read_class("Foo").await; + let result = directory.read_class("Foo"); assert!(matches!(result, Err(ClassNotFound(_)))); - Ok(()) } } diff --git a/ristretto_classloader/src/class_path_entry/jar.rs b/ristretto_classloader/src/class_path_entry/jar.rs index 15cf3057..38083e1b 100644 --- a/ristretto_classloader/src/class_path_entry/jar.rs +++ b/ristretto_classloader/src/class_path_entry/jar.rs @@ -1,13 +1,13 @@ use crate::class_path_entry::manifest::Manifest; -use crate::Error::{ArchiveError, ClassNotFound, FileNotFound, ParseError}; +use crate::Error::{ArchiveError, ClassNotFound, FileNotFound, ParseError, PoisonedLock}; use crate::Result; +use reqwest::blocking::Client; use ristretto_classfile::ClassFile; use std::fmt::Debug; -use std::io; use std::path::PathBuf; use std::str::FromStr; -use std::sync::Arc; -use tokio::sync::RwLock; +use std::sync::{Arc, RwLock}; +use std::{fs, io}; use tracing::instrument; use zip::ZipArchive; @@ -63,9 +63,9 @@ impl Jar { /// /// # Errors /// if the manifest cannot be read. - pub async fn manifest(&self) -> Result { + pub fn manifest(&self) -> Result { let file_name = "META-INF/MANIFEST.MF"; - let Some(file) = self.read_file(file_name).await? else { + let Some(file) = self.read_file(file_name)? else { return Err(FileNotFound(file_name.to_string())); }; let file = String::from_utf8(file).map_err(|error| ParseError(error.to_string()))?; @@ -78,9 +78,12 @@ impl Jar { /// # Errors /// if the file is not found or cannot be read. #[instrument(level = "trace", fields(name = ?name.as_ref()), skip(self))] - pub async fn read_file>(&self, name: S) -> Result>> { - let mut archive = self.archive.write().await; - archive.load_file(name.as_ref()).await + pub fn read_file>(&self, name: S) -> Result>> { + let mut archive = self + .archive + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + archive.load_file(name.as_ref()) } /// Read a class from the jar. @@ -88,14 +91,17 @@ impl Jar { /// # Errors /// if the class file is not found or cannot be read. #[instrument(level = "trace", fields(name = ?name.as_ref()), skip(self))] - pub async fn read_class>(&self, name: S) -> Result { + pub fn read_class>(&self, name: S) -> Result { let name = name.as_ref(); - let mut archive = self.archive.write().await; - let class_file = if archive.is_module().await? { - let name = format!("classes.{name}"); - archive.load_class_file(name.as_str()).await? + let mut archive = self + .archive + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + let class_file = if archive.is_module()? { + let name = format!("classes/{name}"); + archive.load_class_file(name.as_str())? } else { - archive.load_class_file(name).await? + archive.load_class_file(name)? }; let Some(class_file) = class_file else { return Err(ClassNotFound(name.to_string())); @@ -113,7 +119,7 @@ impl PartialEq for Jar { } /// The source of the archive. -#[allow(clippy::struct_field_names)] +#[expect(clippy::struct_field_names)] #[derive(Debug)] struct Archive { path: Option, @@ -167,22 +173,19 @@ impl Archive { /// /// # Errors /// if the archive cannot be read. - async fn zip_archive(&mut self) -> Result<&mut ZipArchive>>> { + fn zip_archive(&mut self) -> Result<&mut ZipArchive>>> { if let Some(ref mut zip_archive) = self.zip_archive { return Ok(zip_archive); } if let Some(path) = &self.path { - #[cfg(target_arch = "wasm32")] - let bytes = std::fs::read(path)?; - #[cfg(not(target_arch = "wasm32"))] - let bytes = tokio::fs::read(path).await?; + let bytes = fs::read(path)?; let cursor = io::Cursor::new(bytes); let archive = ZipArchive::new(cursor)?; self.zip_archive = Some(archive); } else if let Some(url) = &self.url { - let client = reqwest::Client::new(); - let bytes = client.get(url).send().await?.bytes().await?.to_vec(); + let client = Client::new(); + let bytes = client.get(url).send()?.bytes()?.to_vec(); let cursor = io::Cursor::new(bytes); let archive = ZipArchive::new(cursor)?; self.zip_archive = Some(archive); @@ -205,11 +208,10 @@ impl Archive { /// /// # Errors /// if the jar cannot be read or the class file cannot be loaded. - #[allow(clippy::case_sensitive_file_extension_comparisons)] #[instrument(level = "trace")] - async fn load_class_file(&mut self, class_name: &str) -> Result> { - let class_file_name = format!("{}.class", class_name.replace('.', "/")); - let file = self.load_file(&class_file_name).await?; + fn load_class_file(&mut self, class_name: &str) -> Result> { + let class_file_name = format!("{class_name}.class"); + let file = self.load_file(&class_file_name)?; if let Some(bytes) = file { let mut cursor = io::Cursor::new(bytes); let class_file = ClassFile::from_bytes(&mut cursor)?; @@ -223,10 +225,9 @@ impl Archive { /// /// # Errors /// if the jar cannot be read or the class file cannot be loaded. - #[allow(clippy::case_sensitive_file_extension_comparisons)] #[instrument(level = "trace")] - async fn load_file(&mut self, file_name: &str) -> Result>> { - let zip_archive = self.zip_archive().await?; + fn load_file(&mut self, file_name: &str) -> Result>> { + let zip_archive = self.zip_archive()?; if let Some(index) = zip_archive.index_for_name(file_name) { let mut file = zip_archive.by_index(index)?; let file_size = usize::try_from(file.size())?; @@ -241,11 +242,11 @@ impl Archive { /// /// # Errors /// if the module information cannot be read. - async fn is_module(&mut self) -> Result { + fn is_module(&mut self) -> Result { if let Some(is_module) = self.is_module { Ok(is_module) } else { - let module_info = self.load_class_file("classes.module-info").await?; + let module_info = self.load_class_file("classes/module-info")?; let is_module = module_info.is_some(); self.is_module = Some(is_module); Ok(is_module) @@ -272,7 +273,7 @@ mod tests { use std::path::PathBuf; use zip::write::SimpleFileOptions; - #[test_log::test] + #[test] fn test_new() { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_jar = cargo_manifest.join("../classes/classes.jar"); @@ -280,19 +281,19 @@ mod tests { assert!(jar.name().ends_with("classes.jar")); } - #[test_log::test(tokio::test)] - async fn test_from_bytes() -> Result<()> { + #[test] + fn test_from_bytes() -> Result<()> { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_jar = cargo_manifest.join("../classes/classes.jar"); - let bytes = tokio::fs::read(classes_jar).await?; + let bytes = fs::read(classes_jar)?; let jar = Jar::from_bytes("test", bytes); assert_eq!("test", jar.name().as_str()); - let class_file = jar.read_class("HelloWorld").await?; + let class_file = jar.read_class("HelloWorld")?; assert_eq!("HelloWorld", class_file.class_name()?); Ok(()) } - #[test_log::test] + #[test] fn test_equality() { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_jar = cargo_manifest.join("../classes/classes.jar"); @@ -301,46 +302,45 @@ mod tests { assert_eq!(jar1, jar2); } - #[test_log::test(tokio::test)] - async fn test_read_class() -> Result<()> { + #[test] + fn test_read_class() -> Result<()> { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_jar = cargo_manifest.join("../classes/classes.jar"); let jar = Jar::new(classes_jar.to_string_lossy()); // Read the class file twice to test caching for _ in 0..2 { - let class_file = jar.read_class("HelloWorld").await?; + let class_file = jar.read_class("HelloWorld")?; assert_eq!("HelloWorld", class_file.class_name()?); } // Test class file initialization - let result = jar.read_class("Foo").await; + let result = jar.read_class("Foo"); assert!(matches!(result, Err(ClassNotFound(_)))); Ok(()) } - #[test_log::test(tokio::test)] - async fn test_read_manifest() -> Result<()> { + #[test] + fn test_read_manifest() -> Result<()> { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_jar = cargo_manifest.join("../classes/classes.jar"); let jar = Jar::new(classes_jar.to_string_lossy()); - let manifest = jar.manifest().await?; + let manifest = jar.manifest()?; assert_eq!(Some("1.0"), manifest.attribute(MANIFEST_VERSION)); assert_eq!(Some("HelloWorld"), manifest.attribute(MAIN_CLASS)); Ok(()) } - #[test_log::test(tokio::test)] - async fn test_read_class_invalid_class_name() -> Result<()> { + #[test] + fn test_read_class_invalid_class_name() { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_jar = cargo_manifest.join("../classes/classes.jar"); let jar = Jar::new(classes_jar.to_string_lossy()); - let result = jar.read_class("Foo").await; + let result = jar.read_class("Foo"); assert!(matches!(result, Err(ClassNotFound(_)))); - Ok(()) } - #[test_log::test(tokio::test)] - async fn test_bad_class_file() -> Result<()> { + #[test] + fn test_bad_class_file() -> Result<()> { let temp_dir = tempfile::tempdir()?; // Create a jar with a bad class file @@ -352,13 +352,13 @@ mod tests { // Test reading the class file let jar = Jar::new(jar_path.to_string_lossy()); - let result = jar.read_class("HelloWorld").await; + let result = jar.read_class("HelloWorld"); assert!(matches!(result, Err(ClassFileError(_)))); Ok(()) } - #[test_log::test(tokio::test)] - async fn test_invalid_class_file() -> Result<()> { + #[test] + fn test_invalid_class_file() -> Result<()> { let temp_dir = tempfile::tempdir()?; // Create an invalid class file @@ -378,13 +378,13 @@ mod tests { // Test reading the class file let jar = Jar::new(jar_path.to_string_lossy()); - let result = jar.read_class("HelloWorld").await; + let result = jar.read_class("HelloWorld"); assert!(matches!(result, Err(ClassFileError(_)))); Ok(()) } - #[test_log::test(tokio::test)] - async fn test_archive_zip_archive_error() { + #[test] + fn test_archive_zip_archive_error() { let mut archive = Archive { path: None, url: None, @@ -392,20 +392,18 @@ mod tests { zip_archive: None, is_module: None, }; - let result = archive.zip_archive().await; + let result = archive.zip_archive(); assert!(matches!(result, Err(ArchiveError(_)))); } #[cfg(feature = "url")] - #[test_log::test(tokio::test)] - async fn test_from_url_read_class() -> Result<()> { + #[test] + fn test_from_url_read_class() -> Result<()> { let url = "https://repo1.maven.org/maven2/org/springframework/boot/spring-boot/3.3.0/spring-boot-3.3.0.jar"; let url = Jar::from_url(url); // Read the class file twice to test caching for _ in 0..2 { - let class_file = url - .read_class("org.springframework.boot.SpringApplication") - .await?; + let class_file = url.read_class("org/springframework/boot/SpringApplication")?; assert_eq!( "org/springframework/boot/SpringApplication", class_file.class_name()? @@ -413,7 +411,7 @@ mod tests { } // Test class file initialization - let result = url.read_class("Foo").await; + let result = url.read_class("Foo"); assert!(matches!(result, Err(ClassNotFound(_)))); Ok(()) } diff --git a/ristretto_classloader/src/class_path_entry/manifest.rs b/ristretto_classloader/src/class_path_entry/manifest.rs index ede66d9c..bcbbd557 100644 --- a/ristretto_classloader/src/class_path_entry/manifest.rs +++ b/ristretto_classloader/src/class_path_entry/manifest.rs @@ -146,7 +146,7 @@ mod tests { SHA-256-Digest: 0987654321fedcba "}; - #[test_log::test] + #[test] fn test_attribute() -> Result<()> { let manifest = Manifest::from_str(EXPECTED)?; let main_class = manifest.attribute(MAIN_CLASS).expect("main class"); @@ -154,7 +154,7 @@ mod tests { Ok(()) } - #[test_log::test] + #[test] fn test_serde() -> Result<()> { let manifest = Manifest::from_str(EXPECTED)?; let serialized = manifest.to_string(); diff --git a/ristretto_classloader/src/class_path_entry/model.rs b/ristretto_classloader/src/class_path_entry/model.rs index 4f4da874..e611ce43 100644 --- a/ristretto_classloader/src/class_path_entry/model.rs +++ b/ristretto_classloader/src/class_path_entry/model.rs @@ -44,10 +44,10 @@ impl ClassPathEntry { /// # Errors /// if the class file cannot be read. #[instrument(level = "trace", fields(name = ?name.as_ref()), skip(self))] - pub async fn read_class>(&self, name: S) -> Result { + pub fn read_class>(&self, name: S) -> Result { match self { - ClassPathEntry::Directory(directory) => directory.read_class(name).await, - ClassPathEntry::Jar(jar) => jar.read_class(name).await, + ClassPathEntry::Directory(directory) => directory.read_class(name), + ClassPathEntry::Jar(jar) => jar.read_class(name), } } } @@ -68,7 +68,7 @@ mod tests { // Directory Tests // - #[test_log::test] + #[test] fn test_new_directory() { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_directory = cargo_manifest.join("../classes"); @@ -81,12 +81,12 @@ mod tests { ); } - #[test_log::test(tokio::test)] - async fn test_read_class_directory() -> Result<()> { + #[test] + fn test_read_class_directory() -> Result<()> { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_directory = cargo_manifest.join("../classes"); let class_path_entry = ClassPathEntry::new(classes_directory.to_string_lossy()); - let class_file = class_path_entry.read_class("HelloWorld").await?; + let class_file = class_path_entry.read_class("HelloWorld")?; assert!(matches!(class_path_entry, ClassPathEntry::Directory(_))); assert_eq!( @@ -101,7 +101,7 @@ mod tests { // Jar Tests // - #[test_log::test] + #[test] fn test_new_jar() { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_jar = cargo_manifest.join("../classes/classes.jar"); @@ -114,12 +114,12 @@ mod tests { ); } - #[test_log::test(tokio::test)] - async fn test_read_class_jar() -> Result<()> { + #[test] + fn test_read_class_jar() -> Result<()> { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_jar = cargo_manifest.join("../classes/classes.jar"); let class_path_entry = ClassPathEntry::new(classes_jar.to_string_lossy()); - let class_file = class_path_entry.read_class("HelloWorld").await?; + let class_file = class_path_entry.read_class("HelloWorld")?; assert!(matches!(class_path_entry, ClassPathEntry::Jar(_))); assert_eq!( @@ -135,7 +135,7 @@ mod tests { // #[cfg(feature = "url")] - #[test_log::test] + #[test] fn test_new_url() { let url = "https://repo1.maven.org/maven2/org/springframework/boot/spring-boot/3.3.0/spring-boot-3.3.0.jar"; let class_path_entry = ClassPathEntry::new(url); @@ -145,13 +145,12 @@ mod tests { } #[cfg(feature = "url")] - #[test_log::test(tokio::test)] - async fn test_read_class_url() -> Result<()> { + #[test] + fn test_read_class_url() -> Result<()> { let url = "https://repo1.maven.org/maven2/org/springframework/boot/spring-boot/3.3.0/spring-boot-3.3.0.jar"; let class_path_entry = ClassPathEntry::new(url); - let class_file = class_path_entry - .read_class("org.springframework.boot.SpringApplication") - .await?; + let class_file = + class_path_entry.read_class("org/springframework/boot/SpringApplication")?; assert!(matches!(class_path_entry, ClassPathEntry::Jar(_))); assert_eq!(class_path_entry.name(), url); diff --git a/ristretto_classloader/src/concurrent_vec.rs b/ristretto_classloader/src/concurrent_vec.rs new file mode 100644 index 00000000..8342071c --- /dev/null +++ b/ristretto_classloader/src/concurrent_vec.rs @@ -0,0 +1,328 @@ +use crate::Error::PoisonedLock; +use crate::Result; +use std::fmt; +use std::fmt::{Debug, Display, Formatter}; +use std::ops::Deref; +use std::sync::{Arc, RwLock}; + +/// A concurrent vector. +pub struct ConcurrentVec { + inner: Arc>>, +} + +impl ConcurrentVec { + /// Create a new concurrent vector. + #[must_use] + pub fn new() -> Self { + Self::with_capacity(0) + } + + /// Create a new concurrent vector with the defined capacity. + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + Self::from(Vec::with_capacity(capacity)) + } + + /// Create a new concurrent vector from a vector. + #[must_use] + pub fn from(values: Vec) -> Self { + ConcurrentVec { + inner: Arc::new(RwLock::new(values)), + } + } + + /// Push a value onto the vector. + /// + /// # Errors + /// if the lock is poisoned. + pub fn push(&self, value: T) -> Result<()> { + let mut vec = self + .inner + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + vec.push(value); + Ok(()) + } + + /// Pop a value from the vector. + /// + /// # Errors + /// if the lock is poisoned. + pub fn pop(&self) -> Result> { + let mut vec = self + .inner + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + Ok(vec.pop()) + } + + /// Get a value from the vector. + /// + /// # Errors + /// if the lock is poisoned. + pub fn get(&self, index: usize) -> Result> + where + T: Clone, + { + let vec = self + .inner + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; + Ok(vec.get(index).cloned()) + } + + /// Set a value in the vector. + /// + /// # Errors + /// if the lock is poisoned. + pub fn set(&self, index: usize, value: T) -> Result> { + let mut vec = self + .inner + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + let value = if index < vec.len() { + Some(std::mem::replace(&mut vec[index], value)) + } else { + None + }; + Ok(value) + } + + /// Get the length of the vector. + /// + /// # Errors + /// if the lock is poisoned. + pub fn len(&self) -> Result { + let vec = self + .inner + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; + Ok(vec.len()) + } + + /// Check if the vector is empty. + /// + /// # Errors + /// if the lock is poisoned. + pub fn is_empty(&self) -> Result { + Ok(self.len()? == 0) + } + + /// Get the capacity of the vector. + /// + /// # Errors + /// if the lock is poisoned. + pub fn capacity(&self) -> Result { + let vec = self + .inner + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; + Ok(vec.capacity()) + } + + /// Remove a value from the vector. + /// + /// # Errors + /// if the lock is poisoned. + pub fn remove(&self, index: usize) -> Result> { + let mut vec = self + .inner + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + let value = if index < vec.len() { + Some(vec.remove(index)) + } else { + None + }; + Ok(value) + } + + /// Convert to a vector. + /// + /// # Errors + /// if the lock is poisoned. + pub fn to_vec(&self) -> Result> { + let vec = self + .inner + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; + Ok(vec.clone()) + } +} + +impl Clone for ConcurrentVec { + /// Clone the concurrent vector. + fn clone(&self) -> Self { + ConcurrentVec { + inner: Arc::clone(&self.inner), + } + } +} + +impl Debug for ConcurrentVec { + /// Debug the concurrent vector. + #[expect(clippy::unwrap_in_result)] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let vec = self.inner.read().expect("poisoned lock"); + write!(f, "{:?}", &*vec) + } +} + +impl Default for ConcurrentVec { + /// Create a default concurrent vector. + fn default() -> Self { + Self::new() + } +} + +impl Deref for ConcurrentVec { + type Target = RwLock>; + + /// Get a reference to the inner vector. + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl Display for ConcurrentVec { + /// Display the concurrent vector. + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let vec = self.inner.read().map_err(|_| fmt::Error)?; + write!(f, "{:?}", &*vec) + } +} + +impl PartialEq for ConcurrentVec { + /// Compare two concurrent vectors. + fn eq(&self, other: &Self) -> bool { + let vec = self.inner.read().expect("poisoned lock"); + let other = other.inner.read().expect("poisoned lock"); + *vec == *other + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_push() -> Result<()> { + let vec = ConcurrentVec::new(); + vec.push(1)?; + vec.push(2)?; + vec.push(3)?; + assert_eq!(vec.len()?, 3); + Ok(()) + } + + #[test] + fn test_pop() -> Result<()> { + let vec = ConcurrentVec::new(); + vec.push(1)?; + vec.push(2)?; + vec.push(3)?; + assert_eq!(vec.pop()?, Some(3)); + assert_eq!(vec.pop()?, Some(2)); + assert_eq!(vec.pop()?, Some(1)); + assert_eq!(vec.pop()?, None); + Ok(()) + } + + #[test] + fn test_get() -> Result<()> { + let vec = ConcurrentVec::new(); + vec.push(1)?; + vec.push(2)?; + vec.push(3)?; + assert_eq!(vec.get(0)?, Some(1)); + assert_eq!(vec.get(1)?, Some(2)); + assert_eq!(vec.get(2)?, Some(3)); + assert_eq!(vec.get(3)?, None); + Ok(()) + } + + #[test] + fn test_set() -> Result<()> { + let vec = ConcurrentVec::new(); + vec.push(1)?; + vec.push(2)?; + vec.push(3)?; + assert_eq!(vec.set(0, 4)?, Some(1)); + assert_eq!(vec.set(1, 5)?, Some(2)); + assert_eq!(vec.set(2, 6)?, Some(3)); + assert_eq!(vec.set(3, 7)?, None); + assert_eq!(vec.get(0)?, Some(4)); + assert_eq!(vec.get(1)?, Some(5)); + assert_eq!(vec.get(2)?, Some(6)); + Ok(()) + } + + #[test] + fn test_len() -> Result<()> { + let vec = ConcurrentVec::new(); + assert_eq!(vec.len()?, 0); + vec.push(1)?; + assert_eq!(vec.len()?, 1); + vec.push(2)?; + assert_eq!(vec.len()?, 2); + vec.push(3)?; + assert_eq!(vec.len()?, 3); + Ok(()) + } + + #[test] + fn test_is_empty() -> Result<()> { + let vec = ConcurrentVec::new(); + assert!(vec.is_empty()?); + vec.push(1)?; + assert!(!vec.is_empty()?); + vec.push(2)?; + assert!(!vec.is_empty()?); + vec.push(3)?; + assert!(!vec.is_empty()?); + Ok(()) + } + + #[test] + fn test_capacity() -> Result<()> { + let vec: ConcurrentVec = ConcurrentVec::new(); + assert_eq!(vec.capacity()?, 0); + let vec: ConcurrentVec = ConcurrentVec::with_capacity(10); + assert_eq!(vec.capacity()?, 10); + Ok(()) + } + + #[test] + fn test_remove() -> Result<()> { + let vec = ConcurrentVec::new(); + vec.push(1)?; + vec.push(2)?; + vec.push(3)?; + assert_eq!(vec.remove(1)?, Some(2)); + assert_eq!(vec.remove(1)?, Some(3)); + assert_eq!(vec.remove(1)?, None); + Ok(()) + } + + #[test] + fn test_clone() -> Result<()> { + let vec = ConcurrentVec::new(); + vec.push(1)?; + vec.push(2)?; + vec.push(3)?; + let clone = vec.clone(); + assert_eq!(vec, clone); + Ok(()) + } + + #[test] + fn test_debug() -> Result<()> { + let vec = ConcurrentVec::new(); + vec.push(1)?; + vec.push(2)?; + vec.push(3)?; + let debug = format!("{vec:?}"); + assert_eq!("[1, 2, 3]", debug); + Ok(()) + } +} diff --git a/ristretto_classloader/src/error.rs b/ristretto_classloader/src/error.rs index 1130edbd..9ee466a6 100644 --- a/ristretto_classloader/src/error.rs +++ b/ristretto_classloader/src/error.rs @@ -13,12 +13,34 @@ pub enum Error { /// A class was not found #[error("Class not found: {0}")] ClassNotFound(String), + /// Specified field not found + #[error("Field not found: {class_name}.{field_name}")] + FieldNotFound { + class_name: String, + field_name: String, + }, /// A file was not found #[error("File not found: {0}")] FileNotFound(String), + /// Illegal access attempt + #[error("Illegal access: {0}")] + IllegalAccessError(String), + /// An error occurred while parsing a method descriptor + #[error("Invalid method descriptor: {0}")] + InvalidMethodDescriptor(String), + /// Invalid value type + #[error("Invalid value type: {0}")] + InvalidValueType(String), /// An error occurred while performing an IO operation #[error(transparent)] IoError(#[from] std::io::Error), + /// Specified method not found + #[error("Method not found: {class_name}.{method_name}{method_descriptor}")] + MethodNotFound { + class_name: String, + method_name: String, + method_descriptor: String, + }, /// Error parsing data #[error("Parse error: {0}")] ParseError(String), diff --git a/ristretto_classloader/src/field.rs b/ristretto_classloader/src/field.rs new file mode 100644 index 00000000..51c73136 --- /dev/null +++ b/ristretto_classloader/src/field.rs @@ -0,0 +1,249 @@ +use crate::Error::{IllegalAccessError, PoisonedLock}; +use crate::{Result, Value}; +use ristretto_classfile::attributes::Attribute; +use ristretto_classfile::{BaseType, ClassFile, ConstantPool, FieldAccessFlags, FieldType}; +use std::sync::{Arc, RwLock}; + +#[derive(Clone, Debug)] +pub struct Field { + access_flags: FieldAccessFlags, + field_type: FieldType, + name: String, + value: Arc>, +} + +impl Field { + /// Create a new class field with the given parameters. + #[must_use] + pub fn new( + access_flags: FieldAccessFlags, + field_type: FieldType, + name: String, + value: Value, + ) -> Self { + Self { + access_flags, + field_type, + name, + value: Arc::new(RwLock::new(value)), + } + } + + /// Create a new class field with the given definition. + /// + /// # Errors + /// if the field name cannot be read. + pub fn from(class_file: &ClassFile, definition: &ristretto_classfile::Field) -> Result { + let constant_pool = &class_file.constant_pool; + let access_flags = definition.access_flags; + let name = constant_pool.try_get_utf8(definition.name_index)?.clone(); + let field_type = definition.field_type.clone(); + let mut value = if access_flags.contains(FieldAccessFlags::FINAL) { + // Final fields are initialized by the class or object initializer + Value::Unused + } else { + match field_type { + FieldType::Base( + BaseType::Boolean + | BaseType::Byte + | BaseType::Char + | BaseType::Int + | BaseType::Short, + ) => Value::Int(0), + FieldType::Base(BaseType::Double) => Value::Double(0.0), + FieldType::Base(BaseType::Float) => Value::Float(0.0), + FieldType::Base(BaseType::Long) => Value::Long(0), + FieldType::Object(_) | FieldType::Array(_) => Value::Object(None), + } + }; + + if access_flags.contains(FieldAccessFlags::STATIC) { + for attribute in &definition.attributes { + if let Attribute::ConstantValue { + constant_value_index, + .. + } = attribute + { + value = get_typed_value(&field_type, constant_pool, *constant_value_index)?; + break; + } + } + } + + Ok(Self { + access_flags, + field_type, + name, + value: Arc::new(RwLock::new(value)), + }) + } + + /// Get the field access flags. + #[must_use] + pub fn access_flags(&self) -> &FieldAccessFlags { + &self.access_flags + } + + /// Get the field name. + #[must_use] + pub fn name(&self) -> &str { + &self.name + } + + /// Get the field type. + #[must_use] + pub fn field_type(&self) -> &FieldType { + &self.field_type + } + + /// Get the field value. + /// + /// # Errors + pub fn value(&self) -> Result { + let value = self + .value + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; + Ok(value.clone()) + } + + /// Set the field value. + /// + /// # Errors + /// if the field is final. + pub fn set_value(&self, value: Value) -> Result<()> { + let mut guarded_value = self + .value + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + // TODO: Check that the field is not final + // if self.access_flags.contains(FieldAccessFlags::FINAL) && *guarded_value != Value::Unused { + // let error = format!("Cannot set final field: {}", self.name); + // return Err(IllegalAccessError(error)); + // } + // Check that the value permissible for the field type + // See: https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-6.html#jvms-6.5.putstatic + match self.field_type { + FieldType::Base( + BaseType::Boolean + | BaseType::Byte + | BaseType::Char + | BaseType::Int + | BaseType::Short, + ) => { + if !matches!(value, Value::Int(_)) { + return Err(IllegalAccessError(format!( + "Invalid value for {} field", + self.field_type + ))); + } + } + FieldType::Base(BaseType::Double) => { + if !matches!(value, Value::Double(_)) { + return Err(IllegalAccessError( + "Invalid value for double field".to_string(), + )); + } + } + FieldType::Base(BaseType::Float) => { + if !matches!(value, Value::Float(_)) { + return Err(IllegalAccessError( + "Invalid value for float field".to_string(), + )); + } + } + FieldType::Base(BaseType::Long) => { + if !matches!(value, Value::Long(_)) { + return Err(IllegalAccessError( + "Invalid value for long field".to_string(), + )); + } + } + FieldType::Object(_) | FieldType::Array(_) => { + // TODO: Check that the value is of the correct type + if !matches!(value, Value::Object(_)) { + return Err(IllegalAccessError( + "Invalid value for array field".to_string(), + )); + } + } + } + + *guarded_value = value; + Ok(()) + } + + /// Set the field value without checking field permissions or type. + /// + /// # Errors + /// if the field is final. + pub fn unsafe_set_value(&self, value: Value) -> Result<()> { + let mut guarded_value = self + .value + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + *guarded_value = value; + Ok(()) + } +} + +fn get_typed_value( + field_type: &FieldType, + constant_pool: &ConstantPool, + index: u16, +) -> Result { + let value = match field_type { + FieldType::Base(BaseType::Boolean) => { + let value = constant_pool.try_get_integer(index)?; + Value::Int(*value) + } + FieldType::Base(BaseType::Byte) => { + let value = constant_pool.try_get_integer(index)?; + Value::Int(*value) + } + FieldType::Base(BaseType::Char) => { + let value = constant_pool.try_get_integer(index)?; + Value::Int(*value) + } + FieldType::Base(BaseType::Double) => { + let value = constant_pool.try_get_double(index)?; + Value::Double(*value) + } + FieldType::Base(BaseType::Float) => { + let value = constant_pool.try_get_float(index)?; + Value::Float(*value) + } + FieldType::Base(BaseType::Int) => { + let value = constant_pool.try_get_integer(index)?; + Value::Int(*value) + } + FieldType::Base(BaseType::Long) => { + let value = constant_pool.try_get_long(index)?; + Value::Long(*value) + } + FieldType::Base(BaseType::Short) => { + let value = constant_pool.try_get_integer(index)?; + Value::Int(*value) + } + FieldType::Object(_class_name) => { + // Objects are loaded through a class initializer + Value::Unused + } + FieldType::Array(_field_type) => { + // Arrays are loaded through a class initializer + Value::Unused + } + }; + Ok(value) +} + +impl PartialEq for Field { + fn eq(&self, other: &Self) -> bool { + let value = self.value.read().expect("poisoned lock"); + let other_value = other.value.read().expect("poisoned lock"); + self.access_flags == other.access_flags + && self.field_type == other.field_type + && self.name == other.name + && *value == *other_value + } +} diff --git a/ristretto_classloader/src/lib.rs b/ristretto_classloader/src/lib.rs index 7a9f806c..7c621ed0 100644 --- a/ristretto_classloader/src/lib.rs +++ b/ristretto_classloader/src/lib.rs @@ -7,7 +7,7 @@ //! //! ## Getting Started //! -//! Implementation of a [JVM Class Loader](https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-4.html) +//! Implementation of a [JVM Class Loader](https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-4.html) //! that is used to load Java classes. Classes can be loaded from the file system or from a URL; //! jar and modules are supported. A runtime Java class loader can be created from any version of //! [AWS Corretto](https://github.com/corretto). The runtime class loader will download and install @@ -25,12 +25,11 @@ //! use ristretto_classloader::{runtime, ClassLoader, Result}; //! use std::sync::Arc; //! -//! #[tokio::main] -//! async fn main() -> Result<()> { -//! let (version, class_loader) = runtime::class_loader("21").await?; -//! let class_name = "java.util.HashMap"; +//! fn main() -> Result<()> { +//! let (version, class_loader) = runtime::class_loader("21")?; +//! let class_name = "java/util/HashMap"; //! println!("Loading {class_name} from Java runtime {version}"); -//! let class = class_loader.load(class_name).await?; +//! let class = class_loader.load(class_name)?; //! println!("{class:?}"); //! Ok(()) //! } @@ -46,9 +45,10 @@ //! //! ## Safety //! -//! These crates use `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% safe Rust. +//! This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% safe Rust. #![forbid(unsafe_code)] +#![forbid(clippy::allow_attributes)] #![allow(dead_code)] #![deny(clippy::pedantic)] #![deny(clippy::unwrap_in_result)] @@ -58,11 +58,24 @@ mod class; mod class_loader; mod class_path; mod class_path_entry; +mod concurrent_vec; mod error; +mod field; +mod method; +mod object; +mod reference; pub mod runtime; +mod value; pub use class::Class; pub use class_loader::ClassLoader; pub use class_path::ClassPath; pub use class_path_entry::{manifest, ClassPathEntry, Manifest}; +pub use concurrent_vec::ConcurrentVec; pub use error::{Error, Result}; +pub use field::Field; +pub use method::Method; +pub use object::Object; +pub use reference::Reference; +pub use ristretto_classfile::{BaseType, FieldAccessFlags, FieldType, MethodAccessFlags}; +pub use value::Value; diff --git a/ristretto_classloader/src/method.rs b/ristretto_classloader/src/method.rs new file mode 100644 index 00000000..29e9c167 --- /dev/null +++ b/ristretto_classloader/src/method.rs @@ -0,0 +1,407 @@ +use crate::Error::InvalidMethodDescriptor; +use crate::Result; +use ristretto_classfile::attributes::{Attribute, Instruction, LineNumber}; +use ristretto_classfile::{BaseType, ClassFile, FieldType, MethodAccessFlags}; +use std::fmt::Display; + +#[derive(Clone, Debug, PartialEq)] +pub struct Method { + access_flags: MethodAccessFlags, + name: String, + descriptor: String, + parameters: Vec, + return_type: Option, + max_stack: usize, + max_locals: usize, + code: Vec, + line_numbers: Vec, +} + +impl Method { + /// Create a new class method + /// + /// # Errors + /// if the method descriptor cannot be parsed + pub fn new>( + access_flags: MethodAccessFlags, + name: S, + descriptor: S, + max_stack: usize, + max_locals: usize, + code: Vec, + line_numbers: Vec, + ) -> Result { + let (parameters, return_type) = Method::parse_descriptor(descriptor.as_ref())?; + Ok(Self { + access_flags, + name: name.as_ref().to_string(), + descriptor: descriptor.as_ref().to_string(), + parameters, + return_type, + max_stack, + max_locals, + code, + line_numbers, + }) + } + + /// Create a new class method with the given definition. + /// + /// # Errors + /// if the method name cannot be read. + pub fn from(class_file: &ClassFile, definition: &ristretto_classfile::Method) -> Result { + let constant_pool = &class_file.constant_pool; + let name = constant_pool.try_get_utf8(definition.name_index)?; + let descriptor = constant_pool.try_get_utf8(definition.descriptor_index)?; + let (max_stack, max_locals, code, line_numbers) = match definition + .attributes + .iter() + .find(|attribute| matches!(attribute, Attribute::Code { .. })) + { + Some(Attribute::Code { + max_stack, + max_locals, + code, + attributes, + .. + }) => { + let line_numbers = match attributes + .iter() + .find(|attribute| matches!(attribute, Attribute::LineNumberTable { .. })) + { + Some(Attribute::LineNumberTable { line_numbers, .. }) => { + // TODO: avoid cloning line numbers + line_numbers.clone() + } + _ => Vec::new(), + }; + ( + usize::from(*max_stack), + usize::from(*max_locals), + code.clone(), // TODO: avoid cloning code + line_numbers, + ) + } + _ => (0, 0, Vec::new(), Vec::new()), + }; + + Method::new( + definition.access_flags, + name.to_string(), + descriptor.to_string(), + max_stack, + max_locals, + code, + line_numbers, + ) + } + + /// Get the method access flags. + #[must_use] + pub fn access_flags(&self) -> &MethodAccessFlags { + &self.access_flags + } + + /// Check if the method is native. + #[must_use] + pub fn is_native(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::NATIVE) + } + + /// Check if the method is static. + #[must_use] + pub fn is_static(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::STATIC) + } + + /// Get the method name. + #[must_use] + pub fn name(&self) -> &str { + &self.name + } + + /// Get the method descriptor. + #[must_use] + pub fn descriptor(&self) -> &str { + &self.descriptor + } + + /// Get the method parameters. + #[must_use] + pub fn parameters(&self) -> &Vec { + &self.parameters + } + + /// Get the method return type. + #[must_use] + pub fn return_type(&self) -> Option<&FieldType> { + self.return_type.as_ref() + } + + /// Get the method identifier. + #[must_use] + pub fn identifier(&self) -> String { + format!("{}:{}", self.name, self.descriptor) + } + + /// Get the maximum stack size. + #[must_use] + pub fn max_stack(&self) -> usize { + self.max_stack + } + + /// Get the maximum number of local variables. + #[must_use] + pub fn max_locals(&self) -> usize { + self.max_locals + } + + /// Get the code. + #[must_use] + pub fn code(&self) -> &Vec { + &self.code + } + + /// Get the line number for a given program counter. + /// + /// # Errors + /// if the program counter does not index into a valid line number + #[must_use] + pub fn line_number(&self, program_counter: usize) -> usize { + if self.line_numbers.is_empty() { + return 0; + }; + let program_counter = u16::try_from(program_counter).unwrap_or_default(); + let index = self + .line_numbers + .binary_search_by(|line_number| line_number.start_pc.cmp(&program_counter)) + .unwrap_or_else(|index| index - 1); + let line_number = self.line_numbers[index].line_number; + usize::from(line_number) + } + + /// Parse the method descriptor. The descriptor is a string representing the method signature. + /// The descriptor has the following format: + /// + /// See: + /// + /// # Errors + /// if the descriptor cannot be parsed + pub fn parse_descriptor(descriptor: &str) -> Result<(Vec, Option)> { + let mut chars = descriptor.chars().peekable(); + let mut parameters = Vec::new(); + let mut return_type = None; + + if chars.next() != Some('(') { + return Err(InvalidMethodDescriptor(descriptor.to_string())); + } + + while let Some(&ch) = chars.peek() { + if ch == ')' { + chars.next(); + break; + } + parameters.push(Self::parse_field_type(descriptor, &mut chars)?); + } + + match chars.next() { + Some('V') => {} + Some(ch) => { + return_type = Some(Self::parse_field_type( + descriptor, + &mut std::iter::once(ch).chain(chars), + )?); + } + None => return Err(InvalidMethodDescriptor(descriptor.to_string())), + } + + Ok((parameters, return_type)) + } + + /// Parse the field type. + /// + /// # Errors + /// if the field type cannot be parsed + fn parse_field_type(descriptor: &str, chars: &mut I) -> Result + where + I: Iterator, + { + match chars.next() { + Some('L') => { + let mut class_name = String::new(); + for ch in chars.by_ref() { + if ch == ';' { + break; + } + class_name.push(ch); + } + Ok(FieldType::Object(class_name)) + } + Some('[') => { + let component_type = Self::parse_field_type(descriptor, chars)?; + Ok(FieldType::Array(Box::new(component_type))) + } + Some(value) => { + let base_type = BaseType::parse(value)?; + Ok(FieldType::Base(base_type)) + } + None => Err(InvalidMethodDescriptor(descriptor.to_string())), + } + } +} + +impl Display for Method { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let parameters = self + .parameters + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(", "); + let return_type = match &self.return_type { + Some(field_type) => field_type.to_string(), + None => "void".to_string(), + }; + write!(f, "{}({parameters}) -> {return_type}", self.name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ristretto_classfile::ConstantPool; + + #[test] + fn test_method() -> Result<()> { + let mut constant_pool = ConstantPool::new(); + let name_index = constant_pool.add_utf8("test")?; + let descriptor_index = constant_pool.add_utf8("()V")?; + let code_attribute = Attribute::Code { + name_index: 0, + max_stack: 1, + max_locals: 2, + code: Vec::new(), + exceptions: Vec::new(), + attributes: Vec::new(), + }; + let method = ristretto_classfile::Method { + name_index, + descriptor_index, + attributes: vec![code_attribute], + ..Default::default() + }; + let class_file = ClassFile { + constant_pool, + methods: vec![method.clone()], + ..Default::default() + }; + let method = Method::from(&class_file, &method)?; + assert_eq!(method.access_flags(), &MethodAccessFlags::empty()); + assert_eq!(method.name(), "test"); + assert_eq!(method.descriptor(), "()V"); + assert_eq!(method.identifier(), "test:()V"); + assert!(method.parameters().is_empty()); + assert_eq!(method.return_type(), None); + assert_eq!(method.max_stack, 1); + assert_eq!(method.max_locals, 2); + assert!(method.code.is_empty()); + Ok(()) + } + + #[test] + fn test_parse_descriptor() -> Result<()> { + let (parameters, return_type) = Method::parse_descriptor("()V")?; + assert!(parameters.is_empty()); + assert_eq!(return_type, None); + + let (parameters, return_type) = Method::parse_descriptor("()I")?; + assert!(parameters.is_empty()); + assert_eq!(return_type, Some(FieldType::Base(BaseType::Int))); + + let (parameters, return_type) = Method::parse_descriptor("(I)V")?; + assert_eq!(parameters, vec![FieldType::Base(BaseType::Int)]); + assert_eq!(return_type, None); + + let (parameters, return_type) = Method::parse_descriptor("(Ljava.lang.String;)V")?; + assert_eq!( + parameters, + vec![FieldType::Object("java.lang.String".to_string())] + ); + assert_eq!(return_type, None); + + let (parameters, return_type) = Method::parse_descriptor("(Ljava.lang.String;I)V")?; + assert_eq!( + parameters, + vec![ + FieldType::Object("java.lang.String".to_string()), + FieldType::Base(BaseType::Int) + ] + ); + assert_eq!(return_type, None); + + let (parameters, return_type) = Method::parse_descriptor("(Ljava.lang.String;I)I")?; + assert_eq!( + parameters, + vec![ + FieldType::Object("java.lang.String".to_string()), + FieldType::Base(BaseType::Int) + ] + ); + assert_eq!(return_type, Some(FieldType::Base(BaseType::Int))); + + Ok(()) + } + + #[test] + fn test_parse_descriptor_invalid() { + let descriptor = String::new(); + assert!(matches!( + Method::parse_descriptor(&descriptor), + Err(InvalidMethodDescriptor(_)) + )); + + let descriptor = "()"; + assert!(matches!( + Method::parse_descriptor(descriptor), + Err(InvalidMethodDescriptor(_)) + )); + } + + #[test] + fn test_parse_field_type() -> Result<()> { + assert_eq!( + Method::parse_field_type("", &mut "I".chars())?, + FieldType::Base(BaseType::Int) + ); + assert_eq!( + Method::parse_field_type("", &mut "J".chars())?, + FieldType::Base(BaseType::Long) + ); + assert_eq!( + Method::parse_field_type("", &mut "S".chars())?, + FieldType::Base(BaseType::Short) + ); + assert_eq!( + Method::parse_field_type("", &mut "Z".chars())?, + FieldType::Base(BaseType::Boolean) + ); + assert_eq!( + Method::parse_field_type("", &mut "Ljava.lang.String;".chars())?, + FieldType::Object("java.lang.String".to_string()) + ); + assert_eq!( + Method::parse_field_type("", &mut "[Ljava.lang.String;".chars())?, + FieldType::Array(Box::new(FieldType::Object("java.lang.String".to_string()))) + ); + Ok(()) + } + + #[test] + fn test_parse_field_type_invalid() { + let descriptor = String::new(); + assert!(matches!( + Method::parse_field_type(&descriptor, &mut descriptor.chars()), + Err(InvalidMethodDescriptor(_)) + )); + } +} diff --git a/ristretto_classloader/src/object.rs b/ristretto_classloader/src/object.rs new file mode 100644 index 00000000..cb2095b2 --- /dev/null +++ b/ristretto_classloader/src/object.rs @@ -0,0 +1,116 @@ +use crate::Error::{FieldNotFound, InvalidValueType, ParseError}; +use crate::Reference::{ByteArray, CharArray}; +use crate::{Class, Field, Result, Value}; +use ristretto_classfile::{mutf8, FieldAccessFlags, Version}; +use std::collections::HashMap; +use std::fmt::Display; +use std::sync::Arc; + +const JAVA_8: Version = Version::Java8 { minor: 0 }; + +/// Represents an object in the Ristretto VM. +#[derive(Clone, Debug, PartialEq)] +pub struct Object { + class: Arc, + fields: HashMap, +} + +impl Object { + /// Create a new object with the given class. + /// + /// # Errors + /// if the fields of the class cannot be read. + pub fn new(class: Arc) -> Result { + let mut fields = HashMap::new(); + let mut current_class = Some(class.clone()); + while let Some(class) = current_class { + let class_file = class.class_file(); + for class_file_field in &class_file.fields { + if class_file_field + .access_flags + .contains(FieldAccessFlags::STATIC) + { + continue; + } + + let field = Field::from(class_file, class_file_field)?; + if !fields.contains_key(field.name()) { + fields.insert(field.name().to_string(), field); + } + } + + current_class = class.parent()?; + } + Ok(Self { class, fields }) + } + + /// Get the class. + #[must_use] + pub fn class(&self) -> &Arc { + &self.class + } + + /// Check if the object is an instance of the given class. + /// + /// # Errors + /// if the parent class cannot be read. + pub fn instanceof>(&self, class_name: S) -> Result { + self.class.is_assignable_from(class_name) + } + + /// Get field by name. + /// + /// # Errors + /// if the field cannot be found. + pub fn field>(&self, name: S) -> Result<&Field> { + let name = name.as_ref(); + let Some(field) = self.fields.get(name) else { + return Err(FieldNotFound { + class_name: self.class.name().to_string(), + field_name: name.to_string(), + }); + }; + Ok(field) + } + + /// Returns a string value for a java.lang.String object. + /// + /// # Errors + /// if the value is not a string Object + pub fn as_string(&self) -> Result { + let class_name = self.class().name(); + if class_name != "java/lang/String" { + return Err(InvalidValueType( + "Expected a java.lang.String value".to_string(), + )); + } + let Value::Object(Some(reference)) = self.field("value")?.value()? else { + return Err(InvalidValueType( + "Expected an object field value".to_string(), + )); + }; + let class_file_version = &self.class.class_file().version; + let value = if *class_file_version <= JAVA_8 { + let CharArray(bytes) = reference else { + return Err(InvalidValueType("Expected a byte array value".to_string())); + }; + let bytes = bytes.to_vec()?; + String::from_utf16(&bytes).map_err(|error| ParseError(error.to_string()))? + } else { + let ByteArray(bytes) = reference else { + return Err(InvalidValueType("Expected a byte array value".to_string())); + }; + let bytes = bytes.to_vec()?; + #[expect(clippy::cast_sign_loss)] + let bytes: Vec = bytes.iter().map(|&b| b as u8).collect(); + mutf8::from_bytes(&bytes)? + }; + Ok(value) + } +} + +impl Display for Object { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "class {}", self.class) + } +} diff --git a/ristretto_classloader/src/reference.rs b/ristretto_classloader/src/reference.rs new file mode 100644 index 00000000..b5302080 --- /dev/null +++ b/ristretto_classloader/src/reference.rs @@ -0,0 +1,215 @@ +use crate::concurrent_vec::ConcurrentVec; +use crate::{Class, Object, Result}; +use ristretto_classfile::{ClassFile, ConstantPool}; +use std::fmt; +use std::fmt::Display; +use std::sync::Arc; +// TODO: Add support for multi-dimensional array types + +/// Represents a reference to an object in the Ristretto VM. +#[derive(Clone, Debug)] +pub enum Reference { + ByteArray(ConcurrentVec), + CharArray(ConcurrentVec), + ShortArray(ConcurrentVec), + IntArray(ConcurrentVec), + LongArray(ConcurrentVec), + FloatArray(ConcurrentVec), + DoubleArray(ConcurrentVec), + Array(Arc, ConcurrentVec>), + Object(Object), +} + +impl Reference { + /// Get the class name of the reference + #[must_use] + pub fn class_name(&self) -> String { + // TODO: Add support for multi-dimensional array types + match self { + Reference::ByteArray(_) => "[B".to_string(), + Reference::CharArray(_) => "[C".to_string(), + Reference::ShortArray(_) => "[S".to_string(), + Reference::IntArray(_) => "[I".to_string(), + Reference::LongArray(_) => "[J".to_string(), + Reference::FloatArray(_) => "[F".to_string(), + Reference::DoubleArray(_) => "[D".to_string(), + Reference::Array(class, _) => format!("[L{};", class.name()), + Reference::Object(value) => value.class().name().to_string(), + } + } + + /// Get the class of the reference + /// + /// # Errors + /// if the class cannot be created + pub fn class(&self) -> Result> { + let class = if let Reference::Object(value) = self { + value.class().clone() + } else { + let class_name = self.class_name(); + let mut constant_pool = ConstantPool::default(); + let class_index = constant_pool.add_class(class_name.as_str())?; + let class_file = ClassFile { + constant_pool, + this_class: class_index, + ..Default::default() + }; + let class = Class::from(class_file)?; + Arc::new(class) + }; + Ok(class) + } +} + +impl Display for Reference { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Reference::ByteArray(value) => write!(f, "byte{value}"), + Reference::CharArray(value) => write!(f, "char{value}"), + Reference::ShortArray(value) => write!(f, "short{value}"), + Reference::IntArray(value) => write!(f, "int{value}"), + Reference::LongArray(value) => write!(f, "long{value}"), + Reference::FloatArray(value) => write!(f, "float{value}"), + Reference::DoubleArray(value) => write!(f, "double{value}"), + Reference::Array(class, value) => { + let length = value.len().unwrap_or_default(); + write!(f, "{}[{length}]", class.name()) + } + Reference::Object(value) => { + if value.class().name() == "java/lang/String" { + let string = value.as_string().unwrap_or_default(); + write!(f, "string({string})") + } else { + write!(f, "{value}") + } + } + } + } +} + +impl PartialEq for Reference { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Reference::ByteArray(a), Reference::ByteArray(b)) => a == b, + (Reference::CharArray(a), Reference::CharArray(b)) => a == b, + (Reference::ShortArray(a), Reference::ShortArray(b)) => a == b, + (Reference::IntArray(a), Reference::IntArray(b)) => a == b, + (Reference::LongArray(a), Reference::LongArray(b)) => a == b, + (Reference::FloatArray(a), Reference::FloatArray(b)) => a == b, + (Reference::DoubleArray(a), Reference::DoubleArray(b)) => a == b, + (Reference::Array(a_class, a_array), Reference::Array(b_class, b_array)) => { + a_class.name() == b_class.name() && a_array == b_array + } + (Reference::Object(a), Reference::Object(b)) => { + // Compare the references by pointer to determine if they are the same object in + // order to avoid infinite recursion + if std::ptr::eq(a, b) { + true + } else { + a == b + } + } + _ => false, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Class, Result}; + use ristretto_classfile::ClassFile; + use std::io::Cursor; + use std::sync::Arc; + + #[test] + fn test_display_byte_array() -> Result<()> { + let reference = Reference::ByteArray(ConcurrentVec::from(vec![1, 2, 3])); + assert_eq!(reference.class_name(), "[B"); + assert_eq!(reference.class()?.name(), "[B"); + assert_eq!(reference.to_string(), "byte[1, 2, 3]"); + Ok(()) + } + + #[test] + fn test_display_char_array() -> Result<()> { + let reference = Reference::CharArray(ConcurrentVec::from(vec![1, 2, 3])); + assert_eq!(reference.class_name(), "[C"); + assert_eq!(reference.class()?.name(), "[C"); + assert_eq!(reference.to_string(), "char[1, 2, 3]"); + Ok(()) + } + + #[test] + fn test_display_short_array() -> Result<()> { + let reference = Reference::ShortArray(ConcurrentVec::from(vec![1, 2, 3])); + assert_eq!(reference.class_name(), "[S"); + assert_eq!(reference.class()?.name(), "[S"); + assert_eq!(reference.to_string(), "short[1, 2, 3]"); + Ok(()) + } + + #[test] + fn test_display_int_array() -> Result<()> { + let reference = Reference::IntArray(ConcurrentVec::from(vec![1, 2, 3])); + assert_eq!(reference.class_name(), "[I"); + assert_eq!(reference.class()?.name(), "[I"); + assert_eq!(reference.to_string(), "int[1, 2, 3]"); + Ok(()) + } + + #[test] + fn test_display_long_array() -> Result<()> { + let reference = Reference::LongArray(ConcurrentVec::from(vec![1, 2, 3])); + assert_eq!(reference.class_name(), "[J"); + assert_eq!(reference.class()?.name(), "[J"); + assert_eq!(reference.to_string(), "long[1, 2, 3]"); + Ok(()) + } + + #[test] + fn test_display_float_array() -> Result<()> { + let reference = Reference::FloatArray(ConcurrentVec::from(vec![1.0, 2.0, 3.0])); + assert_eq!(reference.class_name(), "[F"); + assert_eq!(reference.class()?.name(), "[F"); + assert_eq!(reference.to_string(), "float[1.0, 2.0, 3.0]"); + Ok(()) + } + + #[test] + fn test_display_double_array() -> Result<()> { + let reference = Reference::DoubleArray(ConcurrentVec::from(vec![1.0, 2.0, 3.0])); + assert_eq!(reference.class_name(), "[D"); + assert_eq!(reference.class()?.name(), "[D"); + assert_eq!(reference.to_string(), "double[1.0, 2.0, 3.0]"); + Ok(()) + } + + #[test] + fn test_display_reference_array() -> Result<()> { + let bytes = include_bytes!("../../classes/Minimum.class").to_vec(); + let mut cursor = Cursor::new(bytes); + let class_file = ClassFile::from_bytes(&mut cursor)?; + let class = Arc::new(Class::from(class_file)?); + let reference = Reference::Array(class, ConcurrentVec::from(vec![None])); + assert_eq!(reference.class_name(), "[LMinimum;"); + assert_eq!(reference.class()?.name(), "[LMinimum;"); + assert_eq!(reference.to_string(), "Minimum[1]"); + Ok(()) + } + + #[test] + fn test_display_reference() -> Result<()> { + let bytes = include_bytes!("../../classes/Minimum.class").to_vec(); + let mut cursor = Cursor::new(bytes); + let class_file = ClassFile::from_bytes(&mut cursor)?; + let class = Arc::new(Class::from(class_file)?); + let object = Object::new(class)?; + let reference = Reference::Object(object); + assert_eq!(reference.class_name(), "Minimum"); + assert_eq!(reference.class()?.name(), "Minimum"); + assert!(reference.to_string().starts_with("class Minimum")); + assert!(reference.to_string().contains("Minimum")); + Ok(()) + } +} diff --git a/ristretto_classloader/src/runtime/bootstrap.rs b/ristretto_classloader/src/runtime/bootstrap.rs index 4ee1488c..2442f757 100644 --- a/ristretto_classloader/src/runtime/bootstrap.rs +++ b/ristretto_classloader/src/runtime/bootstrap.rs @@ -2,7 +2,7 @@ use crate::runtime::util; use crate::{ClassLoader, ClassPath, Error, Result}; use flate2::bufread::GzDecoder; use std::path::{Path, PathBuf}; -use std::{env, io}; +use std::{env, fs, io}; use tar::Archive; use tracing::{debug, instrument}; @@ -13,7 +13,7 @@ use tracing::{debug, instrument}; /// # Errors /// An error will be returned if the class loader cannot be created. #[instrument(level = "debug")] -pub async fn class_loader(version: &str) -> Result<(String, ClassLoader)> { +pub fn class_loader(version: &str) -> Result<(String, ClassLoader)> { let mut archive_version = version.to_string(); let current_dir = env::current_dir().unwrap_or_default(); #[cfg(target_arch = "wasm32")] @@ -24,9 +24,9 @@ pub async fn class_loader(version: &str) -> Result<(String, ClassLoader)> { let mut installation_dir = base_path.join(version); if !installation_dir.exists() { - let (version, file_name, archive) = util::get_runtime_archive(version).await?; + let (version, file_name, archive) = util::get_runtime_archive(version)?; installation_dir = - extract_archive(version.as_str(), file_name.as_str(), &archive, &base_path).await?; + extract_archive(version.as_str(), file_name.as_str(), &archive, &base_path)?; archive_version = version; } @@ -67,7 +67,6 @@ fn get_class_path(version: &str, installation_dir: &Path) -> Result { class_paths.sort_by(Ord::cmp); class_paths.join(":") }; - debug!("Class loader for {version}: {class_path}"); Ok(ClassPath::from(class_path)) } @@ -75,17 +74,14 @@ fn get_class_path(version: &str, installation_dir: &Path) -> Result { /// /// # Errors /// An error will be returned if the archive cannot be extracted. -#[instrument(level = "debug")] -async fn extract_archive( +#[instrument(level = "debug", skip(archive))] +fn extract_archive( version: &str, file_name: &str, archive: &Vec, out_dir: &PathBuf, ) -> Result { - #[cfg(target_arch = "wasm32")] - std::fs::create_dir_all(out_dir)?; - #[cfg(not(target_arch = "wasm32"))] - tokio::fs::create_dir_all(out_dir).await?; + fs::create_dir_all(out_dir)?; let Some(extension) = file_name.split('.').last() else { return Err(Error::ArchiveError( @@ -109,7 +105,6 @@ async fn extract_archive( tar.unpack(extract_dir.clone())?; }; - #[cfg(target_arch = "wasm32")] let runtime_dir = { let mut entries = std::fs::read_dir(&extract_dir)?; let Some(runtime_dir) = entries.next() else { @@ -119,26 +114,13 @@ async fn extract_archive( }; runtime_dir? }; - #[cfg(not(target_arch = "wasm32"))] - let runtime_dir = { - let mut entries = tokio::fs::read_dir(&extract_dir).await?; - let Some(runtime_dir) = entries.next_entry().await? else { - return Err(Error::ArchiveError( - "No directory found in archive".to_string(), - )); - }; - runtime_dir - }; let runtime_dir = runtime_dir.path(); let installation_dir = out_dir.join(version); // Rename the runtime directory to the installation directory. Another process may have // already installed the runtime, so we need to check if the installation directory exists. // If it does, we can ignore the error. - #[cfg(target_arch = "wasm32")] - let rename_result = std::fs::rename(runtime_dir.clone(), installation_dir.clone()); - #[cfg(not(target_arch = "wasm32"))] - let rename_result = tokio::fs::rename(runtime_dir.clone(), installation_dir.clone()).await; + let rename_result = fs::rename(runtime_dir.clone(), installation_dir.clone()); if let Err(error) = rename_result { debug!( "Failed to rename {} to {}", @@ -149,10 +131,7 @@ async fn extract_archive( return Err(Error::ArchiveError(error.to_string())); } } - #[cfg(target_arch = "wasm32")] - std::fs::remove_dir_all(&extract_dir)?; - #[cfg(not(target_arch = "wasm32"))] - tokio::fs::remove_dir_all(&extract_dir).await?; + fs::remove_dir_all(&extract_dir)?; debug!( "Installed {version} to: {}", installation_dir.to_string_lossy() @@ -165,19 +144,19 @@ async fn extract_archive( mod tests { use super::*; - #[test_log::test(tokio::test)] - async fn test_class_loader_v8() -> Result<()> { + #[test] + fn test_class_loader_v8() -> Result<()> { let version = "8.422.05.1"; - let (archive_version, class_loader) = class_loader(version).await?; + let (archive_version, class_loader) = class_loader(version)?; assert_eq!(version, archive_version); assert_eq!("bootstrap", class_loader.name()); Ok(()) } - #[test_log::test(tokio::test)] - async fn test_class_loader_v21() -> Result<()> { + #[test] + fn test_class_loader_v21() -> Result<()> { let version = "21.0.4.7.1"; - let (archive_version, class_loader) = class_loader(version).await?; + let (archive_version, class_loader) = class_loader(version)?; assert_eq!(version, archive_version); assert_eq!("bootstrap", class_loader.name()); Ok(()) diff --git a/ristretto_classloader/src/runtime/util.rs b/ristretto_classloader/src/runtime/util.rs index 66a9cd2e..2029d81a 100644 --- a/ristretto_classloader/src/runtime/util.rs +++ b/ristretto_classloader/src/runtime/util.rs @@ -1,7 +1,7 @@ use crate::runtime::models::Release; use crate::{Error, Result}; +use reqwest::blocking::Client; use reqwest::header; -use reqwest::Client; use std::env; use std::env::consts; use std::sync::LazyLock; @@ -36,7 +36,7 @@ static USER_AGENT: LazyLock = LazyLock::new(|| { /// # Errors /// An error will be returned if the request fails or if the version requirement is not supported. #[instrument(level = "debug")] -pub(crate) async fn get_runtime_archive(version: &str) -> Result<(String, String, Vec)> { +pub(crate) fn get_runtime_archive(version: &str) -> Result<(String, String, Vec)> { let version = if version == "*" { DEFAULT_MAJOR_VERSION.to_string() } else { @@ -51,15 +51,15 @@ pub(crate) async fn get_runtime_archive(version: &str) -> Result<(String, String let version_parts = version.chars().filter(|&c| c == '.').count() + 1; if major_version == 8 && version_parts == 4 || version_parts == 5 { - let (file_name, archive) = download_archive(version).await?; + let (file_name, archive) = download_archive(version)?; return Ok((version.to_string(), file_name, archive)); } let major_version = major_version.to_string(); - let release_versions = get_release_versions(major_version.as_str()).await?; + let release_versions = get_release_versions(major_version.as_str())?; for release_version in release_versions { if release_version.starts_with(version) { - let (file_name, archive) = download_archive(release_version.as_str()).await?; + let (file_name, archive) = download_archive(release_version.as_str())?; return Ok((release_version, file_name, archive)); } } @@ -72,7 +72,7 @@ pub(crate) async fn get_runtime_archive(version: &str) -> Result<(String, String /// # Errors /// An error will be returned if the request fails #[instrument(level = "debug")] -async fn download_archive(version: &str) -> Result<(String, Vec)> { +fn download_archive(version: &str) -> Result<(String, Vec)> { let client = Client::new(); let mut headers = header::HeaderMap::new(); headers.insert( @@ -117,10 +117,9 @@ async fn download_archive(version: &str) -> Result<(String, Vec)> { let response = client .get(url) .headers(headers) - .send() - .await? + .send()? .error_for_status()?; - let archive = response.bytes().await?; + let archive = response.bytes()?; Ok((file_name, archive.to_vec())) } @@ -130,7 +129,7 @@ async fn download_archive(version: &str) -> Result<(String, Vec)> { /// # Errors /// An error will be returned if the request fails #[instrument(level = "debug")] -async fn get_release_versions(major_version: &str) -> Result> { +fn get_release_versions(major_version: &str) -> Result> { let url = format!("https://api.github.com/repos/corretto/corretto-{major_version}/releases"); let client = Client::new(); let mut headers = header::HeaderMap::new(); @@ -160,10 +159,9 @@ async fn get_release_versions(major_version: &str) -> Result> { .get(&url) .headers(headers.clone()) .query(&[("page", page.to_string().as_str()), ("per_page", "100")]) - .send() - .await? + .send()? .error_for_status()?; - let response_releases = response.json::>().await?; + let response_releases = response.json::>()?; if response_releases.is_empty() { break; } @@ -190,75 +188,75 @@ pub(crate) fn parse_major_version(version: &str) -> u64 { mod tests { use super::*; - #[test_log::test(tokio::test)] - async fn test_get_runtime_archive_latest_exact() -> Result<()> { + #[test] + fn test_get_runtime_archive_latest_exact() -> Result<()> { let expected_version = "11.0.24.8.1"; - let (version, file_name, archive) = get_runtime_archive(expected_version).await?; + let (version, file_name, archive) = get_runtime_archive(expected_version)?; assert_eq!(expected_version, version); assert!(file_name.contains(expected_version)); assert!(!archive.is_empty()); Ok(()) } - #[test_log::test(tokio::test)] - async fn test_get_runtime_archive_partial_version() -> Result<()> { + #[test] + fn test_get_runtime_archive_partial_version() -> Result<()> { let partial_version = "8.422"; - let (version, file_name, archive) = get_runtime_archive(partial_version).await?; + let (version, file_name, archive) = get_runtime_archive(partial_version)?; assert!(version.starts_with(partial_version)); assert!(file_name.contains(partial_version)); assert!(!archive.is_empty()); Ok(()) } - #[test_log::test(tokio::test)] - async fn test_get_runtime_archive_latest_major_version() -> Result<()> { + #[test] + fn test_get_runtime_archive_latest_major_version() -> Result<()> { let major_version = "17"; - let (version, file_name, archive) = get_runtime_archive(major_version).await?; + let (version, file_name, archive) = get_runtime_archive(major_version)?; assert!(version.starts_with(major_version)); assert!(file_name.contains(major_version)); assert!(!archive.is_empty()); Ok(()) } - #[test_log::test(tokio::test)] - async fn test_get_runtime_archive_latest_lts() -> Result<()> { - let (version, _file_name, archive) = get_runtime_archive("*").await?; + #[test] + fn test_get_runtime_archive_latest_lts() -> Result<()> { + let (version, _file_name, archive) = get_runtime_archive("*")?; let expected_major_version = DEFAULT_MAJOR_VERSION.to_string(); assert!(version.starts_with(expected_major_version.as_str())); assert!(!archive.is_empty()); Ok(()) } - #[test_log::test(tokio::test)] - async fn test_get_runtime_archive_unsupported_version() { - let result = get_runtime_archive("21.0.0.0.0").await; + #[test] + fn test_get_runtime_archive_unsupported_version() { + let result = get_runtime_archive("21.0.0.0.0"); assert!(matches!(result, Err(Error::RequestError(_)))); } - #[test_log::test(tokio::test)] - async fn test_get_runtime_archive_invalid() { - let result = get_runtime_archive("0").await; + #[test] + fn test_get_runtime_archive_invalid() { + let result = get_runtime_archive("0"); assert!(matches!(result, Err(Error::UnsupportedVersion(_)))); } - #[test_log::test(tokio::test)] - async fn test_download_archive() -> Result<()> { + #[test] + fn test_download_archive() -> Result<()> { let version = "21.0.4.7.1"; - let (_file_name, archive) = download_archive(version).await?; + let (_file_name, archive) = download_archive(version)?; assert!(!archive.is_empty()); Ok(()) } - #[test_log::test(tokio::test)] - async fn test_get_release_versions() -> Result<()> { + #[test] + fn test_get_release_versions() -> Result<()> { let major_version = "21"; - let release_versions = get_release_versions(major_version).await?; + let release_versions = get_release_versions(major_version)?; let expected_version = "21.0.4.7.1".to_string(); assert!(release_versions.contains(&expected_version)); Ok(()) } - #[test_log::test] + #[test] fn test_parse_major_version() { assert_eq!(11, parse_major_version("11")); assert_eq!(8, parse_major_version("8.422.05.1")); diff --git a/ristretto_classloader/src/value.rs b/ristretto_classloader/src/value.rs new file mode 100644 index 00000000..b12aa860 --- /dev/null +++ b/ristretto_classloader/src/value.rs @@ -0,0 +1,184 @@ +use crate::reference::Reference; +use crate::Error::InvalidValueType; +use crate::Result; +use std::fmt; +use std::fmt::Display; + +/// Represents a value in the Ristretto VM. +#[derive(Clone, Debug, PartialEq)] +pub enum Value { + Int(i32), + Long(i64), + Float(f32), + Double(f64), + Object(Option), + Unused, +} + +impl Value { + /// Returns the value as an `i32`. + /// + /// # Errors + /// if the value is not an Int. + pub fn as_int(&self) -> Result { + match self { + Value::Int(value) => Ok(*value), + _ => Err(InvalidValueType("Expected an int value".to_string())), + } + } + + /// Returns the value as an `i64`. + /// + /// # Errors + /// if the value is not a Long + pub fn as_long(&self) -> Result { + match self { + Value::Long(value) => Ok(*value), + _ => Err(InvalidValueType("Expected a long value".to_string())), + } + } + + /// Returns the value as an `f32`. + /// + /// # Errors + /// if the value is not a Float + pub fn as_float(&self) -> Result { + match self { + Value::Float(value) => Ok(*value), + _ => Err(InvalidValueType("Expected a float value".to_string())), + } + } + + /// Returns the value as an `f64`. + /// + /// # Errors + /// if the value is not a Double + pub fn as_double(&self) -> Result { + match self { + Value::Double(value) => Ok(*value), + _ => Err(InvalidValueType("Expected a double value".to_string())), + } + } + + /// Returns the value as an `Option`. + /// + /// # Errors + /// if the value is not an Object + pub fn as_object(&self) -> Result> { + match self { + Value::Object(value) => Ok(value.as_ref()), + _ => Err(InvalidValueType("Expected an object value".to_string())), + } + } + + /// Returns a string value for a java.lang.String object. + /// + /// # Errors + /// if the value is not a string Object + pub fn as_string(&self) -> Result { + match self { + Value::Object(Some(Reference::Object(object))) => object.as_string(), + _ => Err(InvalidValueType("Expected an object value".to_string())), + } + } + + /// Returns true if the value is a category 1 value. + #[must_use] + pub fn is_category_1(&self) -> bool { + !self.is_category_2() + } + + /// Returns true if the value is a category 2 value. + #[must_use] + pub fn is_category_2(&self) -> bool { + matches!(self, Value::Long(_) | Value::Double(_)) + } +} + +impl Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Value::Int(value) => write!(f, "int({value})"), + Value::Long(value) => write!(f, "long({value})"), + Value::Float(value) => write!(f, "float({value})"), + Value::Double(value) => write!(f, "double({value})"), + Value::Object(value) => { + if let Some(value) = value { + if let Ok(class) = value.class() { + if class.name() == "java/lang/String" { + let string = self.as_string().unwrap_or_default(); + return write!(f, "string({string})"); + } + } + write!(f, "object({value})") + } else { + write!(f, "object(null)") + } + } + Value::Unused => write!(f, "unused"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ConcurrentVec; + + #[test] + fn test_int_format() { + let value = Value::Int(42); + assert_eq!("int(42)", format!("{value}")); + assert!(value.is_category_1()); + assert!(!value.is_category_2()); + } + + #[test] + fn test_long_format() { + let value = Value::Long(42); + assert_eq!("long(42)", format!("{value}")); + assert!(!value.is_category_1()); + assert!(value.is_category_2()); + } + + #[test] + fn test_float_format() { + let value = Value::Float(42.1); + assert_eq!("float(42.1)", format!("{value}")); + assert!(value.is_category_1()); + assert!(!value.is_category_2()); + } + + #[test] + fn test_double_format() { + let value = Value::Double(42.1); + assert_eq!("double(42.1)", format!("{value}")); + assert!(!value.is_category_1()); + assert!(value.is_category_2()); + } + + #[test] + fn test_object_format() { + let value = Value::Object(None); + assert_eq!("object(null)", format!("{value}")); + assert!(value.is_category_1()); + assert!(!value.is_category_2()); + assert_eq!( + "object(byte[1, 2, 3])", + format!( + "{}", + Value::Object(Some(Reference::ByteArray(ConcurrentVec::from(vec![ + 1, 2, 3 + ])))) + ) + ); + } + + #[test] + fn test_unused_format() { + let value = Value::Unused; + assert_eq!("unused", format!("{value}")); + assert!(value.is_category_1()); + assert!(!value.is_category_2()); + } +} diff --git a/ristretto_classloader/tests/class_loader.rs b/ristretto_classloader/tests/class_loader.rs index 39e94165..9bd4ad30 100644 --- a/ristretto_classloader/tests/class_loader.rs +++ b/ristretto_classloader/tests/class_loader.rs @@ -1,39 +1,37 @@ use ristretto_classloader::{ClassLoader, ClassPath, Result}; use std::path::PathBuf; -#[test_log::test(tokio::test)] -async fn test_load_class_from_class_path_directory() -> Result<()> { +#[test] +fn test_load_class_from_class_path_directory() -> Result<()> { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_directory = cargo_manifest.join("../classes"); let class_path = classes_directory.to_string_lossy(); let class_loader = ClassLoader::new("directory-test", ClassPath::from(&class_path)); - let class = class_loader.load("HelloWorld").await?; + let class = class_loader.load("HelloWorld")?; let class_file = class.class_file(); assert_eq!("HelloWorld", class_file.class_name()?); Ok(()) } -#[test_log::test(tokio::test)] -async fn test_load_class_from_class_path_jar() -> Result<()> { +#[test] +fn test_load_class_from_class_path_jar() -> Result<()> { let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let classes_directory = cargo_manifest.join("../classes/classes.jar"); let class_path = ClassPath::from(classes_directory.to_string_lossy()); let class_loader = ClassLoader::new("jar-test", class_path); - let class = class_loader.load("HelloWorld").await?; + let class = class_loader.load("HelloWorld")?; let class_file = class.class_file(); assert_eq!("HelloWorld", class_file.class_name()?); Ok(()) } #[cfg(feature = "url")] -#[test_log::test(tokio::test)] -async fn test_load_class_from_class_path_url() -> Result<()> { +#[test] +fn test_load_class_from_class_path_url() -> Result<()> { let class_path_url = "https//repo1.maven.org/maven2/org/springframework/boot/spring-boot/3.3.0/spring-boot-3.3.0.jar"; let class_path = ClassPath::from(class_path_url); let class_loader = ClassLoader::new("url-test", class_path); - let class = class_loader - .load("org.springframework.boot.SpringApplication") - .await?; + let class = class_loader.load("org/springframework/boot/SpringApplication")?; let class_file = class.class_file(); assert_eq!( "org/springframework/boot/SpringApplication", diff --git a/ristretto_classloader/tests/runtime.rs b/ristretto_classloader/tests/runtime.rs index ee5e3b65..7d76691a 100644 --- a/ristretto_classloader/tests/runtime.rs +++ b/ristretto_classloader/tests/runtime.rs @@ -1,56 +1,55 @@ use ristretto_classloader::{runtime, Result}; -async fn test_runtime(version: &str, class: &str) -> Result<()> { - let (runtime_version, class_loader) = runtime::class_loader(version).await?; +fn test_runtime(version: &str, class_name: &str) -> Result<()> { + let (runtime_version, class_loader) = runtime::class_loader(version)?; assert!(runtime_version.starts_with(version)); - let class_name = class.replace('.', "/"); - let class = class_loader.load(class).await?; + let class = class_loader.load(class_name)?; let class_file = class.class_file(); - assert_eq!(&class_name, class_file.class_name()?); + assert_eq!(class_name, class_file.class_name()?); Ok(()) } -#[test_log::test(tokio::test)] -async fn test_get_runtime_v8() -> Result<()> { - test_runtime("8.422.05.1", "java.lang.Object").await +#[test] +fn test_get_runtime_v8() -> Result<()> { + test_runtime("8.422.05.1", "java/lang/Object") } -#[test_log::test(tokio::test)] -async fn test_get_runtime_v11() -> Result<()> { - test_runtime("11.0.24.8.1", "java.lang.Object").await +#[test] +fn test_get_runtime_v11() -> Result<()> { + test_runtime("11.0.24.8.1", "java/lang/Object") } -#[test_log::test(tokio::test)] -async fn test_get_runtime_v17() -> Result<()> { - test_runtime("17.0.12.7.1", "java.lang.Object").await +#[test] +fn test_get_runtime_v17() -> Result<()> { + test_runtime("17.0.12.7.1", "java/lang/Object") } -#[test_log::test(tokio::test)] -async fn test_get_runtime_v18() -> Result<()> { - test_runtime("18.0.2.9.1", "java.lang.Object").await +#[test] +fn test_get_runtime_v18() -> Result<()> { + test_runtime("18.0.2.9.1", "java/lang/Object") } -#[test_log::test(tokio::test)] -async fn test_get_runtime_v19() -> Result<()> { - test_runtime("19.0.2.7.1", "java.lang.Object").await +#[test] +fn test_get_runtime_v19() -> Result<()> { + test_runtime("19.0.2.7.1", "java/lang/Object") } -#[test_log::test(tokio::test)] -async fn test_get_runtime_v20() -> Result<()> { - test_runtime("20.0.2.10.1", "java.lang.Object").await +#[test] +fn test_get_runtime_v20() -> Result<()> { + test_runtime("20.0.2.10.1", "java/lang/Object") } -#[test_log::test(tokio::test)] -async fn test_get_runtime_v21() -> Result<()> { - test_runtime("21.0.4.7.1", "java.lang.Object").await +#[test] +fn test_get_runtime_v21() -> Result<()> { + test_runtime("21.0.4.7.1", "java/lang/Object") } -#[test_log::test(tokio::test)] -async fn test_get_runtime_v22() -> Result<()> { - test_runtime("22.0.2.9.1", "java.lang.Object").await +#[test] +fn test_get_runtime_v22() -> Result<()> { + test_runtime("22.0.2.9.1", "java/lang/Object") } -#[test_log::test(tokio::test)] -async fn test_get_runtime_v23() -> Result<()> { - test_runtime("23.0.0.35.1", "java.lang.Object").await +#[test] +fn test_get_runtime_v23() -> Result<()> { + test_runtime("23.0.0.36.1", "java/lang/Object") } diff --git a/ristretto_cli/Cargo.toml b/ristretto_cli/Cargo.toml new file mode 100644 index 00000000..dfc8cb71 --- /dev/null +++ b/ristretto_cli/Cargo.toml @@ -0,0 +1,29 @@ +[package] +authors.workspace = true +categories.workspace = true +description = "A Java Virtual Machine (JVM) CLI." +documentation = "https://theseus-rs.github.io/ristretto/ristretto_cli/" +edition.workspace = true +keywords = ["java", "jvm", "cli"] +homepage = "https://theseus-rs.github.io/ristretto/ristretto_cli/" +license.workspace = true +name = "ristretto_cli" +repository.workspace = true +version.workspace = true + +[package.metadata.wix] +upgrade-guid = "F6B5B513-84DC-4554-8DFB-50263541E9C9" +path-guid = "F561CAE1-E27E-4A3C-AE43-318392119D63" +license = false +eula = false + +[[bin]] +name = "java" +path = "src/main.rs" # path to your main.rs or other binary file + +[dependencies] +clap = { workspace = true, features = ["derive"] } +os_info = { workspace = true } +ristretto_vm = { path = "../ristretto_vm", version = "0.7.0" } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/ristretto_cli/README.md b/ristretto_cli/README.md new file mode 100644 index 00000000..5bf61e3c --- /dev/null +++ b/ristretto_cli/README.md @@ -0,0 +1,50 @@ +# Ristretto VM + +[![ci](https://github.com/theseus-rs/ristretto/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/theseus-rs/ristretto/actions/workflows/ci.yml) +[![Documentation](https://docs.rs/ristretto_cli/badge.svg)](https://docs.rs/ristretto_cli) +[![Code Coverage](https://codecov.io/gh/theseus-rs/ristretto/branch/main/graph/badge.svg)](https://codecov.io/gh/theseus-rs/ristretto) +[![Benchmarks](https://img.shields.io/badge/%F0%9F%90%B0_bencher-enabled-6ec241)](https://bencher.dev/perf/theseus-rs-ristretto) +[![Latest version](https://img.shields.io/crates/v/ristretto_cli.svg)](https://crates.io/crates/ristretto_cli) +[![License](https://img.shields.io/crates/l/ristretto_cli)](https://github.com/theseus-rs/ristretto#license) +[![Semantic Versioning](https://img.shields.io/badge/%E2%9A%99%EF%B8%8F_SemVer-2.0.0-blue)](https://semver.org/spec/v2.0.0.html) + +## Getting Started + +Command line interface for the [JVM](https://docs.oracle.com/javase/specs/jvms/se23/html/index.html). + +# Examples + +```shell +java HelloWorld +``` + +## Safety + +These crates use `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% safe Rust. + +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. + + +VSCode Development Container + +
+ +GitHub Codespaces + diff --git a/ristretto_cli/src/logging.rs b/ristretto_cli/src/logging.rs new file mode 100644 index 00000000..44b7a943 --- /dev/null +++ b/ristretto_cli/src/logging.rs @@ -0,0 +1,18 @@ +use tracing_subscriber::filter::EnvFilter; +use tracing_subscriber::fmt; + +/// Initializes the logging system. +pub(crate) fn initialize() { + let format = tracing_subscriber::fmt::format() + .with_level(true) + .with_target(false) + .with_thread_names(true) + .with_timer(fmt::time::uptime()) + .compact(); + + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_env("JAVA_LOG")) + .fmt_fields(fmt::format::DefaultFields::new()) + .event_format(format) + .init(); +} diff --git a/ristretto_cli/src/main.rs b/ristretto_cli/src/main.rs new file mode 100644 index 00000000..0bc98069 --- /dev/null +++ b/ristretto_cli/src/main.rs @@ -0,0 +1,91 @@ +#![forbid(unsafe_code)] + +mod logging; +mod version; + +use clap::{ArgGroup, Parser}; +use ristretto_vm::Error::RuntimeError; +use ristretto_vm::{ClassPath, ConfigurationBuilder, Result, VM}; +use std::env::consts::{ARCH, OS}; +use std::path::PathBuf; +use tracing::debug; + +#[derive(Debug, Parser)] +#[command( + name = "java", + about = "Ristretto CLI", + help_expected = true, + trailing_var_arg = true +)] +#[command(group( + ArgGroup::new("execution") + .args(&["mainclass", "jar"]) +))] +struct Cli { + #[arg(help = "The main class to execute")] + mainclass: Option, + + #[arg( + long = "jar", + help = "Execute a jar file", + conflicts_with = "mainclass" + )] + jar: Option, + + #[arg( + long = "classpath", + help = "Class search path of directories and zip/jar files" + )] + classpath: Option, + + #[arg(help = "Additional arguments to pass to the main class")] + arguments: Option>, + + /// Display the version of this tool + #[arg(long)] + version: bool, +} + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +fn main() -> Result<()> { + logging::initialize(); + + let cli = Cli::parse(); + if cli.version { + let version = version::full(); + println!("{version}"); + return Ok(()); + } + + debug!("ristretto/{VERSION}/{OS}/{ARCH}"); + let mut configuration_builder = ConfigurationBuilder::new(); + if let Some(class_path) = cli.classpath { + let class_path = ClassPath::from(class_path.as_str()); + configuration_builder = configuration_builder.class_path(class_path); + } + + if let Some(main_class) = cli.mainclass { + configuration_builder = configuration_builder.main_class(main_class); + } else if let Some(jar) = cli.jar { + configuration_builder = configuration_builder.jar(PathBuf::from(jar)); + } + + let configuration = configuration_builder.build(); + let vm = VM::new(configuration)?; + let Some(main_class_name) = vm.main_class() else { + return Err(RuntimeError("No main class specified".into())); + }; + let main_class = vm.load(main_class_name)?; + let Some(main_method) = main_class.main_method() else { + return Err(RuntimeError("No main method found".into())); + }; + + let mut arguments = Vec::new(); + for argument in cli.arguments.unwrap_or_default() { + arguments.push(vm.string(argument.as_str())?); + } + + vm.invoke(&main_class, &main_method, arguments)?; + Ok(()) +} diff --git a/ristretto_cli/src/version.rs b/ristretto_cli/src/version.rs new file mode 100644 index 00000000..51003ac9 --- /dev/null +++ b/ristretto_cli/src/version.rs @@ -0,0 +1,22 @@ +/// Get the full version of the program (e.g. "java/0.0.0 Linux/5.11.0-37-generic/x86_64"). +pub fn full() -> String { + let program_name = "java"; + let version = env!("CARGO_PKG_VERSION"); + let info = os_info::get(); + let os = format!("{}", info.os_type()).replace(' ', "-"); + let os_version = info.version(); + let architecture = info.architecture().unwrap_or("unknown"); + + format!("{program_name}/{version} {os}/{os_version}/{architecture}") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_full() { + let version = full(); + assert!(version.starts_with("java/")); + } +} diff --git a/ristretto_cli/wix/main.wxs b/ristretto_cli/wix/main.wxs new file mode 100644 index 00000000..7136d58a --- /dev/null +++ b/ristretto_cli/wix/main.wxs @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + + + + + + + + + + + + + + + + + + diff --git a/ristretto_vm/Cargo.toml b/ristretto_vm/Cargo.toml new file mode 100644 index 00000000..5e0d568e --- /dev/null +++ b/ristretto_vm/Cargo.toml @@ -0,0 +1,25 @@ +[package] +authors.workspace = true +categories.workspace = true +description = "Java Virtual Machine" +edition.workspace = true +keywords = ["java", "jvm"] +license.workspace = true +name = "ristretto_vm" +repository.workspace = true +version.workspace = true + +[dependencies] +dirs = { workspace = true } +indexmap = { workspace = true } +os_info = { workspace = true } +ristretto_classfile = { path = "../ristretto_classfile", version = "0.7.0" } +ristretto_classloader = { path = "../ristretto_classloader", version = "0.7.0" } +sysinfo = { workspace = true } +sys-locale = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } +whoami = { workspace = true } + +[dev-dependencies] +criterion = { workspace = true } diff --git a/ristretto_vm/README.md b/ristretto_vm/README.md new file mode 100644 index 00000000..10f102fa --- /dev/null +++ b/ristretto_vm/README.md @@ -0,0 +1,44 @@ +# Ristretto VM + +[![ci](https://github.com/theseus-rs/ristretto/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/theseus-rs/ristretto/actions/workflows/ci.yml) +[![Documentation](https://docs.rs/ristretto_vm/badge.svg)](https://docs.rs/ristretto_vm) +[![Code Coverage](https://codecov.io/gh/theseus-rs/ristretto/branch/main/graph/badge.svg)](https://codecov.io/gh/theseus-rs/ristretto) +[![Benchmarks](https://img.shields.io/badge/%F0%9F%90%B0_bencher-enabled-6ec241)](https://bencher.dev/perf/theseus-rs-ristretto) +[![Latest version](https://img.shields.io/crates/v/ristretto_vm.svg)](https://crates.io/crates/ristretto_vm) +[![License](https://img.shields.io/crates/l/ristretto_vm)](https://github.com/theseus-rs/ristretto#license) +[![Semantic Versioning](https://img.shields.io/badge/%E2%9A%99%EF%B8%8F_SemVer-2.0.0-blue)](https://semver.org/spec/v2.0.0.html) + +## Getting Started + +Implementation of a [JVM](https://docs.oracle.com/javase/specs/jvms/se23/html/index.html). + +## Safety + +These crates use `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% safe Rust. + +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. + + +VSCode Development Container + +
+ +GitHub Codespaces + diff --git a/ristretto_vm/src/arguments.rs b/ristretto_vm/src/arguments.rs new file mode 100644 index 00000000..f7f74dda --- /dev/null +++ b/ristretto_vm/src/arguments.rs @@ -0,0 +1,316 @@ +use crate::Error::{ArgumentsUnderflow, InvalidOperand}; +use crate::Result; +use ristretto_classloader::{Reference, Value}; +use std::fmt::Display; + +/// Arguments for Ristretto VM rust arguments +#[derive(Clone, Debug, Default)] +pub(crate) struct Arguments { + arguments: Vec, +} + +impl Arguments { + /// Create arguments from a vector of values. + pub(crate) fn new(arguments: Vec) -> Self { + Arguments { arguments } + } + + /// Push a value onto the arguments. + #[inline] + pub fn push(&mut self, value: Value) { + self.arguments.push(value); + } + + /// Push an int value onto the arguments. + pub fn push_int(&mut self, value: i32) { + self.push(Value::Int(value)); + } + + /// Push a long value onto the arguments. + pub fn push_long(&mut self, value: i64) { + self.push(Value::Long(value)); + } + + /// Push a float value onto the arguments. + pub fn push_float(&mut self, value: f32) { + self.push(Value::Float(value)); + } + + /// Push a double value onto the arguments. + pub fn push_double(&mut self, value: f64) { + self.push(Value::Double(value)); + } + + /// Push a reference onto the arguments. + pub fn push_object(&mut self, value: Option) { + self.push(Value::Object(value)); + } + + /// Pop a value from the arguments. + #[inline] + pub fn pop(&mut self) -> Result { + let Some(value) = self.arguments.pop() else { + return Err(ArgumentsUnderflow); + }; + Ok(value) + } + + /// Pop an int from the arguments. + pub fn pop_int(&mut self) -> Result { + match self.pop()? { + Value::Int(value) => Ok(value), + value => Err(InvalidOperand { + expected: "int".to_string(), + actual: value.to_string(), + }), + } + } + + /// Pop a long from the arguments. + pub fn pop_long(&mut self) -> Result { + match self.pop()? { + Value::Long(value) => Ok(value), + value => Err(InvalidOperand { + expected: "long".to_string(), + actual: value.to_string(), + }), + } + } + + /// Pop a float from the arguments. + pub fn pop_float(&mut self) -> Result { + match self.pop()? { + Value::Float(value) => Ok(value), + value => Err(InvalidOperand { + expected: "float".to_string(), + actual: value.to_string(), + }), + } + } + + /// Pop a double from the arguments. + pub fn pop_double(&mut self) -> Result { + match self.pop()? { + Value::Double(value) => Ok(value), + value => Err(InvalidOperand { + expected: "double".to_string(), + actual: value.to_string(), + }), + } + } + + /// Pop a null or object from the arguments. + pub fn pop_object(&mut self) -> Result> { + let value = self.pop()?; + match value { + Value::Object(reference) => Ok(reference), + value => Err(InvalidOperand { + expected: "object".to_string(), + actual: value.to_string(), + }), + } + } + + /// Peek at the top value on the arguments. + pub fn peek(&self) -> Result<&Value> { + let Some(value) = self.arguments.last() else { + return Err(ArgumentsUnderflow); + }; + Ok(value) + } + + /// Get the number of values on the arguments. + pub fn len(&self) -> usize { + self.arguments.len() + } + + /// Check if the arguments is empty. + pub fn is_empty(&self) -> bool { + self.arguments.is_empty() + } +} + +impl Display for Arguments { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "[{}]", + self.arguments + .iter() + .map(ToString::to_string) + .collect::>() + .join(", ") + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ristretto_classloader::{ConcurrentVec, Reference}; + + #[test] + fn test_can_push_and_pop_values() -> Result<()> { + let mut arguments = Arguments::default(); + arguments.push(Value::Int(1)); + arguments.push(Value::Int(2)); + + assert_eq!(arguments.len(), 2); + + assert_eq!(arguments.pop()?, Value::Int(2)); + assert_eq!(arguments.pop()?, Value::Int(1)); + Ok(()) + } + + #[test] + fn test_pop_int() -> Result<()> { + let mut arguments = Arguments::default(); + arguments.push_int(42); + assert_eq!(arguments.pop_int()?, 42); + Ok(()) + } + + #[test] + fn test_pop_int_invalid_operand() { + let mut arguments = Arguments::default(); + arguments.push_object(None); + assert!(matches!( + arguments.pop_int(), + Err(InvalidOperand { + expected, + actual + }) if expected == "int" && actual == "object(null)" + )); + } + + #[test] + fn test_pop_long() -> Result<()> { + let mut arguments = Arguments::default(); + arguments.push_long(42); + assert_eq!(arguments.pop_long()?, 42); + Ok(()) + } + + #[test] + fn test_pop_long_invalid_operand() { + let mut arguments = Arguments::default(); + arguments.push_object(None); + assert!(matches!( + arguments.pop_long(), + Err(InvalidOperand { + expected, + actual + }) if expected == "long" && actual == "object(null)" + )); + } + + #[test] + fn test_pop_float() -> Result<()> { + let mut arguments = Arguments::default(); + arguments.push_float(42.1); + let value = arguments.pop_float()? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_pop_float_invalid_operand() { + let mut arguments = Arguments::default(); + arguments.push_object(None); + assert!(matches!( + arguments.pop_float(), + Err(InvalidOperand { + expected, + actual + }) if expected == "float" && actual == "object(null)" + )); + } + + #[test] + fn test_pop_double() -> Result<()> { + let mut arguments = Arguments::default(); + arguments.push_double(42.1); + let value = arguments.pop_double()? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_pop_double_invalid_operand() { + let mut arguments = Arguments::default(); + arguments.push_object(None); + assert!(matches!( + arguments.pop_double(), + Err(InvalidOperand { + expected, + actual + }) if expected == "double" && actual == "object(null)" + )); + } + + #[test] + fn test_pop_object() -> Result<()> { + let mut arguments = Arguments::default(); + let object = Reference::ByteArray(ConcurrentVec::from(vec![42])); + arguments.push_object(None); + arguments.push_object(Some(object.clone())); + assert_eq!(arguments.pop_object()?, Some(object)); + assert_eq!(arguments.pop_object()?, None); + Ok(()) + } + + #[test] + fn test_pop_object_invalid_operand() { + let mut arguments = Arguments::default(); + arguments.push_int(42); + assert!(matches!( + arguments.pop_object(), + Err(InvalidOperand { + expected, + actual + }) if expected == "object" && actual == "int(42)" + )); + } + + #[test] + fn test_pop_underflow() { + let mut arguments = Arguments::default(); + let result = arguments.pop(); + assert!(matches!(result, Err(ArgumentsUnderflow))); + } + + #[test] + fn test_peek_top_value() -> Result<()> { + let mut arguments = Arguments::default(); + arguments.push_int(1); + arguments.push_int(2); + + assert_eq!(arguments.peek()?, &Value::Int(2)); + assert_eq!(arguments.len(), 2); + Ok(()) + } + + #[test] + fn test_peek_underflow() { + let arguments = Arguments::default(); + let result = arguments.peek(); + assert!(matches!(result, Err(ArgumentsUnderflow))); + } + + #[test] + fn test_is_empty() { + let mut arguments = Arguments::default(); + assert!(arguments.is_empty()); + + arguments.push_int(42); + assert!(!arguments.is_empty()); + } + + #[test] + fn test_display() { + let mut arguments = Arguments::default(); + arguments.push_int(1); + arguments.push_int(2); + assert_eq!("[int(1), int(2)]", arguments.to_string()); + } +} diff --git a/ristretto_vm/src/call_stack.rs b/ristretto_vm/src/call_stack.rs new file mode 100644 index 00000000..e2e7c7ad --- /dev/null +++ b/ristretto_vm/src/call_stack.rs @@ -0,0 +1,142 @@ +use crate::arguments::Arguments; +use crate::{native_methods, Frame, Result, VM}; +use ristretto_classloader::Error::MethodNotFound; +use ristretto_classloader::{Class, Method, Value}; +use std::cell::RefCell; +use std::rc::Rc; +use std::sync::Arc; +use tracing::{debug, event_enabled, Level}; + +/// A call stack is a stack of frames that are executed in order. +#[derive(Debug, Default)] +pub struct CallStack { + pub(crate) frames: Vec>>, +} + +impl CallStack { + /// Create a new call stack. + #[must_use] + pub fn new() -> Self { + CallStack { frames: Vec::new() } + } + + /// Add a new frame to the call stack and invoke the method. To invoke a method on an object + /// reference, the object reference must be the first argument in the arguments vector. + /// + /// # Errors + /// if the method cannot be invoked. + pub fn execute( + &mut self, + vm: &VM, + class: &Arc, + method: &Arc, + arguments: Vec, + ) -> Result> { + let class_name = class.name(); + let method_name = method.name(); + let method_descriptor = method.descriptor(); + + if event_enabled!(Level::DEBUG) { + let access_flags = method.access_flags(); + debug!("execute: {class_name}.{method_name}{method_descriptor} {access_flags}"); + } + + let registry = native_methods::registry(); + let rust_method = registry.get(class_name, method_name, method_descriptor); + + let (result, frame_added) = if let Some(rust_method) = rust_method { + let arguments = Arguments::new(arguments); + let result = rust_method(vm, self, arguments); + (result, false) + } else if method.is_native() { + return Err(MethodNotFound { + class_name: class_name.to_string(), + method_name: method_name.to_string(), + method_descriptor: method_descriptor.to_string(), + } + .into()); + } else { + let arguments = CallStack::adjust_arguments(arguments); + let frame = Rc::new(RefCell::new(Frame::new(class, method, arguments)?)); + self.frames.push(frame.clone()); + let mut frame = frame.borrow_mut(); + let result = frame.execute(vm, self); + (result, true) + }; + + if event_enabled!(Level::DEBUG) { + match &result { + Ok(Some(value)) => { + let value = value.to_string(); + if value.len() > 100 { + debug!("result: {}...", &value.as_str()[..97]); + } else { + debug!("result: {value}"); + } + } + Ok(None) => {} + Err(error) => { + debug!("error: {error}"); + } + } + } + + if frame_added { + self.frames.pop(); + } + + match result { + Ok(result) => Ok(result), + Err(error) => { + // TODO: Handle exceptions + Err(error) + } + } + } + + /// The JVM specification requires that Long and Double take two places in the arguments list + /// when passed to a method. This method adjusts the arguments list to account for this. + /// + /// See: + fn adjust_arguments(mut arguments: Vec) -> Vec { + let mut index = arguments.len(); + while index > 0 { + index -= 1; + match &arguments[index] { + Value::Long(_) | Value::Double(_) => { + arguments.insert(index + 1, Value::Unused); + } + _ => {} + } + } + arguments + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ristretto_classloader::Value; + + #[test] + fn test_adjust_arguments() { + let arguments = vec![ + Value::Int(1), + Value::Long(2), + Value::Float(3.0), + Value::Double(4.0), + ]; + let adjusted_arguments = CallStack::adjust_arguments(arguments); + assert_eq!( + adjusted_arguments, + vec![ + Value::Int(1), + Value::Long(2), + Value::Unused, + Value::Float(3.0), + Value::Double(4.0), + Value::Unused, + ] + ); + } +} diff --git a/ristretto_vm/src/configuration.rs b/ristretto_vm/src/configuration.rs new file mode 100644 index 00000000..54c5496b --- /dev/null +++ b/ristretto_vm/src/configuration.rs @@ -0,0 +1,159 @@ +use ristretto_classloader::ClassPath; +use std::env; +use std::path::PathBuf; +use std::string::ToString; + +pub(crate) const DEFAULT_RUNTIME_VERSION: &str = "21.0.4.7.1"; + +/// Configuration +#[derive(Debug, PartialEq)] +pub struct Configuration { + class_path: ClassPath, + main_class: Option, + jar: Option, + runtime_version: String, +} + +/// Configuration +impl Configuration { + /// Get the class path + #[must_use] + pub fn class_path(&self) -> &ClassPath { + &self.class_path + } + + /// Get the main class + #[must_use] + pub fn main_class(&self) -> Option { + self.main_class.clone() + } + + /// Get the jar + #[must_use] + pub fn jar(&self) -> Option<&PathBuf> { + self.jar.as_ref() + } + + /// Get the runtime version for the VM + #[must_use] + pub fn runtime_version(&self) -> &String { + &self.runtime_version + } +} + +/// Configuration builder +#[derive(Debug)] +pub struct ConfigurationBuilder { + class_path: Option, + main_class: Option, + jar: Option, + runtime_version: Option, +} + +/// Configuration builder +impl ConfigurationBuilder { + /// Create a new configuration builder + #[must_use] + pub fn new() -> Self { + ConfigurationBuilder { + class_path: None, + main_class: None, + jar: None, + runtime_version: None, + } + } + + /// Set the VM class path + #[must_use] + pub fn class_path(mut self, class_path: ClassPath) -> Self { + self.class_path = Some(class_path); + self + } + + /// Set the main class to run + #[must_use] + pub fn main_class>(mut self, main_class: S) -> Self { + self.main_class = Some(main_class.as_ref().to_string()); + self + } + + /// Set the jar + #[must_use] + pub fn jar(mut self, jar: PathBuf) -> Self { + self.jar = Some(jar); + self + } + + /// Set the VM runtime version + #[must_use] + pub fn runtime_version>(mut self, version: S) -> Self { + self.runtime_version = Some(version.as_ref().to_string()); + self + } + + /// Build the configuration + #[must_use] + pub fn build(self) -> Configuration { + let class_path = if let Some(class_path) = self.class_path { + class_path + } else { + ClassPath::from(".") + }; + + let runtime_version = if let Some(runtime_version) = self.runtime_version { + runtime_version.to_string() + } else if let Ok(runtime_version) = env::var("JAVA_VERSION") { + runtime_version + } else { + DEFAULT_RUNTIME_VERSION.to_string() + }; + + Configuration { + class_path, + main_class: self.main_class, + jar: self.jar, + runtime_version, + } + } +} + +/// Default configuration builder +impl Default for ConfigurationBuilder { + /// Create a default configuration builder + fn default() -> Self { + ConfigurationBuilder::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_configuration_builder() { + let configuration = ConfigurationBuilder::new() + .class_path(ClassPath::from("..")) + .main_class("Foo") + .jar(PathBuf::from("test.jar")) + .runtime_version("21") + .build(); + assert_eq!(&ClassPath::from(".."), configuration.class_path()); + assert_eq!(Some("Foo".to_string()), configuration.main_class()); + assert_eq!(Some(&PathBuf::from("test.jar")), configuration.jar()); + assert_eq!("21", configuration.runtime_version()); + } + + #[test] + fn test_configuration_builder_new() { + let configuration = ConfigurationBuilder::new().build(); + assert_eq!(&ClassPath::from("."), configuration.class_path()); + assert_eq!(&DEFAULT_RUNTIME_VERSION, configuration.runtime_version()); + } + + #[test] + fn test_configuration_builder_default() { + let configuration = ConfigurationBuilder::default().build(); + assert_eq!(&ClassPath::from("."), configuration.class_path()); + assert_eq!(&DEFAULT_RUNTIME_VERSION, configuration.runtime_version()); + } +} diff --git a/ristretto_vm/src/error.rs b/ristretto_vm/src/error.rs new file mode 100644 index 00000000..0c1ba45f --- /dev/null +++ b/ristretto_vm/src/error.rs @@ -0,0 +1,67 @@ +/// Ristretto VM result type +pub type Result = core::result::Result; + +/// Errors that can occur when loading classes +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// The arguments stack underflow + #[error("Arguments stack underflow")] + ArgumentsUnderflow, + /// An array index is out of bounds + #[error("An array index is out of bounds: {0}")] + ArrayIndexOutOfBounds(usize), + /// An exception occurred casting a value + #[error("An exception occurred casting to: {0}")] + ClassCastError(String), + /// An error occurred while loading a class file + #[error(transparent)] + ClassFileError(#[from] ristretto_classfile::Error), + /// An error occurred while loading a class + #[error(transparent)] + ClassLoaderError(#[from] ristretto_classloader::Error), + /// Invalid constant + #[error("Invalid constant; expected {expected}, found {actual}")] + InvalidConstant { expected: String, actual: String }, + /// Invalid constant index + #[error("Invalid instant index: {0}")] + InvalidConstantIndex(u16), + /// Invalid local variable + #[error("Invalid local variable; expected {expected}, found {actual}")] + InvalidLocalVariable { expected: String, actual: String }, + /// Invalid local variable index + #[error("Invalid local variable index: {0}")] + InvalidLocalVariableIndex(usize), + /// Invalid operand for the operation + #[error("Invalid operand; expected {expected}, found {actual}")] + InvalidOperand { expected: String, actual: String }, + /// Invalid program counter + #[error("Invalid program counter: {0}")] + InvalidProgramCounter(usize), + /// Invalid stack value + #[error("Invalid stack value; expected {expected}, found {actual}")] + InvalidStackValue { expected: String, actual: String }, + /// Null pointer + #[error("Null pointer")] + NullPointer, + /// The operand stack overflowed + #[error("Operand stack overflow")] + OperandStackOverflow, + /// The operand stack underflow + #[error("Operand stack underflow")] + OperandStackUnderflow, + /// An error occurred while attempting to parse an integer + #[error(transparent)] + ParseIntError(#[from] std::num::ParseIntError), + /// Poisoned lock + #[error("Poisoned lock: {0}")] + PoisonedLock(String), + /// Runtime error + #[error("Runtime error: {0}")] + RuntimeError(String), + /// An error occurred while converting from an integer + #[error(transparent)] + TryFromIntError(#[from] std::num::TryFromIntError), + /// Unsupported class file version + #[error("Unsupported class file version: {0}")] + UnsupportedClassFileVersion(u16), +} diff --git a/ristretto_vm/src/frame.rs b/ristretto_vm/src/frame.rs new file mode 100644 index 00000000..355199ec --- /dev/null +++ b/ristretto_vm/src/frame.rs @@ -0,0 +1,579 @@ +use crate::call_stack::CallStack; +use crate::frame::ExecutionResult::{Continue, ContinueAtPosition, Return}; +use crate::instruction::{ + aaload, aastore, aconst_null, aload, aload_0, aload_1, aload_2, aload_3, aload_w, anewarray, + areturn, arraylength, astore, astore_0, astore_1, astore_2, astore_3, astore_w, baload, + bastore, bipush, caload, castore, checkcast, d2f, d2i, d2l, dadd, daload, dastore, dcmpg, + dcmpl, dconst_0, dconst_1, ddiv, dload, dload_0, dload_1, dload_2, dload_3, dload_w, dmul, + dneg, drem, dreturn, dstore, dstore_0, dstore_1, dstore_2, dstore_3, dstore_w, dsub, dup, dup2, + dup2_x1, dup2_x2, dup_x1, dup_x2, f2d, f2i, f2l, fadd, faload, fastore, fcmpg, fcmpl, fconst_0, + fconst_1, fconst_2, fdiv, fload, fload_0, fload_1, fload_2, fload_3, fload_w, fmul, fneg, frem, + freturn, fstore, fstore_0, fstore_1, fstore_2, fstore_3, fstore_w, fsub, getfield, getstatic, + goto, goto_w, i2b, i2c, i2d, i2f, i2l, i2s, iadd, iaload, iand, iastore, iconst_0, iconst_1, + iconst_2, iconst_3, iconst_4, iconst_5, iconst_m1, idiv, if_acmpeq, if_acmpne, if_icmpeq, + if_icmpge, if_icmpgt, if_icmple, if_icmplt, if_icmpne, ifeq, ifge, ifgt, ifle, iflt, ifne, + ifnonnull, ifnull, iinc, iinc_w, iload, iload_0, iload_1, iload_2, iload_3, iload_w, imul, + ineg, instanceof, invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual, + ior, irem, ireturn, ishl, ishr, istore, istore_0, istore_1, istore_2, istore_3, istore_w, isub, + iushr, ixor, jsr, jsr_w, l2d, l2f, l2i, ladd, laload, land, lastore, lcmp, lconst_0, lconst_1, + ldc, ldc2_w, ldc_w, ldiv, lload, lload_0, lload_1, lload_2, lload_3, lload_w, lmul, lneg, + lookupswitch, lor, lrem, lreturn, lshl, lshr, lstore, lstore_0, lstore_1, lstore_2, lstore_3, + lstore_w, lsub, lushr, lxor, multianewarray, new, newarray, pop, pop2, putfield, putstatic, + ret, ret_w, saload, sastore, sipush, swap, tableswitch, +}; +use crate::Error::{InvalidOperand, InvalidProgramCounter}; +use crate::{LocalVariables, OperandStack, Result, VM}; +use ristretto_classfile::attributes::Instruction; +use ristretto_classloader::{Class, Method, Value}; +use std::sync::Arc; +use tracing::{debug, event_enabled, Level}; + +#[derive(Debug, PartialEq)] +pub(crate) enum ExecutionResult { + Return(Option), + Continue, + ContinueAtPosition(usize), +} + +/// A frame stores data and partial results, performs dynamic linking, returns method values, and +/// dispatches exceptions. +/// +/// See: +#[derive(Clone, Debug)] +pub(crate) struct Frame { + pub(crate) class: Arc, + pub(crate) method: Arc, + pub(crate) locals: LocalVariables, + pub(crate) stack: OperandStack, + pub(crate) program_counter: usize, +} + +impl Frame { + /// Create a new frame for the specified class. To invoke a method on an object reference, the + /// object reference must be the first argument in the arguments vector. + pub fn new(class: &Arc, method: &Arc, arguments: Vec) -> Result { + let max_locals = method.max_locals(); + let mut locals = LocalVariables::with_max_size(max_locals); + for (index, argument) in arguments.into_iter().enumerate() { + locals.set(index, argument)?; + } + let max_stack = method.max_stack(); + let stack = OperandStack::with_max_size(max_stack); + Ok(Frame { + class: class.clone(), + method: method.clone(), + locals, + stack, + program_counter: 0, + }) + } + + /// Execute the method in this frame + /// + /// # Errors + /// * if the program counter is invalid + /// * if an invalid instruction is encountered + pub fn execute(&mut self, vm: &VM, call_stack: &mut CallStack) -> Result> { + // TODO: avoid cloning code + let code = self.method.code().clone(); + + loop { + let Some(instruction) = code.get(self.program_counter) else { + return Err(InvalidProgramCounter(self.program_counter)); + }; + + if event_enabled!(Level::DEBUG) { + self.debug_execute(instruction)?; + } + + let result = self.process(vm, call_stack, instruction); + match result { + Ok(Continue) => self.program_counter += 1, + Ok(ContinueAtPosition(pc)) => self.program_counter = pc, + Ok(Return(value)) => return Ok(value.clone()), + Err(error) => { + // TODO: implement exception handling + return Err(error); + } + } + } + } + + /// Debug the execution of an instruction in this frame + #[inline] + fn debug_execute(&self, instruction: &Instruction) -> Result<()> { + let class_name = self.class.name(); + let method_name = self.method.name(); + let method_descriptor = self.method.descriptor(); + let source = if let Some(source_file) = self.class.source_file() { + let line_number = self.method.line_number(self.program_counter); + format!(" ({source_file}:{line_number})") + } else { + String::new() + }; + let constant_pool = self.class.constant_pool(); + let instruction = instruction.to_formatted_string(constant_pool)?; + debug!(" frame: {class_name}.{method_name}{method_descriptor}{source}"); + debug!(" locals: {}", self.locals); + debug!(" stack: {}", self.stack); + debug!( + " pc: {}; instruction: {instruction}", + self.program_counter + ); + Ok(()) + } + + /// Process an instruction in this frame + #[expect(clippy::too_many_lines)] + fn process( + &mut self, + vm: &VM, + call_stack: &mut CallStack, + instruction: &Instruction, + ) -> Result { + match instruction { + Instruction::Nop => Ok(Continue), // Do nothing + Instruction::Aconst_null => aconst_null(&mut self.stack), + Instruction::Iconst_m1 => iconst_m1(&mut self.stack), + Instruction::Iconst_0 => iconst_0(&mut self.stack), + Instruction::Iconst_1 => iconst_1(&mut self.stack), + Instruction::Iconst_2 => iconst_2(&mut self.stack), + Instruction::Iconst_3 => iconst_3(&mut self.stack), + Instruction::Iconst_4 => iconst_4(&mut self.stack), + Instruction::Iconst_5 => iconst_5(&mut self.stack), + Instruction::Lconst_0 => lconst_0(&mut self.stack), + Instruction::Lconst_1 => lconst_1(&mut self.stack), + Instruction::Fconst_0 => fconst_0(&mut self.stack), + Instruction::Fconst_1 => fconst_1(&mut self.stack), + Instruction::Fconst_2 => fconst_2(&mut self.stack), + Instruction::Dconst_0 => dconst_0(&mut self.stack), + Instruction::Dconst_1 => dconst_1(&mut self.stack), + Instruction::Bipush(value) => bipush(&mut self.stack, *value), + Instruction::Sipush(value) => sipush(&mut self.stack, *value), + Instruction::Ldc(index) => ldc(vm, call_stack, self, *index), + Instruction::Ldc_w(index) => ldc_w(vm, call_stack, self, *index), + Instruction::Ldc2_w(index) => ldc2_w(self, *index), + Instruction::Iload(index) => iload(&self.locals, &mut self.stack, *index), + Instruction::Lload(index) => lload(&self.locals, &mut self.stack, *index), + Instruction::Fload(index) => fload(&self.locals, &mut self.stack, *index), + Instruction::Dload(index) => dload(&self.locals, &mut self.stack, *index), + Instruction::Aload(index) => aload(&self.locals, &mut self.stack, *index), + Instruction::Iload_0 => iload_0(&self.locals, &mut self.stack), + Instruction::Iload_1 => iload_1(&self.locals, &mut self.stack), + Instruction::Iload_2 => iload_2(&self.locals, &mut self.stack), + Instruction::Iload_3 => iload_3(&self.locals, &mut self.stack), + Instruction::Lload_0 => lload_0(&self.locals, &mut self.stack), + Instruction::Lload_1 => lload_1(&self.locals, &mut self.stack), + Instruction::Lload_2 => lload_2(&self.locals, &mut self.stack), + Instruction::Lload_3 => lload_3(&self.locals, &mut self.stack), + Instruction::Fload_0 => fload_0(&self.locals, &mut self.stack), + Instruction::Fload_1 => fload_1(&self.locals, &mut self.stack), + Instruction::Fload_2 => fload_2(&self.locals, &mut self.stack), + Instruction::Fload_3 => fload_3(&self.locals, &mut self.stack), + Instruction::Dload_0 => dload_0(&self.locals, &mut self.stack), + Instruction::Dload_1 => dload_1(&self.locals, &mut self.stack), + Instruction::Dload_2 => dload_2(&self.locals, &mut self.stack), + Instruction::Dload_3 => dload_3(&self.locals, &mut self.stack), + Instruction::Aload_0 => aload_0(&self.locals, &mut self.stack), + Instruction::Aload_1 => aload_1(&self.locals, &mut self.stack), + Instruction::Aload_2 => aload_2(&self.locals, &mut self.stack), + Instruction::Aload_3 => aload_3(&self.locals, &mut self.stack), + Instruction::Iaload => iaload(&mut self.stack), + Instruction::Laload => laload(&mut self.stack), + Instruction::Faload => faload(&mut self.stack), + Instruction::Daload => daload(&mut self.stack), + Instruction::Aaload => aaload(&mut self.stack), + Instruction::Baload => baload(&mut self.stack), + Instruction::Caload => caload(&mut self.stack), + Instruction::Saload => saload(&mut self.stack), + Instruction::Istore(index) => istore(&mut self.locals, &mut self.stack, *index), + Instruction::Lstore(index) => lstore(&mut self.locals, &mut self.stack, *index), + Instruction::Fstore(index) => fstore(&mut self.locals, &mut self.stack, *index), + Instruction::Dstore(index) => dstore(&mut self.locals, &mut self.stack, *index), + Instruction::Astore(index) => astore(&mut self.locals, &mut self.stack, *index), + Instruction::Istore_0 => istore_0(&mut self.locals, &mut self.stack), + Instruction::Istore_1 => istore_1(&mut self.locals, &mut self.stack), + Instruction::Istore_2 => istore_2(&mut self.locals, &mut self.stack), + Instruction::Istore_3 => istore_3(&mut self.locals, &mut self.stack), + Instruction::Lstore_0 => lstore_0(&mut self.locals, &mut self.stack), + Instruction::Lstore_1 => lstore_1(&mut self.locals, &mut self.stack), + Instruction::Lstore_2 => lstore_2(&mut self.locals, &mut self.stack), + Instruction::Lstore_3 => lstore_3(&mut self.locals, &mut self.stack), + Instruction::Fstore_0 => fstore_0(&mut self.locals, &mut self.stack), + Instruction::Fstore_1 => fstore_1(&mut self.locals, &mut self.stack), + Instruction::Fstore_2 => fstore_2(&mut self.locals, &mut self.stack), + Instruction::Fstore_3 => fstore_3(&mut self.locals, &mut self.stack), + Instruction::Dstore_0 => dstore_0(&mut self.locals, &mut self.stack), + Instruction::Dstore_1 => dstore_1(&mut self.locals, &mut self.stack), + Instruction::Dstore_2 => dstore_2(&mut self.locals, &mut self.stack), + Instruction::Dstore_3 => dstore_3(&mut self.locals, &mut self.stack), + Instruction::Astore_0 => astore_0(&mut self.locals, &mut self.stack), + Instruction::Astore_1 => astore_1(&mut self.locals, &mut self.stack), + Instruction::Astore_2 => astore_2(&mut self.locals, &mut self.stack), + Instruction::Astore_3 => astore_3(&mut self.locals, &mut self.stack), + Instruction::Iastore => iastore(&mut self.stack), + Instruction::Lastore => lastore(&mut self.stack), + Instruction::Fastore => fastore(&mut self.stack), + Instruction::Dastore => dastore(&mut self.stack), + Instruction::Aastore => aastore(&mut self.stack), + Instruction::Bastore => bastore(&mut self.stack), + Instruction::Castore => castore(&mut self.stack), + Instruction::Sastore => sastore(&mut self.stack), + Instruction::Pop => pop(&mut self.stack), + Instruction::Pop2 => pop2(&mut self.stack), + Instruction::Dup => dup(&mut self.stack), + Instruction::Dup_x1 => dup_x1(&mut self.stack), + Instruction::Dup_x2 => dup_x2(&mut self.stack), + Instruction::Dup2 => dup2(&mut self.stack), + Instruction::Dup2_x1 => dup2_x1(&mut self.stack), + Instruction::Dup2_x2 => dup2_x2(&mut self.stack), + Instruction::Swap => swap(&mut self.stack), + Instruction::Iadd => iadd(&mut self.stack), + Instruction::Ladd => ladd(&mut self.stack), + Instruction::Fadd => fadd(&mut self.stack), + Instruction::Dadd => dadd(&mut self.stack), + Instruction::Isub => isub(&mut self.stack), + Instruction::Lsub => lsub(&mut self.stack), + Instruction::Fsub => fsub(&mut self.stack), + Instruction::Dsub => dsub(&mut self.stack), + Instruction::Imul => imul(&mut self.stack), + Instruction::Lmul => lmul(&mut self.stack), + Instruction::Fmul => fmul(&mut self.stack), + Instruction::Dmul => dmul(&mut self.stack), + Instruction::Idiv => idiv(&mut self.stack), + Instruction::Ldiv => ldiv(&mut self.stack), + Instruction::Fdiv => fdiv(&mut self.stack), + Instruction::Ddiv => ddiv(&mut self.stack), + Instruction::Irem => irem(&mut self.stack), + Instruction::Lrem => lrem(&mut self.stack), + Instruction::Frem => frem(&mut self.stack), + Instruction::Drem => drem(&mut self.stack), + Instruction::Ineg => ineg(&mut self.stack), + Instruction::Lneg => lneg(&mut self.stack), + Instruction::Fneg => fneg(&mut self.stack), + Instruction::Dneg => dneg(&mut self.stack), + Instruction::Ishl => ishl(&mut self.stack), + Instruction::Lshl => lshl(&mut self.stack), + Instruction::Ishr => ishr(&mut self.stack), + Instruction::Lshr => lshr(&mut self.stack), + Instruction::Iushr => iushr(&mut self.stack), + Instruction::Lushr => lushr(&mut self.stack), + Instruction::Iand => iand(&mut self.stack), + Instruction::Land => land(&mut self.stack), + Instruction::Ior => ior(&mut self.stack), + Instruction::Lor => lor(&mut self.stack), + Instruction::Ixor => ixor(&mut self.stack), + Instruction::Lxor => lxor(&mut self.stack), + Instruction::Iinc(index, constant) => iinc(&mut self.locals, *index, *constant), + Instruction::I2l => i2l(&mut self.stack), + Instruction::I2f => i2f(&mut self.stack), + Instruction::I2d => i2d(&mut self.stack), + Instruction::L2i => l2i(&mut self.stack), + Instruction::L2f => l2f(&mut self.stack), + Instruction::L2d => l2d(&mut self.stack), + Instruction::F2i => f2i(&mut self.stack), + Instruction::F2l => f2l(&mut self.stack), + Instruction::F2d => f2d(&mut self.stack), + Instruction::D2i => d2i(&mut self.stack), + Instruction::D2l => d2l(&mut self.stack), + Instruction::D2f => d2f(&mut self.stack), + Instruction::I2b => i2b(&mut self.stack), + Instruction::I2c => i2c(&mut self.stack), + Instruction::I2s => i2s(&mut self.stack), + Instruction::Lcmp => lcmp(&mut self.stack), + Instruction::Fcmpl => fcmpl(&mut self.stack), + Instruction::Fcmpg => fcmpg(&mut self.stack), + Instruction::Dcmpl => dcmpl(&mut self.stack), + Instruction::Dcmpg => dcmpg(&mut self.stack), + Instruction::Ifeq(address) => ifeq(&mut self.stack, *address), + Instruction::Ifne(address) => ifne(&mut self.stack, *address), + Instruction::Iflt(address) => iflt(&mut self.stack, *address), + Instruction::Ifge(address) => ifge(&mut self.stack, *address), + Instruction::Ifgt(address) => ifgt(&mut self.stack, *address), + Instruction::Ifle(address) => ifle(&mut self.stack, *address), + Instruction::If_icmpeq(address) => if_icmpeq(&mut self.stack, *address), + Instruction::If_icmpne(address) => if_icmpne(&mut self.stack, *address), + Instruction::If_icmplt(address) => if_icmplt(&mut self.stack, *address), + Instruction::If_icmpge(address) => if_icmpge(&mut self.stack, *address), + Instruction::If_icmpgt(address) => if_icmpgt(&mut self.stack, *address), + Instruction::If_icmple(address) => if_icmple(&mut self.stack, *address), + Instruction::If_acmpeq(address) => if_acmpeq(&mut self.stack, *address), + Instruction::If_acmpne(address) => if_acmpne(&mut self.stack, *address), + Instruction::Goto(address) => goto(*address), + Instruction::Jsr(address) => jsr(&mut self.stack, *address), + Instruction::Ret(index) => ret(&self.locals, *index), + Instruction::Tableswitch { + default, + low, + high, + offsets, + } => tableswitch( + &mut self.stack, + self.program_counter, + *default, + *low, + *high, + offsets, + ), + Instruction::Lookupswitch { default, pairs } => { + lookupswitch(&mut self.stack, self.program_counter, *default, pairs) + } + Instruction::Ireturn => ireturn(&mut self.stack), + Instruction::Lreturn => lreturn(&mut self.stack), + Instruction::Freturn => freturn(&mut self.stack), + Instruction::Dreturn => dreturn(&mut self.stack), + Instruction::Areturn => areturn(&mut self.stack), + Instruction::Return => Ok(Return(None)), + Instruction::Getstatic(index) => getstatic( + vm, + call_stack, + &mut self.stack, + self.class.constant_pool(), + *index, + ), + Instruction::Putstatic(index) => putstatic( + vm, + call_stack, + &mut self.stack, + self.class.constant_pool(), + *index, + ), + Instruction::Getfield(index) => { + getfield(&mut self.stack, self.class.constant_pool(), *index) + } + Instruction::Putfield(index) => { + putfield(&mut self.stack, self.class.constant_pool(), *index) + } + Instruction::Invokevirtual(index) => invokevirtual( + vm, + call_stack, + &mut self.stack, + self.class.constant_pool(), + *index, + ), + Instruction::Invokespecial(index) => invokespecial( + vm, + call_stack, + &mut self.stack, + self.class.constant_pool(), + *index, + ), + Instruction::Invokestatic(index) => invokestatic( + vm, + call_stack, + &mut self.stack, + self.class.constant_pool(), + *index, + ), + Instruction::Invokeinterface(index, _count) => invokeinterface( + vm, + call_stack, + &mut self.stack, + self.class.constant_pool(), + *index, + ), + Instruction::Invokedynamic(index) => invokedynamic( + vm, + call_stack, + &mut self.stack, + self.class.constant_pool(), + *index, + ), + Instruction::New(index) => new( + vm, + call_stack, + &mut self.stack, + self.class.constant_pool(), + *index, + ), + Instruction::Newarray(array_type) => newarray(&mut self.stack, array_type), + Instruction::Anewarray(index) => { + anewarray(vm, call_stack, &mut self.stack, &self.class, *index) + } + Instruction::Arraylength => arraylength(&mut self.stack), + Instruction::Athrow => todo!(), + Instruction::Checkcast(class_index) => { + let constant_pool = self.class.constant_pool(); + let class_name = constant_pool.try_get_class(*class_index)?; + checkcast(&mut self.stack, class_name) + } + Instruction::Instanceof(class_index) => { + let constant_pool = self.class.constant_pool(); + let class_name = constant_pool.try_get_class(*class_index)?; + instanceof(&mut self.stack, class_name) + } + Instruction::Monitorenter | Instruction::Monitorexit => { + // The monitorenter and monitorexit instructions are not currently used by this + // implementation. + // See: https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-6.html#jvms-6.5.monitorenter + // https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-6.html#jvms-6.5.monitorexit + let _ = self.stack.pop_object()?; + Ok(Continue) + } + Instruction::Wide => { + // The wide instruction is not directly used by this implementation. The wide + // versions of instructions are specifically enumerated in the instruction set. + Err(InvalidOperand { + expected: "*_w instruction".to_string(), + actual: "Wide".to_string(), + }) + } + Instruction::Multianewarray(index, dimensions) => multianewarray( + vm, + call_stack, + &mut self.stack, + &self.class, + *index, + *dimensions, + ), + Instruction::Ifnull(address) => ifnull(&mut self.stack, *address), + Instruction::Ifnonnull(address) => ifnonnull(&mut self.stack, *address), + Instruction::Goto_w(address) => goto_w(*address), + Instruction::Jsr_w(address) => jsr_w(&mut self.stack, *address), + Instruction::Breakpoint | Instruction::Impdep1 | Instruction::Impdep2 => { + // Breakpoint, Impdep1 and Impdep2 instructions are reserved for debugging and implementation + // dependent operations. + // See: https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-6.html#jvms-6.2 + Ok(Continue) + } + // Wide instructions + Instruction::Iload_w(index) => iload_w(&self.locals, &mut self.stack, *index), + Instruction::Lload_w(index) => lload_w(&self.locals, &mut self.stack, *index), + Instruction::Fload_w(index) => fload_w(&self.locals, &mut self.stack, *index), + Instruction::Dload_w(index) => dload_w(&self.locals, &mut self.stack, *index), + Instruction::Aload_w(index) => aload_w(&self.locals, &mut self.stack, *index), + Instruction::Istore_w(index) => istore_w(&mut self.locals, &mut self.stack, *index), + Instruction::Lstore_w(index) => lstore_w(&mut self.locals, &mut self.stack, *index), + Instruction::Fstore_w(index) => fstore_w(&mut self.locals, &mut self.stack, *index), + Instruction::Dstore_w(index) => dstore_w(&mut self.locals, &mut self.stack, *index), + Instruction::Astore_w(index) => astore_w(&mut self.locals, &mut self.stack, *index), + Instruction::Iinc_w(index, constant) => iinc_w(&mut self.locals, *index, *constant), + Instruction::Ret_w(index) => ret_w(&self.locals, *index), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::call_stack::CallStack; + use crate::configuration::ConfigurationBuilder; + use ristretto_classloader::ClassPath; + use std::path::PathBuf; + + fn get_class(class_name: &str) -> Result<(VM, CallStack, Arc)> { + let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let classes_path = cargo_manifest.join("../classes"); + let class_path = ClassPath::from(classes_path.to_string_lossy()); + let configuration = ConfigurationBuilder::new() + .class_path(class_path.clone()) + .build(); + let vm = VM::new(configuration)?; + let mut call_stack = CallStack::new(); + let class = vm.class(&mut call_stack, class_name)?; + Ok((vm, call_stack, class)) + } + + #[test] + fn test_execute() -> Result<()> { + let (vm, mut call_stack, class) = get_class("Expressions")?; + let method = class.method("add", "(II)I").expect("method not found"); + let arguments = vec![Value::Int(1), Value::Int(2)]; + let mut frame = Frame::new(&class, &method, arguments)?; + let result = frame.execute(&vm, &mut call_stack)?; + assert!(matches!(result, Some(Value::Int(3)))); + Ok(()) + } + + #[test] + fn test_initial_frame() -> Result<()> { + let (_vm, _call_stack, frame) = crate::test::frame()?; + assert!(frame.locals.is_empty()); + assert!(frame.stack.is_empty()); + Ok(()) + } + + #[test] + fn test_process_nop() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let process_result = frame.process(&vm, &mut call_stack, &Instruction::Nop)?; + assert_eq!(Continue, process_result); + assert!(frame.locals.is_empty()); + assert!(frame.stack.is_empty()); + Ok(()) + } + + #[test] + fn test_process_return() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let process_result = frame.process(&vm, &mut call_stack, &Instruction::Return)?; + assert!(matches!(process_result, Return(None))); + Ok(()) + } + + // #[test] + // fn test_process_athrow() -> Result<()> { + // todo!() + // } + + #[test] + fn test_process_monitorenter() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + frame.stack.push_object(None)?; + let process_result = frame.process(&vm, &mut call_stack, &Instruction::Monitorenter)?; + assert_eq!(Continue, process_result); + Ok(()) + } + + #[test] + fn test_process_monitorexit() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + frame.stack.push_object(None)?; + let process_result = frame.process(&vm, &mut call_stack, &Instruction::Monitorexit)?; + assert_eq!(Continue, process_result); + Ok(()) + } + + #[test] + fn test_process_wide() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + assert!(matches!( + frame.process(&vm, &mut call_stack, &Instruction::Wide), + Err(InvalidOperand { + expected, + actual + }) if expected == "*_w instruction" && actual == "Wide" + )); + Ok(()) + } + + #[test] + fn test_process_breakpoint() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let process_result = frame.process(&vm, &mut call_stack, &Instruction::Breakpoint)?; + assert_eq!(Continue, process_result); + assert!(frame.locals.is_empty()); + assert!(frame.stack.is_empty()); + Ok(()) + } + + #[test] + fn test_process_impdep1() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let process_result = frame.process(&vm, &mut call_stack, &Instruction::Impdep1)?; + assert_eq!(Continue, process_result); + assert!(frame.locals.is_empty()); + assert!(frame.stack.is_empty()); + Ok(()) + } + + #[test] + fn test_process_impdep2() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let process_result = frame.process(&vm, &mut call_stack, &Instruction::Impdep2)?; + assert_eq!(Continue, process_result); + assert!(frame.locals.is_empty()); + assert!(frame.stack.is_empty()); + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/array.rs b/ristretto_vm/src/instruction/array.rs new file mode 100644 index 00000000..3602333d --- /dev/null +++ b/ristretto_vm/src/instruction/array.rs @@ -0,0 +1,411 @@ +use crate::call_stack::CallStack; +use crate::frame::ExecutionResult; +use crate::frame::ExecutionResult::Continue; +use crate::operand_stack::OperandStack; +use crate::Error::{InvalidStackValue, NullPointer}; +use crate::{Result, VM}; +use ristretto_classfile::attributes::ArrayType; +use ristretto_classloader::{Class, ConcurrentVec, Reference}; +use std::sync::Arc; + +/// See: +#[inline] +pub(crate) fn newarray( + stack: &mut OperandStack, + array_type: &ArrayType, +) -> Result { + let count = stack.pop_int()?; + let count = usize::try_from(count)?; + let array = match array_type { + ArrayType::Char => Reference::CharArray(ConcurrentVec::from(vec![0; count])), + ArrayType::Float => Reference::FloatArray(ConcurrentVec::from(vec![0.0; count])), + ArrayType::Double => Reference::DoubleArray(ConcurrentVec::from(vec![0.0; count])), + ArrayType::Boolean | ArrayType::Byte => { + Reference::ByteArray(ConcurrentVec::from(vec![0; count])) + } + ArrayType::Short => Reference::ShortArray(ConcurrentVec::from(vec![0; count])), + ArrayType::Int => Reference::IntArray(ConcurrentVec::from(vec![0; count])), + ArrayType::Long => Reference::LongArray(ConcurrentVec::from(vec![0; count])), + }; + stack.push_object(Some(array))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn anewarray( + vm: &VM, + call_stack: &mut CallStack, + stack: &mut OperandStack, + class: &Arc, + index: u16, +) -> Result { + let count = stack.pop_int()?; + let count = usize::try_from(count)?; + let constant_pool = class.constant_pool(); + let class_name = constant_pool.try_get_class(index)?; + let class = vm.class(call_stack, class_name)?; + let array = Reference::Array(class, ConcurrentVec::from(vec![None; count])); + stack.push_object(Some(array))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn arraylength(stack: &mut OperandStack) -> Result { + let length = match stack.pop_object()? { + None => return Err(NullPointer), + Some(Reference::ByteArray(ref array)) => array.len()?, + Some(Reference::CharArray(ref array)) => array.len()?, + Some(Reference::FloatArray(ref array)) => array.len()?, + Some(Reference::DoubleArray(ref array)) => array.len()?, + Some(Reference::ShortArray(ref array)) => array.len()?, + Some(Reference::IntArray(ref array)) => array.len()?, + Some(Reference::LongArray(ref array)) => array.len()?, + Some(Reference::Array(_class, ref array)) => array.len()?, + Some(object) => { + return Err(InvalidStackValue { + expected: "array".to_string(), + actual: object.to_string(), + }) + } + }; + stack.push_int(i32::try_from(length)?)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn multianewarray( + vm: &VM, + call_stack: &mut CallStack, + stack: &mut OperandStack, + class: &Arc, + index: u16, + dimensions: u8, +) -> Result { + let count = stack.pop_int()?; + let count = usize::try_from(count)?; + let constant_pool = class.constant_pool(); + let class_name = constant_pool.try_get_class(index)?; + let class = vm.class(call_stack, class_name)?; + let array = Reference::Array(class, ConcurrentVec::from(vec![None; count])); + + for _ in 1..dimensions { + let count = stack.pop_int()?; + let _count = usize::try_from(count)?; + todo!() + } + + stack.push_object(Some(array))?; + Ok(Continue) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::frame::ExecutionResult::Continue; + use crate::frame::Frame; + use ristretto_classfile::attributes::ArrayType; + use ristretto_classfile::MethodAccessFlags; + use ristretto_classloader::{Method, Value}; + use std::sync::Arc; + + #[test] + fn test_newarray_boolean() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = newarray(stack, &ArrayType::Boolean)?; + assert_eq!(Continue, result); + let object = stack.pop()?; + assert!(matches!( + object, + Value::Object(Some(Reference::ByteArray(_))) + )); + Ok(()) + } + + #[test] + fn test_newarray_byte() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = newarray(stack, &ArrayType::Byte)?; + assert_eq!(Continue, result); + let object = stack.pop()?; + assert!(matches!( + object, + Value::Object(Some(Reference::ByteArray(_))) + )); + Ok(()) + } + + #[test] + fn test_newarray_char() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = newarray(stack, &ArrayType::Char)?; + assert_eq!(Continue, result); + let object = stack.pop()?; + assert!(matches!( + object, + Value::Object(Some(Reference::CharArray(_))) + )); + Ok(()) + } + + #[test] + fn test_newarray_double() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = newarray(stack, &ArrayType::Double)?; + assert_eq!(Continue, result); + let object = stack.pop()?; + assert!(matches!( + object, + Value::Object(Some(Reference::DoubleArray(_))) + )); + Ok(()) + } + + #[test] + fn test_newarray_float() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = newarray(stack, &ArrayType::Float)?; + assert_eq!(Continue, result); + let object = stack.pop()?; + assert!(matches!( + object, + Value::Object(Some(Reference::FloatArray(_))) + )); + Ok(()) + } + + #[test] + fn test_newarray_int() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = newarray(stack, &ArrayType::Int)?; + assert_eq!(Continue, result); + let object = stack.pop()?; + assert!(matches!( + object, + Value::Object(Some(Reference::IntArray(_))) + )); + Ok(()) + } + + #[test] + fn test_newarray_long() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = newarray(stack, &ArrayType::Long)?; + assert_eq!(Continue, result); + let object = stack.pop()?; + assert!(matches!( + object, + Value::Object(Some(Reference::LongArray(_))) + )); + Ok(()) + } + + #[test] + fn test_newarray_short() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = newarray(stack, &ArrayType::Short)?; + assert_eq!(Continue, result); + let object = stack.pop()?; + assert!(matches!( + object, + Value::Object(Some(Reference::ShortArray(_))) + )); + Ok(()) + } + + #[test] + fn test_anewarray() -> Result<()> { + let (vm, mut call_stack, mut class) = crate::test::class()?; + let constant_pool = Arc::get_mut(&mut class).expect("class").constant_pool_mut(); + let class_index = constant_pool.add_class("java/lang/Object")?; + let method = Method::new( + MethodAccessFlags::STATIC, + "test", + "()V", + 10, + 10, + Vec::new(), + Vec::new(), + )?; + let arguments = Vec::new(); + let mut frame = Frame::new(&class, &Arc::new(method), arguments)?; + let stack = &mut frame.stack; + let class = frame.class; + + stack.push_int(0)?; + let result = anewarray(&vm, &mut call_stack, stack, &class, class_index)?; + assert_eq!(Continue, result); + let object = stack.pop()?; + assert!(matches!( + object, + Value::Object(Some(Reference::Array(_, _))) + )); + Ok(()) + } + + #[test] + fn test_arraylength_boolean() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(3)?; + let result = newarray(stack, &ArrayType::Boolean)?; + assert_eq!(Continue, result); + let result = arraylength(stack)?; + assert_eq!(Continue, result); + assert_eq!(3, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_arraylength_byte() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(3)?; + let result = newarray(stack, &ArrayType::Byte)?; + assert_eq!(Continue, result); + let result = arraylength(stack)?; + assert_eq!(Continue, result); + assert_eq!(3, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_arraylength_char() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(3)?; + let result = newarray(stack, &ArrayType::Char)?; + assert_eq!(Continue, result); + let result = arraylength(stack)?; + assert_eq!(Continue, result); + assert_eq!(3, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_arraylength_double() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(3)?; + let result = newarray(stack, &ArrayType::Double)?; + assert_eq!(Continue, result); + let result = arraylength(stack)?; + assert_eq!(Continue, result); + assert_eq!(3, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_arraylength_float() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(3)?; + let result = newarray(stack, &ArrayType::Float)?; + assert_eq!(Continue, result); + let result = arraylength(stack)?; + assert_eq!(Continue, result); + assert_eq!(3, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_arraylength_int() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(3)?; + let result = newarray(stack, &ArrayType::Int)?; + assert_eq!(Continue, result); + let result = arraylength(stack)?; + assert_eq!(Continue, result); + assert_eq!(3, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_arraylength_long() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(3)?; + let result = newarray(stack, &ArrayType::Long)?; + assert_eq!(Continue, result); + let result = arraylength(stack)?; + assert_eq!(Continue, result); + assert_eq!(3, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_arraylength_short() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(3)?; + let result = newarray(stack, &ArrayType::Short)?; + assert_eq!(Continue, result); + let result = arraylength(stack)?; + assert_eq!(Continue, result); + assert_eq!(3, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_arraylength_object() -> Result<()> { + let (vm, mut call_stack, mut class) = crate::test::class()?; + let constant_pool = Arc::get_mut(&mut class).expect("class").constant_pool_mut(); + let class_index = constant_pool.add_class("java/lang/Object")?; + let method = Method::new( + MethodAccessFlags::STATIC, + "test", + "()V", + 10, + 10, + Vec::new(), + Vec::new(), + )?; + let arguments = Vec::new(); + let mut frame = Frame::new(&class, &Arc::new(method), arguments)?; + let stack = &mut frame.stack; + let class = frame.class; + + stack.push_int(3)?; + let result = anewarray(&vm, &mut call_stack, stack, &class, class_index)?; + assert_eq!(Continue, result); + + let result = arraylength(stack)?; + assert_eq!(Continue, result); + assert_eq!(3, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_arraylength_null() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = arraylength(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_arraylength_invalid_type() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let invalid_value = vm.to_string_value(&mut call_stack, "foo")?; + let stack = &mut frame.stack; + stack.push(invalid_value)?; + let result = arraylength(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "array" && actual == "string(foo)" + )); + Ok(()) + } + + // #[test] + // fn test_multianewarray() -> Result<()> { + // todo!() + // } +} diff --git a/ristretto_vm/src/instruction/branch.rs b/ristretto_vm/src/instruction/branch.rs new file mode 100644 index 00000000..92b58de0 --- /dev/null +++ b/ristretto_vm/src/instruction/branch.rs @@ -0,0 +1,746 @@ +use crate::frame::ExecutionResult; +use crate::frame::ExecutionResult::{Continue, ContinueAtPosition}; +use crate::local_variables::LocalVariables; +use crate::operand_stack::OperandStack; +use crate::Result; +use indexmap::IndexMap; + +/// See: +#[inline] +pub(crate) fn ifeq(stack: &mut OperandStack, address: u16) -> Result { + if stack.pop_int()? == 0 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn ifne(stack: &mut OperandStack, address: u16) -> Result { + if stack.pop_int()? != 0 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iflt(stack: &mut OperandStack, address: u16) -> Result { + if stack.pop_int()? < 0 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn ifge(stack: &mut OperandStack, address: u16) -> Result { + if stack.pop_int()? >= 0 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn ifgt(stack: &mut OperandStack, address: u16) -> Result { + if stack.pop_int()? > 0 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn ifle(stack: &mut OperandStack, address: u16) -> Result { + if stack.pop_int()? <= 0 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn if_icmpeq(stack: &mut OperandStack, address: u16) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + if value1 == value2 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn if_icmpne(stack: &mut OperandStack, address: u16) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + if value1 != value2 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn if_icmplt(stack: &mut OperandStack, address: u16) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + if value1 < value2 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn if_icmpge(stack: &mut OperandStack, address: u16) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + if value1 >= value2 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn if_icmpgt(stack: &mut OperandStack, address: u16) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + if value1 > value2 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn if_icmple(stack: &mut OperandStack, address: u16) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + if value1 <= value2 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn if_acmpeq(stack: &mut OperandStack, address: u16) -> Result { + let value2 = stack.pop_object()?; + let value1 = stack.pop_object()?; + if value1 == value2 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn if_acmpne(stack: &mut OperandStack, address: u16) -> Result { + let value2 = stack.pop_object()?; + let value1 = stack.pop_object()?; + if value1 != value2 { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[expect(clippy::unnecessary_wraps)] +#[inline] +pub(crate) fn goto(address: u16) -> Result { + Ok(ContinueAtPosition(usize::from(address))) +} + +/// See: +/// See: +#[inline] +pub(crate) fn goto_w(address: i32) -> Result { + Ok(ContinueAtPosition(usize::try_from(address)?)) +} + +/// See: +#[inline] +pub(crate) fn jsr(stack: &mut OperandStack, address: u16) -> Result { + let address = i32::from(address); + stack.push_int(address)?; + Ok(ContinueAtPosition(usize::try_from(address)?)) +} + +/// See: +/// See: +#[inline] +pub(crate) fn jsr_w(stack: &mut OperandStack, address: i32) -> Result { + stack.push_int(address)?; + Ok(ContinueAtPosition(usize::try_from(address)?)) +} + +/// See: +#[inline] +pub(crate) fn ret(locals: &LocalVariables, index: u8) -> Result { + let address = locals.get_int(usize::from(index))?; + Ok(ContinueAtPosition(usize::try_from(address)?)) +} + +/// See: +/// See: +#[inline] +pub(crate) fn ret_w(locals: &LocalVariables, index: u16) -> Result { + let address = locals.get_int(usize::from(index))?; + Ok(ContinueAtPosition(usize::try_from(address)?)) +} + +/// See: +#[expect(clippy::ptr_arg)] +#[inline] +pub(crate) fn tableswitch( + stack: &mut OperandStack, + program_counter: usize, + default: i32, + low: i32, + high: i32, + offsets: &Vec, +) -> Result { + let key = stack.pop_int()?; + let address = if key < low || key > high { + usize::try_from(default)? + } else { + let index = usize::try_from(key - low)?; + usize::try_from(offsets[index])? + }; + Ok(ContinueAtPosition(program_counter + address)) +} + +/// See: +#[inline] +pub(crate) fn lookupswitch( + stack: &mut OperandStack, + program_counter: usize, + default: i32, + pairs: &IndexMap, +) -> Result { + let key = stack.pop_int()?; + let address = match pairs.get(&key) { + Some(offset) => usize::try_from(*offset)?, + None => usize::try_from(default)?, + }; + Ok(ContinueAtPosition(program_counter + address)) +} + +/// See: +#[inline] +pub(crate) fn ifnull(stack: &mut OperandStack, address: u16) -> Result { + if stack.pop_object()?.is_none() { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn ifnonnull(stack: &mut OperandStack, address: u16) -> Result { + if stack.pop_object()?.is_some() { + return Ok(ContinueAtPosition(usize::from(address))); + } + Ok(Continue) +} + +#[cfg(test)] +mod test { + use super::*; + use ristretto_classloader::{ConcurrentVec, Reference}; + + #[test] + fn test_ifeq_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = ifeq(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_ifeq_not_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(1)?; + let result = ifeq(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_ifne_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = ifne(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_ifne_not_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(1)?; + let result = ifne(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_iflt_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = iflt(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_iflt_less_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(-1)?; + let result = iflt(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_ifge() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + + // Test less than + stack.push_int(-1)?; + let result = ifge(stack, 3)?; + assert_eq!(Continue, result); + + // Test equal + stack.push_int(0)?; + let result = ifge(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + + // Test greater than + stack.push_int(1)?; + let result = ifge(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_ifge_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = ifge(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_ifge_less_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(-1)?; + let result = ifge(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_ifge_greater_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(1)?; + let result = ifge(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_ifgt_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = ifgt(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_ifgt_greater_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(1)?; + let result = ifgt(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_ifle_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let result = ifle(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_ifle_less_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(-1)?; + let result = ifle(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_ifle_greater_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(1)?; + let result = ifle(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_if_icmpeq_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(1)?; + stack.push_int(1)?; + let result = if_icmpeq(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_if_icmpeq_not_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(2)?; + stack.push_int(1)?; + let result = if_icmpeq(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_if_icmpne_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(1)?; + stack.push_int(1)?; + let result = if_icmpne(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_if_icmpne_not_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(2)?; + stack.push_int(1)?; + let result = if_icmpne(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_if_icmplt_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(1)?; + stack.push_int(1)?; + let result = if_icmplt(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + #[test] + fn test_if_icmplt_less_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(1)?; + stack.push_int(2)?; + let result = if_icmplt(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_if_icmpge_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(1)?; + stack.push_int(1)?; + let result = if_icmpge(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_if_icmpge_less_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(1)?; + stack.push_int(2)?; + let result = if_icmpge(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_if_icmpge_greater_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(2)?; + stack.push_int(1)?; + let result = if_icmpge(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_if_icmpgt_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(1)?; + stack.push_int(1)?; + let result = if_icmpgt(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_if_icmpgt_greater_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(2)?; + stack.push_int(1)?; + let result = if_icmpgt(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_if_icmple_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(1)?; + stack.push_int(1)?; + let result = if_icmple(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_if_icmple_greater_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(2)?; + stack.push_int(1)?; + let result = if_icmple(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_if_icmple_less_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(1)?; + stack.push_int(2)?; + let result = if_icmple(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_if_acmpeq_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let object = Reference::ByteArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object.clone()))?; + stack.push_object(Some(object.clone()))?; + let result = if_acmpeq(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_if_acmpeq_not_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let object = Reference::ByteArray(ConcurrentVec::from(vec![42])); + stack.push_object(None)?; + stack.push_object(Some(object.clone()))?; + let result = if_acmpeq(stack, 3)?; + assert_eq!(Continue, result); + stack.push_object(Some(object.clone()))?; + stack.push_object(Some(object.clone()))?; + let result = if_acmpeq(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_if_acmpeq_null() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_object(None)?; + stack.push_object(None)?; + let result = if_acmpeq(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_if_acmpne_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let object = Reference::ByteArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object.clone()))?; + stack.push_object(Some(object.clone()))?; + let result = if_acmpne(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_if_acmpne_not_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let object = Reference::ByteArray(ConcurrentVec::from(vec![42])); + stack.push_object(None)?; + stack.push_object(Some(object.clone()))?; + let result = if_acmpne(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_if_acmpne_null() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_object(None)?; + stack.push_object(None)?; + let result = if_acmpne(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_goto() -> Result<()> { + let result = goto(3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_goto_w() -> Result<()> { + let result = goto_w(3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_jsr() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = jsr(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + assert_eq!(3, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_jsr_w() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = jsr_w(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + assert_eq!(3, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_ret() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + locals.set_int(0, 3)?; + let result = ret(locals, 0)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_ret_w() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + locals.set_int(0, 3)?; + let result = ret_w(locals, 0)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_tableswitch() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(2)?; + let program_counter = 10; + let result = tableswitch(stack, program_counter, 14, 1, 3, &vec![11, 12, 13])?; + assert!(matches!(result, ContinueAtPosition(22))); + Ok(()) + } + + #[test] + fn test_tableswitch_default() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let program_counter = 10; + let result = tableswitch(stack, program_counter, 14, 1, 3, &vec![11, 12, 13])?; + assert!(matches!(result, ContinueAtPosition(24))); + Ok(()) + } + + #[test] + fn test_lookupswitch() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(2)?; + let program_counter = 10; + let result = lookupswitch( + stack, + program_counter, + 14, + &IndexMap::from([(1, 11), (2, 12), (3, 13)]), + )?; + assert!(matches!(result, ContinueAtPosition(22))); + Ok(()) + } + + #[test] + fn test_lookupswitch_default() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(0)?; + let program_counter = 10; + let result = lookupswitch( + stack, + program_counter, + 14, + &IndexMap::from([(1, 11), (2, 12), (3, 13)]), + )?; + assert!(matches!(result, ContinueAtPosition(24))); + Ok(()) + } + + #[test] + fn test_ifnull_not_null() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let object = Reference::ByteArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + let result = ifnull(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_ifnull_null() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = ifnull(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_ifnonnull_not_null() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let object = Reference::ByteArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + let result = ifnonnull(stack, 3)?; + assert_eq!(ContinueAtPosition(3), result); + Ok(()) + } + + #[test] + fn test_ifnonnull_null() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = ifnonnull(stack, 3)?; + assert_eq!(Continue, result); + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/byte.rs b/ristretto_vm/src/instruction/byte.rs new file mode 100644 index 00000000..8e2907e9 --- /dev/null +++ b/ristretto_vm/src/instruction/byte.rs @@ -0,0 +1,158 @@ +use crate::frame::ExecutionResult; +use crate::frame::ExecutionResult::Continue; +use crate::operand_stack::OperandStack; +use crate::Error::{ArrayIndexOutOfBounds, InvalidStackValue, NullPointer}; +use crate::Result; +use ristretto_classloader::Reference; + +/// See: +#[inline] +pub(crate) fn baload(stack: &mut OperandStack) -> Result { + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::ByteArray(array)) => { + let index = usize::try_from(index)?; + let Some(value) = array.get(index)? else { + return Err(ArrayIndexOutOfBounds(index)); + }; + stack.push_int(i32::from(value))?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "byte array".to_string(), + actual: object.to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn bastore(stack: &mut OperandStack) -> Result { + let value = stack.pop_int()?; + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::ByteArray(ref mut array)) => { + let index = usize::try_from(index)?; + if index >= array.capacity()? { + return Err(ArrayIndexOutOfBounds(index)); + }; + array.set(index, i8::try_from(value)?)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "byte array".to_string(), + actual: object.to_string(), + }), + } +} + +#[cfg(test)] +mod test { + use super::*; + use ristretto_classloader::ConcurrentVec; + + #[test] + fn test_baload() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::ByteArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + let result = baload(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_baload_invalid_value() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + let result = baload(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "byte array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_baload_invalid_index() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::ByteArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + let result = baload(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_baload_null_pointer() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_object(None)?; + stack.push_int(0)?; + let result = baload(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_bastore() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::ByteArray(ConcurrentVec::from(vec![3])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + stack.push_int(42)?; + let result = bastore(stack)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_bastore_invalid_value() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + stack.push_int(42)?; + let result = bastore(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "byte array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_bastore_invalid_index() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::ByteArray(ConcurrentVec::from(vec![3])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + stack.push_int(42)?; + let result = bastore(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_bastore_null_pointer() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + stack.push_object(None)?; + stack.push_int(0)?; + stack.push_int(42)?; + let result = bastore(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/char.rs b/ristretto_vm/src/instruction/char.rs new file mode 100644 index 00000000..7e7bfee5 --- /dev/null +++ b/ristretto_vm/src/instruction/char.rs @@ -0,0 +1,158 @@ +use crate::frame::ExecutionResult; +use crate::frame::ExecutionResult::Continue; +use crate::operand_stack::OperandStack; +use crate::Error::{ArrayIndexOutOfBounds, InvalidStackValue, NullPointer}; +use crate::Result; +use ristretto_classloader::Reference; + +/// See: +#[inline] +pub(crate) fn caload(stack: &mut OperandStack) -> Result { + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::CharArray(array)) => { + let index = usize::try_from(index)?; + let Some(value) = array.get(index)? else { + return Err(ArrayIndexOutOfBounds(index)); + }; + stack.push_int(i32::from(value))?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "char array".to_string(), + actual: object.to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn castore(stack: &mut OperandStack) -> Result { + let value = stack.pop_int()?; + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::CharArray(ref mut array)) => { + let index = usize::try_from(index)?; + if index >= array.capacity()? { + return Err(ArrayIndexOutOfBounds(index)); + }; + array.set(index, u16::try_from(value)?)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "char array".to_string(), + actual: object.to_string(), + }), + } +} + +#[cfg(test)] +mod test { + use super::*; + use ristretto_classloader::ConcurrentVec; + + #[test] + fn test_caload() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::CharArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + let result = caload(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_caload_invalid_value() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + let result = caload(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "char array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_caload_invalid_index() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::CharArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + let result = caload(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_caload_null_pointer() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_object(None)?; + stack.push_int(0)?; + let result = caload(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_castore() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::CharArray(ConcurrentVec::from(vec![3])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + stack.push_int(42)?; + let result = castore(stack)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_castore_invalid_value() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + stack.push_int(42)?; + let result = castore(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "char array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_castore_invalid_index() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::CharArray(ConcurrentVec::from(vec![3])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + stack.push_int(42)?; + let result = castore(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_castore_null_pointer() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + stack.push_object(None)?; + stack.push_int(0)?; + stack.push_int(42)?; + let result = castore(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/convert.rs b/ristretto_vm/src/instruction/convert.rs new file mode 100644 index 00000000..218a6f7c --- /dev/null +++ b/ristretto_vm/src/instruction/convert.rs @@ -0,0 +1,300 @@ +use crate::frame::ExecutionResult; +use crate::frame::ExecutionResult::Continue; +use crate::operand_stack::OperandStack; + +/// See: +#[inline] +pub(crate) fn i2l(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_int()?; + stack.push_long(i64::from(value))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn i2f(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_int()?; + #[expect(clippy::cast_precision_loss)] + stack.push_float(value as f32)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn i2d(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_int()?; + stack.push_double(f64::from(value))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn l2i(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_long()?; + #[expect(clippy::cast_possible_truncation)] + stack.push_int(value as i32)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn l2f(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_long()?; + #[expect(clippy::cast_precision_loss)] + stack.push_float(value as f32)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn l2d(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_long()?; + #[expect(clippy::cast_precision_loss)] + stack.push_double(value as f64)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn f2i(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_float()?; + #[expect(clippy::cast_possible_truncation)] + stack.push_int(value as i32)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn f2l(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_float()?; + #[expect(clippy::cast_possible_truncation)] + stack.push_long(value as i64)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn f2d(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_float()?; + stack.push_double(f64::from(value))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn d2i(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_double()?; + #[expect(clippy::cast_possible_truncation)] + stack.push_int(value as i32)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn d2l(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_double()?; + #[expect(clippy::cast_possible_truncation)] + stack.push_long(value as i64)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn d2f(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_double()?; + #[expect(clippy::cast_possible_truncation)] + stack.push_float(value as f32)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn i2b(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_int()?; + #[expect(clippy::cast_possible_truncation)] + let byte = value as i8; + stack.push_int(i32::from(byte))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn i2c(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_int()?; + #[expect(clippy::cast_possible_truncation)] + #[expect(clippy::cast_sign_loss)] + let char = value as u16; + stack.push_int(i32::from(char))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn i2s(stack: &mut OperandStack) -> crate::Result { + let value = stack.pop_int()?; + #[expect(clippy::cast_possible_truncation)] + let short = value as i16; + stack.push_int(i32::from(short))?; + Ok(Continue) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_i2l() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = i2l(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_i2f() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = i2f(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 42f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_i2d() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = i2d(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 42f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_l2i() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_long(42)?; + let result = l2i(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_l2f() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_long(42)?; + let result = l2f(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 42f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_l2d() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_long(42)?; + let result = l2d(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 42f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_f2i() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_float(42.1)?; + let result = f2i(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_f2l() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_float(42.1)?; + let result = f2l(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_f2d() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_float(42.1)?; + let result = f2d(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_d2i() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_double(42.1)?; + let result = d2i(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_d2l() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_double(42.1)?; + let result = d2l(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_d2f() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_double(42.1)?; + let result = d2f(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_i2b() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = i2b(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_i2c() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = i2c(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_i2s() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = i2s(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/double.rs b/ristretto_vm/src/instruction/double.rs new file mode 100644 index 00000000..d316097f --- /dev/null +++ b/ristretto_vm/src/instruction/double.rs @@ -0,0 +1,800 @@ +use crate::frame::ExecutionResult::Return; +use crate::frame::{ExecutionResult, ExecutionResult::Continue}; +use crate::local_variables::LocalVariables; +use crate::operand_stack::OperandStack; +use crate::Error::{ArrayIndexOutOfBounds, InvalidStackValue, NullPointer}; +use crate::{Result, Value}; +use ristretto_classloader::Reference; + +/// See: +#[inline] +pub(crate) fn dconst_0(stack: &mut OperandStack) -> Result { + stack.push_double(0f64)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dconst_1(stack: &mut OperandStack) -> Result { + stack.push_double(1f64)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dload( + locals: &LocalVariables, + stack: &mut OperandStack, + index: u8, +) -> Result { + let value = locals.get_double(usize::from(index))?; + stack.push_double(value)?; + Ok(Continue) +} + +/// See: +/// See: +#[inline] +pub(crate) fn dload_w( + locals: &LocalVariables, + stack: &mut OperandStack, + index: u16, +) -> Result { + let value = locals.get_double(usize::from(index))?; + stack.push_double(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dload_0( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_double(0)?; + stack.push_double(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dload_1( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_double(1)?; + stack.push_double(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dload_2( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_double(2)?; + stack.push_double(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dload_3( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_double(3)?; + stack.push_double(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dstore( + locals: &mut LocalVariables, + stack: &mut OperandStack, + index: u8, +) -> Result { + let value = stack.pop_double()?; + locals.set_double(usize::from(index), value)?; + Ok(Continue) +} + +/// See: +/// See: +#[inline] +pub(crate) fn dstore_w( + locals: &mut LocalVariables, + stack: &mut OperandStack, + index: u16, +) -> Result { + let value = stack.pop_double()?; + locals.set_double(usize::from(index), value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dstore_0( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_double()?; + locals.set_double(0, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dstore_1( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_double()?; + locals.set_double(1, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dstore_2( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_double()?; + locals.set_double(2, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dstore_3( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_double()?; + locals.set_double(3, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn daload(stack: &mut OperandStack) -> Result { + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::DoubleArray(array)) => { + let index = usize::try_from(index)?; + let Some(value) = array.get(index)? else { + return Err(ArrayIndexOutOfBounds(index)); + }; + stack.push_double(value)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "double array".to_string(), + actual: object.to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn dastore(stack: &mut OperandStack) -> Result { + let value = stack.pop_double()?; + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::DoubleArray(ref mut array)) => { + let index = usize::try_from(index)?; + if index >= array.capacity()? { + return Err(ArrayIndexOutOfBounds(index)); + }; + array.set(index, value)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "double array".to_string(), + actual: object.to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn dadd(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_double()?; + let value1 = stack.pop_double()?; + stack.push_double(value1 + value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dsub(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_double()?; + let value1 = stack.pop_double()?; + stack.push_double(value1 - value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dmul(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_double()?; + let value1 = stack.pop_double()?; + stack.push_double(value1 * value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn ddiv(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_double()?; + let value1 = stack.pop_double()?; + stack.push_double(value1 / value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn drem(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_double()?; + let value1 = stack.pop_double()?; + stack.push_double(value1 % value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dneg(stack: &mut OperandStack) -> Result { + let value = stack.pop_double()?; + stack.push_double(-value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dcmpl(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_double()?; + let value1 = stack.pop_double()?; + let cmp = if f64::is_nan(value1) || f64::is_nan(value2) { + -1 + } else if value1 > value2 { + 1 + } else if value1 < value2 { + -1 + } else { + 0 + }; + stack.push_int(cmp)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dcmpg(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_double()?; + let value1 = stack.pop_double()?; + let cmp = if f64::is_nan(value1) || f64::is_nan(value2) || value1 > value2 { + 1 + } else if value1 < value2 { + -1 + } else { + 0 + }; + stack.push_int(cmp)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dreturn(stack: &mut OperandStack) -> Result { + let value = stack.pop_double()?; + Ok(Return(Some(Value::Double(value)))) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Error::InvalidOperand; + use ristretto_classloader::ConcurrentVec; + + #[test] + fn test_dconst_0() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = dconst_0(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 0f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dconst_1() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = dconst_1(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dload() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_double(0, 42.1)?; + let stack = &mut OperandStack::with_max_size(1); + let result = dload(&locals, stack, 0)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dload_w() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_double(0, 42.1)?; + let stack = &mut OperandStack::with_max_size(1); + let result = dload_w(&locals, stack, 0)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dload_0() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_double(0, 42.1)?; + let stack = &mut OperandStack::with_max_size(1); + let result = dload_0(&locals, stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dload_1() -> Result<()> { + let mut locals = LocalVariables::with_max_size(2); + locals.set_double(1, 42.1)?; + let stack = &mut OperandStack::with_max_size(1); + let result = dload_1(&locals, stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dload_2() -> Result<()> { + let mut locals = LocalVariables::with_max_size(3); + locals.set_double(2, 42.1)?; + let stack = &mut OperandStack::with_max_size(1); + let result = dload_2(&locals, stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dload_3() -> Result<()> { + let mut locals = LocalVariables::with_max_size(4); + locals.set_double(3, 42.1)?; + let stack = &mut OperandStack::with_max_size(1); + let result = dload_3(&locals, stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dstore() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_double(42.1)?; + let result = dstore(locals, stack, 0)?; + assert_eq!(Continue, result); + let value = locals.get_double(0)? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dstore_w() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_double(42.1)?; + let result = dstore_w(locals, stack, 0)?; + assert_eq!(Continue, result); + let value = locals.get_double(0)? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dstore_0() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_double(42.1)?; + let result = dstore_0(locals, stack)?; + assert_eq!(Continue, result); + let value = locals.get_double(0)? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dstore_1() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(2); + let stack = &mut OperandStack::with_max_size(1); + stack.push_double(42.1)?; + let result = dstore_1(locals, stack)?; + assert_eq!(Continue, result); + let value = locals.get_double(1)? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dstore_2() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(3); + let stack = &mut OperandStack::with_max_size(1); + stack.push_double(42.1)?; + let result = dstore_2(locals, stack)?; + assert_eq!(Continue, result); + let value = locals.get_double(2)? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dstore_3() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(4); + let stack = &mut OperandStack::with_max_size(1); + stack.push_double(42.1)?; + let result = dstore_3(locals, stack)?; + assert_eq!(Continue, result); + let value = locals.get_double(3)? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_daload() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::DoubleArray(ConcurrentVec::from(vec![42f64])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + let result = daload(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 42f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_daload_invalid_value() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + let result = daload(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "double array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_daload_invalid_index() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::DoubleArray(ConcurrentVec::from(vec![42f64])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + let result = daload(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_daload_null_pointer() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_object(None)?; + stack.push_int(0)?; + let result = daload(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_dastore() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::DoubleArray(ConcurrentVec::from(vec![3f64])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + stack.push_double(42f64)?; + let result = dastore(stack)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_dastore_invalid_value() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + stack.push_double(42f64)?; + let result = dastore(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "double array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_dastore_invalid_index() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::DoubleArray(ConcurrentVec::from(vec![3f64])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + stack.push_double(42f64)?; + let result = dastore(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_dastore_null_pointer() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + stack.push_object(None)?; + stack.push_int(0)?; + stack.push_double(42f64)?; + let result = dastore(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_dadd() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(1f64)?; + stack.push_double(2f64)?; + let result = dadd(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 3f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dadd_overflow() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(f64::MAX)?; + stack.push_double(1f64)?; + let result = dadd(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - f64::MAX; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dsub() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(2f64)?; + stack.push_double(1f64)?; + let result = dsub(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dsub_overflow() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(f64::MIN)?; + stack.push_double(1f64)?; + let result = dsub(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - f64::MIN; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dmul() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(2f64)?; + stack.push_double(3f64)?; + let result = dmul(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 6f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_ddiv() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(6f64)?; + stack.push_double(3f64)?; + let result = ddiv(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 2f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_drem() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(1f64)?; + stack.push_double(2f64)?; + let result = drem(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? - 1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dneg() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(1f64)?; + let result = dneg(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_double()? + 1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_dcmpl_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(1.0)?; + stack.push_double(1.0)?; + let result = dcmpl(stack)?; + assert_eq!(Continue, result); + assert_eq!(0, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dcmpl_value1_nan() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(1.0)?; + stack.push_double(f64::NAN)?; + let result = dcmpl(stack)?; + assert_eq!(Continue, result); + assert_eq!(-1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dcmpl_value2_nan() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(f64::NAN)?; + stack.push_double(1.0)?; + let result = dcmpl(stack)?; + assert_eq!(Continue, result); + assert_eq!(-1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dcmpl_greater_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(2.0)?; + stack.push_double(1.0)?; + let result = dcmpl(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dcmpl_less_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(1.0)?; + stack.push_double(2.0)?; + let result = dcmpl(stack)?; + assert_eq!(Continue, result); + assert_eq!(-1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dcmpg_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(1.0)?; + stack.push_double(1.0)?; + let result = dcmpg(stack)?; + assert_eq!(Continue, result); + assert_eq!(0, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dcmpg_value1_nan() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(1.0)?; + stack.push_double(f64::NAN)?; + let result = dcmpg(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dcmpg_value2_nan() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(f64::NAN)?; + stack.push_double(1.0)?; + let result = dcmpg(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dcmpg_greater_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(2.0)?; + stack.push_double(1.0)?; + let result = dcmpg(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dcmpg_less_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_double(1.0)?; + stack.push_double(2.0)?; + let result = dcmpg(stack)?; + assert_eq!(Continue, result); + assert_eq!(-1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dreturn() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_double(42.1)?; + let result = dreturn(stack)?; + assert!(matches!(result, Return(Some(Value::Double(42.1))))); + Ok(()) + } + + #[test] + fn test_dreturn_invalid_type() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = dreturn(stack); + assert!(matches!( + result, + Err(InvalidOperand { + expected, + actual + }) if expected == "double" && actual == "object(null)" + )); + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/field.rs b/ristretto_vm/src/instruction/field.rs new file mode 100644 index 00000000..19d721bb --- /dev/null +++ b/ristretto_vm/src/instruction/field.rs @@ -0,0 +1,197 @@ +use crate::frame::ExecutionResult; +use crate::frame::ExecutionResult::Continue; +use crate::operand_stack::OperandStack; +use crate::Error::InvalidStackValue; +use crate::Result; +use ristretto_classfile::ConstantPool; +use ristretto_classloader::Reference; + +/// See: +#[inline] +pub(crate) fn getfield( + stack: &mut OperandStack, + constant_pool: &ConstantPool, + index: u16, +) -> Result { + match stack.pop_object()? { + Some(Reference::Object(object)) => { + let (_class_index, name_and_type_index) = constant_pool.try_get_field_ref(index)?; + let (name_index, _descriptor_index) = + constant_pool.try_get_name_and_type(*name_and_type_index)?; + let field_name = constant_pool.try_get_utf8(*name_index)?; + let field = object.field(field_name)?; + let value = field.value()?; + stack.push(value)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "object".to_string(), + actual: object.to_string(), + }), + None => Err(InvalidStackValue { + expected: "object".to_string(), + actual: "null".to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn putfield( + stack: &mut OperandStack, + constant_pool: &ConstantPool, + index: u16, +) -> Result { + let value = stack.pop()?; + let mut object = stack.pop_object()?; + match object { + Some(Reference::Object(ref mut object)) => { + let (_class_index, name_and_type_index) = constant_pool.try_get_field_ref(index)?; + let (name_index, _descriptor_index) = + constant_pool.try_get_name_and_type(*name_and_type_index)?; + let field_name = constant_pool.try_get_utf8(*name_index)?; + let field = object.field(field_name)?; + field.set_value(value)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "object".to_string(), + actual: object.to_string(), + }), + None => Err(InvalidStackValue { + expected: "object".to_string(), + actual: "null".to_string(), + }), + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::call_stack::CallStack; + use crate::frame::Frame; + use crate::instruction::{dup, new}; + use crate::VM; + use ristretto_classfile::MethodAccessFlags; + use ristretto_classloader::{Method, Value}; + use std::sync::Arc; + + fn test_class_field( + class_name: &str, + field_name: &str, + field_type: &str, + ) -> Result<(VM, CallStack, Frame, u16, u16)> { + let (vm, _call_stack, mut class) = crate::test::class()?; + let constant_pool = Arc::get_mut(&mut class).expect("class").constant_pool_mut(); + let class_index = constant_pool.add_class(class_name)?; + let field_index = constant_pool.add_field_ref(class_index, field_name, field_type)?; + let method = Method::new( + MethodAccessFlags::STATIC, + "test", + "()V", + 10, + 10, + Vec::new(), + Vec::new(), + )?; + let arguments = Vec::new(); + let call_stack = CallStack::new(); + let frame = Frame::new(&class, &Arc::new(method), arguments)?; + Ok((vm, call_stack, frame, class_index, field_index)) + } + + fn test_put_and_get_field() -> Result<()> { + let (vm, mut call_stack, mut frame, class_index, field_index) = + test_class_field("Child", "zero", "I")?; + let stack = &mut frame.stack; + let constant_pool = frame.class.constant_pool(); + let result = new(&vm, &mut call_stack, stack, constant_pool, class_index)?; + assert_eq!(Continue, result); + + let result = dup(stack)?; + assert_eq!(Continue, result); + + let result = dup(stack)?; + assert_eq!(Continue, result); + + stack.push_int(42)?; + let result = putfield(stack, constant_pool, field_index)?; + assert_eq!(Continue, result); + + let result = getfield(stack, constant_pool, field_index)?; + assert_eq!(Continue, result); + let value = frame.stack.pop()?; + assert_eq!(Value::Int(42), value); + Ok(()) + } + + #[test] + fn test_getfield() -> Result<()> { + test_put_and_get_field() + } + + #[test] + fn test_getfield_field_not_found() -> Result<()> { + let (vm, mut call_stack, mut frame, class_index, field_index) = + test_class_field("Child", "foo", "I")?; + let stack = &mut frame.stack; + let constant_pool = frame.class.constant_pool(); + let result = new(&vm, &mut call_stack, stack, constant_pool, class_index)?; + assert_eq!(Continue, result); + let result = getfield(stack, constant_pool, field_index); + assert!(result.is_err()); + Ok(()) + } + + #[test] + fn test_getfield_invalid_value() -> Result<()> { + let (_vm, _call_stack, frame) = crate::test::frame()?; + let stack = &mut OperandStack::with_max_size(2); + let constant_pool = frame.class.constant_pool(); + stack.push_object(None)?; + let result = getfield(stack, constant_pool, 0); + assert!(matches!(result, Err(InvalidStackValue { + expected, + actual + }) if expected == "object" && actual == "null")); + + Ok(()) + } + + #[test] + fn test_putfield() -> Result<()> { + test_put_and_get_field() + } + + #[test] + fn test_putfield_field_not_found() -> Result<()> { + let (vm, mut call_stack, mut frame, class_index, field_index) = + test_class_field("Child", "foo", "I")?; + let stack = &mut OperandStack::with_max_size(2); + let constant_pool = frame.class.constant_pool(); + let result = new(&vm, &mut call_stack, stack, constant_pool, class_index)?; + assert_eq!(Continue, result); + let result = dup(stack)?; + assert_eq!(Continue, result); + frame.stack.push_int(42)?; + let result = putfield(stack, constant_pool, field_index); + assert!(result.is_err()); + Ok(()) + } + + #[test] + fn test_putfield_invalid_value() -> Result<()> { + let (_vm, _call_stack, frame) = crate::test::frame()?; + let stack = &mut OperandStack::with_max_size(2); + let constant_pool = frame.class.constant_pool(); + stack.push_object(None)?; + stack.push_int(42)?; + let result = putfield(stack, constant_pool, 0); + assert!(matches!(result, Err(InvalidStackValue { + expected, + actual + }) if expected == "object" && actual == "null")); + + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/float.rs b/ristretto_vm/src/instruction/float.rs new file mode 100644 index 00000000..09094782 --- /dev/null +++ b/ristretto_vm/src/instruction/float.rs @@ -0,0 +1,817 @@ +use crate::frame::ExecutionResult::Return; +use crate::frame::{ExecutionResult, ExecutionResult::Continue}; +use crate::local_variables::LocalVariables; +use crate::operand_stack::OperandStack; +use crate::Error::{ArrayIndexOutOfBounds, InvalidStackValue, NullPointer}; +use crate::{Result, Value}; +use ristretto_classloader::Reference; + +/// See: +#[inline] +pub(crate) fn fconst_0(stack: &mut OperandStack) -> Result { + stack.push_float(0f32)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fconst_1(stack: &mut OperandStack) -> Result { + stack.push_float(1f32)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fconst_2(stack: &mut OperandStack) -> Result { + stack.push_float(2f32)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fload( + locals: &LocalVariables, + stack: &mut OperandStack, + index: u8, +) -> Result { + let value = locals.get_float(usize::from(index))?; + stack.push_float(value)?; + Ok(Continue) +} + +/// See: +/// See: +#[inline] +pub(crate) fn fload_w( + locals: &LocalVariables, + stack: &mut OperandStack, + index: u16, +) -> Result { + let value = locals.get_float(usize::from(index))?; + stack.push_float(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fload_0( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_float(0)?; + stack.push_float(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fload_1( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_float(1)?; + stack.push_float(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fload_2( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_float(2)?; + stack.push_float(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fload_3( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_float(3)?; + stack.push_float(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fstore( + locals: &mut LocalVariables, + stack: &mut OperandStack, + index: u8, +) -> Result { + let value = stack.pop_float()?; + locals.set_float(usize::from(index), value)?; + Ok(Continue) +} + +/// See: +/// See: +#[inline] +pub(crate) fn fstore_w( + locals: &mut LocalVariables, + stack: &mut OperandStack, + index: u16, +) -> Result { + let value = stack.pop_float()?; + locals.set_float(usize::from(index), value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fstore_0( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_float()?; + locals.set_float(0, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fstore_1( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_float()?; + locals.set_float(1, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fstore_2( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_float()?; + locals.set_float(2, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fstore_3( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_float()?; + locals.set_float(3, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn faload(stack: &mut OperandStack) -> Result { + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::FloatArray(array)) => { + let index = usize::try_from(index)?; + let Some(value) = array.get(index)? else { + return Err(ArrayIndexOutOfBounds(index)); + }; + stack.push_float(value)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "float array".to_string(), + actual: object.to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn fastore(stack: &mut OperandStack) -> Result { + let value = stack.pop_float()?; + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::FloatArray(ref mut array)) => { + let index = usize::try_from(index)?; + if index >= array.capacity()? { + return Err(ArrayIndexOutOfBounds(index)); + }; + array.set(index, value)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "float array".to_string(), + actual: object.to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn fadd(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_float()?; + let value1 = stack.pop_float()?; + stack.push_float(value1 + value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fsub(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_float()?; + let value1 = stack.pop_float()?; + stack.push_float(value1 - value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fmul(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_float()?; + let value1 = stack.pop_float()?; + stack.push_float(value1 * value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fdiv(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_float()?; + let value1 = stack.pop_float()?; + stack.push_float(value1 / value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn frem(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_float()?; + let value1 = stack.pop_float()?; + stack.push_float(value1 % value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fneg(stack: &mut OperandStack) -> Result { + let value = stack.pop_float()?; + stack.push_float(-value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fcmpl(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_float()?; + let value1 = stack.pop_float()?; + let cmp = if f32::is_nan(value1) || f32::is_nan(value2) { + -1 + } else if value1 > value2 { + 1 + } else if value1 < value2 { + -1 + } else { + 0 + }; + stack.push_int(cmp)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn fcmpg(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_float()?; + let value1 = stack.pop_float()?; + let cmp = if f32::is_nan(value1) || f32::is_nan(value2) || value1 > value2 { + 1 + } else if value1 < value2 { + -1 + } else { + 0 + }; + stack.push_int(cmp)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn freturn(stack: &mut OperandStack) -> Result { + let value = stack.pop_float()?; + Ok(Return(Some(Value::Float(value)))) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Error::InvalidOperand; + use ristretto_classloader::ConcurrentVec; + + #[test] + fn test_fconst_0() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = fconst_0(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 0f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fconst_1() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = fconst_1(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fconst_2() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = fconst_2(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 2f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fload() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_float(0, 42.1)?; + let stack = &mut OperandStack::with_max_size(1); + let result = fload(&locals, stack, 0)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fload_w() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_float(0, 42.1)?; + let stack = &mut OperandStack::with_max_size(1); + let result = fload_w(&locals, stack, 0)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fload_0() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_float(0, 42.1)?; + let stack = &mut OperandStack::with_max_size(1); + let result = fload_0(&locals, stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fload_1() -> Result<()> { + let mut locals = LocalVariables::with_max_size(2); + locals.set_float(1, 42.1)?; + let stack = &mut OperandStack::with_max_size(1); + let result = fload_1(&locals, stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fload_2() -> Result<()> { + let mut locals = LocalVariables::with_max_size(3); + locals.set_float(2, 42.1)?; + let stack = &mut OperandStack::with_max_size(1); + let result = fload_2(&locals, stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fload_3() -> Result<()> { + let mut locals = LocalVariables::with_max_size(4); + locals.set_float(3, 42.1)?; + let stack = &mut OperandStack::with_max_size(1); + let result = fload_3(&locals, stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fstore() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_float(42.1)?; + let result = fstore(locals, stack, 0)?; + assert_eq!(Continue, result); + let value = locals.get_float(0)? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fstore_w() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_float(42.1)?; + let result = fstore_w(locals, stack, 0)?; + assert_eq!(Continue, result); + let value = locals.get_float(0)? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fstore_0() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_float(42.1)?; + let result = fstore_0(locals, stack)?; + assert_eq!(Continue, result); + let value = locals.get_float(0)? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fstore_1() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(2); + let stack = &mut OperandStack::with_max_size(1); + stack.push_float(42.1)?; + let result = fstore_1(locals, stack)?; + assert_eq!(Continue, result); + let value = locals.get_float(1)? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fstore_2() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(3); + let stack = &mut OperandStack::with_max_size(1); + stack.push_float(42.1)?; + let result = fstore_2(locals, stack)?; + assert_eq!(Continue, result); + let value = locals.get_float(2)? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fstore_3() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(4); + let stack = &mut OperandStack::with_max_size(1); + stack.push_float(42.1)?; + let result = fstore_3(locals, stack)?; + assert_eq!(Continue, result); + let value = locals.get_float(3)? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_faload() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::FloatArray(ConcurrentVec::from(vec![42f32])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + let result = faload(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 42f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_faload_invalid_value() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + let result = faload(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "float array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_faload_invalid_index() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::FloatArray(ConcurrentVec::from(vec![42f32])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + let result = faload(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_faload_null_pointer() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_object(None)?; + stack.push_int(0)?; + let result = faload(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_fastore() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::FloatArray(ConcurrentVec::from(vec![3f32])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + stack.push_float(42f32)?; + let result = fastore(stack)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_fastore_invalid_value() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + stack.push_float(42f32)?; + let result = fastore(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "float array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_fastore_invalid_index() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::FloatArray(ConcurrentVec::from(vec![3f32])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + stack.push_float(42f32)?; + let result = fastore(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_fastore_null_pointer() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + stack.push_object(None)?; + stack.push_int(0)?; + stack.push_float(42f32)?; + let result = fastore(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_fadd() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(1f32)?; + stack.push_float(2f32)?; + let result = fadd(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 3f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fadd_overflow() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(f32::MAX)?; + stack.push_float(1f32)?; + let result = fadd(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - f32::MAX; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fsub() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(2f32)?; + stack.push_float(1f32)?; + let result = fsub(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fsub_overflow() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(f32::MIN)?; + stack.push_float(1f32)?; + let result = fsub(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - f32::MIN; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fmul() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(2f32)?; + stack.push_float(3f32)?; + let result = fmul(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 6f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fdiv() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(6f32)?; + stack.push_float(3f32)?; + let result = fdiv(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 2f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_frem() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(1f32)?; + stack.push_float(2f32)?; + let result = frem(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? - 1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fneg() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_float(1f32)?; + let result = fneg(stack)?; + assert_eq!(Continue, result); + let value = stack.pop_float()? + 1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_fcmpl_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(1.0)?; + stack.push_float(1.0)?; + let result = fcmpl(stack)?; + assert_eq!(Continue, result); + assert_eq!(0, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_fcmpl_value1_nan() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(1.0)?; + stack.push_float(f32::NAN)?; + let result = fcmpl(stack)?; + assert_eq!(Continue, result); + assert_eq!(-1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_fcmpl_value2_nan() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(f32::NAN)?; + stack.push_float(1.0)?; + let result = fcmpl(stack)?; + assert_eq!(Continue, result); + assert_eq!(-1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_fcmpl_greater_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(2.0)?; + stack.push_float(1.0)?; + let result = fcmpl(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_fcmpl_less_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(1.0)?; + stack.push_float(2.0)?; + let result = fcmpl(stack)?; + assert_eq!(Continue, result); + assert_eq!(-1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_fcmpg_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(1.0)?; + stack.push_float(1.0)?; + let result = fcmpg(stack)?; + assert_eq!(Continue, result); + assert_eq!(0, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_fcmpg_value1_nan() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(1.0)?; + stack.push_float(f32::NAN)?; + let result = fcmpg(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_fcmpg_value2_nan() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(f32::NAN)?; + stack.push_float(1.0)?; + let result = fcmpg(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_fcmpg_greater_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(2.0)?; + stack.push_float(1.0)?; + let result = fcmpg(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_fcmpg_less_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_float(1.0)?; + stack.push_float(2.0)?; + let result = fcmpg(stack)?; + assert_eq!(Continue, result); + assert_eq!(-1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_freturn() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_float(42.1)?; + let result = freturn(stack)?; + assert!(matches!(result, Return(Some(Value::Float(42.1))))); + Ok(()) + } + + #[test] + fn test_freturn_invalid_type() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = freturn(stack); + assert!(matches!( + result, + Err(InvalidOperand { + expected, + actual + }) if expected == "float" && actual == "object(null)" + )); + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/integer.rs b/ristretto_vm/src/instruction/integer.rs new file mode 100644 index 00000000..5adc3c49 --- /dev/null +++ b/ristretto_vm/src/instruction/integer.rs @@ -0,0 +1,901 @@ +use crate::frame::ExecutionResult::Return; +use crate::frame::{ExecutionResult, ExecutionResult::Continue}; +use crate::local_variables::LocalVariables; +use crate::operand_stack::OperandStack; +use crate::Error::{ArrayIndexOutOfBounds, InvalidStackValue, NullPointer}; +use crate::{Result, Value}; +use ristretto_classloader::Reference; + +/// See: +#[inline] +pub(crate) fn iconst_m1(stack: &mut OperandStack) -> Result { + stack.push_int(-1)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iconst_0(stack: &mut OperandStack) -> Result { + stack.push_int(0)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iconst_1(stack: &mut OperandStack) -> Result { + stack.push_int(1)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iconst_2(stack: &mut OperandStack) -> Result { + stack.push_int(2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iconst_3(stack: &mut OperandStack) -> Result { + stack.push_int(3)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iconst_4(stack: &mut OperandStack) -> Result { + stack.push_int(4)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iconst_5(stack: &mut OperandStack) -> Result { + stack.push_int(5)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iload( + locals: &LocalVariables, + stack: &mut OperandStack, + index: u8, +) -> Result { + let value = locals.get_int(usize::from(index))?; + stack.push_int(value)?; + Ok(Continue) +} + +/// See: +/// See: +#[inline] +pub(crate) fn iload_w( + locals: &LocalVariables, + stack: &mut OperandStack, + index: u16, +) -> Result { + let value = locals.get_int(usize::from(index))?; + stack.push_int(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iload_0( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_int(0)?; + stack.push_int(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iload_1( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_int(1)?; + stack.push_int(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iload_2( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_int(2)?; + stack.push_int(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iload_3( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_int(3)?; + stack.push_int(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn istore( + locals: &mut LocalVariables, + stack: &mut OperandStack, + index: u8, +) -> Result { + let value = stack.pop_int()?; + locals.set_int(usize::from(index), value)?; + Ok(Continue) +} + +/// See: +/// See: +#[inline] +pub(crate) fn istore_w( + locals: &mut LocalVariables, + stack: &mut OperandStack, + index: u16, +) -> Result { + let value = stack.pop_int()?; + locals.set_int(usize::from(index), value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn istore_0( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_int()?; + locals.set_int(0, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn istore_1( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_int()?; + locals.set_int(1, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn istore_2( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_int()?; + locals.set_int(2, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn istore_3( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_int()?; + locals.set_int(3, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iaload(stack: &mut OperandStack) -> Result { + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::IntArray(array)) => { + let index = usize::try_from(index)?; + let Some(value) = array.get(index)? else { + return Err(ArrayIndexOutOfBounds(index)); + }; + stack.push_int(value)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "int array".to_string(), + actual: object.to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn iastore(stack: &mut OperandStack) -> Result { + let value = stack.pop_int()?; + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::IntArray(ref mut array)) => { + let index = usize::try_from(index)?; + if index >= array.capacity()? { + return Err(ArrayIndexOutOfBounds(index)); + }; + array.set(index, value)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "int array".to_string(), + actual: object.to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn iadd(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + stack.push_int(i32::wrapping_add(value1, value2))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn isub(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + stack.push_int(i32::wrapping_sub(value1, value2))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn imul(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + stack.push_int(i32::wrapping_mul(value1, value2))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn idiv(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + stack.push_int(i32::wrapping_div(value1, value2))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn irem(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + stack.push_int(i32::wrapping_rem(value1, value2))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn ineg(stack: &mut OperandStack) -> Result { + let value = stack.pop_int()?; + stack.push_int(-value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn ishl(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + stack.push_int(value1 << (value2 & 0x1f))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn ishr(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + stack.push_int(value1 >> (value2 & 0x1f))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iushr(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + let result = if value1 > 0 { + value1 >> (value2 & 0x1f) + } else { + #[expect(clippy::cast_sign_loss)] + let value1 = value1 as u32; + let result = value1 >> (value2 & 0x1f); + #[expect(clippy::cast_possible_wrap)] + let result = result as i32; + result + }; + stack.push_int(result)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iand(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + stack.push_int(value1 & value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn ior(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + stack.push_int(value1 | value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn ixor(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_int()?; + stack.push_int(value1 ^ value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn iinc( + locals: &mut LocalVariables, + index: u8, + constant: i8, +) -> Result { + let index = usize::from(index); + let local = locals.get_int(index)?; + locals.set_int(index, local + i32::from(constant))?; + Ok(Continue) +} + +/// See: +/// See: +#[inline] +pub(crate) fn iinc_w( + locals: &mut LocalVariables, + index: u16, + constant: i16, +) -> Result { + let index = usize::from(index); + let local = locals.get_int(index)?; + locals.set_int(index, local + i32::from(constant))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn ireturn(stack: &mut OperandStack) -> Result { + let value = stack.pop_int()?; + Ok(Return(Some(Value::Int(value)))) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Error::InvalidOperand; + use ristretto_classloader::ConcurrentVec; + + #[test] + fn test_iconst_m1() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = iconst_m1(stack)?; + assert_eq!(Continue, result); + assert_eq!(-1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iconst_0() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = iconst_0(stack)?; + assert_eq!(Continue, result); + assert_eq!(0, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iconst_1() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = iconst_1(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iconst_2() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = iconst_2(stack)?; + assert_eq!(Continue, result); + assert_eq!(2, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iconst_3() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = iconst_3(stack)?; + assert_eq!(Continue, result); + assert_eq!(3, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iconst_4() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = iconst_4(stack)?; + assert_eq!(Continue, result); + assert_eq!(4, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iconst_5() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = iconst_5(stack)?; + assert_eq!(Continue, result); + assert_eq!(5, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iload() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_int(0, 42)?; + let stack = &mut OperandStack::with_max_size(1); + let result = iload(&locals, stack, 0)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iload_w() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_int(0, 42)?; + let stack = &mut OperandStack::with_max_size(1); + let result = iload_w(&locals, stack, 0)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iload_0() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_int(0, 42)?; + let stack = &mut OperandStack::with_max_size(1); + let result = iload_0(&locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iload_1() -> Result<()> { + let mut locals = LocalVariables::with_max_size(2); + locals.set_int(1, 42)?; + let stack = &mut OperandStack::with_max_size(1); + let result = iload_1(&locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iload_2() -> Result<()> { + let mut locals = LocalVariables::with_max_size(3); + locals.set_int(2, 42)?; + let stack = &mut OperandStack::with_max_size(1); + let result = iload_2(&locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iload_3() -> Result<()> { + let mut locals = LocalVariables::with_max_size(4); + locals.set_int(3, 42)?; + let stack = &mut OperandStack::with_max_size(1); + let result = iload_3(&locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_istore() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = istore(locals, stack, 0)?; + assert_eq!(Continue, result); + assert_eq!(42, locals.get_int(0)?); + Ok(()) + } + + #[test] + fn test_istore_w() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = istore_w(locals, stack, 0)?; + assert_eq!(Continue, result); + assert_eq!(42, locals.get_int(0)?); + Ok(()) + } + + #[test] + fn test_istore_0() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = istore_0(locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, locals.get_int(0)?); + Ok(()) + } + + #[test] + fn test_istore_1() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(2); + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = istore_1(locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, locals.get_int(1)?); + Ok(()) + } + + #[test] + fn test_istore_2() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(3); + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = istore_2(locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, locals.get_int(2)?); + Ok(()) + } + + #[test] + fn test_istore_3() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(4); + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = istore_3(locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, locals.get_int(3)?); + Ok(()) + } + + #[test] + fn test_iaload() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + let result = iaload(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iaload_invalid_value() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let object = Reference::ByteArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + let result = iaload(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "int array" && actual == "byte[42]" + )); + Ok(()) + } + + #[test] + fn test_iaload_invalid_index() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + let result = iaload(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_iaload_null_pointer() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_object(None)?; + stack.push_int(0)?; + let result = iaload(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_iastore() -> Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::IntArray(ConcurrentVec::from(vec![3])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + stack.push_int(42)?; + let result = iastore(stack)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_iastore_invalid_value() -> Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let object = Reference::ByteArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + stack.push_int(42)?; + let result = iastore(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "int array" && actual == "byte[42]" + )); + Ok(()) + } + + #[test] + fn test_iastore_invalid_index() -> Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::IntArray(ConcurrentVec::from(vec![3])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + stack.push_int(42)?; + let result = iastore(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_iastore_null_pointer() -> Result<()> { + let stack = &mut OperandStack::with_max_size(3); + stack.push_object(None)?; + stack.push_int(0)?; + stack.push_int(42)?; + let result = iastore(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_iadd() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(1)?; + stack.push_int(2)?; + let result = iadd(stack)?; + assert_eq!(Continue, result); + assert_eq!(3, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iadd_overflow() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(i32::MAX)?; + stack.push_int(1)?; + let result = iadd(stack)?; + assert_eq!(Continue, result); + assert_eq!(i32::MIN, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_isub() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(2)?; + stack.push_int(1)?; + let result = isub(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_isub_overflow() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(i32::MIN)?; + stack.push_int(1)?; + let result = isub(stack)?; + assert_eq!(Continue, result); + assert_eq!(i32::MAX, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_imul() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(2)?; + stack.push_int(3)?; + let result = imul(stack)?; + assert_eq!(Continue, result); + assert_eq!(6, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_idiv() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(6)?; + stack.push_int(3)?; + let result = idiv(stack)?; + assert_eq!(Continue, result); + assert_eq!(2, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_irem() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(1)?; + stack.push_int(2)?; + let result = irem(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_ineg() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(1)?; + let result = ineg(stack)?; + assert_eq!(Continue, result); + assert_eq!(-1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_ishl() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(4)?; + stack.push_int(1)?; + let result = ishl(stack)?; + assert_eq!(Continue, result); + assert_eq!(8, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_ishr() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(4)?; + stack.push_int(1)?; + let result = ishr(stack)?; + assert_eq!(Continue, result); + assert_eq!(2, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iushr() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(4)?; + stack.push_int(1)?; + let result = iushr(stack)?; + assert_eq!(Continue, result); + assert_eq!(2, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iushr_negative_value1() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(-1)?; + stack.push_int(28)?; + let result = iushr(stack)?; + assert_eq!(Continue, result); + assert_eq!(15, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iand() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(2)?; + stack.push_int(3)?; + let result = iand(stack)?; + assert_eq!(Continue, result); + assert_eq!(2, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_ior() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(2)?; + stack.push_int(4)?; + let result = ior(stack)?; + assert_eq!(Continue, result); + assert_eq!(6, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_ixor() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(2)?; + stack.push_int(3)?; + let result = ixor(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_iinc() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + locals.set_int(0, 1)?; + let result = iinc(locals, 0, 2)?; + assert_eq!(Continue, result); + assert_eq!(3, locals.get_int(0)?); + Ok(()) + } + + #[test] + fn test_iinc_w() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + locals.set_int(0, 1)?; + let result = iinc_w(locals, 0, 2)?; + assert_eq!(Continue, result); + assert_eq!(3, locals.get_int(0)?); + Ok(()) + } + + #[test] + fn test_ireturn() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = ireturn(stack)?; + assert!(matches!(result, Return(Some(Value::Int(42))))); + Ok(()) + } + + #[test] + fn test_ireturn_invalid_type() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = ireturn(stack); + assert!(matches!( + result, + Err(InvalidOperand { + expected, + actual + }) if expected == "int" && actual == "object(null)" + )); + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/invoke.rs b/ristretto_vm/src/instruction/invoke.rs new file mode 100644 index 00000000..334f58ba --- /dev/null +++ b/ristretto_vm/src/instruction/invoke.rs @@ -0,0 +1,244 @@ +use crate::call_stack::CallStack; +use crate::frame::ExecutionResult; +use crate::frame::ExecutionResult::Continue; +use crate::operand_stack::OperandStack; +use crate::Error::RuntimeError; +use crate::{Result, VM}; +use ristretto_classfile::ConstantPool; +use ristretto_classloader::{Class, Method, Reference, Value}; +use std::sync::Arc; + +#[derive(Debug)] +enum InvocationType { + Interface, + Special, + Static, + Virtual, +} + +/// See: +#[inline] +pub(crate) fn invokevirtual( + vm: &VM, + call_stack: &mut CallStack, + stack: &mut OperandStack, + constant_pool: &ConstantPool, + method_index: u16, +) -> Result { + let (class_index, name_and_type_index) = constant_pool.try_get_method_ref(method_index)?; + let class_name = constant_pool.try_get_class(*class_index)?; + let class = vm.class(call_stack, class_name)?; + let (name_index, descriptor_index) = + constant_pool.try_get_name_and_type(*name_and_type_index)?; + let method_name = constant_pool.try_get_utf8(*name_index)?; + let method_descriptor = constant_pool.try_get_utf8(*descriptor_index)?; + let method = class.try_get_virtual_method(method_name, method_descriptor)?; + + invoke_method( + vm, + call_stack, + stack, + class, + method, + &InvocationType::Virtual, + ) +} + +/// See: +#[inline] +pub(crate) fn invokespecial( + vm: &VM, + call_stack: &mut CallStack, + stack: &mut OperandStack, + constant_pool: &ConstantPool, + method_index: u16, +) -> Result { + let (class_index, name_and_type_index) = constant_pool.try_get_method_ref(method_index)?; + let class_name = constant_pool.try_get_class(*class_index)?; + let class = vm.class(call_stack, class_name)?; + let (name_index, descriptor_index) = + constant_pool.try_get_name_and_type(*name_and_type_index)?; + let method_name = constant_pool.try_get_utf8(*name_index)?; + let method_descriptor = constant_pool.try_get_utf8(*descriptor_index)?; + let method = class.try_get_method(method_name, method_descriptor)?; + + invoke_method( + vm, + call_stack, + stack, + class, + method, + &InvocationType::Special, + ) +} + +/// See: +#[inline] +pub(crate) fn invokestatic( + vm: &VM, + call_stack: &mut CallStack, + stack: &mut OperandStack, + constant_pool: &ConstantPool, + method_index: u16, +) -> Result { + let (class_index, name_and_type_index) = constant_pool.try_get_method_ref(method_index)?; + let class_name = constant_pool.try_get_class(*class_index)?; + let class = vm.class(call_stack, class_name)?; + let (name_index, descriptor_index) = + constant_pool.try_get_name_and_type(*name_and_type_index)?; + let method_name = constant_pool.try_get_utf8(*name_index)?; + let method_descriptor = constant_pool.try_get_utf8(*descriptor_index)?; + let method = class.try_get_method(method_name, method_descriptor)?; + + invoke_method( + vm, + call_stack, + stack, + class, + method, + &InvocationType::Static, + ) +} + +/// See: +#[inline] +pub(crate) fn invokeinterface( + vm: &VM, + call_stack: &mut CallStack, + stack: &mut OperandStack, + constant_pool: &ConstantPool, + method_index: u16, +) -> Result { + let (class_index, name_and_type_index) = + constant_pool.try_get_interface_method_ref(method_index)?; + let class_name = constant_pool.try_get_class(*class_index)?; + let class = vm.class(call_stack, class_name)?; + let (name_index, descriptor_index) = + constant_pool.try_get_name_and_type(*name_and_type_index)?; + let method_name = constant_pool.try_get_utf8(*name_index)?; + let method_descriptor = constant_pool.try_get_utf8(*descriptor_index)?; + let method = class.try_get_virtual_method(method_name, method_descriptor)?; + + invoke_method( + vm, + call_stack, + stack, + class, + method, + &InvocationType::Interface, + ) +} + +/// See: +#[inline] +pub(crate) fn invokedynamic( + _vm: &VM, + _call_stack: &mut CallStack, + _stack: &mut OperandStack, + _constant_pool: &ConstantPool, + _method_index: u16, +) -> Result { + todo!() +} + +/// Invoke the method at the specified index +/// +/// # Errors +/// if the method is not found +#[inline] +fn invoke_method( + vm: &VM, + call_stack: &mut CallStack, + stack: &mut OperandStack, + mut class: Arc, + mut method: Arc, + invocation_type: &InvocationType, +) -> Result { + let parameters = method.parameters().len(); + let mut arguments = if method.is_static() { + Vec::with_capacity(parameters) + } else { + // Add one for the object reference + Vec::with_capacity(parameters + 1) + }; + for _ in 0..parameters { + arguments.push(stack.pop()?); + } + if !method.is_static() { + let object = stack.pop_object()?; + arguments.push(Value::Object(object)); + } + arguments.reverse(); + + // TODO: evaluate refactoring this + match invocation_type { + InvocationType::Interface | InvocationType::Virtual => { + let Some(Value::Object(Some(reference))) = arguments.first() else { + return Err(RuntimeError("No reference found".to_string())); + }; + class = match reference { + Reference::Array(class, _) => class.clone(), + Reference::Object(object) => object.class().clone(), + _ => { + // Primitive types do not have a class associated with them so the class must be + // created from the class name. + let class_name = reference.class_name(); + vm.class(call_stack, &class_name)? + } + }; + let method_name = method.name(); + let method_descriptor = method.descriptor(); + + // Find the method in the class hierarchy; the Method.try_get_virtual_method() cannot + // currently be used here because the class constant pool associated with the method is + // required for execution. + loop { + if let Some(class_method) = class.method(method_name, method_descriptor) { + method = class_method; + break; + } + let Some(parent_class) = class.parent()? else { + return Err(RuntimeError( + "No virtual method found for class".to_string(), + )); + }; + class = parent_class; + } + } + _ => {} + } + + let result = call_stack.execute(vm, &class, &method, arguments)?; + if let Some(result) = result { + stack.push(result)?; + } + Ok(Continue) +} + +#[cfg(test)] +mod test { + // #[test] + // fn test_invokevirtual() -> Result<()> { + // todo!() + // } + + // #[test] + // fn test_invokespecial() -> Result<()> { + // todo!() + // } + + // #[test] + // fn test_invokestatic() -> Result<()> { + // todo!() + // } + + // #[test] + // fn test_invokeinterface() -> Result<()> { + // todo!() + // } + + // #[test] + // fn test_invokedynamic() -> Result<()> { + // todo!() + // } +} diff --git a/ristretto_vm/src/instruction/ldc.rs b/ristretto_vm/src/instruction/ldc.rs new file mode 100644 index 00000000..794fc849 --- /dev/null +++ b/ristretto_vm/src/instruction/ldc.rs @@ -0,0 +1,247 @@ +use crate::call_stack::CallStack; +use crate::frame::ExecutionResult::Continue; +use crate::frame::{ExecutionResult, Frame}; +use crate::Error::{InvalidConstant, InvalidConstantIndex}; +use crate::{Result, VM}; +use ristretto_classfile::Constant; +use ristretto_classloader::Value; + +/// See: +#[inline] +pub(crate) fn ldc( + vm: &VM, + call_stack: &mut CallStack, + frame: &mut Frame, + index: u8, +) -> Result { + let index = u16::from(index); + load_constant(vm, call_stack, frame, index) +} + +/// See: +#[inline] +pub(crate) fn ldc_w( + vm: &VM, + call_stack: &mut CallStack, + frame: &mut Frame, + index: u16, +) -> Result { + load_constant(vm, call_stack, frame, index) +} + +/// See: +#[inline] +pub(crate) fn ldc2_w(frame: &mut Frame, index: u16) -> Result { + let constant_pool = frame.class.constant_pool(); + let constant = constant_pool + .get(index) + .ok_or_else(|| InvalidConstantIndex(index))?; + + let value = match constant { + Constant::Long(value) => Value::Long(*value), + Constant::Double(value) => Value::Double(*value), + constant => { + return Err(InvalidConstant { + expected: "long|double".to_string(), + actual: format!("{constant:?}"), + }) + } + }; + frame.stack.push(value)?; + Ok(Continue) +} + +/// Load the constant at the specified index onto the stack +/// +/// # Errors +/// if the constant is not an integer, float, string or class +fn load_constant( + vm: &VM, + call_stack: &mut CallStack, + frame: &mut Frame, + index: u16, +) -> Result { + let constant_pool = frame.class.constant_pool(); + let constant = constant_pool + .get(index) + .ok_or_else(|| InvalidConstantIndex(index))?; + + let value = match constant { + Constant::Integer(value) => Value::Int(*value), + Constant::Float(value) => Value::Float(*value), + Constant::String(utf8_index) => { + let utf8_value = constant_pool.try_get_utf8(*utf8_index)?; + vm.to_string_value(call_stack, utf8_value)? + } + Constant::Class(class_index) => { + let class_name = constant_pool.try_get_utf8(*class_index)?; + vm.to_class_value(call_stack, class_name)? + } + constant => { + return Err(InvalidConstant { + expected: "integer|float|string|class".to_string(), + actual: format!("{constant:?}"), + }) + } + }; + frame.stack.push(value)?; + Ok(Continue) +} + +#[cfg(test)] +mod test { + use super::*; + use std::sync::Arc; + + #[test] + fn test_ldc() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let class = &mut frame.class; + let constant_pool = Arc::get_mut(class).expect("class").constant_pool_mut(); + let index = constant_pool.add_integer(42)?; + let index = u8::try_from(index)?; + let process_result = ldc(&vm, &mut call_stack, &mut frame, index)?; + assert_eq!(process_result, Continue); + assert_eq!(42, frame.stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_ldc_w() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let class = &mut frame.class; + let constant_pool = Arc::get_mut(class).expect("class").constant_pool_mut(); + let index = constant_pool.add_integer(42)?; + let process_result = ldc_w(&vm, &mut call_stack, &mut frame, index)?; + assert_eq!(process_result, Continue); + assert_eq!(42, frame.stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_constant_integer() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let class = &mut frame.class; + let constant_pool = Arc::get_mut(class).expect("class").constant_pool_mut(); + let index = constant_pool.add_integer(42)?; + let process_result = load_constant(&vm, &mut call_stack, &mut frame, index)?; + assert_eq!(process_result, Continue); + assert_eq!(42, frame.stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_load_constant_float() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let class = &mut frame.class; + let constant_pool = Arc::get_mut(class).expect("class").constant_pool_mut(); + let index = constant_pool.add_float(42.1)?; + let process_result = load_constant(&vm, &mut call_stack, &mut frame, index)?; + assert_eq!(process_result, Continue); + let value = frame.stack.pop_float()? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_load_constant_string() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let class = &mut frame.class; + let constant_pool = Arc::get_mut(class).expect("class").constant_pool_mut(); + let index = constant_pool.add_string("foo")?; + let process_result = load_constant(&vm, &mut call_stack, &mut frame, index)?; + assert_eq!(process_result, Continue); + let object = frame.stack.pop_object()?.expect("object"); + assert_eq!("string(foo)", format!("{object}")); + Ok(()) + } + + #[test] + fn test_load_constant_class() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let class = &mut frame.class; + let constant_pool = Arc::get_mut(class).expect("class").constant_pool_mut(); + let index = constant_pool.add_class("java/lang/Object")?; + let process_result = load_constant(&vm, &mut call_stack, &mut frame, index)?; + assert_eq!(process_result, Continue); + let object = frame.stack.pop_object()?.expect("object"); + assert_eq!("class java/lang/Class", format!("{object}")); + Ok(()) + } + + #[test] + fn test_load_constant_invalid_index() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let result = load_constant(&vm, &mut call_stack, &mut frame, 42); + assert!(matches!(result, Err(InvalidConstantIndex(42)))); + Ok(()) + } + + #[test] + fn test_load_constant_invalid_type() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let class = &mut frame.class; + let constant_pool = Arc::get_mut(class).expect("class").constant_pool_mut(); + let index = constant_pool.add_long(42)?; + let result = load_constant(&vm, &mut call_stack, &mut frame, index); + assert!(matches!( + result, + Err(InvalidConstant { + expected, + actual + }) if expected == "integer|float|string|class" && actual == "Long(42)" + )); + Ok(()) + } + + #[test] + fn test_ldc2_w_long() -> Result<()> { + let (_vm, _call_stack, mut frame) = crate::test::frame()?; + let class = &mut frame.class; + let constant_pool = Arc::get_mut(class).expect("class").constant_pool_mut(); + let index = constant_pool.add_long(42)?; + let result = ldc2_w(&mut frame, index)?; + assert_eq!(Continue, result); + assert_eq!(42, frame.stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_ldc2_w_double() -> Result<()> { + let (_vm, _call_stack, mut frame) = crate::test::frame()?; + let class = &mut frame.class; + let constant_pool = Arc::get_mut(class).expect("class").constant_pool_mut(); + let index = constant_pool.add_double(42.1)?; + let result = ldc2_w(&mut frame, index)?; + assert_eq!(Continue, result); + let value = frame.stack.pop_double()? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_ldc2_w_invalid_index() -> Result<()> { + let (_vm, _call_stack, mut frame) = crate::test::frame()?; + let result = ldc2_w(&mut frame, 42); + assert!(matches!(result, Err(InvalidConstantIndex(42)))); + Ok(()) + } + + #[test] + fn test_ldc2_w_invalid_type() -> Result<()> { + let (_vm, _call_stack, mut frame) = crate::test::frame()?; + let class = &mut frame.class; + let constant_pool = Arc::get_mut(class).expect("class").constant_pool_mut(); + let index = constant_pool.add_integer(42)?; + let result = ldc2_w(&mut frame, index); + assert!(matches!( + result, + Err(InvalidConstant { + expected, + actual + }) if expected == "long|double" && actual == "Integer(42)" + )); + + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/long.rs b/ristretto_vm/src/instruction/long.rs new file mode 100644 index 00000000..2d8723de --- /dev/null +++ b/ristretto_vm/src/instruction/long.rs @@ -0,0 +1,822 @@ +use crate::frame::ExecutionResult::Return; +use crate::frame::{ExecutionResult, ExecutionResult::Continue}; +use crate::local_variables::LocalVariables; +use crate::operand_stack::OperandStack; +use crate::Error::{ArrayIndexOutOfBounds, InvalidStackValue, NullPointer}; +use crate::{Result, Value}; +use ristretto_classloader::Reference; +use std::cmp::Ordering; + +/// See: +#[inline] +pub(crate) fn lconst_0(stack: &mut OperandStack) -> Result { + stack.push_long(0)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lconst_1(stack: &mut OperandStack) -> Result { + stack.push_long(1)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lload( + locals: &LocalVariables, + stack: &mut OperandStack, + index: u8, +) -> Result { + let value = locals.get_long(usize::from(index))?; + stack.push_long(value)?; + Ok(Continue) +} + +/// See: +/// See: +#[inline] +pub(crate) fn lload_w( + locals: &LocalVariables, + stack: &mut OperandStack, + index: u16, +) -> Result { + let value = locals.get_long(usize::from(index))?; + stack.push_long(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lload_0( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_long(0)?; + stack.push_long(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lload_1( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_long(1)?; + stack.push_long(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lload_2( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_long(2)?; + stack.push_long(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lload_3( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = locals.get_long(3)?; + stack.push_long(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lstore( + locals: &mut LocalVariables, + stack: &mut OperandStack, + index: u8, +) -> Result { + let value = stack.pop_long()?; + locals.set_long(usize::from(index), value)?; + Ok(Continue) +} + +/// See: +/// See: +#[inline] +pub(crate) fn lstore_w( + locals: &mut LocalVariables, + stack: &mut OperandStack, + index: u16, +) -> Result { + let value = stack.pop_long()?; + locals.set_long(usize::from(index), value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lstore_0( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_long()?; + locals.set_long(0, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lstore_1( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_long()?; + locals.set_long(1, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lstore_2( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_long()?; + locals.set_long(2, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lstore_3( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_long()?; + locals.set_long(3, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn laload(stack: &mut OperandStack) -> Result { + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::LongArray(array)) => { + let index = usize::try_from(index)?; + let Some(value) = array.get(index)? else { + return Err(ArrayIndexOutOfBounds(index)); + }; + stack.push_long(value)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "long array".to_string(), + actual: object.to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn lastore(stack: &mut OperandStack) -> Result { + let value = stack.pop_long()?; + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::LongArray(ref mut array)) => { + let index = usize::try_from(index)?; + if index >= array.capacity()? { + return Err(ArrayIndexOutOfBounds(index)); + }; + array.set(index, value)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "long array".to_string(), + actual: object.to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn ladd(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_long()?; + let value1 = stack.pop_long()?; + stack.push_long(i64::wrapping_add(value1, value2))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lsub(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_long()?; + let value1 = stack.pop_long()?; + stack.push_long(i64::wrapping_sub(value1, value2))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lmul(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_long()?; + let value1 = stack.pop_long()?; + stack.push_long(i64::wrapping_mul(value1, value2))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn ldiv(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_long()?; + let value1 = stack.pop_long()?; + stack.push_long(i64::wrapping_div(value1, value2))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lrem(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_long()?; + let value1 = stack.pop_long()?; + stack.push_long(i64::wrapping_rem(value1, value2))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lneg(stack: &mut OperandStack) -> Result { + let value = stack.pop_long()?; + stack.push_long(-value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lshl(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_long()?; + stack.push_long(value1 << (value2 & 0x3f))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lshr(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_long()?; + stack.push_long(value1 >> (value2 & 0x3f))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lushr(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_int()?; + let value1 = stack.pop_long()?; + let result = if value1 > 0 { + value1 >> (value2 & 0x1f) + } else { + #[expect(clippy::cast_sign_loss)] + let value1 = value1 as u64; + let result = value1 >> value2; + #[expect(clippy::cast_possible_wrap)] + let result = result as i64; + result + }; + stack.push_long(result)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn land(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_long()?; + let value1 = stack.pop_long()?; + stack.push_long(value1 & value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lor(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_long()?; + let value1 = stack.pop_long()?; + stack.push_long(value1 | value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lxor(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_long()?; + let value1 = stack.pop_long()?; + stack.push_long(value1 ^ value2)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lcmp(stack: &mut OperandStack) -> Result { + let value2 = stack.pop_long()?; + let value1 = stack.pop_long()?; + let cmp = match value1.cmp(&value2) { + Ordering::Greater => 1, + Ordering::Less => -1, + Ordering::Equal => 0, + }; + stack.push_int(cmp)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn lreturn(stack: &mut OperandStack) -> Result { + let value = stack.pop_long()?; + Ok(Return(Some(Value::Long(value)))) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Error::InvalidOperand; + use ristretto_classloader::ConcurrentVec; + + #[test] + fn test_lconst_0() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = lconst_0(stack)?; + assert_eq!(Continue, result); + assert_eq!(0, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lconst_1() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = lconst_1(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lload() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_long(0, 42)?; + let stack = &mut OperandStack::with_max_size(1); + let result = lload(&locals, stack, 0)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lload_w() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_long(0, 42)?; + let stack = &mut OperandStack::with_max_size(1); + let result = lload_w(&locals, stack, 0)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lload_0() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_long(0, 42)?; + let stack = &mut OperandStack::with_max_size(1); + let result = lload_0(&locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lload_1() -> Result<()> { + let mut locals = LocalVariables::with_max_size(2); + locals.set_long(1, 42)?; + let stack = &mut OperandStack::with_max_size(1); + let result = lload_1(&locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lload_2() -> Result<()> { + let mut locals = LocalVariables::with_max_size(3); + locals.set_long(2, 42)?; + let stack = &mut OperandStack::with_max_size(1); + let result = lload_2(&locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lload_3() -> Result<()> { + let mut locals = LocalVariables::with_max_size(4); + locals.set_long(3, 42)?; + let stack = &mut OperandStack::with_max_size(1); + let result = lload_3(&locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lstore() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_long(42)?; + let result = lstore(locals, stack, 0)?; + assert_eq!(Continue, result); + assert_eq!(42, locals.get_long(0)?); + Ok(()) + } + + #[test] + fn test_lstore_w() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_long(42)?; + let result = lstore_w(locals, stack, 0)?; + assert_eq!(Continue, result); + assert_eq!(42, locals.get_long(0)?); + Ok(()) + } + + #[test] + fn test_lstore_0() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_long(42)?; + let result = lstore_0(locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, locals.get_long(0)?); + Ok(()) + } + + #[test] + fn test_lstore_1() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(2); + let stack = &mut OperandStack::with_max_size(1); + stack.push_long(42)?; + let result = lstore_1(locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, locals.get_long(1)?); + Ok(()) + } + + #[test] + fn test_lstore_2() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(3); + let stack = &mut OperandStack::with_max_size(1); + stack.push_long(42)?; + let result = lstore_2(locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, locals.get_long(2)?); + Ok(()) + } + + #[test] + fn test_lstore_3() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(4); + let stack = &mut OperandStack::with_max_size(1); + stack.push_long(42)?; + let result = lstore_3(locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(42, locals.get_long(3)?); + Ok(()) + } + + #[test] + fn test_laload() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::LongArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + let result = laload(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_laload_invalid_value() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + let result = laload(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "long array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_laload_invalid_index() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::LongArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + let result = laload(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_laload_null_pointer() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_object(None)?; + stack.push_int(0)?; + let result = laload(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_lastore() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::LongArray(ConcurrentVec::from(vec![3])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + stack.push_long(42)?; + let result = lastore(stack)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_lastore_invalid_value() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + stack.push_long(42)?; + let result = lastore(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "long array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_lastore_invalid_index() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::LongArray(ConcurrentVec::from(vec![3])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + stack.push_long(42)?; + let result = lastore(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_lastore_null_pointer() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + stack.push_object(None)?; + stack.push_int(0)?; + stack.push_long(42)?; + let result = lastore(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_ladd() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(1)?; + stack.push_long(2)?; + let result = ladd(stack)?; + assert_eq!(Continue, result); + assert_eq!(3, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_ladd_overflow() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(i64::MAX)?; + stack.push_long(1)?; + let result = ladd(stack)?; + assert_eq!(Continue, result); + assert_eq!(i64::MIN, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lsub() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(2)?; + stack.push_long(1)?; + let result = lsub(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lsub_underflow() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(i64::MIN)?; + stack.push_long(1)?; + let result = lsub(stack)?; + assert_eq!(Continue, result); + assert_eq!(i64::MAX, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lmul() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(2)?; + stack.push_long(3)?; + let result = lmul(stack)?; + assert_eq!(Continue, result); + assert_eq!(6, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_ldiv() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(6)?; + stack.push_long(3)?; + let result = ldiv(stack)?; + assert_eq!(Continue, result); + assert_eq!(2, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lrem() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(1)?; + stack.push_long(2)?; + let result = lrem(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lneg() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_long(1)?; + let result = lneg(stack)?; + assert_eq!(Continue, result); + assert_eq!(-1, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lshl() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(4)?; + stack.push_int(1)?; + let result = lshl(stack)?; + assert_eq!(Continue, result); + assert_eq!(8, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lshr() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(4)?; + stack.push_int(1)?; + let result = lshr(stack)?; + assert_eq!(Continue, result); + assert_eq!(2, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lushr() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(4)?; + stack.push_int(1)?; + let result = lushr(stack)?; + assert_eq!(Continue, result); + assert_eq!(2, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lushr_negative_value1() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(-1)?; + stack.push_int(60)?; + let result = lushr(stack)?; + assert_eq!(Continue, result); + assert_eq!(15, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_land() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(2)?; + stack.push_long(3)?; + let result = land(stack)?; + assert_eq!(Continue, result); + assert_eq!(2, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lor() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(2)?; + stack.push_long(4)?; + let result = lor(stack)?; + assert_eq!(Continue, result); + assert_eq!(6, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lxor() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(2)?; + stack.push_long(3)?; + let result = lxor(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_lcmp_equal() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(1)?; + stack.push_long(1)?; + let result = lcmp(stack)?; + assert_eq!(Continue, result); + assert_eq!(0, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_lcmp_greater_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(2)?; + stack.push_long(1)?; + let result = lcmp(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_lcmp_less_than() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(1)?; + stack.push_long(2)?; + let result = lcmp(stack)?; + assert_eq!(Continue, result); + assert_eq!(-1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_lreturn() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_long(42)?; + let result = lreturn(stack)?; + assert!(matches!(result, Return(Some(Value::Long(42))))); + Ok(()) + } + + #[test] + fn test_lreturn_invalid_type() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = lreturn(stack); + assert!(matches!( + result, + Err(InvalidOperand { + expected, + actual + }) if expected == "long" && actual == "object(null)" + )); + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/mod.rs b/ristretto_vm/src/instruction/mod.rs new file mode 100644 index 00000000..621acfa0 --- /dev/null +++ b/ristretto_vm/src/instruction/mod.rs @@ -0,0 +1,35 @@ +mod array; +mod branch; +mod byte; +mod char; +mod convert; +mod double; +mod field; +mod float; +mod integer; +mod invoke; +mod ldc; +mod long; +mod object; +mod push; +mod short; +mod stack; +mod r#static; + +pub(crate) use array::*; +pub(crate) use branch::*; +pub(crate) use byte::*; +pub(crate) use char::*; +pub(crate) use convert::*; +pub(crate) use double::*; +pub(crate) use field::*; +pub(crate) use float::*; +pub(crate) use integer::*; +pub(crate) use invoke::*; +pub(crate) use ldc::*; +pub(crate) use long::*; +pub(crate) use object::*; +pub(crate) use push::*; +pub(crate) use r#static::*; +pub(crate) use short::*; +pub(crate) use stack::*; diff --git a/ristretto_vm/src/instruction/object.rs b/ristretto_vm/src/instruction/object.rs new file mode 100644 index 00000000..20d46074 --- /dev/null +++ b/ristretto_vm/src/instruction/object.rs @@ -0,0 +1,731 @@ +use crate::call_stack::CallStack; +use crate::frame::ExecutionResult::Return; +use crate::frame::{ExecutionResult, ExecutionResult::Continue}; +use crate::local_variables::LocalVariables; +use crate::operand_stack::OperandStack; +use crate::Error::{ + ArrayIndexOutOfBounds, ClassCastError, InvalidStackValue, NullPointer, RuntimeError, +}; +use crate::{Result, Value, VM}; +use ristretto_classfile::ConstantPool; +use ristretto_classloader::{Object, Reference}; + +/// See: +#[inline] +pub(crate) fn aconst_null(stack: &mut OperandStack) -> Result { + stack.push_object(None)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn aload( + locals: &LocalVariables, + stack: &mut OperandStack, + index: u8, +) -> Result { + let object = locals.get_object(usize::from(index))?; + stack.push_object(object)?; + Ok(Continue) +} + +/// See: +/// See: +#[inline] +pub(crate) fn aload_w( + locals: &LocalVariables, + stack: &mut OperandStack, + index: u16, +) -> Result { + let object = locals.get_object(usize::from(index))?; + stack.push_object(object)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn aload_0( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let object = locals.get_object(0)?; + stack.push_object(object)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn aload_1( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let object = locals.get_object(1)?; + stack.push_object(object)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn aload_2( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let object = locals.get_object(2)?; + stack.push_object(object)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn aload_3( + locals: &LocalVariables, + stack: &mut OperandStack, +) -> Result { + let object = locals.get_object(3)?; + stack.push_object(object)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn astore( + locals: &mut LocalVariables, + stack: &mut OperandStack, + index: u8, +) -> Result { + let value = stack.pop_object()?; + locals.set_object(usize::from(index), value)?; + Ok(Continue) +} + +/// See: +/// See: +#[inline] +pub(crate) fn astore_w( + locals: &mut LocalVariables, + stack: &mut OperandStack, + index: u16, +) -> Result { + let value = stack.pop_object()?; + locals.set_object(usize::from(index), value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn astore_0( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_object()?; + locals.set_object(0, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn astore_1( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_object()?; + locals.set_object(1, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn astore_2( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_object()?; + locals.set_object(2, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn astore_3( + locals: &mut LocalVariables, + stack: &mut OperandStack, +) -> Result { + let value = stack.pop_object()?; + locals.set_object(3, value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn aaload(stack: &mut OperandStack) -> Result { + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::Array(_class, array)) => { + let index = usize::try_from(index)?; + let Some(value) = array.get(index)? else { + return Err(ArrayIndexOutOfBounds(index)); + }; + stack.push_object(value)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "reference array".to_string(), + actual: object.to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn aastore(stack: &mut OperandStack) -> Result { + let value = stack.pop_object()?; + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::Array(_class, ref mut array)) => { + let index = usize::try_from(index)?; + if index >= array.capacity()? { + return Err(ArrayIndexOutOfBounds(index)); + }; + // TODO: validate object type is compatible with array type + // See: https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-6.html#jvms-6.5.aastore + array.set(index, value)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "reference array".to_string(), + actual: object.to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn areturn(stack: &mut OperandStack) -> Result { + let value = stack.pop_object()?; + Ok(Return(Some(Value::Object(value)))) +} + +/// See: +#[inline] +pub(crate) fn new( + vm: &VM, + call_stack: &mut CallStack, + stack: &mut OperandStack, + constant_pool: &ConstantPool, + index: u16, +) -> Result { + let class_name = constant_pool.try_get_class(index)?; + let class = vm.class(call_stack, class_name)?; + let object = Object::new(class)?; + let reference = Reference::Object(object); + stack.push_object(Some(reference))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn checkcast(stack: &mut OperandStack, class_name: &str) -> Result { + let Value::Object(reference) = stack.peek()? else { + return Err(RuntimeError("Expected object".to_string())); + }; + if reference.is_none() { + return Ok(Continue); + } + if !is_instanceof(reference, class_name)? { + return Err(ClassCastError(class_name.to_string())); + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn instanceof(stack: &mut OperandStack, class_name: &str) -> Result { + let object = stack.pop_object()?; + if object.is_none() { + stack.push_int(0)?; + return Ok(Continue); + } + if is_instanceof(&object, class_name)? { + stack.push_int(1)?; + } else { + stack.push_int(0)?; + } + Ok(Continue) +} + +#[inline] +fn is_instanceof(object: &Option, class_name: &str) -> Result { + let Some(object) = object else { + return Ok(false); + }; + + match object { + Reference::ByteArray(_) + | Reference::CharArray(_) + | Reference::ShortArray(_) + | Reference::IntArray(_) + | Reference::LongArray(_) + | Reference::FloatArray(_) + | Reference::DoubleArray(_) => { + let reference_class_name = object.class_name(); + Ok(reference_class_name == class_name) + } + Reference::Array(array_class, _) => { + let reference_class_name = object.class_name(); + let reference_array_dimensions = + reference_class_name.chars().filter(|&c| c == '[').count(); + let class_array_depth = class_name.chars().filter(|&c| c == '[').count(); + if class_array_depth > 0 && class_array_depth != reference_array_dimensions { + return Ok(false); + } + // Convert an array class name (e.g. [Ljava/lang/String;) into the base class name by + // remove the leading '[' and 'L' and trailing ';' + let class_name = class_name + .trim_start_matches('[') + .strip_prefix("L") + .unwrap_or(class_name) + .strip_suffix(";") + .unwrap_or(class_name); + + Ok(array_class.is_assignable_from(class_name)?) + } + Reference::Object(object) => Ok(object.instanceof(class_name)?), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Error::InvalidOperand; + use ristretto_classloader::ConcurrentVec; + use std::sync::Arc; + + #[test] + fn test_aconst_null() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = aconst_null(stack)?; + assert_eq!(Continue, result); + assert_eq!(Value::Object(None), stack.pop()?); + Ok(()) + } + + #[test] + fn test_aload() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_object(0, None)?; + let stack = &mut OperandStack::with_max_size(1); + let result = aload(&locals, stack, 0)?; + assert_eq!(Continue, result); + assert_eq!(None, stack.pop_object()?); + Ok(()) + } + + #[test] + fn test_aload_w() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_object(0, None)?; + let stack = &mut OperandStack::with_max_size(1); + let result = aload_w(&locals, stack, 0)?; + assert_eq!(Continue, result); + assert_eq!(None, stack.pop_object()?); + Ok(()) + } + + #[test] + fn test_aload_0() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_object(0, None)?; + let stack = &mut OperandStack::with_max_size(1); + let result = aload_0(&locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(None, stack.pop_object()?); + Ok(()) + } + + #[test] + fn test_aload_1() -> Result<()> { + let mut locals = LocalVariables::with_max_size(2); + locals.set_object(1, None)?; + let stack = &mut OperandStack::with_max_size(1); + let result = aload_1(&locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(None, stack.pop_object()?); + Ok(()) + } + + #[test] + fn test_aload_2() -> Result<()> { + let mut locals = LocalVariables::with_max_size(3); + locals.set_object(2, None)?; + let stack = &mut OperandStack::with_max_size(1); + let result = aload_2(&locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(None, stack.pop_object()?); + Ok(()) + } + + #[test] + fn test_aload_3() -> Result<()> { + let mut locals = LocalVariables::with_max_size(4); + locals.set_object(3, None)?; + let stack = &mut OperandStack::with_max_size(1); + let result = aload_3(&locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(None, stack.pop_object()?); + Ok(()) + } + + #[test] + fn test_astore() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = astore(locals, stack, 0)?; + assert_eq!(Continue, result); + assert_eq!(None, locals.get_object(0)?); + Ok(()) + } + + #[test] + fn test_astore_w() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = astore_w(locals, stack, 0)?; + assert_eq!(Continue, result); + assert_eq!(None, locals.get_object(0)?); + Ok(()) + } + + #[test] + fn test_astore_0() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(1); + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = astore_0(locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(None, locals.get_object(0)?); + Ok(()) + } + + #[test] + fn test_astore_1() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(2); + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = astore_1(locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(None, locals.get_object(1)?); + Ok(()) + } + + #[test] + fn test_astore_2() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(3); + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = astore_2(locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(None, locals.get_object(2)?); + Ok(()) + } + + #[test] + fn test_astore_3() -> Result<()> { + let locals = &mut LocalVariables::with_max_size(4); + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = astore_3(locals, stack)?; + assert_eq!(Continue, result); + assert_eq!(None, locals.get_object(3)?); + Ok(()) + } + + #[test] + fn test_aaload() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let stack = &mut frame.stack; + let class = vm.class(&mut call_stack, "java/lang/Object")?; + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + let array = Reference::Array(class, ConcurrentVec::from(vec![Some(object.clone())])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + let result = aaload(stack)?; + assert_eq!(Continue, result); + assert_eq!(Some(object), stack.pop_object()?); + Ok(()) + } + + #[test] + fn test_aaload_invalid_value() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + let result = aaload(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "reference array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_aaload_invalid_index() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let stack = &mut frame.stack; + let class = vm.class(&mut call_stack, "java/lang/Object")?; + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + let array = Reference::Array(class, ConcurrentVec::from(vec![Some(object.clone())])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + let result = aaload(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_aaload_null_pointer() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_object(None)?; + stack.push_int(0)?; + let result = aaload(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_aastore() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let stack = &mut frame.stack; + let class = vm.class(&mut call_stack, "java/lang/Object")?; + let object = Reference::IntArray(ConcurrentVec::from(vec![3])); + let array = Reference::Array(class, ConcurrentVec::from(vec![Some(object)])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + stack.push_object(Some(Reference::IntArray(ConcurrentVec::from(vec![3]))))?; + let result = aastore(stack)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_aastore_invalid_value() -> Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object.clone()))?; + stack.push_int(0)?; + stack.push_object(Some(object))?; + let result = aastore(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "reference array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_aastore_invalid_index() -> crate::Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let stack = &mut frame.stack; + let class = vm.class(&mut call_stack, "java/lang/Object")?; + let object = Reference::IntArray(ConcurrentVec::from(vec![3])); + let array = Reference::Array(class, ConcurrentVec::from(vec![Some(object.clone())])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + stack.push_object(Some(object))?; + let result = aastore(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_aastore_null_pointer() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let object = Reference::IntArray(ConcurrentVec::from(vec![3])); + stack.push_object(None)?; + stack.push_int(0)?; + stack.push_object(Some(object))?; + let result = aastore(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_areturn_object() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let object = Reference::ByteArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + let result = areturn(stack)?; + assert!(matches!(result, Return(Some(Value::Object(_))))); + Ok(()) + } + + #[test] + fn test_areturn_null() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = areturn(stack)?; + assert!(matches!(result, Return(Some(Value::Object(None))))); + Ok(()) + } + + #[test] + fn test_areturn_invalid_type() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = areturn(stack); + assert!(matches!( + result, + Err(InvalidOperand { + expected, + actual + }) if expected == "object" && actual == "int(42)" + )); + Ok(()) + } + + #[test] + fn test_new() -> Result<()> { + let (vm, mut call_stack, mut frame) = crate::test::frame()?; + let stack = &mut frame.stack; + let class = &mut frame.class; + let constant_pool = Arc::get_mut(class).expect("class").constant_pool_mut(); + let class_index = constant_pool.add_class("Child")?; + let process_result = new(&vm, &mut call_stack, stack, constant_pool, class_index)?; + assert_eq!(process_result, Continue); + let object = frame.stack.pop()?; + assert!(matches!(object, Value::Object(Some(Reference::Object(_))))); + Ok(()) + } + + #[test] + fn test_checkcast_null() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = checkcast(stack, "java/lang/Object")?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_checkcast_string_to_object() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let (vm, _, object_class) = crate::test::load_class("java/lang/Object")?; + let string = vm.string("foo")?; + stack.push(string)?; + let result = checkcast(stack, object_class.name())?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_checkcast_object_to_string() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let (_, _, object_class) = crate::test::load_class("java/lang/Object")?; + let (_, _, string_class) = crate::test::load_class("java/lang/String")?; + let object = Object::new(object_class)?; + stack.push_object(Some(Reference::Object(object)))?; + let result = checkcast(stack, string_class.name()); + assert!(matches!( + result, + Err(ClassCastError(class)) if class == "java/lang/String" + )); + Ok(()) + } + + #[test] + fn test_instanceof_null() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let (_, _, object_class) = crate::test::load_class("java/lang/Object")?; + stack.push_object(None)?; + let result = instanceof(stack, object_class.name())?; + assert_eq!(Continue, result); + assert_eq!(0, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_instanceof_string_to_object() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let (vm, _, object_class) = crate::test::load_class("java/lang/Object")?; + let string = vm.string("foo")?; + stack.push(string)?; + let result = instanceof(stack, object_class.name())?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_instanceof_object_to_string() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let (_, _, object_class) = crate::test::load_class("java/lang/Object")?; + let (_, _, string_class) = crate::test::load_class("java/lang/String")?; + let object = Object::new(object_class)?; + stack.push_object(Some(Reference::Object(object)))?; + let result = instanceof(stack, string_class.name())?; + assert_eq!(Continue, result); + assert_eq!(0, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_instanceof_string_array_to_object() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let (_, _, object_class) = crate::test::load_class("java/lang/Object")?; + let (_, _, string_class) = crate::test::load_class("java/lang/String")?; + let string_array = Reference::Array(string_class, ConcurrentVec::default()); + stack.push_object(Some(string_array))?; + let result = instanceof(stack, object_class.name())?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_instanceof_object_array_to_string() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let (_, _, object_class) = crate::test::load_class("java/lang/Object")?; + let (_, _, string_class) = crate::test::load_class("java/lang/String")?; + let object_array = Reference::Array(object_class, ConcurrentVec::default()); + stack.push_object(Some(object_array))?; + let result = instanceof(stack, string_class.name())?; + assert_eq!(Continue, result); + assert_eq!(0, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_instanceof_int_array_to_int_array() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let int_array = Reference::IntArray(ConcurrentVec::default()); + let int_array_class = int_array.class()?; + stack.push_object(Some(int_array))?; + let result = instanceof(stack, int_array_class.name())?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_instanceof_long_array_to_int_array() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let long_array = Reference::LongArray(ConcurrentVec::default()); + let int_array = Reference::IntArray(ConcurrentVec::default()); + let int_array_class = int_array.class()?; + stack.push_object(Some(long_array))?; + let result = instanceof(stack, int_array_class.name())?; + assert_eq!(Continue, result); + assert_eq!(0, stack.pop_int()?); + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/push.rs b/ristretto_vm/src/instruction/push.rs new file mode 100644 index 00000000..1839a15b --- /dev/null +++ b/ristretto_vm/src/instruction/push.rs @@ -0,0 +1,41 @@ +use crate::frame::ExecutionResult; +use crate::frame::ExecutionResult::Continue; +use crate::operand_stack::OperandStack; +use crate::Result; + +/// See: +#[inline] +pub(crate) fn bipush(stack: &mut OperandStack, value: u8) -> Result { + stack.push_int(i32::from(value))?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn sipush(stack: &mut OperandStack, value: i16) -> Result { + stack.push_int(i32::from(value))?; + Ok(Continue) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_bipush() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = bipush(stack, 42)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_sipush() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + let result = sipush(stack, 42)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/short.rs b/ristretto_vm/src/instruction/short.rs new file mode 100644 index 00000000..ec3ba180 --- /dev/null +++ b/ristretto_vm/src/instruction/short.rs @@ -0,0 +1,158 @@ +use crate::frame::ExecutionResult; +use crate::frame::ExecutionResult::Continue; +use crate::operand_stack::OperandStack; +use crate::Error::{ArrayIndexOutOfBounds, InvalidStackValue, NullPointer}; +use crate::Result; +use ristretto_classloader::Reference; + +/// See: +#[inline] +pub(crate) fn saload(stack: &mut OperandStack) -> Result { + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::ShortArray(array)) => { + let index = usize::try_from(index)?; + let Some(value) = array.get(index)? else { + return Err(ArrayIndexOutOfBounds(index)); + }; + stack.push_int(i32::from(value))?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "short array".to_string(), + actual: object.to_string(), + }), + } +} + +/// See: +#[inline] +pub(crate) fn sastore(stack: &mut OperandStack) -> Result { + let value = stack.pop_int()?; + let index = stack.pop_int()?; + match stack.pop_object()? { + None => Err(NullPointer), + Some(Reference::ShortArray(ref mut array)) => { + let index = usize::try_from(index)?; + if index >= array.capacity()? { + return Err(ArrayIndexOutOfBounds(index)); + }; + array.set(index, i16::try_from(value)?)?; + Ok(Continue) + } + Some(object) => Err(InvalidStackValue { + expected: "short array".to_string(), + actual: object.to_string(), + }), + } +} + +#[cfg(test)] +mod test { + use super::*; + use ristretto_classloader::ConcurrentVec; + + #[test] + fn test_saload() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::ShortArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + let result = saload(stack)?; + assert_eq!(Continue, result); + assert_eq!(42, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_saload_invalid_value() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + let result = saload(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "short array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_saload_invalid_index() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + let array = Reference::ShortArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + let result = saload(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_saload_null_pointer() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_object(None)?; + stack.push_int(0)?; + let result = saload(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } + + #[test] + fn test_sastore() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::ShortArray(ConcurrentVec::from(vec![3])); + stack.push_object(Some(array))?; + stack.push_int(0)?; + stack.push_int(42)?; + let result = sastore(stack)?; + assert_eq!(Continue, result); + Ok(()) + } + + #[test] + fn test_sastore_invalid_value() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let object = Reference::IntArray(ConcurrentVec::from(vec![42])); + stack.push_object(Some(object))?; + stack.push_int(2)?; + stack.push_int(42)?; + let result = sastore(stack); + assert!(matches!( + result, + Err(InvalidStackValue { + expected, + actual + }) if expected == "short array" && actual == "int[42]" + )); + Ok(()) + } + + #[test] + fn test_sastore_invalid_index() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + let array = Reference::ShortArray(ConcurrentVec::from(vec![3])); + stack.push_object(Some(array))?; + stack.push_int(2)?; + stack.push_int(42)?; + let result = sastore(stack); + assert!(matches!(result, Err(ArrayIndexOutOfBounds(2)))); + Ok(()) + } + + #[test] + fn test_sastore_null_pointer() -> crate::Result<()> { + let stack = &mut OperandStack::with_max_size(3); + stack.push_object(None)?; + stack.push_int(0)?; + stack.push_int(42)?; + let result = sastore(stack); + assert!(matches!(result, Err(NullPointer))); + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/stack.rs b/ristretto_vm/src/instruction/stack.rs new file mode 100644 index 00000000..a5b6e6b2 --- /dev/null +++ b/ristretto_vm/src/instruction/stack.rs @@ -0,0 +1,357 @@ +use crate::frame::ExecutionResult; +use crate::frame::ExecutionResult::Continue; +use crate::operand_stack::OperandStack; +use crate::Result; + +/// See: +#[inline] +pub(crate) fn pop(stack: &mut OperandStack) -> Result { + let _ = stack.pop()?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn pop2(stack: &mut OperandStack) -> Result { + let value = stack.pop()?; + if value.is_category_1() { + let _ = stack.pop()?; + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dup(stack: &mut OperandStack) -> Result { + let value = stack.peek()?.clone(); + stack.push(value)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dup_x1(stack: &mut OperandStack) -> Result { + let value1 = stack.pop()?; + let value2 = stack.pop()?; + stack.push(value1.clone())?; + stack.push(value2)?; + stack.push(value1)?; + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dup_x2(stack: &mut OperandStack) -> Result { + let value1 = stack.pop()?; + let value2 = stack.pop()?; + if value2.is_category_1() { + let value3 = stack.pop()?; + stack.push(value1.clone())?; + stack.push(value3)?; + stack.push(value2)?; + stack.push(value1)?; + } else { + stack.push(value1.clone())?; + stack.push(value2)?; + stack.push(value1)?; + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dup2(stack: &mut OperandStack) -> Result { + let value1 = stack.pop()?; + if value1.is_category_1() { + let value2 = stack.pop()?; + stack.push(value2.clone())?; + stack.push(value1.clone())?; + stack.push(value2)?; + stack.push(value1)?; + } else { + stack.push(value1.clone())?; + stack.push(value1)?; + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dup2_x1(stack: &mut OperandStack) -> Result { + let value1 = stack.pop()?; + let value2 = stack.pop()?; + if value1.is_category_1() { + let value3 = stack.pop()?; + stack.push(value2.clone())?; + stack.push(value1.clone())?; + stack.push(value3)?; + stack.push(value2)?; + stack.push(value1)?; + } else { + stack.push(value1.clone())?; + stack.push(value2)?; + stack.push(value1)?; + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn dup2_x2(stack: &mut OperandStack) -> Result { + let value1 = stack.pop()?; + let value2 = stack.pop()?; + if value1.is_category_1() { + let value3 = stack.pop()?; + if value3.is_category_1() { + let value4 = stack.pop()?; + stack.push(value2.clone())?; + stack.push(value1.clone())?; + stack.push(value4)?; + } else { + stack.push(value1.clone())?; + } + stack.push(value3)?; + stack.push(value2)?; + stack.push(value1)?; + } else { + if value2.is_category_1() { + let value3 = stack.pop()?; + stack.push(value1.clone())?; + stack.push(value3)?; + } else { + stack.push(value1.clone())?; + } + stack.push(value2)?; + stack.push(value1)?; + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn swap(stack: &mut OperandStack) -> Result { + // Swapping category 2 values (Double and Long) is not supported by the JVM specification and + // there is no mention of what should happen in this case. We will just swap the values and + // ignore the fact that category 2 values could be swapped here. + let value1 = stack.pop()?; + let value2 = stack.pop()?; + stack.push(value1)?; + stack.push(value2)?; + Ok(Continue) +} + +#[cfg(test)] +mod test { + use super::*; + use ristretto_classloader::Value; + + #[test] + fn test_pop() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_object(None)?; + let result = pop(stack)?; + assert_eq!(Continue, result); + assert!(stack.is_empty()); + Ok(()) + } + + #[test] + fn test_pop2_form_1() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_object(None)?; + stack.push_object(None)?; + let result = pop2(stack)?; + assert_eq!(Continue, result); + assert!(stack.is_empty()); + Ok(()) + } + + #[test] + fn test_pop2_form_2() -> Result<()> { + let stack = &mut OperandStack::with_max_size(1); + stack.push_long(42)?; + let result = pop2(stack)?; + assert_eq!(Continue, result); + assert!(stack.is_empty()); + Ok(()) + } + + #[test] + fn test_dup() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_object(None)?; + let result = dup(stack)?; + assert_eq!(Continue, result); + assert_eq!(Value::Object(None), stack.pop()?); + assert_eq!(Value::Object(None), stack.pop()?); + Ok(()) + } + + #[test] + fn test_dup_x1() -> Result<()> { + let stack = &mut OperandStack::with_max_size(3); + stack.push_int(2)?; + stack.push_int(1)?; + let result = dup_x1(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + assert_eq!(2, stack.pop_int()?); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dup_x2_form_1() -> Result<()> { + let stack = &mut OperandStack::with_max_size(4); + stack.push_int(3)?; + stack.push_int(2)?; + stack.push_int(1)?; + let result = dup_x2(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + assert_eq!(2, stack.pop_int()?); + assert_eq!(3, stack.pop_int()?); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dup_x2_form_2() -> Result<()> { + let stack = &mut OperandStack::with_max_size(3); + stack.push_long(2)?; + stack.push_int(1)?; + let result = dup_x2(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + assert_eq!(2, stack.pop_long()?); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dup2_form_1() -> Result<()> { + let stack = &mut OperandStack::with_max_size(4); + stack.push_int(2)?; + stack.push_int(1)?; + let result = dup2(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + assert_eq!(2, stack.pop_int()?); + assert_eq!(1, stack.pop_int()?); + assert_eq!(2, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dup2_form_2() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_long(1)?; + let result = dup2(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_long()?); + assert_eq!(1, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_dup2_x1_form_1() -> Result<()> { + let stack = &mut OperandStack::with_max_size(5); + stack.push_int(3)?; + stack.push_int(2)?; + stack.push_int(1)?; + let result = dup2_x1(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + assert_eq!(2, stack.pop_int()?); + assert_eq!(3, stack.pop_int()?); + assert_eq!(1, stack.pop_int()?); + assert_eq!(2, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dup2_x1_form_2() -> Result<()> { + let stack = &mut OperandStack::with_max_size(5); + stack.push_int(2)?; + stack.push_long(1)?; + let result = dup2_x1(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_long()?); + assert_eq!(2, stack.pop_int()?); + assert_eq!(1, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_dup2_x2_form_1() -> Result<()> { + let stack = &mut OperandStack::with_max_size(6); + stack.push_int(4)?; + stack.push_int(3)?; + stack.push_int(2)?; + stack.push_int(1)?; + let result = dup2_x2(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + assert_eq!(2, stack.pop_int()?); + assert_eq!(3, stack.pop_int()?); + assert_eq!(4, stack.pop_int()?); + assert_eq!(1, stack.pop_int()?); + assert_eq!(2, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dup2_x2_form_2() -> Result<()> { + let stack = &mut OperandStack::with_max_size(4); + stack.push_int(3)?; + stack.push_int(2)?; + stack.push_long(1)?; + let result = dup2_x2(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_long()?); + assert_eq!(2, stack.pop_int()?); + assert_eq!(3, stack.pop_int()?); + assert_eq!(1, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_dup2_x2_form_3() -> Result<()> { + let stack = &mut OperandStack::with_max_size(4); + stack.push_long(3)?; + stack.push_int(2)?; + stack.push_int(1)?; + let result = dup2_x2(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_int()?); + assert_eq!(2, stack.pop_int()?); + assert_eq!(3, stack.pop_long()?); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } + + #[test] + fn test_dup2_x2_form_4() -> Result<()> { + let stack = &mut OperandStack::with_max_size(3); + stack.push_long(2)?; + stack.push_long(1)?; + let result = dup2_x2(stack)?; + assert_eq!(Continue, result); + assert_eq!(1, stack.pop_long()?); + assert_eq!(2, stack.pop_long()?); + assert_eq!(1, stack.pop_long()?); + Ok(()) + } + + #[test] + fn test_swap() -> Result<()> { + let stack = &mut OperandStack::with_max_size(2); + stack.push_int(2)?; + stack.push_int(1)?; + let result = swap(stack)?; + assert_eq!(Continue, result); + assert_eq!(2, stack.pop_int()?); + assert_eq!(1, stack.pop_int()?); + Ok(()) + } +} diff --git a/ristretto_vm/src/instruction/static.rs b/ristretto_vm/src/instruction/static.rs new file mode 100644 index 00000000..31129aff --- /dev/null +++ b/ristretto_vm/src/instruction/static.rs @@ -0,0 +1,146 @@ +use crate::call_stack::CallStack; +use crate::frame::ExecutionResult; +use crate::frame::ExecutionResult::Continue; +use crate::operand_stack::OperandStack; +use crate::{Result, VM}; +use ristretto_classfile::{ConstantPool, FieldType}; + +/// See: +#[inline] +pub(crate) fn getstatic( + vm: &VM, + call_stack: &mut CallStack, + stack: &mut OperandStack, + constant_pool: &ConstantPool, + index: u16, +) -> Result { + let (class_index, name_and_type_index) = constant_pool.try_get_field_ref(index)?; + let (name_index, _descriptor_index) = + constant_pool.try_get_name_and_type(*name_and_type_index)?; + let class_name = constant_pool.try_get_class(*class_index)?; + let class = vm.class(call_stack, class_name)?; + let field_name = constant_pool.try_get_utf8(*name_index)?; + let field = class.static_field(field_name)?; + let value = field.value()?; + stack.push(value)?; + + if let FieldType::Object(class_name) = field.field_type() { + // Load the class of the field value if it is an object. + // https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-5.html#jvms-5.4.3 + vm.class(call_stack, class_name)?; + } + Ok(Continue) +} + +/// See: +#[inline] +pub(crate) fn putstatic( + vm: &VM, + call_stack: &mut CallStack, + stack: &mut OperandStack, + constant_pool: &ConstantPool, + index: u16, +) -> Result { + let (class_index, name_and_type_index) = constant_pool.try_get_field_ref(index)?; + let (name_index, _descriptor_index) = + constant_pool.try_get_name_and_type(*name_and_type_index)?; + let class_name = constant_pool.try_get_class(*class_index)?; + let class = vm.class(call_stack, class_name)?; + let field_name = constant_pool.try_get_utf8(*name_index)?; + let field = class.static_field(field_name)?; + let value = stack.pop()?; + field.set_value(value)?; + + if let FieldType::Object(class_name) = field.field_type() { + // Load the class of the field value if it is an object. + // https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-5.html#jvms-5.4.3 + vm.class(call_stack, class_name)?; + } + Ok(Continue) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::frame::Frame; + use ristretto_classfile::MethodAccessFlags; + use ristretto_classloader::{Method, Value}; + use std::sync::Arc; + + fn test_class_field( + class_name: &str, + field_name: &str, + field_type: &str, + ) -> Result<(VM, CallStack, Frame, u16, u16)> { + let (vm, _call_stack, mut class) = crate::test::class()?; + let constant_pool = Arc::get_mut(&mut class).expect("class").constant_pool_mut(); + let class_index = constant_pool.add_class(class_name)?; + let field_index = constant_pool.add_field_ref(class_index, field_name, field_type)?; + let method = Method::new( + MethodAccessFlags::STATIC, + "test", + "()V", + 10, + 10, + Vec::new(), + Vec::new(), + )?; + let arguments = Vec::new(); + let call_stack = CallStack::new(); + let frame = Frame::new(&class, &Arc::new(method), arguments)?; + Ok((vm, call_stack, frame, class_index, field_index)) + } + + #[test] + fn test_getstatic() -> Result<()> { + let (vm, mut call_stack, mut frame, _class_index, field_index) = + test_class_field("Constants", "INT_VALUE", "I")?; + let stack = &mut frame.stack; + let constant_pool = frame.class.constant_pool(); + let result = getstatic(&vm, &mut call_stack, stack, constant_pool, field_index)?; + assert_eq!(Continue, result); + let value = frame.stack.pop()?; + assert_eq!(Value::Int(3), value); + Ok(()) + } + + #[test] + fn test_getstatic_field_not_found() -> Result<()> { + let (vm, mut call_stack, mut frame, _class_index, field_index) = + test_class_field("Child", "foo", "I")?; + let stack = &mut frame.stack; + let constant_pool = frame.class.constant_pool(); + let result = getstatic(&vm, &mut call_stack, stack, constant_pool, field_index); + assert!(result.is_err()); + Ok(()) + } + + #[test] + fn test_putstatic() -> Result<()> { + let (vm, mut call_stack, mut frame, _class_index, field_index) = + test_class_field("Simple", "ANSWER", "I")?; + let stack = &mut frame.stack; + let constant_pool = frame.class.constant_pool(); + stack.push_int(3)?; + let result = putstatic(&vm, &mut call_stack, stack, constant_pool, field_index)?; + assert_eq!(Continue, result); + + let result = getstatic(&vm, &mut call_stack, stack, constant_pool, field_index)?; + assert_eq!(Continue, result); + let value = frame.stack.pop()?; + assert_eq!(Value::Int(3), value); + Ok(()) + } + + #[test] + fn test_putstatic_field_not_found() -> Result<()> { + let (vm, mut call_stack, mut frame, _class_index, field_index) = + test_class_field("Child", "foo", "I")?; + let stack = &mut frame.stack; + let constant_pool = frame.class.constant_pool(); + stack.push_int(3)?; + let result = putstatic(&vm, &mut call_stack, stack, constant_pool, field_index); + assert!(result.is_err()); + Ok(()) + } +} diff --git a/ristretto_vm/src/lib.rs b/ristretto_vm/src/lib.rs new file mode 100644 index 00000000..3ee465ca --- /dev/null +++ b/ristretto_vm/src/lib.rs @@ -0,0 +1,49 @@ +//! # Ristretto `ClassLoader` +//! +//! [![Code Coverage](https://codecov.io/gh/theseus-rs/ristretto/branch/main/graph/badge.svg)](https://codecov.io/gh/theseus-rs/ristretto) +//! [![Benchmarks](https://img.shields.io/badge/%F0%9F%90%B0_bencher-enabled-6ec241)](https://bencher.dev/perf/theseus-rs-ristretto) +//! [![License](https://img.shields.io/crates/l/ristretto_classloader)](https://github.com/theseus-rs/ristretto#license) +//! [![Semantic Versioning](https://img.shields.io/badge/%E2%9A%99%EF%B8%8F_SemVer-2.0.0-blue)](https://semver.org/spec/v2.0.0.html) +//! +//! ## Getting Started +//! +//! Implementation of a [JVM Class Loader](https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-4.html) +//! that is used to load Java classes. Classes can be loaded from the file system or from a URL; +//! jar and modules are supported. A runtime Java class loader can be created from any version of +//! [AWS Corretto](https://github.com/corretto). The runtime class loader will download and install +//! the requested version of Corretto into and create a class loader that can be used to load Java +//! classes. +//! +//! ## Safety +//! +//! This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% safe Rust. + +#![forbid(unsafe_code)] +#![forbid(clippy::allow_attributes)] +#![allow(dead_code)] +#![deny(clippy::pedantic)] +#![deny(clippy::unwrap_in_result)] +#![deny(clippy::unwrap_used)] +mod arguments; +mod call_stack; +#[expect(clippy::module_name_repetitions)] +mod configuration; +mod error; +mod frame; +mod instruction; +mod local_variables; +mod native_methods; +mod operand_stack; +#[cfg(test)] +pub(crate) mod test; +mod thread; +mod vm; + +pub(crate) use call_stack::CallStack; +pub use configuration::{Configuration, ConfigurationBuilder}; +pub use error::{Error, Result}; +pub(crate) use frame::Frame; +pub(crate) use local_variables::LocalVariables; +pub(crate) use operand_stack::OperandStack; +pub use ristretto_classloader::{Class, ClassPath, Reference, Value}; +pub use vm::VM; diff --git a/ristretto_vm/src/local_variables.rs b/ristretto_vm/src/local_variables.rs new file mode 100644 index 00000000..9e410aa1 --- /dev/null +++ b/ristretto_vm/src/local_variables.rs @@ -0,0 +1,494 @@ +use crate::Error::{InvalidLocalVariable, InvalidLocalVariableIndex}; +use crate::Result; +use ristretto_classloader::{Reference, Value}; +use std::fmt::Display; + +/// Represents the local variables in a frame. +#[derive(Clone, Debug)] +pub(crate) struct LocalVariables { + locals: Vec, +} + +impl LocalVariables { + /// Create a new local variables with a maximum size. + pub fn with_max_size(max_size: usize) -> Self { + LocalVariables { + locals: vec![Value::Unused; max_size], + } + } + + /// Get a value from the local variables. + /// + /// # Errors + /// if the local variable at the given index was not found. + #[inline] + pub fn get(&self, index: usize) -> Result<&Value> { + match self.locals.get(index) { + Some(value) => Ok(value), + None => Err(InvalidLocalVariableIndex(index)), + } + } + + /// Get an int from the local variables. + /// + /// # Errors + /// if the local variable at the given index was not found or if the value is not an int. + pub fn get_int(&self, index: usize) -> Result { + match self.get(index)? { + Value::Int(value) => Ok(*value), + value => Err(InvalidLocalVariable { + expected: "int".to_string(), + actual: value.to_string(), + }), + } + } + + /// Get a long from the local variables. + /// + /// # Errors + /// if the local variable at the given index was not found or if the value is not a long. + pub fn get_long(&self, index: usize) -> Result { + match self.get(index)? { + Value::Long(value) => Ok(*value), + value => Err(InvalidLocalVariable { + expected: "long".to_string(), + actual: value.to_string(), + }), + } + } + + /// Get a float from the local variables. + /// + /// # Errors + /// if the local variable at the given index was not found or if the value is not a float. + pub fn get_float(&self, index: usize) -> Result { + match self.get(index)? { + Value::Float(value) => Ok(*value), + value => Err(InvalidLocalVariable { + expected: "float".to_string(), + actual: value.to_string(), + }), + } + } + + /// Get a double from the local variables. + /// + /// # Errors + /// if the local variable at the given index was not found or if the value is not a double. + pub fn get_double(&self, index: usize) -> Result { + match self.get(index)? { + Value::Double(value) => Ok(*value), + value => Err(InvalidLocalVariable { + expected: "double".to_string(), + actual: value.to_string(), + }), + } + } + + /// Get a null or object from the local variables. + /// + /// # Errors + /// if the local variable at the given index was not found or if the value is not a null or + /// object. + pub fn get_object(&self, index: usize) -> Result> { + let value = self.get(index)?; + match value { + Value::Object(reference) => Ok(reference.clone()), + value => Err(InvalidLocalVariable { + expected: "object".to_string(), + actual: value.to_string(), + }), + } + } + + /// Set a value in the local variables. + /// + /// # Errors + /// if the index is out of bounds. + #[inline] + pub fn set(&mut self, index: usize, value: Value) -> Result<()> { + if index < self.locals.capacity() { + self.locals[index] = value; + Ok(()) + } else { + Err(InvalidLocalVariableIndex(index)) + } + } + + /// Set an int in the local variables. + /// + /// # Errors + /// if the index is out of bounds or if the value is not an int. + pub fn set_int(&mut self, index: usize, value: i32) -> Result<()> { + self.set(index, Value::Int(value)) + } + + /// Set a long in the local variables. + /// + /// # Errors + /// if the index is out of bounds or if the value is not a long. + pub fn set_long(&mut self, index: usize, value: i64) -> Result<()> { + self.set(index, Value::Long(value)) + } + + /// Set a float in the local variables. + /// + /// # Errors + /// if the index is out of bounds or if the value is not a float. + pub fn set_float(&mut self, index: usize, value: f32) -> Result<()> { + self.set(index, Value::Float(value)) + } + + /// Set a double in the local variables. + /// + /// # Errors + /// if the index is out of bounds or if the value is not a double. + pub fn set_double(&mut self, index: usize, value: f64) -> Result<()> { + self.set(index, Value::Double(value)) + } + + /// Set a null or object in the local variables. + /// + /// # Errors + /// if the index is out of bounds or if the value is not a null or object. + pub fn set_object(&mut self, index: usize, value: Option) -> Result<()> { + self.set(index, Value::Object(value)) + } + + /// Get the length of the local variables. + pub fn len(&self) -> usize { + self.locals + .iter() + .rposition(|value| *value != Value::Unused) + .map_or(0, |index| index + 1) + } + + /// Check if the local variables are empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Display for LocalVariables { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut locals = Vec::new(); + for local in &self.locals { + let value = local.to_string(); + if value.len() > 100 { + locals.push(format!("{}...", &value[..97])); + } else { + locals.push(value); + } + } + write!(f, "[{}]", locals.join(", ")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ristretto_classloader::{ConcurrentVec, Reference}; + + #[test] + fn test_get() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set(0, Value::Int(42))?; + assert_eq!(locals.get(0)?, &Value::Int(42)); + Ok(()) + } + + #[test] + fn test_get_invalid_index() { + let locals = LocalVariables::with_max_size(0); + assert!(matches!(locals.get(0), Err(InvalidLocalVariableIndex(0)))); + } + + #[test] + fn test_get_int() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set(0, Value::Int(42))?; + assert_eq!(locals.get_int(0)?, 42); + Ok(()) + } + + #[test] + fn test_get_int_invalid_index() { + let locals = LocalVariables::with_max_size(0); + assert!(matches!( + locals.get_int(0), + Err(InvalidLocalVariableIndex(0)) + )); + } + + #[test] + fn test_get_int_invalid_type() { + let locals = LocalVariables::with_max_size(1); + assert!(matches!( + locals.get_int(0), + Err(InvalidLocalVariable { + expected, + actual + }) if expected == "int" && actual == "unused" + )); + } + + #[test] + fn test_get_long() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set(0, Value::Long(42))?; + assert_eq!(locals.get_long(0)?, 42); + Ok(()) + } + + #[test] + fn test_get_long_invalid_index() { + let locals = LocalVariables::with_max_size(0); + assert!(matches!( + locals.get_long(0), + Err(InvalidLocalVariableIndex(0)) + )); + } + + #[test] + fn test_get_long_invalid_type() { + let locals = LocalVariables::with_max_size(1); + assert!(matches!( + locals.get_long(0), + Err(InvalidLocalVariable { + expected, + actual + }) if expected == "long" && actual == "unused" + )); + } + + #[test] + fn test_get_float() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set(0, Value::Float(42.1))?; + let value = locals.get_float(0)? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_get_float_invalid_index() { + let locals = LocalVariables::with_max_size(0); + assert!(matches!( + locals.get_float(0), + Err(InvalidLocalVariableIndex(0)) + )); + } + + #[test] + fn test_get_float_invalid_type() { + let locals = LocalVariables::with_max_size(1); + assert!(matches!( + locals.get_float(0), + Err(InvalidLocalVariable { + expected, + actual + }) if expected == "float" && actual == "unused" + )); + } + + #[test] + fn test_get_double() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set(0, Value::Double(42.1))?; + let value = locals.get_double(0)? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_get_double_invalid_index() { + let locals = LocalVariables::with_max_size(0); + assert!(matches!( + locals.get_double(0), + Err(InvalidLocalVariableIndex(0)) + )); + } + + #[test] + fn test_get_double_invalid_type() { + let locals = LocalVariables::with_max_size(1); + assert!(matches!( + locals.get_double(0), + Err(InvalidLocalVariable { + expected, + actual + }) if expected == "double" && actual == "unused" + )); + } + + #[test] + fn test_get_object() -> Result<()> { + let mut locals = LocalVariables::with_max_size(2); + let object = Reference::ByteArray(ConcurrentVec::from(vec![42])); + locals.set_object(0, None)?; + locals.set_object(1, Some(object.clone()))?; + assert_eq!(locals.get_object(0)?, None); + assert_eq!(locals.get_object(1)?, Some(object)); + Ok(()) + } + + #[test] + fn test_get_object_invalid_index() { + let locals = LocalVariables::with_max_size(0); + assert!(matches!( + locals.get_object(0), + Err(InvalidLocalVariableIndex(0)) + )); + } + + #[test] + fn test_get_object_invalid_type() { + let locals = LocalVariables::with_max_size(1); + assert!(matches!( + locals.get_object(0), + Err(InvalidLocalVariable { + expected, + actual + }) if expected == "object" && actual == "unused" + )); + } + + #[test] + fn test_set() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set(0, Value::Int(42))?; + assert_eq!(locals.get(0)?, &Value::Int(42)); + Ok(()) + } + + #[test] + fn test_set_invalid_index() { + let mut locals = LocalVariables::with_max_size(0); + assert!(matches!( + locals.set(0, Value::Object(None)), + Err(InvalidLocalVariableIndex(0)) + )); + } + + #[test] + fn test_set_int() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_int(0, 42)?; + assert_eq!(locals.get_int(0)?, 42); + Ok(()) + } + + #[test] + fn test_set_int_invalid_index() { + let mut locals = LocalVariables::with_max_size(0); + assert!(matches!( + locals.set_int(0, 42), + Err(InvalidLocalVariableIndex(0)) + )); + } + + #[test] + fn test_set_long() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_long(0, 42)?; + assert_eq!(locals.get_long(0)?, 42); + Ok(()) + } + + #[test] + fn test_set_long_invalid_index() { + let mut locals = LocalVariables::with_max_size(0); + assert!(matches!( + locals.set_long(0, 42), + Err(InvalidLocalVariableIndex(0)) + )); + } + + #[test] + fn test_set_float() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_float(0, 42.1)?; + let value = locals.get_float(0)? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_set_float_invalid_index() { + let mut locals = LocalVariables::with_max_size(0); + assert!(matches!( + locals.set_float(0, 42.1), + Err(InvalidLocalVariableIndex(0)) + )); + } + + #[test] + fn test_set_double() -> Result<()> { + let mut locals = LocalVariables::with_max_size(1); + locals.set_double(0, 42.1)?; + let value = locals.get_double(0)? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_set_double_invalid_index() { + let mut locals = LocalVariables::with_max_size(0); + assert!(matches!( + locals.set_double(0, 42.1), + Err(InvalidLocalVariableIndex(0)) + )); + } + + #[test] + fn test_set_object() -> Result<()> { + let mut locals = LocalVariables::with_max_size(2); + let object = Reference::ByteArray(ConcurrentVec::from(vec![42])); + locals.set_object(0, None)?; + locals.set_object(1, Some(object.clone()))?; + assert_eq!(locals.get_object(0)?, None); + assert_eq!(locals.get_object(1)?, Some(object)); + Ok(()) + } + + #[test] + fn test_set_object_invalid_index() { + let mut locals = LocalVariables::with_max_size(0); + assert!(matches!( + locals.set_object(0, None), + Err(InvalidLocalVariableIndex(0)) + )); + } + + #[test] + fn test_len() -> Result<()> { + let mut local_variables = LocalVariables::with_max_size(3); + assert_eq!(local_variables.len(), 0); + local_variables.set(1, Value::Int(42))?; + assert_eq!(local_variables.len(), 2); + Ok(()) + } + + #[test] + fn test_is_empty() -> Result<()> { + let mut local_variables = LocalVariables::with_max_size(3); + assert!(local_variables.is_empty()); + local_variables.set(1, Value::Int(42))?; + assert!(!local_variables.is_empty()); + Ok(()) + } + + #[test] + fn test_display() -> Result<()> { + let mut local_variables = LocalVariables::with_max_size(6); + local_variables.set(0, Value::Int(1))?; + local_variables.set(1, Value::Long(42))?; + local_variables.set(2, Value::Float(2.3))?; + local_variables.set(4, Value::Double(42.1))?; + assert_eq!( + "[int(1), long(42), float(2.3), unused, double(42.1), unused]", + local_variables.to_string() + ); + Ok(()) + } +} diff --git a/ristretto_vm/src/native/java_lang_class.rs b/ristretto_vm/src/native/java_lang_class.rs new file mode 100644 index 00000000..d61384c8 --- /dev/null +++ b/ristretto_vm/src/native/java_lang_class.rs @@ -0,0 +1,20 @@ +use crate::call_stack::CallStack; +use crate::native::registry::NativeRegistry; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for the system class. +pub(crate) fn register(registry: &mut NativeRegistry) { + let class_name = "java/lang/Class"; + registry.register(class_name, "registerNatives", "()V", register_natives); +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn register_natives( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Vec, +) -> Result> { + Ok(None) +} diff --git a/ristretto_vm/src/native/java_lang_object.rs b/ristretto_vm/src/native/java_lang_object.rs new file mode 100644 index 00000000..ffc1a3bc --- /dev/null +++ b/ristretto_vm/src/native/java_lang_object.rs @@ -0,0 +1,22 @@ +use crate::call_stack::CallStack; +use crate::native::registry::NativeRegistry; +use crate::Error::RuntimeError; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for the system class. +pub(crate) fn register(registry: &mut NativeRegistry) { + let class_name = "java/lang/Object"; + registry.register(class_name, "getClass", "()Ljava/lang/Class;", get_class); +} + +#[expect(clippy::needless_pass_by_value)] +fn get_class(vm: &VM, call_stack: &mut CallStack, arguments: Vec) -> Result> { + let Some(Value::Object(Some(reference))) = arguments.first() else { + return Err(RuntimeError("no object reference defined".to_string())); + }; + + let class_name = reference.class_name(); + let class = vm.to_class_value(call_stack, class_name.as_str())?; + Ok(Some(class)) +} diff --git a/ristretto_vm/src/native/java_lang_shutdown.rs b/ristretto_vm/src/native/java_lang_shutdown.rs new file mode 100644 index 00000000..48637f38 --- /dev/null +++ b/ristretto_vm/src/native/java_lang_shutdown.rs @@ -0,0 +1,19 @@ +use crate::call_stack::CallStack; +use crate::native::registry::NativeRegistry; +use crate::Error::RuntimeError; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for the system class. +pub(crate) fn register(registry: &mut NativeRegistry) { + let class_name = "java/lang/Shutdown"; + registry.register(class_name, "halt0", "(I)V", halt0); +} + +#[expect(clippy::needless_pass_by_value)] +fn halt0(_vm: &VM, _call_stack: &mut CallStack, arguments: Vec) -> Result> { + let Some(Value::Int(code)) = arguments.first() else { + return Err(RuntimeError("exit status must be an integer".to_string())); + }; + std::process::exit(*code); +} diff --git a/ristretto_vm/src/native/java_lang_system.rs b/ristretto_vm/src/native/java_lang_system.rs new file mode 100644 index 00000000..57a13d12 --- /dev/null +++ b/ristretto_vm/src/native/java_lang_system.rs @@ -0,0 +1,67 @@ +use crate::call_stack::CallStack; +use crate::native::registry::NativeRegistry; +use crate::Error::RuntimeError; +use crate::{Result, VM}; +use ristretto_classloader::Value; +use std::time::{SystemTime, UNIX_EPOCH}; + +/// Register all native methods for the system class. +pub(crate) fn register(registry: &mut NativeRegistry) { + let class_name = "java/lang/System"; + registry.register(class_name, "currentTimeMillis", "()J", current_time_millis); + registry.register(class_name, "gc", "()V", gc); + registry.register(class_name, "nanoTime", "()J", nano_time); + registry.register(class_name, "registerNatives", "()V", register_natives); +} + +#[expect(clippy::needless_pass_by_value)] +fn current_time_millis( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Vec, +) -> Result> { + let now = SystemTime::now(); + let duration = now + .duration_since(UNIX_EPOCH) + .map_err(|error| RuntimeError(error.to_string()))?; + let time = i64::try_from(duration.as_millis())?; + Ok(Some(Value::Long(time))) +} + +#[expect(clippy::needless_pass_by_value)] +fn exit(_vm: &VM, _call_stack: &mut CallStack, arguments: Vec) -> Result> { + let Some(Value::Int(code)) = arguments.first() else { + return Err(RuntimeError("exit status must be an integer".to_string())); + }; + std::process::exit(*code); +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn gc(_vm: &VM, _call_stack: &mut CallStack, _arguments: Vec) -> Result> { + Ok(None) +} + +#[expect(clippy::needless_pass_by_value)] +fn nano_time( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Vec, +) -> Result> { + let now = SystemTime::now(); + let duration = now + .duration_since(UNIX_EPOCH) + .map_err(|error| RuntimeError(error.to_string()))?; + let time = i64::try_from(duration.as_nanos())?; + Ok(Some(Value::Long(time))) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn register_natives( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Vec, +) -> Result> { + Ok(None) +} diff --git a/ristretto_vm/src/native/mod.rs b/ristretto_vm/src/native/mod.rs new file mode 100644 index 00000000..8ad05765 --- /dev/null +++ b/ristretto_vm/src/native/mod.rs @@ -0,0 +1,7 @@ +mod java_lang_class; +mod java_lang_object; +mod java_lang_shutdown; +mod java_lang_system; +mod registry; + +pub use registry::REGISTRY; diff --git a/ristretto_vm/src/native/registry.rs b/ristretto_vm/src/native/registry.rs new file mode 100644 index 00000000..48c19efc --- /dev/null +++ b/ristretto_vm/src/native/registry.rs @@ -0,0 +1,104 @@ +use crate::call_stack::CallStack; +use crate::native::{java_lang_class, java_lang_object, java_lang_shutdown, java_lang_system}; +use crate::Error::MethodNotFound; +use crate::{Result, VM}; +use lazy_static::lazy_static; +use ristretto_classloader::Value; +use std::collections::HashMap; + +lazy_static! { + pub static ref REGISTRY: NativeRegistry = NativeRegistry::default(); +} + +/// A native method is a method that is implemented in Rust and is called from Java code where the +/// method is marked as `native`. +pub type NativeMethod = + fn(vm: &VM, call_stack: &mut CallStack, arguments: Vec) -> Result>; + +#[expect(clippy::module_name_repetitions)] +#[derive(Debug)] +pub struct NativeRegistry { + methods: HashMap, +} + +impl NativeRegistry { + /// Create a new native registry. + #[must_use] + pub fn new() -> Self { + NativeRegistry { + methods: HashMap::new(), + } + } + + /// Register a new native method. + pub fn register( + &mut self, + class_name: &str, + method_name: &str, + method_descriptor: &str, + method: NativeMethod, + ) { + self.methods.insert( + format!("{class_name}.{method_name}{method_descriptor}"), + method, + ); + } + + /// Get a native method by class and method name. + /// + /// # Errors + /// if the method is not found. + pub fn get( + &self, + class_name: &str, + method_name: &str, + method_descriptor: &str, + ) -> Result<&NativeMethod> { + let native_method_signature = format!("{class_name}.{method_name}{method_descriptor}"); + let Some(method) = self.methods.get(&native_method_signature) else { + return Err(MethodNotFound { + class_name: class_name.to_string(), + method_name: method_name.to_string(), + method_descriptor: method_descriptor.to_string(), + }); + }; + Ok(method) + } +} + +impl Default for NativeRegistry { + fn default() -> Self { + let mut registry = NativeRegistry::new(); + java_lang_class::register(&mut registry); + java_lang_object::register(&mut registry); + java_lang_system::register(&mut registry); + java_lang_shutdown::register(&mut registry); + registry + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_register() { + let mut registry = NativeRegistry::new(); + let method: NativeMethod = |_, _, _| Ok(None); + registry.register("java.lang.Object", "hashCode", "()I", method); + assert_eq!(registry.methods.len(), 1); + } + + #[test] + fn test_get() { + let mut registry = NativeRegistry::new(); + let method: NativeMethod = |_, _, _| Ok(None); + registry.register("java.lang.Object", "hashCode", "()I", method); + + let result = registry.get("java.lang.Object", "hashCode", "()I"); + assert!(result.is_ok()); + + let result = registry.get("foo", "hashCode", "()I"); + assert!(result.is_err()); + } +} diff --git a/ristretto_vm/src/native_methods/java_io_filedescriptor.rs b/ristretto_vm/src/native_methods/java_io_filedescriptor.rs new file mode 100644 index 00000000..8865518d --- /dev/null +++ b/ristretto_vm/src/native_methods/java_io_filedescriptor.rs @@ -0,0 +1,55 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for java.io.FileDescriptor. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "java/io/FileDescriptor"; + registry.register(class_name, "getAppend", "(I)Z", get_append); + registry.register(class_name, "getHandle", "(I)J", get_handle); + registry.register(class_name, "initIDs", "()V", init_ids); +} + +#[expect(clippy::match_same_arms)] +fn get_append( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let handle = arguments.pop_int()?; + let append = match handle { + 0 => { + // true if stdin is in append mode + false + } + 1 => { + // true if stdout is in append mode + false + } + 2 => { + // true if stderr is in append mode + false + } + _ => false, + }; + let append = i32::from(append); + Ok(Some(Value::Int(append))) +} + +fn get_handle( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let handle = arguments.pop_int()?; + let handle = i64::from(handle); + Ok(Some(Value::Long(handle))) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn init_ids(_vm: &VM, _call_stack: &mut CallStack, _arguments: Arguments) -> Result> { + Ok(None) +} diff --git a/ristretto_vm/src/native_methods/java_io_fileinputstream.rs b/ristretto_vm/src/native_methods/java_io_fileinputstream.rs new file mode 100644 index 00000000..ac891c94 --- /dev/null +++ b/ristretto_vm/src/native_methods/java_io_fileinputstream.rs @@ -0,0 +1,17 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for java.io.FileInputStream. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "java/io/FileInputStream"; + registry.register(class_name, "initIDs", "()V", init_ids); +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn init_ids(_vm: &VM, _call_stack: &mut CallStack, _arguments: Arguments) -> Result> { + Ok(None) +} diff --git a/ristretto_vm/src/native_methods/java_io_fileoutputstream.rs b/ristretto_vm/src/native_methods/java_io_fileoutputstream.rs new file mode 100644 index 00000000..9f6630f7 --- /dev/null +++ b/ristretto_vm/src/native_methods/java_io_fileoutputstream.rs @@ -0,0 +1,81 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::Error::RuntimeError; +use crate::{Result, VM}; +use ristretto_classloader::{Reference, Value}; +use std::io::Write; + +/// Register all native methods for java.io.FileOutputStream. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "java/io/FileOutputStream"; + registry.register(class_name, "initIDs", "()V", init_ids); + registry.register(class_name, "writeBytes", "([BIIZ)V", write_bytes); +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn init_ids(_vm: &VM, _call_stack: &mut CallStack, _arguments: Arguments) -> Result> { + Ok(None) +} + +#[expect(clippy::cast_sign_loss)] +fn write_bytes( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let _append = arguments.pop_int()? == 1; + let length = usize::try_from(arguments.pop_int()?)?; + let offset = usize::try_from(arguments.pop_int()?)?; + let Some(Reference::ByteArray(bytes)) = arguments.pop_object()? else { + return Err(RuntimeError( + "Invalid argument type; expected byte[]".to_string(), + )); + }; + let bytes: Vec = bytes.to_vec()?.iter().map(|&x| x as u8).collect(); + let Some(Reference::Object(file_output_stream)) = arguments.pop_object()? else { + return Err(RuntimeError( + "Invalid argument type; expected object".to_string(), + )); + }; + let Value::Object(Some(Reference::Object(file_descriptor))) = + file_output_stream.field("fd")?.value()? + else { + return Err(RuntimeError( + "Invalid argument type; expected object".to_string(), + )); + }; + let Value::Long(handle) = file_descriptor.field("handle")?.value()? else { + return Err(RuntimeError( + "Invalid argument type; expected long".to_string(), + )); + }; + + match handle { + 1 => { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + stdout + .write_all(&bytes[offset..offset + length]) + .map_err(|error| RuntimeError(error.to_string()))?; + stdout + .flush() + .map_err(|error| RuntimeError(error.to_string()))?; + } + 2 => { + let stderr = std::io::stderr(); + let mut stderr = stderr.lock(); + stderr + .write_all(&bytes[offset..offset + length]) + .map_err(|error| RuntimeError(error.to_string()))?; + stderr + .flush() + .map_err(|error| RuntimeError(error.to_string()))?; + } + _ => { + return Err(RuntimeError(format!("Invalid file handle: {handle}"))); + } + } + Ok(None) +} diff --git a/ristretto_vm/src/native_methods/java_lang_class.rs b/ristretto_vm/src/native_methods/java_lang_class.rs new file mode 100644 index 00000000..1ca1cd91 --- /dev/null +++ b/ristretto_vm/src/native_methods/java_lang_class.rs @@ -0,0 +1,100 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::Error::RuntimeError; +use crate::{Result, VM}; +use ristretto_classloader::{Reference, Value}; + +/// Register all native methods for java.lang.Class. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "java/lang/Class"; + registry.register( + class_name, + "desiredAssertionStatus0", + "(Ljava/lang/Class;)Z", + desired_assertion_status_0, + ); + registry.register( + class_name, + "getPrimitiveClass", + "(Ljava/lang/String;)Ljava/lang/Class;", + get_primitive_class, + ); + registry.register(class_name, "isPrimitive", "()Z", is_primitive); + registry.register(class_name, "registerNatives", "()V", register_natives); +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn desired_assertion_status_0( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(Some(Value::Int(0))) +} + +fn get_primitive_class( + vm: &VM, + call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let Some(Reference::Object(primitive)) = arguments.pop_object()? else { + return Err(RuntimeError("getPrimitiveClass: no arguments".to_string())); + }; + + let primitive = primitive.as_string()?; + let class_name = match primitive.as_str() { + "boolean" => "java/lang/Boolean", + "byte" => "java/lang/Byte", + "char" => "java/lang/Character", + "double" => "java/lang/Double", + "float" => "java/lang/Float", + "int" => "java/lang/Integer", + "long" => "java/lang/Long", + "short" => "java/lang/Short", + "void" => "java/lang/Void", + _ => { + return Err(RuntimeError(format!( + "getPrimitiveClass: unrecognized primitive: {primitive}" + ))); + } + }; + + let class = vm.to_class_value(call_stack, class_name)?; + Ok(Some(class)) +} + +fn is_primitive( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let Some(Reference::Object(object)) = arguments.pop_object()? else { + return Err(RuntimeError("isPrimitive: no arguments".to_string())); + }; + let field = object.field("name")?; + let class_name = field.value()?.as_string()?; + match class_name.as_str() { + "java/lang/Boolean" + | "java/lang/Byte" + | "java/lang/Character" + | "java/lang/Double" + | "java/lang/Float" + | "java/lang/Integer" + | "java/lang/Long" + | "java/lang/Short" + | "java/lang/Void" => Ok(Some(Value::Int(1))), + _ => Ok(Some(Value::Int(0))), + } +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn register_natives( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(None) +} diff --git a/ristretto_vm/src/native_methods/java_lang_classloader.rs b/ristretto_vm/src/native_methods/java_lang_classloader.rs new file mode 100644 index 00000000..4b63abdb --- /dev/null +++ b/ristretto_vm/src/native_methods/java_lang_classloader.rs @@ -0,0 +1,21 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for java.lang.ClassLoader. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "java/lang/ClassLoader"; + registry.register(class_name, "registerNatives", "()V", register_natives); +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn register_natives( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(None) +} diff --git a/ristretto_vm/src/native_methods/java_lang_double.rs b/ristretto_vm/src/native_methods/java_lang_double.rs new file mode 100644 index 00000000..6e9f45c8 --- /dev/null +++ b/ristretto_vm/src/native_methods/java_lang_double.rs @@ -0,0 +1,40 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for java.lang.Double. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "java/lang/Double"; + registry.register( + class_name, + "doubleToRawLongBits", + "(D)J", + double_to_raw_long_bits, + ); + registry.register(class_name, "longBitsToDouble", "(J)D", long_bits_to_double); +} + +fn double_to_raw_long_bits( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let double = arguments.pop_double()?; + #[expect(clippy::cast_possible_wrap)] + let bits = double.to_bits() as i64; + Ok(Some(Value::Long(bits))) +} + +fn long_bits_to_double( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let long = arguments.pop_long()?; + #[expect(clippy::cast_sign_loss)] + let bits = long as u64; + let double = f64::from_bits(bits); + Ok(Some(Value::Double(double))) +} diff --git a/ristretto_vm/src/native_methods/java_lang_float.rs b/ristretto_vm/src/native_methods/java_lang_float.rs new file mode 100644 index 00000000..5169d7c4 --- /dev/null +++ b/ristretto_vm/src/native_methods/java_lang_float.rs @@ -0,0 +1,27 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for java.lang.Float. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "java/lang/Float"; + registry.register( + class_name, + "floatToRawIntBits", + "(F)I", + float_to_raw_int_bits, + ); +} + +fn float_to_raw_int_bits( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let float = arguments.pop_float()?; + #[expect(clippy::cast_possible_wrap)] + let bits = float.to_bits() as i32; + Ok(Some(Value::Int(bits))) +} diff --git a/ristretto_vm/src/native_methods/java_lang_object.rs b/ristretto_vm/src/native_methods/java_lang_object.rs new file mode 100644 index 00000000..2455e380 --- /dev/null +++ b/ristretto_vm/src/native_methods/java_lang_object.rs @@ -0,0 +1,84 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::Error::RuntimeError; +use crate::{Result, VM}; +use ristretto_classloader::Value; +use std::hash::{DefaultHasher, Hash, Hasher}; + +/// Register all native methods for java.lang.Object. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "java/lang/Object"; + registry.register(class_name, "", "()V", init); + registry.register(class_name, "clone", "()Ljava/lang/Object;", clone); + registry.register(class_name, "getClass", "()Ljava/lang/Class;", get_class); + registry.register(class_name, "hashCode", "()I", hash_code); + registry.register(class_name, "notifyAll", "()V", notify_all); + registry.register(class_name, "registerNatives", "()V", register_natives); +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn init(_vm: &VM, _call_stack: &mut CallStack, _arguments: Arguments) -> Result> { + // This is a no-op method to optimize Object initialization since it is called frequently. + // This prevents the need to create a new frame and allocate memory unnecessarily for the call + // to the constructor for every object. + Ok(None) +} + +fn clone(_vm: &VM, _call_stack: &mut CallStack, mut arguments: Arguments) -> Result> { + let object = arguments.pop_object()?; + let cloned_object = object.clone(); + Ok(Some(Value::Object(cloned_object))) +} + +fn get_class( + vm: &VM, + call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let Some(object) = arguments.pop_object()? else { + return Err(RuntimeError("no object reference defined".to_string())); + }; + + let class_name = object.class_name(); + let class = vm.to_class_value(call_stack, class_name.as_str())?; + Ok(Some(class)) +} + +fn hash_code( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let Some(object) = arguments.pop_object()? else { + return Err(RuntimeError("no object reference defined".to_string())); + }; + let value = format!("{object}"); + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + let hash_code = hasher.finish(); + #[expect(clippy::cast_possible_truncation)] + let hash_code = hash_code as i32; + Ok(Some(Value::Int(hash_code))) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn notify_all( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(None) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn register_natives( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(None) +} diff --git a/ristretto_vm/src/native_methods/java_lang_runtime.rs b/ristretto_vm/src/native_methods/java_lang_runtime.rs new file mode 100644 index 00000000..b81bd0ce --- /dev/null +++ b/ristretto_vm/src/native_methods/java_lang_runtime.rs @@ -0,0 +1,82 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::{Result, VM}; +use ristretto_classloader::Value; +use sysinfo::System; + +/// Register all native methods for java.lang.Runtime. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "java/lang/Runtime"; + registry.register( + class_name, + "availableProcessors", + "()I", + available_processors, + ); + registry.register(class_name, "freeMemory", "()J", free_memory); + registry.register(class_name, "totalMemory", "()J", total_memory); + registry.register(class_name, "maxMemory", "()J", max_memory); + registry.register(class_name, "gc", "()V", gc); +} + +#[expect(clippy::needless_pass_by_value)] +fn available_processors( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + let sys = System::new_all(); + let cpus = sys.physical_core_count().unwrap_or(1); + let cpus = i32::try_from(cpus)?; + Ok(Some(Value::Int(cpus))) +} + +#[expect(clippy::needless_pass_by_value)] +fn free_memory( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + let sys = System::new_all(); + let free_memory = sys.total_memory() - sys.used_memory(); + let free_memory = if free_memory > u64::try_from(i64::MAX)? { + i64::MAX + } else { + i64::try_from(free_memory)? + }; + Ok(Some(Value::Long(free_memory))) +} + +#[expect(clippy::needless_pass_by_value)] +fn total_memory( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + // TODO: This is not the correct implementation; should be the total memory of the JVM + let sys = System::new_all(); + let used_memory = sys.used_memory(); + let free_memory = if used_memory > u64::try_from(i64::MAX)? { + i64::MAX + } else { + i64::try_from(used_memory)? + }; + Ok(Some(Value::Long(free_memory))) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn max_memory( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(Some(Value::Long(i64::MAX))) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn gc(_vm: &VM, _call_stack: &mut CallStack, _arguments: Arguments) -> Result> { + Ok(None) +} diff --git a/ristretto_vm/src/native_methods/java_lang_shutdown.rs b/ristretto_vm/src/native_methods/java_lang_shutdown.rs new file mode 100644 index 00000000..9c6609b7 --- /dev/null +++ b/ristretto_vm/src/native_methods/java_lang_shutdown.rs @@ -0,0 +1,16 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for java.lang.Shutdown. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "java/lang/Shutdown"; + registry.register(class_name, "halt0", "(I)V", halt0); +} + +fn halt0(_vm: &VM, _call_stack: &mut CallStack, mut arguments: Arguments) -> Result> { + let code = arguments.pop_int()?; + std::process::exit(code); +} diff --git a/ristretto_vm/src/native_methods/java_lang_system.rs b/ristretto_vm/src/native_methods/java_lang_system.rs new file mode 100644 index 00000000..76636cd2 --- /dev/null +++ b/ristretto_vm/src/native_methods/java_lang_system.rs @@ -0,0 +1,322 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::properties; +use crate::native_methods::registry::MethodRegistry; +use crate::Error::RuntimeError; +use crate::{Result, VM}; +use indexmap::IndexMap; +use ristretto_classfile::attributes::Instruction; +use ristretto_classfile::{ClassFile, MethodAccessFlags}; +use ristretto_classloader::{Class, ConcurrentVec, Method, Object, Reference, Value}; +use std::cmp::min; +use std::collections::HashMap; +use std::fmt::Debug; +use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; + +/// Register all native methods for java.lang.System +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "java/lang/System"; + registry.register( + class_name, + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + arraycopy, + ); + registry.register( + class_name, + "allowSecurityManager", + "()Z", + allow_security_manager, + ); + registry.register(class_name, "currentTimeMillis", "()J", current_time_millis); + registry.register(class_name, "gc", "()V", gc); + registry.register( + class_name, + "initProperties", + "(Ljava/util/Properties;)Ljava/util/Properties;", + init_properties, + ); + registry.register(class_name, "nanoTime", "()J", nano_time); + registry.register(class_name, "registerNatives", "()V", register_natives); + registry.register(class_name, "setIn0", "(Ljava/io/InputStream;)V", set_in_0); + registry.register(class_name, "setOut0", "(Ljava/io/PrintStream;)V", set_out_0); + registry.register(class_name, "setErr0", "(Ljava/io/PrintStream;)V", set_err_0); +} + +fn arraycopy_vec( + source: &ConcurrentVec, + source_position: usize, + destination: &ConcurrentVec, + mut destination_position: usize, + length: usize, +) -> Result<()> { + // TODO: optimize this logic to avoid the need for looping + let max_length = min(source_position + length, source.len()?); + for i in source_position..max_length { + let Some(value) = source.get(i)? else { + return Err(RuntimeError("invalid source array index".to_string())); + }; + destination.set(destination_position, value)?; + destination_position += 1; + } + Ok(()) +} + +fn arraycopy( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let length = arguments.pop_int()?; + let destination_position = arguments.pop_int()?; + let Some(destination) = arguments.pop_object()? else { + return Err(RuntimeError("destination must be an object".to_string())); + }; + let source_position = arguments.pop_int()?; + let Some(source) = arguments.pop_object()? else { + return Err(RuntimeError("source must be an object".to_string())); + }; + + let source_position = usize::try_from(source_position)?; + let destination_position = usize::try_from(destination_position)?; + let length = usize::try_from(length)?; + + match (source, destination) { + (Reference::ByteArray(source), Reference::ByteArray(destination)) => { + arraycopy_vec( + &source, + source_position, + &destination, + destination_position, + length, + )?; + } + (Reference::CharArray(source), Reference::CharArray(destination)) => { + arraycopy_vec( + &source, + source_position, + &destination, + destination_position, + length, + )?; + } + (Reference::DoubleArray(source), Reference::DoubleArray(destination)) => { + arraycopy_vec( + &source, + source_position, + &destination, + destination_position, + length, + )?; + } + (Reference::FloatArray(source), Reference::FloatArray(destination)) => { + arraycopy_vec( + &source, + source_position, + &destination, + destination_position, + length, + )?; + } + (Reference::IntArray(source), Reference::IntArray(destination)) => { + arraycopy_vec( + &source, + source_position, + &destination, + destination_position, + length, + )?; + } + (Reference::LongArray(source), Reference::LongArray(destination)) => { + arraycopy_vec( + &source, + source_position, + &destination, + destination_position, + length, + )?; + } + (Reference::ShortArray(source), Reference::ShortArray(destination)) => { + arraycopy_vec( + &source, + source_position, + &destination, + destination_position, + length, + )?; + } + (Reference::Array(_, source), Reference::Array(_, destination)) => { + arraycopy_vec( + &source, + source_position, + &destination, + destination_position, + length, + )?; + } + _ => { + return Err(RuntimeError( + "source and destination must be arrays of the same type".to_string(), + )) + } + }; + Ok(None) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn allow_security_manager( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(Some(Value::Int(0))) +} + +#[expect(clippy::needless_pass_by_value)] +fn current_time_millis( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + let now = SystemTime::now(); + let duration = now + .duration_since(UNIX_EPOCH) + .map_err(|error| RuntimeError(error.to_string()))?; + let time = i64::try_from(duration.as_millis())?; + Ok(Some(Value::Long(time))) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn gc(_vm: &VM, _call_stack: &mut CallStack, _arguments: Arguments) -> Result> { + Ok(None) +} + +/// Mechanism for initializing properties for Java versions <= 8 +fn init_properties( + vm: &VM, + call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let properties = arguments.pop_object()?; + let _system_properties = &mut properties::system(vm, call_stack)?; + // TODO: add system properties to the properties object + Ok(Some(Value::Object(properties))) +} + +#[expect(clippy::needless_pass_by_value)] +fn nano_time( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + let now = SystemTime::now(); + let duration = now + .duration_since(UNIX_EPOCH) + .map_err(|error| RuntimeError(error.to_string()))?; + let time = i64::try_from(duration.as_nanos())?; + Ok(Some(Value::Long(time))) +} + +#[expect(clippy::needless_pass_by_value)] +fn register_natives( + vm: &VM, + call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + // Force the initialization of the system properties; this is required because no security + // manager is installed and when System::initPhase1() is called, the resulting call chain: + // + // System::initPhase1() + // System::setJavaLangAccess() + // SharedSecrets::() + // MethodHandles::() + // MethodHandleStatics::() + // GetPropertyAction.privilegedGetProperties() + // System::getProperties() + // + // will eventually call System::getProperty() which fails if this is not initialized. + let system = vm.class(call_stack, "java/lang/System")?; + let set_properties = system.try_get_method("setProperties", "(Ljava/util/Properties;)V")?; + vm.invoke(&system, &set_properties, vec![Value::Object(None)])?; + + // TODO: remove this once threading is implemented + let jdk_java_lang_ref_access = vm.class(call_stack, "jdk/internal/access/JavaLangRefAccess")?; + let interfaces = vec![jdk_java_lang_ref_access]; + let mut methods = HashMap::new(); + let start_threads = Method::new( + MethodAccessFlags::PUBLIC, + "startThreads", + "()V", + 0, + 1, + vec![Instruction::Return], + vec![], + )?; + methods.insert( + format!("{}:{}", start_threads.name(), start_threads.descriptor()), + Arc::new(start_threads), + ); + let java_lang_ref_access = Arc::new(Class::new( + "ristretto/internal/access/JavaLangRefAccess".to_string(), + None, + ClassFile::default(), + None, + interfaces, + IndexMap::new(), + methods, + )); + vm.register_class(java_lang_ref_access.clone())?; + let java_lang_ref_access = + Value::Object(Some(Reference::Object(Object::new(java_lang_ref_access)?))); + let shared_secrets = vm.class(call_stack, "jdk/internal/access/SharedSecrets")?; + let set_java_lang_ref_access = shared_secrets.try_get_method( + "setJavaLangRefAccess", + "(Ljdk/internal/access/JavaLangRefAccess;)V", + )?; + vm.invoke( + &shared_secrets, + &set_java_lang_ref_access, + vec![java_lang_ref_access], + )?; + + Ok(None) +} + +fn set_in_0( + vm: &VM, + call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let input_stream = arguments.pop_object()?; + let system = vm.class(call_stack, "java/lang/System")?; + let in_field = system.static_field("in")?; + in_field.unsafe_set_value(Value::Object(input_stream))?; + Ok(None) +} + +fn set_out_0( + vm: &VM, + call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let print_stream = arguments.pop_object()?; + let system = vm.class(call_stack, "java/lang/System")?; + let out_field = system.static_field("out")?; + out_field.unsafe_set_value(Value::Object(print_stream))?; + Ok(None) +} + +fn set_err_0( + vm: &VM, + call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let print_stream = arguments.pop_object()?; + let system = vm.class(call_stack, "java/lang/System")?; + let err_field = system.static_field("err")?; + err_field.unsafe_set_value(Value::Object(print_stream))?; + Ok(None) +} diff --git a/ristretto_vm/src/native_methods/java_lang_thread.rs b/ristretto_vm/src/native_methods/java_lang_thread.rs new file mode 100644 index 00000000..abf282f8 --- /dev/null +++ b/ristretto_vm/src/native_methods/java_lang_thread.rs @@ -0,0 +1,70 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::{Result, VM}; +use ristretto_classloader::{Object, Reference, Value}; +use std::thread; +use std::time::Duration; + +/// Register all native methods for java.lang.Thread. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "java/lang/Thread"; + registry.register(class_name, "countStackFrames", "()I", count_stack_frames); + registry.register( + class_name, + "currentThread", + "()Ljava/lang/Thread;", + current_thread, + ); + registry.register(class_name, "registerNatives", "()V", register_natives); + registry.register(class_name, "sleep", "(J)V", sleep); + registry.register(class_name, "yield", "()V", r#yield); +} + +#[expect(clippy::needless_pass_by_value)] +fn count_stack_frames( + _vm: &VM, + call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + let frames = call_stack.frames.len(); + let frames = i32::try_from(frames)?; + Ok(Some(Value::Int(frames))) +} + +#[expect(clippy::needless_pass_by_value)] +fn current_thread( + vm: &VM, + call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + // TODO: correct this once threading is implemented + let thread_class = vm.class(call_stack, "java/lang/Thread")?; + let thread = Value::Object(Some(Reference::Object(Object::new(thread_class)?))); + Ok(Some(thread)) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn register_natives( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(None) +} + +fn sleep(_vm: &VM, _call_stack: &mut CallStack, mut arguments: Arguments) -> Result> { + let millis = arguments.pop_long()?; + let millis = u64::try_from(millis)?; + let duration = Duration::from_millis(millis); + thread::sleep(duration); + Ok(None) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn r#yield(_vm: &VM, _call_stack: &mut CallStack, _arguments: Arguments) -> Result> { + thread::yield_now(); + Ok(None) +} diff --git a/ristretto_vm/src/native_methods/java_lang_throwable.rs b/ristretto_vm/src/native_methods/java_lang_throwable.rs new file mode 100644 index 00000000..8573b3d2 --- /dev/null +++ b/ristretto_vm/src/native_methods/java_lang_throwable.rs @@ -0,0 +1,27 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for java.lang.Throwable. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "java/lang/Throwable"; + registry.register( + class_name, + "fillInStackTrace", + "(I)Ljava/lang/Throwable;", + fill_in_stack_trace, + ); +} + +fn fill_in_stack_trace( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let _dummy = arguments.pop_int()?; + let throwable = arguments.pop_object()?; + // TODO: Implement fillInStackTrace + Ok(Some(Value::Object(throwable))) +} diff --git a/ristretto_vm/src/native_methods/jdk_internal_misc_cds.rs b/ristretto_vm/src/native_methods/jdk_internal_misc_cds.rs new file mode 100644 index 00000000..9e6a4847 --- /dev/null +++ b/ristretto_vm/src/native_methods/jdk_internal_misc_cds.rs @@ -0,0 +1,63 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for jdk.internal.misc.CDS. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "jdk/internal/misc/CDS"; + registry.register( + class_name, + "initializeFromArchive", + "(Ljava/lang/Class;)V", + initialize_from_archive, + ); + registry.register(class_name, "isDumpingArchive0", "()Z", is_dumping_archive_0); + registry.register( + class_name, + "isDumpingClassList0", + "()Z", + is_dumping_class_list_0, + ); + registry.register(class_name, "isSharingEnabled0", "()Z", is_sharing_enabled_0); +} + +fn initialize_from_archive( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let class = arguments.pop_object()?; + Ok(Some(Value::Object(class))) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn is_dumping_archive_0( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(Some(Value::Int(0))) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn is_dumping_class_list_0( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(Some(Value::Int(0))) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn is_sharing_enabled_0( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(Some(Value::Int(0))) +} diff --git a/ristretto_vm/src/native_methods/jdk_internal_misc_scopedmemoryaccess.rs b/ristretto_vm/src/native_methods/jdk_internal_misc_scopedmemoryaccess.rs new file mode 100644 index 00000000..b8ae33f2 --- /dev/null +++ b/ristretto_vm/src/native_methods/jdk_internal_misc_scopedmemoryaccess.rs @@ -0,0 +1,21 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for jdk.internal.misc.ScopedMemoryAccess. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "jdk/internal/misc/ScopedMemoryAccess"; + registry.register(class_name, "registerNatives", "()V", register_natives); +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn register_natives( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(None) +} diff --git a/ristretto_vm/src/native_methods/jdk_internal_misc_signal.rs b/ristretto_vm/src/native_methods/jdk_internal_misc_signal.rs new file mode 100644 index 00000000..c2bf58f0 --- /dev/null +++ b/ristretto_vm/src/native_methods/jdk_internal_misc_signal.rs @@ -0,0 +1,85 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::Error::{InvalidOperand, RuntimeError}; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for jdk.internal.misc.Signal. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "jdk/internal/misc/Signal"; + registry.register( + class_name, + "findSignal0", + "(Ljava/lang/String;)I", + find_signal_0, + ); + registry.register(class_name, "handle0", "(IJ)J", handle_0); +} + +fn handle_0( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let _handler = arguments.pop_long()?; + let _signal = arguments.pop_int()?; + // TODO: implement signal handling + Ok(Some(Value::Long(0))) +} + +fn find_signal_0( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let value = arguments.pop()?; + let signal_name = match value { + Value::Object(_) => value.as_string()?, + value => { + return Err(InvalidOperand { + expected: "object".to_string(), + actual: value.to_string(), + }); + } + }; + + // See: https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/signal.h + let signal = match signal_name.as_str() { + "HUP" => 1, + "INT" => 2, + "QUIT" => 3, + "ILL" => 4, + "TRAP" => 5, + "ABRT" | "IOT" => 6, + "BUS" => 7, + "FPE" => 8, + "KILL" => 9, + "USR1" => 10, + "SEGV" => 11, + "USR2" => 12, + "PIPE" => 13, + "ALRM" => 14, + "TERM" => 15, + "STKFLT" => 16, + "CHLD" => 17, + "CONT" => 18, + "STOP" => 19, + "TSTP" => 20, + "TTIN" => 21, + "TTOU" => 22, + "URG" => 23, + "XCPU" => 24, + "XFSZ" => 25, + "VTALRM" => 26, + "PROF" => 27, + "WINCH" => 28, + "IO" | "POLL" | "LOST" => 29, + "PWR" => 30, + "SYS" | "UNUSED" => 31, + _ => { + return Err(RuntimeError(format!("Unknown signal: {signal_name}"))); + } + }; + Ok(Some(Value::Int(signal))) +} diff --git a/ristretto_vm/src/native_methods/jdk_internal_misc_unsafe.rs b/ristretto_vm/src/native_methods/jdk_internal_misc_unsafe.rs new file mode 100644 index 00000000..d12cafcc --- /dev/null +++ b/ristretto_vm/src/native_methods/jdk_internal_misc_unsafe.rs @@ -0,0 +1,444 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::Error::{InvalidOperand, RuntimeError}; +use crate::{Result, VM}; +use ristretto_classloader::{Reference, Value}; + +/// Register all native methods for jdk.internal.misc.Unsafe. +#[expect(clippy::too_many_lines)] +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "jdk/internal/misc/Unsafe"; + registry.register(class_name, "", "()V", init); + registry.register( + class_name, + "arrayBaseOffset0", + "(Ljava/lang/Class;)I", + array_base_offset_0, + ); + registry.register( + class_name, + "arrayIndexScale0", + "(Ljava/lang/Class;)I", + array_index_scale_0, + ); + registry.register( + class_name, + "compareAndSetInt", + "(Ljava/lang/Object;JII)Z", + compare_and_set_int, + ); + registry.register( + class_name, + "compareAndSetLong", + "(Ljava/lang/Object;JJJ)Z", + compare_and_set_long, + ); + registry.register( + class_name, + "compareAndSetReference", + "(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z", + compare_and_set_reference, + ); + registry.register( + class_name, + "ensureClassInitialized0", + "(Ljava/lang/Class;)V", + ensure_class_initialized_0, + ); + registry.register(class_name, "fullFence", "()V", full_fence); + registry.register( + class_name, + "getReference", + "(Ljava/lang/Object;J)Ljava/lang/Object;", + get_reference, + ); + registry.register( + class_name, + "getReferenceVolatile", + "(Ljava/lang/Object;J)Ljava/lang/Object;", + get_reference_volatile, + ); + registry.register( + class_name, + "objectFieldOffset1", + "(Ljava/lang/Class;Ljava/lang/String;)J", + object_field_offset_1, + ); + registry.register( + class_name, + "putReferenceVolatile", + "(Ljava/lang/Object;JLjava/lang/Object;)V", + put_reference_volatile, + ); + registry.register(class_name, "registerNatives", "()V", register_natives); + + // Delegate get/put type volatile methods + + registry.register( + class_name, + "getBooleanVolatile", + "(Ljava/lang/Object;J)Z", + get_reference_volatile, + ); + registry.register( + class_name, + "putBooleanVolatile", + "(Ljava/lang/Object;JZ)V", + put_reference_volatile, + ); + + registry.register( + class_name, + "getByteVolatile", + "(Ljava/lang/Object;J)B", + get_reference_volatile, + ); + registry.register(class_name, "loadFence", "()V", load_fence); + registry.register( + class_name, + "putByteVolatile", + "(Ljava/lang/Object;JB)V", + put_reference_volatile, + ); + + registry.register( + class_name, + "getCharVolatile", + "(Ljava/lang/Object;J)C", + get_reference_volatile, + ); + registry.register( + class_name, + "putCharVolatile", + "(Ljava/lang/Object;JC)V", + put_reference_volatile, + ); + + registry.register( + class_name, + "getDoubleVolatile", + "(Ljava/lang/Object;J)D", + get_reference_volatile, + ); + registry.register( + class_name, + "putDoubleVolatile", + "(Ljava/lang/Object;JD)V", + put_reference_volatile, + ); + + registry.register( + class_name, + "getFloatVolatile", + "(Ljava/lang/Object;J)F", + get_reference_volatile, + ); + registry.register( + class_name, + "putFloatVolatile", + "(Ljava/lang/Object;JF)V", + put_reference_volatile, + ); + + registry.register( + class_name, + "getIntVolatile", + "(Ljava/lang/Object;J)I", + get_reference_volatile, + ); + registry.register( + class_name, + "putIntVolatile", + "(Ljava/lang/Object;JI)V", + put_reference_volatile, + ); + + registry.register( + class_name, + "getLongVolatile", + "(Ljava/lang/Object;J)J", + get_reference_volatile, + ); + registry.register( + class_name, + "putLongVolatile", + "(Ljava/lang/Object;JJ)V", + put_reference_volatile, + ); + + registry.register( + class_name, + "getShortVolatile", + "(Ljava/lang/Object;J)S", + get_reference_volatile, + ); + registry.register( + class_name, + "putShortVolatile", + "(Ljava/lang/Object;JS)V", + put_reference_volatile, + ); + + registry.register(class_name, "storeFence", "()V", store_fence); +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn init(_vm: &VM, _call_stack: &mut CallStack, _arguments: Arguments) -> Result> { + // Unsafe is a no-op and the class is deprecated; override the default behavior to avoid + // the performance penalty of creating a new frame. + Ok(None) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn array_base_offset_0( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(Some(Value::Int(0))) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn array_index_scale_0( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(Some(Value::Int(1))) +} + +fn compare_and_set_int( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let x = arguments.pop_int()?; + let expected = arguments.pop_int()?; + let offset = arguments.pop_long()?; + let Some(Reference::Object(object)) = arguments.pop_object()? else { + return Err(RuntimeError( + "compareAndSetInt: Invalid reference".to_string(), + )); + }; + let class = object.class(); + let offset = usize::try_from(offset)?; + let field_name = class.field_name(offset)?; + let field = object.field(&field_name)?; + let value = field.value()?.as_int()?; + // TODO: the compare and set operation should be atomic + let result = if value == expected { + field.set_value(Value::Int(x))?; + 1 + } else { + 0 + }; + Ok(Some(Value::Int(result))) +} + +fn compare_and_set_long( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let x = arguments.pop_long()?; + let expected = arguments.pop_long()?; + let offset = arguments.pop_long()?; + let Some(Reference::Object(object)) = arguments.pop_object()? else { + return Err(RuntimeError( + "compareAndSetLong: Invalid reference".to_string(), + )); + }; + let class = object.class(); + let offset = usize::try_from(offset)?; + let field_name = class.field_name(offset)?; + let field = object.field(&field_name)?; + let value = field.value()?.as_long()?; + // TODO: the compare and set operation should be atomic + let result = if value == expected { + field.set_value(Value::Long(x))?; + 1 + } else { + 0 + }; + Ok(Some(Value::Int(result))) +} + +fn compare_and_set_reference( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let x = arguments.pop_object()?; + let expected = arguments.pop_object()?; + let offset = arguments.pop_long()?; + let offset = usize::try_from(offset)?; + let Some(object) = arguments.pop_object()? else { + return Err(RuntimeError( + "compareAndSetReference: Invalid reference".to_string(), + )); + }; + let result = match object { + Reference::Array(_class, array) => { + let Some(reference) = array.get(offset)? else { + return Err(RuntimeError( + "getReference: Invalid reference index".to_string(), + )); + }; + // TODO: the compare and set operation should be atomic + if reference == expected { + array.set(offset, x)?; + 1 + } else { + 0 + } + } + _ => { + return Err(RuntimeError("getReference: Invalid reference".to_string())); + } + }; + Ok(Some(Value::Int(result))) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn ensure_class_initialized_0( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(None) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn full_fence( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(None) +} + +fn get_reference( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let offset = arguments.pop_long()?; + let offset = usize::try_from(offset)?; + let Some(reference) = arguments.pop_object()? else { + return Err(RuntimeError("getReference: Invalid reference".to_string())); + }; + match reference { + Reference::Array(_class, array) => { + let Some(reference) = array.get(offset)? else { + return Err(RuntimeError( + "getReference: Invalid reference index".to_string(), + )); + }; + Ok(Some(Value::Object(reference))) + } + Reference::Object(object) => { + let field_name = object.class().field_name(offset)?; + let field = object.field(&field_name)?; + let value = field.value()?; + Ok(Some(value)) + } + _ => Err(RuntimeError("getReference: Invalid reference".to_string())), + } +} + +#[inline] +fn get_reference_volatile( + vm: &VM, + call_stack: &mut CallStack, + arguments: Arguments, +) -> Result> { + get_reference(vm, call_stack, arguments) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn load_fence( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(None) +} + +fn object_field_offset_1( + vm: &VM, + call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let value = arguments.pop()?; + let field_name = match value { + Value::Object(_) => value.as_string()?, + value => { + return Err(InvalidOperand { + expected: "object".to_string(), + actual: value.to_string(), + }); + } + }; + let Some(Reference::Object(class_object)) = arguments.pop_object()? else { + return Err(RuntimeError( + "objectFieldOffset1: Invalid class reference".to_string(), + )); + }; + let name_field = class_object.field("name")?; + let class_name = name_field.value()?.as_string()?; + let class = vm.class(call_stack, &class_name)?; + let offset = class.field_offset(&field_name)?; + let offset = i64::try_from(offset)?; + Ok(Some(Value::Long(offset))) +} + +fn put_reference_volatile( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let x = arguments.pop_object()?; + let offset = arguments.pop_long()?; + let offset = usize::try_from(offset)?; + let Some(object) = arguments.pop_object()? else { + return Err(RuntimeError( + "compareAndSetReference: Invalid reference".to_string(), + )); + }; + match object { + Reference::Array(_class, array) => { + array.set(offset, x)?; + } + _ => { + return Err(RuntimeError("getReference: Invalid reference".to_string())); + } + } + Ok(None) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn register_natives( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(None) +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn store_fence( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(None) +} diff --git a/ristretto_vm/src/native_methods/jdk_internal_misc_vm.rs b/ristretto_vm/src/native_methods/jdk_internal_misc_vm.rs new file mode 100644 index 00000000..28d2b418 --- /dev/null +++ b/ristretto_vm/src/native_methods/jdk_internal_misc_vm.rs @@ -0,0 +1,21 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for jdk.internal.misc.VM. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "jdk/internal/misc/VM"; + registry.register(class_name, "initialize", "()V", initialize); +} + +#[expect(clippy::needless_pass_by_value)] +#[expect(clippy::unnecessary_wraps)] +fn initialize( + _vm: &VM, + _call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + Ok(None) +} diff --git a/ristretto_vm/src/native_methods/jdk_internal_util_systemprops_raw.rs b/ristretto_vm/src/native_methods/jdk_internal_util_systemprops_raw.rs new file mode 100644 index 00000000..24c45083 --- /dev/null +++ b/ristretto_vm/src/native_methods/jdk_internal_util_systemprops_raw.rs @@ -0,0 +1,139 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::properties; +use crate::native_methods::registry::MethodRegistry; +use crate::Error::RuntimeError; +use crate::{Result, VM}; +use ristretto_classfile::Version; +use ristretto_classloader::{ConcurrentVec, Reference, Value}; +use std::collections::HashMap; + +const JAVA_19: Version = Version::Java19 { minor: 0 }; + +/// Register all native methods for jdk.internal.util.SystemProps$Raw. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "jdk/internal/util/SystemProps$Raw"; + registry.register( + class_name, + "platformProperties", + "()[Ljava/lang/String;", + platform_properties, + ); + registry.register( + class_name, + "vmProperties", + "()[Ljava/lang/String;", + vm_properties, + ); +} + +#[expect(clippy::needless_pass_by_value)] +fn platform_properties( + vm: &VM, + call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + let string_class = vm.class(call_stack, "java/lang/String")?; + let system_properties = &mut properties::system(vm, call_stack)?; + let java_version = vm.java_version(); + + // VM properties must be returned in a specific order as they are accessed by array index. + let mut properties: Vec> = Vec::new(); + push_property(system_properties, &mut properties, "user.country")?; + push_property(system_properties, &mut properties, "user.language")?; + push_property(system_properties, &mut properties, "user.script")?; + push_property(system_properties, &mut properties, "user.variant")?; + push_property(system_properties, &mut properties, "native.encoding")?; + push_property(system_properties, &mut properties, "file.separator")?; + push_property(system_properties, &mut properties, "format.country")?; + push_property(system_properties, &mut properties, "format.language")?; + push_property(system_properties, &mut properties, "format.script")?; + push_property(system_properties, &mut properties, "format.variant")?; + push_property(system_properties, &mut properties, "ftp.nonProxyHosts")?; + push_property(system_properties, &mut properties, "ftp.proxyHost")?; + push_property(system_properties, &mut properties, "ftp.proxyPort")?; + push_property(system_properties, &mut properties, "http.nonProxyHosts")?; + push_property(system_properties, &mut properties, "http.proxyHost")?; + push_property(system_properties, &mut properties, "http.proxyPort")?; + push_property(system_properties, &mut properties, "https.proxyHost")?; + push_property(system_properties, &mut properties, "https.proxyPort")?; + push_property(system_properties, &mut properties, "java.io.tmpdir")?; + push_property(system_properties, &mut properties, "line.separator")?; + push_property(system_properties, &mut properties, "os.arch")?; + push_property(system_properties, &mut properties, "os.name")?; + push_property(system_properties, &mut properties, "os.version")?; + push_property(system_properties, &mut properties, "path.separator")?; + push_property(system_properties, &mut properties, "socksNonProxyHosts")?; + push_property(system_properties, &mut properties, "socksProxyHost")?; + push_property(system_properties, &mut properties, "socksProxyPort")?; + if java_version >= &JAVA_19 { + push_property(system_properties, &mut properties, "stderr.encoding")?; + push_property(system_properties, &mut properties, "stdout.encoding")?; + } + push_property(system_properties, &mut properties, "sun.arch.abi")?; + push_property(system_properties, &mut properties, "sun.arch.data.model")?; + push_property(system_properties, &mut properties, "sun.cpu.endian")?; + push_property(system_properties, &mut properties, "sun.cpu.isalist")?; + push_property( + system_properties, + &mut properties, + "sun.io.unicode.encoding", + )?; + push_property(system_properties, &mut properties, "sun.jnu.encoding")?; + push_property(system_properties, &mut properties, "sun.os.patch.level")?; + if java_version < &JAVA_19 { + push_property(system_properties, &mut properties, "sun.stderr.encoding")?; + push_property(system_properties, &mut properties, "sun.stdout.encoding")?; + } + push_property(system_properties, &mut properties, "user.dir")?; + push_property(system_properties, &mut properties, "user.home")?; + push_property(system_properties, &mut properties, "user.name")?; + + let properties = ConcurrentVec::from(properties); + let result = Value::Object(Some(Reference::Array(string_class, properties))); + Ok(Some(result)) +} + +fn push_property( + system_properties: &mut HashMap<&str, Value>, + properties: &mut Vec>, + property_name: &str, +) -> Result<()> { + let Some(Value::Object(value)) = system_properties.remove(property_name) else { + return Err(RuntimeError(format!("Property not found: {property_name}"))); + }; + properties.push(value); + Ok(()) +} + +#[expect(clippy::needless_pass_by_value)] +fn vm_properties( + vm: &VM, + call_stack: &mut CallStack, + _arguments: Arguments, +) -> Result> { + let string_class = vm.class(call_stack, "java/lang/String")?; + // TODO: Implement platform command properties (e.g. -Dkey=value) + let mut platform_properties = HashMap::new(); + platform_properties.insert("java.home", String::new()); + + let mut properties: Vec> = Vec::new(); + for (key, value) in platform_properties { + let Value::Object(key) = vm.to_string_value(call_stack, key)? else { + return Err(RuntimeError(format!( + "Unable to convert key to string: {key}" + ))); + }; + properties.push(key); + let Value::Object(value) = vm.to_string_value(call_stack, value.as_str())? else { + return Err(RuntimeError(format!( + "Unable to convert value to string: {value}" + ))); + }; + properties.push(value); + } + + let properties = ConcurrentVec::from(properties); + let result = Value::Object(Some(Reference::Array(string_class, properties))); + Ok(Some(result)) +} diff --git a/ristretto_vm/src/native_methods/mod.rs b/ristretto_vm/src/native_methods/mod.rs new file mode 100644 index 00000000..6592dcb5 --- /dev/null +++ b/ristretto_vm/src/native_methods/mod.rs @@ -0,0 +1,24 @@ +mod java_io_filedescriptor; +mod java_io_fileinputstream; +mod java_io_fileoutputstream; +mod java_lang_class; +mod java_lang_classloader; +mod java_lang_double; +mod java_lang_float; +mod java_lang_object; +mod java_lang_runtime; +mod java_lang_shutdown; +mod java_lang_system; +mod java_lang_thread; +mod java_lang_throwable; +mod jdk_internal_misc_cds; +mod jdk_internal_misc_scopedmemoryaccess; +mod jdk_internal_misc_signal; +mod jdk_internal_misc_unsafe; +mod jdk_internal_misc_vm; +mod jdk_internal_util_systemprops_raw; +pub(crate) mod properties; +mod registry; +mod sun_io_win32errormode; + +pub use registry::registry; diff --git a/ristretto_vm/src/native_methods/properties.rs b/ristretto_vm/src/native_methods/properties.rs new file mode 100644 index 00000000..a84e5c67 --- /dev/null +++ b/ristretto_vm/src/native_methods/properties.rs @@ -0,0 +1,179 @@ +use crate::call_stack::CallStack; +use crate::Error::RuntimeError; +use crate::{Result, VM}; +use ristretto_classloader::Value; +use std::collections::HashMap; +use std::env; +use std::env::consts::{ARCH, OS}; +use std::path::MAIN_SEPARATOR_STR; + +/// Get the system properties. +pub(crate) fn system(vm: &VM, call_stack: &mut CallStack) -> Result> { + let system_properties = system_properties(vm)?; + let mut properties = HashMap::new(); + for (key, value) in system_properties { + let value = vm.to_string_value(call_stack, &value)?; + properties.insert(key, value); + } + Ok(properties) +} + +#[expect(clippy::too_many_lines)] +fn system_properties(vm: &VM) -> Result> { + let mut properties = HashMap::new(); + let class_file_version = vm.java_version(); + let major_java_version = class_file_version.java(); + let major_class_version = class_file_version.major(); + let minor_class_version = class_file_version.minor(); + let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")); + let locale_parts = locale.split('-').collect::>(); + let language = *locale_parts.first().unwrap_or(&"en"); + let country = *locale_parts.get(1).unwrap_or(&""); + + properties.insert("file.encoding", "UTF-8".to_string()); + properties.insert("file.separator", MAIN_SEPARATOR_STR.to_string()); + + properties.insert("format.country", country.to_string()); + properties.insert("format.language", language.to_string()); + // TODO: implement format.script + properties.insert("format.script", String::new()); + // TODO: implement format.variant + properties.insert("format.variant", String::new()); + + // TODO: implement ftp.nonProxyHosts + properties.insert("ftp.nonProxyHosts", String::new()); + // TODO: implement ftp.proxyHost + properties.insert("ftp.proxyHost", String::new()); + // TODO: implement ftp.proxyPort + properties.insert("ftp.proxyPort", String::new()); + + // TODO: implement http.nonProxyHosts + properties.insert("http.nonProxyHosts", String::new()); + // TODO: implement http.proxyHost + properties.insert("http.proxyHost", String::new()); + // TODO: implement http.proxyPort + properties.insert("http.proxyPort", String::new()); + // TODO: implement https.proxyHost + properties.insert("https.proxyHost", String::new()); + // TODO: implement https.proxyPort + properties.insert("https.proxyPort", String::new()); + + let class_path = vm.configuration().class_path().to_string(); + properties.insert("java.class.path", class_path); + properties.insert( + "java.class.version", + format!("{major_class_version}.{minor_class_version}"), + ); + properties.insert("java.compiler", "no JIT".to_string()); + // TODO: implement java.ext.dirs + properties.insert("java.ext.dirs", String::new()); + // TODO: implement java.home + properties.insert("java.home", String::new()); + + let tmp_dir = env::temp_dir(); + properties.insert("java.io.tmpdir", format!("{}", tmp_dir.to_string_lossy())); + + // TODO: implement java.library.path + properties.insert("java.library.path", String::new()); + properties.insert( + "java.specification.name", + "Java Platform API Specification".to_string(), + ); + properties.insert( + "java.specification.vendor", + "Oracle Corporation".to_string(), + ); + // TODO: implement java.specification.maintenance.version + properties.insert("java.specification.maintenance.version", String::new()); + properties.insert("java.vendor", "ristretto".to_string()); + properties.insert( + "java.vendor.url", + "https://github.com/theseus-rs/ristretto".to_string(), + ); + let vm_version = env!("CARGO_PKG_VERSION"); + properties.insert("java.vendor.version", vm_version.to_string()); + let java_version = vm.runtime_version(); + properties.insert("java.version", java_version.to_string()); + let architecture_bits = usize::BITS; + let vm_name = + format!("ristretto {vm_version} (Java {java_version}) {architecture_bits}-bit VM"); + properties.insert("java.vm.name", vm_name); + properties.insert( + "java.vm.specification.name", + "Java Virtual Machine Specification".to_string(), + ); + properties.insert( + "java.vm.specification.vendor", + "Oracle and Ristretto".to_string(), + ); + properties.insert( + "java.vm.specification.version", + format!("{major_java_version}"), + ); + properties.insert("java.vm.vendor", "ristretto".to_string()); + properties.insert("java.vm.version", vm_version.to_string()); + + #[cfg(not(target_os = "windows"))] + properties.insert("line.separator", "\n".to_string()); + #[cfg(target_os = "windows")] + properties.insert("line.separator", "\r\n".to_string()); + + properties.insert("native.encoding", "UTF8".to_string()); + + properties.insert("os.arch", ARCH.to_string()); + let os = match OS { + "linux" => "Linux", + "macos" => "Mac OS X", + "windows" => "Windows", + _ => OS, + }; + properties.insert("os.name", os.to_string()); + + let os_information = os_info::get(); + let os_version = format!("{}", os_information.version()); + properties.insert("os.version", os_version); + + #[cfg(not(target_os = "windows"))] + properties.insert("path.separator", ":".to_string()); + #[cfg(target_os = "windows")] + properties.insert("path.separator", ";".to_string()); + + // TODO: implement socksNonProxyHosts + properties.insert("socksNonProxyHosts", String::new()); + // TODO: implement socksProxyHost + properties.insert("socksProxyHost", String::new()); + // TODO: implement socksProxyPort + properties.insert("socksProxyPort", String::new()); + + properties.insert("stderr.encoding", "UTF-8".to_string()); + properties.insert("stdout.encoding", "UTF-8".to_string()); + + // TODO: implement sun.arch.abi + properties.insert("sun.arch.abi", String::new()); + properties.insert("sun.arch.data.model", format!("{architecture_bits}")); + #[cfg(target_endian = "little")] + properties.insert("sun.cpu.endian", "little".to_string()); + #[cfg(target_endian = "big")] + properties.insert("sun.cpu.endian", "big".to_string()); + // TODO: implement sun.cpu.isalist + properties.insert("sun.cpu.isalist", String::new()); + properties.insert("sun.io.unicode.encoding", "UnicodeBig".to_string()); + properties.insert("sun.jnu.encoding", "UTF-8".to_string()); + // TODO: implement sun.os.patch.level + properties.insert("sun.os.patch.level", String::new()); + properties.insert("sun.stderr.encoding", "UTF-8".to_string()); + properties.insert("sun.stdout.encoding", "UTF-8".to_string()); + + properties.insert("user.country", country.to_string()); + let current_dir = env::current_dir().map_err(|error| RuntimeError(error.to_string()))?; + properties.insert("user.dir", format!("{}", current_dir.to_string_lossy())); + let home_dir = dirs::home_dir().unwrap_or_default(); + properties.insert("user.home", format!("{}", home_dir.to_string_lossy())); + properties.insert("user.language", language.to_string()); + properties.insert("user.name", whoami::username()); + // TODO: implement user.script + properties.insert("user.script", String::new()); + // TODO: implement user.variant + properties.insert("user.variant", String::new()); + Ok(properties) +} diff --git a/ristretto_vm/src/native_methods/registry.rs b/ristretto_vm/src/native_methods/registry.rs new file mode 100644 index 00000000..a4fb6895 --- /dev/null +++ b/ristretto_vm/src/native_methods/registry.rs @@ -0,0 +1,124 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::{ + java_io_filedescriptor, java_io_fileinputstream, java_io_fileoutputstream, java_lang_class, + java_lang_classloader, java_lang_double, java_lang_float, java_lang_object, java_lang_runtime, + java_lang_shutdown, java_lang_system, java_lang_thread, java_lang_throwable, + jdk_internal_misc_cds, jdk_internal_misc_scopedmemoryaccess, jdk_internal_misc_signal, + jdk_internal_misc_unsafe, jdk_internal_misc_vm, jdk_internal_util_systemprops_raw, + sun_io_win32errormode, +}; +use crate::{Result, VM}; +use ristretto_classloader::Value; +use std::collections::HashMap; +use std::sync::OnceLock; +use tracing::debug; + +/// Lazy static reference to the registry. +pub fn registry() -> &'static MethodRegistry { + static REGISTRY: OnceLock = OnceLock::new(); + REGISTRY.get_or_init(MethodRegistry::default) +} + +/// A Rust method is a method that is implemented in Rust and is called from Java code instead of +/// being implemented in Java byte code. +pub type RustMethod = + fn(vm: &VM, call_stack: &mut CallStack, arguments: Arguments) -> Result>; + +#[expect(clippy::module_name_repetitions)] +#[derive(Debug)] +pub struct MethodRegistry { + methods: HashMap, +} + +impl MethodRegistry { + /// Create a new registry. + #[must_use] + pub fn new() -> Self { + MethodRegistry { + methods: HashMap::new(), + } + } + + /// Register a new Rust method. + pub fn register( + &mut self, + class_name: &str, + method_name: &str, + method_descriptor: &str, + method: RustMethod, + ) { + self.methods.insert( + format!("{class_name}.{method_name}{method_descriptor}"), + method, + ); + } + + /// Get a Rust method by class and method name. + /// + /// # Errors + /// if the method is not found. + pub fn get( + &self, + class_name: &str, + method_name: &str, + method_descriptor: &str, + ) -> Option<&RustMethod> { + let method_signature = format!("{class_name}.{method_name}{method_descriptor}"); + self.methods.get(&method_signature) + } +} + +impl Default for MethodRegistry { + fn default() -> Self { + debug!("configuring default method registry"); + let mut registry = MethodRegistry::new(); + java_io_filedescriptor::register(&mut registry); + java_io_fileinputstream::register(&mut registry); + java_io_fileoutputstream::register(&mut registry); + java_lang_class::register(&mut registry); + java_lang_classloader::register(&mut registry); + java_lang_double::register(&mut registry); + java_lang_float::register(&mut registry); + java_lang_object::register(&mut registry); + java_lang_runtime::register(&mut registry); + java_lang_system::register(&mut registry); + java_lang_shutdown::register(&mut registry); + java_lang_thread::register(&mut registry); + java_lang_throwable::register(&mut registry); + jdk_internal_misc_cds::register(&mut registry); + jdk_internal_misc_scopedmemoryaccess::register(&mut registry); + jdk_internal_misc_signal::register(&mut registry); + jdk_internal_misc_unsafe::register(&mut registry); + jdk_internal_misc_vm::register(&mut registry); + jdk_internal_util_systemprops_raw::register(&mut registry); + sun_io_win32errormode::register(&mut registry); + registry + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_register() { + let mut registry = MethodRegistry::new(); + let method: RustMethod = |_, _, _| Ok(None); + registry.register("java.lang.Object", "hashCode", "()I", method); + assert_eq!(registry.methods.len(), 1); + } + + #[test] + fn test_get() { + let mut registry = MethodRegistry::new(); + let method: RustMethod = |_, _, _| Ok(None); + registry.register("java.lang.Object", "hashCode", "()I", method); + + let result = registry.get("java.lang.Object", "hashCode", "()I"); + assert!(result.is_some()); + + let result = registry.get("foo", "hashCode", "()I"); + assert!(result.is_none()); + } +} diff --git a/ristretto_vm/src/native_methods/sun_io_win32errormode.rs b/ristretto_vm/src/native_methods/sun_io_win32errormode.rs new file mode 100644 index 00000000..90bc86e3 --- /dev/null +++ b/ristretto_vm/src/native_methods/sun_io_win32errormode.rs @@ -0,0 +1,20 @@ +use crate::arguments::Arguments; +use crate::call_stack::CallStack; +use crate::native_methods::registry::MethodRegistry; +use crate::{Result, VM}; +use ristretto_classloader::Value; + +/// Register all native methods for sun.io.Win32ErrorMode. +pub(crate) fn register(registry: &mut MethodRegistry) { + let class_name = "sun/io/Win32ErrorMode"; + registry.register(class_name, "setErrorMode", "(J)J", set_error_mode); +} + +fn set_error_mode( + _vm: &VM, + _call_stack: &mut CallStack, + mut arguments: Arguments, +) -> Result> { + let _error_mode = arguments.pop_long()?; + Ok(Some(Value::Long(0))) +} diff --git a/ristretto_vm/src/operand_stack.rs b/ristretto_vm/src/operand_stack.rs new file mode 100644 index 00000000..f65a9915 --- /dev/null +++ b/ristretto_vm/src/operand_stack.rs @@ -0,0 +1,348 @@ +use crate::Error::{InvalidOperand, OperandStackOverflow, OperandStackUnderflow}; +use crate::Result; +use ristretto_classloader::{Reference, Value}; +use std::fmt::Display; + +/// Operand stack for the Ristretto VM +/// +/// See: +#[derive(Clone, Debug)] +pub(crate) struct OperandStack { + stack: Vec, +} + +impl OperandStack { + /// Create a new operand stack with a maximum size. + pub fn with_max_size(max_size: usize) -> Self { + OperandStack { + stack: Vec::with_capacity(max_size), + } + } + + /// Push a value onto the operand stack. + #[inline] + pub fn push(&mut self, value: Value) -> Result<()> { + if self.stack.len() >= self.stack.capacity() { + return Err(OperandStackOverflow); + } + self.stack.push(value); + Ok(()) + } + + /// Push an int value onto the operand stack. + pub fn push_int(&mut self, value: i32) -> Result<()> { + self.push(Value::Int(value)) + } + + /// Push a long value onto the operand stack. + pub fn push_long(&mut self, value: i64) -> Result<()> { + self.push(Value::Long(value)) + } + + /// Push a float value onto the operand stack. + pub fn push_float(&mut self, value: f32) -> Result<()> { + self.push(Value::Float(value)) + } + + /// Push a double value onto the operand stack. + pub fn push_double(&mut self, value: f64) -> Result<()> { + self.push(Value::Double(value)) + } + + /// Push a reference onto the operand stack. + pub fn push_object(&mut self, value: Option) -> Result<()> { + self.push(Value::Object(value)) + } + + /// Pop a value from the operand stack. + #[inline] + pub fn pop(&mut self) -> Result { + let Some(value) = self.stack.pop() else { + return Err(OperandStackUnderflow); + }; + Ok(value) + } + + /// Pop an int from the operand stack. + pub fn pop_int(&mut self) -> Result { + match self.pop()? { + Value::Int(value) => Ok(value), + value => Err(InvalidOperand { + expected: "int".to_string(), + actual: value.to_string(), + }), + } + } + + /// Pop a long from the operand stack. + pub fn pop_long(&mut self) -> Result { + let value = match self.pop()? { + Value::Long(value) => value, + value => { + return Err(InvalidOperand { + expected: "long".to_string(), + actual: value.to_string(), + }); + } + }; + Ok(value) + } + + /// Pop a float from the operand stack. + pub fn pop_float(&mut self) -> Result { + match self.pop()? { + Value::Float(value) => Ok(value), + value => Err(InvalidOperand { + expected: "float".to_string(), + actual: value.to_string(), + }), + } + } + + /// Pop a double from the operand stack. + pub fn pop_double(&mut self) -> Result { + let value = match self.pop()? { + Value::Double(value) => value, + value => { + return Err(InvalidOperand { + expected: "double".to_string(), + actual: value.to_string(), + }); + } + }; + Ok(value) + } + + /// Pop a null or object from the operand stack. + pub fn pop_object(&mut self) -> Result> { + let value = self.pop()?; + match value { + Value::Object(reference) => Ok(reference), + value => Err(InvalidOperand { + expected: "object".to_string(), + actual: value.to_string(), + }), + } + } + + /// Peek at the top value on the operand stack. + pub fn peek(&self) -> Result<&Value> { + let Some(value) = self.stack.last() else { + return Err(OperandStackUnderflow); + }; + Ok(value) + } + + /// Get the number of values on the operand stack. + pub fn len(&self) -> usize { + self.stack.len() + } + + /// Check if the operand stack is empty. + pub fn is_empty(&self) -> bool { + self.stack.is_empty() + } +} + +impl Display for OperandStack { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut stack = Vec::new(); + for stack_entry in &self.stack { + let value = stack_entry.to_string(); + if value.len() > 100 { + stack.push(format!("{}...", &value[..97])); + } else { + stack.push(value); + } + } + write!(f, "[{}]", stack.join(", ")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::operand_stack::OperandStack; + use ristretto_classloader::{ConcurrentVec, Reference}; + + #[test] + fn test_can_push_and_pop_values() -> Result<()> { + let mut stack = OperandStack::with_max_size(2); + stack.push_int(1)?; + stack.push_int(2)?; + + assert_eq!(stack.len(), 2); + + assert_eq!(stack.pop()?, Value::Int(2)); + assert_eq!(stack.pop()?, Value::Int(1)); + Ok(()) + } + + #[test] + fn test_pop_int() -> Result<()> { + let mut stack = OperandStack::with_max_size(1); + stack.push_int(42)?; + assert_eq!(stack.pop_int()?, 42); + Ok(()) + } + + #[test] + fn test_pop_int_invalid_operand() -> Result<()> { + let mut stack = OperandStack::with_max_size(1); + stack.push_object(None)?; + assert!(matches!( + stack.pop_int(), + Err(InvalidOperand { + expected, + actual + }) if expected == "int" && actual == "object(null)" + )); + Ok(()) + } + + #[test] + fn test_pop_long() -> Result<()> { + let mut stack = OperandStack::with_max_size(2); + stack.push_long(42)?; + assert_eq!(stack.pop_long()?, 42); + Ok(()) + } + + #[test] + fn test_pop_long_invalid_operand() -> Result<()> { + let mut stack = OperandStack::with_max_size(2); + stack.push_double(42.1)?; + assert!(matches!( + stack.pop_long(), + Err(InvalidOperand { + expected, + actual + }) if expected == "long" && actual == "double(42.1)" + )); + Ok(()) + } + + #[test] + fn test_pop_float() -> Result<()> { + let mut stack = OperandStack::with_max_size(1); + stack.push_float(42.1)?; + let value = stack.pop_float()? - 42.1f32; + assert!(value.abs() < 0.1f32); + Ok(()) + } + + #[test] + fn test_pop_float_invalid_operand() -> Result<()> { + let mut stack = OperandStack::with_max_size(1); + stack.push_object(None)?; + assert!(matches!( + stack.pop_float(), + Err(InvalidOperand { + expected, + actual + }) if expected == "float" && actual == "object(null)" + )); + Ok(()) + } + + #[test] + fn test_pop_double() -> Result<()> { + let mut stack = OperandStack::with_max_size(2); + stack.push_double(42.1)?; + let value = stack.pop_double()? - 42.1f64; + assert!(value.abs() < 0.1f64); + Ok(()) + } + + #[test] + fn test_pop_double_invalid_operand() -> Result<()> { + let mut stack = OperandStack::with_max_size(2); + stack.push_long(42)?; + assert!(matches!( + stack.pop_double(), + Err(InvalidOperand { + expected, + actual + }) if expected == "double" && actual == "long(42)" + )); + Ok(()) + } + + #[test] + fn test_pop_object() -> Result<()> { + let mut stack = OperandStack::with_max_size(2); + let object = Reference::ByteArray(ConcurrentVec::from(vec![42])); + stack.push_object(None)?; + stack.push_object(Some(object.clone()))?; + assert_eq!(stack.pop_object()?, Some(object)); + assert_eq!(stack.pop_object()?, None); + Ok(()) + } + + #[test] + fn test_pop_object_invalid_operand() -> Result<()> { + let mut stack = OperandStack::with_max_size(1); + stack.push_int(42)?; + assert!(matches!( + stack.pop_object(), + Err(InvalidOperand { + expected, + actual + }) if expected == "object" && actual == "int(42)" + )); + Ok(()) + } + + #[test] + fn test_pop_underflow() { + let mut stack = OperandStack::with_max_size(1); + let result = stack.pop(); + assert!(matches!(result, Err(OperandStackUnderflow))); + } + + #[test] + fn test_push_overflow() -> Result<()> { + let mut stack = OperandStack::with_max_size(1); + stack.push_int(42)?; + let result = stack.push_int(43); + assert!(matches!(result, Err(OperandStackOverflow))); + Ok(()) + } + + #[test] + fn test_peek_top_value() -> Result<()> { + let mut stack = OperandStack::with_max_size(2); + stack.push_int(1)?; + stack.push_int(2)?; + + assert_eq!(stack.peek()?, &Value::Int(2)); + assert_eq!(stack.len(), 2); + Ok(()) + } + + #[test] + fn test_peek_underflow() { + let stack = OperandStack::with_max_size(1); + let result = stack.peek(); + assert!(matches!(result, Err(OperandStackUnderflow))); + } + + #[test] + fn test_is_empty() -> Result<()> { + let mut stack = OperandStack::with_max_size(1); + assert!(stack.is_empty()); + + stack.push_int(42)?; + assert!(!stack.is_empty()); + Ok(()) + } + + #[test] + fn test_display() -> Result<()> { + let mut stack = OperandStack::with_max_size(4); + stack.push_int(1)?; + stack.push_int(2)?; + assert_eq!("[int(1), int(2)]", stack.to_string()); + Ok(()) + } +} diff --git a/ristretto_vm/src/test.rs b/ristretto_vm/src/test.rs new file mode 100644 index 00000000..60a433fa --- /dev/null +++ b/ristretto_vm/src/test.rs @@ -0,0 +1,58 @@ +use crate::frame::Frame; +use crate::{CallStack, Class, ConfigurationBuilder, Result, VM}; +use ristretto_classfile::{ClassFile, ConstantPool, MethodAccessFlags}; +use ristretto_classloader::{ClassPath, Method}; +use std::path::PathBuf; +use std::sync::Arc; + +/// Get the specific class for testing. +pub(crate) fn load_class(class_name: &str) -> Result<(VM, CallStack, Arc)> { + let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let classes_path = cargo_manifest.join("../classes"); + let class_path = ClassPath::from(classes_path.to_string_lossy()); + let configuration = ConfigurationBuilder::new() + .class_path(class_path.clone()) + .build(); + let vm = VM::new(configuration)?; + let mut call_stack = CallStack::new(); + let class = vm.class(&mut call_stack, class_name)?; + Ok((vm, call_stack, class)) +} + +/// Get a test class for testing. +pub(crate) fn class() -> Result<(VM, CallStack, Arc)> { + let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let classes_path = cargo_manifest.join("../classes"); + let class_path = ClassPath::from(classes_path.to_string_lossy()); + let configuration = ConfigurationBuilder::new() + .class_path(class_path.clone()) + .build(); + let vm = VM::new(configuration)?; + let call_stack = CallStack::new(); + let mut constant_pool = ConstantPool::default(); + let this_class = constant_pool.add_class("Test")?; + let class_file = ClassFile { + constant_pool, + this_class, + ..Default::default() + }; + let class = Class::from(class_file)?; + Ok((vm, call_stack, Arc::new(class))) +} + +/// Get a test frame for testing. +pub(crate) fn frame() -> Result<(VM, CallStack, Frame)> { + let (vm, call_stack, class) = class()?; + let method = Method::new( + MethodAccessFlags::STATIC, + "test", + "()V", + 10, + 10, + Vec::new(), + Vec::new(), + )?; + let arguments = Vec::new(); + let frame = Frame::new(&class, &Arc::new(method), arguments)?; + Ok((vm, call_stack, frame)) +} diff --git a/ristretto_vm/src/thread.rs b/ristretto_vm/src/thread.rs new file mode 100644 index 00000000..2245ca58 --- /dev/null +++ b/ristretto_vm/src/thread.rs @@ -0,0 +1,13 @@ +use crate::CallStack; + +struct Thread { + call_stack: CallStack, +} + +impl Thread { + fn new() -> Self { + Thread { + call_stack: CallStack::new(), + } + } +} diff --git a/ristretto_vm/src/vm.rs b/ristretto_vm/src/vm.rs new file mode 100644 index 00000000..7b4a0352 --- /dev/null +++ b/ristretto_vm/src/vm.rs @@ -0,0 +1,513 @@ +use crate::call_stack::CallStack; +use crate::Error::{PoisonedLock, UnsupportedClassFileVersion}; +use crate::{Configuration, ConfigurationBuilder, Result}; +use ristretto_classfile::{mutf8, Version}; +use ristretto_classloader::manifest::MAIN_CLASS; +use ristretto_classloader::Error::ParseError; +use ristretto_classloader::Reference::{ByteArray, CharArray}; +use ristretto_classloader::{ + runtime, Class, ClassLoader, ClassPath, ClassPathEntry, ConcurrentVec, Method, Object, + Reference, Value, +}; +use std::sync::{Arc, RwLock}; +use tracing::debug; + +const JAVA_8: Version = Version::Java8 { minor: 0 }; + +/// Java Virtual Machine +#[derive(Debug)] +pub struct VM { + configuration: Configuration, + class_loader: RwLock, + main_class: Option, + runtime_version: String, + java_version: Version, +} + +/// VM +impl VM { + /// The offset to add to the major version to get the class file version. Java 1.0 has a class + /// file major version of 45, so the class file major version is the Java version (1) + the + /// class file offset version (44) = the Java 1 class file version (45). + const CLASS_FILE_MAJOR_VERSION_OFFSET: u16 = 44; + + /// Create a new VM + /// + /// # Errors + /// if the VM cannot be created + pub fn new(configuration: Configuration) -> Result { + let runtime_version = configuration.runtime_version(); + debug!("runtime_version {runtime_version}"); + + let major_version: u16 = runtime_version.split('.').next().unwrap_or("0").parse()?; + let java_version = Version::from(major_version + Self::CLASS_FILE_MAJOR_VERSION_OFFSET, 0)?; + debug!("Java version {java_version}"); + + let (runtime_version, boostrap_class_loader) = runtime::class_loader(runtime_version)?; + let boostrap_class_loader = boostrap_class_loader; + + // TODO: implement extension class loader + // /jre/lib/ext directory or any other directory specified by the java.ext.dirs + // system property + + let class_path = configuration.class_path().clone(); + let mut system_class_loader = ClassLoader::new("system", class_path); + system_class_loader.set_parent(Some(boostrap_class_loader.clone())); + let mut main_class_name = configuration.main_class(); + + let class_loader = if let Some(jar) = configuration.jar() { + let path = jar.to_string_lossy(); + let jar_class_path = ClassPath::from(path); + let mut jar_class_loader = ClassLoader::new("jar", jar_class_path); + jar_class_loader.set_parent(Some(system_class_loader.clone())); + + // If the main class is not specified, try to get it from the jar manifest file + if main_class_name.is_none() { + for class_path_entry in jar_class_loader.class_path().iter() { + if let ClassPathEntry::Jar(jar) = class_path_entry { + let manifest = jar.manifest()?; + if let Some(jar_main_class) = manifest.attribute(MAIN_CLASS) { + main_class_name = Some(jar_main_class.to_string()); + break; + }; + } + } + } + + jar_class_loader + } else { + system_class_loader.clone() + }; + debug!("classloader: {class_loader}"); + + let main_class = if let Some(main_class_name) = main_class_name { + debug!("main class: {main_class_name}"); + Some(main_class_name) + } else { + None + }; + + let vm = Self { + configuration, + class_loader: RwLock::new(class_loader), + main_class, + runtime_version, + java_version, + }; + vm.initialize()?; + Ok(vm) + } + + /// Get the configuration + #[must_use] + pub fn configuration(&self) -> &Configuration { + &self.configuration + } + + /// Get the main class + #[must_use] + pub fn main_class(&self) -> Option<&String> { + self.main_class.as_ref() + } + + /// Get the version + #[must_use] + pub fn runtime_version(&self) -> &str { + &self.runtime_version + } + + /// Get the Java version + #[must_use] + pub fn java_version(&self) -> &Version { + &self.java_version + } + + /// Initialize the VM + /// + /// # Errors + /// if the VM cannot be initialized + fn initialize(&self) -> Result<()> { + let system_class = self.load("java.lang.System")?; + + if self.java_version <= JAVA_8 { + let initialize_system_class_method = + system_class.try_get_method("initializeSystemClass", "()V")?; + self.invoke(&system_class, &initialize_system_class_method, vec![])?; + } else { + let init_phase1_method = system_class.try_get_method("initPhase1", "()V")?; + self.invoke(&system_class, &init_phase1_method, vec![])?; + + // TODO: Implement System::initPhase2() + // let init_phase2_method = system_class.try_get_method("initPhase2", "(ZZ)I")?; + // let phase2_result = self.invoke( + // &system_class, + // &init_phase2_method, + // vec![Value::Int(1), Value::Int(1)], + // )?; + // let Some(Value::Int(result)) = phase2_result else { + // return Err(RuntimeError(format!( + // "System::initPhase2() call failed: {phase2_result:?}" + // ))); + // }; + // if result != 0 { + // return Err(RuntimeError(format!( + // "System::initPhase2() call failed: {result}" + // ))); + // } + + // TODO: Implement System::initPhase3() + // let init_phase3_method = system_class.try_get_method("initPhase3", "()V")?; + // self.invoke(&system_class, &init_phase3_method, vec![])?; + } + + Ok(()) + } + + /// Load a class (e.g. "java.lang.Object"). + /// + /// # Errors + /// if the class cannot be loaded + pub fn load(&self, class_name: &str) -> Result> { + let class_name = class_name.replace('.', "/"); + let call_stack = &mut CallStack::new(); + self.class(call_stack, &class_name) + } + + /// Get a class. + /// + /// See: + /// + /// # Errors + /// if the class cannot be loaded + pub(crate) fn class(&self, call_stack: &mut CallStack, class_name: &str) -> Result> { + let class_load_result = { + let class_loader = self + .class_loader + .read() + .map_err(|error| PoisonedLock(error.to_string()))?; + class_loader.load_with_status(class_name) + }; + + let class = match class_load_result { + Ok((class, previously_loaded)) => { + if previously_loaded { + return Ok(class); + } + class + } + Err(error) => { + if class_name.starts_with('[') { + let array_class = Arc::new(Class::new_array(class_name)?); + // Register the array class so that it will be available for future lookups. + self.register_class(array_class.clone())?; + array_class + } else { + return Err(error.into()); + } + } + }; + + let classes = self.prepare_class_initialization(&class)?; + for current_class in classes { + if let Some(class_initializer) = current_class.class_initializer() { + call_stack.execute(self, ¤t_class, &class_initializer, vec![])?; + } + } + Ok(class) + } + + /// Prepare class initialization. + /// + /// # Errors + /// if the class cannot be resolved + fn prepare_class_initialization(&self, class: &Arc) -> Result>> { + let mut classes = Vec::new(); + let class_loader = self + .class_loader + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + let mut current_class = class.clone(); + loop { + if current_class.class_file().version > self.java_version { + return Err(UnsupportedClassFileVersion( + current_class.class_file().version.major(), + )); + } + let super_class_index = current_class.class_file().super_class; + let super_class_name = if super_class_index == 0 { + "java/lang/Object" + } else { + let constant_pool = current_class.constant_pool(); + constant_pool.try_get_class(super_class_index)? + }; + + classes.push(current_class.clone()); + let current_class_name = current_class.name(); + if current_class_name == "java/lang/Object" { + break; + } + + let mut interfaces = Vec::new(); + for interface_index in ¤t_class.class_file().interfaces { + let interface_name = current_class + .constant_pool() + .try_get_class(*interface_index)?; + let interface_class = class_loader.load(interface_name)?; + interfaces.push(interface_class); + } + classes.extend(interfaces.clone()); + current_class.set_interfaces(interfaces)?; + + let (super_class, previously_loaded) = + class_loader.load_with_status(super_class_name)?; + current_class.set_parent(Some(super_class.clone()))?; + + if previously_loaded { + break; + } + + current_class = super_class; + } + // Classes are discovered from the top of the hierarchy to the bottom. However, the class + // initialization order is from the bottom to the top. Reverse the classes so that the + // classes are initialized from the bottom to the top. + classes.reverse(); + Ok(classes) + } + + /// Register a class. + /// + /// # Errors + /// if the class cannot be registered + pub(crate) fn register_class(&self, class: Arc) -> Result<()> { + debug!("register class: {class}"); + let mut class_loader = self + .class_loader + .write() + .map_err(|error| PoisonedLock(error.to_string()))?; + class_loader.register(class)?; + Ok(()) + } + + /// Invoke a method. To invoke a method on an object reference, the object reference must be + /// the first argument in the arguments vector. + /// + /// # Errors + /// if the method cannot be invoked + pub fn invoke( + &self, + class: &Arc, + method: &Arc, + arguments: Vec, + ) -> Result> { + let call_stack = &mut CallStack::new(); + call_stack.execute(self, class, method, arguments) + } + + /// Create a new java.lang.Class object as a VM Value. + /// + /// # Errors + /// if the class object cannot be created + pub(crate) fn to_class_value( + &self, + call_stack: &mut CallStack, + class_name: &str, + ) -> Result { + let object_class_name = "java/lang/Class"; + let class = self.class(call_stack, object_class_name)?; + let object = Object::new(class)?; + let name = self.to_string_value(call_stack, class_name)?; + let name_field = object.field("name")?; + name_field.set_value(name)?; + // TODO: a "null" class loader indicates a system class loader; this should be re-evaluated + // to support custom class loaders + let class_loader_field = object.field("classLoader")?; + class_loader_field.unsafe_set_value(Value::Object(None))?; + let reference = Reference::Object(object); + let value = Value::Object(Some(reference)); + Ok(value) + } + + /// Create a new string object. + /// + /// # Errors + /// if the string object cannot be created + pub fn string>(&self, value: S) -> Result { + let call_stack = &mut CallStack::new(); + let value = value.as_ref(); + self.to_string_value(call_stack, value) + } + + /// Create a new java.lang.String object as a VM Value. + /// + /// # Errors + /// if the string object cannot be created + pub(crate) fn to_string_value(&self, call_stack: &mut CallStack, value: &str) -> Result { + let class_name = "java/lang/String"; + let class = self.class(call_stack, class_name)?; + let object = Object::new(class)?; + + // The String implementation changed in Java 9. + // In Java 8 and earlier, the value field is a char array. + // In Java 9 and later, the value field is a byte array. + let array = if self.java_version <= JAVA_8 { + let bytes = mutf8::to_bytes(value)?; + let utf8_string = + String::from_utf8(bytes).map_err(|error| ParseError(error.to_string()))?; + let ucs2_chars: Vec = utf8_string.encode_utf16().collect(); + let chars = ConcurrentVec::from(ucs2_chars); + CharArray(chars) + } else { + let coder_field = object.field("coder")?; + coder_field.set_value(Value::Int(0))?; // LATIN1 + + let bytes = mutf8::to_bytes(value)?; + #[expect(clippy::cast_possible_wrap)] + let bytes = bytes.iter().map(|&b| b as i8).collect(); + let bytes = ConcurrentVec::from(bytes); + ByteArray(bytes) + }; + + let value_field = object.field("value")?; + value_field.set_value(Value::Object(Some(array)))?; + + let hash_field = object.field("hash")?; + hash_field.set_value(Value::Int(0))?; + + let reference = Reference::Object(object); + let value = Value::Object(Some(reference)); + Ok(value) + } +} + +impl Default for VM { + fn default() -> Self { + let configuration = ConfigurationBuilder::default().build(); + VM::new(configuration).expect("VM") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::configuration::{ConfigurationBuilder, DEFAULT_RUNTIME_VERSION}; + use ristretto_classloader::ClassPath; + use std::path::PathBuf; + + fn classes_jar_path() -> PathBuf { + let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + cargo_manifest.join("../classes/classes.jar") + } + + fn classes_jar_class_path() -> ClassPath { + let classes_jar_path = classes_jar_path(); + ClassPath::from(classes_jar_path.to_string_lossy()) + } + + fn test_vm() -> Result { + let class_path = classes_jar_class_path(); + let configuration = ConfigurationBuilder::new() + .class_path(class_path.clone()) + .build(); + VM::new(configuration) + } + + #[test] + fn test_vm_new() -> Result<()> { + let vm = test_vm()?; + assert!(vm + .configuration + .class_path() + .to_string() + .contains("classes.jar")); + assert_eq!(DEFAULT_RUNTIME_VERSION, vm.runtime_version()); + assert!(vm.main_class().is_none()); + Ok(()) + } + + #[test] + fn test_vm_set_main_class() -> Result<()> { + let class_path = classes_jar_class_path(); + let configuration = ConfigurationBuilder::new() + .class_path(class_path.clone()) + .main_class("HelloWorld") + .build(); + let vm = VM::new(configuration)?; + let main_class = vm.main_class().expect("main class"); + assert_eq!("HelloWorld", main_class); + Ok(()) + } + + #[test] + fn test_vm_set_jar_with_main_class() -> Result<()> { + let classes_jar_path = classes_jar_path(); + let configuration = ConfigurationBuilder::new().jar(classes_jar_path).build(); + let vm = VM::new(configuration)?; + let main_class = vm.main_class().expect("main class"); + assert_eq!("HelloWorld", main_class); + Ok(()) + } + + #[test] + fn test_vm_load_java_lang_object() -> Result<()> { + let vm = test_vm()?; + let class = vm.load("java.lang.Object")?; + assert_eq!("java/lang/Object", class.name()); + Ok(()) + } + + #[test] + fn test_hello_world_class() -> Result<()> { + let vm = test_vm()?; + let call_stack = &mut CallStack::new(); + let class = vm.class(call_stack, "HelloWorld")?; + assert_eq!("HelloWorld", class.name()); + Ok(()) + } + + #[test] + fn test_constants_class() -> Result<()> { + let vm = test_vm()?; + let call_stack = &mut CallStack::new(); + let class = vm.class(call_stack, "Constants")?; + assert_eq!("Constants", class.name()); + Ok(()) + } + + #[test] + fn test_class_inheritance() -> Result<()> { + let vm = test_vm()?; + let call_stack = &mut CallStack::new(); + let child_class = vm.class(call_stack, "Child")?; + assert_eq!("Child", child_class.name()); + + let parent_class = child_class.parent()?.expect("parent"); + assert_eq!("Parent", parent_class.name()); + + let grandparent_class = parent_class.parent()?.expect("grand parent"); + assert_eq!("GrandParent", grandparent_class.name()); + + let object_class = grandparent_class.parent()?.expect("object"); + assert_eq!("java/lang/Object", object_class.name()); + + assert!(object_class.parent()?.is_none()); + Ok(()) + } + + #[test] + fn test_invoke_main_method() -> Result<()> { + let class_path = classes_jar_class_path(); + let configuration = ConfigurationBuilder::new() + .class_path(class_path.clone()) + .main_class("HelloWorld") + .build(); + let vm = VM::new(configuration)?; + let main_class_name = vm.main_class().expect("main class"); + let main_class = vm.load(main_class_name)?; + let main_method = main_class.main_method().expect("main method"); + let arguments = vec![Value::Object(None)]; + let result = vm.invoke(&main_class, &main_method, arguments)?; + assert!(result.is_none()); + Ok(()) + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..bbf217f2 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.81.0" +profile = "default"