#[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 sizeof_type_bytes(type_name: &str) -> Option<usize> {
let t = type_name.trim();
if t.ends_with('*') {
return Some(8);
}
match t {
"int64_t" | "uint64_t" => Some(8),
"int32_t" | "uint32_t" => Some(4),
"int16_t" | "uint16_t" => Some(2),
"int8_t" | "uint8_t" => Some(1),
"size_t" => Some(8),
"wchar_t" => Some(4),
"long long" | "unsigned long long" | "signed long long" => Some(8),
"long" | "unsigned long" | "signed long" => Some(8),
"int" | "unsigned int" | "signed int" => Some(4),
"char" | "unsigned char" | "signed char" => Some(1),
"short" | "unsigned short" | "signed short" => Some(2),
"float" => Some(4),
"double" => Some(8),
_ => None,
}
}
const TYPE_SIZE_SUBSTRING_ORDER: &[(&str, usize)] = &[
("int64_t", 8),
("uint64_t", 8),
("int32_t", 4),
("uint32_t", 4),
("int16_t", 2),
("uint16_t", 2),
("int8_t", 1),
("uint8_t", 1),
("size_t", 8),
("wchar_t", 4),
("long long", 8),
("long", 8),
("int", 4),
("char", 1),
("short", 2),
("float", 4),
("double", 8),
];
pub fn extract_sizeof_value(s: &str) -> Option<usize> {
if !s.contains("sizeof") {
return None;
}
if let Some(after_sizeof) = s.split_once("sizeof(").map(|(_, rest)| rest) {
if let Some(type_name) = after_sizeof.split(')').next() {
if let Some(size) = sizeof_type_bytes(type_name) {
return Some(size);
}
}
}
for (type_name, size) in TYPE_SIZE_SUBSTRING_ORDER {
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 let Some(caps) = regex::Regex::new(r"^\(\s*(\d+)\s*([+*\-])\s*(\d+)\s*\)\s*\*\s*sizeof")
.ok()
.and_then(|re| re.captures(trimmed))
{
let a = caps[1].parse::<usize>().ok();
let op = caps.get(2).map(|m| m.as_str());
let b = caps.get(3).and_then(|m| m.as_str().parse::<usize>().ok());
if let Some(n) = eval_arith(a, op, b) {
return Some(BufferSize::DynamicCalculated(n));
}
}
if trimmed.contains('*') && trimmed.contains("sizeof") {
if let Some(mult_pos) = trimmed.find('*') {
let left = trimmed[..mult_pos].trim();
let right = trimmed[mult_pos + 1..].trim();
if let Some(c) = extract_numeric_value(left) {
return Some(BufferSize::DynamicCalculated(c));
}
if left.contains("sizeof") {
if let Some(c) = extract_numeric_value(right) {
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 left = trimmed[..mult_pos].trim();
let right = trimmed[mult_pos + 1..].trim();
if let (Some(c), Some(s)) = (extract_numeric_value(left), extract_sizeof_value(right)) {
return Some(c * s);
}
if let (Some(s), Some(c)) = (extract_sizeof_value(left), extract_numeric_value(right)) {
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;
memset_content_length_in_range(
var_name,
source,
fn_start,
std::cmp::min(call_line, fn_end + 1),
)
}
pub fn memset_content_length_in_range(
var_name: &str,
source: &str,
start: usize,
end: usize,
) -> Option<usize> {
let lines: Vec<&str> = source.lines().collect();
let mut best_size: Option<usize> = None;
for i in start..std::cmp::min(end, lines.len()) {
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 resolve_bare_alias_in_range(
var_name: &str,
source: &str,
start: usize,
end: usize,
) -> Option<String> {
let lines: Vec<&str> = source.lines().collect();
let pattern = format!(
r"\b{}\s*=\s*(?:\([^)]*\)\s*)?(\w+)\s*;",
regex::escape(var_name)
);
let re = regex::Regex::new(&pattern).ok()?;
let end = end.min(lines.len().saturating_sub(1));
for line in &lines[start..=end] {
if let Some(caps) = re.captures(line) {
let target = &caps[1];
if target == var_name || target == "NULL" || target == "0" {
continue;
}
return Some(target.to_string());
}
}
None
}
pub fn resolve_alloc_assigned_in_range(
var_name: &str,
source: &str,
start: usize,
end: usize,
) -> Option<usize> {
let lines: Vec<&str> = source.lines().collect();
let pattern = format!(
r"\b{}\s*=\s*(?:\([^)]*\)\s*)?({})\s*\(([^;]*)\)",
regex::escape(var_name),
ALLOC_FUNCTIONS.join("|")
);
let re = regex::Regex::new(&pattern).ok()?;
let end = end.min(lines.len().saturating_sub(1));
let mut best: Option<usize> = None;
for line in &lines[start..=end] {
let Some(caps) = re.captures(line) else {
continue;
};
let Some(size) = alloc_call_element_count(&caps[1], &caps[2]) else {
continue;
};
best = Some(best.map_or(size, |b: usize| b.max(size)));
}
best
}
pub fn resolves_to_strlen_call(
var_name: &str,
lines: &[&str],
fn_start: usize,
fn_end: usize,
wide: bool,
) -> bool {
let pattern = if wide {
format!(r"\b{}\s*=\s*wcslen\s*\(", regex::escape(var_name))
} else {
format!(r"\b{}\s*=\s*(?:w?)strlen\s*\(", regex::escape(var_name))
};
let Ok(re) = regex::Regex::new(&pattern) else {
return false;
};
if lines.is_empty() || fn_start >= lines.len() {
return false;
}
let end = fn_end.min(lines.len().saturating_sub(1));
if fn_start > end {
return false;
}
lines[fn_start..=end].iter().any(|l| re.is_match(l))
}
pub fn resolve_strlen_based_alloc_size(
var_name: &str,
lines: &[&str],
fn_start: usize,
fn_end: usize,
) -> Option<usize> {
let assign_re = regex::Regex::new(&format!(r"\b{}\b\s*=[^=]", regex::escape(var_name))).ok()?;
let calloc_narrow_re =
regex::Regex::new(r"calloc\s*\(\s*(\w+)\s*\+\s*1\s*,\s*(?:1|sizeof\s*\(\s*char\s*\))\s*\)")
.ok();
let calloc_wide_re =
regex::Regex::new(r"calloc\s*\(\s*(\w+)\s*\+\s*1\s*,\s*sizeof\s*\(\s*wchar_t\s*\)\s*\)")
.ok();
let malloc_indirect_re = regex::Regex::new(r"malloc\s*\(\s*(\w+)\s*\+\s*1\s*\)").ok();
let end = fn_end.min(lines.len().saturating_sub(1));
if lines.is_empty() || fn_start > end {
return None;
}
for line in &lines[fn_start..=end] {
let assigns_here = assign_re.is_match(line);
if !assigns_here {
continue;
}
if (line.contains("malloc") || line.contains("calloc"))
&& line.contains("strlen")
&& line.contains("+ 1")
{
return Some(usize::MAX);
}
if line.contains("calloc") {
if let Some(caps) = calloc_narrow_re.as_ref().and_then(|re| re.captures(line)) {
if resolves_to_strlen_call(&caps[1], lines, fn_start, fn_end, false) {
return Some(usize::MAX);
}
}
if let Some(caps) = calloc_wide_re.as_ref().and_then(|re| re.captures(line)) {
if resolves_to_strlen_call(&caps[1], lines, fn_start, fn_end, true) {
return Some(usize::MAX);
}
}
}
if line.contains("malloc") {
if let Some(caps) = malloc_indirect_re.as_ref().and_then(|re| re.captures(line)) {
if resolves_to_strlen_call(&caps[1], lines, fn_start, fn_end, false) {
return Some(usize::MAX);
}
}
}
}
None
}
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_type_bytes_exact_match_canonical_table() {
assert_eq!(sizeof_type_bytes("int"), Some(4));
assert_eq!(sizeof_type_bytes("unsigned int"), Some(4));
assert_eq!(sizeof_type_bytes("char"), Some(1));
assert_eq!(sizeof_type_bytes("long"), Some(8));
assert_eq!(sizeof_type_bytes("unsigned long"), Some(8));
assert_eq!(sizeof_type_bytes("int64_t"), Some(8));
assert_eq!(sizeof_type_bytes("uint64_t"), Some(8));
assert_eq!(sizeof_type_bytes("int8_t"), Some(1));
assert_eq!(sizeof_type_bytes("size_t"), Some(8));
assert_eq!(sizeof_type_bytes("wchar_t"), Some(4));
assert_eq!(sizeof_type_bytes("float"), Some(4));
assert_eq!(sizeof_type_bytes("double"), Some(8));
assert_eq!(sizeof_type_bytes("int *"), Some(8));
assert_eq!(sizeof_type_bytes("struct foo*"), Some(8));
assert_eq!(sizeof_type_bytes("twoIntsStruct"), None);
assert_eq!(sizeof_type_bytes("struct foo"), 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 sizeof_stdint_widths_not_shadowed_by_int_substring() {
assert_eq!(extract_sizeof_value("sizeof(int64_t)"), Some(8));
assert_eq!(extract_sizeof_value("sizeof(uint64_t)"), Some(8));
assert_eq!(extract_sizeof_value("sizeof(int32_t)"), Some(4));
assert_eq!(extract_sizeof_value("sizeof(uint32_t)"), Some(4));
assert_eq!(extract_sizeof_value("sizeof(int16_t)"), Some(2));
assert_eq!(extract_sizeof_value("sizeof(uint16_t)"), Some(2));
assert_eq!(extract_sizeof_value("sizeof(int8_t)"), Some(1));
assert_eq!(extract_sizeof_value("sizeof(uint8_t)"), Some(1));
assert_eq!(extract_sizeof_value("sizeof(size_t)"), Some(8));
assert_eq!(extract_sizeof_value("sizeof(long long)"), Some(8));
assert_eq!(extract_sizeof_value("sizeof(unsigned long long)"), Some(8));
}
#[test]
fn sizeof_pointer_types_are_eight_bytes_regardless_of_base_type() {
assert_eq!(extract_sizeof_value("sizeof(int*)"), Some(8));
assert_eq!(extract_sizeof_value("sizeof(char*)"), Some(8));
assert_eq!(extract_sizeof_value("sizeof(wchar_t*)"), Some(8));
assert_eq!(extract_sizeof_value("sizeof(void*)"), Some(8));
assert_eq!(extract_sizeof_value("sizeof(struct foo*)"), Some(8));
}
#[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 malloc_size_handles_nested_paren_arith_before_sizeof() {
assert!(matches!(
calculate_malloc_size("(4*3) * sizeof(int)"),
Some(BufferSize::DynamicCalculated(12))
));
assert!(matches!(
calculate_malloc_size("(4+3) * sizeof(int)"),
Some(BufferSize::DynamicCalculated(7))
));
assert!(matches!(
calculate_malloc_size("(10-2) * sizeof(char)"),
Some(BufferSize::DynamicCalculated(8))
));
}
#[test]
fn malloc_size_handles_reversed_sizeof_times_count_order() {
assert!(matches!(
calculate_malloc_size("sizeof(int) * 5"),
Some(BufferSize::DynamicCalculated(5))
));
assert_eq!(calculate_alloc_bytes("sizeof(int) * 5"), Some(20));
assert!(matches!(
calculate_malloc_size("sizeof(char) * n"),
Some(BufferSize::Dynamic(_))
));
assert_eq!(calculate_alloc_bytes("sizeof(char) * n"), Some(1));
}
#[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);
}
}