1use super::{PyBytesRef, PyStrRef, PyTupleRef, PyType};
4use crate::common::lock::PyMutex;
5use crate::{
6 AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
7 builtins::PyStrInterned,
8 bytecode::{self, AsBag, BorrowedConstant, CodeFlags, Constant, ConstantBag, Instruction},
9 class::{PyClassImpl, StaticType},
10 convert::{ToPyException, ToPyObject},
11 frozen,
12 function::OptionalArg,
13 types::{Comparable, Constructor, Hashable, Representable},
14};
15use alloc::fmt;
16use core::{
17 borrow::Borrow,
18 ops::Deref,
19 sync::atomic::{AtomicPtr, AtomicU64, Ordering},
20};
21use malachite_bigint::BigInt;
22use num_traits::Zero;
23use rustpython_compiler_core::{OneIndexed, bytecode::CodeUnits, bytecode::PyCodeLocationInfoKind};
24
25struct PyCodeAddressRange<'a> {
27 ar_start: i32,
28 ar_end: i32,
29 ar_line: i32,
30 computed_line: i32,
31 reader: LineTableReader<'a>,
32}
33
34impl<'a> PyCodeAddressRange<'a> {
35 fn new(linetable: &'a [u8], first_line: i32) -> Self {
36 PyCodeAddressRange {
37 ar_start: 0,
38 ar_end: 0,
39 ar_line: -1,
40 computed_line: first_line,
41 reader: LineTableReader::new(linetable),
42 }
43 }
44
45 fn is_no_line_marker(byte: u8) -> bool {
47 (byte >> 3) == 0x1f
48 }
49
50 fn advance(&mut self) -> bool {
52 if self.reader.at_end() {
53 return false;
54 }
55
56 let first_byte = match self.reader.read_byte() {
57 Some(b) => b,
58 None => return false,
59 };
60
61 if (first_byte & 0x80) == 0 {
62 return false; }
64
65 let code = (first_byte >> 3) & 0x0f;
66 let length = ((first_byte & 0x07) + 1) as i32;
67
68 let line_delta = self.get_line_delta(code);
70
71 self.computed_line += line_delta;
73
74 if Self::is_no_line_marker(first_byte) {
76 self.ar_line = -1;
77 } else {
78 self.ar_line = self.computed_line;
79 }
80
81 self.ar_start = self.ar_end;
83 self.ar_end += length * 2; while !self.reader.at_end() {
87 if let Some(b) = self.reader.peek_byte() {
88 if (b & 0x80) != 0 {
89 break;
90 }
91 self.reader.read_byte();
92 } else {
93 break;
94 }
95 }
96
97 true
98 }
99
100 fn get_line_delta(&mut self, code: u8) -> i32 {
101 let kind = match PyCodeLocationInfoKind::from_code(code) {
102 Some(k) => k,
103 None => return 0,
104 };
105
106 match kind {
107 PyCodeLocationInfoKind::None => 0, PyCodeLocationInfoKind::Long => {
109 let delta = self.reader.read_signed_varint();
110 self.reader.read_varint();
112 self.reader.read_varint();
113 self.reader.read_varint();
114 delta
115 }
116 PyCodeLocationInfoKind::NoColumns => self.reader.read_signed_varint(),
117 PyCodeLocationInfoKind::OneLine0 => {
118 self.reader.read_byte(); self.reader.read_byte(); 0
121 }
122 PyCodeLocationInfoKind::OneLine1 => {
123 self.reader.read_byte(); self.reader.read_byte(); 1
126 }
127 PyCodeLocationInfoKind::OneLine2 => {
128 self.reader.read_byte(); self.reader.read_byte(); 2
131 }
132 _ if kind.is_short() => {
133 self.reader.read_byte(); 0
135 }
136 _ => 0,
137 }
138 }
139}
140
141#[derive(FromArgs)]
142pub struct ReplaceArgs {
143 #[pyarg(named, optional)]
144 co_posonlyargcount: OptionalArg<u32>,
145 #[pyarg(named, optional)]
146 co_argcount: OptionalArg<u32>,
147 #[pyarg(named, optional)]
148 co_kwonlyargcount: OptionalArg<u32>,
149 #[pyarg(named, optional)]
150 co_filename: OptionalArg<PyStrRef>,
151 #[pyarg(named, optional)]
152 co_firstlineno: OptionalArg<u32>,
153 #[pyarg(named, optional)]
154 co_consts: OptionalArg<Vec<PyObjectRef>>,
155 #[pyarg(named, optional)]
156 co_name: OptionalArg<PyStrRef>,
157 #[pyarg(named, optional)]
158 co_names: OptionalArg<Vec<PyObjectRef>>,
159 #[pyarg(named, optional)]
160 co_flags: OptionalArg<u32>,
161 #[pyarg(named, optional)]
162 co_varnames: OptionalArg<Vec<PyObjectRef>>,
163 #[pyarg(named, optional)]
164 co_nlocals: OptionalArg<u32>,
165 #[pyarg(named, optional)]
166 co_stacksize: OptionalArg<u32>,
167 #[pyarg(named, optional)]
168 co_code: OptionalArg<crate::builtins::PyBytesRef>,
169 #[pyarg(named, optional)]
170 co_linetable: OptionalArg<crate::builtins::PyBytesRef>,
171 #[pyarg(named, optional)]
172 co_exceptiontable: OptionalArg<crate::builtins::PyBytesRef>,
173 #[pyarg(named, optional)]
174 co_freevars: OptionalArg<Vec<PyObjectRef>>,
175 #[pyarg(named, optional)]
176 co_cellvars: OptionalArg<Vec<PyObjectRef>>,
177 #[pyarg(named, optional)]
178 co_qualname: OptionalArg<PyStrRef>,
179}
180
181#[derive(Clone)]
182#[repr(transparent)]
183pub struct Literal(PyObjectRef);
184
185impl Borrow<PyObject> for Literal {
186 fn borrow(&self) -> &PyObject {
187 &self.0
188 }
189}
190
191impl From<Literal> for PyObjectRef {
192 fn from(obj: Literal) -> Self {
193 obj.0
194 }
195}
196
197impl From<PyObjectRef> for Literal {
198 fn from(obj: PyObjectRef) -> Self {
199 Literal(obj)
200 }
201}
202
203fn borrow_obj_constant(obj: &PyObject) -> BorrowedConstant<'_, Literal> {
204 match_class!(match obj {
205 ref i @ super::int::PyInt => {
206 let value = i.as_bigint();
207 if obj.class().is(super::bool_::PyBool::static_type()) {
208 BorrowedConstant::Boolean {
209 value: !value.is_zero(),
210 }
211 } else {
212 BorrowedConstant::Integer { value }
213 }
214 }
215 ref f @ super::float::PyFloat => BorrowedConstant::Float { value: f.to_f64() },
216 ref c @ super::complex::PyComplex => BorrowedConstant::Complex {
217 value: c.to_complex()
218 },
219 ref s @ super::pystr::PyStr => BorrowedConstant::Str { value: s.as_wtf8() },
220 ref b @ super::bytes::PyBytes => BorrowedConstant::Bytes {
221 value: b.as_bytes()
222 },
223 ref c @ PyCode => {
224 BorrowedConstant::Code { code: &c.code }
225 }
226 ref t @ super::tuple::PyTuple => {
227 let elements = t.as_slice();
228 let elements = unsafe { &*(elements as *const [PyObjectRef] as *const [Literal]) };
231 BorrowedConstant::Tuple { elements }
232 }
233 super::singletons::PyNone => BorrowedConstant::None,
234 super::slice::PyEllipsis => BorrowedConstant::Ellipsis,
235 ref s @ super::slice::PySlice => {
236 let start = s.start.clone().unwrap();
240 let stop = s.stop.clone();
241 let step = s.step.clone().unwrap();
242 let arr = Box::leak(Box::new([Literal(start), Literal(stop), Literal(step)]));
243 BorrowedConstant::Slice { elements: arr }
244 }
245 ref fs @ super::set::PyFrozenSet => {
246 let elems: Vec<Literal> = fs.elements().into_iter().map(Literal).collect();
249 let elements = Box::leak(elems.into_boxed_slice());
250 BorrowedConstant::Frozenset { elements }
251 }
252 _ => panic!("unexpected payload for constant python value"),
253 })
254}
255
256impl Constant for Literal {
257 type Name = &'static PyStrInterned;
258 fn borrow_constant(&self) -> BorrowedConstant<'_, Self> {
259 borrow_obj_constant(&self.0)
260 }
261}
262
263impl<'a> AsBag for &'a Context {
264 type Bag = PyObjBag<'a>;
265 fn as_bag(self) -> PyObjBag<'a> {
266 PyObjBag(self)
267 }
268}
269
270impl<'a> AsBag for &'a VirtualMachine {
271 type Bag = PyObjBag<'a>;
272 fn as_bag(self) -> PyObjBag<'a> {
273 PyObjBag(&self.ctx)
274 }
275}
276
277#[derive(Clone, Copy)]
278pub struct PyObjBag<'a>(pub &'a Context);
279
280impl ConstantBag for PyObjBag<'_> {
281 type Constant = Literal;
282
283 fn make_constant<C: Constant>(&self, constant: BorrowedConstant<'_, C>) -> Self::Constant {
284 let ctx = self.0;
285 let obj = match constant {
286 BorrowedConstant::Integer { value } => ctx.new_bigint(value).into(),
287 BorrowedConstant::Float { value } => ctx.new_float(value).into(),
288 BorrowedConstant::Complex { value } => ctx.new_complex(value).into(),
289 BorrowedConstant::Str { value } if value.len() <= 20 => {
290 ctx.intern_str(value).to_object()
291 }
292 BorrowedConstant::Str { value } => ctx.new_str(value).into(),
293 BorrowedConstant::Bytes { value } => ctx.new_bytes(value.to_vec()).into(),
294 BorrowedConstant::Boolean { value } => ctx.new_bool(value).into(),
295 BorrowedConstant::Code { code } => ctx.new_code(code.map_clone_bag(self)).into(),
296 BorrowedConstant::Tuple { elements } => {
297 let elements = elements
298 .iter()
299 .map(|constant| self.make_constant(constant.borrow_constant()).0)
300 .collect();
301 ctx.new_tuple(elements).into()
302 }
303 BorrowedConstant::Slice { elements } => {
304 let [start, stop, step] = elements;
305 let start_obj = self.make_constant(start.borrow_constant()).0;
306 let stop_obj = self.make_constant(stop.borrow_constant()).0;
307 let step_obj = self.make_constant(step.borrow_constant()).0;
308 use crate::builtins::PySlice;
311 PySlice {
312 start: Some(start_obj),
313 stop: stop_obj,
314 step: Some(step_obj),
315 }
316 .into_ref(ctx)
317 .into()
318 }
319 BorrowedConstant::Frozenset { elements: _ } => {
320 unimplemented!(
324 "frozenset constant in PyObjBag::make_constant requires VirtualMachine"
325 )
326 }
327 BorrowedConstant::None => ctx.none(),
328 BorrowedConstant::Ellipsis => ctx.ellipsis.clone().into(),
329 };
330
331 Literal(obj)
332 }
333
334 fn make_name(&self, name: &str) -> &'static PyStrInterned {
335 self.0.intern_str(name)
336 }
337
338 fn make_int(&self, value: BigInt) -> Self::Constant {
339 Literal(self.0.new_int(value).into())
340 }
341
342 fn make_tuple(&self, elements: impl Iterator<Item = Self::Constant>) -> Self::Constant {
343 Literal(self.0.new_tuple(elements.map(|lit| lit.0).collect()).into())
344 }
345
346 fn make_code(&self, code: CodeObject) -> Self::Constant {
347 Literal(self.0.new_code(code).into())
348 }
349}
350
351pub type CodeObject = bytecode::CodeObject<Literal>;
352
353pub trait IntoCodeObject {
354 fn into_code_object(self, ctx: &Context) -> CodeObject;
355}
356
357impl IntoCodeObject for CodeObject {
358 fn into_code_object(self, _ctx: &Context) -> Self {
359 self
360 }
361}
362
363impl IntoCodeObject for bytecode::CodeObject {
364 fn into_code_object(self, ctx: &Context) -> CodeObject {
365 self.map_bag(PyObjBag(ctx))
366 }
367}
368
369impl<B: AsRef<[u8]>> IntoCodeObject for frozen::FrozenCodeObject<B> {
370 fn into_code_object(self, ctx: &Context) -> CodeObject {
371 self.decode(ctx)
372 }
373}
374
375pub struct CoMonitoringData {
378 pub line_opcodes: Vec<u8>,
381
382 pub per_instruction_opcodes: Vec<u8>,
385}
386
387#[pyclass(module = false, name = "code")]
388pub struct PyCode {
389 pub code: CodeObject,
390 source_path: AtomicPtr<PyStrInterned>,
391 pub instrumentation_version: AtomicU64,
394 pub monitoring_data: PyMutex<Option<CoMonitoringData>>,
396 pub quickened: core::sync::atomic::AtomicBool,
398}
399
400impl Deref for PyCode {
401 type Target = CodeObject;
402 fn deref(&self) -> &Self::Target {
403 &self.code
404 }
405}
406
407impl PyCode {
408 pub fn new(code: CodeObject) -> Self {
409 let sp = code.source_path as *const PyStrInterned as *mut PyStrInterned;
410 Self {
411 code,
412 source_path: AtomicPtr::new(sp),
413 instrumentation_version: AtomicU64::new(0),
414 monitoring_data: PyMutex::new(None),
415 quickened: core::sync::atomic::AtomicBool::new(false),
416 }
417 }
418
419 pub fn source_path(&self) -> &'static PyStrInterned {
420 unsafe { &*self.source_path.load(Ordering::Relaxed) }
422 }
423
424 pub fn set_source_path(&self, new: &'static PyStrInterned) {
425 self.source_path.store(
426 new as *const PyStrInterned as *mut PyStrInterned,
427 Ordering::Relaxed,
428 );
429 }
430 pub fn from_pyc_path(path: &std::path::Path, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
431 let name = match path.file_stem() {
432 Some(stem) => stem.display().to_string(),
433 None => "".to_owned(),
434 };
435 let content = std::fs::read(path).map_err(|e| e.to_pyexception(vm))?;
436 Self::from_pyc(
437 &content,
438 Some(&name),
439 Some(&path.display().to_string()),
440 Some("<source>"),
441 vm,
442 )
443 }
444 pub fn from_pyc(
445 pyc_bytes: &[u8],
446 name: Option<&str>,
447 bytecode_path: Option<&str>,
448 source_path: Option<&str>,
449 vm: &VirtualMachine,
450 ) -> PyResult<PyRef<Self>> {
451 if !crate::import::check_pyc_magic_number_bytes(pyc_bytes) {
452 return Err(vm.new_value_error("pyc bytes has wrong MAGIC"));
453 }
454 let bootstrap_external = vm.import("_frozen_importlib_external", 0)?;
455 let compile_bytecode = bootstrap_external.get_attr("_compile_bytecode", vm)?;
456 let Some((_, code_bytes)) = pyc_bytes.split_at_checked(16) else {
458 return Err(vm.new_value_error(format!(
459 "pyc_bytes header is broken. 16 bytes expected but {} bytes given.",
460 pyc_bytes.len()
461 )));
462 };
463 let code_bytes_obj = vm.ctx.new_bytes(code_bytes.to_vec());
464 let compiled =
465 compile_bytecode.call((code_bytes_obj, name, bytecode_path, source_path), vm)?;
466 compiled.try_downcast(vm)
467 }
468}
469
470impl fmt::Debug for PyCode {
471 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472 write!(f, "code: {:?}", self.code)
473 }
474}
475
476impl PyPayload for PyCode {
477 #[inline]
478 fn class(ctx: &Context) -> &'static Py<PyType> {
479 ctx.types.code_type
480 }
481}
482
483impl Representable for PyCode {
484 #[inline]
485 fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
486 let code = &zelf.code;
487 Ok(format!(
488 "<code object {} at {:#x} file {:?}, line {}>",
489 code.obj_name,
490 zelf.get_id(),
491 zelf.source_path().as_str(),
492 code.first_line_number.map_or(-1, |n| n.get() as i32)
493 ))
494 }
495}
496
497impl Comparable for PyCode {
498 fn cmp(
499 zelf: &Py<Self>,
500 other: &PyObject,
501 op: crate::types::PyComparisonOp,
502 vm: &VirtualMachine,
503 ) -> PyResult<crate::function::PyComparisonValue> {
504 op.eq_only(|| {
505 let other = class_or_notimplemented!(Self, other);
506 let a = &zelf.code;
507 let b = &other.code;
508 let eq = a.obj_name == b.obj_name
509 && a.arg_count == b.arg_count
510 && a.posonlyarg_count == b.posonlyarg_count
511 && a.kwonlyarg_count == b.kwonlyarg_count
512 && a.flags == b.flags
513 && a.first_line_number == b.first_line_number
514 && a.instructions.original_bytes() == b.instructions.original_bytes()
515 && a.linetable == b.linetable
516 && a.exceptiontable == b.exceptiontable
517 && a.names == b.names
518 && a.varnames == b.varnames
519 && a.freevars == b.freevars
520 && a.cellvars == b.cellvars
521 && {
522 let a_consts: Vec<_> = a.constants.iter().map(|c| c.0.clone()).collect();
523 let b_consts: Vec<_> = b.constants.iter().map(|c| c.0.clone()).collect();
524 if a_consts.len() != b_consts.len() {
525 false
526 } else {
527 let mut eq = true;
528 for (ac, bc) in a_consts.iter().zip(b_consts.iter()) {
529 if !vm.bool_eq(ac, bc)? {
530 eq = false;
531 break;
532 }
533 }
534 eq
535 }
536 };
537 Ok(eq.into())
538 })
539 }
540}
541
542impl Hashable for PyCode {
543 fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<crate::common::hash::PyHash> {
544 let code = &zelf.code;
545 let tuple = vm.ctx.new_tuple(vec![
547 vm.ctx.new_str(code.obj_name.as_str()).into(),
548 vm.ctx.new_int(code.arg_count).into(),
549 vm.ctx.new_int(code.posonlyarg_count).into(),
550 vm.ctx.new_int(code.kwonlyarg_count).into(),
551 vm.ctx.new_int(code.varnames.len()).into(),
552 vm.ctx.new_int(code.flags.bits()).into(),
553 vm.ctx
554 .new_int(code.first_line_number.map_or(0, |n| n.get()) as i64)
555 .into(),
556 vm.ctx.new_bytes(code.instructions.original_bytes()).into(),
557 {
558 let consts: Vec<_> = code.constants.iter().map(|c| c.0.clone()).collect();
559 vm.ctx.new_tuple(consts).into()
560 },
561 ]);
562 tuple.as_object().hash(vm)
563 }
564}
565
566#[derive(FromArgs)]
568pub struct PyCodeNewArgs {
569 argcount: u32,
570 posonlyargcount: u32,
571 kwonlyargcount: u32,
572 nlocals: u32,
573 stacksize: u32,
574 flags: u32,
575 co_code: PyBytesRef,
576 consts: PyTupleRef,
577 names: PyTupleRef,
578 varnames: PyTupleRef,
579 filename: PyStrRef,
580 name: PyStrRef,
581 qualname: PyStrRef,
582 firstlineno: i32,
583 linetable: PyBytesRef,
584 exceptiontable: PyBytesRef,
585 freevars: PyTupleRef,
586 cellvars: PyTupleRef,
587}
588
589impl Constructor for PyCode {
590 type Args = PyCodeNewArgs;
591
592 fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
593 let names: Box<[&'static PyStrInterned]> = args
595 .names
596 .iter()
597 .map(|obj| {
598 let s = obj
599 .downcast_ref::<super::pystr::PyStr>()
600 .ok_or_else(|| vm.new_type_error("names must be tuple of strings"))?;
601 Ok(vm.ctx.intern_str(s.as_wtf8()))
602 })
603 .collect::<PyResult<Vec<_>>>()?
604 .into_boxed_slice();
605
606 let varnames: Box<[&'static PyStrInterned]> = args
607 .varnames
608 .iter()
609 .map(|obj| {
610 let s = obj
611 .downcast_ref::<super::pystr::PyStr>()
612 .ok_or_else(|| vm.new_type_error("varnames must be tuple of strings"))?;
613 Ok(vm.ctx.intern_str(s.as_wtf8()))
614 })
615 .collect::<PyResult<Vec<_>>>()?
616 .into_boxed_slice();
617
618 let cellvars: Box<[&'static PyStrInterned]> = args
619 .cellvars
620 .iter()
621 .map(|obj| {
622 let s = obj
623 .downcast_ref::<super::pystr::PyStr>()
624 .ok_or_else(|| vm.new_type_error("cellvars must be tuple of strings"))?;
625 Ok(vm.ctx.intern_str(s.as_wtf8()))
626 })
627 .collect::<PyResult<Vec<_>>>()?
628 .into_boxed_slice();
629
630 let freevars: Box<[&'static PyStrInterned]> = args
631 .freevars
632 .iter()
633 .map(|obj| {
634 let s = obj
635 .downcast_ref::<super::pystr::PyStr>()
636 .ok_or_else(|| vm.new_type_error("freevars must be tuple of strings"))?;
637 Ok(vm.ctx.intern_str(s.as_wtf8()))
638 })
639 .collect::<PyResult<Vec<_>>>()?
640 .into_boxed_slice();
641
642 if args.nlocals as usize != varnames.len() {
644 return Err(vm.new_value_error(format!(
645 "nlocals ({}) != len(varnames) ({})",
646 args.nlocals,
647 varnames.len()
648 )));
649 }
650
651 let bytecode_bytes = args.co_code.as_bytes();
653 let instructions = CodeUnits::try_from(bytecode_bytes)
654 .map_err(|e| vm.new_value_error(format!("invalid bytecode: {}", e)))?;
655
656 let constants = args
658 .consts
659 .iter()
660 .map(|obj| {
661 Literal(obj.clone())
663 })
664 .collect();
665
666 let row = if args.firstlineno > 0 {
668 OneIndexed::new(args.firstlineno as usize).unwrap_or(OneIndexed::MIN)
669 } else {
670 OneIndexed::MIN
671 };
672 let loc = rustpython_compiler_core::SourceLocation {
673 line: row,
674 character_offset: OneIndexed::from_zero_indexed(0),
675 };
676 let locations: Box<
677 [(
678 rustpython_compiler_core::SourceLocation,
679 rustpython_compiler_core::SourceLocation,
680 )],
681 > = vec![(loc, loc); instructions.len()].into_boxed_slice();
682
683 let localspluskinds = {
685 use rustpython_compiler_core::bytecode::*;
686 let nlocals = varnames.len();
687 let ncells = cellvars.len();
688 let nfrees = freevars.len();
689 let numdropped = cellvars
690 .iter()
691 .filter(|cv| varnames.iter().any(|v| *v == **cv))
692 .count();
693 let nlocalsplus = nlocals + ncells - numdropped + nfrees;
694 let mut kinds = vec![0u8; nlocalsplus];
695 for kind in kinds.iter_mut().take(nlocals) {
696 *kind = CO_FAST_LOCAL;
697 }
698 let mut cell_numdropped = 0usize;
699 for (i, cv) in cellvars.iter().enumerate() {
700 let merged_idx = varnames.iter().position(|v| **v == **cv);
701 if let Some(local_idx) = merged_idx {
702 kinds[local_idx] |= CO_FAST_CELL;
703 cell_numdropped += 1;
704 } else {
705 kinds[nlocals + i - cell_numdropped] = CO_FAST_CELL;
706 }
707 }
708 let free_start = nlocals + ncells - numdropped;
709 for i in 0..nfrees {
710 kinds[free_start + i] = CO_FAST_FREE;
711 }
712 kinds.into_boxed_slice()
713 };
714
715 let code = CodeObject {
717 instructions,
718 locations,
719 flags: CodeFlags::from_bits_truncate(args.flags),
720 posonlyarg_count: args.posonlyargcount,
721 arg_count: args.argcount,
722 kwonlyarg_count: args.kwonlyargcount,
723 source_path: vm.ctx.intern_str(args.filename.as_wtf8()),
724 first_line_number: if args.firstlineno > 0 {
725 OneIndexed::new(args.firstlineno as usize)
726 } else {
727 None
728 },
729 max_stackdepth: args.stacksize,
730 obj_name: vm.ctx.intern_str(args.name.as_wtf8()),
731 qualname: vm.ctx.intern_str(args.qualname.as_wtf8()),
732 constants,
733 names,
734 varnames,
735 cellvars,
736 freevars,
737 localspluskinds,
738 linetable: args.linetable.as_bytes().to_vec().into_boxed_slice(),
739 exceptiontable: args.exceptiontable.as_bytes().to_vec().into_boxed_slice(),
740 };
741
742 Ok(PyCode::new(code))
743 }
744}
745
746#[pyclass(
747 with(Representable, Constructor, Comparable, Hashable),
748 flags(HAS_WEAKREF)
749)]
750impl PyCode {
751 #[pygetset]
752 const fn co_posonlyargcount(&self) -> usize {
753 self.code.posonlyarg_count as usize
754 }
755
756 #[pygetset]
757 const fn co_argcount(&self) -> usize {
758 self.code.arg_count as usize
759 }
760
761 #[pygetset]
762 const fn co_stacksize(&self) -> u32 {
763 self.code.max_stackdepth
764 }
765
766 #[pygetset]
767 pub fn co_filename(&self) -> PyStrRef {
768 self.source_path().to_owned()
769 }
770
771 #[pygetset]
772 pub fn co_cellvars(&self, vm: &VirtualMachine) -> PyTupleRef {
773 let cellvars = self
774 .cellvars
775 .iter()
776 .map(|name| name.to_pyobject(vm))
777 .collect();
778 vm.ctx.new_tuple(cellvars)
779 }
780
781 #[pygetset]
782 fn co_nlocals(&self) -> usize {
783 self.code.varnames.len()
784 }
785
786 #[pygetset]
787 fn co_firstlineno(&self) -> u32 {
788 self.code.first_line_number.map_or(0, |n| n.get() as _)
789 }
790
791 #[pygetset]
792 const fn co_kwonlyargcount(&self) -> usize {
793 self.code.kwonlyarg_count as usize
794 }
795
796 #[pygetset]
797 fn co_consts(&self, vm: &VirtualMachine) -> PyTupleRef {
798 let consts = self.code.constants.iter().map(|x| x.0.clone()).collect();
799 vm.ctx.new_tuple(consts)
800 }
801
802 #[pygetset]
803 fn co_name(&self) -> PyStrRef {
804 self.code.obj_name.to_owned()
805 }
806 #[pygetset]
807 fn co_qualname(&self) -> PyStrRef {
808 self.code.qualname.to_owned()
809 }
810
811 #[pygetset]
812 fn co_names(&self, vm: &VirtualMachine) -> PyTupleRef {
813 let names = self
814 .code
815 .names
816 .deref()
817 .iter()
818 .map(|name| name.to_pyobject(vm))
819 .collect();
820 vm.ctx.new_tuple(names)
821 }
822
823 #[pygetset]
824 const fn co_flags(&self) -> u32 {
825 self.code.flags.bits()
826 }
827
828 #[pygetset]
829 pub fn co_varnames(&self, vm: &VirtualMachine) -> PyTupleRef {
830 let varnames = self.code.varnames.iter().map(|s| s.to_object()).collect();
831 vm.ctx.new_tuple(varnames)
832 }
833
834 #[pygetset]
835 pub fn co_code(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
836 vm.ctx.new_bytes(self.code.instructions.original_bytes())
837 }
838
839 #[pygetset]
840 pub fn _co_code_adaptive(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
841 let bytes = unsafe {
843 core::slice::from_raw_parts(
844 self.code.instructions.as_ptr() as *const u8,
845 self.code.instructions.len() * 2,
846 )
847 };
848 vm.ctx.new_bytes(bytes.to_vec())
849 }
850
851 #[pygetset]
852 pub fn co_freevars(&self, vm: &VirtualMachine) -> PyTupleRef {
853 let names = self
854 .code
855 .freevars
856 .deref()
857 .iter()
858 .map(|name| name.to_pyobject(vm))
859 .collect();
860 vm.ctx.new_tuple(names)
861 }
862
863 #[pygetset]
864 pub fn co_linetable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
865 vm.ctx.new_bytes(self.code.linetable.to_vec())
867 }
868
869 #[pygetset]
870 pub fn co_exceptiontable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
871 vm.ctx.new_bytes(self.code.exceptiontable.to_vec())
873 }
874
875 #[pymethod]
881 pub fn co_lines(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
882 let linetable = self.code.linetable.as_ref();
887 let mut lines = Vec::new();
888
889 if !linetable.is_empty() {
890 let first_line = self.code.first_line_number.map_or(0, |n| n.get() as i32);
891 let mut range = PyCodeAddressRange::new(linetable, first_line);
892
893 let mut pending_entry: Option<(i32, i32, i32)> = None;
895
896 while range.advance() {
897 let start = range.ar_start;
898 let end = range.ar_end;
899 let line = range.ar_line;
900
901 if let Some((prev_start, _, prev_line)) = pending_entry {
902 if prev_line == line {
903 pending_entry = Some((prev_start, end, prev_line));
905 } else {
906 let tuple = if prev_line == -1 {
908 vm.ctx.new_tuple(vec![
909 vm.ctx.new_int(prev_start).into(),
910 vm.ctx.new_int(start).into(),
911 vm.ctx.none(),
912 ])
913 } else {
914 vm.ctx.new_tuple(vec![
915 vm.ctx.new_int(prev_start).into(),
916 vm.ctx.new_int(start).into(),
917 vm.ctx.new_int(prev_line).into(),
918 ])
919 };
920 lines.push(tuple.into());
921 pending_entry = Some((start, end, line));
922 }
923 } else {
924 pending_entry = Some((start, end, line));
926 }
927 }
928
929 if let Some((start, end, line)) = pending_entry {
931 let tuple = if line == -1 {
932 vm.ctx.new_tuple(vec![
933 vm.ctx.new_int(start).into(),
934 vm.ctx.new_int(end).into(),
935 vm.ctx.none(),
936 ])
937 } else {
938 vm.ctx.new_tuple(vec![
939 vm.ctx.new_int(start).into(),
940 vm.ctx.new_int(end).into(),
941 vm.ctx.new_int(line).into(),
942 ])
943 };
944 lines.push(tuple.into());
945 }
946 }
947
948 let list = vm.ctx.new_list(lines);
949 vm.call_method(list.as_object(), "__iter__", ())
950 }
951
952 #[pymethod]
953 pub fn co_positions(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
954 let linetable = self.code.linetable.as_ref();
956 let mut positions = Vec::new();
957
958 if !linetable.is_empty() {
959 let mut reader = LineTableReader::new(linetable);
960 let mut line = self.code.first_line_number.map_or(0, |n| n.get() as i32);
961
962 while !reader.at_end() {
963 let first_byte = match reader.read_byte() {
964 Some(b) => b,
965 None => break,
966 };
967
968 if (first_byte & 0x80) == 0 {
969 break; }
971
972 let code = (first_byte >> 3) & 0x0f;
973 let length = ((first_byte & 0x07) + 1) as i32;
974
975 let kind = match PyCodeLocationInfoKind::from_code(code) {
976 Some(k) => k,
977 None => break, };
979
980 let (line_delta, end_line_delta, column, end_column): (
981 i32,
982 i32,
983 Option<i32>,
984 Option<i32>,
985 ) = match kind {
986 PyCodeLocationInfoKind::None => {
987 (0, 0, None, None)
989 }
990 PyCodeLocationInfoKind::Long => {
991 let delta = reader.read_signed_varint();
993 let end_line_delta = reader.read_varint() as i32;
994
995 let col = reader.read_varint();
996 let column = if col == 0 {
997 None
998 } else {
999 Some((col - 1) as i32)
1000 };
1001
1002 let end_col = reader.read_varint();
1003 let end_column = if end_col == 0 {
1004 None
1005 } else {
1006 Some((end_col - 1) as i32)
1007 };
1008
1009 (delta, end_line_delta, column, end_column)
1011 }
1012 PyCodeLocationInfoKind::NoColumns => {
1013 let delta = reader.read_signed_varint();
1015 (delta, 0, None, None) }
1017 PyCodeLocationInfoKind::OneLine0
1018 | PyCodeLocationInfoKind::OneLine1
1019 | PyCodeLocationInfoKind::OneLine2 => {
1020 let col = reader.read_byte().unwrap_or(0) as i32;
1022 let end_col = reader.read_byte().unwrap_or(0) as i32;
1023 let delta = kind.one_line_delta().unwrap_or(0);
1024 (delta, 0, Some(col), Some(end_col)) }
1026 _ if kind.is_short() => {
1027 let col_data = reader.read_byte().unwrap_or(0);
1029 let col_group = kind.short_column_group().unwrap_or(0);
1030 let col = ((col_group as i32) << 3) | ((col_data >> 4) as i32);
1031 let end_col = col + (col_data & 0x0f) as i32;
1032 (0, 0, Some(col), Some(end_col)) }
1034 _ => (0, 0, None, None),
1035 };
1036
1037 line += line_delta;
1039
1040 for _ in 0..length {
1042 let final_line = if kind == PyCodeLocationInfoKind::None {
1044 None
1045 } else {
1046 Some(line)
1047 };
1048
1049 let final_endline = if kind == PyCodeLocationInfoKind::None {
1050 None
1051 } else {
1052 Some(line + end_line_delta)
1053 };
1054
1055 let line_obj = final_line.to_pyobject(vm);
1056 let end_line_obj = final_endline.to_pyobject(vm);
1057 let column_obj = column.to_pyobject(vm);
1058 let end_column_obj = end_column.to_pyobject(vm);
1059
1060 let tuple =
1061 vm.ctx
1062 .new_tuple(vec![line_obj, end_line_obj, column_obj, end_column_obj]);
1063 positions.push(tuple.into());
1064 }
1065 }
1066 }
1067
1068 let list = vm.ctx.new_list(positions);
1069 vm.call_method(list.as_object(), "__iter__", ())
1070 }
1071
1072 #[pymethod]
1073 pub fn co_branches(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
1074 let instructions = &self.code.instructions;
1075 let mut branches = Vec::new();
1076 let mut extended_arg: u32 = 0;
1077
1078 for (i, unit) in instructions.iter().enumerate() {
1079 let op = unit.op.to_base().unwrap_or(unit.op);
1081 let raw_arg = u32::from(u8::from(unit.arg));
1082
1083 if matches!(op, Instruction::ExtendedArg) {
1084 extended_arg = (extended_arg | raw_arg) << 8;
1085 continue;
1086 }
1087
1088 let oparg = extended_arg | raw_arg;
1089 extended_arg = 0;
1090
1091 let caches = op.cache_entries();
1092 let (src, left, right) = match op {
1093 Instruction::ForIter { .. } => {
1094 let after_cache = i + 1 + caches;
1098 let target = after_cache + oparg as usize;
1099 let right = if matches!(
1100 instructions.get(target).map(|u| u.op),
1101 Some(Instruction::EndFor) | Some(Instruction::InstrumentedEndFor)
1102 ) {
1103 (target + 1) * 2
1104 } else {
1105 target * 2
1106 };
1107 (i * 2, after_cache * 2, right)
1108 }
1109 Instruction::PopJumpIfFalse { .. }
1110 | Instruction::PopJumpIfTrue { .. }
1111 | Instruction::PopJumpIfNone { .. }
1112 | Instruction::PopJumpIfNotNone { .. } => {
1113 let after_cache = i + 1 + caches;
1116 let next_op = instructions
1117 .get(after_cache)
1118 .map(|u| u.op.to_base().unwrap_or(u.op));
1119 let fallthrough = if matches!(next_op, Some(Instruction::NotTaken)) {
1120 (after_cache + 1) * 2
1121 } else {
1122 after_cache * 2
1123 };
1124 let right_target = after_cache + oparg as usize;
1125 (i * 2, fallthrough, right_target * 2)
1126 }
1127 Instruction::EndAsyncFor => {
1128 let next_i = i + 1;
1130 let Some(src_i) = next_i.checked_sub(oparg as usize) else {
1131 continue;
1132 };
1133 (src_i * 2, (src_i + 2) * 2, next_i * 2)
1135 }
1136 _ => continue,
1137 };
1138
1139 let tuple = vm.ctx.new_tuple(vec![
1140 vm.ctx.new_int(src).into(),
1141 vm.ctx.new_int(left).into(),
1142 vm.ctx.new_int(right).into(),
1143 ]);
1144 branches.push(tuple.into());
1145 }
1146
1147 let list = vm.ctx.new_list(branches);
1148 vm.call_method(list.as_object(), "__iter__", ())
1149 }
1150
1151 #[pymethod]
1152 pub fn __replace__(&self, args: ReplaceArgs, vm: &VirtualMachine) -> PyResult<Self> {
1153 self.replace(args, vm)
1154 }
1155
1156 #[pymethod]
1157 pub fn replace(&self, args: ReplaceArgs, vm: &VirtualMachine) -> PyResult<Self> {
1158 let ReplaceArgs {
1159 co_posonlyargcount,
1160 co_argcount,
1161 co_kwonlyargcount,
1162 co_filename,
1163 co_firstlineno,
1164 co_consts,
1165 co_name,
1166 co_names,
1167 co_flags,
1168 co_varnames,
1169 co_nlocals,
1170 co_stacksize,
1171 co_code,
1172 co_linetable,
1173 co_exceptiontable,
1174 co_freevars,
1175 co_cellvars,
1176 co_qualname,
1177 } = args;
1178 let posonlyarg_count = match co_posonlyargcount {
1179 OptionalArg::Present(posonlyarg_count) => posonlyarg_count,
1180 OptionalArg::Missing => self.code.posonlyarg_count,
1181 };
1182
1183 let arg_count = match co_argcount {
1184 OptionalArg::Present(arg_count) => arg_count,
1185 OptionalArg::Missing => self.code.arg_count,
1186 };
1187
1188 let source_path = match co_filename {
1189 OptionalArg::Present(source_path) => source_path,
1190 OptionalArg::Missing => self.source_path().to_owned(),
1191 };
1192
1193 let first_line_number = match co_firstlineno {
1194 OptionalArg::Present(first_line_number) => OneIndexed::new(first_line_number as _),
1195 OptionalArg::Missing => self.code.first_line_number,
1196 };
1197
1198 let kwonlyarg_count = match co_kwonlyargcount {
1199 OptionalArg::Present(kwonlyarg_count) => kwonlyarg_count,
1200 OptionalArg::Missing => self.code.kwonlyarg_count,
1201 };
1202
1203 let constants = match co_consts {
1204 OptionalArg::Present(constants) => constants,
1205 OptionalArg::Missing => self.code.constants.iter().map(|x| x.0.clone()).collect(),
1206 };
1207
1208 let obj_name = match co_name {
1209 OptionalArg::Present(obj_name) => obj_name,
1210 OptionalArg::Missing => self.code.obj_name.to_owned(),
1211 };
1212
1213 let names = match co_names {
1214 OptionalArg::Present(names) => names,
1215 OptionalArg::Missing => self
1216 .code
1217 .names
1218 .deref()
1219 .iter()
1220 .map(|name| name.to_pyobject(vm))
1221 .collect(),
1222 };
1223
1224 let flags = match co_flags {
1225 OptionalArg::Present(flags) => flags,
1226 OptionalArg::Missing => self.code.flags.bits(),
1227 };
1228
1229 let varnames = match co_varnames {
1230 OptionalArg::Present(varnames) => varnames,
1231 OptionalArg::Missing => self.code.varnames.iter().map(|s| s.to_object()).collect(),
1232 };
1233
1234 let qualname = match co_qualname {
1235 OptionalArg::Present(qualname) => qualname,
1236 OptionalArg::Missing => self.code.qualname.to_owned(),
1237 };
1238
1239 let max_stackdepth = match co_stacksize {
1240 OptionalArg::Present(stacksize) => stacksize,
1241 OptionalArg::Missing => self.code.max_stackdepth,
1242 };
1243
1244 let instructions = match co_code {
1245 OptionalArg::Present(code_bytes) => {
1246 CodeUnits::try_from(code_bytes.as_bytes())
1248 .map_err(|e| vm.new_value_error(format!("invalid bytecode: {}", e)))?
1249 }
1250 OptionalArg::Missing => self.code.instructions.clone(),
1251 };
1252
1253 let cellvars = match co_cellvars {
1254 OptionalArg::Present(cellvars) => cellvars
1255 .into_iter()
1256 .map(|o| o.as_interned_str(vm).unwrap())
1257 .collect(),
1258 OptionalArg::Missing => self.code.cellvars.clone(),
1259 };
1260
1261 let freevars = match co_freevars {
1262 OptionalArg::Present(freevars) => freevars
1263 .into_iter()
1264 .map(|o| o.as_interned_str(vm).unwrap())
1265 .collect(),
1266 OptionalArg::Missing => self.code.freevars.clone(),
1267 };
1268
1269 if let OptionalArg::Present(nlocals) = co_nlocals
1271 && nlocals as usize != varnames.len()
1272 {
1273 return Err(vm.new_value_error(format!(
1274 "co_nlocals ({}) != len(co_varnames) ({})",
1275 nlocals,
1276 varnames.len()
1277 )));
1278 }
1279
1280 let linetable = match co_linetable {
1282 OptionalArg::Present(linetable) => linetable.as_bytes().to_vec().into_boxed_slice(),
1283 OptionalArg::Missing => self.code.linetable.clone(),
1284 };
1285
1286 let exceptiontable = match co_exceptiontable {
1287 OptionalArg::Present(exceptiontable) => {
1288 exceptiontable.as_bytes().to_vec().into_boxed_slice()
1289 }
1290 OptionalArg::Missing => self.code.exceptiontable.clone(),
1291 };
1292
1293 let new_code = CodeObject {
1294 flags: CodeFlags::from_bits_truncate(flags),
1295 posonlyarg_count,
1296 arg_count,
1297 kwonlyarg_count,
1298 source_path: source_path.as_object().as_interned_str(vm).unwrap(),
1299 first_line_number,
1300 obj_name: obj_name.as_object().as_interned_str(vm).unwrap(),
1301 qualname: qualname.as_object().as_interned_str(vm).unwrap(),
1302
1303 max_stackdepth,
1304 instructions,
1305 locations: self.code.locations.clone(),
1308 constants: constants.into_iter().map(Literal).collect(),
1309 names: names
1310 .into_iter()
1311 .map(|o| o.as_interned_str(vm).unwrap())
1312 .collect(),
1313 varnames: varnames
1314 .into_iter()
1315 .map(|o| o.as_interned_str(vm).unwrap())
1316 .collect(),
1317 cellvars,
1318 freevars,
1319 localspluskinds: self.code.localspluskinds.clone(),
1320 linetable,
1321 exceptiontable,
1322 };
1323
1324 Ok(PyCode::new(new_code))
1325 }
1326
1327 #[pymethod]
1328 fn _varname_from_oparg(&self, opcode: i32, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
1329 let idx_err = |vm: &VirtualMachine| vm.new_index_error("tuple index out of range");
1330
1331 let idx = usize::try_from(opcode).map_err(|_| idx_err(vm))?;
1332
1333 let varnames_len = self.code.varnames.len();
1334 let nonparam_cellvars: Vec<_> = self
1336 .code
1337 .cellvars
1338 .iter()
1339 .filter(|s| {
1340 let s_str: &str = s.as_ref();
1341 !self.code.varnames.iter().any(|v| {
1342 let v_str: &str = v.as_ref();
1343 v_str == s_str
1344 })
1345 })
1346 .collect();
1347 let nonparam_len = nonparam_cellvars.len();
1348
1349 let name = if idx < varnames_len {
1350 self.code.varnames.get(idx).ok_or_else(|| idx_err(vm))?
1352 } else if idx < varnames_len + nonparam_len {
1353 *nonparam_cellvars
1355 .get(idx - varnames_len)
1356 .ok_or_else(|| idx_err(vm))?
1357 } else {
1358 self.code
1360 .freevars
1361 .get(idx - varnames_len - nonparam_len)
1362 .ok_or_else(|| idx_err(vm))?
1363 };
1364 Ok(name.to_object())
1365 }
1366}
1367
1368impl fmt::Display for PyCode {
1369 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1370 (**self).fmt(f)
1371 }
1372}
1373
1374impl ToPyObject for CodeObject {
1375 fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
1376 vm.ctx.new_code(self).into()
1377 }
1378}
1379
1380impl ToPyObject for bytecode::CodeObject {
1381 fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
1382 vm.ctx.new_code(self).into()
1383 }
1384}
1385
1386struct LineTableReader<'a> {
1388 data: &'a [u8],
1389 pos: usize,
1390}
1391
1392impl<'a> LineTableReader<'a> {
1393 fn new(data: &'a [u8]) -> Self {
1394 Self { data, pos: 0 }
1395 }
1396
1397 fn read_byte(&mut self) -> Option<u8> {
1398 if self.pos < self.data.len() {
1399 let byte = self.data[self.pos];
1400 self.pos += 1;
1401 Some(byte)
1402 } else {
1403 None
1404 }
1405 }
1406
1407 fn peek_byte(&self) -> Option<u8> {
1408 if self.pos < self.data.len() {
1409 Some(self.data[self.pos])
1410 } else {
1411 None
1412 }
1413 }
1414
1415 fn read_varint(&mut self) -> u32 {
1416 if let Some(first) = self.read_byte() {
1417 let mut val = (first & 0x3f) as u32;
1418 let mut shift = 0;
1419 let mut byte = first;
1420 while (byte & 0x40) != 0 {
1421 if let Some(next) = self.read_byte() {
1422 shift += 6;
1423 val |= ((next & 0x3f) as u32) << shift;
1424 byte = next;
1425 } else {
1426 break;
1427 }
1428 }
1429 val
1430 } else {
1431 0
1432 }
1433 }
1434
1435 fn read_signed_varint(&mut self) -> i32 {
1436 let uval = self.read_varint();
1437 if uval & 1 != 0 {
1438 -((uval >> 1) as i32)
1439 } else {
1440 (uval >> 1) as i32
1441 }
1442 }
1443
1444 fn at_end(&self) -> bool {
1445 self.pos >= self.data.len()
1446 }
1447}
1448
1449pub fn init(ctx: &'static Context) {
1450 PyCode::extend_class(ctx, ctx.types.code_type);
1451}