A conservative MIR-based checker for a restricted set of Rust safe/unsafe API counterparts. The current implementation focuses on local numerical and pointer-nullness conditions under which specific unchecked calls may be safe, while explicitly downgrading unsupported calls to unknown and keeping only a tiny wrapper-like exception set at call boundaries.
This tool analyzes MIR-level control-flow and integer constraints around a small supported fragment of checked/unchecked APIs. In the current implementation, it is intended to:
slice.get(index)→slice.get_unchecked(index)when bounds are proven safeslice.split_at(mid)→slice.split_at_unchecked(mid)when split bounds are proven safeslice.swap(a, b)under locally provable in-bounds indicesinteger.checked_add(other)when overflow is provably impossibleptr.as_ref(),ptr.as_mut(), andNonNull::new(ptr)when pointer nullness is locally known
The project is currently being narrowed into a self-contained demo artifact. The intended claim is conservative:
For a known set of standard-library API pairs, the analyzer can prove selected call-site preconditions from local MIR facts and report replacement opportunities.
The demo does not claim whole-program optimization, whole-application speedup, full alias analysis, full raw-pointer reasoning, or semantic preservation for arbitrary Rust programs. Unsupported calls and memory effects are downgraded to local unknown, and replacement candidates are suppressed when their preconditions depend on those unknown facts.
The primary demo matrix is:
| API family | Required condition | Demo case |
|---|---|---|
checked_add |
result is within the integer type range | tests/checked_add |
slice::get / get_mut |
index < slice.len() |
tests/get |
slice::split_at / split_at_mut |
mid <= slice.len() |
tests/split_at, tests/ring_buffer_split |
slice::swap |
i < slice.len() && j < slice.len() |
tests/swap, case-study/kmerge_impl.rs |
See DEMO_SCOPE.md for the detailed demo boundary and non-goals.
fn process_array(arr: &[i32]) {
for i in 0..arr.len() {
// Redundant bounds check - loop condition guarantees i < arr.len()
let value = arr.get(i).unwrap(); // Can optimize to arr[i] or arr.get_unchecked(i)
println!("{}", value);
}
}The tool aims to recognize that the bounds check in arr.get(i) is locally redundant and to surface a diagnostic within its supported fragment, rather than to perform automatic rewriting.
Ordinary helper calls remain local unknowns by default, except for a tiny wrapper-like shim set that is intentionally suppressed rather than analyzed interprocedurally.
The analyzer is intentionally narrow.
- The abstract state combines interval-style numerical facts with local pointer-nullness facts.
- The active numerical domain is
interval. - The current reasoning is intraprocedural in spirit: default descent into ordinary callees is disabled.
- Special handling is limited to a small whitelist of local checked/unchecked APIs, such as
get,split_at,swap,checked_add, and pointer nullness checks. - A tiny micro-wrapper exception set suppresses selected boolean function-trait shims without restoring general interprocedural descent.
- Calls outside this fragment are downgraded to local unknowns at the call boundary.
Diagnostics should be interpreted conservatively.
- A supported diagnostic comes from the supported numerical or pointer-nullness fragment.
- An unsupported or call-boundary diagnostic means the analyzer deliberately stopped and downgraded the result to unknown.
- Selected boolean callback wrappers may also be downgraded to unknown silently when they are treated as local shims rather than reportable boundaries.
- The absence of a diagnostic is not a global proof of safety.
- Rust nightly (
nightly-2025-01-10) - Dependencies:
$ rustup component add rustc-dev llvm-tools-preview $ sudo apt-get install libgmp-dev libmpfr-dev libz3-dev # Ubuntu
$ git clone https://github.com/Rust-API/Rust-API-Bypass.git
$ cd rust-api-bypass
$ export RUSTFLAGS="-Clink-args=-fuse-ld=lld"
$ cargo buildThe root crate currently pins nightly-2025-01-10. A separate benchmark-only workspace under API-counterprats/ may use a newer nightly for Criterion experiments.
# Analyze a crate via main.rs or lib.rs
# Inspect candidate entry functions
$ ./target/debug/api-bypass <file> --show_reachable_entries
# Analyze a particular function as the root of a local numerical run
$ ./target/debug/api-bypass <file> --entry_def_id_index <defid> --entry_def_id_index <function>: Entry function DefId (acquired viashow_reachable_entries)--show_all_entries: Display all candidate entry functions within the current crate.--show_reachable_entries: Display entry candidates discovered by the current front-end scan.
When a supported safe API call is proven to satisfy the corresponding unchecked precondition, the analyzer emits a replacement-candidate diagnostic:
[Bypasser] Replacement candidate
Safe API:
slice::split_at(mid)
Suggested replacement:
unsafe { slice::split_at_unchecked(mid) }
Required condition:
mid <= slice.len()
Analysis result:
proven from local MIR split-index facts
The diagnostic is a reporting aid, not an automatic source rewrite.
tests/checked_add/: Local integer overflow-check scenariostests/get/: Local slice bounds-check scenariostests/split_at/: Local split-index reasoning scenariostests/swap/: Local two-index bounds reasoning scenariostests/nullness/: Local pointer nullness scenarios for checked pointer APIscase-study/: A larger MIR case used to stress-test the reducedswapsupport path under local callback-boundary downgrades
Built upon:
- MirChecker - Original MIR analysis framework from which this checker was narrowed and adapted
- MIRAI - Static analysis techniques
- Crab - Abstract domain implementations