fix bugs found by the fuzzer

This commit is contained in:
2026-07-01 10:49:38 +02:00
parent 43a20c99f6
commit 131db6a453
6 changed files with 106 additions and 63 deletions

2
.gitignore vendored
View File

@@ -2,3 +2,5 @@
/out* /out*
/TODO /TODO
/vscode /vscode
/fuzz
/perf.data*

View File

@@ -15,8 +15,8 @@ use tokenizer::ZernError;
fn compile_file(args: Args) -> Result<(), ZernError> { fn compile_file(args: Args) -> Result<(), ZernError> {
let source = match fs::read_to_string(&args.path) { let source = match fs::read_to_string(&args.path) {
Ok(x) => x, Ok(x) => x,
Err(_) => { Err(e) => {
eprintln!("\x1b[91mERROR\x1b[0m: failed to open {}", args.path); eprintln!("\x1b[91mERROR\x1b[0m: failed to open {}: {e}", args.path);
process::exit(1); process::exit(1);
} }
}; };

View File

@@ -154,6 +154,7 @@ pub struct Parser {
tokens: Vec<Token>, tokens: Vec<Token>,
current: usize, current: usize,
is_inside_function: bool, is_inside_function: bool,
depth: usize,
} }
impl Parser { impl Parser {
@@ -162,6 +163,7 @@ impl Parser {
tokens, tokens,
current: 0, current: 0,
is_inside_function: false, is_inside_function: false,
depth: 0,
} }
} }
@@ -446,7 +448,14 @@ impl Parser {
} }
fn expression(&mut self) -> Result<Expr, ZernError> { fn expression(&mut self) -> Result<Expr, ZernError> {
self.or_and() self.depth += 1;
if self.depth > 200 {
return error!(self.previous().loc, "maximum expression depth reached");
}
let out = self.or_and();
self.depth -= 1;
out
} }
fn or_and(&mut self) -> Result<Expr, ZernError> { fn or_and(&mut self) -> Result<Expr, ZernError> {

View File

@@ -5,31 +5,35 @@ use crate::{
tokenizer::{ZernError, error}, tokenizer::{ZernError, error},
}; };
pub type Type = String;
pub struct StructField { pub struct StructField {
pub offset: usize, pub offset: usize,
pub field_type: Type, pub field_type: String,
}
#[derive(Clone)]
pub enum FnParams {
Normal(Vec<String>),
Variadic,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct FnType { pub struct FnType {
pub return_type: Type, pub return_type: String,
pub params: Option<Vec<Type>>, pub params: FnParams,
} }
impl FnType { impl FnType {
fn new(return_type: &str, params: Vec<&str>) -> FnType { fn new(return_type: &str, params: Vec<&str>) -> FnType {
FnType { FnType {
return_type: return_type.to_string(), return_type: return_type.to_string(),
params: Some(params.iter().map(|x| x.to_string()).collect()), params: FnParams::Normal(params.iter().map(|x| x.to_string()).collect()),
} }
} }
fn new_variadic(return_type: &str) -> FnType { fn new_variadic(return_type: &str) -> FnType {
FnType { FnType {
return_type: return_type.to_string(), return_type: return_type.to_string(),
params: None, params: FnParams::Variadic,
} }
} }
} }
@@ -74,9 +78,15 @@ impl SymbolTable {
return error!(name.loc, format!("tried to redefine '{}'", name.lexeme)); return error!(name.loc, format!("tried to redefine '{}'", name.lexeme));
} }
let mut value = if value.lexeme.starts_with("0x") { let mut value = if value.lexeme.starts_with("0x") {
u64::from_str_radix(&value.lexeme[2..], 16).unwrap() match u64::from_str_radix(&value.lexeme[2..], 16) {
Ok(v) => v,
Err(_) => return error!(value.loc, "failed to parse hex numeric constant"),
}
} else { } else {
value.lexeme.parse().unwrap() match value.lexeme.parse() {
Ok(v) => v,
Err(_) => return error!(value.loc, "failed to parse numeric constant"),
}
} as i64; } as i64;
if *neg { if *neg {
value = -value; value = -value;
@@ -110,7 +120,7 @@ impl SymbolTable {
name.lexeme.clone(), name.lexeme.clone(),
FnType { FnType {
return_type, return_type,
params: Some( params: FnParams::Normal(
params.iter().map(|x| x.var_type.lexeme.clone()).collect(), params.iter().map(|x| x.var_type.lexeme.clone()).collect(),
), ),
}, },
@@ -119,7 +129,7 @@ impl SymbolTable {
name.lexeme.clone(), name.lexeme.clone(),
FnType { FnType {
return_type, return_type,
params: None, params: FnParams::Variadic,
}, },
), ),
}; };

View File

@@ -245,6 +245,9 @@ impl Tokenizer {
return error!(self.loc, "unterminated char literal"); return error!(self.loc, "unterminated char literal");
} }
_ = self.match_char('\\'); // if its an escape sequence skip \ and read one more _ = self.match_char('\\'); // if its an escape sequence skip \ and read one more
if self.eof() {
return error!(self.loc, "unterminated char literal");
}
self.advance(); self.advance();
if !self.match_char('\'') { if !self.match_char('\'') {
return error!(self.loc, "expected ' after char literal"); return error!(self.loc, "expected ' after char literal");
@@ -257,6 +260,12 @@ impl Tokenizer {
while !self.eof() { while !self.eof() {
if self.peek() == '\\' { if self.peek() == '\\' {
self.advance(); self.advance();
if self.eof() {
return error!(
self.loc,
format!("unterminated string, started at {}", start_loc)
);
}
} else if self.peek() == '"' { } else if self.peek() == '"' {
break; break;
} else if self.peek() == '\n' { } else if self.peek() == '\n' {
@@ -340,7 +349,10 @@ impl Tokenizer {
fn scan_number(&mut self) -> Result<(), ZernError> { fn scan_number(&mut self) -> Result<(), ZernError> {
let mut is_float = false; let mut is_float = false;
if self.match_char('x') { if self.source[self.current - 1] == '0' && self.match_char('x') {
if !self.peek().is_ascii_hexdigit() {
return error!(self.loc, "expected a digit after '0x'");
}
while self.peek().is_ascii_hexdigit() { while self.peek().is_ascii_hexdigit() {
self.advance(); self.advance();
} }

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use crate::{ use crate::{
parser::{Expr, ExprKind, Params, Stmt}, parser::{Expr, ExprKind, Params, Stmt},
symbol_table::{SymbolTable, Type}, symbol_table::{FnParams, SymbolTable},
tokenizer::{TokenType, ZernError, error}, tokenizer::{TokenType, ZernError, error},
}; };
@@ -36,7 +36,7 @@ macro_rules! expect_types {
static BUILTIN_TYPES: [&str; 8] = ["void", "u8", "i64", "f64", "str", "bool", "ptr", "any"]; static BUILTIN_TYPES: [&str; 8] = ["void", "u8", "i64", "f64", "str", "bool", "ptr", "any"];
pub struct Env { pub struct Env {
scopes: Vec<HashMap<String, Type>>, scopes: Vec<HashMap<String, String>>,
} }
impl Env { impl Env {
@@ -59,7 +59,7 @@ impl Env {
self.scopes.last_mut().unwrap().insert(name, var_type); self.scopes.last_mut().unwrap().insert(name, var_type);
} }
fn get_var_type(&self, name: &str) -> Option<&Type> { fn get_var_type(&self, name: &str) -> Option<&String> {
for scope in self.scopes.iter().rev() { for scope in self.scopes.iter().rev() {
if let Some(var) = scope.get(name) { if let Some(var) = scope.get(name) {
return Some(var); return Some(var);
@@ -365,7 +365,7 @@ impl<'a> TypeChecker<'a> {
Ok(()) Ok(())
} }
pub fn typecheck_expr(&mut self, env: &mut Env, expr: &Expr) -> Result<Type, ZernError> { pub fn typecheck_expr(&mut self, env: &mut Env, expr: &Expr) -> Result<String, ZernError> {
let expr_type = match &expr.kind { let expr_type = match &expr.kind {
ExprKind::Binary { left, op, right } => { ExprKind::Binary { left, op, right } => {
let left_type = self.typecheck_expr(env, left)?; let left_type = self.typecheck_expr(env, left)?;
@@ -464,24 +464,31 @@ impl<'a> TypeChecker<'a> {
if let ExprKind::Variable(callee_name) = &callee.kind { if let ExprKind::Variable(callee_name) = &callee.kind {
if let Some(fn_type) = self.symbol_table.functions.get(&callee_name.lexeme) { if let Some(fn_type) = self.symbol_table.functions.get(&callee_name.lexeme) {
// its a function (defined/builtin/extern) // its a function (defined/builtin/extern)
if let Some(params) = fn_type.params.clone() { match &fn_type.params {
if params.len() != args.len() { FnParams::Normal(params) => {
return error!( if params.len() != args.len() {
&paren.loc, return error!(
format!( &paren.loc,
"expected {} arguments, got {}", format!(
params.len(), "expected {} arguments, got {}",
args.len() params.len(),
) args.len()
); )
);
}
for (i, arg) in args.iter().enumerate() {
expect_type!(
self.typecheck_expr(env, arg)?,
params[i],
paren.loc
);
}
} }
for (i, arg) in args.iter().enumerate() { FnParams::Variadic => {
expect_type!(self.typecheck_expr(env, arg)?, params[i], paren.loc); // cant check arg types
} for arg in args {
} else { self.typecheck_expr(env, arg)?;
// its a variadic function, cant check arg types }
for arg in args {
self.typecheck_expr(env, arg)?;
} }
} }
Ok(fn_type.return_type.clone()) Ok(fn_type.return_type.clone())
@@ -587,35 +594,38 @@ impl<'a> TypeChecker<'a> {
} }
}; };
if let Some(params) = &func_type.params { match &func_type.params {
if params.len() != args.len() + 1 { FnParams::Normal(params) => {
return error!( if params.len() != args.len() + 1 {
method.loc, return error!(
format!( method.loc,
"expected {} arguments, got {}", format!(
params.len() - 1, "expected {} arguments, got {}",
args.len() params.len() - 1,
) args.len()
); )
);
}
if params[0] != receiver_type {
return error!(
method.loc,
format!(
"first parameter of the method must be of type {}",
receiver_type
)
);
}
for (i, arg) in args.iter().enumerate() {
expect_type!(self.typecheck_expr(env, arg)?, params[i + 1], method.loc);
}
Ok(func_type.return_type.clone())
} }
if params[0] != receiver_type { FnParams::Variadic => {
return error!( for arg in args {
method.loc, self.typecheck_expr(env, arg)?;
format!( }
"first parameter of the method must be of type {}", Ok(func_type.return_type.clone())
receiver_type
)
);
} }
for (i, arg) in args.iter().enumerate() {
expect_type!(self.typecheck_expr(env, arg)?, params[i + 1], method.loc);
}
Ok(func_type.return_type.clone())
} else {
for arg in args {
self.typecheck_expr(env, arg)?;
}
Ok(func_type.return_type.clone())
} }
} }
}?; }?;