cargo-perf 0.6.0

Preventive performance analysis for Rust - catch anti-patterns before production
Documentation
name: 'Cargo Perf'
description: 'Static analysis for Rust performance anti-patterns and async correctness'
author: 'cargo-perf'

branding:
  icon: 'zap'
  color: 'orange'

inputs:
  path:
    description: 'Path to analyze (defaults to current directory)'
    required: false
    default: '.'
  fail-on-error:
    description: 'Fail the action if any errors are found'
    required: false
    default: 'true'
  fail-on-warning:
    description: 'Fail the action if any warnings are found'
    required: false
    default: 'false'
  sarif:
    description: 'Generate SARIF output and upload to GitHub Code Scanning'
    required: false
    default: 'true'
  version:
    description: 'Version of cargo-perf to install (defaults to latest)'
    required: false
    default: 'latest'
  token:
    description: 'GitHub token for uploading SARIF results'
    required: false
    default: ${{ github.token }}

outputs:
  issues-found:
    description: 'Number of issues found'
    value: ${{ steps.analyze.outputs.issues-found }}
  errors-found:
    description: 'Number of errors found'
    value: ${{ steps.analyze.outputs.errors-found }}
  warnings-found:
    description: 'Number of warnings found'
    value: ${{ steps.analyze.outputs.warnings-found }}

