1use tree_sitter::Node;
19
20use crate::declarations::node_text;
21use brokk_bifrost_core::hash::{HashMap, HashSet};
22
23const TRIVIAL_TYPE_KINDS: &[&str] = &["primitive_type", "sized_type_specifier"];
26
27#[derive(Default)]
29struct NameFacts {
30 declarations: usize,
32 trivial_signature: bool,
35 poisoned: bool,
39}
40
41pub struct CppTemporaryFreeCallIndex<'a> {
44 source: &'a str,
45 facts: Option<HashMap<&'a str, NameFacts>>,
48 trivial_arrays: HashSet<&'a str>,
54 visited_nodes: usize,
56}
57
58impl<'a> CppTemporaryFreeCallIndex<'a> {
59 pub fn build(source: &'a str, root: Node<'a>) -> Self {
61 let mut facts: HashMap<&'a str, NameFacts> = HashMap::default();
62 let mut trivial_arrays: HashSet<&'a str> = HashSet::default();
63 let mut rejected_arrays: HashSet<&'a str> = HashSet::default();
64 let mut declaration_names: HashSet<usize> = HashSet::default();
65 let mut provable_file = true;
66 let mut visited_nodes = 0usize;
67 let mut stack = vec![root];
68 while let Some(node) = stack.pop() {
69 visited_nodes += 1;
70 let kind = node.kind();
71 if kind.starts_with("preproc_") {
72 provable_file = false;
75 }
76 if matches!(
77 kind,
78 "declaration" | "parameter_declaration" | "field_declaration"
79 ) {
80 record_object_declarations(node, source, &mut trivial_arrays, &mut rejected_arrays);
81 }
82 if let Some(candidate) = free_function_candidate(node) {
83 declaration_names.insert(candidate.name.id());
84 let name = node_text(candidate.name, source);
85 let entry = facts.entry(name).or_default();
86 entry.declarations += 1;
87 entry.trivial_signature = candidate.trivial_signature;
88 }
89 if matches!(
90 kind,
91 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
92 ) && !declaration_names.contains(&node.id())
93 && !(kind == "identifier" && is_callee_position(node))
94 {
95 facts.entry(node_text(node, source)).or_default().poisoned = true;
96 }
97 let mut cursor = node.walk();
98 stack.extend(node.named_children(&mut cursor));
99 }
100 for name in &rejected_arrays {
101 trivial_arrays.remove(name);
102 }
103 Self {
104 source,
105 facts: provable_file.then_some(facts),
106 trivial_arrays,
107 visited_nodes,
108 }
109 }
110
111 pub fn visited_nodes(&self) -> usize {
113 self.visited_nodes
114 }
115
116 pub fn call_is_provably_temporary_free(&self, call: Node<'_>) -> bool {
123 debug_assert_eq!(call.kind(), "call_expression");
124 let Some(facts) = &self.facts else {
125 return false;
126 };
127 let Some(callee) = call.child_by_field_name("function") else {
128 return false;
129 };
130 if callee.kind() != "identifier" {
131 return false;
132 }
133 let Some(name) = facts.get(node_text(callee, self.source)) else {
134 return false;
135 };
136 if name.declarations != 1 || !name.trivial_signature || name.poisoned {
137 return false;
138 }
139 let Some(arguments) = call.child_by_field_name("arguments") else {
140 return false;
141 };
142 let mut cursor = arguments.walk();
143 arguments
144 .named_children(&mut cursor)
145 .all(|argument| self.argument_is_trivially_shaped(argument))
146 }
147
148 fn argument_is_trivially_shaped(&self, argument: Node<'_>) -> bool {
153 let mut node = argument;
154 loop {
155 match node.kind() {
156 "field_expression" => {
163 let Some(base) = node.child_by_field_name("argument") else {
164 return false;
165 };
166 let mut cursor = node.walk();
167 if node
168 .children(&mut cursor)
169 .any(|child| matches!(child.kind(), "->" | "->*"))
170 {
171 return false;
172 }
173 node = base;
174 }
175 "subscript_expression" => {
176 let Some(base) = node.child_by_field_name("argument") else {
177 return false;
178 };
179 if base.kind() != "identifier"
180 || !self.trivial_arrays.contains(node_text(base, self.source))
181 {
182 return false;
183 }
184 let mut cursor = node.walk();
185 let indices = node
186 .named_children(&mut cursor)
187 .filter(|child| child.id() != base.id())
188 .collect::<Vec<_>>();
189 if !indices
190 .iter()
191 .all(|index| self.subscript_is_trivially_shaped(*index))
192 {
193 return false;
194 }
195 node = base;
196 }
197 "parenthesized_expression" => {
198 let mut cursor = node.walk();
199 let mut children = node.named_children(&mut cursor);
200 let (Some(inner), None) = (children.next(), children.next()) else {
201 return false;
202 };
203 node = inner;
204 }
205 "identifier"
206 | "number_literal"
207 | "char_literal"
208 | "string_literal"
209 | "concatenated_string"
210 | "true"
211 | "false"
212 | "null"
213 | "nullptr" => return true,
214 "call_expression" => return true,
215 _ => return false,
216 }
217 }
218 }
219
220 fn subscript_is_trivially_shaped(&self, node: Node<'_>) -> bool {
223 if node.kind() != "subscript_argument_list" {
224 return self.argument_is_trivially_shaped(node);
225 }
226 let mut cursor = node.walk();
227 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
228 children
229 .iter()
230 .all(|child| self.argument_is_trivially_shaped(*child))
231 }
232}
233
234fn record_object_declarations<'a>(
241 declaration: Node<'a>,
242 source: &'a str,
243 trivial_arrays: &mut HashSet<&'a str>,
244 rejected: &mut HashSet<&'a str>,
245) {
246 let element_is_trivial = declaration
247 .child_by_field_name("type")
248 .is_some_and(|node| TRIVIAL_TYPE_KINDS.contains(&node.kind()));
249 let mut cursor = declaration.walk();
250 let declarators = declaration
251 .children_by_field_name("declarator", &mut cursor)
252 .collect::<Vec<_>>();
253 for declarator in declarators {
254 let mut current = declarator;
255 let mut is_array = false;
256 let name = loop {
260 match current.kind() {
261 "array_declarator" | "init_declarator" => {
262 is_array |= current.kind() == "array_declarator";
263 match current.child_by_field_name("declarator") {
264 Some(inner) => current = inner,
265 None => break None,
266 }
267 }
268 "identifier" => break Some(current),
269 _ => break None,
270 }
271 };
272 let Some(name) = name else {
273 continue;
274 };
275 let name = node_text(name, source);
276 if is_array && element_is_trivial && !rejected.contains(name) {
277 trivial_arrays.insert(name);
278 } else {
279 rejected.insert(name);
280 }
281 }
282}
283
284struct FreeFunctionCandidate<'a> {
287 name: Node<'a>,
288 trivial_signature: bool,
289}
290
291fn free_function_candidate(node: Node<'_>) -> Option<FreeFunctionCandidate<'_>> {
292 if !matches!(node.kind(), "function_definition" | "declaration") {
293 return None;
294 }
295 if node.parent()?.kind() != "translation_unit" {
298 return None;
299 }
300 let type_node = node.child_by_field_name("type")?;
301 let mut declarator = node.child_by_field_name("declarator")?;
302 let mut indirect_return = false;
303 while matches!(
304 declarator.kind(),
305 "pointer_declarator" | "reference_declarator"
306 ) {
307 indirect_return = true;
308 declarator = declarator.child_by_field_name("declarator")?;
309 }
310 if declarator.kind() != "function_declarator" {
311 return None;
312 }
313 let name = declarator.child_by_field_name("declarator")?;
314 if name.kind() != "identifier" {
315 return None;
316 }
317 let trivial_return = indirect_return || TRIVIAL_TYPE_KINDS.contains(&type_node.kind());
320 let trivial_signature = trivial_return
321 && declarator
322 .child_by_field_name("parameters")
323 .is_some_and(|parameters| {
324 let mut cursor = parameters.walk();
325 parameters
326 .named_children(&mut cursor)
327 .all(parameter_is_provably_trivial)
328 });
329 Some(FreeFunctionCandidate {
330 name,
331 trivial_signature,
332 })
333}
334
335fn parameter_is_provably_trivial(parameter: Node<'_>) -> bool {
339 if parameter.kind() != "parameter_declaration" {
340 return false;
341 }
342 let Some(type_node) = parameter.child_by_field_name("type") else {
343 return false;
344 };
345 if TRIVIAL_TYPE_KINDS.contains(&type_node.kind()) {
346 return true;
347 }
348 parameter
353 .child_by_field_name("declarator")
354 .is_some_and(declarator_contains_pointer)
355}
356
357fn declarator_contains_pointer(declarator: Node<'_>) -> bool {
358 let mut stack = vec![declarator];
359 while let Some(node) = stack.pop() {
360 if matches!(
361 node.kind(),
362 "pointer_declarator" | "abstract_pointer_declarator"
363 ) {
364 return true;
365 }
366 let mut cursor = node.walk();
367 stack.extend(node.named_children(&mut cursor));
368 }
369 false
370}
371
372fn is_callee_position(node: Node<'_>) -> bool {
373 node.parent().is_some_and(|parent| {
374 parent.kind() == "call_expression"
375 && parent
376 .child_by_field_name("function")
377 .is_some_and(|function| function.id() == node.id())
378 })
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384 use tree_sitter::{Parser, Tree};
385
386 fn parse_cpp(source: &str) -> Tree {
387 let mut parser = Parser::new();
388 parser
389 .set_language(&tree_sitter_cpp::LANGUAGE.into())
390 .expect("cpp language");
391 parser.parse(source, None).expect("cpp tree")
392 }
393
394 fn classified_calls(source: &str) -> Vec<(String, bool)> {
396 let tree = parse_cpp(source);
397 let index = CppTemporaryFreeCallIndex::build(source, tree.root_node());
398 let mut calls = Vec::new();
399 let mut stack = vec![tree.root_node()];
400 while let Some(node) = stack.pop() {
401 if node.kind() == "call_expression" {
402 calls.push((
403 node_text(node, source).to_string(),
404 index.call_is_provably_temporary_free(node),
405 ));
406 }
407 let mut cursor = node.walk();
408 stack.extend(node.named_children(&mut cursor));
409 }
410 calls.sort();
411 calls
412 }
413
414 fn assert_calls(source: &str, expected: &[(&str, bool)]) {
415 let mut expected = expected
416 .iter()
417 .map(|(text, provable)| (text.to_string(), *provable))
418 .collect::<Vec<_>>();
419 expected.sort();
420 assert_eq!(classified_calls(source), expected);
421 }
422
423 #[test]
426 fn exact_local_free_function_calls_are_provable() {
427 assert_calls(
428 r#"
429const char *dfb_source() {
430 return "tainted";
431}
432
433void dfb_sink(const char *value) {}
434
435void run() {
436 dfb_sink(dfb_source());
437}
438"#,
439 &[("dfb_source()", true), ("dfb_sink(dfb_source())", true)],
440 );
441 }
442
443 #[test]
446 fn discarded_result_and_literal_argument_are_provable() {
447 assert_calls(
448 r#"
449const char *dfb_source() {
450 return "tainted";
451}
452
453void dfb_sink(const char *value) {}
454
455void run() {
456 dfb_source();
457 dfb_sink("clean");
458}
459"#,
460 &[("dfb_source()", true), ("dfb_sink(\"clean\")", true)],
461 );
462 }
463
464 #[test]
466 fn local_prototype_is_provable() {
467 assert_calls(
468 r#"
469const char *dfb_source();
470
471void run() {
472 dfb_source();
473}
474"#,
475 &[("dfb_source()", true)],
476 );
477 }
478
479 #[test]
482 fn overloaded_functions_stay_unproven() {
483 assert_calls(
484 r#"
485const char *dfb_source() { return "a"; }
486const char *dfb_source(int selector) { return "b"; }
487
488void run() {
489 dfb_source();
490}
491"#,
492 &[("dfb_source()", false)],
493 );
494 }
495
496 #[test]
499 fn function_pointer_calls_stay_unproven() {
500 assert_calls(
501 r#"
502const char *real_source() { return "a"; }
503
504void run() {
505 const char *(*dfb_source)() = real_source;
506 dfb_source();
507}
508"#,
509 &[("dfb_source()", false)],
510 );
511 }
512
513 #[test]
516 fn virtual_member_calls_stay_unproven() {
517 assert_calls(
518 r#"
519struct Producer {
520 virtual const char *dfb_source() { return "a"; }
521};
522
523void run(Producer *producer) {
524 producer->dfb_source();
525}
526"#,
527 &[("producer->dfb_source()", false)],
528 );
529 }
530
531 #[test]
534 fn member_declaration_poisons_unqualified_calls() {
535 assert_calls(
536 r#"
537const char *dfb_source() { return "a"; }
538
539struct Wrapper {
540 const char *dfb_source() { return "b"; }
541 const char *read() { return dfb_source(); }
542};
543"#,
544 &[("dfb_source()", false)],
545 );
546 }
547
548 #[test]
551 fn template_functions_stay_unproven() {
552 assert_calls(
553 r#"
554template <typename T>
555T dfb_source() { return T(); }
556
557void run() {
558 dfb_source<const char *>();
559}
560"#,
561 &[("dfb_source<const char *>()", false), ("T()", false)],
562 );
563 }
564
565 #[test]
568 fn class_return_types_stay_unproven() {
569 assert_calls(
570 r#"
571struct Token {};
572Token dfb_source() { return Token{}; }
573
574void run() {
575 dfb_source();
576}
577"#,
578 &[("dfb_source()", false)],
579 );
580 }
581
582 #[test]
585 fn class_reference_parameters_stay_unproven() {
586 assert_calls(
587 r#"
588struct Token {};
589void dfb_sink(const Token &value) {}
590
591void run() {
592 dfb_sink(Token{});
593}
594"#,
595 &[("dfb_sink(Token{})", false)],
596 );
597 }
598
599 #[test]
601 fn default_arguments_stay_unproven() {
602 assert_calls(
603 r#"
604void dfb_sink(const char *value = "d") {}
605
606void run() {
607 dfb_sink();
608}
609"#,
610 &[("dfb_sink()", false)],
611 );
612 }
613
614 #[test]
618 fn complex_argument_expressions_stay_unproven() {
619 assert_calls(
620 r#"
621void dfb_sink(const char *value) {}
622
623void run(const char *left) {
624 dfb_sink(left + 1);
625}
626"#,
627 &[("dfb_sink(left + 1)", false)],
628 );
629 }
630
631 #[test]
634 fn preprocessor_content_makes_the_file_unprovable() {
635 assert_calls(
636 r#"
637#include <string>
638
639const char *dfb_source() { return "a"; }
640
641void run() {
642 dfb_source();
643}
644"#,
645 &[("dfb_source()", false)],
646 );
647 }
648
649 #[test]
652 fn address_taken_names_stay_unproven() {
653 assert_calls(
654 r#"
655const char *dfb_source() { return "a"; }
656
657void keep(const char *(*pointer)()) {}
658
659void run() {
660 keep(&dfb_source);
661 dfb_source();
662}
663"#,
664 &[("dfb_source()", false), ("keep(&dfb_source)", false)],
665 );
666 }
667
668 #[test]
671 fn pointer_indirection_over_class_types_is_provable() {
672 assert_calls(
673 r#"
674struct Token;
675Token *dfb_source() { return nullptr; }
676
677void dfb_sink(Token *value) {}
678
679void run() {
680 dfb_sink((dfb_source()));
681}
682"#,
683 &[("dfb_source()", true), ("dfb_sink((dfb_source()))", true)],
684 );
685 }
686
687 #[test]
692 fn member_access_and_trivial_array_subscript_arguments_are_provable() {
693 assert_calls(
694 r#"
695struct Holder {
696 int value;
697};
698
699void consume(int value) {}
700
701void run() {
702 Holder holder;
703 int values[2];
704 consume(holder.value);
705 consume(values[0]);
706}
707"#,
708 &[
709 ("consume(holder.value)", true),
710 ("consume(values[0])", true),
711 ],
712 );
713 }
714
715 #[test]
719 fn subscript_of_an_unproven_base_stays_unproven() {
720 assert_calls(
721 r#"
722struct Table {
723 int &operator[](int index);
724};
725
726void consume(int value) {}
727
728void run(Table table) {
729 consume(table[0]);
730}
731"#,
732 &[("consume(table[0])", false)],
733 );
734 }
735}