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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
use super::function_summary::FunctionSummary;
use super::macro_expand::FunctionMacro;
use super::null_state::NullState;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::sync::Arc;
/// 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.
///
/// The tables are `Arc`-wrapped because every scanned file hands this context
/// to a fresh set of rule instances, each of which keeps its own handle
/// (`set_project_context`). A handle is a refcount bump; a deep copy of the
/// function summaries of a few-thousand-file project, per rule, per file, was
/// the dominant cost of a scan. Build the tables in full, then wrap; after
/// that, mutate only through `Arc::make_mut` and only before rules see the
/// context (`resolve_includes`, the compile-database merge).
#[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: Arc<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: Arc<HashSet<String>>,
/// Function summaries computed during prescan for inter-procedural analysis.
pub function_summaries: Arc<HashMap<String, FunctionSummary>>,
/// Call graph: maps function name to the set of functions it calls.
pub call_graph: Arc<HashMap<String, HashSet<String>>>,
/// The inverse of `call_graph`: maps a function name to the set of
/// functions that call it. Computed once when the context is built, so
/// a rule asking "who calls this?" per file does not re-invert the whole
/// graph per file (six rules did, each on every file).
#[serde(default)]
pub callers: Arc<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: Arc<HashSet<String>>,
/// Macro constants collected from `#define` directives across all scanned files.
pub macro_constants: Arc<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: Arc<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: Arc<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: Arc<HashSet<String>>,
/// Names of functions known never to return to their caller, collected
/// across all scanned files (incl. headers) by
/// [`crate::analyze::noreturn::collect_noreturn_function_names`]: the
/// fixed C standard library set, `_Noreturn` qualifiers,
/// `__attribute__((noreturn))`, and the recovered bare-identifier
/// attribute macros. Cross-file because the declaration carrying the
/// attribute is routinely in a header the single-file parse never sees
/// -- pure-ftpd marks its `no_mem()` allocation-failure helper
/// `__attribute__((noreturn))` in `ftpd.h` while every call site is in
/// a `.c` file (task 1076).
#[serde(default)]
pub noreturn_functions: Arc<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: Arc<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: Arc<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: Arc<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: Arc<HashSet<String>>,
/// Names of every object-like `#define` whose replacement text is an
/// unused-attribute annotation — `__attribute__((unused))`,
/// `[[maybe_unused]]`, and the reserved spellings — collected across all
/// scanned files (incl. headers). seL4's `UNUSED`, hostap's
/// `STRUCT_PACKED`-adjacent annotations and the rest are recognized by
/// what they *expand to*, never by name, and the `#define` almost always
/// lives in a different file from the declaration it annotates.
///
/// Used by MSC13-C: aurora-lint has no preprocessor, so such a macro sits
/// in the declaration where a type or declarator is expected and the
/// recovered parse misnames the variable. The annotation is the author
/// stating the variable may legitimately go unused, which is exactly
/// what MSC13-C exists to respect, so a declaration carrying one is not
/// reported at all.
#[serde(default)]
pub unused_attribute_macros: Arc<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>,
/// Every place the macro-expansion engine declined or failed to see a
/// definition while building this context — skipped variadic / `#`/`##`
/// macros, platform-dead and ambiguous definitions, cross-file conflicts,
/// unresolvable `#include`s. Recorded unconditionally (it is a by-product
/// of scans that already run) and surfaced only by `--report-macro-gaps`;
/// nothing in analysis reads it (task 1180).
#[serde(default)]
pub macro_gaps: Vec<super::macro_gaps::MacroGap>,
/// `function name -> indices of its restrict-qualified parameters`, for
/// every function any scanned file defines or declares with at least one.
/// First definition seen wins. Lets EXP43-C confine its aliasing check
/// to callees whose contract actually forbids aliasing (task 1171).
#[serde(default)]
pub restrict_params: HashMap<String, Vec<usize>>,
/// `function name -> indices of the parameters whose doc comment states
/// a non-NULL precondition` ("must be initialized", "must not be NULL",
/// ...), from every definition and prototype any scanned file carries a
/// Doxygen comment for. The function's own published contract, which is
/// what lets API00-C and the EXP34-C parameter seeding honour a
/// caller-validates discipline the code documents (task 1171).
#[serde(default)]
pub documented_nonnull_params: HashMap<String, Vec<usize>>,
/// 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: Arc<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: Arc<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: Arc<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: Arc<HashMap<String, String>>,
/// Names of typedefs whose declared type is a function pointer -- e.g.
/// sqlite's `typedef int (*RecordCompare)(void *, int);` in
/// `sqliteInt.h`. `collect_from_simple_typedef` filed under
/// `typedef_types` only stores primitive/sized/named RHSs, so a
/// function-pointer typedef leaves that map with no entry for its
/// alias name; DCL31-C needs the *category* (function-pointer
/// typedef?), not the RHS text, to decide whether a parameter of
/// that type is directly callable (task 1054, second consumer of
/// task 736's shared typedef-chain resolver).
#[serde(default)]
pub function_pointer_typedef_names: Arc<HashSet<String>>,
/// Names of typedefs that hide a pointer in DCL05-C's sense -- a pointer
/// in the declarator chain, not a function pointer, not a pointer to
/// const (`declarator_utils::pointer_typedef_names_in`). The typedef is
/// usually in a header; the `const LPPOINT pt` parameter that the rule
/// is about is in a .c file that only names the alias (task 1188).
#[serde(default)]
pub pointer_typedef_names: Arc<HashSet<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)
}
}