#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct BufferInfo {
pub name: String,
pub size: BufferSize,
pub element_type: String,
pub allocation_line: usize,
pub alloc_bytes: Option<usize>,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum BufferSize {
Static(usize),
DynamicCalculated(usize),
Dynamic(String),
Symbolic(String),
Unknown,
}
pub fn evaluate_simple_arithmetic(expr: &str) -> Option<isize> {
let expr = expr.trim();
if expr.contains('-') && !expr.starts_with('-') {
let parts: Vec<&str> = expr.split('-').collect();
if parts.len() == 2 {
let a: isize = parts[0].trim().parse().ok()?;
let b: isize = parts[1].trim().parse().ok()?;
return Some(a - b);
}
}
if expr.contains('+') {
let parts: Vec<&str> = expr.split('+').collect();
if parts.len() == 2 {
let a: isize = parts[0].trim().parse().ok()?;
let b: isize = parts[1].trim().parse().ok()?;
return Some(a + b);
}
}
None
}
pub fn extract_numeric_value(s: &str) -> Option<usize> {
let trimmed = s.trim();
let inner = if trimmed.starts_with('(') && trimmed.ends_with(')') {
&trimmed[1..trimmed.len() - 1]
} else {
trimmed
};
if let Ok(v) = inner.parse() {
return Some(v);
}
let result = evaluate_simple_arithmetic(inner)?;
if result >= 0 {
Some(result as usize)
} else {
None
}
}
pub fn extract_sizeof_value(s: &str) -> Option<usize> {
if !s.contains("sizeof") {
return None;
}
let type_sizes = [
("wchar_t", 4),
("int", 4),
("char", 1),
("short", 2),
("long", 8),
("float", 4),
("double", 8),
("void*", 8),
("int*", 8),
("char*", 8),
("wchar_t*", 8),
];
for (type_name, size) in &type_sizes {
if s.contains(type_name) {
return Some(*size);
}
}
Some(8)
}
pub const ALLOC_FUNCTIONS: &[&str] =
&["malloc", "calloc", "realloc", "alloca", "_alloca", "ALLOCA"];
pub fn alloc_call_element_count(func_name: &str, args_text: &str) -> Option<usize> {
let size_expr = if func_name == "calloc" {
args_text.split(',').next().unwrap_or(args_text)
} else {
args_text
};
match calculate_malloc_size(size_expr)? {
BufferSize::Static(n) | BufferSize::DynamicCalculated(n) => Some(n),
_ => None,
}
}
pub fn calculate_malloc_size(malloc_args: &str) -> Option<BufferSize> {
let trimmed = malloc_args.trim();
if let Some(size) = extract_numeric_value(trimmed) {
return Some(BufferSize::DynamicCalculated(size));
}
if trimmed.contains('*') && trimmed.contains("sizeof") {
if let Some(mult_pos) = trimmed.find('*') {
let count_str = &trimmed[..mult_pos].trim();
let sizeof_str = &trimmed[mult_pos + 1..].trim();
let count = extract_numeric_value(count_str);
let _sizeof_val = extract_sizeof_value(sizeof_str);
if let Some(c) = count {
return Some(BufferSize::DynamicCalculated(c));
}
return Some(BufferSize::Dynamic(trimmed.to_string()));
}
}
if let Some(sizeof_val) = extract_sizeof_value(trimmed) {
return Some(BufferSize::DynamicCalculated(sizeof_val));
}
Some(BufferSize::Dynamic(trimmed.to_string()))
}
pub fn calculate_alloc_bytes(malloc_args: &str) -> Option<usize> {
let trimmed = malloc_args.trim();
if let Some(size) = extract_numeric_value(trimmed) {
return Some(size);
}
if trimmed.contains('*') && trimmed.contains("sizeof") {
if let Some(mult_pos) = trimmed.find('*') {
let count_str = trimmed[..mult_pos].trim();
let sizeof_str = trimmed[mult_pos + 1..].trim();
let count = extract_numeric_value(count_str);
let sizeof_val = extract_sizeof_value(sizeof_str);
if let (Some(c), Some(s)) = (count, sizeof_val) {
return Some(c * s);
}
}
}
if let Some(sizeof_val) = extract_sizeof_value(trimmed) {
return Some(sizeof_val);
}
None
}
pub fn parse_simple_size_expr(expr: &str) -> Option<usize> {
let expr = expr.trim();
if let Ok(n) = expr.parse::<usize>() {
return Some(n);
}
if let Some(pos) = expr.rfind('-') {
if pos > 0 {
let left = expr[..pos].trim();
let right = expr[pos + 1..].trim();
if let (Ok(l), Ok(r)) = (left.parse::<usize>(), right.parse::<usize>()) {
return l.checked_sub(r);
}
}
}
None
}
pub fn enclosing_function_lines(node: &tree_sitter::Node) -> Option<(usize, usize)> {
let mut current = node.parent();
while let Some(n) = current {
if n.kind() == "function_definition" {
return Some((n.start_position().row, n.end_position().row));
}
current = n.parent();
}
None
}
pub fn memset_content_length(
var_name: &str,
source: &str,
call_node: &tree_sitter::Node,
) -> Option<usize> {
let (fn_start, fn_end) = enclosing_function_lines(call_node)?;
let call_line = call_node.start_position().row;
let lines: Vec<&str> = source.lines().collect();
let mut best_size: Option<usize> = None;
for i in fn_start..std::cmp::min(call_line, fn_end + 1) {
if i >= lines.len() {
break;
}
let trimmed = lines[i].trim();
let call_start = if let Some(pos) = trimmed.find("wmemset(") {
pos + "wmemset(".len()
} else if let Some(pos) = trimmed.find("memset(") {
pos + "memset(".len()
} else {
continue;
};
let after_call = &trimmed[call_start..];
let close_paren = match after_call.rfind(')') {
Some(p) => p,
None => continue,
};
let args_str = &after_call[..close_paren];
let parts: Vec<&str> = args_str.splitn(3, ',').collect();
if parts.len() != 3 {
continue;
}
if parts[0].trim() != var_name {
continue;
}
let size = match parse_simple_size_expr(parts[2].trim()) {
Some(s) => s,
None => continue,
};
let null_term_prefix = format!("{}[", var_name);
let search_end = std::cmp::min(i + 4, lines.len());
for next_line in lines[(i + 1)..search_end].iter().map(|l| l.trim()) {
if next_line.contains(&null_term_prefix)
&& (next_line.contains("'\\0'") || next_line.contains("L'\\0'"))
{
best_size = Some(match best_size {
Some(prev) => std::cmp::max(prev, size),
None => size,
});
break;
}
}
}
best_size
}
pub fn eval_arith(a: Option<usize>, op: Option<&str>, b: Option<usize>) -> Option<usize> {
match (a, op, b) {
(Some(a), Some("+"), Some(b)) => a.checked_add(b),
(Some(a), Some("-"), Some(b)) => a.checked_sub(b),
(Some(a), Some("*"), Some(b)) => a.checked_mul(b),
(Some(a), None, None) => Some(a),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_arithmetic_two_operands_only() {
assert_eq!(evaluate_simple_arithmetic("10 + 1"), Some(11));
assert_eq!(evaluate_simple_arithmetic("50 - 1"), Some(49));
assert_eq!(evaluate_simple_arithmetic("1 + 2 + 3"), None);
assert_eq!(evaluate_simple_arithmetic("5 * 4"), None);
}
#[test]
fn numeric_value_strips_parens_and_evaluates() {
assert_eq!(extract_numeric_value("100"), Some(100));
assert_eq!(extract_numeric_value("(10+1)"), Some(11));
assert_eq!(extract_numeric_value("1-5"), None);
}
#[test]
fn sizeof_uses_fixedwidth_table_with_pointer_default() {
assert_eq!(extract_sizeof_value("sizeof(int)"), Some(4));
assert_eq!(extract_sizeof_value("sizeof(char)"), Some(1));
assert_eq!(extract_sizeof_value("sizeof(wchar_t)"), Some(4));
assert_eq!(extract_sizeof_value("sizeof(struct foo)"), Some(8));
assert_eq!(extract_sizeof_value("42"), None);
}
#[test]
fn malloc_size_returns_element_count_bytes_returns_total() {
assert!(matches!(
calculate_malloc_size("5 * sizeof(int)"),
Some(BufferSize::DynamicCalculated(5))
));
assert_eq!(calculate_alloc_bytes("5 * sizeof(int)"), Some(20));
assert!(matches!(
calculate_malloc_size("n * sizeof(char)"),
Some(BufferSize::Dynamic(_))
));
}
#[test]
fn eval_arith_matches_checked_semantics() {
assert_eq!(eval_arith(Some(10), Some("+"), Some(1)), Some(11));
assert_eq!(eval_arith(Some(10), Some("-"), Some(3)), Some(7));
assert_eq!(eval_arith(Some(4), Some("*"), Some(3)), Some(12));
assert_eq!(eval_arith(Some(8), None, None), Some(8));
assert_eq!(eval_arith(Some(3), Some("-"), Some(5)), None);
assert_eq!(eval_arith(Some(usize::MAX), Some("+"), Some(1)), None);
assert_eq!(eval_arith(None, Some("+"), Some(1)), None);
assert_eq!(eval_arith(Some(1), Some("/"), Some(1)), None);
}
}