Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,115 @@ env:
CARGO_TERM_COLOR: always

jobs:
gates:
name: Release Gates (Rust + Extension + Analyzer)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Ensure checklist exists
run: test -f docs/release-checklist.md

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt

- name: Rust Cache
uses: Swatinem/rust-cache@v2

- name: Check Formatting
run: cargo fmt --all -- --check

- name: Run Clippy (deny warnings)
run: cargo clippy --workspace --all-targets --all-features -- -D warnings

- name: Run Tests
run: cargo test --workspace --all-features

- name: Build Debug Binary (for extension smoke tests)
run: cargo build

- name: Security Analyzer Sanity (static)
run: cargo run --quiet --bin soroban-debug -- analyze --contract tests/fixtures/wasm/echo.wasm --format json

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: extensions/vscode/package-lock.json

- name: VS Code Extension Install
working-directory: extensions/vscode
run: npm ci

- name: VS Code Extension Compile
working-directory: extensions/vscode
run: npm run -s compile

- name: VS Code Extension Tests
working-directory: extensions/vscode
env:
SOROBAN_DEBUG_BIN: ${{ github.workspace }}/target/debug/soroban-debug
run: npm test

- name: Checklist link
run: |
echo "Release checklist: docs/release-checklist.md" >> "$GITHUB_STEP_SUMMARY"

bench:
name: Benchmark Sanity (thresholded)
runs-on: ubuntu-latest
needs: gates
env:
BENCH_WARN_PCT: 10
BENCH_FAIL_PCT: 20
BENCH_SAMPLE_SIZE: 20
BENCH_MEASUREMENT_TIME: 5
BENCH_WARMUP_TIME: 2
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable

- uses: Swatinem/rust-cache@v2

- name: Restore benchmark baseline (from cache)
uses: actions/cache/restore@v4
with:
path: .bench/baseline.json
key: bench-baseline-${{ runner.os }}-${{ github.sha }}
restore-keys: |
bench-baseline-${{ runner.os }}-

- name: Run benchmarks (current)
run: cargo bench --benches -- --noplot --sample-size $BENCH_SAMPLE_SIZE --measurement-time $BENCH_MEASUREMENT_TIME --warm-up-time $BENCH_WARMUP_TIME

- name: Record current results (JSON)
run: cargo run --quiet --bin bench-regression -- record --criterion target/criterion --out .bench/current.json

- name: Compare against baseline (pass/warn/fail)
shell: bash
run: |
set -e
report="$(cargo run --quiet --bin bench-regression -- compare \
--baseline .bench/baseline.json \
--current .bench/current.json \
--warn-pct "$BENCH_WARN_PCT" \
--fail-pct "$BENCH_FAIL_PCT" \
--annotate-top 20 \
--max-rows 50 \
--require-baseline false)"
echo "$report"
echo "$report" >> "$GITHUB_STEP_SUMMARY"

