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 }}
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