diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 762585783c..18215003ee 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -41,8 +41,10 @@ use rustpython_compiler_core::{ }; use rustpython_compiler_source::SourceCode; // use rustpython_parser_core::source_code::{LineNumber, SourceLocation}; +use crate::error::InternalError; use std::borrow::Cow; +pub(crate) type InternalResult = Result; type CompileResult = Result; #[derive(PartialEq, Eq, Clone, Copy)] @@ -210,6 +212,54 @@ macro_rules! emit { }; } +fn eprint_location(zelf: &Compiler<'_>) { + let start = zelf + .source_code + .source_location(zelf.current_source_range.start()); + let end = zelf + .source_code + .source_location(zelf.current_source_range.end()); + eprintln!( + "LOCATION: {} from {}:{} to {}:{}", + zelf.source_code.path.to_owned(), + start.row, + start.column, + end.row, + end.column + ); +} + +/// Better traceback for internal error +fn unwrap_internal(zelf: &Compiler<'_>, r: InternalResult) -> T { + if let Err(ref r_err) = r { + eprintln!("=== CODEGEN PANIC INFO ==="); + eprintln!("This IS an internal error: {}", r_err); + eprint_location(zelf); + eprintln!("=== END PANIC INFO ==="); + } + r.unwrap() +} + +fn compiler_unwrap_option(zelf: &Compiler<'_>, o: Option) -> T { + if o.is_none() { + eprintln!("=== CODEGEN PANIC INFO ==="); + eprintln!("This IS an internal error, an option was unwrapped during codegen"); + eprint_location(zelf); + eprintln!("=== END PANIC INFO ==="); + } + o.unwrap() +} + +// fn compiler_result_unwrap(zelf: &Compiler<'_>, result: Result) -> T { +// if result.is_err() { +// eprintln!("=== CODEGEN PANIC INFO ==="); +// eprintln!("This IS an internal error, an result was unwrapped during codegen"); +// eprint_location(zelf); +// eprintln!("=== END PANIC INFO ==="); +// } +// result.unwrap() +// } + /// The pattern context holds information about captured names and jump targets. #[derive(Clone)] pub struct PatternContext { @@ -372,10 +422,9 @@ impl Compiler<'_> { fn pop_code_object(&mut self) -> CodeObject { let table = self.pop_symbol_table(); assert!(table.sub_tables.is_empty()); - self.code_stack - .pop() - .unwrap() - .finalize_code(self.opts.optimize) + let pop = self.code_stack.pop(); + let stack_top = compiler_unwrap_option(self, pop); + unwrap_internal(self, stack_top.finalize_code(self.opts.optimize)) } // could take impl Into>, but everything is borrowed from ast structs; we never @@ -482,7 +531,8 @@ impl Compiler<'_> { self.current_block().instructions.pop(); // pop Instruction::Pop } Stmt::FunctionDef(_) | Stmt::ClassDef(_) => { - let store_inst = self.current_block().instructions.pop().unwrap(); // pop Instruction::Store + let pop_instructions = self.current_block().instructions.pop(); + let store_inst = compiler_unwrap_option(self, pop_instructions); // pop Instruction::Store emit!(self, Instruction::Duplicate); self.current_block().instructions.push(store_inst); } @@ -540,8 +590,11 @@ impl Compiler<'_> { self.check_forbidden_name(&name, usage)?; let symbol_table = self.symbol_table_stack.last().unwrap(); - let symbol = symbol_table.lookup(name.as_ref()).unwrap_or_else(|| - unreachable!("the symbol '{name}' should be present in the symbol table, even when it is undefined in python."), + let symbol = unwrap_internal( + self, + symbol_table + .lookup(name.as_ref()) + .ok_or_else(|| InternalError::MissingSymbol(name.to_string())), ); let info = self.code_stack.last_mut().unwrap(); let mut cache = &mut info.name_cache; @@ -1476,12 +1529,12 @@ impl Compiler<'_> { } for var in &*code.freevars { let table = self.symbol_table_stack.last().unwrap(); - let symbol = table.lookup(var).unwrap_or_else(|| { - panic!( - "couldn't look up var {} in {} in {}", - var, code.obj_name, self.source_code.path - ) - }); + let symbol = unwrap_internal( + self, + table + .lookup(var) + .ok_or_else(|| InternalError::MissingSymbol(var.to_owned())), + ); let parent_code = self.code_stack.last().unwrap(); let vars = match symbol.scope { SymbolScope::Free => &parent_code.freevar_cache, @@ -1564,8 +1617,11 @@ impl Compiler<'_> { // Check if the class is declared global let symbol_table = self.symbol_table_stack.last().unwrap(); - let symbol = symbol_table.lookup(name.as_ref()).expect( - "The symbol must be present in the symbol table, even when it is undefined in python.", + let symbol = unwrap_internal( + self, + symbol_table + .lookup(name.as_ref()) + .ok_or_else(|| InternalError::MissingSymbol(name.to_owned())), ); let mut global_path_prefix = Vec::new(); if symbol.scope == SymbolScope::GlobalExplicit { diff --git a/compiler/codegen/src/error.rs b/compiler/codegen/src/error.rs index 4394b936d2..5e0ac12934 100644 --- a/compiler/codegen/src/error.rs +++ b/compiler/codegen/src/error.rs @@ -34,6 +34,27 @@ impl fmt::Display for CodegenError { } } +#[derive(Debug)] +#[non_exhaustive] +pub enum InternalError { + StackOverflow, + StackUnderflow, + MissingSymbol(String), +} + +impl Display for InternalError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::StackOverflow => write!(f, "stack overflow"), + Self::StackUnderflow => write!(f, "stack underflow"), + Self::MissingSymbol(s) => write!( + f, + "The symbol '{s}' must be present in the symbol table, even when it is undefined in python." + ), + } + } +} + #[derive(Debug)] #[non_exhaustive] pub enum CodegenErrorType { diff --git a/compiler/codegen/src/ir.rs b/compiler/codegen/src/ir.rs index ee29c65a16..bb1f8b7564 100644 --- a/compiler/codegen/src/ir.rs +++ b/compiler/codegen/src/ir.rs @@ -1,6 +1,7 @@ use std::ops; use crate::IndexSet; +use crate::error::InternalError; use ruff_source_file::{OneIndexed, SourceLocation}; use rustpython_compiler_core::bytecode::{ CodeFlags, CodeObject, CodeUnit, ConstantData, InstrDisplayContext, Instruction, Label, OpArg, @@ -82,12 +83,12 @@ pub struct CodeInfo { pub freevar_cache: IndexSet, } impl CodeInfo { - pub fn finalize_code(mut self, optimize: u8) -> CodeObject { + pub fn finalize_code(mut self, optimize: u8) -> crate::InternalResult { if optimize > 0 { self.dce(); } - let max_stackdepth = self.max_stackdepth(); + let max_stackdepth = self.max_stackdepth()?; let cell2arg = self.cell2arg(); let CodeInfo { @@ -154,7 +155,7 @@ impl CodeInfo { locations.clear() } - CodeObject { + Ok(CodeObject { flags, posonlyarg_count, arg_count, @@ -172,7 +173,7 @@ impl CodeInfo { cellvars: cellvar_cache.into_iter().collect(), freevars: freevar_cache.into_iter().collect(), cell2arg, - } + }) } fn cell2arg(&self) -> Option> { @@ -219,7 +220,7 @@ impl CodeInfo { } } - fn max_stackdepth(&self) -> u32 { + fn max_stackdepth(&self) -> crate::InternalResult { let mut maxdepth = 0u32; let mut stack = Vec::with_capacity(self.blocks.len()); let mut start_depths = vec![u32::MAX; self.blocks.len()]; @@ -244,10 +245,13 @@ impl CodeInfo { let instr_display = instr.display(display_arg, self); eprint!("{instr_display}: {depth} {effect:+} => "); } - if effect < 0 && depth < effect.unsigned_abs() { - panic!("The stack will underflow at {depth} with {effect} effect on {instr:?}"); - } - let new_depth = depth.checked_add_signed(effect).unwrap(); + let new_depth = depth.checked_add_signed(effect).ok_or({ + if effect < 0 { + InternalError::StackUnderflow + } else { + InternalError::StackOverflow + } + })?; if DEBUG { eprintln!("{new_depth}"); } @@ -264,7 +268,13 @@ impl CodeInfo { ) { let effect = instr.stack_effect(ins.arg, true); - let target_depth = depth.checked_add_signed(effect).unwrap(); + let target_depth = depth.checked_add_signed(effect).ok_or({ + if effect < 0 { + InternalError::StackUnderflow + } else { + InternalError::StackOverflow + } + })?; if target_depth > maxdepth { maxdepth = target_depth } @@ -280,7 +290,7 @@ impl CodeInfo { if DEBUG { eprintln!("DONE: {maxdepth}"); } - maxdepth + Ok(maxdepth) } } diff --git a/compiler/codegen/src/lib.rs b/compiler/codegen/src/lib.rs index 90e11c5b84..3ef6a7456f 100644 --- a/compiler/codegen/src/lib.rs +++ b/compiler/codegen/src/lib.rs @@ -18,6 +18,8 @@ mod unparse; pub use compile::CompileOpts; use ruff_python_ast::Expr; +pub(crate) use compile::InternalResult; + pub trait ToPythonName { /// Returns a short name for the node suitable for use in error messages. fn python_name(&self) -> &'static str;