build:
name: Build (${{ matrix.target }})
runs-on: ${{ matrix.os }}
needs: [gates, bench]
strategy:
fail-fast: false
matrix:
Expand Down
14 changes: 11 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ Thank you for your interest in contributing to the **Soroban Debugger** project!
9. [Issue Guidelines](#issue-guidelines)
10. [Areas for Contribution](#areas-for-contribution)
11. [Project Structure](#project-structure)
12. [Updating Man Pages](#updating-man-pages)
13. [Code of Conduct](#code-of-conduct)
14. [Communication](#communication)
12. [Code of Conduct](#code-of-conduct)
13. [Communication](#communication)
14. [Release Process](#release-process)

---

Expand Down Expand Up @@ -399,3 +399,11 @@ We are committed to providing a welcoming and inclusive environment for everyone
---

Thank you for helping make Soroban Debugger better!

---

## Release Process

Releases are gated by a single unified checklist that covers Rust/CLI, analyzers, VS Code extension checks, and benchmark thresholds:

- `docs/release-checklist.md`
8 changes: 8 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,11 @@ Inside the interactive shell, you can use commands like:
- Explore [Source-Level Debugging](source-level-debugging.md) to map WASM back to your Rust code.
- Learn about [Time-Travel Debugging](remote-debugging.md) to step backward through execution.
- Check the [FAQ](faq.md) for troubleshooting common issues.

---

## Maintainers

For release readiness gates (CLI + extension + analyzer + benchmarks), follow:

- `docs/release-checklist.md`
90 changes: 90 additions & 0 deletions docs/release-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Release Checklist

This checklist is the single release gate for the Soroban Debugger repo (CLI + analyzers + VS Code extension + benchmarks).

Use this for:
- Tag releases (`vX.Y.Z`) and crates.io publishes
- Hotfix releases

## Roles / Owners

- **Release Manager:** owns the go/no-go decision and waiver sign-off
- **Rust/CLI Owner:** owns core build/lint/test and packaging
- **VS Code Extension Owner:** owns extension build/test + DAP/protocol compatibility
- **Security/Analyzer Owner:** owns `analyze` sanity and any security-facing changes
- **Performance Owner:** owns benchmark sanity gates

## Required Gates (no waivers by default)

### Rust (workspace)

- Format check: `cargo fmt --all -- --check`
- Pass criteria: exit code 0
- Clippy: `cargo clippy --workspace --all-targets --all-features -- -D warnings`
- Pass criteria: exit code 0 (no warnings)
- Tests: `cargo test --workspace --all-features`
- Pass criteria: exit code 0

### Security analyzer sanity

- Static analysis: `cargo run --quiet --bin soroban-debug -- analyze --contract tests/fixtures/wasm/echo.wasm --format json`
- Pass criteria: exit code 0
- Optional dynamic analysis (when touching runtime/debug server):
`cargo run --quiet --bin soroban-debug -- analyze --contract tests/fixtures/wasm/echo.wasm --function echo --args '[7]' --timeout 30 --format json`
- Pass criteria: exit code 0

### VS Code extension

From `extensions/vscode`:

- Install deps: `npm ci`
- Pass criteria: exit code 0
- Compile: `npm run -s compile`
- Pass criteria: exit code 0
- Tests: `npm test`
- Pass criteria: exit code 0
- Notes:
- For best coverage, set `SOROBAN_DEBUG_BIN` to a locally-built debug binary path (e.g. `target/debug/soroban-debug`) so the smoke test exercises the real debugger server.

### Benchmarks (sanity thresholds)

Benchmarks must not regress beyond the configured thresholds:

- Thresholds (CI defaults):
- Warn: 10%
- Fail: 20%
- Command (CI-style):
- `cargo bench --benches -- --noplot --sample-size 20 --measurement-time 5 --warm-up-time 2`
- `cargo run --quiet --bin bench-regression -- record --criterion target/criterion --out .bench/current.json`
- `cargo run --quiet --bin bench-regression -- compare --baseline .bench/baseline.json --current .bench/current.json --warn-pct 10 --fail-pct 20`
- Pass criteria: compare exits 0 and reports no FAIL-level regressions

## Release Metadata Gates

- Version consistency:
- Tag is `vX.Y.Z`
- `Cargo.toml` version equals `X.Y.Z`
- `extensions/vscode/package.json` version equals `X.Y.Z` (if publishing the extension as part of the release)
- Changelog:
- `CHANGELOG.md` updated for `X.Y.Z`

## Waiver process (when absolutely necessary)

If any required gate is waived, the release must include a waiver record and explicit sign-off:

1. Create an issue or PR comment titled `Release waiver: vX.Y.Z`.
2. Include:
- Which gate was waived
- Why it failed / why it is safe to proceed
- Scope/impact
- Mitigation and follow-up owner + deadline
3. Release Manager signs off by linking the waiver record in the release notes under a `Waivers` section.

## Sign-off (fill before tagging)

- [ ] Release Manager: @____ (link to waiver record(s) if any)
- [ ] Rust/CLI Owner: @____
- [ ] VS Code Extension Owner: @____
- [ ] Security/Analyzer Owner: @____
- [ ] Performance Owner: @____

21 changes: 19 additions & 2 deletions docs/remote-debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,26 @@ soroban-debug remote \
--args '["user1", 100]'
```

## Security Model
### Timeouts and Retries (network instability)

### Important assumptions
Remote sessions often run across CI, containers, or flaky links. The remote client supports deterministic timeouts and controlled retries for **idempotent** operations.

- Retries apply to: `Ping`, `Inspect`, `GetStorage` (and other read-only state queries).
- No-retry semantics apply to: execution/stepping commands (e.g. `Execute`, `Continue`, `StepIn/Next/StepOut`) to avoid unintended side effects.

Example (tighter ping timeout, more retries):

```bash
soroban-debug remote \
--remote host:9229 \
--token "$TOKEN" \
--ping-timeout-ms 1000 \
--retry-attempts 5 \
--retry-base-delay-ms 100 \
--retry-max-delay-ms 1500
```

## Features

- A token protects **authentication**, not **confidentiality**.
- If you run remote debugging without TLS, the traffic should be treated as plaintext.
Expand Down
30 changes: 27 additions & 3 deletions examples/test_unbounded_iteration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,24 @@ fn main() {
if let Some(confidence) = &finding.confidence {
println!(" Confidence: {:.0}%", confidence * 100.0);
}
if let Some(rationale) = &finding.rationale {
println!(" Rationale: {}", rationale);

if let Some(context) = &finding.context {
if let Some(depth) = context.loop_nesting_depth {
println!(" Loop Nesting Depth: {}", depth);
}

if let Some(pattern) = &context.storage_call_pattern {
println!(" Storage Calls in Loops: {}", pattern.calls_in_loops);
println!(
" Storage Calls Outside Loops: {}",
pattern.calls_outside_loops
);
}

if let Some(cf_info) = &context.control_flow_info {
println!(" Loop Types: {:?}", cf_info.loop_types);
println!(" Conditional Branches: {}", cf_info.conditional_branches);
}
}

println!(" Remediation: {}", finding.remediation);
Expand Down Expand Up @@ -120,7 +136,15 @@ mod tests {
soroban_debugger::analyzer::security::Severity::High
);

// Should have confidence metadata
assert!(finding.confidence.is_some());
assert!(finding.rationale.is_some());
let confidence = finding.confidence.as_ref().unwrap();
assert!(!confidence.rationale.is_empty());

// Should have context metadata
assert!(finding.context.is_some());
let context = finding.context.as_ref().unwrap();
assert!(context.loop_nesting_depth.is_some());
assert!(context.storage_call_pattern.is_some());
}
}
55 changes: 34 additions & 21 deletions extensions/vscode/src/cli/debuggerProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,9 @@ type DebugRequest =
| { type: 'Continue' }
| { type: 'Inspect' }
| { type: 'GetStorage' }
| {
type: 'SetBreakpoint';
id: string;
function: string;
condition?: string;
hit_condition?: string;
log_message?: string;
}
| { type: 'ClearBreakpoint'; id: string }
| { type: 'SetBreakpoint'; function: string }
| { type: 'ClearBreakpoint'; function: string }
| { type: 'ResolveSourceBreakpoints'; source_path: string; lines: number[]; exported_functions: string[] }
| { type: 'Evaluate'; expression: string; frame_id?: number }
| { type: 'Ping' }
| { type: 'Disconnect' }
Expand All @@ -87,18 +81,9 @@ type DebugResponse =
| { type: 'InspectionResult'; function?: string; args?: string; step_count: number; paused: boolean; call_stack: string[] }
| { type: 'StorageState'; storage_json: string }
| { type: 'SnapshotLoaded'; summary: string }
| { type: 'BreakpointSet'; id: string; function: string }
| { type: 'BreakpointCleared'; id: string }
| {
type: 'BreakpointsList';
breakpoints: Array<{
id: string;
function: string;
condition?: string;
hit_condition?: string;
log_message?: string;
}>;
}
| { type: 'BreakpointSet'; function: string }
| { type: 'BreakpointCleared'; function: string }
| { type: 'SourceBreakpointsResolved'; breakpoints: Array<{ requested_line: number; line: number; verified: boolean; function?: string; reason_code: string; message: string }> }
| { type: 'EvaluateResult'; result: string; result_type?: string; variables_reference: number }
| {
type: 'Capabilities';
Expand Down Expand Up @@ -413,6 +398,34 @@ export class DebuggerProcess {
return functions;
}

async resolveSourceBreakpoints(
sourcePath: string,
lines: number[],
exportedFunctions: Set<string>,
options?: RequestOptions
): Promise<Array<{ requestedLine: number; line: number; verified: boolean; functionName?: string; reasonCode: string; message: string }>> {
const response = await this.sendRequest(
{
type: 'ResolveSourceBreakpoints',
source_path: sourcePath,
lines,
exported_functions: Array.from(exportedFunctions)
},
options
);

this.expectResponse(response, 'SourceBreakpointsResolved');

return response.breakpoints.map((bp) => ({
requestedLine: bp.requested_line,
line: bp.line,
verified: bp.verified,
functionName: bp.function,
reasonCode: bp.reason_code,
message: bp.message
}));
}

async stop(): Promise<void> {
try {
if (this.socket && !this.socket.destroyed) {
Expand Down
Loading
Loading