Skip to content

Commit 2e3d1a7

Browse files
authored
Add error for duplicate macro labels (Original author is 0xrusowsky) (#7)
1 parent 324be49 commit 2e3d1a7

File tree

11 files changed

+106
-45
lines changed

11 files changed

+106
-45
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/codegen/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ let contract = Contract {
9494
functions: vec![],
9595
events: vec![],
9696
tables: vec![],
97+
labels: HashSet::new(),
9798
};
9899

99100
// Generate the main bytecode
@@ -149,6 +150,7 @@ let contract = Contract {
149150
functions: vec![],
150151
events: vec![],
151152
tables: vec![],
153+
labels: HashSet::new(),
152154
};
153155

154156
// Generate the constructor bytecode

crates/codegen/tests/abigen.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
use huff_neo_codegen::Codegen;
2+
use huff_neo_utils::prelude::*;
3+
use std::collections::HashSet;
14
use std::{
25
collections::BTreeMap,
36
sync::{Arc, Mutex},
47
};
58

6-
use huff_neo_codegen::Codegen;
7-
use huff_neo_utils::prelude::*;
8-
99
#[test]
1010
fn constructs_valid_abi() {
1111
let constructor = MacroDefinition {
@@ -28,6 +28,7 @@ fn constructs_valid_abi() {
2828
functions: vec![],
2929
events: vec![],
3030
tables: vec![],
31+
labels: HashSet::new(),
3132
};
3233

3334
// Generate the abi from the contract
@@ -68,6 +69,7 @@ fn missing_constructor_fails() {
6869
functions: vec![],
6970
events: vec![],
7071
tables: vec![],
72+
labels: HashSet::new(),
7173
};
7274

7375
// Generate the abi from the contract

crates/lexer/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@ keywords = ["bytecode", "compiler", "evm", "huff", "rust"]
1212

1313
[dependencies]
1414
huff-neo-utils.workspace = true
15+
16+
lazy_static.workspace = true
1517
regex.workspace = true
1618
tracing.workspace = true

crates/lexer/src/lib.rs

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,37 @@
11
use huff_neo_utils::prelude::*;
2+
use lazy_static::lazy_static;
23
use regex::Regex;
4+
use std::collections::HashMap;
35
use std::{
46
iter::{Peekable, Zip},
57
ops::RangeFrom,
68
str::Chars,
79
};
810

11+
lazy_static! {
12+
static ref TOKEN: HashMap<String, TokenKind> = HashMap::from_iter(vec![
13+
(TokenKind::Macro.to_string(), TokenKind::Macro),
14+
(TokenKind::Fn.to_string(), TokenKind::Fn),
15+
(TokenKind::Test.to_string(), TokenKind::Test),
16+
(TokenKind::Function.to_string(), TokenKind::Function),
17+
(TokenKind::Constant.to_string(), TokenKind::Constant),
18+
(TokenKind::Error.to_string(), TokenKind::Error),
19+
(TokenKind::Takes.to_string(), TokenKind::Takes),
20+
(TokenKind::Returns.to_string(), TokenKind::Returns),
21+
(TokenKind::Event.to_string(), TokenKind::Event),
22+
(TokenKind::NonPayable.to_string(), TokenKind::NonPayable),
23+
(TokenKind::Payable.to_string(), TokenKind::Payable),
24+
(TokenKind::Indexed.to_string(), TokenKind::Indexed),
25+
(TokenKind::View.to_string(), TokenKind::View),
26+
(TokenKind::Pure.to_string(), TokenKind::Pure),
27+
// First check for packed jump table
28+
(TokenKind::JumpTablePacked.to_string(), TokenKind::JumpTablePacked),
29+
// Match with jump table if not
30+
(TokenKind::JumpTable.to_string(), TokenKind::JumpTable),
31+
(TokenKind::CodeTable.to_string(), TokenKind::CodeTable),
32+
]);
33+
}
34+
935
/// Defines a context in which the lexing happens.
1036
/// Allows to differentiate between EVM types and opcodes that can either
1137
/// be identical or the latter being a substring of the former (example : bytes32 and byte)
@@ -168,37 +194,9 @@ impl<'a> Lexer<'a> {
168194
let (word, start, mut end) = self.eat_while(Some(ch), |c| c.is_alphanumeric() || c == '_');
169195

170196
let mut found_kind: Option<TokenKind> = None;
171-
let keys = [
172-
TokenKind::Macro,
173-
TokenKind::Fn,
174-
TokenKind::Test,
175-
TokenKind::Function,
176-
TokenKind::Constant,
177-
TokenKind::Error,
178-
TokenKind::Takes,
179-
TokenKind::Returns,
180-
TokenKind::Event,
181-
TokenKind::NonPayable,
182-
TokenKind::Payable,
183-
TokenKind::Indexed,
184-
TokenKind::View,
185-
TokenKind::Pure,
186-
// First check for packed jump table
187-
TokenKind::JumpTablePacked,
188-
// Match with jump table if not
189-
TokenKind::JumpTable,
190-
TokenKind::CodeTable,
191-
];
192-
for kind in keys.into_iter() {
193-
if self.context == Context::MacroBody {
194-
break;
195-
}
196-
let key = kind.to_string();
197-
let peeked = word.clone();
198-
199-
if key == peeked {
200-
found_kind = Some(kind);
201-
break;
197+
if self.context != Context::MacroBody {
198+
if let Some(kind) = TOKEN.get(&word) {
199+
found_kind = Some(kind.clone());
202200
}
203201
}
204202

@@ -209,6 +207,7 @@ impl<'a> Lexer<'a> {
209207
found_kind = None;
210208
}
211209

210+
// Set the context based on the found token kind
212211
if let Some(kind) = &found_kind {
213212
match kind {
214213
TokenKind::Macro | TokenKind::Fn | TokenKind::Test => self.context = Context::MacroDefinition,
@@ -437,10 +436,9 @@ impl<'a> Lexer<'a> {
437436
let (integer_str, start, end) = self.eat_while(Some(initial_char), |ch| ch.is_ascii_digit());
438437

439438
let integer = integer_str.parse().unwrap();
440-
441439
let integer_token = TokenKind::Num(integer);
442-
let span = self.source.relative_span_by_pos(start, end);
443-
Ok(Token { kind: integer_token, span })
440+
441+
Ok(Token { kind: integer_token, span: self.source.relative_span_by_pos(start, end) })
444442
}
445443

446444
fn eat_hex_digit(&mut self, initial_char: char) -> TokenResult {
@@ -461,8 +459,8 @@ impl<'a> Lexer<'a> {
461459
};
462460

463461
start += 2;
464-
let span = self.source.relative_span_by_pos(start, end);
465-
Ok(Token { kind, span })
462+
463+
Ok(Token { kind, span: self.source.relative_span_by_pos(start, end) })
466464
}
467465

468466
/// Skips white space. They are not significant in the source language

crates/parser/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ let expected_contract = Contract {
5858
functions: vec![],
5959
events: vec![],
6060
tables: vec![],
61+
labels: HashSet::new(),
6162
};
6263
assert_eq!(unwrapped_contract.macros, expected_contract.macros);
6364
```

crates/parser/src/lib.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ impl Parser {
6868
}
6969
// Check for a decorator above a test macro
7070
else if self.check(TokenKind::Pound) {
71-
let m = self.parse_macro()?;
71+
let m = self.parse_macro(&mut contract)?;
7272
tracing::info!(target: "parser", "SUCCESSFULLY PARSED MACRO {}", m.name);
7373
contract.macros.push(m);
7474
}
@@ -100,7 +100,7 @@ impl Parser {
100100
contract.errors.push(e);
101101
}
102102
TokenKind::Macro | TokenKind::Fn | TokenKind::Test => {
103-
let m = self.parse_macro()?;
103+
let m = self.parse_macro(&mut contract)?;
104104
tracing::info!(target: "parser", "SUCCESSFULLY PARSED MACRO {}", m.name);
105105
self.check_duplicate_macro(&contract, &m)?;
106106
contract.macros.push(m);
@@ -187,6 +187,24 @@ impl Parser {
187187
std::mem::discriminant(&self.current_token.kind) == std::mem::discriminant(&kind)
188188
}
189189

190+
/// Checks whether the input label is unique in a macro.
191+
fn check_duplicate_label(&self, contract: &mut Contract, macro_name: &str, label: String, span: AstSpan) -> Result<(), ParserError> {
192+
let key = format!("{macro_name}{label}");
193+
if contract.labels.contains(&key) {
194+
println!("DUPLICATED LABEL NAME: {:?}", span);
195+
tracing::error!(target: "parser", "DUPLICATED LABEL NAME: {}", label);
196+
Err(ParserError {
197+
kind: ParserErrorKind::DuplicateLabel(label.clone()),
198+
hint: Some(format!("Duplicated label name: \"{label}\" in macro: \"{macro_name}\"")),
199+
spans: span,
200+
cursor: self.cursor,
201+
})
202+
} else {
203+
contract.labels.insert(key);
204+
Ok(())
205+
}
206+
}
207+
190208
/// Checks if there is a duplicate macro name
191209
pub fn check_duplicate_macro(&self, contract: &Contract, m: &MacroDefinition) -> Result<(), ParserError> {
192210
if contract.macros.binary_search_by(|_macro| _macro.name.cmp(&m.name)).is_ok() {
@@ -489,7 +507,7 @@ impl Parser {
489507
/// Parses a macro.
490508
///
491509
/// It should parse the following : macro MACRO_NAME(args...) = takes (x) returns (n) {...}
492-
pub fn parse_macro(&mut self) -> Result<MacroDefinition, ParserError> {
510+
pub fn parse_macro(&mut self, contract: &mut Contract) -> Result<MacroDefinition, ParserError> {
493511
let mut decorator: Option<Decorator> = None;
494512
if self.check(TokenKind::Pound) {
495513
decorator = Some(self.parse_decorator()?);
@@ -518,7 +536,7 @@ impl Parser {
518536
let macro_takes = self.match_kind(TokenKind::Takes).map_or(Ok(0), |_| self.parse_single_arg())?;
519537
let macro_returns = self.match_kind(TokenKind::Returns).map_or(Ok(0), |_| self.parse_single_arg())?;
520538

521-
let macro_statements: Vec<Statement> = self.parse_body()?;
539+
let macro_statements: Vec<Statement> = self.parse_body(&macro_name, contract)?;
522540

523541
Ok(MacroDefinition::new(
524542
macro_name,
@@ -536,7 +554,7 @@ impl Parser {
536554
/// Parse the body of a macro.
537555
///
538556
/// Only HEX, OPCODES, labels, builtins, and MACRO calls should be authorized.
539-
pub fn parse_body(&mut self) -> Result<Vec<Statement>, ParserError> {
557+
pub fn parse_body(&mut self, macro_name: &str, contract: &mut Contract) -> Result<Vec<Statement>, ParserError> {
540558
let mut statements: Vec<Statement> = Vec::new();
541559
self.match_kind(TokenKind::OpenBrace)?;
542560
tracing::info!(target: "parser", "PARSING MACRO BODY");
@@ -618,6 +636,7 @@ impl Parser {
618636
TokenKind::Label(l) => {
619637
let mut curr_spans = vec![self.current_token.span.clone()];
620638
self.consume();
639+
self.check_duplicate_label(contract, macro_name, l.to_string(), AstSpan(curr_spans.clone()))?;
621640
let inner_statements: Vec<Statement> = self.parse_label()?;
622641
inner_statements.iter().for_each(|a| curr_spans.extend_from_slice(a.span.inner_ref()));
623642
tracing::info!(target: "parser", "PARSED LABEL \"{}\" INSIDE MACRO WITH {} STATEMENTS.", l, inner_statements.len());

crates/parser/tests/labels.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use huff_neo_lexer::*;
22
use huff_neo_parser::*;
3-
use huff_neo_utils::{evm::Opcode, prelude::*};
3+
use huff_neo_utils::{error::ParserErrorKind, evm::Opcode, prelude::*};
44

55
#[test]
66
fn multiline_labels() {
@@ -382,3 +382,25 @@ pub fn builtins_under_labels() {
382382
assert_eq!(s.span, md_expected.statements[i].span);
383383
}
384384
}
385+
386+
#[test]
387+
fn duplicated_labels() {
388+
let source = r#"
389+
#define macro MAIN() = takes(0) returns(0) {
390+
cool_label jump
391+
cool_label jump
392+
cool_label: 0x00
393+
dup_label: 0x00
394+
dup_label: 0x00
395+
}
396+
"#;
397+
let flattened_source = FullFileSource { source, file: None, spans: vec![] };
398+
let lexer = Lexer::new(flattened_source);
399+
let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
400+
let mut parser = Parser::new(tokens, None);
401+
402+
// Grab the first macro
403+
let parse_result = parser.parse();
404+
assert!(parse_result.is_err());
405+
assert_eq!(parse_result.unwrap_err().kind, ParserErrorKind::DuplicateLabel("dup_label".to_string()));
406+
}

crates/utils/src/ast.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
evm_version::EVMVersion,
1111
prelude::{MacroArg::Ident, Span, TokenKind},
1212
};
13+
use std::collections::HashSet;
1314
use std::{
1415
collections::BTreeMap,
1516
fmt::{Display, Formatter},
@@ -112,6 +113,8 @@ pub struct Contract {
112113
pub events: Vec<EventDefinition>,
113114
/// Tables
114115
pub tables: Vec<TableDefinition>,
116+
/// Labels
117+
pub labels: HashSet<String>,
115118
}
116119

117120
impl Contract {

crates/utils/src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ pub enum ParserErrorKind {
6565
InvalidDecoratorFlag(String),
6666
/// Invalid decorator flag argument
6767
InvalidDecoratorFlagArg(TokenKind),
68+
/// Duplicate label
69+
DuplicateLabel(String),
6870
/// Duplicate MACRO
6971
DuplicateMacro(String),
7072
}
@@ -413,6 +415,9 @@ impl fmt::Display for CompilerError {
413415
pe.spans.error(pe.hint.as_ref())
414416
)
415417
}
418+
ParserErrorKind::DuplicateLabel(label) => {
419+
write!(f, "\nError: Duplicate label: \"{}\" \n{}\n", label, pe.spans.error(pe.hint.as_ref()))
420+
}
416421
ParserErrorKind::DuplicateMacro(mn) => {
417422
write!(f, "\nError: Duplicate MACRO name found: \"{}\" \n{}\n", mn, pe.spans.error(pe.hint.as_ref()))
418423
}

0 commit comments

Comments
 (0)