From 45171f688cdf8f463734607ba8fec5843dd553ca Mon Sep 17 00:00:00 2001 From: Toni Date: Fri, 17 Jul 2026 14:21:29 +0200 Subject: [PATCH] more fuzzing, recursion, prevent including block devices, utf8 fix --- src/parser.rs | 55 ++++++++++++++++++++++++++++++++++++++-------- src/tokenizer.rs | 14 ++++++++++++ src/typechecker.rs | 13 +++++++---- 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 19119f1..3568a5a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use crate::tokenizer::{ - Token, + Loc, Token, TokenType::{self, Identifier}, ZernError, error, }; @@ -79,6 +79,40 @@ pub enum Stmt { }, } +// https://stackoverflow.com/a/29963675 +pub struct ScopeCall { + pub c: F, +} + +impl Drop for ScopeCall { + fn drop(&mut self) { + (self.c)(); + } +} + +macro_rules! recursion_guard { + ($self:ident) => { + $self.depth += 1; + if $self.depth > 200 { + return error!( + Loc { + filename: "".into(), + line: 0, + column: 0, + }, + "maximum expression depth reached" + ); + } + let self_ptr = $self as *mut Self; + let _scope_call = ScopeCall { + c: || unsafe { + (*self_ptr).depth -= 1; + }, + }; + }; +} +pub(crate) use recursion_guard; + pub static NEXT_EXPR_ID: AtomicUsize = AtomicUsize::new(0); #[derive(Debug, Clone)] @@ -479,17 +513,12 @@ impl Parser { } fn expression(&mut self) -> Result { - 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 + recursion_guard!(self); + self.or_and() } fn or_and(&mut self) -> Result { + recursion_guard!(self); let mut expr = self.equality()?; while self.match_token(&[TokenType::LogicalOr, TokenType::LogicalAnd]) { @@ -506,6 +535,7 @@ impl Parser { } fn equality(&mut self) -> Result { + recursion_guard!(self); let mut expr = self.comparison()?; while self.match_token(&[TokenType::DoubleEqual, TokenType::NotEqual]) { @@ -522,6 +552,7 @@ impl Parser { } fn comparison(&mut self) -> Result { + recursion_guard!(self); let mut expr = self.term()?; while self.match_token(&[ @@ -543,6 +574,7 @@ impl Parser { } fn term(&mut self) -> Result { + recursion_guard!(self); let mut expr = self.factor()?; while self.match_token(&[ @@ -565,6 +597,7 @@ impl Parser { } fn factor(&mut self) -> Result { + recursion_guard!(self); let mut expr = self.cast()?; while self.match_token(&[ @@ -587,6 +620,7 @@ impl Parser { } fn cast(&mut self) -> Result { + recursion_guard!(self); let mut expr = self.unary()?; while self.match_token(&[TokenType::KeywordAs]) { @@ -601,6 +635,8 @@ impl Parser { } fn unary(&mut self) -> Result { + recursion_guard!(self); + if self.match_token(&[TokenType::Xor]) { let op = self.previous().clone(); let right = self.unary()?; @@ -622,6 +658,7 @@ impl Parser { } fn call(&mut self) -> Result { + recursion_guard!(self); let mut expr = self.primary()?; loop { diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 915ee19..706d69e 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -2,6 +2,7 @@ use std::{ cmp::Ordering, collections::HashSet, fmt, fs, + os::unix::fs::FileTypeExt, path::{Path, PathBuf}, }; @@ -473,6 +474,19 @@ impl<'a> Tokenizer<'a> { return Ok(()); } + let meta = match std::fs::metadata(&canonical) { + Ok(x) => x, + Err(_) => { + return error!(self.loc, format!("failed to access {}", path)); + } + }; + if !meta.file_type().is_file() { + return error!( + self.loc, + format!("refusing to read {} because it is not a regular file", path) + ); + } + let source = match fs::read_to_string(&canonical) { Ok(x) => x, Err(_) => { diff --git a/src/typechecker.rs b/src/typechecker.rs index bd477b3..cb7261e 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use crate::{ - parser::{Expr, ExprKind, Params, Stmt}, + parser::{Expr, ExprKind, Params, ScopeCall, Stmt, recursion_guard}, symbol_table::{FnParams, SymbolTable}, - tokenizer::{TokenType, ZernError, error}, + tokenizer::{Loc, TokenType, ZernError, error}, }; macro_rules! expect_type { @@ -73,6 +73,7 @@ pub struct TypeChecker<'a> { symbol_table: &'a SymbolTable, pub expr_types: HashMap, current_function_return_type: String, + depth: usize, } impl<'a> TypeChecker<'a> { @@ -81,6 +82,7 @@ impl<'a> TypeChecker<'a> { symbol_table, expr_types: HashMap::new(), current_function_return_type: String::new(), + depth: 0, } } @@ -373,6 +375,8 @@ impl<'a> TypeChecker<'a> { } pub fn typecheck_expr(&mut self, env: &mut Env, expr: &Expr) -> Result { + recursion_guard!(self); + let expr_type = match &expr.kind { ExprKind::Binary { left, op, right } => { let left_type = self.typecheck_expr(env, left)?; @@ -685,7 +689,7 @@ impl<'a> TypeChecker<'a> { if let Some(args) = inner { return args.iter().all(|arg| self.is_valid_type_name(arg)); } - return false; + unreachable!(); } if BUILTIN_TYPES.contains(&name) { return true; @@ -700,7 +704,8 @@ impl<'a> TypeChecker<'a> { fn parse_generic_type(s: &str) -> (&str, Option>) { if let Some(lt_pos) = s.find('<') { let base = &s[..lt_pos]; - let inner = &s[lt_pos + 1..s.len() - 1]; + let close_pos = s.rfind('>').unwrap_or(s.len()); + let inner = &s[lt_pos + 1..close_pos]; let mut args = Vec::new(); let mut depth = 0; let mut start = 0;