runs:
  using: 'composite'
  steps:
    - name: Install Rust
      uses: dtolnay/rust-toolchain@stable

    - name: Validate inputs
      shell: bash
      env:
        INPUT_PATH: ${{ inputs.path }}
        INPUT_VERSION: ${{ inputs.version }}
      run: |
        # Validate path - only allow safe characters (strict allowlist)
        if [[ ! "$INPUT_PATH" =~ ^[a-zA-Z0-9_./-]+$ ]]; then
          echo "::error::Invalid path format. Only alphanumeric characters, dots, underscores, hyphens, and slashes are allowed."
          exit 1
        fi

        # Prevent paths that could be interpreted as flags
        if [[ "$INPUT_PATH" =~ ^- ]]; then
          echo "::error::Path cannot start with a hyphen (could be interpreted as a flag)."
          exit 1
        fi

        # Prevent path traversal attempts
        if [[ "$INPUT_PATH" =~ \.\. ]]; then
          echo "::error::Path cannot contain '..' (path traversal not allowed)."
          exit 1
        fi

        # Verify path exists within the workspace
        if [[ ! -e "$INPUT_PATH" ]]; then
          echo "::error::Path does not exist: $INPUT_PATH"
          exit 1
        fi

        # Validate version - must be 'latest' or semver format
        if [ "$INPUT_VERSION" != "latest" ]; then
          if [[ ! "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
            echo "::error::Invalid version format. Use 'latest' or semver (e.g., '0.5.6')."
            exit 1
          fi
        fi

        # Store validated path for subsequent steps
        echo "VALIDATED_PATH=$INPUT_PATH" >> $GITHUB_ENV
        echo "Inputs validated successfully"

    - name: Cache cargo-perf
      uses: actions/cache@v4
      with:
        path: ~/.cargo/bin/cargo-perf
        key: cargo-perf-${{ inputs.version }}-${{ runner.os }}

    - name: Install cargo-perf
      shell: bash
      env:
        INPUT_VERSION: ${{ inputs.version }}
      run: |
        if [ "$INPUT_VERSION" = "latest" ]; then
          cargo install cargo-perf --locked
        else
          cargo install cargo-perf --version "$INPUT_VERSION" --locked
        fi

    - name: Run analysis
      id: analyze
      shell: bash
      env:
        INPUT_SARIF: ${{ inputs.sarif }}
        INPUT_FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
        INPUT_FAIL_ON_WARNING: ${{ inputs.fail-on-warning }}
        # Maximum SARIF output size (10 MB) to prevent disk exhaustion
        MAX_SARIF_SIZE: 10485760
      run: |
        set +e

        # Run cargo-perf and capture output (using validated path from env)
        if [ "$INPUT_SARIF" = "true" ]; then
          # Limit SARIF output size to prevent disk exhaustion
          cargo perf check "$VALIDATED_PATH" --format sarif 2>&1 | head -c "$MAX_SARIF_SIZE" > results.sarif
          EXIT_CODE=${PIPESTATUS[0]}

          # Check if output was truncated
          SARIF_SIZE=$(stat -f%z results.sarif 2>/dev/null || stat -c%s results.sarif 2>/dev/null || echo "0")
          if [ "$SARIF_SIZE" -ge "$MAX_SARIF_SIZE" ]; then
            echo "::warning::SARIF output was truncated (exceeded ${MAX_SARIF_SIZE} bytes)"
          fi
        else
          OUTPUT=$(cargo perf check "$VALIDATED_PATH" --format json 2>&1)
          EXIT_CODE=$?
        fi

        # Parse results for GitHub output
        if [ "$INPUT_SARIF" = "true" ] && [ -f results.sarif ]; then
          # Count issues from SARIF
          ERRORS=$(jq '[.runs[0].results[] | select(.level == "error")] | length' results.sarif 2>/dev/null || echo "0")
          WARNINGS=$(jq '[.runs[0].results[] | select(.level == "warning")] | length' results.sarif 2>/dev/null || echo "0")
          TOTAL=$((ERRORS + WARNINGS))
        else
          ERRORS=0
          WARNINGS=0
          TOTAL=0
        fi

        echo "issues-found=$TOTAL" >> $GITHUB_OUTPUT
        echo "errors-found=$ERRORS" >> $GITHUB_OUTPUT
        echo "warnings-found=$WARNINGS" >> $GITHUB_OUTPUT

        # Display summary
        echo "### Cargo Perf Results" >> $GITHUB_STEP_SUMMARY
        echo "" >> $GITHUB_STEP_SUMMARY
        echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY
        echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
        echo "| Errors | $ERRORS |" >> $GITHUB_STEP_SUMMARY
        echo "| Warnings | $WARNINGS |" >> $GITHUB_STEP_SUMMARY
        echo "| **Total** | **$TOTAL** |" >> $GITHUB_STEP_SUMMARY

        # Determine exit code based on settings
        SHOULD_FAIL=0
        if [ "$INPUT_FAIL_ON_ERROR" = "true" ] && [ "$ERRORS" -gt 0 ]; then
          SHOULD_FAIL=1
        fi
        if [ "$INPUT_FAIL_ON_WARNING" = "true" ] && [ "$WARNINGS" -gt 0 ]; then
          SHOULD_FAIL=1
        fi

        # Save SARIF even if we're going to fail
        if [ "$SHOULD_FAIL" -eq 1 ]; then
          echo "::error::cargo-perf found issues that exceed threshold"
        fi

        exit 0  # Don't fail yet, let SARIF upload first

    - name: Upload SARIF results
      if: inputs.sarif == 'true' && always()
      uses: github/codeql-action/upload-sarif@v3
      with:
        sarif_file: results.sarif
        token: ${{ inputs.token }}
      continue-on-error: true

    - name: Check failure threshold
      shell: bash
      env:
        ERRORS: ${{ steps.analyze.outputs.errors-found }}
        WARNINGS: ${{ steps.analyze.outputs.warnings-found }}
        INPUT_FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
        INPUT_FAIL_ON_WARNING: ${{ inputs.fail-on-warning }}
      run: |
        if [ "$INPUT_FAIL_ON_ERROR" = "true" ] && [ "$ERRORS" -gt 0 ]; then
          echo "::error::Found $ERRORS error(s)"
          exit 1
        fi
        if [ "$INPUT_FAIL_ON_WARNING" = "true" ] && [ "$WARNINGS" -gt 0 ]; then
          echo "::error::Found $WARNINGS warning(s)"
          exit 1
        fi