1use super::{PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyIntRef};
6use crate::{
7 AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
8 class::PyClassImpl,
9 frame::{FrameObject, FrameOwner},
10 function::PySetterValue,
11 types::{Destructor, Representable},
12};
13use core::sync::atomic::Ordering::Relaxed;
14use num_traits::Zero;
15#[allow(unused_imports)]
16use rustpython_common::atomic::Radium;
17use rustpython_compiler_core::bytecode::{self, Constant, Instruction, StackEffect};
18use stack_analysis::*;
19
20pub(crate) mod stack_analysis {
26 use super::*;
27
28 const BITS_PER_BLOCK: u32 = 3;
29 const MASK: i64 = (1 << BITS_PER_BLOCK) - 1; const MAX_STACK_ENTRIES: u32 = 63 / BITS_PER_BLOCK; const WILL_OVERFLOW: u64 = 1u64 << ((MAX_STACK_ENTRIES - 1) * BITS_PER_BLOCK);
32
33 pub(crate) const EMPTY_STACK: i64 = 0;
34 pub(crate) const UNINITIALIZED: i64 = -2;
35 pub(crate) const OVERFLOWED: i64 = -1;
36
37 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
39 #[repr(i64)]
40 pub(crate) enum Kind {
41 Iterator = 1,
42 Except = 2,
43 Object = 3,
44 Null = 4,
45 Lasti = 5,
46 }
47
48 impl Kind {
49 const fn from_i64(v: i64) -> Option<Self> {
50 Some(match v {
51 1 => Self::Iterator,
52 2 => Self::Except,
53 3 => Self::Object,
54 4 => Self::Null,
55 5 => Self::Lasti,
56 _ => return None,
57 })
58 }
59 }
60
61 pub(crate) const fn push_value(stack: i64, kind: i64) -> i64 {
62 if (stack as u64) >= WILL_OVERFLOW {
63 OVERFLOWED
64 } else {
65 (stack << BITS_PER_BLOCK) | kind
66 }
67 }
68
69 pub(crate) const fn pop_value(stack: i64) -> i64 {
70 stack >> BITS_PER_BLOCK
71 }
72
73 pub(crate) const fn top_of_stack(stack: i64) -> i64 {
74 stack & MASK
75 }
76
77 const fn peek(stack: i64, n: u32) -> i64 {
78 debug_assert!(n >= 1);
79 (stack >> (BITS_PER_BLOCK * (n - 1))) & MASK
80 }
81
82 const fn stack_swap(stack: i64, n: u32) -> i64 {
83 debug_assert!(n >= 1);
84 let to_swap = peek(stack, n);
85 let top = top_of_stack(stack);
86 let shift = BITS_PER_BLOCK * (n - 1);
87 let replaced_low = (stack & !(MASK << shift)) | (top << shift);
88 (replaced_low & !MASK) | to_swap
89 }
90
91 const fn pop_to_level(mut stack: i64, level: u32) -> i64 {
92 if level == 0 {
93 return EMPTY_STACK;
94 }
95 let max_item: i64 = (1 << BITS_PER_BLOCK) - 1;
96 let level_max_stack = max_item << ((level - 1) * BITS_PER_BLOCK);
97 while stack > level_max_stack {
98 stack = pop_value(stack);
99 }
100 stack
101 }
102
103 #[must_use]
104 const fn compatible_kind(from: i64, to: i64) -> bool {
105 if to == 0 {
106 false
107 } else if to == Kind::Object as i64 {
108 from != Kind::Null as i64
109 } else if to == Kind::Null as i64 {
110 true
111 } else {
112 from == to
113 }
114 }
115
116 #[must_use]
117 pub(crate) const fn compatible_stack(from_stack: i64, to_stack: i64) -> bool {
118 if from_stack < 0 || to_stack < 0 {
119 return false;
120 }
121 let mut from = from_stack;
122 let mut to = to_stack;
123 while from > to {
124 from = pop_value(from);
125 }
126 while from != 0 {
127 let from_top = top_of_stack(from);
128 let to_top = top_of_stack(to);
129 if !compatible_kind(from_top, to_top) {
130 return false;
131 }
132 from = pop_value(from);
133 to = pop_value(to);
134 }
135 to == 0
136 }
137
138 pub(crate) const fn explain_incompatible_stack(to_stack: i64) -> &'static str {
139 debug_assert!(to_stack != 0);
140
141 if to_stack == OVERFLOWED {
142 return "stack is too deep to analyze";
143 }
144
145 if to_stack == UNINITIALIZED {
146 return "can't jump into an exception handler, or code may be unreachable";
147 }
148
149 match Kind::from_i64(top_of_stack(to_stack)) {
150 Some(Kind::Except) => "can't jump into an 'except' block as there's no exception",
151 Some(Kind::Lasti) => "can't jump into a re-raising block as there's no location",
152 Some(Kind::Iterator) => "can't jump into the body of a for loop",
153 _ => "incompatible stacks",
154 }
155 }
156
157 pub(crate) fn mark_stacks<C: Constant>(code: &bytecode::CodeObject<C>) -> Vec<i64> {
159 let instructions = &*code.instructions;
160 let len = instructions.len();
161
162 let mut stacks = vec![UNINITIALIZED; len + 1];
163 stacks[0] = EMPTY_STACK;
164
165 let mut todo = true;
166 while todo {
167 todo = false;
168
169 let mut i = 0;
170 while i < len {
171 let mut next_stack = stacks[i];
172 let mut opcode = instructions[i].op;
173 let mut oparg: u32 = 0;
174
175 while matches!(opcode, Instruction::ExtendedArg) {
177 oparg = (oparg << 8) | u32::from(u8::from(instructions[i].arg));
178 i += 1;
179 if i >= len {
180 break;
181 }
182 stacks[i] = next_stack;
183 opcode = instructions[i].op;
184 }
185 if i >= len {
186 break;
187 }
188 oparg = (oparg << 8) | u32::from(u8::from(instructions[i].arg));
189
190 let opcode = opcode.to_base().unwrap_or(opcode).deoptimize();
192
193 let caches = opcode.cache_entries();
194 let next_i = i + 1 + caches;
195
196 if next_stack == UNINITIALIZED {
197 i = next_i;
198 continue;
199 }
200
201 match opcode {
202 Instruction::PopJumpIfFalse { .. }
203 | Instruction::PopJumpIfTrue { .. }
204 | Instruction::PopJumpIfNone { .. }
205 | Instruction::PopJumpIfNotNone { .. } => {
206 let j = next_i + oparg as usize;
208 next_stack = pop_value(next_stack);
209 let target_stack = next_stack;
210 if j < stacks.len() && stacks[j] == UNINITIALIZED {
211 stacks[j] = target_stack;
212 }
213 if next_i < stacks.len() {
214 stacks[next_i] = next_stack;
215 }
216 }
217 Instruction::Send { .. } => {
218 let j = next_i + oparg as usize;
220 if j < stacks.len() && stacks[j] == UNINITIALIZED {
221 stacks[j] = next_stack;
222 }
223 if next_i < stacks.len() {
224 stacks[next_i] = next_stack;
225 }
226 }
227 Instruction::JumpForward { .. } => {
228 let j = next_i + oparg as usize;
230 if j < stacks.len() && stacks[j] == UNINITIALIZED {
231 stacks[j] = next_stack;
232 }
233 }
234 Instruction::JumpBackward { .. }
235 | Instruction::JumpBackwardNoInterrupt { .. } => {
236 let j = next_i - oparg as usize;
238 if j < stacks.len() && stacks[j] == UNINITIALIZED {
239 stacks[j] = next_stack;
240 if j < i {
241 todo = true;
242 }
243 }
244 }
245 Instruction::GetIter | Instruction::GetAiter => {
246 next_stack = push_value(pop_value(next_stack), Kind::Iterator as i64);
247 if next_i < stacks.len() {
248 stacks[next_i] = next_stack;
249 }
250 }
251 Instruction::ForIter { .. } => {
252 let body_stack = push_value(next_stack, Kind::Object as i64);
254 if next_i < stacks.len() {
255 stacks[next_i] = body_stack;
256 }
257 let mut j = next_i + oparg as usize;
259 if j < instructions.len() {
260 let target_op =
261 instructions[j].op.to_base().unwrap_or(instructions[j].op);
262 if matches!(target_op, Instruction::EndFor) {
263 j += 1;
264 }
265 }
266 if j < stacks.len() && stacks[j] == UNINITIALIZED {
267 stacks[j] = next_stack;
268 }
269 }
270 Instruction::EndAsyncFor => {
271 next_stack = pop_value(pop_value(next_stack));
272 if next_i < stacks.len() {
273 stacks[next_i] = next_stack;
274 }
275 }
276 Instruction::PushExcInfo => {
277 next_stack = push_value(next_stack, Kind::Except as i64);
278 if next_i < stacks.len() {
279 stacks[next_i] = next_stack;
280 }
281 }
282 Instruction::PopExcept => {
283 next_stack = pop_value(next_stack);
284 if next_i < stacks.len() {
285 stacks[next_i] = next_stack;
286 }
287 }
288 Instruction::ReturnValue => {
289 }
291 Instruction::RaiseVarargs { .. } => {
292 }
294 Instruction::Reraise { .. } => {
295 }
297 Instruction::PushNull => {
298 next_stack = push_value(next_stack, Kind::Null as i64);
299 if next_i < stacks.len() {
300 stacks[next_i] = next_stack;
301 }
302 }
303 Instruction::LoadGlobal { .. } => {
304 next_stack = push_value(next_stack, Kind::Object as i64);
305 if oparg & 1 != 0 {
306 next_stack = push_value(next_stack, Kind::Null as i64);
307 }
308 if next_i < stacks.len() {
309 stacks[next_i] = next_stack;
310 }
311 }
312 Instruction::LoadAttr { .. } => {
313 let attr_oparg = oparg;
316 if attr_oparg & 1 != 0 {
317 next_stack = pop_value(next_stack);
318 next_stack = push_value(next_stack, Kind::Object as i64);
319 next_stack = push_value(next_stack, Kind::Null as i64);
320 }
321 else {
323 let effect: StackEffect = opcode.stack_effect_info(oparg);
324 let popped = effect.popped() as i64;
325 let pushed = effect.pushed() as i64;
326 for _ in 0..popped {
327 next_stack = pop_value(next_stack);
328 }
329 for _ in 0..pushed {
330 next_stack = push_value(next_stack, Kind::Object as i64);
331 }
332 }
333 if next_i < stacks.len() {
334 stacks[next_i] = next_stack;
335 }
336 }
337 Instruction::Swap { .. } => {
338 let n = oparg;
339 next_stack = stack_swap(next_stack, n);
340 if next_i < stacks.len() {
341 stacks[next_i] = next_stack;
342 }
343 }
344 Instruction::Copy { .. } => {
345 let n = oparg;
346 next_stack = push_value(next_stack, peek(next_stack, n));
347 if next_i < stacks.len() {
348 stacks[next_i] = next_stack;
349 }
350 }
351 _ => {
352 let mut delta = opcode.stack_effect(oparg);
356 while delta < 0 {
357 next_stack = pop_value(next_stack);
358 delta += 1;
359 }
360 while delta > 0 {
361 next_stack = push_value(next_stack, Kind::Object as i64);
362 delta -= 1;
363 }
364 if next_i < stacks.len() {
365 stacks[next_i] = next_stack;
366 }
367 }
368 }
369 i = next_i;
370 }
371
372 let exception_table = bytecode::decode_exception_table(&code.exceptiontable);
374 for entry in &exception_table {
375 let start_offset = entry.start as usize;
376 let handler = entry.target as usize;
377 let level = entry.depth as u32;
378 let has_lasti = entry.push_lasti;
379
380 if start_offset < stacks.len()
381 && stacks[start_offset] != UNINITIALIZED
382 && handler < stacks.len()
383 && stacks[handler] == UNINITIALIZED
384 {
385 todo = true;
386 let mut target_stack = pop_to_level(stacks[start_offset], level);
387 if has_lasti {
388 target_stack = push_value(target_stack, Kind::Lasti as i64);
389 }
390 target_stack = push_value(target_stack, Kind::Except as i64);
391 stacks[handler] = target_stack;
392 }
393 }
394 }
395
396 stacks
397 }
398
399 pub(crate) fn mark_lines<C: Constant>(code: &bytecode::CodeObject<C>) -> Vec<i32> {
402 let len = code.instructions.len();
403 let mut line_starts = vec![-1i32; len];
404 let mut last_line: i32 = -1;
405
406 for (i, (loc, _)) in code.locations.iter().enumerate() {
407 if i >= len {
408 break;
409 }
410 let line = loc.line.get() as i32;
411 if line != last_line && line > 0 {
412 line_starts[i] = line;
413 last_line = line;
414 }
415 }
416 line_starts
417 }
418
419 pub(crate) fn first_line_not_before(lines: &[i32], line: i32) -> i32 {
421 let mut result = i32::MAX;
422 for &l in lines {
423 if l >= line && l < result {
424 result = l;
425 }
426 }
427 if result == i32::MAX { -1 } else { result }
428 }
429}
430
431pub(crate) fn init(context: &'static Context) {
432 FrameObject::extend_class(context, context.types.frame_type);
433}
434
435impl Representable for FrameObject {
436 fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
437 let code = zelf.iframe().code();
438 let file_repr = code.source_path().to_owned().as_object().repr(vm)?;
439 let lineno = zelf.lineno();
440 let name = code.code.obj_name.as_wtf8();
441 let ptr = zelf as *const Py<Self> as usize;
442 Ok(format!(
443 "<frame at {ptr:#x}, file {}, line {lineno}, code {name}>",
444 file_repr.as_wtf8(),
445 ))
446 }
447}
448
449impl FrameObject {
450 pub(crate) fn find_live_source_iframe(&self) -> *const crate::frame::InterpreterFrame {
455 let self_py_ptr = unsafe { Py::<Self>::from_payload_ptr(self) } as usize;
456 let mut cur = crate::vm::thread::get_current_frame();
457 while !cur.is_null() {
458 let materialized = unsafe { (*cur).materialized.load(Relaxed) };
459 if materialized == self_py_ptr {
460 return cur;
461 }
462 cur = unsafe { &*cur }.previous();
463 }
464 core::ptr::null()
465 }
466}
467
468impl FrameObject {
469 pub fn lineno(&self) -> i32 {
471 self.f_code().addr2line(self.f_lasti() as i32)
472 }
473
474 pub fn f_code(&self) -> PyRef<PyCode> {
475 self.iframe().code().to_owned()
476 }
477 fn f_lasti(&self) -> u32 {
478 let live = self.find_live_source_iframe();
482 let val = if !live.is_null() {
483 unsafe { (*live).lasti.load(Relaxed) }
484 } else {
485 self.lasti()
486 };
487 if val == 0 { 0 } else { (val - 1) * 2 }
488 }
489}
490
491#[pyclass(
492 itemsize = core::mem::size_of::<crate::PyObjectRef>(),
493 flags(DISALLOW_INSTANTIATION),
494 with(Py, Representable)
495)]
496impl FrameObject {}
497
498#[pyclass]
499impl Py<FrameObject> {
500 #[pymethod]
501 fn clear(&self, vm: &VirtualMachine) -> PyResult<()> {
503 if self.iframe().attached_tid() != 0 {
508 return Err(vm.new_runtime_error("cannot clear an executing frame"));
509 }
510 let owner = FrameOwner::from_i8(
511 self.iframe()
512 .owner
513 .load(core::sync::atomic::Ordering::Acquire),
514 );
515 match owner {
516 FrameOwner::Generator => {
517 if self.lasti() != 0 {
520 return Err(vm.new_runtime_error("cannot clear a suspended frame"));
521 }
522 if let Some(owner) = self.iframe().generator.to_owned() {
523 if let Some(coro) = owner.downcast_ref::<PyCoroutine>() {
524 let _ = PyCoroutine::del(coro, vm);
525 } else if let Some(async_gen) = owner.downcast_ref::<PyAsyncGen>() {
526 let _ = PyAsyncGen::del(async_gen, vm);
527 } else if let Some(generator) = owner.downcast_ref::<PyGenerator>() {
528 let _ = PyGenerator::del(generator, vm);
529 }
530 }
531 return Ok(());
532 }
533 FrameOwner::Thread => {
534 return Err(vm.new_runtime_error("cannot clear an executing frame"));
535 }
536 FrameOwner::FrameObject => {
537 if !self.find_live_source_iframe().is_null() {
538 return Err(vm.new_runtime_error("cannot clear an executing frame"));
539 }
540 }
541 }
542
543 let fastlocals = {
547 let slots = unsafe { self.fastlocals_mut() };
549 slots
550 .iter_mut()
551 .filter_map(Option::take)
552 .collect::<Vec<_>>()
553 };
554
555 self.clear_stack_and_cells();
557
558 let (temporary_refs, extra_locals, locals_cache, overwritten, retained_back) =
559 match self.iframe().cold_opt() {
560 Some(cold) => (
561 core::mem::take(&mut *cold.temporary_refs.lock()),
562 cold.f_extra_locals.lock().take(),
563 cold.f_locals_cache.lock().take(),
564 core::mem::take(&mut *cold.f_overwritten_fast_locals.lock()),
565 cold.retained_back.lock().take(),
566 ),
567 None => (Vec::new(), None, None, Vec::new(), None),
568 };
569 drop((
570 fastlocals,
571 temporary_refs,
572 extra_locals,
573 locals_cache,
574 overwritten,
575 retained_back,
576 ));
577
578 Ok(())
579 }
580
581 #[pygetset]
582 pub fn f_locals(&self, vm: &VirtualMachine) -> PyResult {
583 if self.uses_locals_proxy(vm)? {
584 let proxy = crate::builtins::FrameLocalsProxy::new(self.to_owned());
585 Ok(proxy.into_ref(&vm.ctx).into())
586 } else {
587 Ok(self.iframe().locals.clone_mapping(vm).into())
588 }
589 }
590
591 #[pygetset]
592 fn f_generator(&self) -> Option<PyObjectRef> {
593 self.iframe().generator.to_owned()
594 }
595
596 #[pygetset]
597 pub fn f_back(&self, #[allow(unused)] vm: &VirtualMachine) -> Option<PyRef<FrameObject>> {
598 let mut prev = self.previous_iframe();
599
600 if prev.is_null() {
603 let self_py_ptr = self as *const Self as usize;
606 let mut cur = crate::vm::thread::get_current_frame();
607 while !cur.is_null() {
608 let materialized = unsafe { (*cur).materialized.load(Relaxed) };
609 if materialized == self_py_ptr {
610 prev = unsafe { (*cur).previous() };
612 break;
613 }
614 cur = unsafe { (*cur).previous() };
615 }
616 if prev.is_null() {
617 let retained = self.iframe().cold().retained_back.lock().clone();
619 if let Some(frame) = retained {
620 return Some(frame);
621 }
622 return None;
623 }
624 }
625
626 {
630 let mut cur = crate::vm::thread::get_current_frame();
631 while !cur.is_null() {
632 if core::ptr::eq(cur, prev) {
633 let iframe_ref = unsafe { &*cur };
634 let fo = iframe_ref.materialize(vm);
635 return Some(fo.to_owned());
636 }
637 cur = unsafe { (*cur).previous() };
638 }
639 }
640
641 let retained = self.iframe().cold().retained_back.lock().clone();
643 if let Some(frame) = retained {
644 return Some(frame);
645 }
646
647 #[cfg(feature = "threading")]
650 {
651 vm.state.stop_the_world.stop_the_world(&vm.state);
654 scopeguard::defer! { vm.state.stop_the_world.start_the_world(&vm.state); }
655 let prev_ref = unsafe { &*prev };
656 if let Some(fo) = prev_ref.frame_obj() {
658 return Some(fo.to_owned());
659 }
660 let fo = unsafe { prev_ref.materialize_detached_chain(vm) };
663 return Some(fo);
664 }
665
666 #[allow(unreachable_code)]
667 None
668 }
669
670 #[pygetset]
671 pub fn f_globals(&self) -> PyDictRef {
672 self.iframe().globals().to_owned()
673 }
674
675 #[pygetset]
676 pub fn f_builtins(&self) -> PyObjectRef {
677 self.iframe().builtins().to_owned()
678 }
679
680 #[pygetset]
681 pub fn f_code(&self) -> PyRef<PyCode> {
682 self.payload.f_code()
683 }
684
685 #[pygetset]
686 fn f_lasti(&self) -> u32 {
687 self.payload.f_lasti()
688 }
689
690 #[pygetset]
691 fn f_lineno(&self) -> Option<usize> {
692 let lineno = self.lineno();
693 (lineno >= 0).then_some(lineno as usize)
694 }
695
696 #[pygetset(setter)]
697 fn set_f_lineno(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
698 let l_new_lineno = match value {
699 PySetterValue::Assign(val) => {
700 let line_ref: PyIntRef = val
701 .downcast()
702 .map_err(|_| vm.new_value_error("lineno must be an integer"))?;
703 line_ref
704 .try_to_primitive::<i32>(vm)
705 .map_err(|_| vm.new_value_error("lineno must be an integer"))?
706 }
707 PySetterValue::Delete => {
708 return Err(vm.new_attribute_error("cannot delete attribute"));
709 }
710 };
711
712 let Some(what_event) = vm.what_event.get() else {
713 return Err(
714 vm.new_value_error("f_lineno can only be set in a trace function".to_owned())
715 );
716 };
717 {
718 use crate::stdlib::sys::monitoring::MonitoringEvent as Ev;
719 match what_event {
720 Ev::PyResume
721 | Ev::Jump
722 | Ev::Branch
723 | Ev::BranchLeft
724 | Ev::BranchRight
725 | Ev::Line
726 | Ev::PyYield => {}
727 Ev::PyStart => {
728 return Err(vm.new_value_error(
729 "can't jump from the 'call' trace event of a new frame".to_owned(),
730 ));
731 }
732 Ev::Call | Ev::CReturn => {
733 return Err(vm.new_value_error("can't jump during a call".to_owned()));
734 }
735 Ev::PyReturn
736 | Ev::PyUnwind
737 | Ev::PyThrow
738 | Ev::Raise
739 | Ev::CRaise
740 | Ev::Instruction
741 | Ev::ExceptionHandled => {
742 return Err(
743 vm.new_value_error("can only jump from a 'line' trace event".to_owned())
744 );
745 }
746 Ev::StopIteration | Ev::Reraise => {
747 return Err(vm.new_system_error("unexpected event type".to_owned()));
748 }
749 }
750 }
751
752 let first_line = self
753 .iframe()
754 .code()
755 .first_line_number
756 .map_or(1, |n| n.get() as i32);
757
758 if l_new_lineno < first_line {
759 return Err(vm.new_value_error(format!(
760 "line {l_new_lineno} comes before the current code block"
761 )));
762 }
763
764 let py_code: &Py<PyCode> = self.iframe().code();
765 let code = &py_code.code;
766 let lines = mark_lines(code);
767
768 let new_lineno = first_line_not_before(&lines, l_new_lineno);
770 if new_lineno < 0 {
771 return Err(vm.new_value_error(format!(
772 "line {l_new_lineno} comes after the current code block"
773 )));
774 }
775
776 let stacks = mark_stacks(code);
777 let len = self.iframe().code().instructions.len();
778
779 let live = self.find_live_source_iframe();
785 let current_lasti = if !live.is_null() {
786 (unsafe { (*live).lasti.load(Relaxed) }) as usize
787 } else {
788 self.lasti() as usize
789 };
790 let start_idx = current_lasti.saturating_sub(1);
791 let mut start_stack = if start_idx < stacks.len() {
792 stacks[start_idx]
793 } else {
794 OVERFLOWED
795 };
796 let mut best_stack = OVERFLOWED;
797 let mut best_addr: i32 = -1;
798 let mut err: i32 = -1;
799 let mut msg = "cannot find bytecode for specified line";
800
801 for i in 0..len {
802 if lines[i] == new_lineno {
803 let target_stack = stacks[i];
804 if compatible_stack(start_stack, target_stack) {
805 err = 0;
806 if target_stack > best_stack {
807 best_stack = target_stack;
808 best_addr = i as i32;
809 }
810 } else if err < 0 {
811 if start_stack == OVERFLOWED {
812 msg = "stack to deep to analyze";
813 } else if start_stack == UNINITIALIZED {
814 msg = "can't jump from unreachable code";
815 } else {
816 msg = explain_incompatible_stack(target_stack);
817 err = 1;
818 }
819 }
820 }
821 }
822
823 if err != 0 {
824 return Err(vm.new_value_error(msg.to_owned()));
825 }
826
827 let is_suspended = live.is_null()
830 && current_lasti > 0
831 && matches!(
832 FrameOwner::from_i8(self.iframe().owner.load(Relaxed)),
833 FrameOwner::Generator
834 );
835 if is_suspended {
836 start_stack = pop_value(start_stack);
837 }
838
839 let mut pop_count = 0usize;
841 {
842 let mut s = start_stack;
843 while s > best_stack {
844 pop_count += 1;
845 s = pop_value(s);
846 }
847 }
848
849 let target = if !live.is_null() {
853 unsafe { &*live }
854 } else {
855 self.iframe()
856 };
857 target
858 .cold()
859 .pending_stack_pops
860 .store(pop_count as u32, Relaxed);
861 target
862 .cold()
863 .pending_unwind_from_stack
864 .store(start_stack, Relaxed);
865 let unbound = {
868 let fastlocals = unsafe {
869 let ptr = target as *const crate::frame::InterpreterFrame
870 as *mut crate::frame::InterpreterFrame;
871 (*ptr).localsplus.fastlocals()
872 };
873 fastlocals.iter().filter(|slot| slot.is_none()).count()
874 };
875 if unbound > 0 {
876 let s = if unbound == 1 { "" } else { "s" };
877 crate::stdlib::_warnings::warn(
878 vm.ctx.exceptions.runtime_warning,
879 format!("assigning None to {unbound} unbound local{s}"),
880 1,
881 vm,
882 )?;
883 let none = vm.ctx.none();
884 let fastlocals = unsafe {
885 let ptr = target as *const crate::frame::InterpreterFrame
886 as *mut crate::frame::InterpreterFrame;
887 (*ptr).localsplus.fastlocals_mut()
888 };
889 for slot in fastlocals.iter_mut() {
890 if slot.is_none() {
891 *slot = Some(none.clone());
892 }
893 }
894 }
895
896 target.lasti.store(best_addr as u32, Relaxed);
897 Ok(())
898 }
899
900 #[pygetset]
901 fn f_trace(&self, vm: &VirtualMachine) -> PyObjectRef {
902 let live = self.find_live_source_iframe();
904 let trace = if !live.is_null() {
905 unsafe { &*live }.cold().trace.lock().clone()
906 } else {
907 self.iframe().cold().trace.lock().clone()
908 };
909 trace.unwrap_or_else(|| vm.ctx.none())
910 }
911
912 #[pygetset(setter)]
913 fn set_f_trace(&self, value: PySetterValue, vm: &VirtualMachine) {
914 let trace = match value {
915 PySetterValue::Assign(v) => {
916 if vm.is_none(&v) {
917 None
918 } else {
919 Some(v)
920 }
921 }
922 PySetterValue::Delete => None,
923 };
924 let trace_is_some = trace.is_some();
938 let live = self.find_live_source_iframe();
939 let canonical = if !live.is_null() {
940 unsafe { &*live }
941 } else {
942 self.iframe()
943 };
944 let was_unset = canonical.cold().trace.lock().is_none();
945
946 (*self.iframe().cold().trace.lock()).clone_from(&trace);
948 if !live.is_null() {
952 *unsafe { &*live }.cold().trace.lock() = trace;
953 }
954
955 if was_unset && trace_is_some {
956 canonical.sync_prev_line_from_lasti();
957 }
958 }
959
960 #[pygetset]
961 fn f_trace_opcodes(&self, vm: &VirtualMachine) -> PyObjectRef {
962 let trace_opcodes = self.iframe().cold().trace_opcodes.lock();
963 vm.ctx.new_bool(*trace_opcodes).into()
964 }
965
966 #[pygetset(setter)]
967 fn set_f_trace_opcodes(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
968 match value {
969 PySetterValue::Assign(value) => {
970 let value: PyIntRef = value
971 .downcast()
972 .map_err(|_| vm.new_type_error("attribute value type must be bool"))?;
973
974 let val = !value.as_bigint().is_zero();
975 *self.iframe().cold().trace_opcodes.lock() = val;
976 let live = self.find_live_source_iframe();
978 if !live.is_null() {
979 *unsafe { &*live }.cold().trace_opcodes.lock() = val;
980 }
981
982 Ok(())
985 }
986 PySetterValue::Delete => Err(vm.new_type_error("can't delete numeric/char attribute")),
987 }
988 }
989}