const HEAP_ALLOC_FUNCS: &[&str] = &["malloc", "calloc", "realloc", "aligned_alloc"];
const STRING_DUP_FUNCS: &[&str] = &["strdup", "strndup"];
const PRINTF_FUNCS: &[&str] = &[
"printf",
"fprintf",
"sprintf",
"snprintf",
"vprintf",
"vfprintf",
"vsprintf",
"vsnprintf",
"dprintf",
"vdprintf",
"wprintf",
"fwprintf",
"swprintf",
];
const SCANF_FUNCS: &[&str] = &["scanf", "fscanf", "sscanf", "vscanf", "vfscanf", "vsscanf"];
const RESOURCE_ACQUISITION_FUNCS: &[&str] =
&["fopen", "malloc", "calloc", "realloc", "open", "socket"];
pub fn is_heap_allocator(name: &str) -> bool {
HEAP_ALLOC_FUNCS.contains(&name)
}
pub fn is_string_duplicator(name: &str) -> bool {
STRING_DUP_FUNCS.contains(&name)
}
pub fn is_allocator_call(name: &str) -> bool {
is_heap_allocator(name) || is_string_duplicator(name)
}
pub fn is_printf_family(name: &str) -> bool {
PRINTF_FUNCS.contains(&name)
}
pub fn is_scanf_family(name: &str) -> bool {
SCANF_FUNCS.contains(&name)
}
pub fn is_format_function(name: &str) -> bool {
is_printf_family(name) || is_scanf_family(name)
}
pub fn is_resource_acquisition_text(expr: &str) -> bool {
RESOURCE_ACQUISITION_FUNCS
.iter()
.any(|name| contains_word_boundary_call(expr, name))
}
fn contains_word_boundary_call(expr: &str, name: &str) -> bool {
let bytes = expr.as_bytes();
let mut start = 0;
while let Some(rel) = expr[start..].find(name) {
let pos = start + rel;
let before_ok = pos == 0 || !is_ident_byte(bytes[pos - 1]);
let after = pos + name.len();
let after_is_paren = bytes.get(after) == Some(&b'(');
if before_ok && after_is_paren {
return true;
}
start = pos + name.len();
}
false
}
pub fn is_sizeof_text(expr: &str) -> bool {
let bytes = expr.as_bytes();
let needle = b"sizeof";
let mut start = 0;
while let Some(rel) = expr[start..].find("sizeof") {
let pos = start + rel;
let before_ok = pos == 0 || !is_ident_byte(bytes[pos - 1]);
let after = pos + needle.len();
let after_ok = after >= bytes.len() || !is_ident_byte(bytes[after]);
if before_ok && after_ok {
return true;
}
start = pos + needle.len();
}
false
}
fn is_ident_byte(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_'
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn heap_allocator_excludes_string_dup() {
assert!(is_heap_allocator("malloc"));
assert!(is_heap_allocator("aligned_alloc"));
assert!(!is_heap_allocator("strdup"));
}
#[test]
fn allocator_call_includes_string_dup() {
assert!(is_allocator_call("strdup"));
assert!(is_allocator_call("strndup"));
assert!(is_allocator_call("calloc"));
assert!(!is_allocator_call("free"));
}
#[test]
fn printf_family_includes_wide_and_fd_variants() {
assert!(is_printf_family("dprintf"));
assert!(is_printf_family("wprintf"));
assert!(!is_printf_family("puts"));
}
#[test]
fn sizeof_text_is_word_boundary_aware() {
assert!(is_sizeof_text("sizeof(int)"));
assert!(is_sizeof_text("n * sizeof x"));
assert!(!is_sizeof_text("mysizeof(int)"));
assert!(!is_sizeof_text("sizeofx"));
}
#[test]
fn resource_acquisition_text_covers_file_memory_and_socket() {
assert!(is_resource_acquisition_text("fopen(\"f\", \"r\")"));
assert!(is_resource_acquisition_text("malloc(sizeof(object_t))"));
assert!(is_resource_acquisition_text(
"socket(AF_INET, SOCK_STREAM, 0)"
));
assert!(!is_resource_acquisition_text("fclose(f)"));
}
#[test]
fn resource_acquisition_text_is_word_boundary_aware() {
assert!(!is_resource_acquisition_text("myfopen(\"f\", \"r\")"));
}
}