use crate::config::Config;
use crate::diagnostics::{self, Diagnostic, Location};
use crate::lints::{is_framealloc_call, FrameallocCall};
use crate::parser::span_to_location;
use std::path::Path;
use syn::visit::Visit;
use syn::spanned::Spanned;
pub fn check(ast: &syn::File, path: &Path, config: &Config) -> Vec<Diagnostic> {
let mut visitor = DirtyMemVisitor::new(path, config);
visitor.visit_file(ast);
visitor.diagnostics
}
struct DirtyMemVisitor<'a> {
path: &'a Path,
config: &'a Config,
diagnostics: Vec<Diagnostic>,
in_loop: bool,
loop_depth: usize,
current_loop_span: Option<proc_macro2::Span>,
has_begin_frame: bool,
has_end_frame: bool,
in_main_loop: bool,
frame_allocations: Vec<FrameAllocation>,
}
struct FrameAllocation {
var_name: String,
span: proc_macro2::Span,
call_type: FrameallocCall,
}
impl<'a> DirtyMemVisitor<'a> {
fn new(path: &'a Path, config: &'a Config) -> Self {
Self {
path,
config,
diagnostics: Vec::new(),
in_loop: false,
loop_depth: 0,
current_loop_span: None,
has_begin_frame: false,
has_end_frame: false,
in_main_loop: false,
frame_allocations: Vec::new(),
}
}
fn check_allocation_in_loop(&mut self, call: FrameallocCall, span: proc_macro2::Span) {
if self.in_loop && self.config.is_lint_enabled("FA602") {
let loop_type = if self.loop_depth > 1 { "nested" } else { "tight" };
self.diagnostics.push(diagnostics::fa602(
span_to_location(span, self.path),
call.alloc_type_str(),
loop_type,
));
}
}
fn check_frame_escape(&mut self, var_name: &str, assigned_to: &str, span: proc_macro2::Span) {
if let Some(alloc) = self.frame_allocations.iter().find(|a| a.var_name == var_name) {
if self.config.is_lint_enabled("FA601") {
self.diagnostics.push(diagnostics::fa601(
span_to_location(span, self.path),
assigned_to,
));
}
}
}
}
impl<'a> Visit<'a> for DirtyMemVisitor<'a> {
fn visit_item_fn(&mut self, func: &'a syn::ItemFn) {
let is_main = func.sig.ident == "main";
if is_main {
self.has_begin_frame = false;
self.has_end_frame = false;
}
syn::visit::visit_item_fn(self, func);
if is_main && self.in_main_loop && !self.has_begin_frame && !self.has_end_frame {
if self.config.is_lint_enabled("FA603") {
self.diagnostics.push(diagnostics::fa603(
span_to_location(func.sig.ident.span(), self.path),
));
}
}
}
fn visit_expr(&mut self, expr: &'a syn::Expr) {
match expr {
syn::Expr::Loop(loop_expr) => {
let was_in_loop = self.in_loop;
let prev_depth = self.loop_depth;
let prev_span = self.current_loop_span;
self.in_loop = true;
self.loop_depth += 1;
self.current_loop_span = Some(loop_expr.span());
self.in_main_loop = true;
syn::visit::visit_expr_loop(self, loop_expr);
self.in_loop = was_in_loop;
self.loop_depth = prev_depth;
self.current_loop_span = prev_span;
return;
}
syn::Expr::While(while_expr) => {
let was_in_loop = self.in_loop;
let prev_depth = self.loop_depth;
let prev_span = self.current_loop_span;
self.in_loop = true;
self.loop_depth += 1;
self.current_loop_span = Some(while_expr.span());
self.in_main_loop = true;
syn::visit::visit_expr_while(self, while_expr);
self.in_loop = was_in_loop;
self.loop_depth = prev_depth;
self.current_loop_span = prev_span;
return;
}
syn::Expr::ForLoop(for_expr) => {
let was_in_loop = self.in_loop;
let prev_depth = self.loop_depth;
let prev_span = self.current_loop_span;
self.in_loop = true;
self.loop_depth += 1;
self.current_loop_span = Some(for_expr.span());
self.in_main_loop = true;
syn::visit::visit_expr_for_loop(self, for_expr);
self.in_loop = was_in_loop;
self.loop_depth = prev_depth;
self.current_loop_span = prev_span;
return;
}
syn::Expr::MethodCall(call) => {
if let Some(fa_call) = is_framealloc_call(expr) {
match fa_call {
FrameallocCall::BeginFrame => self.has_begin_frame = true,
FrameallocCall::EndFrame => self.has_end_frame = true,
_ => {}
}
if fa_call.is_any_allocation() {
self.check_allocation_in_loop(fa_call, call.span());
}
}
}
syn::Expr::Assign(assign) => {
if let syn::Expr::Path(path) = assign.right.as_ref() {
if let Some(ident) = path.path.get_ident() {
let var_name = ident.to_string();
if let syn::Expr::Field(field) = assign.left.as_ref() {
self.check_frame_escape(
&var_name,
&format!("field assignment"),
assign.span(),
);
}
}
}
}
_ => {}
}
syn::visit::visit_expr(self, expr);
}
fn visit_local(&mut self, local: &'a syn::Local) {
if let Some(init) = &local.init {
if let Some(fa_call) = is_framealloc_call(&init.expr) {
if fa_call.is_frame_allocation() {
if let syn::Pat::Ident(pat_ident) = &local.pat {
self.frame_allocations.push(FrameAllocation {
var_name: pat_ident.ident.to_string(),
span: local.span(),
call_type: fa_call,
});
}
}
}
}
syn::visit::visit_local(self, local);
}
}