use std::{
sync::Arc,
};
use delegate::delegate;
use foundry_compilers::artifacts::{
ast::SourceLocation,
BlockOrStatement,
DoWhileStatement,
Expression,
ForStatement,
FunctionCall,
FunctionDefinition,
IfStatement,
ModifierDefinition,
Statement,
TryStatement,
WhileStatement, };
use once_cell::sync::OnceCell;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use serde::{Deserialize, Serialize};
use crate::analysis::{macros::universal_id, VariableRef, VariableScopeRef, UFID};
universal_id! {
USID => 0
}
#[derive(Debug, Clone)]
pub struct StepRef {
inner: Arc<RwLock<Step>>,
usid: OnceCell<USID>,
ufid: OnceCell<UFID>,
variant: OnceCell<StepVariant>,
function_calls: OnceCell<usize>,
}
impl From<Step> for StepRef {
fn from(step: Step) -> Self {
Self::new(step)
}
}
impl Serialize for StepRef {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.inner.read().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for StepRef {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let step = Step::deserialize(deserializer)?;
Ok(Self::new(step))
}
}
impl StepRef {
pub fn new(inner: Step) -> Self {
Self {
inner: Arc::new(RwLock::new(inner)),
usid: OnceCell::new(),
ufid: OnceCell::new(),
variant: OnceCell::new(),
function_calls: OnceCell::new(),
}
}
pub(crate) fn read(&self) -> RwLockReadGuard<'_, Step> {
self.inner.read()
}
pub(crate) fn write(&self) -> RwLockWriteGuard<'_, Step> {
self.inner.write()
}
pub fn usid(&self) -> USID {
*self.usid.get_or_init(|| self.inner.read().usid)
}
pub fn ufid(&self) -> UFID {
*self.ufid.get_or_init(|| self.inner.read().ufid)
}
pub fn variant(&self) -> &StepVariant {
self.variant.get_or_init(|| self.inner.read().variant.clone())
}
pub fn function_calls(&self) -> usize {
let calls = &self.inner.read().function_calls;
let mut function_calls = calls.len();
match self.variant() {
StepVariant::Statement(Statement::EmitStatement { .. }) => {
function_calls = function_calls.saturating_sub(1);
}
StepVariant::Statements(ref stmts) => {
let emit_n = stmts
.iter()
.filter(|stmt| matches!(stmt, Statement::EmitStatement { .. }))
.count();
function_calls = function_calls.saturating_sub(emit_n);
}
_ => {}
}
static BUILT_IN_FUNCTIONS: &[&str] =
&["require", "assert", "keccak256", "sha256", "ripemd160", "ecrecover", "type"];
let built_in_n = calls
.iter()
.filter(|call| {
if let Expression::Identifier(ref id) = call.expression {
BUILT_IN_FUNCTIONS.contains(&id.name.as_str())
} else {
false
}
})
.count();
function_calls = function_calls.saturating_sub(built_in_n);
*self.function_calls.get_or_init(|| function_calls)
}
pub fn function_entry(&self) -> Option<UFID> {
if let StepVariant::FunctionEntry(_) = self.variant() {
Some(self.read().ufid)
} else {
None
}
}
pub fn modifier_entry(&self) -> Option<UFID> {
if let StepVariant::ModifierEntry(_) = self.variant() {
Some(self.read().ufid)
} else {
None
}
}
pub fn contains_return(&self) -> bool {
match self.variant() {
StepVariant::Statement(Statement::Return(..)) => true,
StepVariant::Statements(stmts) => {
stmts.iter().any(|s| matches!(s, Statement::Return(..)))
}
_ => false,
}
}
}
impl StepRef {
delegate! {
to self.inner.read() {
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Step {
pub usid: USID,
pub ufid: UFID,
pub variant: StepVariant,
pub src: SourceLocation,
pub function_calls: Vec<FunctionCall>,
pub accessible_variables: Vec<VariableRef>,
pub declared_variables: Vec<VariableRef>,
pub updated_variables: Vec<VariableRef>,
pub scope: VariableScopeRef,
}
impl Step {
pub fn new(
ufid: UFID,
variant: StepVariant,
src: SourceLocation,
scope: VariableScopeRef,
accessible_variables: Vec<VariableRef>,
) -> Self {
let usid = USID::next();
Self {
usid,
ufid,
variant,
src,
function_calls: vec![],
accessible_variables,
declared_variables: vec![],
updated_variables: vec![],
scope,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(clippy::large_enum_variant)]
pub enum StepVariant {
FunctionEntry(FunctionDefinition),
ModifierEntry(ModifierDefinition),
Statement(Statement),
Statements(Vec<Statement>),
IfCondition(IfStatement),
ForLoop(ForStatement),
WhileLoop(WhileStatement),
DoWhileLoop(DoWhileStatement),
Try(TryStatement),
}
pub fn sloc_ldiff(a: SourceLocation, b: SourceLocation) -> SourceLocation {
assert_eq!(a.index, b.index, "The index of `a` and `b` must be the same");
let length = b.start.zip(a.start).map(|(end, start)| end.saturating_sub(start));
SourceLocation { start: a.start, length, index: a.index }
}
pub fn sloc_rdiff(a: SourceLocation, b: SourceLocation) -> SourceLocation {
assert_eq!(a.index, b.index, "The index of `a` and `b` must be the same");
let start = b.start.zip(b.length).map(|(start, length)| start + length);
let length = a
.start
.zip(a.length)
.map(|(start, length)| start + length)
.zip(start)
.map(|(end, start)| end.saturating_sub(start));
SourceLocation { start, length, index: a.index }
}
pub fn stmt_src(stmt: &Statement) -> SourceLocation {
match stmt {
Statement::Block(block) => block.src,
Statement::ExpressionStatement(expression_statement) => expression_statement.src,
Statement::Break(break_stmt) => break_stmt.src,
Statement::Continue(continue_stmt) => continue_stmt.src,
Statement::DoWhileStatement(do_while_statement) => do_while_statement.src,
Statement::EmitStatement(emit_statement) => emit_statement.src,
Statement::ForStatement(for_statement) => for_statement.src,
Statement::IfStatement(if_statement) => if_statement.src,
Statement::InlineAssembly(inline_assembly) => inline_assembly.src,
Statement::PlaceholderStatement(placeholder_statement) => placeholder_statement.src,
Statement::Return(return_stmt) => return_stmt.src,
Statement::RevertStatement(revert_statement) => revert_statement.src,
Statement::TryStatement(try_statement) => try_statement.src,
Statement::UncheckedBlock(unchecked_block) => unchecked_block.src,
Statement::VariableDeclarationStatement(variable_declaration_statement) => {
variable_declaration_statement.src
}
Statement::WhileStatement(while_statement) => while_statement.src,
}
}
pub fn block_or_stmt_src(block_or_stmt: &BlockOrStatement) -> SourceLocation {
match block_or_stmt {
BlockOrStatement::Block(block) => block.src,
BlockOrStatement::Statement(statement) => stmt_src(statement),
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! sloc {
($start:expr, $length:expr, $index:expr) => {
SourceLocation { start: Some($start), length: Some($length), index: Some($index) }
};
}
#[test]
fn test_sloc_ldiff() {
let a = sloc!(0, 10, 0);
let b = sloc!(5, 5, 0);
let c = sloc_ldiff(a, b);
assert_eq!(c, sloc!(0, 5, 0));
let a = sloc!(0, 10, 0);
let b = sloc!(0, 10, 0);
let c = sloc_ldiff(a, b);
assert_eq!(c, sloc!(0, 0, 0));
let a = sloc!(0, 10, 0);
let b = sloc!(10, 10, 0);
let c = sloc_ldiff(a, b);
assert_eq!(c, sloc!(0, 10, 0));
let a = sloc!(5, 5, 0);
let b = sloc!(0, 10, 0);
let c = sloc_ldiff(a, b);
assert_eq!(c, sloc!(5, 0, 0));
}
#[test]
fn test_sloc_rdiff() {
let a = sloc!(0, 10, 0);
let b = sloc!(5, 5, 0);
let c = sloc_rdiff(a, b);
assert_eq!(c, sloc!(10, 0, 0));
let a = sloc!(0, 10, 0);
let b = sloc!(0, 10, 0);
let c = sloc_rdiff(a, b);
assert_eq!(c, sloc!(10, 0, 0));
let a = sloc!(0, 10, 0);
let b = sloc!(0, 5, 0);
let c = sloc_rdiff(a, b);
assert_eq!(c, sloc!(5, 5, 0));
let a = sloc!(5, 5, 0);
let b = sloc!(0, 10, 0);
let c = sloc_rdiff(a, b);
assert_eq!(c, sloc!(10, 0, 0));
}
}