diff --git a/CHANGELOG.md b/CHANGELOG.md index ef62792..f36ee7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Supported `polkadot-sdk` rev: `2503.0.1` - Column numbers in debug information. - Support for the YUL optimizer details in the standard json input definition. - The `revive-explorer` compiler utility. +- `revive-yul`: The AST visitor interface. ### Fixed - The debug info source file matches the YUL path in `--debug-output-dir`, allowing tools to display the source line. diff --git a/crates/explorer/src/dwarfdump.rs b/crates/explorer/src/dwarfdump.rs index 6439391..77c0518 100644 --- a/crates/explorer/src/dwarfdump.rs +++ b/crates/explorer/src/dwarfdump.rs @@ -29,7 +29,16 @@ pub fn source_file( dwarfdump_executable: &Option, ) -> anyhow::Result { let output = dwarfdump(shared_object, dwarfdump_executable, &SOURCE_FILE_ARGUMENTS)?; - Ok(output.trim().into()) + let output = output.trim(); + + if output.is_empty() { + anyhow::bail!( + "the shared object at path `{}` doesn't contain the source file name. Hint: compile with debug information (-g)?", + shared_object.display() + ); + } + + Ok(output.into()) } /// The internal `llvm-dwarfdump` helper function. diff --git a/crates/explorer/src/dwarfdump_analyzer.rs b/crates/explorer/src/dwarfdump_analyzer.rs index bb0098a..6ceccd6 100644 --- a/crates/explorer/src/dwarfdump_analyzer.rs +++ b/crates/explorer/src/dwarfdump_analyzer.rs @@ -7,34 +7,7 @@ use std::{ use revive_yul::lexer::token::location::Location; -use crate::location_mapper::{self, map_locations, LocationMap}; - -/// Unknwon code. -pub const OTHER: &str = "other"; -/// Compiler internal code. -pub const INTERNAL: &str = "internal"; -/// YUL block code. -pub const BLOCK: &str = "block"; -/// YUL function call code. -pub const FUNCTION_CALL: &str = "function_call"; -/// YUL conditional code. -pub const IF: &str = "if"; -/// YUL loop code. -pub const FOR: &str = "for"; -/// YUL loop continue code. -pub const CONTINUE: &str = "continue"; -/// YUL loop break code. -pub const BREAK: &str = "break"; -/// YUL switch code. -pub const SWITCH: &str = "switch"; -/// YUL variable declaration code. -pub const DECLARATION: &str = "let"; -/// YUL variable assignment code. -pub const ASSIGNMENT: &str = "assignment"; -/// YUL function definition code. -pub const FUNCTION_DEFINITION: &str = "function_definition"; -/// YUL function leave code. -pub const LEAVE: &str = "leave"; +use crate::location_mapper::{self, LocationMapper}; /// The dwarf dump analyzer. /// @@ -48,7 +21,7 @@ pub struct DwarfdumpAnalyzer { source: PathBuf, /// The YUL location to statements map. - location_map: LocationMap, + location_map: HashMap, /// The `llvm-dwarfdump --debug-lines` output. debug_lines: String, @@ -81,7 +54,7 @@ impl DwarfdumpAnalyzer { /// Populate the maps so that we can always unwrap later. fn map_locations(&mut self) -> anyhow::Result<()> { - self.location_map = map_locations(&self.source)?; + self.location_map = LocationMapper::map_locations(&self.source)?; self.statements_count = HashMap::with_capacity(self.location_map.len()); self.statements_size = HashMap::with_capacity(self.location_map.len()); @@ -176,13 +149,12 @@ impl DwarfdumpAnalyzer { location_mapper::BLOCK => "--block-cost", location_mapper::FUNCTION_CALL => "--function-call-cost", location_mapper::IF => "--if-cost", - location_mapper::CONTINUE => "--continue-cost", - location_mapper::BREAK => "--break-cost", - location_mapper::LEAVE => "--leave-cost", location_mapper::SWITCH => "--switch-cost", location_mapper::DECLARATION => "--variable-declaration-cost", location_mapper::ASSIGNMENT => "--assignment-cost", location_mapper::FUNCTION_DEFINITION => "--function-definition-cost", + location_mapper::IDENTIFIER => "--identifier-cost", + location_mapper::LITERAL => "--literal-cost", _ => "--expression-statement-cost", }; diff --git a/crates/explorer/src/location_mapper.rs b/crates/explorer/src/location_mapper.rs index 06d26b2..f170cf0 100644 --- a/crates/explorer/src/location_mapper.rs +++ b/crates/explorer/src/location_mapper.rs @@ -1,158 +1,123 @@ //! The location mapper utility maps YUL source locations to AST statements. -//! -//! TODO: Refactor when the AST visitor is implemented. use std::{collections::HashMap, path::Path}; use revive_yul::{ lexer::{token::location::Location, Lexer}, - parser::statement::{ - block::Block, - expression::{function_call::name::Name, Expression}, - object::Object, - Statement, + parser::{ + identifier::Identifier, + statement::{ + assignment::Assignment, + block::Block, + expression::{function_call::FunctionCall, literal::Literal}, + for_loop::ForLoop, + function_definition::FunctionDefinition, + if_conditional::IfConditional, + object::Object, + switch::Switch, + variable_declaration::VariableDeclaration, + }, }, + visitor::{AstNode, AstVisitor}, }; /// Code attributed to an unknown location. pub const OTHER: &str = "other"; /// Code attributed to a compiler internal location. pub const INTERNAL: &str = "internal"; -/// Code attributed to a +/// Code attributed to a block. pub const BLOCK: &str = "block"; +/// Code attributed to a function call. pub const FUNCTION_CALL: &str = "function_call"; +/// Code attributed to a for loop. pub const FOR: &str = "for"; +/// Code attributed to an if statement. pub const IF: &str = "if"; -pub const CONTINUE: &str = "continue"; -pub const BREAK: &str = "break"; -pub const LEAVE: &str = "leave"; +/// Code attributed to a switch statement. pub const SWITCH: &str = "switch"; +/// Code attributed to a variable declaration. pub const DECLARATION: &str = "let"; +/// Code attributed to a variable assignement. pub const ASSIGNMENT: &str = "assignment"; +/// Code attributed to a function definition. pub const FUNCTION_DEFINITION: &str = "function_definition"; +/// Code attributed to an identifier. +pub const IDENTIFIER: &str = "identifier"; +/// Code attributed to a literal. +pub const LITERAL: &str = "literal"; -/// The location to statements map type alias. -pub type LocationMap = HashMap; +/// The location to statements mapper. +pub struct LocationMapper(HashMap); -/// Construct a [LocationMap] from the given YUL `source` file. -pub fn map_locations(source: &Path) -> anyhow::Result { - let mut lexer = Lexer::new(std::fs::read_to_string(source)?); - let ast = Object::parse(&mut lexer, None).map_err(|error| { - anyhow::anyhow!("Contract `{}` parsing error: {:?}", source.display(), error) - })?; +impl LocationMapper { + /// Construct a node location map from the given YUL `source` file. + pub fn map_locations(source: &Path) -> anyhow::Result> { + let mut lexer = Lexer::new(std::fs::read_to_string(source)?); + let ast = Object::parse(&mut lexer, None).map_err(|error| { + anyhow::anyhow!("Contract `{}` parsing error: {:?}", source.display(), error) + })?; - let mut location_map = HashMap::with_capacity(1024); - crate::location_mapper::object_mapper(&mut location_map, &ast); - location_map.insert(Location::new(0, 0), OTHER.to_string()); - location_map.insert(Location::new(1, 0), INTERNAL.to_string()); + let mut location_map = Self(Default::default()); + ast.accept(&mut location_map); + location_map.0.insert(Location::new(0, 0), OTHER.into()); + location_map.0.insert(Location::new(1, 0), INTERNAL.into()); - Ok(location_map) -} - -/// Map the [Block]. -fn block_mapper(map: &mut LocationMap, block: &Block) { - map.insert(block.location, BLOCK.to_string()); - - for statement in &block.statements { - statement_mapper(map, statement); + Ok(location_map.0) } } -/// Map the [Expression]. -fn expression_mapper(map: &mut LocationMap, expression: &Expression) { - if let Expression::FunctionCall(call) = expression { - let id = match call.name { - Name::UserDefined(_) => FUNCTION_CALL.to_string(), - _ => format!("{:?}", call.name), - }; - map.insert(expression.location(), id); +impl AstVisitor for LocationMapper { + fn visit(&mut self, node: &impl AstNode) { + node.visit_children(self); + } - for expression in &call.arguments { - expression_mapper(map, expression); - } - } -} - -/// Map the [Statement]. -fn statement_mapper(map: &mut LocationMap, statement: &Statement) { - match statement { - Statement::Object(object) => object_mapper(map, object), - - Statement::Code(code) => block_mapper(map, &code.block), - - Statement::Block(block) => block_mapper(map, block), - - Statement::ForLoop(for_loop) => { - map.insert(for_loop.location, FOR.to_string()); - - expression_mapper(map, &for_loop.condition); - block_mapper(map, &for_loop.body); - block_mapper(map, &for_loop.initializer); - block_mapper(map, &for_loop.finalizer); - } - - Statement::IfConditional(if_conditional) => { - map.insert(if_conditional.location, IF.to_string()); - - expression_mapper(map, &if_conditional.condition); - block_mapper(map, &if_conditional.block); - } - - Statement::Expression(expression) => expression_mapper(map, expression), - - Statement::Continue(location) => { - map.insert(*location, CONTINUE.to_string()); - } - - Statement::Leave(location) => { - map.insert(*location, LEAVE.to_string()); - } - - Statement::Break(location) => { - map.insert(*location, BREAK.to_string()); - } - - Statement::Switch(switch) => { - map.insert(switch.expression.location(), SWITCH.to_string()); - - expression_mapper(map, &switch.expression); - for case in &switch.cases { - block_mapper(map, &case.block); - } - if let Some(block) = switch.default.as_ref() { - block_mapper(map, block); - } - } - - Statement::Assignment(assignment) => { - map.insert(assignment.location, ASSIGNMENT.to_string()); - - expression_mapper(map, &assignment.initializer); - } - - Statement::VariableDeclaration(declaration) => { - map.insert(declaration.location, DECLARATION.to_string()); - - if let Some(expression) = declaration.expression.as_ref() { - expression_mapper(map, expression); - } - } - - Statement::FunctionDefinition(definition) => { - map.insert(definition.location, FUNCTION_DEFINITION.to_string()); - - block_mapper(map, &definition.body); - } - } -} - -/// Map the [Object]. -fn object_mapper(map: &mut LocationMap, object: &Object) { - map.insert(object.location, object.identifier.clone()); - - block_mapper(map, &object.code.block); - - if let Some(object) = object.inner_object.as_ref() { - object_mapper(map, object); + fn visit_block(&mut self, node: &Block) { + node.visit_children(self); + self.0.insert(node.location, BLOCK.into()); + } + + fn visit_assignment(&mut self, node: &Assignment) { + node.visit_children(self); + self.0.insert(node.location, ASSIGNMENT.into()); + } + + fn visit_if_conditional(&mut self, node: &IfConditional) { + node.visit_children(self); + self.0.insert(node.location, IF.into()); + } + + fn visit_variable_declaration(&mut self, node: &VariableDeclaration) { + node.visit_children(self); + self.0.insert(node.location, DECLARATION.into()); + } + + fn visit_function_call(&mut self, node: &FunctionCall) { + node.visit_children(self); + self.0.insert(node.location, node.name.to_string()); + } + + fn visit_function_definition(&mut self, node: &FunctionDefinition) { + node.visit_children(self); + self.0.insert(node.location, FUNCTION_DEFINITION.into()); + } + + fn visit_identifier(&mut self, node: &Identifier) { + node.visit_children(self); + self.0.insert(node.location, IDENTIFIER.into()); + } + + fn visit_literal(&mut self, node: &Literal) { + node.visit_children(self); + self.0.insert(node.location, LITERAL.into()); + } + + fn visit_for_loop(&mut self, node: &ForLoop) { + node.visit_children(self); + self.0.insert(node.location, FOR.into()); + } + + fn visit_switch(&mut self, node: &Switch) { + node.visit_children(self); + self.0.insert(node.location, SWITCH.into()); } } diff --git a/crates/explorer/src/main.rs b/crates/explorer/src/main.rs index 5d114c5..10036d1 100644 --- a/crates/explorer/src/main.rs +++ b/crates/explorer/src/main.rs @@ -10,6 +10,8 @@ use revive_explorer::{dwarfdump, dwarfdump_analyzer::DwarfdumpAnalyzer, yul_phas /// - The count of each YUL statement translated. /// - A per YUL statement break-down of bytecode size contributed per. /// - Estimated `yul-phaser` cost parameters. +/// +/// Note: This tool might not be fully accurate, especially when the code was optimized. #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { @@ -26,6 +28,7 @@ struct Args { yul_phaser: Option, /// Path of the shared object to analyze. + /// It must have been compiled with debug info (-g). file: PathBuf, } diff --git a/crates/yul/src/lexer/token/lexeme/literal/mod.rs b/crates/yul/src/lexer/token/lexeme/literal/mod.rs index 925db69..03821b7 100644 --- a/crates/yul/src/lexer/token/lexeme/literal/mod.rs +++ b/crates/yul/src/lexer/token/lexeme/literal/mod.rs @@ -27,7 +27,7 @@ impl std::fmt::Display for Literal { match self { Self::Boolean(inner) => write!(f, "{inner}"), Self::Integer(inner) => write!(f, "{inner}"), - Self::String(inner) => write!(f, "{inner}"), + Self::String(inner) => write!(f, "\"{inner}\""), } } } diff --git a/crates/yul/src/lib.rs b/crates/yul/src/lib.rs index 2309683..2097938 100644 --- a/crates/yul/src/lib.rs +++ b/crates/yul/src/lib.rs @@ -3,3 +3,4 @@ pub mod error; pub mod lexer; pub mod parser; +pub mod visitor; diff --git a/crates/yul/src/parser/identifier.rs b/crates/yul/src/parser/identifier.rs index bfb86bc..42fdfe5 100644 --- a/crates/yul/src/parser/identifier.rs +++ b/crates/yul/src/parser/identifier.rs @@ -10,6 +10,8 @@ use crate::lexer::token::location::Location; use crate::lexer::token::Token; use crate::lexer::Lexer; use crate::parser::r#type::Type; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; /// The YUL source code identifier. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -113,3 +115,15 @@ impl Identifier { } } } + +impl AstNode for Identifier { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_identifier(self); + } + + fn visit_children(&self, _ast_visitor: &mut impl AstVisitor) {} + + fn location(&self) -> Location { + self.location + } +} diff --git a/crates/yul/src/parser/statement/assignment.rs b/crates/yul/src/parser/statement/assignment.rs index 77ccd8f..a5e9a99 100644 --- a/crates/yul/src/parser/statement/assignment.rs +++ b/crates/yul/src/parser/statement/assignment.rs @@ -15,6 +15,8 @@ use crate::lexer::Lexer; use crate::parser::error::Error as ParserError; use crate::parser::identifier::Identifier; use crate::parser::statement::expression::Expression; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; /// The Yul assignment expression statement. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -184,3 +186,20 @@ where Ok(()) } } + +impl AstNode for Assignment { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_assignment(self); + } + + fn visit_children(&self, ast_visitor: &mut impl AstVisitor) { + for binding in &self.bindings { + binding.accept(ast_visitor); + } + self.initializer.accept(ast_visitor); + } + + fn location(&self) -> Location { + self.location + } +} diff --git a/crates/yul/src/parser/statement/block.rs b/crates/yul/src/parser/statement/block.rs index f7c3fb4..87edfd3 100644 --- a/crates/yul/src/parser/statement/block.rs +++ b/crates/yul/src/parser/statement/block.rs @@ -17,6 +17,8 @@ use crate::parser::error::Error as ParserError; use crate::parser::statement::assignment::Assignment; use crate::parser::statement::expression::Expression; use crate::parser::statement::Statement; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; /// The Yul source code block. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -226,6 +228,22 @@ where } } +impl AstNode for Block { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_block(self); + } + + fn visit_children(&self, ast_visitor: &mut impl AstVisitor) { + for statement in &self.statements { + statement.accept(ast_visitor); + } + } + + fn location(&self) -> Location { + self.location + } +} + #[cfg(test)] mod tests { use crate::lexer::token::location::Location; diff --git a/crates/yul/src/parser/statement/code.rs b/crates/yul/src/parser/statement/code.rs index e550553..9675f1e 100644 --- a/crates/yul/src/parser/statement/code.rs +++ b/crates/yul/src/parser/statement/code.rs @@ -13,6 +13,8 @@ use crate::lexer::token::Token; use crate::lexer::Lexer; use crate::parser::error::Error as ParserError; use crate::parser::statement::block::Block; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; /// The YUL code entity, which is the first block of the object. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -66,6 +68,20 @@ where } } +impl AstNode for Code { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_code(self); + } + + fn visit_children(&self, ast_visitor: &mut impl AstVisitor) { + self.block.accept(ast_visitor); + } + + fn location(&self) -> Location { + self.location + } +} + #[cfg(test)] mod tests { use crate::lexer::token::location::Location; diff --git a/crates/yul/src/parser/statement/expression/function_call/mod.rs b/crates/yul/src/parser/statement/expression/function_call/mod.rs index 653e62b..93c7756 100644 --- a/crates/yul/src/parser/statement/expression/function_call/mod.rs +++ b/crates/yul/src/parser/statement/expression/function_call/mod.rs @@ -19,6 +19,8 @@ use crate::lexer::Lexer; use crate::parser::error::Error as ParserError; use crate::parser::statement::expression::literal::Literal; use crate::parser::statement::expression::Expression; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; use self::name::Name; @@ -1021,3 +1023,19 @@ impl FunctionCall { Ok(arguments.try_into().expect("Always successful")) } } + +impl AstNode for FunctionCall { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_function_call(self); + } + + fn visit_children(&self, ast_visitor: &mut impl AstVisitor) { + for argument in &self.arguments { + argument.accept(ast_visitor); + } + } + + fn location(&self) -> Location { + self.location + } +} diff --git a/crates/yul/src/parser/statement/expression/function_call/name.rs b/crates/yul/src/parser/statement/expression/function_call/name.rs index 4010cfe..a230fbf 100644 --- a/crates/yul/src/parser/statement/expression/function_call/name.rs +++ b/crates/yul/src/parser/statement/expression/function_call/name.rs @@ -1,10 +1,13 @@ //! The function name. +use std::fmt; + use serde::Deserialize; use serde::Serialize; /// The function name. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] pub enum Name { /// The user-defined function. UserDefined(String), @@ -356,3 +359,130 @@ impl From<&str> for Name { } } } + +impl fmt::Display for Name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Self::Verbatim { + input_size, + output_size, + } = self + { + return write!(f, "verbatim_{input_size}i_{output_size}o"); + } + + let token = match self { + Self::Add => "add", + Self::Sub => "sub", + Self::Mul => "mul", + Self::Div => "div", + Self::Mod => "mod", + Self::Sdiv => "sdiv", + Self::Smod => "smod", + + Self::Lt => "lt", + Self::Gt => "gt", + Self::Eq => "eq", + Self::IsZero => "iszero", + Self::Slt => "slt", + Self::Sgt => "sgt", + + Self::Or => "or", + Self::Xor => "xor", + Self::Not => "not", + Self::And => "and", + Self::Shl => "shl", + Self::Shr => "shr", + Self::Sar => "sar", + Self::Byte => "byte", + Self::Pop => "pop", + + Self::AddMod => "addmod", + Self::MulMod => "mulmod", + Self::Exp => "exp", + Self::SignExtend => "signextend", + + Self::Keccak256 => "keccak256", + + Self::MLoad => "mload", + Self::MStore => "mstore", + Self::MStore8 => "mstore8", + Self::MCopy => "mcopy", + + Self::SLoad => "sload", + Self::SStore => "sstore", + Self::TLoad => "tload", + Self::TStore => "tstore", + Self::LoadImmutable => "loadimmutable", + Self::SetImmutable => "setimmutable", + + Self::CallDataLoad => "calldataload", + Self::CallDataSize => "calldatasize", + Self::CallDataCopy => "calldatacopy", + Self::CodeSize => "codesize", + Self::CodeCopy => "codecopy", + Self::ReturnDataSize => "returndatasize", + Self::ReturnDataCopy => "returndatacopy", + Self::ExtCodeSize => "extcodesize", + Self::ExtCodeHash => "extcodehash", + + Self::Return => "return", + Self::Revert => "revert", + + Self::Log0 => "log0", + Self::Log1 => "log1", + Self::Log2 => "log2", + Self::Log3 => "log3", + Self::Log4 => "log4", + + Self::Call => "call", + Self::DelegateCall => "delegatecall", + Self::StaticCall => "staticcall", + Self::Create => "create", + Self::Create2 => "create2", + + Self::DataSize => "datasize", + Self::DataOffset => "dataoffset", + Self::DataCopy => "datacopy", + + Self::Stop => "stop", + Self::Invalid => "invalid", + + Self::LinkerSymbol => "linkersymbol", + Self::MemoryGuard => "memoryguard", + + Self::Address => "address", + Self::Caller => "caller", + + Self::CallValue => "callvalue", + Self::Gas => "gas", + Self::Balance => "balance", + Self::SelfBalance => "selfbalance", + + Self::GasLimit => "gaslimit", + Self::GasPrice => "gasprice", + Self::Origin => "origin", + Self::ChainId => "chainid", + Self::Timestamp => "timestamp", + Self::Number => "number", + Self::BlockHash => "blockhash", + Self::BlobHash => "blobhash", + Self::Difficulty => "difficulty", + Self::Prevrandao => "prevrandao", + Self::CoinBase => "coinbase", + Self::BaseFee => "basefee", + Self::BlobBaseFee => "blobbasefee", + Self::MSize => "msize", + + Self::CallCode => "callcode", + Self::Pc => "pc", + Self::ExtCodeCopy => "extcodecopy", + Self::SelfDestruct => "selfdestruct", + + Self::UserDefined(s) => s.as_str(), + + Self::Verbatim { .. } => unreachable!(), + }; + + write!(f, "{token}") + } +} diff --git a/crates/yul/src/parser/statement/expression/literal.rs b/crates/yul/src/parser/statement/expression/literal.rs index d7b10de..d5aabab 100644 --- a/crates/yul/src/parser/statement/expression/literal.rs +++ b/crates/yul/src/parser/statement/expression/literal.rs @@ -18,6 +18,8 @@ use crate::lexer::token::Token; use crate::lexer::Lexer; use crate::parser::error::Error as ParserError; use crate::parser::r#type::Type; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; /// Represents a literal in YUL without differentiating its type. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -221,3 +223,15 @@ impl Literal { } } } + +impl AstNode for Literal { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_literal(self); + } + + fn visit_children(&self, _ast_visitor: &mut impl AstVisitor) {} + + fn location(&self) -> Location { + self.location + } +} diff --git a/crates/yul/src/parser/statement/expression/mod.rs b/crates/yul/src/parser/statement/expression/mod.rs index 0bc0bd1..2a6a609 100644 --- a/crates/yul/src/parser/statement/expression/mod.rs +++ b/crates/yul/src/parser/statement/expression/mod.rs @@ -16,6 +16,8 @@ use crate::lexer::token::Token; use crate::lexer::Lexer; use crate::parser::error::Error as ParserError; use crate::parser::identifier::Identifier; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; use self::function_call::FunctionCall; use self::literal::Literal; @@ -144,3 +146,21 @@ impl Expression { } } } + +impl AstNode for Expression { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_expression(self); + } + + fn visit_children(&self, ast_visitor: &mut impl AstVisitor) { + match self { + Self::FunctionCall(inner) => inner.accept(ast_visitor), + Self::Identifier(inner) => inner.accept(ast_visitor), + Self::Literal(inner) => inner.accept(ast_visitor), + } + } + + fn location(&self) -> Location { + self.location() + } +} diff --git a/crates/yul/src/parser/statement/for_loop.rs b/crates/yul/src/parser/statement/for_loop.rs index 3923125..605bded 100644 --- a/crates/yul/src/parser/statement/for_loop.rs +++ b/crates/yul/src/parser/statement/for_loop.rs @@ -11,6 +11,8 @@ use crate::lexer::token::Token; use crate::lexer::Lexer; use crate::parser::statement::block::Block; use crate::parser::statement::expression::Expression; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; /// The Yul for-loop statement. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -113,3 +115,20 @@ where Ok(()) } } + +impl AstNode for ForLoop { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_for_loop(self); + } + + fn visit_children(&self, ast_visitor: &mut impl AstVisitor) { + self.initializer.accept(ast_visitor); + self.condition.accept(ast_visitor); + self.finalizer.accept(ast_visitor); + self.body.accept(ast_visitor); + } + + fn location(&self) -> Location { + self.location + } +} diff --git a/crates/yul/src/parser/statement/function_definition.rs b/crates/yul/src/parser/statement/function_definition.rs index 4cb42b5..7b1ecb5 100644 --- a/crates/yul/src/parser/statement/function_definition.rs +++ b/crates/yul/src/parser/statement/function_definition.rs @@ -18,6 +18,8 @@ use crate::parser::error::Error as ParserError; use crate::parser::identifier::Identifier; use crate::parser::statement::block::Block; use crate::parser::statement::expression::function_call::name::Name as FunctionName; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; /// The function definition statement. /// All functions are translated in two steps: @@ -329,6 +331,28 @@ where } } +impl AstNode for FunctionDefinition { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_function_definition(self); + } + + fn visit_children(&self, ast_visitor: &mut impl AstVisitor) { + for argument in &self.arguments { + argument.accept(ast_visitor); + } + + self.body.accept(ast_visitor); + + for result in &self.result { + result.accept(ast_visitor); + } + } + + fn location(&self) -> Location { + self.location + } +} + #[cfg(test)] mod tests { use std::collections::BTreeSet; diff --git a/crates/yul/src/parser/statement/if_conditional.rs b/crates/yul/src/parser/statement/if_conditional.rs index 5408a52..5ac04fa 100644 --- a/crates/yul/src/parser/statement/if_conditional.rs +++ b/crates/yul/src/parser/statement/if_conditional.rs @@ -11,6 +11,8 @@ use crate::lexer::token::Token; use crate::lexer::Lexer; use crate::parser::statement::block::Block; use crate::parser::statement::expression::Expression; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; /// The Yul if-conditional statement. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -82,3 +84,18 @@ where Ok(()) } } + +impl AstNode for IfConditional { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_if_conditional(self); + } + + fn visit_children(&self, ast_visitor: &mut impl AstVisitor) { + self.condition.accept(ast_visitor); + self.block.accept(ast_visitor); + } + + fn location(&self) -> Location { + self.location + } +} diff --git a/crates/yul/src/parser/statement/mod.rs b/crates/yul/src/parser/statement/mod.rs index ec3d6d1..0f3436d 100644 --- a/crates/yul/src/parser/statement/mod.rs +++ b/crates/yul/src/parser/statement/mod.rs @@ -23,6 +23,8 @@ use crate::lexer::token::location::Location; use crate::lexer::token::Token; use crate::lexer::Lexer; use crate::parser::error::Error as ParserError; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; use self::assignment::Assignment; use self::block::Block; @@ -177,3 +179,31 @@ impl Statement { } } } + +impl AstNode for Statement { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_statement(self); + } + + fn visit_children(&self, ast_visitor: &mut impl AstVisitor) { + match self { + Self::Object(inner) => inner.accept(ast_visitor), + Self::Code(inner) => inner.accept(ast_visitor), + Self::Block(inner) => inner.accept(ast_visitor), + Self::Expression(inner) => inner.accept(ast_visitor), + Self::FunctionDefinition(inner) => inner.accept(ast_visitor), + Self::VariableDeclaration(inner) => inner.accept(ast_visitor), + Self::Assignment(inner) => inner.accept(ast_visitor), + Self::IfConditional(inner) => inner.accept(ast_visitor), + Self::Switch(inner) => inner.accept(ast_visitor), + Self::ForLoop(inner) => inner.accept(ast_visitor), + Self::Continue(_location) => {} + Self::Break(_location) => {} + Self::Leave(_location) => {} + } + } + + fn location(&self) -> Location { + self.location() + } +} diff --git a/crates/yul/src/parser/statement/object.rs b/crates/yul/src/parser/statement/object.rs index ffd9202..b79d54c 100644 --- a/crates/yul/src/parser/statement/object.rs +++ b/crates/yul/src/parser/statement/object.rs @@ -17,6 +17,8 @@ use crate::lexer::token::Token; use crate::lexer::Lexer; use crate::parser::error::Error as ParserError; use crate::parser::statement::code::Code; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; /// The upper-level YUL object, representing the deploy code. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -297,6 +299,24 @@ where } } +impl AstNode for Object { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_object(self); + } + + fn visit_children(&self, ast_visitor: &mut impl AstVisitor) { + self.code.accept(ast_visitor); + + if let Some(inner_object) = &self.inner_object { + inner_object.accept(ast_visitor); + } + } + + fn location(&self) -> Location { + self.location + } +} + #[cfg(test)] mod tests { use crate::lexer::token::location::Location; diff --git a/crates/yul/src/parser/statement/switch/case.rs b/crates/yul/src/parser/statement/switch/case.rs index 8ba71ef..d618ae0 100644 --- a/crates/yul/src/parser/statement/switch/case.rs +++ b/crates/yul/src/parser/statement/switch/case.rs @@ -13,6 +13,8 @@ use crate::lexer::Lexer; use crate::parser::error::Error as ParserError; use crate::parser::statement::block::Block; use crate::parser::statement::expression::literal::Literal; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; /// The Yul switch statement case. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -61,6 +63,21 @@ impl Case { } } +impl AstNode for Case { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_case(self); + } + + fn visit_children(&self, ast_visitor: &mut impl AstVisitor) { + self.literal.accept(ast_visitor); + self.block.accept(ast_visitor); + } + + fn location(&self) -> Location { + self.location + } +} + #[cfg(test)] mod tests { use crate::lexer::token::location::Location; diff --git a/crates/yul/src/parser/statement/switch/mod.rs b/crates/yul/src/parser/statement/switch/mod.rs index b7d0aad..2b374ea 100644 --- a/crates/yul/src/parser/statement/switch/mod.rs +++ b/crates/yul/src/parser/statement/switch/mod.rs @@ -16,6 +16,8 @@ use crate::lexer::Lexer; use crate::parser::error::Error as ParserError; use crate::parser::statement::block::Block; use crate::parser::statement::expression::Expression; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; use self::case::Case; @@ -179,6 +181,28 @@ where } } +impl AstNode for Switch { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_switch(self); + } + + fn visit_children(&self, ast_visitor: &mut impl AstVisitor) { + self.expression.accept(ast_visitor); + + for case in &self.cases { + case.accept(ast_visitor); + } + + if let Some(default) = self.default.as_ref() { + default.accept(ast_visitor); + } + } + + fn location(&self) -> Location { + self.location + } +} + #[cfg(test)] mod tests { use crate::lexer::token::location::Location; diff --git a/crates/yul/src/parser/statement/variable_declaration.rs b/crates/yul/src/parser/statement/variable_declaration.rs index 849b152..8231d33 100644 --- a/crates/yul/src/parser/statement/variable_declaration.rs +++ b/crates/yul/src/parser/statement/variable_declaration.rs @@ -17,6 +17,8 @@ use crate::parser::error::Error as ParserError; use crate::parser::identifier::Identifier; use crate::parser::statement::expression::function_call::name::Name as FunctionName; use crate::parser::statement::expression::Expression; +use crate::visitor::AstNode; +use crate::visitor::AstVisitor; /// The Yul variable declaration statement. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -218,6 +220,26 @@ where } } +impl AstNode for VariableDeclaration { + fn accept(&self, ast_visitor: &mut impl AstVisitor) { + ast_visitor.visit_variable_declaration(self); + } + + fn visit_children(&self, ast_visitor: &mut impl AstVisitor) { + for binding in &self.bindings { + binding.accept(ast_visitor); + } + + if let Some(initializer) = self.expression.as_ref() { + initializer.accept(ast_visitor); + } + } + + fn location(&self) -> Location { + self.location + } +} + #[cfg(test)] mod tests { use crate::lexer::token::location::Location; diff --git a/crates/yul/src/visitor.rs b/crates/yul/src/visitor.rs new file mode 100644 index 0000000..710a054 --- /dev/null +++ b/crates/yul/src/visitor.rs @@ -0,0 +1,768 @@ +//! The YUL AST visitor interface definitions. + +use crate::{ + lexer::token::location::Location, + parser::{ + identifier::Identifier, + statement::{ + assignment::Assignment, + block::Block, + code::Code, + expression::{function_call::FunctionCall, literal::Literal, Expression}, + for_loop::ForLoop, + function_definition::FunctionDefinition, + if_conditional::IfConditional, + object::Object, + switch::{case::Case, Switch}, + variable_declaration::VariableDeclaration, + Statement, + }, + }, +}; + +/// This trait is implemented by all AST node types. +/// +/// It allows to define how the AST is visited on a per-node basis. +pub trait AstNode: std::fmt::Debug { + /// Accept the given [AstVisitor]. + /// + /// This is supposed to call the corresponding `AstVisitor::visit_*` method. + fn accept(&self, ast_visitor: &mut impl AstVisitor); + + /// Let any child nodes accept the given [AstVisitor]. + /// + /// This is supposed visit child nodes in the correct order. + /// + /// Visitor implementations call this method for traversing. + fn visit_children(&self, _ast_visitor: &mut impl AstVisitor) {} + + /// Returns the lexer (source) location of the node. + fn location(&self) -> Location; +} + +/// This trait allows implementing custom AST visitor logic for each node type. +/// +/// The visitor can call the nodes [AstNode::visit_children] method (from any +/// other trait method). This simplifies the implementation of AST visitors. +/// +/// Default implementations which do nothing except accepting the visitor via the +/// [AstVisitor::visit] method are provided for each node type. +/// +/// The [AstVisitor::visit] method is the generic visitor method, seen by all +/// nodes. +/// +/// Visited nodes are given read only access (non-mutable references); it's a +/// compiler design practice to not mutate the AST after parsing. +/// Instead, mutable access to the [AstVisitor] instance itself is provided, +/// allowing to build a new representation if needed. +/// +/// # Example +/// +/// ```rust +/// use revive_yul::visitor::*; +/// +/// /// A very simple visitor that counts all nodes in the AST. +/// #[derive(Default, Debug)] +/// pub struct CountVisitor(usize); +/// +/// impl AstVisitor for CountVisitor { +/// /// Increment the counter for ech node we visit. +/// fn visit(&mut self, node: &impl AstNode) { +/// node.visit_children(self); +/// self.0 += 1; +/// } +/// +/// /* +/// +/// /// If we were interested in a per-statement breakdown of the AST, +/// /// we would implement `visit_*` methods to cover each node like this: +/// fn visit_block(&mut self, node: &Block) { +/// self.visit_children(node); +/// self.block_count += 1; +/// } +/// +/// */ +/// } +/// +/// ``` +pub trait AstVisitor { + /// The generic visitor logic for all node types is executed upon visiting any statement. + fn visit(&mut self, node: &impl AstNode); + + /// The logic to execute upon visiting [Assignment] statements. + fn visit_assignment(&mut self, node: &Assignment) { + self.visit(node); + } + + /// The logic to execute upon visiting any [Block]. + fn visit_block(&mut self, node: &Block) { + self.visit(node); + } + + /// The logic to execute upon visiting [Case] statements. + fn visit_case(&mut self, node: &Case) { + self.visit(node); + } + + /// The logic to execute upon visiting [Code] statements. + fn visit_code(&mut self, node: &Code) { + self.visit(node); + } + + /// The logic to execute upon visiting any [Expression]. + fn visit_expression(&mut self, node: &Expression) { + self.visit(node); + } + + /// The logic to execute upon visiting [ForLoop] statements. + fn visit_for_loop(&mut self, node: &ForLoop) { + self.visit(node); + } + + /// The logic to execute upon visiting [FunctionCall] statements. + fn visit_function_call(&mut self, node: &FunctionCall) { + self.visit(node); + } + + /// The logic to execute upon visiting any [FunctionDefinition]. + fn visit_function_definition(&mut self, node: &FunctionDefinition) { + self.visit(node); + } + + /// The logic to execute upon visiting any [Identifier]. + fn visit_identifier(&mut self, node: &Identifier) { + self.visit(node); + } + + /// The logic to execute upon visiting [IfConditional] statements. + fn visit_if_conditional(&mut self, node: &IfConditional) { + self.visit(node); + } + + /// The logic to execute upon visiting any [Literal]. + fn visit_literal(&mut self, node: &Literal) { + self.visit(node); + } + + /// The logic to execute upon visiting [Object] definitions. + fn visit_object(&mut self, node: &Object) { + self.visit(node); + } + + /// The logic to execute upon visiting any YUL [Statement]. + fn visit_statement(&mut self, node: &Statement) { + self.visit(node); + } + + /// The logic to execute upon visiting [Switch] statements. + fn visit_switch(&mut self, node: &Switch) { + self.visit(node); + } + + /// The logic to execute upon visiting any [VariableDeclaration]. + fn visit_variable_declaration(&mut self, node: &VariableDeclaration) { + self.visit(node); + } +} + +#[cfg(test)] +mod tests { + use crate::{ + lexer::Lexer, + parser::{ + identifier::Identifier, + statement::{ + assignment::Assignment, + block::Block, + code::Code, + expression::{function_call::FunctionCall, literal::Literal, Expression}, + for_loop::ForLoop, + function_definition::FunctionDefinition, + if_conditional::IfConditional, + object::Object, + switch::{case::Case, Switch}, + variable_declaration::VariableDeclaration, + Statement, + }, + }, + }; + + use super::{AstNode, AstVisitor}; + + /// The [Printer] visitor builds the AST back into its textual representation. + #[derive(Default)] + struct Printer { + /// The print buffer. + buffer: String, + /// The current indentation level. + indentation: usize, + } + + impl Printer { + /// Append a newline with the current identation to the print buffer. + fn newline(&mut self) { + self.buffer.push('\n'); + self.indent(); + } + + /// Append the current identation to the print buffer. + fn indent(&mut self) { + for _ in 0..self.indentation { + self.buffer.push_str(" "); + } + } + + /// Append the given `nodes` comma-separated. + fn separate(&mut self, nodes: &[impl AstNode]) { + for (index, argument) in nodes.iter().enumerate() { + argument.accept(self); + + if index < nodes.len() - 1 { + self.buffer.push_str(", "); + } + } + } + } + + impl AstVisitor for Printer { + fn visit(&mut self, node: &impl AstNode) { + node.accept(self); + } + + fn visit_assignment(&mut self, node: &Assignment) { + self.separate(&node.bindings); + + self.buffer.push_str(" := "); + + node.initializer.visit_children(self); + } + + fn visit_block(&mut self, node: &Block) { + self.newline(); + self.buffer.push('{'); + self.indentation += 1; + + node.visit_children(self); + + self.indentation -= 1; + self.newline(); + self.buffer.push('}'); + } + + fn visit_case(&mut self, node: &Case) { + self.newline(); + self.buffer.push_str("case "); + node.visit_children(self); + } + + fn visit_code(&mut self, node: &Code) { + self.buffer.push_str("code "); + node.visit_children(self); + } + + fn visit_expression(&mut self, node: &Expression) { + node.visit_children(self); + } + + fn visit_for_loop(&mut self, node: &ForLoop) { + self.buffer.push_str("for "); + node.visit_children(self); + } + + fn visit_function_call(&mut self, node: &FunctionCall) { + self.buffer.push_str(&format!("{}", node.name)); + self.buffer.push('('); + + self.separate(&node.arguments); + + self.buffer.push(')'); + } + + fn visit_function_definition(&mut self, node: &FunctionDefinition) { + self.buffer + .push_str(&format!("function {}", node.identifier)); + + self.buffer.push('('); + self.separate(&node.arguments); + self.buffer.push(')'); + + self.buffer.push_str(" -> "); + self.separate(&node.result); + + node.body.accept(self); + } + + fn visit_identifier(&mut self, node: &Identifier) { + self.buffer.push_str(&node.inner); + } + + fn visit_if_conditional(&mut self, node: &IfConditional) { + self.buffer.push_str("if "); + node.visit_children(self); + } + + fn visit_literal(&mut self, node: &Literal) { + self.buffer.push_str(&format!("{}", node.inner)); + } + + fn visit_object(&mut self, node: &Object) { + self.newline(); + self.buffer.push_str("object \""); + self.buffer.push_str(&node.identifier); + self.buffer.push_str("\" {"); + self.indentation += 1; + self.newline(); + + node.visit_children(self); + + self.indentation -= 1; + self.newline(); + self.buffer.push('}'); + } + + fn visit_statement(&mut self, node: &Statement) { + self.newline(); + node.visit_children(self); + } + + fn visit_switch(&mut self, node: &Switch) { + self.buffer.push_str("switch "); + node.visit_children(self); + } + + fn visit_variable_declaration(&mut self, node: &VariableDeclaration) { + self.buffer.push_str("let "); + self.separate(&node.bindings); + + if let Some(initializer) = node.expression.as_ref() { + self.buffer.push_str(" := "); + initializer.visit_children(self); + } + } + } + + const ERC20: &str = r#"/// @use-src 0:"crates/integration/contracts/ERC20.sol" +object "ERC20_247" { + code { + { + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + mstore(64, memoryguard(0x80)) + if callvalue() { revert(0, 0) } + let oldLen := extract_byte_array_length(sload(/** @src 0:1542:1563 "\"Solidity by Example\"" */ 0x03)) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + if gt(oldLen, 31) + { + mstore(/** @src -1:-1:-1 */ 0, /** @src 0:1542:1563 "\"Solidity by Example\"" */ 0x03) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let data := keccak256(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 0x20) + let deleteStart := add(data, 1) + deleteStart := data + let _1 := add(data, shr(5, add(oldLen, 31))) + let start := data + for { } lt(start, _1) { start := add(start, 1) } + { + sstore(start, /** @src -1:-1:-1 */ 0) + } + } + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + sstore(/** @src 0:1542:1563 "\"Solidity by Example\"" */ 0x03, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ add("Solidity by Example", 38)) + let oldLen_1 := extract_byte_array_length(sload(/** @src 0:1592:1601 "\"SOLBYEX\"" */ 0x04)) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + if gt(oldLen_1, 31) + { + mstore(/** @src -1:-1:-1 */ 0, /** @src 0:1592:1601 "\"SOLBYEX\"" */ 0x04) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let data_1 := keccak256(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 0x20) + let deleteStart_1 := add(data_1, 1) + deleteStart_1 := data_1 + let _2 := add(data_1, shr(5, add(oldLen_1, 31))) + let start_1 := data_1 + for { } lt(start_1, _2) { start_1 := add(start_1, 1) } + { + sstore(start_1, /** @src -1:-1:-1 */ 0) + } + } + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + sstore(/** @src 0:1592:1601 "\"SOLBYEX\"" */ 0x04, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ add("SOLBYEX", 14)) + sstore(/** @src 0:1631:1633 "18" */ 0x05, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ or(and(sload(/** @src 0:1631:1633 "18" */ 0x05), /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ not(255)), /** @src 0:1631:1633 "18" */ 0x12)) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let _3 := mload(64) + let _4 := datasize("ERC20_247_deployed") + codecopy(_3, dataoffset("ERC20_247_deployed"), _4) + return(_3, _4) + } + function extract_byte_array_length(data) -> length + { + length := shr(1, data) + let outOfPlaceEncoding := and(data, 1) + if iszero(outOfPlaceEncoding) { length := and(length, 0x7f) } + if eq(outOfPlaceEncoding, lt(length, 32)) + { + mstore(0, shl(224, 0x4e487b71)) + mstore(4, 0x22) + revert(0, 0x24) + } + } + } + /// @use-src 0:"crates/integration/contracts/ERC20.sol" + object "ERC20_247_deployed" { + code { + { + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + mstore(64, memoryguard(0x80)) + if iszero(lt(calldatasize(), 4)) + { + switch shr(224, calldataload(0)) + case 0x06fdde03 { + if callvalue() { revert(0, 0) } + if slt(add(calldatasize(), not(3)), 0) { revert(0, 0) } + /// @src 0:1521:1563 "string public name = \"Solidity by Example\"" + let value := /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 0 + let offset := 0 + offset := 0 + let memPtr := mload(64) + let ret := 0 + let slotValue := sload(/** @src 0:1521:1563 "string public name = \"Solidity by Example\"" */ 3) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let length := 0 + length := shr(1, slotValue) + let outOfPlaceEncoding := and(slotValue, 1) + if iszero(outOfPlaceEncoding) { length := and(length, 0x7f) } + if eq(outOfPlaceEncoding, lt(length, 32)) + { + mstore(0, shl(224, 0x4e487b71)) + mstore(4, 0x22) + revert(0, 0x24) + } + mstore(memPtr, length) + switch outOfPlaceEncoding + case 0 { + mstore(add(memPtr, 32), and(slotValue, not(255))) + ret := add(add(memPtr, shl(5, iszero(iszero(length)))), 32) + } + case 1 { + mstore(0, /** @src 0:1521:1563 "string public name = \"Solidity by Example\"" */ 3) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let dataPos := keccak256(0, 32) + let i := 0 + for { } lt(i, length) { i := add(i, 32) } + { + mstore(add(add(memPtr, i), 32), sload(dataPos)) + dataPos := add(dataPos, 1) + } + ret := add(add(memPtr, i), 32) + } + let newFreePtr := add(memPtr, and(add(sub(ret, memPtr), 31), not(31))) + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) + { + mstore(0, shl(224, 0x4e487b71)) + mstore(4, 0x41) + revert(0, 0x24) + } + mstore(64, newFreePtr) + value := memPtr + let memPos := mload(64) + return(memPos, sub(abi_encode_string(memPos, memPtr), memPos)) + } + case 0x095ea7b3 { + if callvalue() { revert(0, 0) } + if slt(add(calldatasize(), not(3)), 64) { revert(0, 0) } + let value0 := abi_decode_address_3473() + let value_1 := calldataload(36) + mstore(0, /** @src 0:1974:1984 "msg.sender" */ caller()) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + mstore(32, /** @src 0:1964:1973 "allowance" */ 0x02) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let dataSlot := keccak256(0, 64) + /// @src 0:1964:1994 "allowance[msg.sender][spender]" + let dataSlot_1 := /** @src -1:-1:-1 */ 0 + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + mstore(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ and(/** @src 0:1964:1994 "allowance[msg.sender][spender]" */ value0, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ sub(shl(160, 1), 1))) + mstore(0x20, /** @src 0:1964:1985 "allowance[msg.sender]" */ dataSlot) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + dataSlot_1 := keccak256(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 0x40) + sstore(/** @src 0:1964:1994 "allowance[msg.sender][spender]" */ dataSlot_1, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ value_1) + /// @src 0:2018:2055 "Approval(msg.sender, spender, amount)" + let _1 := /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ mload(64) + mstore(_1, value_1) + /// @src 0:2018:2055 "Approval(msg.sender, spender, amount)" + log3(_1, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 32, /** @src 0:2018:2055 "Approval(msg.sender, spender, amount)" */ 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925, /** @src 0:1974:1984 "msg.sender" */ caller(), /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ and(/** @src 0:2018:2055 "Approval(msg.sender, spender, amount)" */ value0, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ sub(shl(160, 1), 1))) + let memPos_1 := mload(64) + mstore(memPos_1, 1) + return(memPos_1, 32) + } + case 0x18160ddd { + if callvalue() { revert(0, 0) } + if slt(add(calldatasize(), not(3)), 0) { revert(0, 0) } + let _2 := sload(0) + let memPos_2 := mload(64) + mstore(memPos_2, _2) + return(memPos_2, 32) + } + case 0x23b872dd { + if callvalue() { revert(0, 0) } + if slt(add(calldatasize(), not(3)), 96) { revert(0, 0) } + let value0_1 := abi_decode_address_3473() + let value1 := abi_decode_address() + let value_2 := calldataload(68) + let _3 := and(value0_1, sub(shl(160, 1), 1)) + mstore(0, _3) + mstore(32, /** @src 0:2223:2232 "allowance" */ 0x02) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let dataSlot_2 := keccak256(0, 64) + /// @src 0:2223:2252 "allowance[sender][msg.sender]" + let dataSlot_3 := /** @src -1:-1:-1 */ 0 + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + mstore(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ and(/** @src 0:2241:2251 "msg.sender" */ caller(), /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ sub(shl(160, 1), 1))) + mstore(0x20, /** @src 0:2223:2252 "allowance[sender][msg.sender]" */ dataSlot_2) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + dataSlot_3 := keccak256(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 0x40) + sstore(/** @src 0:2223:2252 "allowance[sender][msg.sender]" */ dataSlot_3, /** @src 0:2223:2262 "allowance[sender][msg.sender] -= amount" */ checked_sub_uint256(/** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ sload(/** @src 0:2223:2252 "allowance[sender][msg.sender]" */ dataSlot_3), /** @src 0:2223:2262 "allowance[sender][msg.sender] -= amount" */ value_2)) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + mstore(0, _3) + mstore(32, 1) + let dataSlot_4 := keccak256(0, 64) + sstore(dataSlot_4, /** @src 0:2272:2299 "balanceOf[sender] -= amount" */ checked_sub_uint256(/** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ sload(/** @src 0:2272:2299 "balanceOf[sender] -= amount" */ dataSlot_4), value_2)) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let _4 := and(value1, sub(shl(160, 1), 1)) + mstore(0, _4) + mstore(32, 1) + let dataSlot_5 := keccak256(0, 64) + sstore(dataSlot_5, /** @src 0:2309:2339 "balanceOf[recipient] += amount" */ checked_add_uint256(/** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ sload(/** @src 0:2309:2339 "balanceOf[recipient] += amount" */ dataSlot_5), value_2)) + /// @src 0:2354:2389 "Transfer(sender, recipient, amount)" + let _5 := /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ mload(64) + mstore(_5, value_2) + /// @src 0:2354:2389 "Transfer(sender, recipient, amount)" + log3(_5, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 32, /** @src 0:2354:2389 "Transfer(sender, recipient, amount)" */ 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, _3, _4) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let memPos_3 := mload(64) + mstore(memPos_3, 1) + return(memPos_3, 32) + } + case 0x313ce567 { + if callvalue() { revert(0, 0) } + if slt(add(calldatasize(), not(3)), 0) { revert(0, 0) } + let value_3 := and(sload(/** @src 0:1607:1633 "uint8 public decimals = 18" */ 5), /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 0xff) + let memPos_4 := mload(64) + mstore(memPos_4, value_3) + return(memPos_4, 32) + } + case 0x42966c68 { + if callvalue() { revert(0, 0) } + if slt(add(calldatasize(), not(3)), 32) { revert(0, 0) } + let value_4 := calldataload(4) + mstore(0, /** @src 0:2655:2665 "msg.sender" */ caller()) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + mstore(32, 1) + let dataSlot_6 := keccak256(0, 64) + sstore(dataSlot_6, /** @src 0:2645:2676 "balanceOf[msg.sender] -= amount" */ checked_sub_uint256(/** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ sload(/** @src 0:2645:2676 "balanceOf[msg.sender] -= amount" */ dataSlot_6), value_4)) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + sstore(0, /** @src 0:2686:2707 "totalSupply -= amount" */ checked_sub_uint256(/** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ sload(0), /** @src 0:2686:2707 "totalSupply -= amount" */ value_4)) + /// @src 0:2722:2762 "Transfer(msg.sender, address(0), amount)" + let _6 := /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ mload(64) + mstore(_6, value_4) + /// @src 0:2722:2762 "Transfer(msg.sender, address(0), amount)" + log3(_6, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 32, /** @src 0:2722:2762 "Transfer(msg.sender, address(0), amount)" */ 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, /** @src 0:2655:2665 "msg.sender" */ caller(), /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 0) + return(0, 0) + } + case 0x70a08231 { + if callvalue() { revert(0, 0) } + if slt(add(calldatasize(), not(3)), 32) { revert(0, 0) } + mstore(0, and(abi_decode_address_3473(), sub(shl(160, 1), 1))) + mstore(32, /** @src 0:1407:1448 "mapping(address => uint) public balanceOf" */ 1) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let _7 := sload(keccak256(0, 64)) + let memPos_5 := mload(64) + mstore(memPos_5, _7) + return(memPos_5, 32) + } + case 0x95d89b41 { + if callvalue() { revert(0, 0) } + if slt(add(calldatasize(), not(3)), 0) { revert(0, 0) } + /// @src 0:1569:1601 "string public symbol = \"SOLBYEX\"" + let value_5 := /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 0 + let offset_1 := 0 + offset_1 := 0 + let memPtr_1 := mload(64) + let ret_1 := 0 + let slotValue_1 := sload(4) + let length_1 := 0 + length_1 := shr(1, slotValue_1) + let outOfPlaceEncoding_1 := and(slotValue_1, 1) + if iszero(outOfPlaceEncoding_1) + { + length_1 := and(length_1, 0x7f) + } + if eq(outOfPlaceEncoding_1, lt(length_1, 32)) + { + mstore(0, shl(224, 0x4e487b71)) + mstore(4, 0x22) + revert(0, 0x24) + } + mstore(memPtr_1, length_1) + switch outOfPlaceEncoding_1 + case 0 { + mstore(add(memPtr_1, 32), and(slotValue_1, not(255))) + ret_1 := add(add(memPtr_1, shl(5, iszero(iszero(length_1)))), 32) + } + case 1 { + mstore(0, 4) + let dataPos_1 := keccak256(0, 32) + let i_1 := 0 + for { } lt(i_1, length_1) { i_1 := add(i_1, 32) } + { + mstore(add(add(memPtr_1, i_1), 32), sload(dataPos_1)) + dataPos_1 := add(dataPos_1, 1) + } + ret_1 := add(add(memPtr_1, i_1), 32) + } + let newFreePtr_1 := add(memPtr_1, and(add(sub(ret_1, memPtr_1), 31), not(31))) + if or(gt(newFreePtr_1, 0xffffffffffffffff), lt(newFreePtr_1, memPtr_1)) + { + mstore(0, shl(224, 0x4e487b71)) + mstore(4, 0x41) + revert(0, 0x24) + } + mstore(64, newFreePtr_1) + value_5 := memPtr_1 + let memPos_6 := mload(64) + return(memPos_6, sub(abi_encode_string(memPos_6, memPtr_1), memPos_6)) + } + case 0xa0712d68 { + if callvalue() { revert(0, 0) } + if slt(add(calldatasize(), not(3)), 32) { revert(0, 0) } + let value_6 := calldataload(4) + mstore(0, /** @src 0:2479:2489 "msg.sender" */ caller()) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + mstore(32, 1) + let dataSlot_7 := keccak256(0, 64) + sstore(dataSlot_7, /** @src 0:2469:2500 "balanceOf[msg.sender] += amount" */ checked_add_uint256(/** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ sload(/** @src 0:2469:2500 "balanceOf[msg.sender] += amount" */ dataSlot_7), value_6)) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + sstore(0, /** @src 0:2510:2531 "totalSupply += amount" */ checked_add_uint256(/** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ sload(0), /** @src 0:2510:2531 "totalSupply += amount" */ value_6)) + /// @src 0:2546:2586 "Transfer(address(0), msg.sender, amount)" + let _8 := /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ mload(64) + mstore(_8, value_6) + /// @src 0:2546:2586 "Transfer(address(0), msg.sender, amount)" + log3(_8, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 32, /** @src 0:2546:2586 "Transfer(address(0), msg.sender, amount)" */ 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 0, /** @src 0:2479:2489 "msg.sender" */ caller()) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + return(0, 0) + } + case 0xa9059cbb { + if callvalue() { revert(0, 0) } + if slt(add(calldatasize(), not(3)), 64) { revert(0, 0) } + let value0_2 := abi_decode_address_3473() + let value_7 := calldataload(36) + mstore(0, /** @src 0:1734:1744 "msg.sender" */ caller()) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + mstore(32, 1) + let dataSlot_8 := keccak256(0, 64) + sstore(dataSlot_8, /** @src 0:1724:1755 "balanceOf[msg.sender] -= amount" */ checked_sub_uint256(/** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ sload(/** @src 0:1724:1755 "balanceOf[msg.sender] -= amount" */ dataSlot_8), value_7)) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let _9 := and(value0_2, sub(shl(160, 1), 1)) + mstore(0, _9) + mstore(32, 1) + let dataSlot_9 := keccak256(0, 64) + sstore(dataSlot_9, /** @src 0:1765:1795 "balanceOf[recipient] += amount" */ checked_add_uint256(/** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ sload(/** @src 0:1765:1795 "balanceOf[recipient] += amount" */ dataSlot_9), value_7)) + /// @src 0:1810:1849 "Transfer(msg.sender, recipient, amount)" + let _10 := /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ mload(64) + mstore(_10, value_7) + /// @src 0:1810:1849 "Transfer(msg.sender, recipient, amount)" + log3(_10, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 32, /** @src 0:1810:1849 "Transfer(msg.sender, recipient, amount)" */ 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, /** @src 0:1734:1744 "msg.sender" */ caller(), /** @src 0:1810:1849 "Transfer(msg.sender, recipient, amount)" */ _9) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let memPos_7 := mload(64) + mstore(memPos_7, 1) + return(memPos_7, 32) + } + case 0xdd62ed3e { + if callvalue() { revert(0, 0) } + if slt(add(calldatasize(), not(3)), 64) { revert(0, 0) } + let value0_3 := abi_decode_address_3473() + let value1_1 := abi_decode_address() + mstore(0, and(value0_3, sub(shl(160, 1), 1))) + mstore(32, /** @src 0:1454:1515 "mapping(address => mapping(address => uint)) public allowance" */ 2) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let dataSlot_10 := keccak256(0, 64) + /// @src 0:1454:1515 "mapping(address => mapping(address => uint)) public allowance" + let dataSlot_11 := /** @src -1:-1:-1 */ 0 + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + mstore(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ and(/** @src 0:1454:1515 "mapping(address => mapping(address => uint)) public allowance" */ value1_1, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ sub(shl(160, 1), 1))) + mstore(0x20, /** @src 0:1454:1515 "mapping(address => mapping(address => uint)) public allowance" */ dataSlot_10) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + dataSlot_11 := keccak256(/** @src -1:-1:-1 */ 0, /** @src 0:1347:2771 "contract ERC20 is IERC20 {..." */ 0x40) + let _11 := sload(/** @src 0:1454:1515 "mapping(address => mapping(address => uint)) public allowance" */ dataSlot_11) + /// @src 0:1347:2771 "contract ERC20 is IERC20 {..." + let memPos_8 := mload(64) + mstore(memPos_8, _11) + return(memPos_8, 32) + } + } + revert(0, 0) + } + function abi_encode_string(headStart, value0) -> tail + { + mstore(headStart, 32) + let length := mload(value0) + mstore(add(headStart, 32), length) + mcopy(add(headStart, 64), add(value0, 32), length) + mstore(add(add(headStart, length), 64), 0) + tail := add(add(headStart, and(add(length, 31), not(31))), 64) + } + function abi_decode_address_3473() -> value + { + value := calldataload(4) + if iszero(eq(value, and(value, sub(shl(160, 1), 1)))) { revert(0, 0) } + } + function abi_decode_address() -> value + { + value := calldataload(36) + if iszero(eq(value, and(value, sub(shl(160, 1), 1)))) { revert(0, 0) } + } + function checked_sub_uint256(x, y) -> diff + { + diff := sub(x, y) + if gt(diff, x) + { + mstore(0, shl(224, 0x4e487b71)) + mstore(4, 0x11) + revert(0, 0x24) + } + } + function checked_add_uint256(x, y) -> sum + { + sum := add(x, y) + if gt(x, sum) + { + mstore(0, shl(224, 0x4e487b71)) + mstore(4, 0x11) + revert(0, 0x24) + } + } + } + data ".metadata" hex"a264697066735822122050b3876a7c06489a119481ba2b8611bfb9d92d0624a61503d2b86a77af8e277164736f6c634300081c0033" + } +}"#; + + /// Parsing the output of the print visitor as a basic integration test. + #[test] + fn print_visitor_works() { + let mut printer = Printer::default(); + Object::parse(&mut Lexer::new(ERC20.into()), None) + .unwrap() + .accept(&mut printer); + + let mut printer2 = Printer::default(); + Object::parse(&mut Lexer::new(printer.buffer.clone()), None) + .unwrap() + .accept(&mut printer2); + + assert_eq!( + printer.buffer, printer2.buffer, + "the output from the printers must converge immediately" + ); + + assert!( + !printer.buffer.is_empty(), + "the printer must produce output" + ); + } +}