@@ -534,6 +534,10 @@ pub struct VM<'gc> {
534534 generator_yield_value: Option<Value<'gc>>,
535535 // Set by Opcode::GeneratorParamInitDone during generator call-time preflight.
536536 generator_param_init_done: bool,
537+ // Bytecode IP at which the most recent Throw opcode executed (for line-number reporting).
538+ last_throw_ip: Option<usize>,
539+ // IP of the opcode currently being executed.
540+ current_opcode_ip: usize,
537541 // %GeneratorPrototype% intrinsic — shared prototype for generator .prototype objects
538542 generator_prototype: Value<'gc>,
539543 // %GeneratorFunction.prototype% — proto for generator functions themselves
@@ -657,6 +661,8 @@ impl<'gc> VM<'gc> {
657661 next_generator_id: 1,
658662 generator_yield_value: None,
659663 generator_param_init_done: false,
664+ last_throw_ip: None,
665+ current_opcode_ip: 0,
660666 generator_prototype: Value::Undefined,
661667 generator_function_prototype: Value::Undefined,
662668 async_generator_prototype: Value::Undefined,
@@ -9125,6 +9131,9 @@ impl<'gc> VM<'gc> {
91259131 for ip in &eval_chunk.method_function_ips {
91269132 self.chunk.method_function_ips.insert(ip + code_offset);
91279133 }
9134+ for &(ip, line, col) in &eval_chunk.line_map {
9135+ self.chunk.line_map.push((ip + code_offset, line, col));
9136+ }
91289137
91299138 (code_offset, const_offset)
91309139 }
@@ -11424,10 +11433,6 @@ impl<'gc> VM<'gc> {
1142411433 self.script_path.as_deref().unwrap_or("<anonymous>")
1142511434 }
1142611435
11427- fn source_lines(&self) -> Option<Vec<&str>> {
11428- self.script_source.as_ref().map(|source| source.split('\n').collect())
11429- }
11430-
1143111436 fn current_named_frames(&self) -> Vec<(usize, String)> {
1143211437 self.frames
1143311438 .iter()
@@ -11439,123 +11444,31 @@ impl<'gc> VM<'gc> {
1143911444 .collect()
1144011445 }
1144111446
11442- fn find_function_declaration_line(lines: &[&str], function_name: &str) -> Option<usize> {
11443- let patterns = [
11444- format!("function {}(", function_name),
11445- format!(".{} = function", function_name),
11446- format!("{} = function", function_name),
11447- format!("{}: function", function_name),
11448- ];
11449-
11450- lines
11451- .iter()
11452- .enumerate()
11453- .find(|(_, line)| patterns.iter().any(|pattern| line.contains(pattern)))
11454- .map(|(index, _)| index + 1)
11455- }
11456-
11457- fn find_line_and_column(lines: &[&str], needle: &str, start_line: usize, end_line: usize) -> Option<(usize, usize)> {
11458- let start_index = start_line.saturating_sub(1);
11459- let end_index = end_line.min(lines.len());
11460- for (offset, line) in lines[start_index..end_index].iter().enumerate() {
11461- if let Some(column) = line.find(needle) {
11462- return Some((start_index + offset + 1, column + 1));
11463- }
11464- }
11465- None
11466- }
11467-
11468- fn infer_throw_site(&self, current_function: Option<&str>) -> Option<(usize, usize)> {
11469- let lines = self.source_lines()?;
11470- if let Some(function_name) = current_function
11471- && let Some(decl_line) = Self::find_function_declaration_line(&lines, function_name)
11472- && let Some(found) = Self::find_line_and_column(&lines, "throw ", decl_line, lines.len())
11473- {
11474- return Some(found);
11475- }
11476- Self::find_line_and_column(&lines, "throw ", 1, lines.len())
11477- }
11478-
11479- fn infer_callsite(&self, function_name: &str, scope_function: Option<&str>) -> Option<(usize, usize)> {
11480- let lines = self.source_lines()?;
11481- let call_patterns = [format!("{}(", function_name), format!(".{}(", function_name)];
11482-
11483- let (start_line, end_line) = if let Some(scope_name) = scope_function {
11484- if let Some(decl_line) = Self::find_function_declaration_line(&lines, scope_name) {
11485- (decl_line, lines.len())
11486- } else {
11487- (1, lines.len())
11488- }
11489- } else {
11490- (1, lines.len())
11491- };
11492-
11493- for line_number in start_line..=end_line {
11494- let line = lines.get(line_number.saturating_sub(1))?;
11495- if line.contains(&format!("function {}(", function_name)) {
11496- continue;
11497- }
11498- if let Some(column) = line.find(&call_patterns[1]) {
11499- return Some((line_number, column + 2));
11500- }
11501- if let Some(column) = line.find(&call_patterns[0]) {
11502- return Some((line_number, column + 1));
11503- }
11504- }
11505-
11506- None
11447+ /// Find the source line/column for the most recent throw, using the IP→line/col map.
11448+ fn infer_throw_site(&self) -> Option<(usize, usize)> {
11449+ let throw_ip = self.last_throw_ip?;
11450+ self.chunk.get_line_col_for_ip(throw_ip)
1150711451 }
1150811452
11453+ /// Find the source line/column for a call-site IP, using the IP→line/col map.
1150911454 fn infer_callsite_from_call_ip(&self, call_ip: usize) -> Option<(usize, usize)> {
11510- let callee_name = self.chunk.call_callee_names.get(&call_ip)?;
11511- let mut ips_for_name: Vec<usize> = self
11512- .chunk
11513- .call_callee_names
11514- .iter()
11515- .filter_map(|(ip, name)| if name == callee_name { Some(*ip) } else { None })
11516- .collect();
11517- ips_for_name.sort_unstable();
11518- let ordinal = ips_for_name.iter().position(|ip| *ip == call_ip)?;
11519-
11520- let lines = self.source_lines()?;
11521- let call_patterns = [format!("{}(", callee_name), format!(".{}(", callee_name)];
11522- let mut seen = 0usize;
11523- for (idx, line) in lines.iter().enumerate() {
11524- if line.contains(&format!("function {}(", callee_name)) {
11525- continue;
11526- }
11527- if let Some(column) = line.find(&call_patterns[1]) {
11528- if seen == ordinal {
11529- return Some((idx + 1, column + 2));
11530- }
11531- seen += 1;
11532- continue;
11533- }
11534- if let Some(column) = line.find(&call_patterns[0]) {
11535- if seen == ordinal {
11536- return Some((idx + 1, column + 1));
11537- }
11538- seen += 1;
11539- }
11540- }
11541-
11542- None
11455+ self.chunk.get_line_col_for_ip(call_ip)
1154311456 }
1154411457
1154511458 fn build_error_stack(&self, error_name: &str, message: &str) -> (Option<(usize, usize)>, Vec<String>) {
1154611459 let mut lines = vec![Self::format_error_name_message(error_name, message)];
1154711460 let named_frames = self.current_named_frames();
1154811461
1154911462 if named_frames.is_empty() {
11550- if let Some((line, column)) = self.infer_throw_site(None ) {
11463+ if let Some((line, column)) = self.infer_throw_site() {
1155111464 lines.push(format!(" at <anonymous> ({}:{}:{})", self.current_script_file(), line, column));
1155211465 return (Some((line, column)), lines);
1155311466 }
1155411467 return (None, lines);
1155511468 }
1155611469
1155711470 let current_function = named_frames.last().map(|(_, name)| name.as_str());
11558- let throw_site = self.infer_throw_site(current_function );
11471+ let throw_site = self.infer_throw_site();
1155911472 if let Some((line, column)) = throw_site {
1156011473 let function_name = current_function.unwrap_or("<anonymous>");
1156111474 lines.push(format!(
@@ -11569,12 +11482,9 @@ impl<'gc> VM<'gc> {
1156911482
1157011483 for pair in named_frames.windows(2).rev() {
1157111484 let caller_name = &pair[0].1;
11572- let callee_name = &pair[1].1;
1157311485 let callee_frame_idx = pair[1].0;
1157411486 let call_ip = self.frames[callee_frame_idx].return_ip.saturating_sub(2);
11575- let call_site = self
11576- .infer_callsite_from_call_ip(call_ip)
11577- .or_else(|| self.infer_callsite(callee_name, Some(caller_name)));
11487+ let call_site = self.infer_callsite_from_call_ip(call_ip);
1157811488 if let Some((line, column)) = call_site {
1157911489 lines.push(format!(
1158011490 " at {} ({}:{}:{})",
@@ -11586,12 +11496,9 @@ impl<'gc> VM<'gc> {
1158611496 }
1158711497 }
1158811498
11589- if let Some((outer_idx, outermost_name )) = named_frames.first() {
11499+ if let Some((outer_idx, _outermost_name )) = named_frames.first() {
1159011500 let outer_call_ip = self.frames[*outer_idx].return_ip.saturating_sub(2);
11591- if let Some((line, column)) = self
11592- .infer_callsite_from_call_ip(outer_call_ip)
11593- .or_else(|| self.infer_callsite(outermost_name, None))
11594- {
11501+ if let Some((line, column)) = self.infer_callsite_from_call_ip(outer_call_ip) {
1159511502 lines.push(format!(" at <anonymous> ({}:{}:{})", self.current_script_file(), line, column));
1159611503 }
1159711504 }
@@ -24405,6 +24312,10 @@ impl<'gc> VM<'gc> {
2440524312 /// Handle a thrown value: unwind to nearest try/catch or return error
2440624313 fn handle_throw(&mut self, ctx: &GcContext<'gc>, thrown: &Value<'gc>) -> Result<(), JSError> {
2440724314 self.pending_throw = None;
24315+ // Record the throw-site IP if not already set by a Throw opcode.
24316+ if self.last_throw_ip.is_none() {
24317+ self.last_throw_ip = Some(self.current_opcode_ip);
24318+ }
2440824319 if let Value::VmObject(map) = &thrown {
2440924320 self.annotate_error_object(ctx, map);
2441024321 }
@@ -25021,6 +24932,7 @@ impl<'gc> VM<'gc> {
2502124932 continue;
2502224933 }
2502324934 // Fetch instruction
24935+ self.current_opcode_ip = self.ip;
2502424936 let instruction_byte = self.read_byte();
2502524937 let instruction = Opcode::try_from(instruction_byte)?;
2502624938
@@ -25141,6 +25053,11 @@ impl<'gc> VM<'gc> {
2514125053 if frame.is_method {
2514225054 self.this_stack.pop();
2514325055 }
25056+ // Pop any try frames that belonged to the returning function.
25057+ let current_depth = self.frames.len();
25058+ while self.try_stack.last().is_some_and(|tf| tf.frame_depth > current_depth) {
25059+ self.try_stack.pop();
25060+ }
2514425061 self.stack.truncate(frame.bp - 1);
2514525062 self.ip = frame.return_ip;
2514625063 if self.frames.len() < min_depth {
@@ -28377,6 +28294,8 @@ impl<'gc> VM<'gc> {
2837728294 // Opcode::Throw
2837828295 fn run_opcode_throw(&mut self, ctx: &GcContext<'gc>) -> Result<OpcodeAction<'gc>, JSError> {
2837928296 let thrown = self.stack.pop().unwrap_or(Value::Undefined);
28297+ // Record the throw-site IP for accurate line-number reporting.
28298+ self.last_throw_ip = Some(self.current_opcode_ip);
2838028299 // diagnostic logging
2838128300 log::warn!("Throw opcode value={}", self.vm_to_string(ctx, &thrown));
2838228301 if let Value::VmObject(obj) = &thrown {
0 commit comments