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*
/TODO
/vscode
/fuzz
/perf.data*

View File

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

View File

@@ -154,6 +154,7 @@ pub struct Parser {
tokens: Vec<Token>,
current: usize,
is_inside_function: bool,
depth: usize,
}
impl Parser {
@@ -162,6 +163,7 @@ impl Parser {
tokens,
current: 0,
is_inside_function: false,
depth: 0,
}
}
@@ -446,7 +448,14 @@ impl Parser {
}
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> {

View File

@@ -5,31 +5,35 @@ use crate::{
tokenizer::{ZernError, error},
};
pub type Type = String;
pub struct StructField {
pub offset: usize,
pub field_type: Type,
pub field_type: String,
}
#[derive(Clone)]
pub enum FnParams {
Normal(Vec<String>),
Variadic,
}
#[derive(Clone)]
pub struct FnType {
pub return_type: Type,
pub params: Option<Vec<Type>>,
pub return_type: String,
pub params: FnParams,
}
impl FnType {
fn new(return_type: &str, params: Vec<&str>) -> FnType {
FnType {
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 {
FnType {
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));
}
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 {
value.lexeme.parse().unwrap()
match value.lexeme.parse() {
Ok(v) => v,
Err(_) => return error!(value.loc, "failed to parse numeric constant"),
}
} as i64;
if *neg {
value = -value;
@@ -110,7 +120,7 @@ impl SymbolTable {
name.lexeme.clone(),
FnType {
return_type,
params: Some(
params: FnParams::Normal(
params.iter().map(|x| x.var_type.lexeme.clone()).collect(),
),
},
@@ -119,7 +129,7 @@ impl SymbolTable {
name.lexeme.clone(),
FnType {
return_type,
params: None,
params: FnParams::Variadic,
},
),
};

View File

@@ -245,6 +245,9 @@ impl Tokenizer {
return error!(self.loc, "unterminated char literal");
}
_ = 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();
if !self.match_char('\'') {
return error!(self.loc, "expected ' after char literal");
@@ -257,6 +260,12 @@ impl Tokenizer {
while !self.eof() {
if self.peek() == '\\' {
self.advance();
if self.eof() {
return error!(
self.loc,
format!("unterminated string, started at {}", start_loc)
);
}
} else if self.peek() == '"' {
break;
} else if self.peek() == '\n' {
@@ -340,7 +349,10 @@ impl Tokenizer {
fn scan_number(&mut self) -> Result<(), ZernError> {
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() {
self.advance();
}

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use crate::{
parser::{Expr, ExprKind, Params, Stmt},
symbol_table::{SymbolTable, Type},
symbol_table::{FnParams, SymbolTable},
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"];
pub struct Env {
scopes: Vec<HashMap<String, Type>>,
scopes: Vec<HashMap<String, String>>,
}
impl Env {
@@ -59,7 +59,7 @@ impl Env {
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() {
if let Some(var) = scope.get(name) {
return Some(var);
@@ -365,7 +365,7 @@ impl<'a> TypeChecker<'a> {
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 {
ExprKind::Binary { left, op, right } => {
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 Some(fn_type) = self.symbol_table.functions.get(&callee_name.lexeme) {
// its a function (defined/builtin/extern)
if let Some(params) = fn_type.params.clone() {
if params.len() != args.len() {
return error!(
&paren.loc,
format!(
"expected {} arguments, got {}",
params.len(),
args.len()
)
);
match &fn_type.params {
FnParams::Normal(params) => {
if params.len() != args.len() {
return error!(
&paren.loc,
format!(
"expected {} arguments, got {}",
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() {
expect_type!(self.typecheck_expr(env, arg)?, params[i], paren.loc);
}
} else {
// its a variadic function, cant check arg types
for arg in args {
self.typecheck_expr(env, arg)?;
FnParams::Variadic => {
// cant check arg types
for arg in args {
self.typecheck_expr(env, arg)?;
}
}
}
Ok(fn_type.return_type.clone())
@@ -587,35 +594,38 @@ impl<'a> TypeChecker<'a> {
}
};
if let Some(params) = &func_type.params {
if params.len() != args.len() + 1 {
return error!(
method.loc,
format!(
"expected {} arguments, got {}",
params.len() - 1,
args.len()
)
);
match &func_type.params {
FnParams::Normal(params) => {
if params.len() != args.len() + 1 {
return error!(
method.loc,
format!(
"expected {} arguments, got {}",
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 {
return error!(
method.loc,
format!(
"first parameter of the method must be of type {}",
receiver_type
)
);
FnParams::Variadic => {
for arg in args {
self.typecheck_expr(env, arg)?;
}
Ok(func_type.return_type.clone())
}
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())
}
}
}?;