Skip to content

Commit f0b6463

Browse files
feat(jit_context): v2.5.5 JIT-framework classifier (Cranelift/LLVM/Wasm/JS) (#107)
## Summary v2.5.5 `jit_context` slice — foundation phase. Factors the existing inline Cranelift detection (`src/assail/analyzer.rs:1117..1129`) into a reusable module + extends recognition to LLVM, WebAssembly engines, and JS embeddings. ## What this adds ```rust pub enum JitContext { Cranelift, // cranelift_jit::JITModule / cranelift_module::* Llvm, // inkwell::execution_engine / LLVMOrcJIT / MCJIT Wasm, // wasmtime::Module/Instance / wasmer::Module Javascript, // rusty_v8::Isolate / boa_engine::Context None, } ``` Plus: - `classify_rust(content) -> JitContext` — pure-content heuristic - `JitContext::permits_fn_ptr_transmute()` — drives Critical → High severity downgrade + suppression eligibility - `transmute_targets_fn_ptr(content)` — refactored out of analyzer.rs:1123-1129, tolerates the let-binding form where `=` is followed by an `unsafe { ... }` wrapper ## What this does NOT do (follow-up) - Replace the analyzer's inline Cranelift check with the new module — analyzer code is correct as-is; this PR is the foundation for consolidation - Document JIT safety invariants on `WeakPoint` (Roadmap item 3) — needs field threading - Create a top-level JIT `WeakPointCategory` (Roadmap item 4) — keeping `UnsafeCode` / `UnsafeTypeCoercion` with a `JitContext` sidecar is semantically cleaner ## Verification - `cargo test --release --lib`: **344 pass / 0 fail** - 16 jit_context tests cover: Cranelift (use + fully-qualified forms), LLVM (inkwell), WASM (wasmtime + wasmer), JS (rusty_v8 + boa), None default, Cranelift-priority-over-LLVM, transmute-to-fn-ptr (turbofish + let-binding + unsafe-fn forms), transmute-to-non-fn negative, permits_fn_ptr_transmute consistency. ## Refs - ROADMAP.adoc v2.5.5 `jit_context` section — final slice of v2.5.5 cohort - PR #102 (test_context), #105 (comment_marker), #106 (ffi_refinement) — same v2.5.5 cohort - PR #104 (PROOF-PROGRAMME.md) — Layer 1.5 `UnsafeCode` proof obligation can now reference `JitContext` as a refinement axis
1 parent 7d633e3 commit f0b6463

2 files changed

Lines changed: 239 additions & 0 deletions

File tree

src/jit_context.rs

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
3+
//! JIT-compilation context detection (v2.5.5 `jit_context` slice).
4+
//!
5+
//! Some `mem::transmute` / `mem::transmute_copy` calls are the canonical
6+
//! way to invoke a JIT-emitted function pointer — there is no
7+
//! `unsafe`-free way to call into code that wasn't visible to the Rust
8+
//! compiler at build time. In that context, the `UnsafeCode` finding is
9+
//! a hard requirement of the JIT pattern rather than an avoidable
10+
//! type-pun.
11+
//!
12+
//! This module factors the per-language JIT-context detection out of
13+
//! the analyzer so emit sites can ask:
14+
//!
15+
//! ```ignore
16+
//! use panic_attack::jit_context::{JitContext, classify_rust};
17+
//! let ctx = classify_rust(file_content);
18+
//! if ctx == JitContext::Cranelift && targets_fn_ptr(span) {
19+
//! wp.suppressed = true;
20+
//! wp.severity = Severity::High; // not Critical
21+
//! }
22+
//! ```
23+
//!
24+
//! The existing analyzer at `src/assail/analyzer.rs:1117..1129` already
25+
//! performs this check inline for the Rust mem::transmute site; this
26+
//! module is the canonical home so subsequent JIT-related detectors
27+
//! (LLVM `MCJIT`, V8/CoreCLR embeddings) can share the classifier.
28+
29+
use serde::{Deserialize, Serialize};
30+
31+
/// JIT-compilation framework recognised in a source file.
32+
///
33+
/// Used as metadata on `WeakPoint` to communicate that an `UnsafeCode`
34+
/// or `UnsafeTypeCoercion` finding sits inside a JIT dispatch surface
35+
/// where the unsafe-ness is structural to the pattern.
36+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37+
#[serde(rename_all = "snake_case")]
38+
pub enum JitContext {
39+
/// Cranelift JIT (`cranelift-jit` / `cranelift-module` crates).
40+
/// Detected via use of `JITModule`, `cranelift_jit::*`, or
41+
/// `get_finalized_function`.
42+
Cranelift,
43+
/// LLVM JIT (`inkwell` or raw `llvm-sys` bindings, MCJIT/OrcJIT).
44+
/// Detected via use of `inkwell::execution_engine` or `LLVMOrcJIT*`.
45+
Llvm,
46+
/// WebAssembly engine embedding (`wasmtime` / `wasmer`). The
47+
/// `Module::new` / `Instance::new` boundary returns function
48+
/// references that must be reinterpreted before invocation.
49+
Wasm,
50+
/// V8 / SpiderMonkey / JavaScriptCore embedding (typically via
51+
/// `rusty_v8` / `boa_engine`). Same fn-ptr / value-conversion
52+
/// idioms apply.
53+
Javascript,
54+
/// No JIT context detected.
55+
None,
56+
}
57+
58+
impl JitContext {
59+
/// Returns true if a `transmute` to a function-pointer type inside
60+
/// this context is the canonical JIT-dispatch idiom and should be
61+
/// downgraded from Critical to High severity.
62+
pub fn permits_fn_ptr_transmute(self) -> bool {
63+
!matches!(self, JitContext::None)
64+
}
65+
}
66+
67+
/// Classify a Rust source file's JIT context. Pure-content heuristic —
68+
/// detects imports / API surface usage without parsing.
69+
pub fn classify_rust(content: &str) -> JitContext {
70+
if is_cranelift(content) {
71+
JitContext::Cranelift
72+
} else if is_llvm(content) {
73+
JitContext::Llvm
74+
} else if is_wasm(content) {
75+
JitContext::Wasm
76+
} else if is_javascript_embedding(content) {
77+
JitContext::Javascript
78+
} else {
79+
JitContext::None
80+
}
81+
}
82+
83+
fn is_cranelift(content: &str) -> bool {
84+
content.contains("cranelift_jit::JITModule")
85+
|| content.contains("cranelift_module::Module")
86+
|| content.contains("JITModule::new(")
87+
|| (content.contains("cranelift_jit")
88+
&& content.contains("get_finalized_function"))
89+
|| content.contains("use cranelift_jit")
90+
|| content.contains("use cranelift_module")
91+
}
92+
93+
fn is_llvm(content: &str) -> bool {
94+
content.contains("inkwell::execution_engine")
95+
|| content.contains("LLVMOrcJIT")
96+
|| content.contains("LLVMCreateExecutionEngine")
97+
|| content.contains("MCJIT")
98+
|| content.contains("OrcJITStack")
99+
}
100+
101+
fn is_wasm(content: &str) -> bool {
102+
content.contains("wasmtime::Module")
103+
|| content.contains("wasmtime::Instance")
104+
|| content.contains("wasmer::Module")
105+
|| content.contains("wasmer::Instance")
106+
|| (content.contains("wasmtime") && content.contains("get_typed_func"))
107+
}
108+
109+
fn is_javascript_embedding(content: &str) -> bool {
110+
content.contains("rusty_v8::Isolate")
111+
|| content.contains("boa_engine::Context")
112+
|| content.contains("rusty_jsc::JSContext")
113+
|| (content.contains("v8::") && content.contains("Function::call"))
114+
}
115+
116+
/// True if a `mem::transmute` (or `transmute_copy`) in this content
117+
/// targets a function-pointer type. Pattern-matches the standard
118+
/// turbofish forms and let-binding forms documented in
119+
/// `src/assail/analyzer.rs:1110..1129`.
120+
pub fn transmute_targets_fn_ptr(content: &str) -> bool {
121+
// Turbofish forms — unambiguous.
122+
if content.contains("transmute::<_, fn(")
123+
|| content.contains("transmute::<_, unsafe fn(")
124+
|| content.contains("transmute::<*const u8, fn(")
125+
|| content.contains("transmute::<*mut u8, fn(")
126+
{
127+
return true;
128+
}
129+
// Let-binding forms — accept when a `: fn(...)` (or
130+
// `: unsafe fn(...)`) annotation co-occurs with a `transmute(`
131+
// call. The `=` may have intervening tokens (`unsafe { ... }`,
132+
// closure wrappers, etc.), so we don't require it adjacent.
133+
let has_fn_annotation =
134+
content.contains(": fn(") || content.contains(": unsafe fn(");
135+
let has_transmute_call = content.contains("transmute(")
136+
|| content.contains("mem::transmute(")
137+
|| content.contains("std::mem::transmute(");
138+
has_fn_annotation && has_transmute_call
139+
}
140+
141+
#[cfg(test)]
142+
mod tests {
143+
use super::*;
144+
145+
#[test]
146+
fn cranelift_module_use_classified() {
147+
let src = "use cranelift_jit::JITModule;\nfn main() {}\n";
148+
assert_eq!(classify_rust(src), JitContext::Cranelift);
149+
}
150+
151+
#[test]
152+
fn cranelift_fully_qualified_classified() {
153+
let src = "fn build() -> cranelift_module::Module { todo!() }";
154+
assert_eq!(classify_rust(src), JitContext::Cranelift);
155+
}
156+
157+
#[test]
158+
fn llvm_inkwell_classified() {
159+
let src =
160+
"use inkwell::execution_engine::ExecutionEngine;\nfn run() {}\n";
161+
assert_eq!(classify_rust(src), JitContext::Llvm);
162+
}
163+
164+
#[test]
165+
fn wasmtime_classified() {
166+
let src = "use wasmtime::Instance;\nfn host() {}\n";
167+
assert_eq!(classify_rust(src), JitContext::Wasm);
168+
}
169+
170+
#[test]
171+
fn wasmer_classified() {
172+
let src = "use wasmer::Module;\nfn host() {}\n";
173+
assert_eq!(classify_rust(src), JitContext::Wasm);
174+
}
175+
176+
#[test]
177+
fn rusty_v8_classified() {
178+
let src = "use rusty_v8::Isolate;\nfn embed() {}\n";
179+
assert_eq!(classify_rust(src), JitContext::Javascript);
180+
}
181+
182+
#[test]
183+
fn boa_classified() {
184+
let src = "use boa_engine::Context;\nfn embed() {}\n";
185+
assert_eq!(classify_rust(src), JitContext::Javascript);
186+
}
187+
188+
#[test]
189+
fn no_jit_context() {
190+
let src = "fn main() { println!(\"hi\"); }\n";
191+
assert_eq!(classify_rust(src), JitContext::None);
192+
}
193+
194+
#[test]
195+
fn cranelift_priority_over_llvm_if_both_present() {
196+
// Cranelift can target LLVM IR internally — if both are
197+
// referenced, we report Cranelift because that's the higher-level
198+
// engine being used.
199+
let src = "use cranelift_jit::JITModule;\n// also uses LLVMOrcJIT internally\n";
200+
assert_eq!(classify_rust(src), JitContext::Cranelift);
201+
}
202+
203+
#[test]
204+
fn transmute_to_fn_ptr_turbofish_recognised() {
205+
let src =
206+
"let f: fn() = unsafe { std::mem::transmute::<_, fn()>(ptr) };";
207+
assert!(transmute_targets_fn_ptr(src));
208+
}
209+
210+
#[test]
211+
fn transmute_to_fn_ptr_let_binding_recognised() {
212+
let src =
213+
"let f: fn(i64) -> i64 = unsafe { mem::transmute(raw_ptr) };";
214+
assert!(transmute_targets_fn_ptr(src));
215+
}
216+
217+
#[test]
218+
fn transmute_to_unsafe_fn_recognised() {
219+
let src = "let f: unsafe fn() = unsafe { transmute(raw) };";
220+
assert!(transmute_targets_fn_ptr(src));
221+
}
222+
223+
#[test]
224+
fn transmute_to_non_fn_not_recognised() {
225+
let src =
226+
"let n: u64 = unsafe { std::mem::transmute::<_, u64>(value) };";
227+
assert!(!transmute_targets_fn_ptr(src));
228+
}
229+
230+
#[test]
231+
fn permits_flag_consistent() {
232+
assert!(JitContext::Cranelift.permits_fn_ptr_transmute());
233+
assert!(JitContext::Llvm.permits_fn_ptr_transmute());
234+
assert!(JitContext::Wasm.permits_fn_ptr_transmute());
235+
assert!(JitContext::Javascript.permits_fn_ptr_transmute());
236+
assert!(!JitContext::None.permits_fn_ptr_transmute());
237+
}
238+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub mod bridge;
2929
pub mod campaign;
3030
pub mod comment_marker;
3131
pub mod i18n;
32+
pub mod jit_context;
3233
pub mod kanren;
3334
pub mod mass_panic;
3435
pub mod notify;

0 commit comments

Comments
 (0)