YUL tree visitor interface (#369)

- Implement the visitor interface. This simplifies working with the AST,
for example transformations into other IRs or collecting and analyzing
various statistics.
- Switch the explorer to use the visitor interface.
- Add the reciprocal function name conversion for function names.
- Some drive-by cosmetic fixes.

---------

Signed-off-by: xermicus <bigcyrill@hotmail.com>
This commit is contained in:
xermicus
2025-08-10 00:08:25 +02:00
committed by GitHub
parent 72f9e4f66e
commit 903cbd7159
24 changed files with 1303 additions and 162 deletions
+1
View File
@@ -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.
+10 -1
View File
@@ -29,7 +29,16 @@ pub fn source_file(
dwarfdump_executable: &Option<PathBuf>,
) -> anyhow::Result<PathBuf> {
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.
+5 -33
View File
@@ -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<Location, String>,
/// 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",
};
+92 -127
View File
@@ -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<Location, String>;
/// The location to statements mapper.
pub struct LocationMapper(HashMap<Location, String>);
/// Construct a [LocationMap] from the given YUL `source` file.
pub fn map_locations(source: &Path) -> anyhow::Result<LocationMap> {
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<HashMap<Location, String>> {
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());
}
}
+3
View File
@@ -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<PathBuf>,
/// Path of the shared object to analyze.
/// It must have been compiled with debug info (-g).
file: PathBuf,
}
@@ -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}\""),
}
}
}
+1
View File
@@ -3,3 +3,4 @@
pub mod error;
pub mod lexer;
pub mod parser;
pub mod visitor;
+14
View File
@@ -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
}
}
@@ -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
}
}
+18
View File
@@ -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;
+16
View File
@@ -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;
@@ -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
}
}
@@ -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}")
}
}
@@ -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
}
}
@@ -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()
}
}
@@ -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
}
}
@@ -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;
@@ -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
}
}
+30
View File
@@ -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()
}
}
+20
View File
@@ -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;
@@ -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;
@@ -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;
@@ -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;
+768
View File
@@ -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"
);
}
}