From 7583a00e48005daa9a3404b0b8ccd3d7889ac8e0 Mon Sep 17 00:00:00 2001 From: philogy Date: Wed, 13 Nov 2024 19:30:31 +0700 Subject: [PATCH 1/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Rename=20to=20`CodeTab?= =?UTF-8?q?le`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/analysis/src/lib.rs | 4 ++-- crates/ast/src/ast.rs | 4 ++-- crates/ast/src/parser.rs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/analysis/src/lib.rs b/crates/analysis/src/lib.rs index ffe8f97..fb0faec 100644 --- a/crates/analysis/src/lib.rs +++ b/crates/analysis/src/lib.rs @@ -249,7 +249,7 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a if !global_exists!( self.global_defs, table_ref.ident(), - Definition::Table { .. } | Definition::Jumptable(_) + Definition::CodeTable { .. } | Definition::Jumptable(_) ) { self.emit(AnalysisError::DefinitionNotFound { scope: self.m, @@ -478,7 +478,7 @@ mod test { expr: (ConstExpr::FreeStoragePointer, span), }; - let unrelated_table = Definition::Table { + let unrelated_table = Definition::CodeTable { name: ("awesome_stuff", span), data: Box::new([0x00, 0x01]), }; diff --git a/crates/ast/src/ast.rs b/crates/ast/src/ast.rs index 1739a13..9951feb 100644 --- a/crates/ast/src/ast.rs +++ b/crates/ast/src/ast.rs @@ -20,7 +20,7 @@ pub enum Definition<'src> { expr: Spanned, }, Jumptable(Jumptable<'src>), - Table { + CodeTable { name: Spanned<&'src str>, data: Box<[u8]>, }, @@ -47,7 +47,7 @@ impl<'src> IdentifiableNode<'src> for Definition<'src> { Self::Macro(m) => &m.name, Self::Constant { name, .. } => name, Self::Jumptable(jt) => &jt.name, - Self::Table { name, .. } => name, + Self::CodeTable { name, .. } => name, Self::SolEvent(e) => &e.name, Self::SolError(e) => &e.name, Self::SolFunction(f) => &f.name, diff --git a/crates/ast/src/parser.rs b/crates/ast/src/parser.rs index 528df3b..5320f19 100644 --- a/crates/ast/src/parser.rs +++ b/crates/ast/src/parser.rs @@ -280,7 +280,7 @@ fn table<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definition .collect::>() .delimited_by(punct('{'), punct('}')), ) - .map(|(name, code)| ast::Definition::Table { + .map(|(name, code)| ast::Definition::CodeTable { name, data: code .into_iter() @@ -668,7 +668,7 @@ mod tests { assert_ok!( table(), vec![Ident("table"), Ident("TEST"), Punct('{'), Hex("0xc0de"), Punct('}')], - ast::Definition::Table { + ast::Definition::CodeTable { name: ("TEST", span), data: Box::new([0xc0, 0xde]) } @@ -683,7 +683,7 @@ mod tests { Hex("0xcc00ddee"), Punct('}') ], - ast::Definition::Table { + ast::Definition::CodeTable { name: ("TEST", span), data: Box::new([0xc0, 0xde, 0xcc, 0x00, 0xdd, 0xee]) } From 89ce9f784c38645d97236aad33fe29365eef0f1b Mon Sep 17 00:00:00 2001 From: philogy Date: Wed, 13 Nov 2024 19:39:42 +0700 Subject: [PATCH 2/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Make=20keywords=20expl?= =?UTF-8?q?icit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/ast/src/lexer.rs | 38 ++++++++++++++++++-------------------- crates/ast/src/parser.rs | 8 ++++---- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/crates/ast/src/lexer.rs b/crates/ast/src/lexer.rs index e6aeef2..c31fce1 100644 --- a/crates/ast/src/lexer.rs +++ b/crates/ast/src/lexer.rs @@ -3,8 +3,7 @@ use chumsky::{ error::Rich, extra, primitive::{any, choice, just, none_of, one_of}, - text::{self, ascii::keyword}, - IterParser, Parser, + text, IterParser, Parser, }; use std::fmt; @@ -13,15 +12,17 @@ pub(crate) fn lex(src: &str) -> Result>, Vec>() + .collect() }) } /// Lexer token #[derive(Debug, Clone, PartialEq, Eq)] pub enum Token<'src> { + Define, + Include, + Comment(&'src str), - Keyword(&'src str), Ident(&'src str), Punct(char), Dec(&'src str), @@ -35,12 +36,11 @@ pub enum Token<'src> { impl fmt::Display for Token<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Token::Comment(s) - | Token::Keyword(s) - | Token::Ident(s) - | Token::Dec(s) - | Token::Hex(s) - | Token::Bin(s) => write!(f, "{}", s), + Token::Define => write!(f, "#define"), + Token::Include => write!(f, "#include"), + Token::Comment(s) | Token::Ident(s) | Token::Dec(s) | Token::Hex(s) | Token::Bin(s) => { + write!(f, "{}", s) + } Token::String(s) => write!(f, "{}", s), Token::Punct(c) | Token::Error(c) => write!(f, "{}", c), } @@ -59,10 +59,11 @@ fn lexer<'src>( } } }); - let keyword = just("#") - .ignore_then(choice((keyword("define"), keyword("include")))) - .then_ignore(validate_end) - .map(Token::Keyword); + let keyword = choice(( + just("#define").to(Token::Define), + just("#include").to(Token::Include), + )) + .then_ignore(validate_end); let ident = text::ident().then_ignore(validate_end).map(Token::Ident); @@ -96,7 +97,7 @@ fn lexer<'src>( // comments let single_line_comment = just("//") - .then(any().and_is(just('\n').not()).repeated()) + .then(any().and_is(text::newline().not()).repeated()) .padded(); let multi_line_comment = just("/*") .then(any().and_is(just("*/").not()).repeated()) @@ -135,11 +136,8 @@ mod tests { #[test] fn lex_keyword() { - assert_ok!("#define", (Token::Keyword("define"), SimpleSpan::new(0, 7))); - assert_ok!( - "#include", - (Token::Keyword("include"), SimpleSpan::new(0, 8)) - ); + assert_ok!("#define", (Token::Define, SimpleSpan::new(0, 7))); + assert_ok!("#include", (Token::Include, SimpleSpan::new(0, 8))); } #[test] diff --git a/crates/ast/src/parser.rs b/crates/ast/src/parser.rs index 5320f19..872864c 100644 --- a/crates/ast/src/parser.rs +++ b/crates/ast/src/parser.rs @@ -65,7 +65,7 @@ fn root<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Root<'src>> fn root_section<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::RootSection<'src>> { let definition = definition().map(ast::RootSection::Definition); - let include = just(Keyword("include")) + let include = just(Include) .ignore_then(select! {String(s) => s}.map_with(|s, ex| (s, ex.span()))) .map(ast::RootSection::Include); @@ -73,7 +73,7 @@ fn root_section<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Roo } fn definition<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definition<'src>> { - just(Keyword("define")).ignore_then(choice(( + just(Define).ignore_then(choice(( r#macro(), constant(), table(), @@ -496,12 +496,12 @@ mod tests { assert_ok!( root_section(), - vec![Keyword("include"), String("test".to_string())], + vec![Include, String("test".to_string())], ast::RootSection::Include(("test".to_string(), span)) ); assert_ok!( root_section(), - vec![Keyword("define"), Ident("constant"), Ident("TEST"), Punct('='), Hex("0x1")], + vec![Define, Ident("constant"), Ident("TEST"), Punct('='), Hex("0x1")], ast::RootSection::Definition(ast::Definition::Constant { name: ("TEST", span), expr: (ast::ConstExpr::Value(uint!(1_U256)), span) From 3603dc0ae8159bf062fa161f8d9644db2c8cf297 Mon Sep 17 00:00:00 2001 From: philogy Date: Thu, 14 Nov 2024 10:51:19 +0700 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=9A=A7=20Parse=20jump=20tables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/analysis/src/lib.rs | 4 +-- crates/ast/src/ast.rs | 17 +++++++------ crates/ast/src/lexer.rs | 15 +++++++++++ crates/ast/src/parser.rs | 51 ++++++++++++++++++++++++-------------- 4 files changed, 59 insertions(+), 28 deletions(-) diff --git a/crates/analysis/src/lib.rs b/crates/analysis/src/lib.rs index fb0faec..0b58f2b 100644 --- a/crates/analysis/src/lib.rs +++ b/crates/analysis/src/lib.rs @@ -478,10 +478,10 @@ mod test { expr: (ConstExpr::FreeStoragePointer, span), }; - let unrelated_table = Definition::CodeTable { + let unrelated_table = Definition::CodeTable(CodeTable { name: ("awesome_stuff", span), data: Box::new([0x00, 0x01]), - }; + }); let d3 = Definition::Macro(Macro { name: ("TheWhat", span), args: (Box::new([("nice", span)]), span), diff --git a/crates/ast/src/ast.rs b/crates/ast/src/ast.rs index 9951feb..55bc69d 100644 --- a/crates/ast/src/ast.rs +++ b/crates/ast/src/ast.rs @@ -20,10 +20,7 @@ pub enum Definition<'src> { expr: Spanned, }, Jumptable(Jumptable<'src>), - CodeTable { - name: Spanned<&'src str>, - data: Box<[u8]>, - }, + CodeTable(CodeTable<'src>), SolFunction(SolFunction<'src>), SolEvent(SolEvent<'src>), SolError(SolError<'src>), @@ -47,7 +44,7 @@ impl<'src> IdentifiableNode<'src> for Definition<'src> { Self::Macro(m) => &m.name, Self::Constant { name, .. } => name, Self::Jumptable(jt) => &jt.name, - Self::CodeTable { name, .. } => name, + Self::CodeTable(ct) => &ct.name, Self::SolEvent(e) => &e.name, Self::SolError(e) => &e.name, Self::SolFunction(f) => &f.name, @@ -118,11 +115,17 @@ pub enum Invoke<'src> { BuiltinError(Spanned<&'src str>), } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CodeTable<'src> { + pub name: Spanned<&'src str>, + pub data: Box<[u8]>, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Jumptable<'src> { pub name: Spanned<&'src str>, - pub size: u8, - pub labels: Box<[&'src str]>, + pub label_size: u8, + pub labels: Box<[Spanned<&'src str>]>, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/ast/src/lexer.rs b/crates/ast/src/lexer.rs index c31fce1..957acab 100644 --- a/crates/ast/src/lexer.rs +++ b/crates/ast/src/lexer.rs @@ -21,6 +21,9 @@ pub(crate) fn lex(src: &str) -> Result>, Vec { Define, Include, + Table, + JumpTable, + JumpTablePacked, Comment(&'src str), Ident(&'src str), @@ -38,6 +41,9 @@ impl fmt::Display for Token<'_> { match self { Token::Define => write!(f, "#define"), Token::Include => write!(f, "#include"), + Token::Table => write!(f, "table"), + Token::JumpTable => write!(f, "jumptable"), + Token::JumpTablePacked => write!(f, "jumptable__packed"), Token::Comment(s) | Token::Ident(s) | Token::Dec(s) | Token::Hex(s) | Token::Bin(s) => { write!(f, "{}", s) } @@ -62,6 +68,9 @@ fn lexer<'src>( let keyword = choice(( just("#define").to(Token::Define), just("#include").to(Token::Include), + just("table").to(Token::Table), + just("jumptable__packed").to(Token::JumpTablePacked), + just("jumptable").to(Token::JumpTable), )) .then_ignore(validate_end); @@ -138,6 +147,12 @@ mod tests { fn lex_keyword() { assert_ok!("#define", (Token::Define, SimpleSpan::new(0, 7))); assert_ok!("#include", (Token::Include, SimpleSpan::new(0, 8))); + assert_ok!("table", (Token::Table, SimpleSpan::new(0, 5))); + assert_ok!("jumptable", (Token::JumpTable, SimpleSpan::new(0, 9))); + assert_ok!( + "jumptable__packed", + (Token::JumpTablePacked, SimpleSpan::new(0, 17)) + ); } #[test] diff --git a/crates/ast/src/parser.rs b/crates/ast/src/parser.rs index 872864c..59c7bb4 100644 --- a/crates/ast/src/parser.rs +++ b/crates/ast/src/parser.rs @@ -76,7 +76,8 @@ fn definition<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Defin just(Define).ignore_then(choice(( r#macro(), constant(), - table(), + code_table(), + jump_table(), sol_function(), sol_event(), sol_error(), @@ -271,8 +272,8 @@ fn constant<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definit .map(|(name, expr)| ast::Definition::Constant { name, expr }) } -fn table<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definition<'src>> { - just(Ident("table")) +fn code_table<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definition<'src>> { + just(Table) .ignore_then(ident()) .then( code() @@ -280,7 +281,7 @@ fn table<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definition .collect::>() .delimited_by(punct('{'), punct('}')), ) - .map(|(name, code)| ast::Definition::CodeTable { + .map(|(name, code)| ast::CodeTable { name, data: code .into_iter() @@ -288,6 +289,25 @@ fn table<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definition .collect::>() .into_boxed_slice(), }) + .map(ast::Definition::CodeTable) +} + +fn jump_table<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definition<'src>> { + choice((just(JumpTable).to(32), just(JumpTablePacked).to(2))) + .then(ident()) + .then( + ident() + .repeated() + .collect() + .map(|x: Vec<_>| x.into_boxed_slice()) + .delimited_by(punct('{'), punct('}')), + ) + .map(|((label_size, name), labels)| ast::Jumptable { + name, + label_size, + labels, + }) + .map(ast::Definition::Jumptable) } fn sol_function<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definition<'src>> { @@ -666,27 +686,20 @@ mod tests { let span: Span = SimpleSpan::new(0, 0); assert_ok!( - table(), - vec![Ident("table"), Ident("TEST"), Punct('{'), Hex("0xc0de"), Punct('}')], - ast::Definition::CodeTable { + code_table(), + vec![Table, Ident("TEST"), Punct('{'), Hex("0xc0de"), Punct('}')], + ast::Definition::CodeTable(ast::CodeTable { name: ("TEST", span), data: Box::new([0xc0, 0xde]) - } + }) ); assert_ok!( - table(), - vec![ - Ident("table"), - Ident("TEST"), - Punct('{'), - Hex("0xc0de"), - Hex("0xcc00ddee"), - Punct('}') - ], - ast::Definition::CodeTable { + code_table(), + vec![Table, Ident("TEST"), Punct('{'), Hex("0xc0de"), Hex("0xcc00ddee"), Punct('}')], + ast::Definition::CodeTable(ast::CodeTable { name: ("TEST", span), data: Box::new([0xc0, 0xde, 0xcc, 0x00, 0xdd, 0xee]) - } + }) ); } From e10170e3516dbb1efbcc8269a713b700641aea70 Mon Sep 17 00:00:00 2001 From: philogy Date: Thu, 14 Nov 2024 11:03:32 +0700 Subject: [PATCH 4/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Cleanup=20spanned?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/ast/src/ast.rs | 4 +- crates/ast/src/parser.rs | 89 +++++++++++++++++++++++++++++++--------- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/crates/ast/src/ast.rs b/crates/ast/src/ast.rs index 55bc69d..93b3537 100644 --- a/crates/ast/src/ast.rs +++ b/crates/ast/src/ast.rs @@ -19,7 +19,7 @@ pub enum Definition<'src> { name: Spanned<&'src str>, expr: Spanned, }, - Jumptable(Jumptable<'src>), + Jumptable(Spanned>), CodeTable(CodeTable<'src>), SolFunction(SolFunction<'src>), SolEvent(SolEvent<'src>), @@ -43,7 +43,7 @@ impl<'src> IdentifiableNode<'src> for Definition<'src> { match self { Self::Macro(m) => &m.name, Self::Constant { name, .. } => name, - Self::Jumptable(jt) => &jt.name, + Self::Jumptable(jt) => &jt.0.name, Self::CodeTable(ct) => &ct.name, Self::SolEvent(e) => &e.name, Self::SolError(e) => &e.name, diff --git a/crates/ast/src/parser.rs b/crates/ast/src/parser.rs index 59c7bb4..d592c74 100644 --- a/crates/ast/src/parser.rs +++ b/crates/ast/src/parser.rs @@ -45,15 +45,21 @@ type ParserInput<'tokens, 'src> = SpannedInput, Span, &'tokens [Span trait Parser<'tokens, 'src: 'tokens, T>: ChumskyParser<'tokens, ParserInput<'tokens, 'src>, T, extra::Err, Span>>> { + fn spanned(self) -> impl Parser<'tokens, 'src, Spanned>; } -impl<'tokens, 'src: 'tokens, P, T> Parser<'tokens, 'src, T> for P where + +impl<'tokens, 'src: 'tokens, P, T> Parser<'tokens, 'src, T> for P +where P: ChumskyParser< 'tokens, ParserInput<'tokens, 'src>, T, extra::Err, Span>>, - > + >, { + fn spanned(self) -> impl Parser<'tokens, 'src, Spanned> { + self.map_with(|s, ex| (s, ex.span())) + } } fn root<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Root<'src>> { @@ -66,7 +72,7 @@ fn root<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Root<'src>> fn root_section<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::RootSection<'src>> { let definition = definition().map(ast::RootSection::Definition); let include = just(Include) - .ignore_then(select! {String(s) => s}.map_with(|s, ex| (s, ex.span()))) + .ignore_then(select! {String(s) => s}.spanned()) .map(ast::RootSection::Include); choice((definition, include)) @@ -76,8 +82,8 @@ fn definition<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Defin just(Define).ignore_then(choice(( r#macro(), constant(), - code_table(), - jump_table(), + code_table().map(ast::Definition::CodeTable), + jump_table().spanned().map(ast::Definition::Jumptable), sol_function(), sol_event(), sol_error(), @@ -92,7 +98,8 @@ fn r#macro<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definiti .then( macro_args .delimited_by(punct('('), punct(')')) - .map_with(|args, ex| (args.into_boxed_slice(), ex.span())), + .map(Vec::into_boxed_slice) + .spanned(), ) .then_ignore(punct('=')) .then( @@ -230,7 +237,8 @@ fn invoke<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Invoke<'s .separated_by(punct(',')) .collect::>() .delimited_by(punct('('), punct(')')) - .map_with(|args, ex| (args.into_boxed_slice(), ex.span())); + .map(Vec::into_boxed_slice) + .spanned(); let invoke_macro = ident() .then(invoke_macro_args) @@ -262,7 +270,8 @@ fn constant<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definit just(Ident("FREE_STORAGE_POINTER")) .ignore_then(just(Punct('('))) .ignore_then(just(Punct(')'))) - .map_with(|_, ex| (ast::ConstExpr::FreeStoragePointer, ex.span())), + .to(ast::ConstExpr::FreeStoragePointer) + .spanned(), )); just(Ident("constant")) @@ -272,7 +281,7 @@ fn constant<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definit .map(|(name, expr)| ast::Definition::Constant { name, expr }) } -fn code_table<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definition<'src>> { +fn code_table<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::CodeTable<'src>> { just(Table) .ignore_then(ident()) .then( @@ -289,10 +298,9 @@ fn code_table<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Defin .collect::>() .into_boxed_slice(), }) - .map(ast::Definition::CodeTable) } -fn jump_table<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definition<'src>> { +fn jump_table<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Jumptable<'src>> { choice((just(JumpTable).to(32), just(JumpTablePacked).to(2))) .then(ident()) .then( @@ -307,7 +315,6 @@ fn jump_table<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Defin label_size, labels, }) - .map(ast::Definition::Jumptable) } fn sol_function<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, ast::Definition<'src>> { @@ -402,11 +409,11 @@ fn sol_type<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, Spanned() -> impl Parser<'tokens, 'src, Spanned<&'src str>> { - select! {Ident(s) => s}.map_with(|s, ex| (s, ex.span())) + select! {Ident(s) => s}.spanned() } fn dec<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, Spanned> { - select! {Dec(s) => s.parse::().unwrap()}.map_with(|s, ex| (s, ex.span())) + select! {Dec(s) => s.parse::().unwrap()}.spanned() } fn word<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, Spanned> { @@ -416,7 +423,7 @@ fn word<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, Spanned> { Dec(s) => U256::from_str_radix(s, 10) } .try_map_with(|value, ex| value.map_err(|_e| Rich::custom(ex.span(), "word overflows"))) - .map_with(|value, ex| (value, ex.span())) + .spanned() } fn code<'tokens, 'src: 'tokens>() -> impl Parser<'tokens, 'src, Vec> { @@ -682,24 +689,66 @@ mod tests { } #[test] - fn parse_table() { + fn parse_code_table() { let span: Span = SimpleSpan::new(0, 0); assert_ok!( code_table(), vec![Table, Ident("TEST"), Punct('{'), Hex("0xc0de"), Punct('}')], - ast::Definition::CodeTable(ast::CodeTable { + ast::CodeTable { name: ("TEST", span), data: Box::new([0xc0, 0xde]) - }) + } ); assert_ok!( code_table(), vec![Table, Ident("TEST"), Punct('{'), Hex("0xc0de"), Hex("0xcc00ddee"), Punct('}')], - ast::Definition::CodeTable(ast::CodeTable { + ast::CodeTable { name: ("TEST", span), data: Box::new([0xc0, 0xde, 0xcc, 0x00, 0xdd, 0xee]) - }) + } + ); + } + + #[test] + fn parse_jump_tale() { + let span: Span = SimpleSpan::new(0, 0); + + assert_ok!( + jump_table(), + vec![ + JumpTable, + Ident("MY_TABLE"), + Punct('{'), + Ident("label1"), + Ident("nice"), + Punct('}') + ], + ast::Jumptable { + name: ("MY_TABLE", span), + label_size: 32, + labels: Box::new([("label1", span), ("nice", span),]) + } + ); + + assert_ok!( + jump_table(), + vec![JumpTable, Ident("ANOTHER_TABLE"), Punct('{'), Punct('}')], + ast::Jumptable { + name: ("ANOTHER_TABLE", span), + label_size: 32, + labels: Box::new([]) + } + ); + + assert_ok!( + jump_table(), + vec![JumpTablePacked, Ident("PRETTY_packed"), Punct('{'), Ident("awesome"), Punct('}')], + ast::Jumptable { + name: ("PRETTY_packed", span), + label_size: 2, + labels: Box::new([("awesome", span)]) + } ); } From 6f8b644c80c5c2377a27297977b26cd6f3147a0c Mon Sep 17 00:00:00 2001 From: philogy Date: Mon, 18 Nov 2024 18:58:37 +0700 Subject: [PATCH 5/8] =?UTF-8?q?=E2=9C=A8=20Jump=20table=20label=20analysis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/analysis/src/errors.rs | 97 ++++++++++++++------- crates/analysis/src/label_stack.rs | 7 +- crates/analysis/src/lib.rs | 90 +++++++++++++------ crates/ast/src/ast.rs | 50 ++++++----- crates/cli/src/main.rs | 2 +- examples/errors/JumpTableLabelNotFound.huff | 19 ++++ 6 files changed, 177 insertions(+), 88 deletions(-) create mode 100644 examples/errors/JumpTableLabelNotFound.huff diff --git a/crates/analysis/src/errors.rs b/crates/analysis/src/errors.rs index 7c9bdf3..525cf50 100644 --- a/crates/analysis/src/errors.rs +++ b/crates/analysis/src/errors.rs @@ -1,11 +1,11 @@ use ariadne::{Color, Config, Fmt, IndexType, Label, Report, ReportKind}; -use huff_ast::{Definition, IdentifiableNode, Instruction, Macro, Spanned}; +use huff_ast::{Definition, IdentifiableNode, Instruction, Macro, Span, Spanned, Text}; -type InvokeChain<'src, 'ast> = Box<[(&'ast Macro<'src>, &'ast Spanned<&'src str>)]>; +type InvokeChain<'src, 'ast> = Box<[(&'ast Text<'src>, &'ast Text<'src>)]>; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Inclusion<'src, 'ast: 'src> { - pub entry_point: Spanned<&'src str>, + pub entry_point: Text<'src>, pub invoke_stack: InvokeChain<'src, 'ast>, pub inclusion: Spanned<&'src str>, } @@ -23,43 +23,48 @@ pub enum AnalysisError<'ast, 'src> { RecursiveCodeInclusion { linking_inclusions: Box<[Inclusion<'src, 'ast>]>, }, - LabelNotFound { - scope: &'ast Macro<'src>, + MacroLabelNotFound { + scope: &'ast Text<'src>, invocation_chain: InvokeChain<'src, 'ast>, - not_found: &'ast Spanned<&'src str>, + not_found: &'ast Text<'src>, + }, + TableLabelNotFound { + scope: &'ast Text<'src>, + table_ref: &'ast Text<'src>, + table_def: &'ast Text<'src>, + not_found: &'ast Text<'src>, }, MacroArgNotFound { scope: &'ast Macro<'src>, - not_found: &'ast Spanned<&'src str>, + not_found: &'ast Text<'src>, }, EntryPointNotFound { name: &'src str, }, DefinitionNotFound { - scope: &'ast Macro<'src>, def_type: &'static str, - not_found: &'ast Spanned<&'src str>, + not_found: &'ast Text<'src>, }, EntryPointHasArgs { target: &'ast Macro<'src>, }, MacroArgumentCountMismatch { scope: &'ast Macro<'src>, - invoke: &'ast Spanned<&'src str>, + invoke: &'ast Text<'src>, args: &'ast Spanned]>>, target: &'ast Macro<'src>, }, DuplicateLabelDefinition { scope: &'ast Macro<'src>, - duplicates: Box<[&'ast Spanned<&'src str>]>, + duplicates: Box<[&'ast Text<'src>]>, }, DuplicateMacroArgDefinition { scope: &'ast Macro<'src>, - duplicates: Box<[&'ast Spanned<&'src str>]>, + duplicates: Box<[&'ast Text<'src>]>, }, NotYetSupported { intent: String, - span: Spanned<()>, + span: Span, }, } @@ -123,7 +128,7 @@ impl AnalysisError<'_, '_> { }) .fold(base_report, |report, (is_last, scope, invoking)| { let report = report.with_label( - Label::new((filename.clone(), scope.name.1.into_range())) + Label::new((filename.clone(), scope.1.into_range())) .with_color(Color::Red), ); @@ -190,22 +195,20 @@ impl AnalysisError<'_, '_> { .finish() } Self::DefinitionNotFound { - scope, def_type, not_found, } => Report::build(ReportKind::Error, filename.clone(), not_found.1.start) .with_config(Config::default().with_index_type(IndexType::Byte)) .with_message(format!( - "Definition of {} '{}' not found in macro {}", + "Definition of {} '{}' not found ", def_type.fg(Color::Cyan), not_found.0.fg(Color::Red), - scope.ident().fg(Color::Blue) )) .with_label( Label::new((filename.clone(), not_found.1.into_range())).with_color(Color::Red), ) .finish(), - Self::LabelNotFound { + Self::MacroLabelNotFound { scope, invocation_chain, not_found, @@ -221,12 +224,7 @@ impl AnalysisError<'_, '_> { |(parent_scope, invoke)| { [ Label::new((filename.clone(), parent_scope.span().into_range())) - .with_color(Color::Yellow) - .with_message(format!( - "No label '{}' found in parent {}", - not_found.ident().fg(Color::Red), - parent_scope.ident().fg(Color::Yellow) - )), + .with_color(Color::Yellow), Label::new((filename.clone(), invoke.1.into_range())).with_color( if invoke.ident() == scope.ident() { Color::Blue @@ -237,14 +235,13 @@ impl AnalysisError<'_, '_> { ] }, )) + .with_label( + Label::new((filename.clone(), not_found.span().into_range())) + .with_color(Color::Red), + ) .with_label( Label::new((filename.clone(), scope.span().into_range())) - .with_color(Color::Blue) - .with_message(format!( - "No label '{}' found in {}", - not_found.ident().fg(Color::Red), - scope.ident().fg(Color::Blue) - )), + .with_color(Color::Blue), ) .with_help(format!( "Ensure you've correctly entered the label (case-sensitive) or {}", @@ -252,6 +249,40 @@ impl AnalysisError<'_, '_> { )) .finish() } + Self::TableLabelNotFound { + scope, + table_ref, + table_def, + not_found, + } => Report::build(ReportKind::Error, filename.clone(), not_found.1.start) + .with_config(Config::default().with_index_type(IndexType::Byte)) + .with_message(format!( + "Jump table {} referenced label '{}' not found in direct parent macro {}", + table_ref.ident().fg(Color::Magenta), + not_found.ident().fg(Color::Red), + scope.ident().fg(Color::Yellow) + )) + .with_label( + Label::new((filename.clone(), scope.span().into_range())) + .with_color(Color::Yellow), + ) + .with_label( + Label::new((filename.clone(), table_ref.span().into_range())) + .with_color(Color::Magenta), + ) + .with_label( + Label::new((filename.clone(), table_def.span().into_range())) + .with_color(Color::Magenta), + ) + .with_label( + Label::new((filename.clone(), not_found.span().into_range())) + .with_color(Color::Red), + ) + .with_help(concat!( + "Unlike macro label references, jump tables can only reference labels in their", + " direct parent macro" + )) + .finish(), Self::MacroArgumentCountMismatch { scope: _, invoke, @@ -359,11 +390,11 @@ impl AnalysisError<'_, '_> { .finish() } Self::NotYetSupported { intent, span } => { - Report::build(ReportKind::Error, filename.clone(), span.1.start) + Report::build(ReportKind::Error, filename.clone(), span.start) .with_config(Config::default().with_index_type(IndexType::Byte)) .with_message(format!("{} is not yet supported", intent.fg(Color::Cyan),)) .with_label( - Label::new((filename.clone(), span.1.into_range())).with_color(Color::Red), + Label::new((filename.clone(), span.into_range())).with_color(Color::Red), ) .finish() } @@ -397,7 +428,7 @@ impl AnalysisError<'_, '_> { |report, (scope, invoking)| { report .with_label( - Label::new((filename.clone(), scope.name.1.into_range())) + Label::new((filename.clone(), scope.1.into_range())) .with_color(Color::Red), ) .with_label( diff --git a/crates/analysis/src/label_stack.rs b/crates/analysis/src/label_stack.rs index 23af65c..b435ef1 100644 --- a/crates/analysis/src/label_stack.rs +++ b/crates/analysis/src/label_stack.rs @@ -12,6 +12,11 @@ impl<'a, V> LabelStack<'a, V> { } } + pub fn get_locals(&self) -> &[(&'a str, V)] { + let local_start_index = *self.context_sizes.last().unwrap_or(&0); + &self.label_stack[local_start_index..] + } + pub fn enter_context(&mut self) { self.context_sizes.push(self.label_stack.len()); } @@ -42,7 +47,7 @@ impl<'a, V> LabelStack<'a, V> { .next() } - pub fn contains(&mut self, label: &'a str) -> bool { + pub fn contains(&self, label: &'a str) -> bool { self.get(label).is_some() } } diff --git a/crates/analysis/src/lib.rs b/crates/analysis/src/lib.rs index 0b58f2b..a40be0f 100644 --- a/crates/analysis/src/lib.rs +++ b/crates/analysis/src/lib.rs @@ -3,8 +3,8 @@ pub mod label_stack; use crate::errors::{AnalysisError, Inclusion}; use crate::label_stack::LabelStack; -use huff_ast::{Definition, IdentifiableNode, Instruction, Invoke, Macro, MacroStatement, Spanned}; -use std::collections::BTreeMap; +use huff_ast::{Definition, IdentifiableNode, Instruction, Invoke, Macro, MacroStatement, Text}; +use std::collections::{BTreeMap, HashSet}; pub fn analyze_global_for_dups<'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)>( global_defs: &BTreeMap<&'src str, Vec<&'ast Definition<'src>>>, @@ -108,9 +108,10 @@ struct MacroAnalysis<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> global_defs: &'a BTreeMap<&'src str, Vec<&'ast Definition<'src>>>, m: &'ast Macro<'src>, label_stack: &'a mut LabelStack<'src, ()>, - invoke_stack: &'a mut Vec<(&'ast Macro<'src>, &'ast Spanned<&'src str>)>, + invoke_stack: &'a mut Vec<(&'ast Text<'src>, &'ast Text<'src>)>, emit_error: &'a mut E, macros_to_include: &'a mut Vec>, + validated_tables: HashSet<&'src str>, } impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a, 'src, 'ast, E> { @@ -122,7 +123,7 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a global_defs: &'a BTreeMap<&'src str, Vec<&'ast Definition<'src>>>, m: &'ast Macro<'src>, label_stack: &'a mut LabelStack<'src, ()>, - invoke_stack: &'a mut Vec<(&'ast Macro<'src>, &'ast Spanned<&'src str>)>, + invoke_stack: &'a mut Vec<(&'ast Text<'src>, &'ast Text<'src>)>, emit_error: &'a mut E, macros_to_include: &mut Vec>, ) { @@ -133,6 +134,7 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a invoke_stack, emit_error, macros_to_include, + validated_tables: HashSet::with_capacity(16), } .analyze(); } @@ -151,7 +153,7 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a if self .invoke_stack .iter() - .any(|(invoked, _)| invoked.name.0 == name) + .any(|(invoked, _)| invoked.ident() == name) { self.emit(AnalysisError::RecursiveMacroInvocation { invocation_chain: self.invoke_stack.clone().into_boxed_slice(), @@ -205,12 +207,11 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a // Emit error if we don't find at least 1 macro by the given name. if !global_exists!(self.global_defs, name.ident(), Definition::Macro(_)) { self.emit(AnalysisError::DefinitionNotFound { - scope: self.m, def_type: "macro", not_found: name, }); } - self.invoke_stack.push((self.m, name)); + self.invoke_stack.push((&self.m.name, name)); // Filter and process all macros with given name to make sure errors are complete. self.global_defs @@ -249,24 +250,59 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a if !global_exists!( self.global_defs, table_ref.ident(), - Definition::CodeTable { .. } | Definition::Jumptable(_) + Definition::CodeTable(_) | Definition::Jumptable(_) ) { self.emit(AnalysisError::DefinitionNotFound { - scope: self.m, def_type: "table", not_found: table_ref, - }) + }); + return; } - self.emit(AnalysisError::NotYetSupported { - intent: "__tablesize and __tableoffset".to_string(), - span: ((), table_ref.1), - }); + let newly_inserted = self.validated_tables.insert(table_ref.ident()); + if newly_inserted { + if let Some(defs) = self.global_defs.get(table_ref.ident()) { + self.invoke_stack.push((&self.m.name, table_ref)); + defs.iter().for_each(|def| { + if let Definition::Jumptable(jump_table) = def { + jump_table.0.labels.iter().for_each(|label| { + let locals = self.label_stack.get_locals(); + let is_local = + locals.iter().any(|local| local.0 == label.ident()); + if !is_local { + self.emit(AnalysisError::TableLabelNotFound { + scope: &self.m.name, + table_ref, + table_def: &jump_table.0.name, + not_found: label, + }); + } + }); + } + }); + self.invoke_stack.pop().unwrap(); + }; + } + + let has_code_table = self + .global_defs + .get(table_ref.ident()) + .map(|defs| { + defs.iter() + .any(|def| matches!(def, Definition::CodeTable(_))) + }) + .unwrap_or(false); + + if has_code_table { + self.emit(AnalysisError::NotYetSupported { + intent: "__tablesize and __tableoffset for code table".to_string(), + span: table_ref.1, + }); + } } Invoke::BuiltinCodeSize(code_ref) | Invoke::BuiltinCodeOffset(code_ref) => { if !global_exists!(self.global_defs, code_ref.ident(), Definition::Macro(_)) { self.emit(AnalysisError::DefinitionNotFound { - scope: self.m, def_type: "macro", not_found: code_ref, }); @@ -283,7 +319,7 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a { self.emit(AnalysisError::NotYetSupported { intent: "code introspection for macros with arguments".to_owned(), - span: ((), code_ref.1), + span: code_ref.1, }); return; } @@ -317,7 +353,6 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a Definition::SolFunction(_) | Definition::SolError(_) ) { self.emit(AnalysisError::DefinitionNotFound { - scope: self.m, def_type: "solidity function / error", not_found: func_or_error_ref, }) @@ -325,14 +360,13 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a self.emit(AnalysisError::NotYetSupported { intent: "__FUNC_SIG and __ERROR".to_string(), - span: ((), func_or_error_ref.1), + span: func_or_error_ref.1, }); } Invoke::BuiltinEventHash(event_ref) => { if !global_exists!(self.global_defs, event_ref.ident(), Definition::SolEvent(_)) { self.emit(AnalysisError::DefinitionNotFound { - scope: self.m, def_type: "solidity event", not_found: event_ref, }) @@ -340,7 +374,7 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a self.emit(AnalysisError::NotYetSupported { intent: "__EVENT_HASH".to_string(), - span: ((), event_ref.1), + span: event_ref.1, }); } }, @@ -351,14 +385,14 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a fn analyze_instruction( &mut self, - macro_args: &BTreeMap<&str, Vec<&Spanned<&str>>>, + macro_args: &BTreeMap<&str, Vec<&Text<'_>>>, instruction: &'ast Instruction<'src>, ) { match instruction { Instruction::LabelReference(label) => { if !self.label_stack.contains(label.ident()) { - self.emit(AnalysisError::LabelNotFound { - scope: self.m, + self.emit(AnalysisError::MacroLabelNotFound { + scope: &self.m.name, invocation_chain: self.invoke_stack.clone().into_boxed_slice(), not_found: label, }) @@ -379,7 +413,6 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a Definition::Constant { .. } ) { self.emit(AnalysisError::DefinitionNotFound { - scope: self.m, def_type: "constant", not_found: const_ref, }); @@ -522,7 +555,7 @@ mod test { [&m], "TheRizzler", [AnalysisError::RecursiveMacroInvocation { - invocation_chain: Box::new([(&inner_macro, &("TheRizzler", span))]), + invocation_chain: Box::new([(&inner_macro.name, &("TheRizzler", span))]), }], ); } @@ -572,9 +605,9 @@ mod test { "VeryTop", [AnalysisError::RecursiveMacroInvocation { invocation_chain: Box::new([ - (&inner_m1, &("Top", span)), - (&inner_m2, &("Lower", span)), - (&inner_m3, &("VeryTop", span)), + (&inner_m1.name, &("Top", span)), + (&inner_m2.name, &("Lower", span)), + (&inner_m3.name, &("VeryTop", span)), ]), }], ); @@ -601,7 +634,6 @@ mod test { [&m], "MAIN", [AnalysisError::DefinitionNotFound { - scope: &inner_macro, def_type: "macro", not_found: &("MY_FUNC", invoke_span), }], diff --git a/crates/ast/src/ast.rs b/crates/ast/src/ast.rs index 93b3537..e509630 100644 --- a/crates/ast/src/ast.rs +++ b/crates/ast/src/ast.rs @@ -3,6 +3,8 @@ use alloy_primitives::U256; use chumsky::span::SimpleSpan; use evm_glue::opcodes::Opcode; +pub type Text<'src> = Spanned<&'src str>; + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Root<'src>(pub Box<[RootSection<'src>]>); @@ -16,7 +18,7 @@ pub enum RootSection<'src> { pub enum Definition<'src> { Macro(Macro<'src>), Constant { - name: Spanned<&'src str>, + name: Text<'src>, expr: Spanned, }, Jumptable(Spanned>), @@ -27,7 +29,7 @@ pub enum Definition<'src> { } pub trait IdentifiableNode<'a> { - fn spanned(&self) -> &Spanned<&'a str>; + fn spanned(&self) -> &Text<'a>; fn ident(&self) -> &'a str { self.spanned().0 @@ -39,7 +41,7 @@ pub trait IdentifiableNode<'a> { } impl<'src> IdentifiableNode<'src> for Definition<'src> { - fn spanned(&self) -> &Spanned<&'src str> { + fn spanned(&self) -> &Text<'src> { match self { Self::Macro(m) => &m.name, Self::Constant { name, .. } => name, @@ -54,14 +56,14 @@ impl<'src> IdentifiableNode<'src> for Definition<'src> { #[derive(Debug, Clone, PartialEq, Eq)] pub struct Macro<'src> { - pub name: Spanned<&'src str>, - pub args: Spanned]>>, + pub name: Text<'src>, + pub args: Spanned]>>, pub takes_returns: Option<(Spanned, Spanned)>, pub body: Box<[MacroStatement<'src>]>, } impl<'src> IdentifiableNode<'src> for Macro<'src> { - fn spanned(&self) -> &Spanned<&'src str> { + fn spanned(&self) -> &Text<'src> { &self.name } } @@ -83,9 +85,9 @@ pub enum MacroStatement<'src> { pub enum Instruction<'src> { Op(Spanned), VariablePush(Spanned), - LabelReference(Spanned<&'src str>), - MacroArgReference(Spanned<&'src str>), - ConstantReference(Spanned<&'src str>), + LabelReference(Text<'src>), + MacroArgReference(Text<'src>), + ConstantReference(Text<'src>), } impl Instruction<'_> { @@ -103,47 +105,47 @@ impl Instruction<'_> { #[derive(Debug, Clone, PartialEq, Eq)] pub enum Invoke<'src> { Macro { - name: Spanned<&'src str>, + name: Text<'src>, args: Spanned]>>, }, - BuiltinTableStart(Spanned<&'src str>), - BuiltinTableSize(Spanned<&'src str>), - BuiltinCodeSize(Spanned<&'src str>), - BuiltinCodeOffset(Spanned<&'src str>), - BuiltinFuncSig(Spanned<&'src str>), - BuiltinEventHash(Spanned<&'src str>), - BuiltinError(Spanned<&'src str>), + BuiltinTableStart(Text<'src>), + BuiltinTableSize(Text<'src>), + BuiltinCodeSize(Text<'src>), + BuiltinCodeOffset(Text<'src>), + BuiltinFuncSig(Text<'src>), + BuiltinEventHash(Text<'src>), + BuiltinError(Text<'src>), } #[derive(Debug, Clone, PartialEq, Eq)] pub struct CodeTable<'src> { - pub name: Spanned<&'src str>, + pub name: Text<'src>, pub data: Box<[u8]>, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Jumptable<'src> { - pub name: Spanned<&'src str>, + pub name: Text<'src>, pub label_size: u8, - pub labels: Box<[Spanned<&'src str>]>, + pub labels: Box<[Text<'src>]>, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct SolFunction<'src> { - pub name: Spanned<&'src str>, + pub name: Text<'src>, pub args: Box<[Spanned]>, pub rets: Box<[Spanned]>, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct SolEvent<'src> { - pub name: Spanned<&'src str>, + pub name: Text<'src>, pub args: Box<[Spanned]>, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct SolError<'src> { - pub name: Spanned<&'src str>, + pub name: Text<'src>, pub args: Box<[Spanned]>, } @@ -154,7 +156,7 @@ pub type Span = SimpleSpan; pub type Spanned = (T, Span); impl<'src> IdentifiableNode<'src> for Spanned<&'src str> { - fn spanned(&self) -> &Spanned<&'src str> { + fn spanned(&self) -> &Text<'src> { self } } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index bbd0ae6..d4dc0a7 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -98,7 +98,7 @@ fn main() -> Result<(), Box> { RootSection::Include(huff_include) => { analysis_errors.push(errors::AnalysisError::NotYetSupported { intent: "Huff '#include'".to_owned(), - span: ((), huff_include.1), + span: huff_include.1, }); None } diff --git a/examples/errors/JumpTableLabelNotFound.huff b/examples/errors/JumpTableLabelNotFound.huff new file mode 100644 index 0000000..1318fcf --- /dev/null +++ b/examples/errors/JumpTableLabelNotFound.huff @@ -0,0 +1,19 @@ +#define macro MAIN() = takes(0) returns(0) { + ANOTHER() + asterix: +} + +#define macro ANOTHER() = { + 0x0 calldataload 0x0 byte // [byte] + __tablesize(NICE) + __tablestart(NICE) + + bob: +} + + +#define jumptable NICE { + bob + asterix +} + From 0a1eb1bd95d25768e1dea6f68dfa11a63ccef12e Mon Sep 17 00:00:00 2001 From: philogy Date: Mon, 18 Nov 2024 19:59:33 +0700 Subject: [PATCH 6/8] =?UTF-8?q?=E2=9C=A8=20Jump=20tables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/compilation/src/lib.rs | 80 +++++++++++++++++++++++++++++++- examples/features/JumpTable.huff | 32 +++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 examples/features/JumpTable.huff diff --git a/crates/compilation/src/lib.rs b/crates/compilation/src/lib.rs index dbe2638..c346e60 100644 --- a/crates/compilation/src/lib.rs +++ b/crates/compilation/src/lib.rs @@ -6,7 +6,7 @@ use evm_glue::{ }; use huff_analysis::label_stack::LabelStack; use huff_ast::*; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; #[derive(Debug, Clone)] pub struct IncludedMacro<'src> { @@ -33,6 +33,12 @@ impl IncludedMacro<'_> { } } +struct IncludedObject { + start_id: usize, + end_id: usize, + body: Box<[Asm]>, +} + pub fn generate_for_entrypoint<'src>( globals: &mut CompileGlobals<'src, '_>, entry_point: &Macro<'src>, @@ -49,6 +55,8 @@ pub fn generate_for_entrypoint<'src>( end_id, }); + let mut objects: Vec = Vec::with_capacity(10); + let mut asm = Vec::with_capacity(10_000); asm.push(Asm::Mark(start_id)); generate_for_macro( @@ -58,6 +66,7 @@ pub fn generate_for_entrypoint<'src>( &mut mark_tracker, &mut label_stack, &mut included_macros, + &mut objects, &mut asm, ); @@ -73,6 +82,12 @@ pub fn generate_for_entrypoint<'src>( asm.push(Asm::Mark(included.end_id)); }); + objects.into_iter().for_each(|obj| { + asm.push(Asm::Mark(obj.start_id)); + asm.extend(obj.body); + asm.push(Asm::Mark(obj.end_id)); + }); + asm.push(Asm::Mark(end_id)); globals.assemble(asm.as_slice()) @@ -98,6 +113,12 @@ pub fn generate_default_constructor(runtime: Vec) -> Box<[Asm]> { ]) } +struct IncludedJumpTable { + start_id: usize, + end_id: usize, +} + +#[allow(clippy::too_many_arguments)] fn generate_for_macro<'src: 'cmp, 'cmp>( globals: &mut CompileGlobals<'src, '_>, current: &Macro<'src>, @@ -105,6 +126,7 @@ fn generate_for_macro<'src: 'cmp, 'cmp>( mark_tracker: &mut MarkTracker, label_stack: &'cmp mut LabelStack<'src, usize>, included_macros: &'cmp mut Vec>, + objects: &'cmp mut Vec, asm: &mut Vec, ) { let current_args: BTreeMap<&str, Asm> = BTreeMap::from_iter( @@ -116,6 +138,8 @@ fn generate_for_macro<'src: 'cmp, 'cmp>( .zip(arg_values), ); + let mut jump_tables: HashMap<&str, IncludedJumpTable> = HashMap::with_capacity(2); + label_stack.enter_context(); current.body.iter().for_each(|stmt| { @@ -149,6 +173,7 @@ fn generate_for_macro<'src: 'cmp, 'cmp>( mark_tracker, label_stack, included_macros, + objects, asm, ) } @@ -190,6 +215,59 @@ fn generate_for_macro<'src: 'cmp, 'cmp>( }; asm.push(Asm::Ref(mref)); } + Invoke::BuiltinTableSize(table_ref) => { + let jump_table = match globals.defs.get(table_ref.ident()).unwrap() { + Definition::Jumptable(jump_table) => jump_table, + other_def => panic!("Unexpected table def {:?}", other_def), + }; + let included_table = jump_tables.entry(table_ref.ident()).or_insert_with(|| { + let start_id = mark_tracker.next_mark(); + let end_id = mark_tracker.next_mark(); + objects.push(IncludedObject { + start_id, + end_id, + body: Box::from_iter(jump_table.0.labels.iter().map(|label| { + let label_id = label_stack.get(label.ident()).unwrap(); + let mref = MarkRef { + ref_type: RefType::Direct(*label_id), + is_pushed: false, + set_size: Some(jump_table.0.label_size), + }; + Asm::Ref(mref) + })), + }); + IncludedJumpTable { start_id, end_id } + }); + asm.push(Asm::delta_ref( + included_table.start_id, + included_table.end_id, + )); + } + Invoke::BuiltinTableStart(table_ref) => { + let jump_table = match globals.defs.get(table_ref.ident()).unwrap() { + Definition::Jumptable(jump_table) => jump_table, + other_def => panic!("Unexpected table def {:?}", other_def), + }; + let included_table = jump_tables.entry(table_ref.ident()).or_insert_with(|| { + let start_id = mark_tracker.next_mark(); + let end_id = mark_tracker.next_mark(); + objects.push(IncludedObject { + start_id, + end_id, + body: Box::from_iter(jump_table.0.labels.iter().map(|label| { + let label_id = label_stack.get(label.ident()).unwrap(); + let mref = MarkRef { + ref_type: RefType::Direct(*label_id), + is_pushed: false, + set_size: Some(jump_table.0.label_size), + }; + Asm::Ref(mref) + })), + }); + IncludedJumpTable { start_id, end_id } + }); + asm.push(Asm::mref(included_table.start_id)); + } _ => panic!( "Compilation not yet implemented for this invocation type `{:?}`", invoke diff --git a/examples/features/JumpTable.huff b/examples/features/JumpTable.huff new file mode 100644 index 0000000..6534cb8 --- /dev/null +++ b/examples/features/JumpTable.huff @@ -0,0 +1,32 @@ +#define macro MAIN() = takes(0) returns(0) { + 0x21 calldataload // [y] + 0x01 calldataload // [y, x] + 2 // [y, x, 2] + 0x0 calldataload 0x0 byte // [y, x, 2, b] + 0x1 shl // [y, x, 2, b * 2] + __tablestart(OPS) add // [y, x, 2, label_offset] + 0x1e codecopy // [y, x] + 0x0 mload // [y, x, label] + jump + + add_op: + add + _RET() + sub_op: + sub + _RET() + mul_op: + mul + _RET() +} + +#define macro _RET() = { + 0x0 mstore + msize 0x0 return +} + +#define jumptable__packed OPS { + add_op + sub_op + mul_op +} From 3bee4c495d5c92760df5903708c93aac23028cff Mon Sep 17 00:00:00 2001 From: philogy Date: Mon, 18 Nov 2024 20:00:43 +0700 Subject: [PATCH 7/8] =?UTF-8?q?=F0=9F=93=9D=20Update=20readme's=20todos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 781004d..9454a8b 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,6 @@ in Rust. It comes with: ## Missing Features / TODOs -- [ ] Jump tables - - [ ] parsing - - [ ] builtins (`__tablestart`, `__tablesize`) - [ ] Code tables - [ ] builtins (`__tablestart`, `__tablesize`) - [ ] ABI builtins (`__FUNC_SIG`, `__EVEN_HASH`, `__ERROR`) From 470ff2466a67aad8c274563a2a26046e09e5e93a Mon Sep 17 00:00:00 2001 From: philogy Date: Mon, 18 Nov 2024 20:05:07 +0700 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=A5=A2=20Clippy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/analysis/src/lib.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/crates/analysis/src/lib.rs b/crates/analysis/src/lib.rs index a40be0f..a7d6ced 100644 --- a/crates/analysis/src/lib.rs +++ b/crates/analysis/src/lib.rs @@ -308,15 +308,10 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a }); return; } - if self - .global_defs - .get(code_ref.ident()) - .map_or(false, |defs| { - defs.iter().any( - |def| matches!(def, Definition::Macro(m) if m.args.0.len() > 0), - ) - }) - { + if self.global_defs.get(code_ref.ident()).is_some_and(|defs| { + defs.iter() + .any(|def| matches!(def, Definition::Macro(m) if m.args.0.len() > 0)) + }) { self.emit(AnalysisError::NotYetSupported { intent: "code introspection for macros with arguments".to_owned(), span: code_ref.1,