fix bugs found by the fuzzer
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@
|
|||||||
/out*
|
/out*
|
||||||
/TODO
|
/TODO
|
||||||
/vscode
|
/vscode
|
||||||
|
/fuzz
|
||||||
|
/perf.data*
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 +464,8 @@ 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 {
|
||||||
|
FnParams::Normal(params) => {
|
||||||
if params.len() != args.len() {
|
if params.len() != args.len() {
|
||||||
return error!(
|
return error!(
|
||||||
&paren.loc,
|
&paren.loc,
|
||||||
@@ -476,14 +477,20 @@ impl<'a> TypeChecker<'a> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (i, arg) in args.iter().enumerate() {
|
for (i, arg) in args.iter().enumerate() {
|
||||||
expect_type!(self.typecheck_expr(env, arg)?, params[i], paren.loc);
|
expect_type!(
|
||||||
|
self.typecheck_expr(env, arg)?,
|
||||||
|
params[i],
|
||||||
|
paren.loc
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// its a variadic function, cant check arg types
|
FnParams::Variadic => {
|
||||||
|
// cant check arg types
|
||||||
for arg in args {
|
for arg in args {
|
||||||
self.typecheck_expr(env, arg)?;
|
self.typecheck_expr(env, arg)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok(fn_type.return_type.clone())
|
Ok(fn_type.return_type.clone())
|
||||||
} else {
|
} else {
|
||||||
// its a variable containing function address
|
// its a variable containing function address
|
||||||
@@ -587,7 +594,8 @@ impl<'a> TypeChecker<'a> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(params) = &func_type.params {
|
match &func_type.params {
|
||||||
|
FnParams::Normal(params) => {
|
||||||
if params.len() != args.len() + 1 {
|
if params.len() != args.len() + 1 {
|
||||||
return error!(
|
return error!(
|
||||||
method.loc,
|
method.loc,
|
||||||
@@ -611,13 +619,15 @@ impl<'a> TypeChecker<'a> {
|
|||||||
expect_type!(self.typecheck_expr(env, arg)?, params[i + 1], method.loc);
|
expect_type!(self.typecheck_expr(env, arg)?, params[i + 1], method.loc);
|
||||||
}
|
}
|
||||||
Ok(func_type.return_type.clone())
|
Ok(func_type.return_type.clone())
|
||||||
} else {
|
}
|
||||||
|
FnParams::Variadic => {
|
||||||
for arg in args {
|
for arg in args {
|
||||||
self.typecheck_expr(env, arg)?;
|
self.typecheck_expr(env, arg)?;
|
||||||
}
|
}
|
||||||
Ok(func_type.return_type.clone())
|
Ok(func_type.return_type.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
self.expr_types.insert(expr.id, expr_type.clone());
|
self.expr_types.insert(expr.id, expr_type.clone());
|
||||||
|
|||||||
Reference in New Issue
Block a user