use crate::utility::cert_c::ast_utils::get_node_text;
use lang_parsing_substrate::query;
use std::collections::HashSet;
use tree_sitter::Node;
const STDLIB_NORETURN_FUNCTIONS: &[&str] = &["abort", "exit", "_Exit", "quick_exit", "longjmp"];
pub const NORETURN_ATTRIBUTE_MACRO_NAMES: &[&str] = &["NORETURN"];
const MARKER: &str = "/*R*/";
pub fn write_marker(source: &str, start: usize, end: usize) -> Option<String> {
let len = end - start;
if len < MARKER.len() {
return None;
}
let mut out = String::with_capacity(source.len());
out.push_str(&source[..start]);
out.push_str(MARKER);
out.push_str(&" ".repeat(len - MARKER.len()));
out.push_str(&source[end..]);
Some(out)
}
fn has_marker(text: &str) -> bool {
text.contains(MARKER)
}
fn find_function_declarator<'a>(node: &Node<'a>) -> Option<Node<'a>> {
if node.kind() == "function_declarator" {
return Some(*node);
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if let Some(found) = find_function_declarator(&child) {
return Some(found);
}
}
}
None
}
fn has_noreturn_qualifier_or_attribute(decl_or_def: &Node, source: &str) -> bool {
let mut cursor = decl_or_def.walk();
let result = decl_or_def.children(&mut cursor).any(|c| match c.kind() {
"type_qualifier" => get_node_text(&c, source).trim() == "_Noreturn",
"attribute_specifier" => {
let text = get_node_text(&c, source);
text.contains("noreturn")
}
_ => false,
});
result
}
pub fn collect_noreturn_function_names(root: &Node, source: &str) -> HashSet<String> {
let mut names: HashSet<String> = STDLIB_NORETURN_FUNCTIONS
.iter()
.map(|s| s.to_string())
.collect();
for node in query::find_descendants_of_kinds(*root, &["declaration", "function_definition"]) {
let declarator = match node.child_by_field_name("declarator") {
Some(d) => d,
None => continue,
};
let Some(func_declarator) = find_function_declarator(&declarator) else {
continue;
};
let Some(name_node) = func_declarator.child_by_field_name("declarator") else {
continue;
};
let name = get_node_text(&name_node, source).trim().to_string();
if name.is_empty() {
continue;
}
let marked = has_marker(&source[node.start_byte()..func_declarator.start_byte()]);
if marked || has_noreturn_qualifier_or_attribute(&node, source) {
names.insert(name);
}
}
names
}
pub fn is_noreturn_call_statement(
node: &Node,
source: &str,
noreturn_names: &HashSet<String>,
) -> bool {
if node.kind() != "expression_statement" {
return false;
}
let Some(call) = node.child(0).filter(|c| c.kind() == "call_expression") else {
return false;
};
let Some(function) = call.child_by_field_name("function") else {
return false;
};
if function.kind() != "identifier" {
return false;
}
let name = get_node_text(&function, source).trim();
noreturn_names.contains(name)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::CParser;
fn parse(src: &str) -> (tree_sitter::Tree, String) {
let mut parser = CParser::new().expect("parser");
parser.parse_source(src).expect("parse")
}
#[test]
fn stdlib_names_always_present() {
let (tree, source) = parse("int main(void) { return 0; }\n");
let names = collect_noreturn_function_names(&tree.root_node(), &source);
assert!(names.contains("abort"));
assert!(names.contains("exit"));
assert!(names.contains("longjmp"));
}
#[test]
fn recognizes_c11_noreturn_keyword() {
let (tree, source) = parse("_Noreturn void die(void) { for (;;) {} }\n");
let names = collect_noreturn_function_names(&tree.root_node(), &source);
assert!(names.contains("die"));
}
#[test]
fn recognizes_gnu_attribute() {
let (tree, source) = parse("__attribute__((noreturn)) void die(void) { for (;;) {} }\n");
let names = collect_noreturn_function_names(&tree.root_node(), &source);
assert!(names.contains("die"));
}
#[test]
fn recognizes_marker_recovered_bare_macro_prototype() {
let src = "void NORETURN slowpath(int x);\nvoid slowpath(int x) { for (;;) {} }\n";
let (tree, source) = parse(src);
assert!(source.contains(MARKER), "expected marker in: {source:?}");
let names = collect_noreturn_function_names(&tree.root_node(), &source);
assert!(names.contains("slowpath"));
}
#[test]
fn does_not_flag_unrelated_unknown_macro() {
let src = "void VISIBLE foo(void) { return; }\n";
let (tree, source) = parse(src);
let names = collect_noreturn_function_names(&tree.root_node(), &source);
assert!(!names.contains("foo"));
}
#[test]
fn is_noreturn_call_statement_matches_expression_statement_call() {
let src = "void f(void) { abort(); }\n";
let (tree, source) = parse(src);
let names = collect_noreturn_function_names(&tree.root_node(), &source);
let call_stmt =
query::find_descendants_of_kinds(tree.root_node(), &["expression_statement"])
.into_iter()
.next()
.expect("expression_statement");
assert!(is_noreturn_call_statement(&call_stmt, &source, &names));
}
}