use super::super::{CertRule, RuleViolation};
use crate::analyze::cfg::{self as cfg_mod, FunctionCfg};
use crate::analyze::context::ProjectContext;
use crate::analyze::dataflow::find_node_at_range;
use crate::analyze::function_summary::FunctionSummary;
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::{self, get_node_text};
use lang_parsing_substrate::query;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet, VecDeque};
use tree_sitter::Node;
pub struct Mem01C {
function_cfgs: RefCell<HashMap<usize, FunctionCfg>>,
cross_file_summaries: RefCell<HashMap<String, FunctionSummary>>,
}
struct AddressOfCallContext {
read_only_params: HashMap<String, HashSet<usize>>,
}
impl Mem01C {
pub fn new() -> Self {
Self {
function_cfgs: RefCell::new(HashMap::new()),
cross_file_summaries: RefCell::new(HashMap::new()),
}
}
fn build_read_only_params(&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
}
}
#[derive(Debug, PartialEq)]
enum PtrAction {
Reassigned,
FreedAgain,
Used,
Irrelevant,
}
impl CertRule for Mem01C {
fn rule_id(&self) -> &'static str {
"MEM01-C"
}
fn description(&self) -> &'static str {
"Store a new value in pointers immediately after free()"
}
fn severity(&self) -> Severity {
Severity::High
}
fn category(&self) -> RuleCategory {
RuleCategory::Recommendation
}
fn cert_id(&self) -> &'static str {
"MEM01-C"
}
fn set_function_cfgs(&self, cfgs: &HashMap<usize, FunctionCfg>) {
*self.function_cfgs.borrow_mut() = cfgs.clone();
}
fn set_project_context(&self, context: &ProjectContext) {
*self.cross_file_summaries.borrow_mut() = context.function_summaries.clone();
}
fn scan(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
self.check_node(node, source, violations);
}
}
impl Mem01C {
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "function_definition" {
self.check_function(node, source, violations);
return; }
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.check_node(&child, source, violations);
}
}
}
fn check_function(&self, func_node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
let body = match func_node.child_by_field_name("body") {
Some(b) => b,
None => return,
};
let addr_ctx = AddressOfCallContext {
read_only_params: self.build_read_only_params(),
};
let cfgs = self.function_cfgs.borrow();
let inline_cfg;
let cfg = if let Some(c) = cfgs.get(&func_node.start_byte()) {
c
} else if let Some(c) = cfg_mod::build_function_cfg(func_node, source) {
inline_cfg = c;
&inline_cfg
} else {
return; };
let free_calls = self.collect_free_calls(&body, source);
for (ptr_name, free_byte, line, column) in free_calls {
if self.ptr_has_post_free_use(cfg, &body, source, &ptr_name, free_byte, &addr_ctx) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::High,
message: format!(
"Pointer '{}' is used or freed again after free() without reassignment",
ptr_name
),
file_path: String::new(),
line,
column,
suggestion: Some(format!(
"Set '{} = NULL;' after free({}) or remove the subsequent use",
ptr_name, ptr_name
)),
..Default::default()
});
}
}
}
fn collect_free_calls(&self, node: &Node, source: &str) -> Vec<(String, usize, usize, usize)> {
let mut results = Vec::new();
for node in query::find_descendants_of_kind(*node, "call_expression") {
if let Some(func) = node.child_by_field_name("function") {
let func_name = get_node_text(&func, source);
if func_name == "free" {
if let Some(ptr_name) = self.extract_free_arg(&node, source) {
let pos = node.start_position();
results.push((ptr_name, node.start_byte(), pos.row + 1, pos.column + 1));
}
}
}
}
results
}
fn extract_free_arg(&self, call_node: &Node, source: &str) -> Option<String> {
let args = call_node.child_by_field_name("arguments")?;
for i in 0..args.child_count() {
if let Some(arg) = args.child(i) {
if arg.kind() != "(" && arg.kind() != ")" && arg.kind() != "," {
return Some(get_node_text(&arg, source).to_string());
}
}
}
None
}
fn ptr_has_post_free_use(
&self,
cfg: &FunctionCfg,
body: &Node,
source: &str,
ptr_name: &str,
free_byte: usize,
addr_ctx: &AddressOfCallContext,
) -> bool {
let containing_block = match find_block_containing(cfg, free_byte) {
Some(b) => b,
None => return true, };
match self.scan_block_from(
containing_block,
body,
source,
ptr_name,
free_byte,
addr_ctx,
) {
Some(PtrAction::FreedAgain) | Some(PtrAction::Used) => return true,
Some(PtrAction::Reassigned) => return false,
_ => {} }
let mut visited: HashSet<usize> = HashSet::new();
visited.insert(containing_block.id);
let mut queue: VecDeque<usize> = VecDeque::new();
for (succ_id, _edge) in cfg.successors(containing_block.id) {
queue.push_back(succ_id);
}
while let Some(block_id) = queue.pop_front() {
if !visited.insert(block_id) {
continue; }
let block = match cfg.get_block(block_id) {
Some(b) => b,
None => continue,
};
match self.scan_block_all(block, body, source, ptr_name, addr_ctx) {
Some(PtrAction::FreedAgain) | Some(PtrAction::Used) => return true,
Some(PtrAction::Reassigned) => continue, _ => {
for (succ_id, _edge) in cfg.successors(block_id) {
queue.push_back(succ_id);
}
}
}
}
false }
fn scan_block_from(
&self,
block: &crate::analyze::cfg::BasicBlock,
body: &Node,
source: &str,
ptr_name: &str,
after_byte: usize,
addr_ctx: &AddressOfCallContext,
) -> Option<PtrAction> {
for &(start, end) in &block.statements {
if start <= after_byte {
continue;
}
if let Some(stmt_node) = find_node_at_range(body, start, end) {
let action = classify_stmt_for_ptr(&stmt_node, source, ptr_name, addr_ctx);
if action != PtrAction::Irrelevant {
return Some(action);
}
}
}
None
}
fn scan_block_all(
&self,
block: &crate::analyze::cfg::BasicBlock,
body: &Node,
source: &str,
ptr_name: &str,
addr_ctx: &AddressOfCallContext,
) -> Option<PtrAction> {
for &(start, end) in &block.statements {
if let Some(stmt_node) = find_node_at_range(body, start, end) {
let action = classify_stmt_for_ptr(&stmt_node, source, ptr_name, addr_ctx);
if action != PtrAction::Irrelevant {
return Some(action);
}
}
}
None
}
}
fn classify_stmt_for_ptr(
node: &Node,
source: &str,
ptr_name: &str,
addr_ctx: &AddressOfCallContext,
) -> PtrAction {
match node.kind() {
"expression_statement" => {
if let Some(expr) = node.child(0) {
classify_expr_for_ptr(&expr, source, ptr_name, addr_ctx)
} else {
PtrAction::Irrelevant
}
}
"return_statement" => {
if node.child_count() > 1 {
if let Some(expr) = node.child(1) {
if subtree_contains_identifier(&expr, source, ptr_name) {
return PtrAction::Used;
}
}
}
PtrAction::Irrelevant
}
"declaration" => {
if let Some(declarator) = find_declarator_name(node, source) {
if declarator == ptr_name {
return PtrAction::Reassigned;
}
}
if subtree_contains_identifier(node, source, ptr_name) {
return PtrAction::Used;
}
PtrAction::Irrelevant
}
"parenthesized_expression" => {
if subtree_assigns_identifier(node, source, ptr_name) {
return PtrAction::Reassigned;
}
if let Some(action) = subtree_address_of_call_action(node, source, ptr_name, addr_ctx) {
return action;
}
if subtree_contains_identifier(node, source, ptr_name) {
PtrAction::Used
} else {
PtrAction::Irrelevant
}
}
_ => {
if let Some(action) = subtree_address_of_call_action(node, source, ptr_name, addr_ctx) {
action
} else if subtree_contains_identifier(node, source, ptr_name) {
PtrAction::Used
} else {
PtrAction::Irrelevant
}
}
}
}
fn classify_expr_for_ptr(
expr: &Node,
source: &str,
ptr_name: &str,
addr_ctx: &AddressOfCallContext,
) -> PtrAction {
match expr.kind() {
"assignment_expression" => {
if let Some(left) = expr.child_by_field_name("left") {
let left_text = get_node_text(&left, source);
if left_text == ptr_name {
return PtrAction::Reassigned;
}
}
if subtree_contains_identifier(expr, source, ptr_name) {
return PtrAction::Used;
}
PtrAction::Irrelevant
}
"call_expression" => {
if let Some(func) = expr.child_by_field_name("function") {
let func_name = get_node_text(&func, source);
if func_name == "free" {
if let Some(args) = expr.child_by_field_name("arguments") {
if arg_list_contains_identifier(&args, source, ptr_name) {
return PtrAction::FreedAgain;
}
}
}
}
if let Some(action) = call_address_of_action(expr, source, ptr_name, addr_ctx) {
return action;
}
if let Some(args) = expr.child_by_field_name("arguments") {
if arg_list_contains_identifier(&args, source, ptr_name) {
return PtrAction::Used;
}
}
PtrAction::Irrelevant
}
"update_expression" => {
if subtree_contains_identifier(expr, source, ptr_name) {
return PtrAction::Used;
}
PtrAction::Irrelevant
}
_ => {
if subtree_contains_identifier(expr, source, ptr_name) {
PtrAction::Used
} else {
PtrAction::Irrelevant
}
}
}
}
fn subtree_contains_identifier(node: &Node, source: &str, name: &str) -> bool {
query::find_first_descendant(*node, |n| {
n.kind() == "identifier" && get_node_text(&n, source) == name
})
.is_some()
}
fn subtree_assigns_identifier(node: &Node, source: &str, name: &str) -> bool {
query::find_first_descendant(*node, |n| {
if n.kind() != "assignment_expression" {
return false;
}
n.child_by_field_name("left")
.map(|left| get_node_text(&left, source) == name)
.unwrap_or(false)
})
.is_some()
}
fn subtree_address_of_call_action(
node: &Node,
source: &str,
name: &str,
addr_ctx: &AddressOfCallContext,
) -> Option<PtrAction> {
for call in query::find_descendants_of_kind(*node, "call_expression") {
if let Some(action) = call_address_of_action(&call, source, name, addr_ctx) {
return Some(action);
}
}
None
}
fn call_address_of_action(
call: &Node,
source: &str,
name: &str,
addr_ctx: &AddressOfCallContext,
) -> Option<PtrAction> {
let args = call.child_by_field_name("arguments")?;
let idx = address_of_arg_index(&args, source, name)?;
let func_name = call
.child_by_field_name("function")
.map(|f| get_node_text(&f, source).to_string())
.unwrap_or_default();
if addr_ctx
.read_only_params
.get(&func_name)
.is_some_and(|indices| indices.contains(&idx))
{
Some(PtrAction::Used)
} else {
Some(PtrAction::Reassigned)
}
}
fn arg_list_contains_identifier(args: &Node, source: &str, name: &str) -> bool {
for i in 0..args.child_count() {
if let Some(arg) = args.child(i) {
if arg.kind() != "(" && arg.kind() != ")" && arg.kind() != "," {
if subtree_contains_identifier(&arg, source, name) {
return true;
}
}
}
}
false
}
fn address_of_arg_index(args: &Node, source: &str, name: &str) -> Option<usize> {
let mut idx = 0;
for i in 0..args.child_count() {
let Some(arg) = args.child(i) else { continue };
if matches!(arg.kind(), "(" | ")" | ",") {
continue;
}
if arg.kind() == "pointer_expression" {
let is_address_of = arg
.child_by_field_name("operator")
.map(|op| get_node_text(&op, source) == "&")
.unwrap_or(false);
if is_address_of {
if let Some(operand) = arg.child_by_field_name("argument") {
if operand.kind() == "identifier" && get_node_text(&operand, source) == name {
return Some(idx);
}
}
}
}
idx += 1;
}
None
}
fn find_declarator_name(decl: &Node, source: &str) -> Option<String> {
for i in 0..decl.child_count() {
if let Some(child) = decl.child(i) {
match child.kind() {
"init_declarator" => {
if let Some(d) = child.child_by_field_name("declarator") {
return extract_identifier_from_declarator(&d, source);
}
}
"pointer_declarator" | "array_declarator" | "identifier" => {
return extract_identifier_from_declarator(&child, source);
}
_ => {}
}
}
}
None
}
fn extract_identifier_from_declarator(node: &Node, source: &str) -> Option<String> {
let name = ast_utils::get_identifier_from_declarator(node, source);
if name.is_empty() {
None
} else {
Some(name)
}
}
fn find_block_containing(
cfg: &FunctionCfg,
byte_offset: usize,
) -> Option<&crate::analyze::cfg::BasicBlock> {
for block in &cfg.blocks {
for &(start, end) in &block.statements {
if byte_offset >= start && byte_offset < end {
return Some(block);
}
}
}
cfg.blocks.iter().find(|block| {
block.byte_range.0 > 0
&& byte_offset >= block.byte_range.0
&& byte_offset < block.byte_range.1
})
}