use crate::sema::ast::{Builtin, CallArgs, Diagnostic, EventDecl, Expression, Namespace};
use crate::sema::symtable::{Symtable, VariableUsage};
use crate::sema::{ast, symtable};
use solang_parser::pt::{ContractTy, Loc};
pub fn assigned_variable(ns: &mut Namespace, exp: &Expression, symtable: &mut Symtable) {
match &exp {
Expression::StorageVariable(_, _, contract_no, offset) => {
ns.contracts[*contract_no].variables[*offset].assigned = true;
}
Expression::Variable(_, _, offset) => {
let var = symtable.vars.get_mut(offset).unwrap();
var.assigned = true;
}
Expression::StructMember(_, _, str, _) => {
assigned_variable(ns, str, symtable);
}
Expression::Subscript(_, _, _, array, index) => {
assigned_variable(ns, array, symtable);
used_variable(ns, index, symtable);
}
Expression::StorageLoad(_, _, expr)
| Expression::Load(_, _, expr)
| Expression::Trunc { expr, .. }
| Expression::Cast { expr, .. }
| Expression::BytesCast { expr, .. } => {
assigned_variable(ns, expr, symtable);
}
_ => {}
}
}
pub fn used_variable(ns: &mut Namespace, exp: &Expression, symtable: &mut Symtable) {
match &exp {
Expression::StorageVariable(_, _, contract_no, offset) => {
ns.contracts[*contract_no].variables[*offset].read = true;
}
Expression::Variable(_, _, offset) => {
let var = symtable.vars.get_mut(offset).unwrap();
var.read = true;
}
Expression::ConstantVariable(_, _, Some(contract_no), offset) => {
ns.contracts[*contract_no].variables[*offset].read = true;
}
Expression::ConstantVariable(_, _, None, offset) => {
ns.constants[*offset].read = true;
}
Expression::StructMember(_, _, str, _) => {
used_variable(ns, str, symtable);
}
Expression::Subscript(_, _, _, array, index) => {
used_variable(ns, array, symtable);
used_variable(ns, index, symtable);
}
Expression::Builtin(_, _, Builtin::ArrayLength, args) => {
assigned_variable(ns, &args[0], symtable);
used_variable(ns, &args[0], symtable);
}
Expression::StorageArrayLength { array, .. } => {
assigned_variable(ns, array, symtable);
used_variable(ns, array, symtable);
}
Expression::StorageLoad(_, _, expr)
| Expression::Load(_, _, expr)
| Expression::SignExt { expr, .. }
| Expression::ZeroExt { expr, .. }
| Expression::Trunc { expr, .. }
| Expression::Cast { expr, .. }
| Expression::BytesCast { expr, .. } => {
used_variable(ns, expr, symtable);
}
Expression::InternalFunctionCall { .. } | Expression::ExternalFunctionCall { .. } => {
check_function_call(ns, exp, symtable);
}
_ => {}
}
}
pub fn check_function_call(ns: &mut Namespace, exp: &Expression, symtable: &mut Symtable) {
match &exp {
Expression::Load(..) | Expression::StorageLoad(..) | Expression::Variable(..) => {
used_variable(ns, exp, symtable);
}
Expression::InternalFunctionCall { function, args, .. } => {
for arg in args {
used_variable(ns, arg, symtable);
}
check_function_call(ns, function, symtable);
}
Expression::ExternalFunctionCall {
function,
args,
call_args,
..
} => {
for arg in args {
used_variable(ns, arg, symtable);
}
check_call_args(ns, call_args, symtable);
check_function_call(ns, function, symtable);
}
Expression::Constructor {
args, call_args, ..
} => {
for arg in args {
used_variable(ns, arg, symtable);
}
check_call_args(ns, call_args, symtable);
}
Expression::ExternalFunctionCallRaw {
address,
args,
call_args,
..
} => {
used_variable(ns, args, symtable);
used_variable(ns, address, symtable);
check_call_args(ns, call_args, symtable);
}
Expression::ExternalFunction { address, .. } => {
used_variable(ns, address, symtable);
}
Expression::Builtin(_, _, expr_type, args) => match expr_type {
Builtin::ArrayPush => {
assigned_variable(ns, &args[0], symtable);
if args.len() > 1 {
used_variable(ns, &args[1], symtable);
}
}
_ => {
for arg in args {
used_variable(ns, arg, symtable);
}
}
},
Expression::FormatString(_, args) => {
for (_, expr) in args {
used_variable(ns, expr, symtable);
}
}
_ => {}
}
}
fn check_call_args(ns: &mut Namespace, call_args: &CallArgs, symtable: &mut Symtable) {
if let Some(gas) = &call_args.gas {
used_variable(ns, gas.as_ref(), symtable);
}
if let Some(salt) = &call_args.salt {
used_variable(ns, salt.as_ref(), symtable);
}
if let Some(value) = &call_args.value {
used_variable(ns, value.as_ref(), symtable);
}
if let Some(address) = &call_args.address {
used_variable(ns, address.as_ref(), symtable);
}
if let Some(accounts) = &call_args.accounts {
used_variable(ns, accounts.as_ref(), symtable);
}
if let Some(seeds) = &call_args.seeds {
used_variable(ns, seeds.as_ref(), symtable);
}
}
pub fn check_var_usage_expression(
ns: &mut Namespace,
left: &Expression,
right: &Expression,
symtable: &mut Symtable,
) {
used_variable(ns, left, symtable);
used_variable(ns, right, symtable);
}
pub fn emit_warning_local_variable(variable: &symtable::Variable) -> Option<Diagnostic> {
match &variable.usage_type {
VariableUsage::Parameter => {
if !variable.read {
return Some(Diagnostic::warning(
variable.id.loc,
format!(
"function parameter '{}' has never been read",
variable.id.name
),
));
}
None
}
VariableUsage::ReturnVariable => {
if !variable.assigned {
return Some(Diagnostic::warning(
variable.id.loc,
format!(
"return variable '{}' has never been assigned",
variable.id.name
),
));
}
None
}
VariableUsage::LocalVariable => {
let assigned = variable.initializer.has_initializer() || variable.assigned;
if !assigned && !variable.read {
return Some(Diagnostic::warning(
variable.id.loc,
format!(
"local variable '{}' has never been read nor assigned",
variable.id.name
),
));
} else if assigned && !variable.read && !variable.is_reference() {
return Some(Diagnostic::warning(
variable.id.loc,
format!(
"local variable '{}' has been assigned, but never read",
variable.id.name
),
));
}
None
}
VariableUsage::DestructureVariable => {
if !variable.read {
return Some(Diagnostic::warning(
variable.id.loc,
format!(
"destructure variable '{}' has never been used",
variable.id.name
),
));
}
None
}
VariableUsage::TryCatchReturns => {
if !variable.read {
return Some(Diagnostic::warning(
variable.id.loc,
format!(
"try-catch returns variable '{}' has never been read",
variable.id.name
),
));
}
None
}
VariableUsage::TryCatchErrorBytes => {
if !variable.read {
return Some(Diagnostic::warning(
variable.id.loc,
format!(
"try-catch error bytes '{}' has never been used",
variable.id.name
),
));
}
None
}
VariableUsage::TryCatchErrorString => {
if !variable.read {
return Some(Diagnostic::warning(
variable.id.loc,
format!(
"try-catch error string '{}' has never been used",
variable.id.name
),
));
}
None
}
VariableUsage::YulLocalVariable => {
let has_value = variable.assigned || variable.initializer.has_initializer();
if !variable.read && !has_value {
return Some(Diagnostic::warning(
variable.id.loc,
format!(
"yul variable '{}' has never been read or assigned",
variable.id.name
),
));
} else if !variable.read {
return Some(Diagnostic::warning(
variable.id.loc,
format!("yul variable '{}' has never been read", variable.id.name),
));
}
None
}
VariableUsage::AnonymousReturnVariable => None,
}
}
fn emit_warning_contract_variables(variable: &ast::Variable) -> Option<Diagnostic> {
if variable.assigned && !variable.read {
return Some(Diagnostic::warning(
variable.loc,
format!(
"storage variable '{}' has been assigned, but never read",
variable.name
),
));
} else if !variable.assigned && !variable.read {
return Some(Diagnostic::warning(
variable.loc,
format!("storage variable '{}' has never been used", variable.name),
));
}
None
}
pub fn check_unused_namespace_variables(ns: &mut Namespace) {
for contract in &ns.contracts {
for variable in &contract.variables {
if let Some(warning) = emit_warning_contract_variables(variable) {
ns.diagnostics.push(warning);
}
}
}
for constant in &ns.constants {
if !constant.read {
ns.diagnostics.push(Diagnostic::warning(
constant.loc,
format!("global constant '{}' has never been used", constant.name),
));
}
}
}
fn shadowing_events(
event_no: usize,
event: &EventDecl,
shadows: &mut Vec<usize>,
events: &[(Loc, usize)],
ns: &Namespace,
) {
for e in events {
let other_no = e.1;
if event_no != other_no && ns.events[other_no].signature == event.signature {
shadows.push(other_no);
}
}
}
pub fn check_unused_events(ns: &mut Namespace) {
for event_no in 0..ns.events.len() {
let event = &ns.events[event_no];
if !event.used {
continue;
}
let mut shadows = Vec::new();
if let Some(contract_no) = event.contract {
if let Some(ast::Symbol::Event(events)) =
ns.variable_symbols
.get(&(event.loc.file_no(), None, event.name.to_owned()))
{
shadowing_events(event_no, event, &mut shadows, events, ns);
}
for base_no in ns.contract_bases(contract_no) {
let base_file_no = ns.contracts[base_no].loc.file_no();
if let Some(ast::Symbol::Event(events)) =
ns.variable_symbols
.get(&(base_file_no, Some(base_no), event.name.to_owned()))
{
shadowing_events(event_no, event, &mut shadows, events, ns);
}
}
}
for shadow in shadows {
ns.events[shadow].used = true;
}
}
for event in &ns.events {
if !event.used {
if let Some(contract_no) = event.contract {
if matches!(
ns.contracts[contract_no].ty,
ContractTy::Interface(_) | ContractTy::Abstract(_)
) {
continue;
}
}
ns.diagnostics.push(Diagnostic::warning(
event.loc,
format!("event '{}' has never been emitted", event.name),
));
}
}
}