use super::super::{CertRule, RuleViolation};
use crate::analyze::cfg::{self as cfg_mod, FunctionCfg};
use crate::analyze::context::ProjectContext;
use crate::analyze::function_summary::FunctionSummary;
use crate::analyze::init_state::{self, InitAnalysisResult, InitState, InitStateMap};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::{get_identifier_from_declarator, get_node_text};
use lang_parsing_substrate::query;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use tree_sitter::Node;
pub struct Exp33C {
function_cfgs: RefCell<HashMap<usize, FunctionCfg>>,
file_scope_statics: RefCell<InitStateMap>,
realloc_wrapper_fns: RefCell<HashSet<String>>,
conditionally_init_fns: RefCell<HashMap<String, HashSet<usize>>>,
cross_file_summaries: RefCell<HashMap<String, FunctionSummary>>,
file_scope_constants: RefCell<HashMap<String, i64>>,
function_macros: RefCell<HashMap<String, crate::analyze::macro_expand::FunctionMacro>>,
macro_output_params: RefCell<HashMap<String, Vec<usize>>>,
}
impl Exp33C {
pub fn new() -> Self {
Self {
function_cfgs: RefCell::new(HashMap::new()),
file_scope_statics: RefCell::new(InitStateMap::new()),
realloc_wrapper_fns: RefCell::new(HashSet::new()),
conditionally_init_fns: RefCell::new(HashMap::new()),
cross_file_summaries: RefCell::new(HashMap::new()),
file_scope_constants: RefCell::new(HashMap::new()),
function_macros: RefCell::new(HashMap::new()),
macro_output_params: RefCell::new(HashMap::new()),
}
}
fn build_read_only_deref_fns(&self) -> HashMap<String, HashSet<usize>> {
let summaries = self.cross_file_summaries.borrow();
let mut result = HashMap::new();
for (name, summary) in summaries.iter() {
let read_only: HashSet<usize> = summary
.dereferences_params
.difference(&summary.modifies_params)
.copied()
.collect();
if !read_only.is_empty() {
result.insert(name.clone(), read_only);
}
}
result
}
fn build_cross_file_output_params(&self) -> HashMap<String, HashSet<usize>> {
let summaries = self.cross_file_summaries.borrow();
let mut result = HashMap::new();
for (name, summary) in summaries.iter() {
if !summary.modifies_params.is_empty() {
result.insert(name.clone(), summary.modifies_params.clone());
}
}
result
}
}
impl CertRule for Exp33C {
fn rule_id(&self) -> &'static str {
"EXP33-C"
}
fn description(&self) -> &'static str {
"Do not read uninitialized memory"
}
fn severity(&self) -> Severity {
Severity::High
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"EXP33-C"
}
fn set_project_context(&self, context: &ProjectContext) {
*self.cross_file_summaries.borrow_mut() = context.function_summaries.clone();
let mut constants = self.file_scope_constants.borrow_mut();
for (k, v) in &context.global_constants {
constants.entry(k.clone()).or_insert(*v);
}
for (k, v) in &context.macro_constants {
constants.entry(k.clone()).or_insert(*v);
}
drop(constants);
*self.function_macros.borrow_mut() = context.function_macros.clone();
}
fn set_function_cfgs(&self, cfgs: &HashMap<usize, FunctionCfg>) {
*self.function_cfgs.borrow_mut() = cfgs.clone();
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let cfgs = self.function_cfgs.borrow();
for n in
query::find_descendants_of_kinds(*node, &["translation_unit", "function_definition"])
{
let node = &n;
if node.kind() == "translation_unit" {
let statics = init_state::collect_file_scope_statics(node, source);
*self.file_scope_statics.borrow_mut() = statics;
let file_constants = init_state::collect_file_scope_constants(node, source);
let fn_constants = init_state::collect_constant_functions(node, source);
{
let mut constants = self.file_scope_constants.borrow_mut();
constants.extend(file_constants);
constants.extend(fn_constants);
}
let mut wrappers = HashSet::new();
scan_realloc_wrappers(node, source, &mut wrappers);
*self.realloc_wrapper_fns.borrow_mut() = wrappers;
let mut cond_init = HashMap::new();
scan_conditionally_init_functions(node, source, &mut cond_init);
*self.conditionally_init_fns.borrow_mut() = cond_init;
let macros = self.function_macros.borrow();
if !macros.is_empty() {
let mut invoked = HashSet::new();
collect_invoked_macro_names(node, source, ¯os, &mut invoked);
let mut out_params = HashMap::new();
let cross_file_summaries = self.cross_file_summaries.borrow();
for name in invoked {
let mut idx = crate::analyze::macro_expand::macro_output_param_indices(
¯os, &name,
);
if idx.is_empty() {
if let Some((callee, param_map)) =
crate::analyze::macro_expand::macro_forwarding_target(
¯os, &name,
)
{
if let Some(summary) = cross_file_summaries.get(&callee) {
let mut mapped: Vec<usize> = summary
.modifies_params
.iter()
.filter_map(|&callee_idx| {
param_map.get(callee_idx).copied().flatten()
})
.collect();
mapped.sort_unstable();
idx = mapped;
}
}
}
if !idx.is_empty() {
out_params.insert(name, idx);
}
}
drop(cross_file_summaries);
*self.macro_output_params.borrow_mut() = out_params;
}
}
if node.kind() == "function_definition" {
if let Some(body) = node.child_by_field_name("body") {
let inline_cfg;
let cfg = if let Some(c) = cfgs.get(&node.start_byte()) {
c
} else if let Some(c) = cfg_mod::build_function_cfg(node, source) {
inline_cfg = c;
&inline_cfg
} else {
continue;
};
let statics = self.file_scope_statics.borrow();
let cond_fns = self.conditionally_init_fns.borrow();
let realloc_fns = self.realloc_wrapper_fns.borrow();
let read_only_fns = self.build_read_only_deref_fns();
let cross_file_output_params = self.build_cross_file_output_params();
let file_constants = self.file_scope_constants.borrow();
let macro_out = self.macro_output_params.borrow();
let config = init_state::InitAnalysisConfig {
conditionally_init_fns: cond_fns.clone(),
realloc_wrapper_fns: realloc_fns.clone(),
read_only_deref_fns: read_only_fns.clone(),
file_scope_constants: file_constants.clone(),
macro_output_params: macro_out.clone(),
cross_file_output_params,
};
let analysis = init_state::analyze_init_states_with_statics(
cfg, node, source, &statics, &config,
);
let mut reported: HashSet<String> = HashSet::new();
check_reads(
&body,
source,
&analysis,
cfg,
&body,
&mut violations,
&mut reported,
&config,
);
check_append_call_reads(
&body,
source,
&analysis,
cfg,
&body,
&mut violations,
&mut reported,
&config,
);
if !read_only_fns.is_empty() {
check_cross_file_uninit_calls(
&body,
source,
&analysis,
cfg,
&body,
&read_only_fns,
&mut violations,
&mut reported,
);
}
}
}
}
violations
}
}
fn check_reads(
node: &Node,
source: &str,
analysis: &InitAnalysisResult,
cfg: &FunctionCfg,
body: &Node,
violations: &mut Vec<RuleViolation>,
reported: &mut HashSet<String>,
config: &init_state::InitAnalysisConfig,
) {
let asm_ranges = asm_call_ranges(body, source);
for n in query::find_descendants_of_kinds(
*node,
&["identifier", "pointer_expression", "subscript_expression"],
) {
match n.kind() {
"identifier" => {
check_identifier_read(
&n,
source,
analysis,
cfg,
body,
violations,
reported,
config,
&asm_ranges,
);
}
"pointer_expression" => {
let text = get_node_text(&n, source);
if text.starts_with('*') {
check_deref_read(
&n, source, analysis, cfg, body, violations, reported, config,
);
}
}
"subscript_expression" => {
check_subscript_read(
&n, source, analysis, cfg, body, violations, reported, config,
);
}
_ => {}
}
}
}
fn collect_invoked_macro_names(
node: &Node,
source: &str,
macros: &HashMap<String, crate::analyze::macro_expand::FunctionMacro>,
out: &mut HashSet<String>,
) {
for call in query::find_descendants_of_kind(*node, "call_expression") {
if let Some(func) = call.child_by_field_name("function") {
if func.kind() == "identifier" {
let name = get_node_text(&func, source);
if macros.contains_key(name) {
out.insert(name.to_string());
}
}
}
}
}
fn is_function_macro_output_arg(
node: &Node,
source: &str,
macro_output_params: &HashMap<String, Vec<usize>>,
) -> bool {
if node.kind() != "identifier" {
return false;
}
let arg_list = match node.parent() {
Some(p) if p.kind() == "argument_list" => p,
_ => return false,
};
let call = match arg_list.parent() {
Some(c) if c.kind() == "call_expression" => c,
_ => return false,
};
let func_name = match call.child_by_field_name("function") {
Some(f) => get_node_text(&f, source),
None => return false,
};
let out_indices = match macro_output_params.get(func_name) {
Some(v) => v,
None => return false,
};
let args = crate::analyze::macro_semantics::positional_args(&call);
let target = node.id();
for (pos, arg) in args.into_iter().enumerate() {
if arg.id() == target {
return out_indices.contains(&pos);
}
}
false
}
fn enclosing_ifdef_guard_key(node: &Node, boundary: &Node, source: &str) -> Option<String> {
let mut current = *node;
while current.id() != boundary.id() {
let Some(parent) = current.parent() else {
return blanked_label_guard_key(node, boundary, source);
};
if parent.kind() == "preproc_ifdef" {
let is_alt = parent
.child_by_field_name("alternative")
.is_some_and(|alt| alt.id() == current.id());
if is_alt {
return None;
}
let directive = parent.child(0).map(|c| get_node_text(&c, source))?;
let name = parent.child_by_field_name("name")?;
return Some(format!("{directive}:{}", get_node_text(&name, source)));
}
if parent.id() == boundary.id() {
return blanked_label_guard_key(node, boundary, source);
}
current = parent;
}
None
}
fn blanked_label_guard_key(node: &Node, boundary: &Node, source: &str) -> Option<String> {
let node_line = node.start_position().row;
let boundary_line = boundary.start_position().row;
if node_line <= boundary_line {
return None;
}
let lines: Vec<&str> = source.lines().collect();
for line in lines.get(boundary_line..node_line)?.iter().rev() {
let trimmed = line.trim();
if trimmed == "/*E*/" {
return None;
}
if let Some(rest) = trimmed.strip_prefix("/*G") {
let Some(rest) = rest.strip_suffix("*/") else {
continue;
};
let mut parts = rest.splitn(2, ':');
let (Some(sigil), Some(name)) = (parts.next(), parts.next()) else {
continue;
};
let directive = match sigil {
"d" => "#ifdef",
"n" => "#ifndef",
_ => continue,
};
return Some(format!("{directive}:{name}"));
}
}
None
}
fn all_write_sites_ifdef_correlated(
body: &Node,
var_name: &str,
read_guard_key: &str,
source: &str,
) -> bool {
let mut found_any = false;
for assign in query::find_descendants_of_kind(*body, "assignment_expression") {
let Some(left) = assign.child_by_field_name("left") else {
continue;
};
if left.kind() != "identifier" || get_node_text(&left, source) != var_name {
continue;
}
found_any = true;
match enclosing_ifdef_guard_key(&assign, body, source) {
Some(key) if key == read_guard_key => {}
_ => return false,
}
}
for init_decl in query::find_descendants_of_kind(*body, "init_declarator") {
let Some(declarator) = init_decl.child_by_field_name("declarator") else {
continue;
};
if get_identifier_from_declarator(&declarator, source) != var_name {
continue;
}
found_any = true;
match enclosing_ifdef_guard_key(&init_decl, body, source) {
Some(key) if key == read_guard_key => {}
_ => return false,
}
}
found_any
}
fn has_macro_shadow_definition(body: &Node, var_name: &str, source: &str) -> bool {
query::find_descendants_of_kind(*body, "preproc_def")
.iter()
.any(|def| {
def.child_by_field_name("name")
.is_some_and(|n| get_node_text(&n, source) == var_name)
})
}
const APPEND_FUNCTIONS: &[&str] = &["strcat", "strncat", "wcscat", "wcsncat"];
fn check_append_call_reads(
node: &Node,
source: &str,
analysis: &InitAnalysisResult,
cfg: &FunctionCfg,
body: &Node,
violations: &mut Vec<RuleViolation>,
reported: &mut HashSet<String>,
config: &init_state::InitAnalysisConfig,
) {
for call in query::find_descendants_of_kind(*node, "call_expression") {
let Some(func) = call.child_by_field_name("function") else {
continue;
};
if func.kind() != "identifier" {
continue;
}
let func_name = get_node_text(&func, source);
if !APPEND_FUNCTIONS.contains(&func_name) {
continue;
}
let Some(args) = call.child_by_field_name("arguments") else {
continue;
};
let Some(first_arg) = (0..args.child_count())
.filter_map(|i| args.child(i))
.find(|c| !matches!(c.kind(), "," | "(" | ")"))
else {
continue;
};
if first_arg.kind() != "identifier" {
continue;
}
let var_name = get_node_text(&first_arg, source).to_string();
if !analysis.tracked_vars.contains(&var_name) || reported.contains(&var_name) {
continue;
}
let Some(info) = init_state::get_var_info_at_with_config(
analysis,
cfg,
body,
source,
&var_name,
call.start_byte(),
config,
) else {
continue;
};
if matches!(info.state, InitState::MallocUninitialized) && !info.is_unsigned_char {
reported.insert(var_name.clone());
violations.push(RuleViolation {
rule_id: "EXP33-C".to_string(),
severity: Severity::High,
message: format!(
"'{}' passed to '{}' without prior initialization (append requires an existing null terminator)",
var_name, func_name
),
file_path: String::new(),
line: call.start_position().row + 1,
column: call.start_position().column + 1,
suggestion: Some(format!(
"Initialize '{}' (e.g., null-terminate it) before appending to it",
var_name
)),
..Default::default()
});
}
}
}
fn check_cross_file_uninit_calls(
node: &Node,
source: &str,
analysis: &InitAnalysisResult,
cfg: &FunctionCfg,
body: &Node,
read_only_fns: &HashMap<String, HashSet<usize>>,
violations: &mut Vec<RuleViolation>,
reported: &mut HashSet<String>,
) {
for call in query::find_descendants_of_kind(*node, "call_expression") {
if let Some(func) = call.child_by_field_name("function") {
let func_name = get_node_text(&func, source).to_string();
if let Some(read_only_params) = read_only_fns.get(&func_name) {
if let Some(args) = call.child_by_field_name("arguments") {
let mut arg_idx: usize = 0;
for i in 0..args.child_count() {
if let Some(arg) = args.child(i) {
if arg.kind() == "," || arg.kind() == "(" || arg.kind() == ")" {
continue;
}
if read_only_params.contains(&arg_idx) {
let var_name = extract_addr_of_var(&arg, source);
if !var_name.is_empty()
&& analysis.tracked_vars.contains(&var_name)
&& !reported.contains(&var_name)
{
if let Some(info) = init_state::get_var_info_at_with_config(
analysis,
cfg,
body,
source,
&var_name,
call.start_byte(),
&init_state::InitAnalysisConfig::default(),
) {
if info.state.is_unsafe() && !info.is_unsigned_char {
reported.insert(var_name.clone());
violations.push(RuleViolation {
rule_id: "EXP33-C".to_string(),
severity: Severity::High,
message: format!(
"Passing pointer to uninitialized variable '{}' to '{}' which reads the value",
var_name, func_name
),
file_path: String::new(),
line: call.start_position().row + 1,
column: call.start_position().column + 1,
suggestion: Some(format!(
"Initialize '{}' before passing its address to '{}'",
var_name, func_name
)),
..Default::default()
});
}
}
}
}
arg_idx += 1;
}
}
}
}
}
}
}
fn extract_addr_of_var(node: &Node, source: &str) -> String {
if node.kind() == "pointer_expression" {
let text = get_node_text(node, source);
if text.starts_with('&') {
if let Some(arg) = node.child_by_field_name("argument") {
if arg.kind() == "identifier" {
return get_node_text(&arg, source).to_string();
}
}
}
}
if node.kind() == "parenthesized_expression" {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
let result = extract_addr_of_var(&child, source);
if !result.is_empty() {
return result;
}
}
}
}
String::new()
}
fn check_identifier_read(
node: &Node,
source: &str,
analysis: &InitAnalysisResult,
cfg: &FunctionCfg,
body: &Node,
violations: &mut Vec<RuleViolation>,
reported: &mut HashSet<String>,
config: &init_state::InitAnalysisConfig,
asm_ranges: &[(usize, usize)],
) {
let var_name = get_node_text(node, source).to_string();
if !analysis.tracked_vars.contains(&var_name) {
return;
}
if reported.contains(&var_name) {
return;
}
if !is_read_context(node, source, &config.cross_file_output_params, asm_ranges) {
return;
}
if crate::analyze::macro_semantics::is_macro_output_arg(node, source) {
return;
}
if is_function_macro_output_arg(node, source, &config.macro_output_params) {
return;
}
let info = match init_state::get_var_info_at_with_config(
analysis,
cfg,
body,
source,
&var_name,
node.start_byte(),
config,
) {
Some(i) => i,
None => return,
};
if info.is_unsigned_char {
return;
}
if !info.state.is_unsafe() {
return;
}
if info.is_array {
if let Some(parent) = node.parent() {
if parent.kind() == "assignment_expression" {
if let Some(right) = parent.child_by_field_name("right") {
if right.id() == node.id() {
return;
}
}
}
if parent.kind() == "argument_list" {
if let Some(call_expr) = parent.parent() {
if call_expr.kind() == "call_expression" {
if let Some(func) = call_expr.child_by_field_name("function") {
let fname = get_node_text(&func, source).to_string();
if init_state::match_initializing_function(&fname).is_none()
&& !init_state::is_non_initializing_function(&fname)
{
return;
}
}
}
}
}
}
}
if matches!(info.state, InitState::MaybeUninitialized) {
if let Some(read_guard) = enclosing_ifdef_guard_key(node, body, source) {
if all_write_sites_ifdef_correlated(body, &var_name, &read_guard, source) {
return;
}
}
if has_macro_shadow_definition(body, &var_name, source) {
return;
}
}
reported.insert(var_name.clone());
let message = if info.is_static {
format!(
"Static variable '{}' used without explicit initialization",
var_name
)
} else if matches!(info.state, InitState::MaybeUninitialized) {
format!(
"Variable '{}' may be used uninitialized (not assigned on all paths)",
var_name
)
} else {
format!("Variable '{}' is used uninitialized", var_name)
};
violations.push(RuleViolation {
rule_id: "EXP33-C".to_string(),
severity: Severity::High,
message,
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(format!(
"Initialize '{}' before use, e.g., at its declaration",
var_name
)),
..Default::default()
});
}
fn check_deref_read(
node: &Node,
source: &str,
analysis: &InitAnalysisResult,
cfg: &FunctionCfg,
body: &Node,
violations: &mut Vec<RuleViolation>,
reported: &mut HashSet<String>,
config: &init_state::InitAnalysisConfig,
) {
let var_name = if let Some(arg) = node.child_by_field_name("argument") {
if arg.kind() == "identifier" {
get_node_text(&arg, source).to_string()
} else {
return;
}
} else {
return;
};
if !analysis.tracked_vars.contains(&var_name) || reported.contains(&var_name) {
return;
}
if !is_deref_read_context(node) {
return;
}
if let Some(parent) = node.parent() {
if has_sizeof_or_alignof_ancestor(parent) {
return;
}
}
let info = match init_state::get_var_info_at_with_config(
analysis,
cfg,
body,
source,
&var_name,
node.start_byte(),
config,
) {
Some(i) => i,
None => return,
};
if info.is_static {
return;
}
if info.state.is_unsafe() {
reported.insert(var_name.clone());
violations.push(RuleViolation {
rule_id: "EXP33-C".to_string(),
severity: Severity::High,
message: format!("Dereference of uninitialized pointer '{}'", var_name),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(format!(
"Initialize pointer '{}' before dereferencing",
var_name
)),
..Default::default()
});
} else if info.state.is_content_unsafe() {
reported.insert(var_name.clone());
violations.push(RuleViolation {
rule_id: "EXP33-C".to_string(),
severity: Severity::High,
message: format!(
"Reading from '{}' which points to uninitialized memory (allocated without initialization)",
var_name
),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(
"Use calloc() instead of malloc(), or memset() after allocation".to_string(),
),
..Default::default()
});
}
}
fn check_subscript_read(
node: &Node,
source: &str,
analysis: &InitAnalysisResult,
cfg: &FunctionCfg,
body: &Node,
violations: &mut Vec<RuleViolation>,
reported: &mut HashSet<String>,
config: &init_state::InitAnalysisConfig,
) {
let var_name = {
let mut name = String::new();
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" {
name = get_node_text(&child, source).to_string();
break;
}
if child.kind() == "field_expression" {
name = extract_root_identifier(&child, source);
break;
}
}
}
name
};
if var_name.is_empty()
|| !analysis.tracked_vars.contains(&var_name)
|| reported.contains(&var_name)
{
return;
}
if !is_subscript_read_context(node, source) {
return;
}
if let Some(parent) = node.parent() {
if has_sizeof_or_alignof_ancestor(parent) {
return;
}
}
let info = match init_state::get_var_info_at_with_config(
analysis,
cfg,
body,
source,
&var_name,
node.start_byte(),
config,
) {
Some(i) => i,
None => return,
};
if info.is_unsigned_char {
return;
}
if info.is_static {
return;
}
let content_unsafe = matches!(
info.state,
InitState::Uninitialized | InitState::MallocUninitialized
);
if content_unsafe {
reported.insert(var_name.clone());
violations.push(RuleViolation {
rule_id: "EXP33-C".to_string(),
severity: Severity::High,
message: format!(
"Reading from '{}' which may contain uninitialized data",
var_name
),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(format!(
"Initialize the contents of '{}' before reading",
var_name
)),
..Default::default()
});
}
}
fn asm_call_ranges(body: &Node, source: &str) -> Vec<(usize, usize)> {
let mut ranges = Vec::new();
for call in query::find_descendants_of_kind(*body, "call_expression") {
let Some(func) = call.child_by_field_name("function") else {
continue;
};
if func.kind() != "identifier" {
continue;
}
let name = get_node_text(&func, source).to_ascii_lowercase();
if name == "asm" || name == "__asm" || name == "__asm__" {
ranges.push((call.start_byte(), call.end_byte()));
}
}
ranges
}
fn is_in_asm_call(node: &Node, asm_ranges: &[(usize, usize)]) -> bool {
let start = node.start_byte();
asm_ranges
.iter()
.any(|&(from, to)| start >= from && start < to)
}
fn is_read_context(
node: &Node,
source: &str,
cross_file_output_params: &HashMap<String, HashSet<usize>>,
asm_ranges: &[(usize, usize)],
) -> bool {
if is_in_asm_call(node, asm_ranges) {
return false;
}
if let Some(parent) = node.parent() {
if matches!(
parent.kind(),
"preproc_def" | "preproc_function_def" | "preproc_params" | "preproc_undef"
) {
return false;
}
}
if is_declarator_name_of_declaration(node) {
return false;
}
let parent = match node.parent() {
Some(p) => p,
None => return true,
};
if has_sizeof_or_alignof_ancestor(parent) {
return false;
}
match parent.kind() {
"assignment_expression" => is_read_in_assignment(&parent, node, source),
"augmented_assignment_expression" => true,
"declaration" | "init_declarator" => false,
"sizeof_expression" => false,
"pointer_expression" => is_read_in_pointer_expression(&parent, source),
"update_expression" => false,
"field_expression" => is_read_in_field_expression(&parent),
"subscript_expression" => false,
"parameter_declaration" => false,
"cast_expression" => !is_void_discard_cast(&parent, node, source),
"binary_expression" | "unary_expression" | "conditional_expression" => true,
"argument_list" => {
if is_misparsed_asm_output_operand(&parent, source) {
return false;
}
is_read_in_argument_list(node, &parent, source, cross_file_output_params)
}
"return_statement" => true,
"comma_expression" => true,
"gnu_asm_output_operand" => false,
_ => true,
}
}
fn is_declarator_name_of_declaration(node: &Node) -> bool {
let mut cur = *node;
loop {
let Some(parent) = cur.parent() else {
return false;
};
match parent.kind() {
"pointer_declarator" | "array_declarator" => {
if parent.child_by_field_name("declarator").map(|d| d.id()) != Some(cur.id()) {
return false; }
cur = parent;
}
"declaration" | "init_declarator" => return true,
_ => return false,
}
}
}
fn has_sizeof_or_alignof_ancestor(parent: Node) -> bool {
let mut ancestor = Some(parent);
let mut depth = 0;
while let Some(anc) = ancestor {
depth += 1;
if depth > 5 {
return false;
}
match anc.kind() {
"sizeof_expression" | "_Alignof" => return true,
"parenthesized_expression"
| "pointer_expression"
| "subscript_expression"
| "field_expression" => {
ancestor = anc.parent();
continue;
}
_ => return false,
}
}
false
}
fn is_read_in_assignment(parent: &Node, node: &Node, source: &str) -> bool {
let Some(left) = parent.child_by_field_name("left") else {
return true; };
if left.id() != node.id() {
return true; }
for i in 0..parent.child_count() {
if let Some(op) = parent.child(i) {
let op_text = get_node_text(&op, source);
if matches!(
op_text,
"+=" | "-=" | "*=" | "/=" | "%=" | "<<=" | ">>=" | "&=" | "|=" | "^="
) {
return true; }
}
}
false }
fn is_void_discard_cast(cast: &Node, node: &Node, source: &str) -> bool {
let Some(value) = cast.child_by_field_name("value") else {
return false;
};
if value.id() != node.id() {
return false;
}
let Some(type_node) = cast.child_by_field_name("type") else {
return false;
};
get_node_text(&type_node, source).trim() == "void"
}
fn is_read_in_pointer_expression(parent: &Node, source: &str) -> bool {
let text = get_node_text(parent, source);
if !text.starts_with('&') {
return true; }
let Some(arg_list) = parent.parent() else {
return false;
};
if arg_list.kind() != "argument_list" {
return false;
}
let Some(call) = arg_list.parent() else {
return false;
};
if call.kind() != "call_expression" {
return false;
}
let Some(func) = call.child_by_field_name("function") else {
return false;
};
let func_name = get_node_text(&func, source);
init_state::is_non_initializing_function(&func_name) }
fn is_read_in_field_expression(parent: &Node) -> bool {
let mut outer = *parent;
while let Some(next) = outer.parent() {
if next.kind() != "field_expression" && next.kind() != "subscript_expression" {
break;
}
outer = next;
}
let Some(grandparent) = outer.parent() else {
return true;
};
if grandparent.kind() != "assignment_expression" {
return true;
}
let Some(left) = grandparent.child_by_field_name("left") else {
return true;
};
left.id() != outer.id() }
fn is_misparsed_asm_output_operand(arg_list: &Node, source: &str) -> bool {
let Some(call_gp) = arg_list.parent() else {
return false;
};
if call_gp.kind() != "call_expression" {
return false;
}
let Some(func) = call_gp.child_by_field_name("function") else {
return false;
};
if func.kind() != "string_literal" {
return false;
}
get_node_text(&func, source).contains('=')
}
fn is_read_in_argument_list(
node: &Node,
arg_list: &Node,
source: &str,
cross_file_output_params: &HashMap<String, HashSet<usize>>,
) -> bool {
let call_expr = match arg_list.parent() {
Some(c) if c.kind() == "call_expression" => c,
_ => return true,
};
let func_name = match call_expr.child_by_field_name("function") {
Some(f) => get_node_text(&f, source).to_string(),
None => return true,
};
if func_name == "va_start" || func_name == "va_copy" {
if let Some(first_arg) = call_expr.child_by_field_name("arguments").and_then(|args| {
for i in 0..args.child_count() {
if let Some(c) = args.child(i) {
if c.kind() != "(" && c.kind() != ")" && c.kind() != "," {
return Some(c);
}
}
}
None
}) {
if contains_node(&first_arg, node) {
return false; }
}
return true;
}
let output_indices: HashSet<usize> = match init_state::match_initializing_function(&func_name) {
Some(base_name) => init_state::get_output_arg_indices(base_name)
.into_iter()
.collect(),
None => {
match cross_file_output_params.get(&func_name) {
Some(indices) if !indices.is_empty() => indices.clone(),
_ => return true,
}
}
};
if output_indices.is_empty() {
return true; }
let mut arg_idx = 0;
for i in 0..arg_list.child_count() {
if let Some(child) = arg_list.child(i) {
if child.kind() == "," || child.kind() == "(" || child.kind() == ")" {
continue;
}
if contains_node(&child, node) {
return !output_indices.contains(&arg_idx);
}
arg_idx += 1;
}
}
true }
fn contains_node(haystack: &Node, needle: &Node) -> bool {
let needle_id = needle.id();
query::find_first_descendant(*haystack, |n| n.id() == needle_id).is_some()
}
fn is_deref_read_context(node: &Node) -> bool {
let parent = match node.parent() {
Some(p) => p,
None => return true,
};
match parent.kind() {
"assignment_expression" => {
if let Some(left) = parent.child_by_field_name("left") {
left.id() != node.id()
} else {
true
}
}
_ => true,
}
}
fn is_subscript_read_context(node: &Node, source: &str) -> bool {
let mut current = *node;
for _ in 0..5 {
let parent = match current.parent() {
Some(p) => p,
None => return true,
};
match parent.kind() {
"assignment_expression" => {
if let Some(left) = parent.child_by_field_name("left") {
return left.id() != current.id();
}
return true;
}
"field_expression" => {
current = parent;
continue;
}
"pointer_expression" => {
let text = get_node_text(&parent, source);
if !text.starts_with('&') {
return true;
}
let Some(arg_list) = parent.parent() else {
return false;
};
if arg_list.kind() != "argument_list" {
return false;
}
let Some(call) = arg_list.parent() else {
return false;
};
if call.kind() != "call_expression" {
return false;
}
let Some(func) = call.child_by_field_name("function") else {
return false;
};
let func_name = get_node_text(&func, source);
return init_state::is_non_initializing_function(&func_name);
}
_ => return true,
}
}
true
}
fn body_calls_function(body: &Node, source: &str, name: &str) -> bool {
query::find_descendants_of_kind(*body, "call_expression")
.iter()
.any(|c| {
c.child_by_field_name("function")
.is_some_and(|f| get_node_text(&f, source) == name)
})
}
fn scan_realloc_wrappers(node: &Node, source: &str, wrappers: &mut HashSet<String>) {
for func_def in query::find_descendants_of_kind(*node, "function_definition") {
if let Some(body) = func_def.child_by_field_name("body") {
if body_calls_function(&body, source, "realloc")
&& !body_calls_function(&body, source, "memset")
{
if let Some(declarator) = func_def.child_by_field_name("declarator") {
let name = get_func_name(&declarator, source);
if !name.is_empty() {
wrappers.insert(name);
}
}
}
}
}
}
fn scan_conditionally_init_functions(
node: &Node,
source: &str,
result: &mut HashMap<String, HashSet<usize>>,
) {
for func_def in query::find_descendants_of_kind(*node, "function_definition") {
let cond_indices = get_conditional_init_param_indices(&func_def, source);
if !cond_indices.is_empty() {
if let Some(declarator) = func_def.child_by_field_name("declarator") {
let name = get_func_name(&declarator, source);
if !name.is_empty() {
result.insert(name, cond_indices);
}
}
}
}
}
fn get_conditional_init_param_indices(func_node: &Node, source: &str) -> HashSet<usize> {
let mut result = HashSet::new();
let body = match func_node.child_by_field_name("body") {
Some(b) => b,
None => return result,
};
let mut all_params: Vec<(usize, String, bool)> = Vec::new(); collect_param_list_with_indices(func_node, source, &mut all_params);
let is_param_deref = |node: &Node, param_name: &str| {
node.kind() == "pointer_expression"
&& node
.child_by_field_name("operator")
.is_some_and(|o| get_node_text(&o, source) == "*")
&& node.child_by_field_name("argument").is_some_and(|a| {
a.kind() == "identifier" && get_node_text(&a, source) == param_name
})
};
for (idx, param_name, is_pointer) in &all_params {
if !is_pointer {
continue;
}
let derefs_param = query::find_descendants_of_kind(body, "pointer_expression")
.iter()
.any(|p| is_param_deref(p, param_name));
if !derefs_param {
continue; }
let has_unconditional_write =
(0..body.child_count())
.filter_map(|i| body.child(i))
.any(|child| {
if child.kind() != "expression_statement" {
return false;
}
let Some(expr) = child.named_child(0) else {
return false;
};
expr.kind() == "assignment_expression"
&& expr
.child_by_field_name("left")
.is_some_and(|left| is_param_deref(&left, param_name))
});
if !has_unconditional_write {
result.insert(*idx);
}
}
result
}
fn collect_param_list_with_indices(
func_node: &Node,
source: &str,
params: &mut Vec<(usize, String, bool)>,
) {
let declarator = match func_node.child_by_field_name("declarator") {
Some(d) => d,
None => return,
};
let func_decl = match find_function_declarator_node(&declarator) {
Some(d) => d,
None => return,
};
for i in 0..func_decl.child_count() {
if let Some(child) = func_decl.child(i) {
if child.kind() == "parameter_list" {
let mut param_idx = 0;
for j in 0..child.child_count() {
if let Some(param) = child.child(j) {
if param.kind() == "parameter_declaration" {
let param_text = get_node_text(¶m, source);
let is_pointer = param_text.contains('*');
let name = get_declarator_name_from(¶m, source);
if !name.is_empty() {
params.push((param_idx, name, is_pointer));
}
param_idx += 1;
}
}
}
}
}
}
}
fn find_function_declarator_node<'a>(node: &Node<'a>) -> Option<Node<'a>> {
query::find_first_descendant(*node, |n| n.kind() == "function_declarator")
}
fn get_declarator_name_from(node: &Node, source: &str) -> String {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" {
return get_node_text(&child, source).to_string();
}
if child.kind() == "pointer_declarator" {
return get_declarator_name_from(&child, source);
}
}
}
String::new()
}
fn extract_root_identifier(node: &Node, source: &str) -> String {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" {
return get_node_text(&child, source).to_string();
}
if child.kind() == "field_expression" || child.kind() == "subscript_expression" {
return extract_root_identifier(&child, source);
}
}
}
String::new()
}
fn get_func_name(declarator: &Node, source: &str) -> String {
match declarator.kind() {
"identifier" => get_node_text(declarator, source).to_string(),
"function_declarator" | "pointer_declarator" => {
if let Some(inner) = declarator.child_by_field_name("declarator") {
get_func_name(&inner, source)
} else {
for i in 0..declarator.child_count() {
if let Some(child) = declarator.child(i) {
if child.kind() == "identifier" {
return get_node_text(&child, source).to_string();
}
}
}
String::new()
}
}
_ => String::new(),
}
}