|
|
@@ -5,6 +5,7 @@ use super::{
|
|
|
Builtin, CallTy, DestructureField, Diagnostic, Expression, Function, Mutability, Namespace,
|
|
|
RetrieveType, Statement, Type,
|
|
|
},
|
|
|
+ diagnostics::Diagnostics,
|
|
|
yul::ast::{YulExpression, YulStatement},
|
|
|
Recurse,
|
|
|
};
|
|
|
@@ -16,7 +17,7 @@ use bitflags::bitflags;
|
|
|
use solang_parser::pt::Loc;
|
|
|
use solang_parser::{helpers::CodeLocation, pt};
|
|
|
|
|
|
-#[derive(PartialEq, PartialOrd)]
|
|
|
+#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd)]
|
|
|
enum Access {
|
|
|
None,
|
|
|
Read,
|
|
|
@@ -49,16 +50,16 @@ pub fn mutability(file_no: usize, ns: &mut Namespace) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- let mut diagnostics = check_mutability(func, ns);
|
|
|
+ let diagnostics = check_mutability(func, ns);
|
|
|
|
|
|
- ns.diagnostics.append(&mut diagnostics);
|
|
|
+ ns.diagnostics.extend(diagnostics);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// While we recurse through the AST, maintain some state
|
|
|
struct StateCheck<'a> {
|
|
|
- diagnostics: Vec<Diagnostic>,
|
|
|
+ diagnostic: Diagnostics,
|
|
|
declared_access: Access,
|
|
|
required_access: Access,
|
|
|
func: &'a Function,
|
|
|
@@ -69,91 +70,65 @@ struct StateCheck<'a> {
|
|
|
|
|
|
impl<'a> StateCheck<'a> {
|
|
|
fn value(&mut self, loc: &pt::Loc) {
|
|
|
- if self.declared_access != Access::Value {
|
|
|
- if let Some(modifier_loc) = &self.modifier {
|
|
|
- self.diagnostics.push(Diagnostic::error_with_note(
|
|
|
- *modifier_loc,
|
|
|
- format!(
|
|
|
- "function declared '{}' but modifier accesses value sent, which is only allowed for payable functions",
|
|
|
- self.func.mutability
|
|
|
- ),
|
|
|
- *loc,
|
|
|
- "access of value sent".into()
|
|
|
- ));
|
|
|
- } else {
|
|
|
- self.diagnostics.push(Diagnostic::error(
|
|
|
- *loc,
|
|
|
- format!(
|
|
|
- "function declared '{}' but this expression accesses value sent, which is only allowed for payable functions",
|
|
|
- self.func.mutability
|
|
|
- ),
|
|
|
- ));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
+ self.check_level(loc, Access::Value);
|
|
|
self.required_access.increase_to(Access::Value);
|
|
|
}
|
|
|
|
|
|
fn write(&mut self, loc: &pt::Loc) {
|
|
|
- if self.declared_access < Access::Write {
|
|
|
- if let Some(modifier_loc) = &self.modifier {
|
|
|
- self.diagnostics.push(Diagnostic::error_with_note(
|
|
|
- *modifier_loc,
|
|
|
- format!(
|
|
|
- "function declared '{}' but modifier writes to state",
|
|
|
- self.func.mutability
|
|
|
- ),
|
|
|
- *loc,
|
|
|
- "write to state".into(),
|
|
|
- ));
|
|
|
- } else {
|
|
|
- self.diagnostics.push(Diagnostic::error(
|
|
|
- *loc,
|
|
|
- format!(
|
|
|
- "function declared '{}' but this expression writes to state",
|
|
|
- self.func.mutability
|
|
|
- ),
|
|
|
- ));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
+ self.check_level(loc, Access::Write);
|
|
|
self.required_access.increase_to(Access::Write);
|
|
|
}
|
|
|
|
|
|
fn read(&mut self, loc: &pt::Loc) {
|
|
|
- if self.declared_access < Access::Read {
|
|
|
- if let Some(modifier_loc) = &self.modifier {
|
|
|
- self.diagnostics.push(Diagnostic::error_with_note(
|
|
|
- *modifier_loc,
|
|
|
- format!(
|
|
|
- "function declared '{}' but modifier reads from state",
|
|
|
- self.func.mutability
|
|
|
- ),
|
|
|
- *loc,
|
|
|
- "read to state".into(),
|
|
|
- ));
|
|
|
- } else {
|
|
|
- self.diagnostics.push(Diagnostic::error(
|
|
|
- *loc,
|
|
|
- format!(
|
|
|
- "function declared '{}' but this expression reads from state",
|
|
|
- self.func.mutability
|
|
|
- ),
|
|
|
- ));
|
|
|
- }
|
|
|
+ self.check_level(loc, Access::Read);
|
|
|
+ self.required_access.increase_to(Access::Read);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Compare the declared access level to the desired access level.
|
|
|
+ /// If there is an access violation, it'll be reported to the diagnostics.
|
|
|
+ fn check_level(&mut self, loc: &pt::Loc, desired: Access) {
|
|
|
+ if self.declared_access >= desired {
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- self.required_access.increase_to(Access::Read);
|
|
|
+ let (message, note) = match desired {
|
|
|
+ Access::Read => ("reads from state", "read to state"),
|
|
|
+ Access::Write => ("writes to state", "write to state"),
|
|
|
+ Access::Value => (
|
|
|
+ "accesses value sent, which is only allowed for payable functions",
|
|
|
+ "access of value sent",
|
|
|
+ ),
|
|
|
+ Access::None => unreachable!("desired access can't be None"),
|
|
|
+ };
|
|
|
+
|
|
|
+ let diagnostic = self
|
|
|
+ .modifier
|
|
|
+ .map(|modifier_loc| {
|
|
|
+ let message = format!(
|
|
|
+ "function declared '{}' but modifier {}",
|
|
|
+ self.func.mutability, message
|
|
|
+ );
|
|
|
+ Diagnostic::error_with_note(modifier_loc, message, *loc, note.into())
|
|
|
+ })
|
|
|
+ .unwrap_or_else(|| {
|
|
|
+ let message = format!(
|
|
|
+ "function declared '{}' but this expression {}",
|
|
|
+ self.func.mutability, message
|
|
|
+ );
|
|
|
+ Diagnostic::error(*loc, message)
|
|
|
+ });
|
|
|
+
|
|
|
+ self.diagnostic.push(diagnostic);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-fn check_mutability(func: &Function, ns: &Namespace) -> Vec<Diagnostic> {
|
|
|
+fn check_mutability(func: &Function, ns: &Namespace) -> Diagnostics {
|
|
|
if func.is_virtual {
|
|
|
- return Vec::new();
|
|
|
+ return Default::default();
|
|
|
}
|
|
|
|
|
|
let mut state = StateCheck {
|
|
|
- diagnostics: Vec::new(),
|
|
|
+ diagnostic: Default::default(),
|
|
|
declared_access: match func.mutability {
|
|
|
Mutability::Pure(_) => Access::None,
|
|
|
Mutability::View(_) => Access::Read,
|
|
|
@@ -213,13 +188,13 @@ fn check_mutability(func: &Function, ns: &Namespace) -> Vec<Diagnostic> {
|
|
|
match func.mutability {
|
|
|
Mutability::Payable(_) | Mutability::Pure(_) => (),
|
|
|
Mutability::Nonpayable(_) => {
|
|
|
- state.diagnostics.push(Diagnostic::warning(
|
|
|
+ state.diagnostic.push(Diagnostic::warning(
|
|
|
func.loc,
|
|
|
"function can be declared 'pure'".to_string(),
|
|
|
));
|
|
|
}
|
|
|
_ => {
|
|
|
- state.diagnostics.push(Diagnostic::warning(
|
|
|
+ state.diagnostic.push(Diagnostic::warning(
|
|
|
func.loc,
|
|
|
format!(
|
|
|
"function declared '{}' can be declared 'pure'",
|
|
|
@@ -232,7 +207,7 @@ fn check_mutability(func: &Function, ns: &Namespace) -> Vec<Diagnostic> {
|
|
|
|
|
|
// don't suggest marking payable as view (declared_access == Value)
|
|
|
if state.required_access == Access::Read && state.declared_access == Access::Write {
|
|
|
- state.diagnostics.push(Diagnostic::warning(
|
|
|
+ state.diagnostic.push(Diagnostic::warning(
|
|
|
func.loc,
|
|
|
"function can be declared 'view'".to_string(),
|
|
|
));
|
|
|
@@ -271,7 +246,7 @@ fn check_mutability(func: &Function, ns: &Namespace) -> Vec<Diagnostic> {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- state.diagnostics
|
|
|
+ state.diagnostic
|
|
|
}
|
|
|
|
|
|
fn recurse_statements(stmts: &[Statement], ns: &Namespace, state: &mut StateCheck) {
|
|
|
@@ -359,12 +334,15 @@ fn recurse_statements(stmts: &[Statement], ns: &Namespace, state: &mut StateChec
|
|
|
|
|
|
fn read_expression(expr: &Expression, state: &mut StateCheck) -> bool {
|
|
|
match expr {
|
|
|
+ Expression::StorageLoad { loc, .. } => {
|
|
|
+ state.data_account |= DataAccountUsage::READ;
|
|
|
+ state.read(loc)
|
|
|
+ }
|
|
|
Expression::PreIncrement { expr, .. }
|
|
|
| Expression::PreDecrement { expr, .. }
|
|
|
| Expression::PostIncrement { expr, .. }
|
|
|
| Expression::PostDecrement { expr, .. } => {
|
|
|
expr.recurse(state, write_expression);
|
|
|
- return false;
|
|
|
}
|
|
|
Expression::Assign { left, right, .. } => {
|
|
|
right.recurse(state, read_expression);
|
|
|
@@ -374,14 +352,6 @@ fn read_expression(expr: &Expression, state: &mut StateCheck) -> bool {
|
|
|
Expression::StorageArrayLength { loc, .. } => {
|
|
|
state.data_account |= DataAccountUsage::READ;
|
|
|
state.read(loc);
|
|
|
- return false;
|
|
|
- }
|
|
|
- Expression::Subscript { loc, array_ty, .. } if array_ty.is_contract_storage() => {
|
|
|
- state.data_account |= DataAccountUsage::READ;
|
|
|
- state.read(loc);
|
|
|
- }
|
|
|
- Expression::Variable { ty, .. } if ty.is_contract_storage() => {
|
|
|
- state.data_account |= DataAccountUsage::READ;
|
|
|
}
|
|
|
Expression::StorageVariable { loc, .. } => {
|
|
|
state.data_account |= DataAccountUsage::READ;
|