use crate::sema::ast::{
Builtin, CallArgs, Diagnostic, EventDecl, Expression, ExternalCallAccounts, Namespace,
RetrieveType,
};
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,
var_no,
..
} => {
ns.contracts[*contract_no].variables[*var_no].assigned = true;
}
Expression::Variable { var_no, .. } => {
let var = symtable.vars.get_mut(var_no).unwrap();
var.assigned = true;
}
Expression::StructMember { expr, .. } => {
assigned_variable(ns, expr, symtable);
}
Expression::Subscript { array, index, .. } => {
if array.ty().is_contract_storage() {
subscript_variable(ns, array, symtable);
} else {
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);
}
_ => {}
}
}
fn subscript_variable(ns: &mut Namespace, exp: &Expression, symtable: &mut Symtable) {
match &exp {
Expression::StorageVariable {
contract_no,
var_no,
..
} => {
ns.contracts[*contract_no].variables[*var_no].assigned = true;
}
Expression::Variable { var_no, .. } => {
let var = symtable.vars.get_mut(var_no).unwrap();
var.read = true;
}
Expression::StructMember { expr, .. } => {
subscript_variable(ns, expr, symtable);
}
Expression::Subscript { array, index, .. } => {
subscript_variable(ns, array, symtable);
subscript_variable(ns, index, symtable);
}
Expression::InternalFunctionCall { .. } | Expression::ExternalFunctionCall { .. } => {
check_function_call(ns, exp, symtable);
}
_ => (),
}
}
pub fn used_variable(ns: &mut Namespace, exp: &Expression, symtable: &mut Symtable) {
match &exp {
Expression::StorageVariable {
contract_no,
var_no,
..
} => {
ns.contracts[*contract_no].variables[*var_no].read = true;
}
Expression::Variable { var_no, .. } => {
let var = symtable.vars.get_mut(var_no).unwrap();
var.read = true;
}
Expression::ConstantVariable {
contract_no: Some(contract_no),
var_no,
..
} => {
ns.contracts[*contract_no].variables[*var_no].read = true;
}
Expression::ConstantVariable {
contract_no: None,
var_no,
..
} => {
ns.constants[*var_no].read = true;
}
Expression::StructMember { expr, .. } => {
used_variable(ns, expr, symtable);
}
Expression::Subscript { array, index, .. } => {
used_variable(ns, array, symtable);
used_variable(ns, index, symtable);
}
Expression::Builtin {
kind: Builtin::ArrayLength,
args,
..
} => {
used_variable(ns, &args[0], symtable);
}
Expression::Builtin {
kind: Builtin::ArrayPush | Builtin::ArrayPop,
args,
..
} => {
used_variable(ns, &args[0], symtable);
assigned_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::ExternalFunction { .. }
| 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 {
kind: expr_type,
args,
..
} => match expr_type {
Builtin::ArrayPush | Builtin::ArrayPop => {
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 { format, .. } => {
for (_, expr) in format {
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 ExternalCallAccounts::Present(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);
}
if let Some(flags) = &call_args.flags {
used_variable(ns, flags.as_ref(), symtable);
}
if let Some(program_id) = &call_args.program_id {
used_variable(ns, program_id.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,
ns: &Namespace,
) -> Option<Diagnostic> {
match &variable.usage_type {
VariableUsage::Parameter => {
if (!variable.read && !variable.ty.is_reference_type(ns))
|| (!variable.read && !variable.assigned && variable.ty.is_reference_type(ns))
{
return Some(Diagnostic::warning(
variable.id.loc,
format!("function parameter '{}' is unused", variable.id.name),
));
}
None
}
VariableUsage::ReturnVariable => {
if !variable.assigned {
if variable.ty.is_contract_storage() {
return Some(Diagnostic::error(
variable.id.loc,
format!(
"storage reference '{}' must be assigned a value",
variable.id.name
),
));
} else {
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 !variable.assigned && !variable.read {
return Some(Diagnostic::warning(
variable.id.loc,
format!("local variable '{}' is unused", variable.id.name),
));
} else if assigned && !variable.read && !variable.is_reference(ns) {
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.id.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.id.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.id.loc,
format!("event '{}' has never been emitted", event.id),
));
}
}
}
pub fn check_unused_errors(ns: &mut Namespace) {
for error in &ns.errors {
if !error.used {
if let Some(contract_no) = error.contract {
if matches!(
ns.contracts[contract_no].ty,
ContractTy::Interface(_) | ContractTy::Abstract(_)
) {
continue;
}
}
ns.diagnostics.push(Diagnostic::warning(
error.loc,
format!("error '{}' has never been used", error.name),
));
}
}
}