1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
use super::function_summary::FunctionSummary;
use super::macro_expand::FunctionMacro;
use super::null_state::NullState;
use std::collections::{HashMap, HashSet};
use std::path::Path;
/// Cross-file context gathered by pre-scanning additional directories.
///
/// Holds function names found in `.c`/`.h` files so that rules like DCL31-C
/// and DCL07-C can suppress false positives for project-internal functions
/// defined in other translation units.
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProjectContext {
/// Every function name found in the pre-scanned `.c`/`.h` files.
pub known_functions: HashSet<String>,
/// Functions declared (prototyped) in `.h` header files.
/// A function with a header prototype is public API and should not be
/// flagged by DCL15-C/DCL19-C as needing `static`.
pub header_declared_functions: HashSet<String>,
/// Function summaries computed during prescan for inter-procedural analysis.
pub function_summaries: HashMap<String, FunctionSummary>,
/// Call graph: maps function name to the set of functions it calls.
pub call_graph: HashMap<String, HashSet<String>>,
/// Callee names that must never be resolved to a same-named function
/// definition by name matching alone: names reached only through a
/// `field_expression` call (`obj->cb(...)`) or through a plain
/// identifier that is also a parameter name of the calling function
/// (a callback passed by the caller, shadowing any same-named global
/// function per C scoping rules). `call_graph` may still contain edges
/// to these names (recorded by the underlying, name-matching-only call
/// graph builder), so a consumer doing cycle/reachability analysis
/// through unresolved indirect calls should treat any callee in this
/// set as opaque rather than chase it (task 562).
#[serde(default)]
pub ambiguous_call_targets: HashSet<String>,
/// Macro constants collected from `#define` directives across all scanned files.
pub macro_constants: HashMap<String, i64>,
/// Macro aliases: `#define ALIAS identifier` patterns (e.g., `SYSTEM` → `system`).
/// Used by rules to resolve function calls through macro indirection.
pub macro_aliases: HashMap<String, String>,
/// Struct field types: maps `struct_name -> field_name -> type_text`.
/// Enables resolving types of `field_expression` nodes (e.g., `s->count` → "int").
pub struct_field_types: HashMap<String, HashMap<String, String>>,
/// Names of struct (and typedef-aliased) types declared
/// `__attribute__((packed))` (directly or via a macro like
/// `STRUCT_PACKED` whose `#define` expands to packed) across all scanned
/// files, incl. headers. A packed struct's actual alignment is 1, so
/// EXP36-C must not treat a cast into it as alignment-increasing.
#[serde(default)]
pub packed_structs: HashSet<String>,
/// Global constants: `[const] TYPE NAME = VALUE;` from across all scanned files.
/// Used by init-state analysis for dead-branch elimination.
#[serde(default)]
pub global_constants: HashMap<String, i64>,
/// Global pointer variable null states from across all scanned files.
/// Maps variable name to its joined null state across all assignment sites.
/// Used by EXP34-C to resolve `extern` pointer globals declared in other
/// translation units (Juliet CWE-476 variant 68 pattern).
#[serde(default)]
pub global_var_null_states: HashMap<String, NullState>,
/// File-scope `static` variable writers: maps static-variable name to the
/// set of function names that assign to it. Used by ENV03-C (and other
/// taint-aware rules) to decide whether a `char *data = g_static;` read
/// brings in taint — if every writer's summary is taint-free, the global
/// is treated as clean. Targets Juliet CWE-78 variant 45 (goodG2BSink
/// pattern).
#[serde(default)]
pub global_writers: HashMap<String, HashSet<String>>,
/// Function-like macro definitions (`#define NAME(a,b) body`) collected
/// across all scanned files (incl. headers) during the prescan pre-pass.
/// Consumed by `macro_expand` to expand opaque macro invocations on demand
/// (Phase 2 of docs/design/macro-expansion.md). Macros using `#`/`##` or
/// variadics are intentionally excluded (see `macro_expand`).
#[serde(default)]
pub function_macros: HashMap<String, FunctionMacro>,
/// Names of every `#define NAME ...` object-like macro collected across
/// all scanned files (incl. headers), regardless of what they expand to.
/// Used by DCL40-C to recognize a trailing bare identifier after a
/// struct/union/enum body (e.g. hostap's `struct foo { ... }
/// STRUCT_PACKED;`) as an attribute-position macro invocation rather
/// than a genuine object declaration — the `#define` commonly lives in a
/// different file than the struct (task 432).
#[serde(default)]
pub defined_macro_names: HashSet<String>,
/// Functions whose name appears as a bare value inside an aggregate
/// initializer (e.g. `{ "mysql", pw_mysql_parse, pw_mysql_check,
/// pw_mysql_exit }` or a designated `.check = pw_mysql_check`) — the
/// dispatch-table registration idiom used by callback-style backends
/// (auth/log/protocol handler tables) — and that are never invoked
/// through a direct-by-name `identifier(...)` call anywhere in the
/// project. Such a function is reachable only through the single
/// indirect call site that walks the table, so API00-C treats it like
/// a project-internal helper (task 594, extending task 169's
/// internal-contract suppression to the dispatch-table-callback shape).
#[serde(default)]
pub dispatch_table_callbacks: HashSet<String>,
/// `#include` paths that name a *project* header which is not on disk:
/// the directory prefix resolves under one of the search roots but the
/// file itself does not exist (e.g. seL4's `<object/structures_gen.h>`,
/// emitted at build time by `tools/bitfield_gen.py` from an `.bf` spec;
/// likewise `*.pb-c.h`, `*.tab.h`, and other generated headers).
///
/// A system header that simply isn't on the `-I` path (`<sys/socket.h>`)
/// does *not* land here — its directory prefix doesn't exist under the
/// project either — so this set means specifically "this project's
/// declaration set is incomplete because a build step we can't run
/// produces part of it" (task 580).
#[serde(default)]
pub unresolved_project_headers: HashSet<String>,
/// Function names reachable (including the root itself) from a real
/// concurrent-execution root: an ISR handler, a thread-spawn entry
/// point (`pthread_create`/`thrd_create`/`CreateThread`, direct or
/// forwarded through a function-like macro), or a `signal()`-registered
/// handler. Computed once during prescan by forward-walking
/// `call_graph` from every detected root (`ambiguous_call_targets`
/// edges excluded — see that field's docs). Empty when the scanned
/// project has no such root anywhere (e.g. a genuinely single-threaded
/// codebase). Used by CON03-C/CON07-C to gate findings on whether the
/// flagged code is ever reachable from a concurrent context at all,
/// rather than firing unconditionally (task 608; see
/// `docs/design/con03-con07-isr-thread-reachability.md`).
#[serde(default)]
pub concurrency_reachable: HashSet<String>,
/// Names of project-wide (file-scope, non-local) variables declared with
/// a plain, non-pointer/non-array/non-function type -- across every
/// scanned `.c` AND `.h` file, extern declarations included, since the
/// `extern` forward-declaration and the actual definition are typically
/// in different files. A name is excluded if it is ever declared with a
/// pointer or array declarator anywhere in the project (conservative:
/// only one true global object can exist per name at link time, so
/// disagreement means something this heuristic shouldn't guess about).
///
/// Mirrors MEM31-C's per-function `value_only_locals`
/// (`collect_value_only_locals`) but at project scope: seL4's
/// `current_lookup_fault`/`current_fault` globals are `extern`-declared
/// in a header and assigned via a bitfield-generator `_new()` value
/// constructor (`current_lookup_fault = lookup_fault_new(...)`) from
/// several other translation units, with no local declaration in any of
/// them -- MEM31-C's per-function pointer-evidence guard can't see a
/// declaration at all in that shape, so it needs this project-wide set
/// instead (task 652).
#[serde(default)]
pub value_only_globals: HashSet<String>,
/// Struct/union typedef aliases: `alias name -> the tag name its fields
/// are filed under in `struct_field_types``, for every
/// `typedef struct Tag Alias;` across the scanned files.
///
/// `collect_from_typedef` files a BODIED typedef under both the tag and
/// the alias, so the gap this closes is the bodyless spelling:
/// sqlite's `vdbe.h` says `typedef struct sqlite3_value Mem;` while
/// `vdbeInt.h` declares `struct sqlite3_value { ... }`, so the fields are
/// filed under `sqlite3_value` and nothing maps `Mem` onto them. The
/// typedef and the use are routinely in different files, so no file-local
/// pass can close it (task 963).
///
/// Deliberately kept OUT of `struct_field_types` itself. That map is read
/// by INT30-C, INT32-C, INT33-C and FLP03-C, and filing the alias there
/// would move four other rules' finding sets as a side effect of an
/// ARR36-C fix; a consumer opts in by resolving through this map, which
/// so far only ARR36-C does.
#[serde(default)]
pub struct_typedef_aliases: HashMap<String, String>,
/// One-level `typedef` alias map: `alias name -> underlying type text as
/// written` (e.g. `"paddr_t" -> "word_t"`, `"word_t" -> "unsigned long"`),
/// collected across every scanned `.c`/`.h` file. Simple scalar aliases
/// only (`typedef <type> <name>;`) -- struct/union/enum-bodied typedefs
/// are tracked separately by `struct_field_types`, and pointer/array/
/// function typedefs are excluded since they don't participate in a
/// scalar signedness chain.
///
/// A typedef's declaring header is frequently not the file that uses the
/// alias (seL4's `word_t` family: `paddr_t`/`pptr_t`/`vptr_t`/`seL4_Word`
/// each typedef onto `word_t`, sometimes from an arch-specific header
/// different from where `word_t` itself is defined), so resolving one
/// level locally isn't enough -- a consumer must walk this map
/// recursively (see `overflow_helpers::typedef_chain_is_unsigned`) and
/// project-wide (task 657).
#[serde(default)]
pub typedef_types: HashMap<String, String>,
}
impl ProjectContext {
/// An empty context, as if nothing had been pre-scanned yet.
pub fn new() -> Self {
Self::default()
}
/// Returns `true` if the given name was found during the pre-scan.
pub fn is_known_function(&self, name: &str) -> bool {
self.known_functions.contains(name)
}
/// Returns the summary for a function, if available.
pub fn get_function_summary(&self, name: &str) -> Option<&FunctionSummary> {
self.function_summaries.get(name)
}
/// Returns `true` if the function has a prototype in a `.h` header file,
/// indicating it is public API with intentional external linkage.
pub fn is_header_declared(&self, name: &str) -> bool {
self.header_declared_functions.contains(name)
}
/// Look up the type of a struct field given the struct name and field name.
/// `struct_name` should be the bare name (e.g., "MyStruct", not "struct MyStruct").
pub fn get_struct_field_type(&self, struct_name: &str, field_name: &str) -> Option<&str> {
self.struct_field_types
.get(struct_name)
.and_then(|fields| fields.get(field_name))
.map(|s| s.as_str())
}
/// Returns `true` if any cross-file data was collected.
///
/// `header_declared_functions` is included so that a lightweight
/// header-only prescan (no `-d` flag) still triggers `set_project_context`
/// on rules like DCL15-C that only need the public-API declaration set.
pub fn has_cross_file_data(&self) -> bool {
!self.known_functions.is_empty()
|| !self.function_summaries.is_empty()
|| !self.macro_constants.is_empty()
|| !self.struct_field_types.is_empty()
|| !self.header_declared_functions.is_empty()
|| !self.typedef_types.is_empty()
}
/// Save prescan context to a binary cache file.
pub fn save_to_file(&self, path: &Path) -> anyhow::Result<()> {
let encoded = bincode::serialize(self)?;
std::fs::write(path, &encoded)?;
Ok(())
}
/// Load prescan context from a binary cache file.
pub fn load_from_file(path: &Path) -> anyhow::Result<Self> {
let data = std::fs::read(path)?;
let context: Self = bincode::deserialize(&data)?;
Ok(context)
}
}