use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use tree_sitter::Node;
pub struct Msc39C;
impl Msc39C {
pub fn new() -> Self {
Self
}
fn is_va_list_type(&self, type_node: &Node, source: &str) -> bool {
let type_text = get_node_text(type_node, source);
type_text.contains("va_list") && !type_text.contains('*')
}
#[allow(dead_code)]
fn is_va_list_pointer(&self, type_node: &Node, source: &str) -> bool {
let type_text = get_node_text(type_node, source);
type_text.contains("va_list") && type_text.contains('*')
}
fn get_parameter_name(&self, param: &Node, source: &str) -> Option<String> {
if let Some(declarator) = param.child_by_field_name("declarator") {
return self.extract_identifier(&declarator, source);
}
None
}
fn extract_identifier(&self, node: &Node, source: &str) -> Option<String> {
match node.kind() {
"identifier" => Some(get_node_text(node, source).trim().to_string()),
"pointer_declarator" | "array_declarator" | "function_declarator" => {
if let Some(decl) = node.child_by_field_name("declarator") {
return self.extract_identifier(&decl, source);
}
None
}
_ => {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" {
return Some(get_node_text(&child, source).trim().to_string());
}
}
}
None
}
}
}
fn has_va_arg_on_param(&self, body: &Node, param_name: &str, source: &str) -> bool {
if body.kind() == "call_expression" {
if let Some(function_node) = body.child_by_field_name("function") {
let function_name = get_node_text(&function_node, source).trim();
if function_name == "va_arg" {
if let Some(arguments) = body.child_by_field_name("arguments") {
for i in 0..arguments.child_count() {
if let Some(arg) = arguments.child(i) {
if arg.kind() == "identifier" {
let arg_text = get_node_text(&arg, source).trim();
if arg_text == param_name {
return true;
}
break; }
}
}
}
}
}
}
for i in 0..body.child_count() {
if let Some(child) = body.child(i) {
if self.has_va_arg_on_param(&child, param_name, source) {
return true;
}
}
}
false
}
fn check_function_definition(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() != "function_definition" {
return;
}
let declarator = match node.child_by_field_name("declarator") {
Some(d) => d,
None => return,
};
let params_node = self.find_parameters(&declarator);
if params_node.is_none() {
return;
}
let params_node = params_node.unwrap();
let body = match node.child_by_field_name("body") {
Some(b) => b,
None => return,
};
for i in 0..params_node.child_count() {
if let Some(param) = params_node.child(i) {
if param.kind() == "parameter_declaration" {
if let Some(type_node) = param.child_by_field_name("type") {
if self.is_va_list_type(&type_node, source) {
if let Some(param_name) = self.get_parameter_name(¶m, source) {
if self.has_va_arg_on_param(&body, ¶m_name, source) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message: format!(
"Function takes 'va_list {}' by value and calls va_arg() on it. This makes the caller's va_list indeterminate after the function returns.",
param_name
),
file_path: String::new(),
line: param.start_position().row + 1,
column: param.start_position().column + 1,
suggestion: Some(
format!("Change parameter '{}' to 'va_list *{}' and use va_copy() to create a local copy before calling va_arg().", param_name, param_name)
),
..Default::default()
});
}
}
}
}
}
}
}
}
fn find_parameters<'a>(&self, node: &Node<'a>) -> Option<Node<'a>> {
if node.kind() == "parameter_list" {
return Some(*node);
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if let Some(params) = self.find_parameters(&child) {
return Some(params);
}
}
}
None
}
}
impl CertRule for Msc39C {
fn rule_id(&self) -> &'static str {
"MSC39-C"
}
fn description(&self) -> &'static str {
"Do not call va_arg() on a va_list that has an indeterminate value"
}
fn severity(&self) -> Severity {
Severity::Low
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"MSC39-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_node(node, source, &mut violations);
violations
}
}
impl Msc39C {
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
self.check_function_definition(node, source, violations);
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.check_node(&child, source, violations);
}
}
}
}