use crate::codegen::cfg::{ASTFunction, ControlFlowGraph, Instr};
use crate::codegen::reaching_definitions::{apply_transfers, VarDefs};
use crate::codegen::{Builtin, Expression};
use crate::sema::ast::{Diagnostic, ErrorType, Level, Namespace, Note, Type};
use crate::sema::symtable;
use solang_parser::pt::CodeLocation;
use solang_parser::pt::{Loc, StorageLocation};
use std::collections::HashMap;
pub struct FindUndefinedVariablesParams<'a> {
pub func_no: ASTFunction,
pub defs: &'a VarDefs,
pub ns: &'a mut Namespace,
pub cfg: &'a ControlFlowGraph,
pub diagnostics: &'a mut HashMap<usize, Diagnostic>,
}
pub fn find_undefined_variables(
cfg: &ControlFlowGraph,
ns: &mut Namespace,
func_no: ASTFunction,
) -> bool {
let mut diagnostics: HashMap<usize, Diagnostic> = HashMap::new();
for block in &cfg.blocks {
let mut var_defs: VarDefs = block.defs.clone();
for (instr_no, (_, instruction)) in block.instr.iter().enumerate() {
check_variables_in_expression(
func_no,
instruction,
&var_defs,
ns,
cfg,
&mut diagnostics,
);
apply_transfers(&block.transfers[instr_no], &mut var_defs);
}
}
let mut all_diagnostics: Vec<Diagnostic> = diagnostics.into_values().collect();
ns.diagnostics.append(&mut all_diagnostics);
!all_diagnostics.is_empty()
}
pub fn check_variables_in_expression(
func_no: ASTFunction,
instr: &Instr,
defs: &VarDefs,
ns: &mut Namespace,
cfg: &ControlFlowGraph,
diagnostics: &mut HashMap<usize, Diagnostic>,
) {
if matches!(instr, Instr::Store { .. }) {
return;
}
let mut params = FindUndefinedVariablesParams {
func_no,
defs,
ns,
cfg,
diagnostics,
};
instr.recurse_expressions(&mut params, find_undefined_variables_in_expression);
}
pub fn find_undefined_variables_in_expression(
exp: &Expression,
ctx: &mut FindUndefinedVariablesParams,
) -> bool {
match &exp {
Expression::Variable(_, _, pos) => {
let variable = match ctx.func_no {
ASTFunction::YulFunction(func_no) => {
ctx.ns.yul_functions[func_no].symtable.vars.get(pos)
}
ASTFunction::SolidityFunction(func_no) => {
ctx.ns.functions[func_no].symtable.vars.get(pos)
}
ASTFunction::None => None,
};
if let (Some(def_map), Some(var)) = (ctx.defs.get(pos), variable) {
for (def, modified) in def_map {
if let Instr::Set {
expr: instr_expr, ..
} = &ctx.cfg.blocks[def.block_no].instr[def.instr_no].1
{
if matches!(instr_expr, Expression::Undefined(_))
&& !*modified
&& !matches!(var.ty, Type::Array(..))
{
add_diagnostic(var, pos, &exp.loc(), ctx.diagnostics);
}
}
}
}
false
}
Expression::Builtin(_, _, Builtin::ArrayLength, _) => false,
_ => true,
}
}
fn add_diagnostic(
var: &symtable::Variable,
var_no: &usize,
expr_loc: &Loc,
diagnostics: &mut HashMap<usize, Diagnostic>,
) {
if matches!(var.usage_type, symtable::VariableUsage::ReturnVariable)
&& !matches!(var.storage_location, Some(StorageLocation::Storage(_)))
{
return;
}
if !diagnostics.contains_key(var_no) {
diagnostics.insert(
*var_no,
Diagnostic {
level: Level::Error,
ty: ErrorType::TypeError,
loc: var.id.loc,
message: format!("Variable '{}' is undefined", var.id.name),
notes: vec![],
},
);
}
let diag = diagnostics.get_mut(var_no).unwrap();
diag.notes.push(Note {
loc: *expr_loc,
message: "Variable read before being defined".to_string(),
});
}