use crate::utility::cert_c::{ast_utils, overflow_helpers};
use lang_parsing_substrate::query;
use std::collections::{HashMap, HashSet};
use tree_sitter::Node;
#[derive(Debug, Default, Clone)]
pub struct ObjectFrame {
pub pointer_vars: HashSet<String>,
pub array_objects: HashSet<String>,
pub pointer_members: HashSet<String>,
}
impl ObjectFrame {
pub fn new() -> Self {
Self::default()
}
pub fn note_declared(&mut self, name: &str, is_array: bool) {
self.pointer_vars.insert(name.to_string());
if is_array {
self.array_objects.insert(name.to_string());
}
}
pub fn record_declaration(&mut self, node: &Node, source: &str) {
for declared in declared_pointers(node, source) {
self.note_declared(&declared.name, declared.is_array);
}
}
pub fn record_parameter(&mut self, node: &Node, source: &str) -> Option<String> {
if !is_pointer_or_array_parameter(node) {
return None;
}
let declarator = node.child_by_field_name("declarator")?;
let name = ast_utils::get_identifier_from_declarator(&declarator, source);
if name.is_empty() {
return None;
}
self.pointer_vars.insert(name.clone());
Some(name)
}
pub fn record_pointer_members(
&mut self,
func: &Node,
source: &str,
struct_field_types: &HashMap<String, HashMap<String, String>>,
) {
if struct_field_types.is_empty() {
return;
}
let type_map = overflow_helpers::collect_variable_types(func, source);
for field in query::find_descendants_of_kind(*func, "field_expression") {
let resolved = ast_utils::resolve_field_expression_type(
&field,
source,
&type_map,
struct_field_types,
);
if resolved.is_some_and(|field_type| field_type.trim_end().ends_with('*')) {
self.pointer_members
.insert(ast_utils::get_node_text(&field, source).to_string());
}
}
}
pub fn collect_file_scope(&mut self, node: &Node, source: &str) {
match node.kind() {
"translation_unit" | "preproc_ifdef" | "preproc_if" | "preproc_else"
| "preproc_elif" => {
for i in 0..node.child_count() {
let Some(child) = node.child(i) else { continue };
if child.kind() == "declaration" {
self.record_declaration(&child, source);
} else if child.kind().starts_with("preproc_") {
self.collect_file_scope(&child, source);
}
}
}
_ => {}
}
}
pub fn collect_function(&mut self, func: &Node, source: &str) {
for n in query::find_descendants_of_kinds(*func, &["declaration", "parameter_declaration"])
{
match n.kind() {
"declaration" => self.record_declaration(&n, source),
"parameter_declaration" => {
self.record_parameter(&n, source);
}
_ => {}
}
}
}
pub fn argument_object_base(&self, node: &Node, source: &str) -> Option<String> {
let text = |n: &Node| source[n.start_byte()..n.end_byte()].to_string();
match node.kind() {
"identifier" => {
let name = text(node);
self.array_objects.contains(&name).then_some(name)
}
"string_literal" | "compound_literal_expression" => {
Some(format!("{}@{}", text(node), node.start_byte()))
}
"cast_expression" => {
self.argument_object_base(&node.child_by_field_name("value")?, source)
}
"binary_expression" => {
self.argument_object_base(&node.child_by_field_name("left")?, source)
}
"call_expression" => allocation_object(node, source),
"pointer_expression" | "unary_expression" => {
let operator = node.child(0)?;
if ast_utils::get_node_text(&operator, source) != "&" {
return None;
}
self.object_of_lvalue(&node.child_by_field_name("argument")?, source)
}
_ => None,
}
}
fn object_of_lvalue(&self, node: &Node, source: &str) -> Option<String> {
let text = |n: &Node| source[n.start_byte()..n.end_byte()].to_string();
match node.kind() {
"identifier" => {
let name = text(node);
let names_storage =
self.array_objects.contains(&name) || !self.pointer_vars.contains(&name);
names_storage.then_some(name)
}
"field_expression" => {
let path = text(node);
(!self.pointer_members.contains(&path)).then_some(path)
}
"subscript_expression" => {
self.object_of_lvalue(&node.child_by_field_name("argument")?, source)
}
_ => None,
}
}
}
pub struct DeclaredPointer<'tree> {
pub name: String,
pub is_array: bool,
pub init_declarator: Option<Node<'tree>>,
}
pub fn declared_pointers<'tree>(node: &Node<'tree>, source: &str) -> Vec<DeclaredPointer<'tree>> {
let mut declared = Vec::new();
for i in 0..node.child_count() {
let Some(child) = node.child(i) else { continue };
let declarator = if child.kind() == "init_declarator" {
child.child_by_field_name("declarator")
} else if is_pointer_declarator(&child) {
Some(child)
} else {
None
};
let Some(declarator) = declarator else {
continue;
};
if !is_pointer_declarator(&declarator) {
continue;
}
let name = ast_utils::get_identifier_from_declarator(&declarator, source);
if name.is_empty() {
continue;
}
declared.push(DeclaredPointer {
name,
is_array: declarator.kind() == "array_declarator",
init_declarator: (child.kind() == "init_declarator").then_some(child),
});
}
declared
}
pub fn is_pointer_declarator(declarator: &Node) -> bool {
matches!(declarator.kind(), "pointer_declarator" | "array_declarator")
}
pub fn is_pointer_or_array_parameter(param_node: &Node) -> bool {
if let Some(declarator) = param_node.child_by_field_name("declarator") {
if is_pointer_declarator(&declarator) {
return true;
}
for i in 0..declarator.child_count() {
if let Some(child) = declarator.child(i) {
if is_pointer_declarator(&child) {
return true;
}
}
}
}
param_node
.child_by_field_name("type")
.is_some_and(|type_node| type_node.kind() == "pointer_declarator")
}
fn allocation_object(node: &Node, source: &str) -> Option<String> {
let func_node = node.child_by_field_name("function")?;
let func_name = ast_utils::get_node_text(&func_node, source);
let canonical = func_name.strip_prefix("os_").unwrap_or(func_name);
matches!(
canonical,
"malloc" | "calloc" | "realloc" | "aligned_alloc" | "alloca"
)
.then(|| format!("alloc@{}", node.start_byte()))
}
pub fn argument_nodes<'tree>(args: &Node<'tree>) -> Vec<Node<'tree>> {
(0..args.child_count())
.filter_map(|i| args.child(i))
.filter(|child| child.is_named() && child.kind() != "comment")
.collect()
}
pub fn distinct_object_pairs(
frame: &ObjectFrame,
args: &[Node],
source: &str,
) -> Vec<(usize, usize)> {
let bases: Vec<Option<String>> = args
.iter()
.map(|arg| frame.argument_object_base(arg, source))
.collect();
let mut pairs = Vec::new();
for (left, left_base) in bases.iter().enumerate() {
let Some(left_base) = left_base else { continue };
for (right, right_base) in bases.iter().enumerate().skip(left + 1) {
if right_base.as_ref().is_some_and(|base| base != left_base) {
pairs.push((left, right));
}
}
}
pairs
}