Skip to main content

waf_wasm/
report.rs

1// SPDX-FileCopyrightText: 2026 0x00spor3
2// SPDX-License-Identifier: Apache-2.0
3
4//! Boot-time classification of a guest's declared imports (policy D3=A: explicit coverage,
5//! never a silent partial). A missing import would make instantiation fail, so the loader
6//! STUBS every import it does not implement; this report says which were stubbed and — the
7//! load-bearing distinction (paletto #4) — whether any stub *alters detection semantics*.
8//!
9//! - **implemented**: the host function does the real thing.
10//! - **cosmetic stub**: absence does not change what the plugin detects (metrics, ticks…).
11//! - **semantic stub**: the plugin's detection logic *may* depend on it (`http_call`,
12//!   `shared_data`, header mutation…); stubbing it makes a plugin that CALLS it behave
13//!   differently from how it was written.
14//!
15//! Crucial nuance learned from a real SDK filter (paletto #4): a Proxy-Wasm SDK *declares*
16//! every host import regardless of use, so a *declared* semantic import is NOT evidence the
17//! plugin depends on it. This boot report is therefore **informational** (what the plugin
18//! links + which links are semantic-capable); the accurate "degraded" signal is emitted at
19//! RUNTIME, the first time the plugin actually invokes a stubbed semantic host call
20//! (`runtime::register_stubs` → `HostState::semantic_stub_hit`). We never reject at load.
21
22use wasmi::Module;
23
24/// Host functions the runtime implements for real.
25pub const IMPLEMENTED: &[&str] = &[
26    "proxy_log",
27    "proxy_get_buffer_bytes",
28    "proxy_get_header_map_pairs",
29    "proxy_get_header_map_value",
30    "proxy_get_property",
31    "proxy_send_local_response",
32    "proxy_get_current_time_nanoseconds",
33    "proxy_set_tick_period_milliseconds",
34];
35
36/// Stubbing these changes detection semantics → a plugin that uses one is `degraded`.
37pub const SEMANTIC: &[&str] = &[
38    "proxy_http_call",
39    "proxy_dispatch_http_call",
40    "proxy_get_shared_data",
41    "proxy_set_shared_data",
42    "proxy_register_shared_queue",
43    "proxy_resolve_shared_queue",
44    "proxy_enqueue_shared_queue",
45    "proxy_dequeue_shared_queue",
46    "proxy_set_header_map_pairs",
47    "proxy_replace_header_map_value",
48    "proxy_add_header_map_value",
49    "proxy_remove_header_map_value",
50    "proxy_set_property",
51    "proxy_set_buffer_bytes",
52    "proxy_call_foreign_function",
53    "proxy_grpc_call",
54    "proxy_grpc_stream",
55    "proxy_grpc_send",
56    "proxy_grpc_cancel",
57    "proxy_grpc_close",
58];
59
60/// How a single stubbed import was classified.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum StubClass {
63    Cosmetic,
64    Semantic,
65}
66
67/// Outcome of classifying a module's imports.
68#[derive(Debug, Clone, Default)]
69pub struct ImportReport {
70    /// `(name, class)` for every import the loader had to stub.
71    pub stubbed: Vec<(String, StubClass)>,
72    /// Implemented host functions the guest actually imports.
73    pub implemented_used: Vec<String>,
74}
75
76impl ImportReport {
77    /// Count of semantic-capable stubbed imports the plugin LINKS (not necessarily calls).
78    pub fn semantic_linked(&self) -> usize {
79        self.stubbed.iter().filter(|(_, c)| *c == StubClass::Semantic).count()
80    }
81
82    /// Names of the semantic-capable stubs the plugin links.
83    pub fn semantic_stubs(&self) -> Vec<&str> {
84        self.stubbed
85            .iter()
86            .filter(|(_, c)| *c == StubClass::Semantic)
87            .map(|(n, _)| n.as_str())
88            .collect()
89    }
90
91    /// One-line boot summary — informational. Whether the plugin is actually degraded is a
92    /// runtime fact (see the module docs), not derivable from declared imports.
93    pub fn summary(&self) -> String {
94        let semantic = self.semantic_linked();
95        let cosmetic = self.stubbed.len() - semantic;
96        format!(
97            "WASM import: {} implemented, {} stubbed ({} semantic-capable, {} cosmetic); \
98             semantic host calls return Unimplemented IF the plugin invokes them at runtime",
99            self.implemented_used.len(),
100            self.stubbed.len(),
101            semantic,
102            cosmetic,
103        )
104    }
105}
106
107/// Classify the func imports a module declares (namespace `env`).
108pub fn classify(module: &Module) -> ImportReport {
109    let mut report = ImportReport::default();
110    for import in module.imports() {
111        // Only function imports matter here; memory/global/table imports are handled by
112        // instantiation directly.
113        if import.ty().func().is_none() {
114            continue;
115        }
116        let name = import.name();
117        if IMPLEMENTED.contains(&name) {
118            report.implemented_used.push(name.to_string());
119        } else if SEMANTIC.contains(&name) {
120            report.stubbed.push((name.to_string(), StubClass::Semantic));
121        } else {
122            report.stubbed.push((name.to_string(), StubClass::Cosmetic));
123        }
124    }
125    report
126}