Skip to content

Commit bea7329

Browse files
committed
feat(jit_context): v2.5.5 JIT-framework classifier (Cranelift/LLVM/Wasm/JS)
ROADMAP v2.5.5 `jit_context` slice — foundation phase. The analyzer already does per-file Cranelift JIT detection inline at src/assail/analyzer.rs:1117..1129 to downgrade transmute-to-fn-ptr findings from Critical to High. This PR factors that detection out into `src/jit_context.rs` so: 1. Multiple emit sites can share the classifier. 2. The set of recognised frameworks extends beyond Cranelift to LLVM (inkwell + raw llvm-sys MCJIT/OrcJIT), WebAssembly engines (wasmtime, wasmer), and JS embeddings (rusty_v8, boa_engine). What this adds: - JitContext enum: Cranelift / Llvm / Wasm / Javascript / None. - classify_rust(content) - pure-content heuristic. - JitContext::permits_fn_ptr_transmute() - true for any framework, drives the Critical -> High severity downgrade + suppression eligibility. - transmute_targets_fn_ptr(content) - factored out of the analyzer's inline check, made tolerant of the let-binding form where the `=` is followed by an `unsafe { ... }` wrapper. What this PR does NOT do (follow-up): - Replace the analyzer's inline Cranelift check with calls to classify_rust + transmute_targets_fn_ptr - the inline check works; this PR is the foundation for the consolidation. - Document JIT safety invariants in WeakPoint metadata (Roadmap item 3) - needs the field threaded through emit sites; deferred. - Create a JIT category for specialized analysis (Roadmap item 4) - keeping UnsafeCode/UnsafeTypeCoercion with a JitContext sidecar is semantically cleaner than a new top-level WeakPointCategory. Refs ROADMAP.adoc v2.5.5 jit_context section. 344 lib tests pass (335 baseline post-test_context + comment_marker + ffi_kind merges + 16 new jit_context tests + ~7 from prior PRs).
1 parent 7d633e3 commit bea7329

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)