1use super::descriptor::{MemberKind, MemberLayout};
4use super::{PyBytesRef, PyStrRef, PyTupleRef, PyType, set::PyFrozenSet};
5use crate::common::lock::PyMutex;
6#[cfg(feature = "host_env")]
7use crate::convert::ToPyException;
8use crate::{
9 AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
10 builtins::PyStrInterned,
11 bytecode::{self, AsBag, BorrowedConstant, CodeFlags, Constant, ConstantBag, Instruction},
12 class::{PyClassImpl, StaticType},
13 convert::ToPyObject,
14 frozen,
15 function::OptionalArg,
16 types::{Comparable, Constructor, Hashable, Representable},
17};
18use alloc::fmt;
19use core::{
20 borrow::Borrow,
21 ops::Deref,
22 sync::atomic::{AtomicPtr, AtomicU64, Ordering},
23};
24use malachite_bigint::BigInt;
25use num_traits::Zero;
26use rustpython_compiler_core::{OneIndexed, bytecode::CodeUnits, bytecode::PyCodeLocationInfoKind};
27
28struct PyCodeAddressRange<'a> {
30 ar_start: i32,
31 ar_end: i32,
32 ar_line: i32,
33 computed_line: i32,
34 reader: LineTableReader<'a>,
35}
36
37impl<'a> PyCodeAddressRange<'a> {
38 fn new(linetable: &'a [u8], first_line: i32) -> Self {
39 PyCodeAddressRange {
40 ar_start: 0,
41 ar_end: 0,
42 ar_line: -1,
43 computed_line: first_line,
44 reader: LineTableReader::new(linetable),
45 }
46 }
47
48 fn is_no_line_marker(byte: u8) -> bool {
50 (byte >> 3) == 0x1f
51 }
52
53 fn advance(&mut self) -> bool {
55 if self.reader.at_end() {
56 return false;
57 }
58
59 let first_byte = match self.reader.read_byte() {
60 Some(b) => b,
61 None => return false,
62 };
63
64 if (first_byte & 0x80) == 0 {
65 return false; }
67
68 let code = (first_byte >> 3) & 0x0f;
69 let length = ((first_byte & 0x07) + 1) as i32;
70
71 let line_delta = self.get_line_delta(code);
73
74 self.computed_line += line_delta;
76
77 if Self::is_no_line_marker(first_byte) {
79 self.ar_line = -1;
80 } else {
81 self.ar_line = self.computed_line;
82 }
83
84 self.ar_start = self.ar_end;
86 self.ar_end += length * 2; while !self.reader.at_end() {
90 if let Some(b) = self.reader.peek_byte() {
91 if (b & 0x80) != 0 {
92 break;
93 }
94 self.reader.read_byte();
95 } else {
96 break;
97 }
98 }
99
100 true
101 }
102
103 fn get_line_delta(&mut self, code: u8) -> i32 {
104 let kind = match PyCodeLocationInfoKind::from_code(code) {
105 Some(k) => k,
106 None => return 0,
107 };
108
109 match kind {
110 PyCodeLocationInfoKind::None => 0, PyCodeLocationInfoKind::Long => {
112 let delta = self.reader.read_signed_varint();
113 self.reader.read_varint();
115 self.reader.read_varint();
116 self.reader.read_varint();
117 delta
118 }
119 PyCodeLocationInfoKind::NoColumns => self.reader.read_signed_varint(),
120 PyCodeLocationInfoKind::OneLine0 => {
121 self.reader.read_byte(); self.reader.read_byte(); 0
124 }
125 PyCodeLocationInfoKind::OneLine1 => {
126 self.reader.read_byte(); self.reader.read_byte(); 1
129 }
130 PyCodeLocationInfoKind::OneLine2 => {
131 self.reader.read_byte(); self.reader.read_byte(); 2
134 }
135 _ if kind.is_short() => {
136 self.reader.read_byte(); 0
138 }
139 _ => 0,
140 }
141 }
142}
143
144#[derive(FromArgs)]
145pub struct ReplaceArgs {
146 #[pyarg(named, optional)]
147 co_posonlyargcount: OptionalArg<u32>,
148 #[pyarg(named, optional)]
149 co_argcount: OptionalArg<u32>,
150 #[pyarg(named, optional)]
151 co_kwonlyargcount: OptionalArg<u32>,
152 #[pyarg(named, optional)]
153 co_filename: OptionalArg<PyStrRef>,
154 #[pyarg(named, optional)]
155 co_firstlineno: OptionalArg<u32>,
156 #[pyarg(named, optional)]
157 co_consts: OptionalArg<Vec<PyObjectRef>>,
158 #[pyarg(named, optional)]
159 co_name: OptionalArg<PyStrRef>,
160 #[pyarg(named, optional)]
161 co_names: OptionalArg<Vec<PyObjectRef>>,
162 #[pyarg(named, optional)]
163 co_flags: OptionalArg<u32>,
164 #[pyarg(named, optional)]
165 co_varnames: OptionalArg<Vec<PyObjectRef>>,
166 #[pyarg(named, optional)]
167 co_nlocals: OptionalArg<u32>,
168 #[pyarg(named, optional)]
169 co_stacksize: OptionalArg<u32>,
170 #[pyarg(named, optional)]
171 co_code: OptionalArg<crate::builtins::PyBytesRef>,
172 #[pyarg(named, optional)]
173 co_linetable: OptionalArg<crate::builtins::PyBytesRef>,
174 #[pyarg(named, optional)]
175 co_exceptiontable: OptionalArg<crate::builtins::PyBytesRef>,
176 #[pyarg(named, optional)]
177 co_freevars: OptionalArg<Vec<PyObjectRef>>,
178 #[pyarg(named, optional)]
179 co_cellvars: OptionalArg<Vec<PyObjectRef>>,
180 #[pyarg(named, optional)]
181 co_qualname: OptionalArg<PyStrRef>,
182}
183
184#[derive(Clone)]
185#[repr(transparent)]
186pub struct Literal(PyObjectRef);
187
188impl Borrow<PyObject> for Literal {
189 fn borrow(&self) -> &PyObject {
190 &self.0
191 }
192}
193
194impl From<Literal> for PyObjectRef {
195 fn from(obj: Literal) -> Self {
196 obj.0
197 }
198}
199
200impl From<PyObjectRef> for Literal {
201 fn from(obj: PyObjectRef) -> Self {
202 Self(obj)
203 }
204}
205
206fn borrow_obj_constant(obj: &PyObject) -> BorrowedConstant<'_, Literal> {
207 match_class!(match obj {
208 ref i @ super::int::PyInt => {
209 let value = i.as_bigint();
210 if obj.class().is(super::bool_::PyBool::static_type()) {
211 BorrowedConstant::Boolean {
212 value: !value.is_zero(),
213 }
214 } else {
215 BorrowedConstant::Integer { value }
216 }
217 }
218 ref f @ super::float::PyFloat => BorrowedConstant::Float { value: f.to_f64() },
219 ref c @ super::complex::PyComplex => BorrowedConstant::Complex {
220 value: c.as_complex()
221 },
222 ref s @ super::pystr::PyStr => BorrowedConstant::Str { value: s.as_wtf8() },
223 ref b @ super::bytes::PyBytes => BorrowedConstant::Bytes {
224 value: b.as_bytes()
225 },
226 ref c @ PyCode => {
227 BorrowedConstant::Code { code: &c.code }
228 }
229 ref t @ super::tuple::PyTuple => {
230 let elements = t.as_slice();
231 let elements = unsafe { &*(elements as *const [PyObjectRef] as *const [Literal]) };
234 BorrowedConstant::Tuple { elements }
235 }
236 super::singletons::PyNone => BorrowedConstant::None,
237 super::slice::PyEllipsis => BorrowedConstant::Ellipsis,
238 ref s @ super::slice::PySlice => {
239 let start = s.start.clone().unwrap();
243 let stop = s.stop.clone();
244 let step = s.step.clone().unwrap();
245 let arr = Box::leak(Box::new([Literal(start), Literal(stop), Literal(step)]));
246 BorrowedConstant::Slice { elements: arr }
247 }
248 ref fs @ super::set::PyFrozenSet => {
249 let elems: Vec<Literal> = fs.elements().into_iter().map(Literal).collect();
252 let elements = Box::leak(elems.into_boxed_slice());
253 BorrowedConstant::Frozenset { elements }
254 }
255 _ => panic!("unexpected payload for constant python value"),
256 })
257}
258
259impl Constant for Literal {
260 type Name = &'static PyStrInterned;
261 fn borrow_constant(&self) -> BorrowedConstant<'_, Self> {
262 borrow_obj_constant(&self.0)
263 }
264}
265
266impl<'a> AsBag for &'a Context {
267 type Bag = PyObjBag<'a>;
268 fn as_bag(self) -> PyObjBag<'a> {
269 PyObjBag(self)
270 }
271}
272
273#[derive(Clone, Copy)]
274pub struct PyObjBag<'a>(pub &'a Context);
275
276fn is_name_chars(value: &crate::common::wtf8::Wtf8) -> bool {
279 value
280 .as_bytes()
281 .iter()
282 .all(|&b| b.is_ascii_alphanumeric() || b == b'_')
283}
284
285impl ConstantBag for PyObjBag<'_> {
286 type Constant = Literal;
287
288 fn make_constant<C: Constant>(&self, constant: BorrowedConstant<'_, C>) -> Self::Constant {
289 let ctx = self.0;
290 let obj = match constant {
291 BorrowedConstant::Integer { value } => ctx.new_bigint(value).into(),
292 BorrowedConstant::Float { value } => ctx.new_float(value).into(),
293 BorrowedConstant::Complex { value } => ctx.new_complex(value).into(),
294 BorrowedConstant::Str { value } if is_name_chars(value) => {
295 ctx.intern_str(value).to_object()
296 }
297 BorrowedConstant::Str { value } => ctx.new_str(value).into(),
298 BorrowedConstant::Bytes { value } => ctx.new_bytes(value.to_vec()).into(),
299 BorrowedConstant::Boolean { value } => ctx.new_bool(value).into(),
300 BorrowedConstant::Code { code } => ctx.new_code(code.map_clone_bag(self)).into(),
301 BorrowedConstant::Tuple { elements } => {
302 let elements = elements
303 .iter()
304 .map(|constant| self.make_constant(constant.borrow_constant()).0)
305 .collect();
306 ctx.new_tuple(elements).into()
307 }
308 BorrowedConstant::Slice { elements } => {
309 let [start, stop, step] = elements;
310 let start_obj = self.make_constant(start.borrow_constant()).0;
311 let stop_obj = self.make_constant(stop.borrow_constant()).0;
312 let step_obj = self.make_constant(step.borrow_constant()).0;
313 use crate::builtins::PySlice;
316 PySlice {
317 start: Some(start_obj),
318 stop: stop_obj,
319 step: Some(step_obj),
320 }
321 .into_ref(ctx)
322 .into()
323 }
324 BorrowedConstant::Frozenset { elements: _ } => {
325 unimplemented!(
329 "frozenset constant in PyObjBag::make_constant requires VirtualMachine"
330 )
331 }
332 BorrowedConstant::None => ctx.none(),
333 BorrowedConstant::Ellipsis => ctx.ellipsis.clone().into(),
334 };
335
336 Literal(obj)
337 }
338
339 fn make_name(&self, name: &str) -> &'static PyStrInterned {
340 self.0.intern_str(name)
341 }
342
343 fn make_int(&self, value: BigInt) -> Self::Constant {
344 Literal(self.0.new_int(value).into())
345 }
346
347 fn make_tuple(&self, elements: impl Iterator<Item = Self::Constant>) -> Self::Constant {
348 Literal(self.0.new_tuple(elements.map(|lit| lit.0).collect()).into())
349 }
350
351 fn make_code(&self, code: CodeObject) -> Self::Constant {
352 Literal(self.0.new_code(code).into())
353 }
354}
355
356#[derive(Clone, Copy)]
357pub(crate) struct PyVmBag<'a>(pub &'a VirtualMachine);
358
359impl ConstantBag for PyVmBag<'_> {
360 type Constant = Literal;
361
362 fn make_constant<C: Constant>(&self, constant: BorrowedConstant<'_, C>) -> Self::Constant {
363 let vm = self.0;
364 let ctx = &vm.ctx;
365 let obj = match constant {
366 BorrowedConstant::Integer { value } => ctx.new_bigint(value).into(),
367 BorrowedConstant::Float { value } => ctx.new_float(value).into(),
368 BorrowedConstant::Complex { value } => ctx.new_complex(value).into(),
369 BorrowedConstant::Str { value } if is_name_chars(value) => {
370 ctx.intern_str(value).to_object()
371 }
372 BorrowedConstant::Str { value } => ctx.new_str(value).into(),
373 BorrowedConstant::Bytes { value } => ctx.new_bytes(value.to_vec()).into(),
374 BorrowedConstant::Boolean { value } => ctx.new_bool(value).into(),
375 BorrowedConstant::Code { code } => {
376 PyCode::new_ref_with_bag(vm, code.map_clone_bag(self)).into()
377 }
378 BorrowedConstant::Tuple { elements } => {
379 let elements = elements
380 .iter()
381 .map(|constant| self.make_constant(constant.borrow_constant()).0)
382 .collect();
383 ctx.new_tuple(elements).into()
384 }
385 BorrowedConstant::Slice { elements } => {
386 let [start, stop, step] = elements;
387 let start_obj = self.make_constant(start.borrow_constant()).0;
388 let stop_obj = self.make_constant(stop.borrow_constant()).0;
389 let step_obj = self.make_constant(step.borrow_constant()).0;
390 use crate::builtins::PySlice;
391 PySlice {
392 start: Some(start_obj),
393 stop: stop_obj,
394 step: Some(step_obj),
395 }
396 .into_ref(ctx)
397 .into()
398 }
399 BorrowedConstant::Frozenset { elements } => {
400 let elements = elements
401 .iter()
402 .map(|constant| self.make_constant(constant.borrow_constant()).0);
403 PyFrozenSet::from_iter(vm, elements)
404 .unwrap()
405 .into_ref(ctx)
406 .into()
407 }
408 BorrowedConstant::None => ctx.none(),
409 BorrowedConstant::Ellipsis => ctx.ellipsis.clone().into(),
410 };
411
412 Literal(obj)
413 }
414
415 fn make_name(&self, name: &str) -> &'static PyStrInterned {
416 self.0.ctx.intern_str(name)
417 }
418
419 fn make_int(&self, value: BigInt) -> Self::Constant {
420 Literal(self.0.ctx.new_int(value).into())
421 }
422
423 fn make_tuple(&self, elements: impl Iterator<Item = Self::Constant>) -> Self::Constant {
424 Literal(
425 self.0
426 .ctx
427 .new_tuple(elements.map(|lit| lit.0).collect())
428 .into(),
429 )
430 }
431
432 fn make_code(&self, code: CodeObject) -> Self::Constant {
433 Literal(PyCode::new_ref_with_bag(self.0, code).into())
434 }
435}
436
437pub(crate) type CodeObject = bytecode::CodeObject<Literal>;
438
439pub trait IntoCodeObject {
440 fn into_code_object(self, ctx: &Context) -> CodeObject;
441}
442
443impl IntoCodeObject for CodeObject {
444 fn into_code_object(self, _ctx: &Context) -> Self {
445 self
446 }
447}
448
449impl IntoCodeObject for bytecode::CodeObject {
450 fn into_code_object(self, ctx: &Context) -> CodeObject {
451 self.map_bag(PyObjBag(ctx))
452 }
453}
454
455impl<B: AsRef<[u8]>> IntoCodeObject for frozen::FrozenCodeObject<B> {
456 fn into_code_object(self, ctx: &Context) -> CodeObject {
457 self.decode(ctx)
458 }
459}
460
461pub struct CoMonitoringData {
464 pub line_opcodes: Vec<u8>,
467
468 pub per_instruction_opcodes: Vec<u8>,
471}
472
473#[pyclass(module = false, name = "code")]
474pub struct PyCode {
475 #[pymember(name = "co_argcount", path = "arg_count")]
476 #[pymember(name = "co_posonlyargcount", path = "posonlyarg_count")]
477 #[pymember(name = "co_kwonlyargcount", path = "kwonlyarg_count")]
478 #[pymember(name = "co_stacksize", path = "max_stackdepth")]
479 #[pymember(name = "co_name", path = "obj_name")]
480 #[pymember(name = "co_qualname", path = "qualname")]
481 #[pymember(name = "co_flags", path = "flags")]
482 pub code: CodeObject,
483 localsplus_names: Box<[&'static PyStrInterned]>,
487 #[pymember(name = "co_filename")]
488 source_path: AtomicPtr<PyStrInterned>,
489 #[pymember(name = "co_nlocals")]
490 nlocals: i32,
491 pub instrumentation_version: AtomicU64,
494 pub monitoring_data: PyMutex<Option<CoMonitoringData>>,
496 pub quickened: core::sync::atomic::AtomicBool,
498 pub has_exc_handling: bool,
503}
504
505impl Deref for PyCode {
506 type Target = CodeObject;
507 fn deref(&self) -> &Self::Target {
508 &self.code
509 }
510}
511
512fn build_localspluskinds(
513 varnames: &[&'static PyStrInterned],
514 cellvars: &[&'static PyStrInterned],
515 freevars: &[&'static PyStrInterned],
516 arg_counts: (u32, u32, u32),
517 flags: CodeFlags,
518 instructions: &CodeUnits,
519) -> Result<Box<[u8]>, usize> {
520 use rustpython_compiler_core::bytecode::{
521 CO_FAST_ARG_KW, CO_FAST_ARG_POS, CO_FAST_ARG_VAR, CO_FAST_CELL, CO_FAST_FREE,
522 CO_FAST_HIDDEN, CO_FAST_LOCAL, OpArgState,
523 };
524
525 let num_merged_cells = cellvars
526 .iter()
527 .filter(|cell| varnames.iter().any(|local| *local == **cell))
528 .count();
529 let mut kinds = vec![0; varnames.len() + cellvars.len() - num_merged_cells + freevars.len()];
530
531 let (posonlyarg_count, arg_count, kwonlyarg_count) = arg_counts;
532 let positional_only = posonlyarg_count as usize;
533 let positional_or_keyword = arg_count.saturating_sub(posonlyarg_count) as usize;
534 let argument_kinds = [
535 (positional_only, CO_FAST_ARG_POS),
536 (positional_or_keyword, CO_FAST_ARG_POS | CO_FAST_ARG_KW),
537 (kwonlyarg_count as usize, CO_FAST_ARG_KW),
538 (
539 usize::from(flags.contains(CodeFlags::VARARGS)),
540 CO_FAST_ARG_VAR | CO_FAST_ARG_POS,
541 ),
542 (
543 usize::from(flags.contains(CodeFlags::VARKEYWORDS)),
544 CO_FAST_ARG_VAR | CO_FAST_ARG_KW,
545 ),
546 (usize::MAX, 0),
547 ];
548 let mut local_index = 0;
549 let mut argument_end = 0usize;
550 for (count, argument_kind) in argument_kinds {
551 argument_end = argument_end.saturating_add(count);
552 while local_index < argument_end && local_index < varnames.len() {
553 kinds[local_index] = CO_FAST_LOCAL | argument_kind;
554 local_index += 1;
555 }
556 }
557
558 let mut dropped_cells = 0;
559 for (cell_index, cell) in cellvars.iter().enumerate() {
560 if let Some(local_index) = varnames.iter().position(|local| *local == *cell) {
561 kinds[local_index] |= CO_FAST_CELL;
562 dropped_cells += 1;
563 } else {
564 kinds[varnames.len() + cell_index - dropped_cells] = CO_FAST_CELL;
565 }
566 }
567
568 let free_start = varnames.len() + cellvars.len() - num_merged_cells;
569 for kind in kinds.iter_mut().skip(free_start) {
570 *kind = CO_FAST_FREE;
571 }
572
573 if !flags.contains(CodeFlags::OPTIMIZED) {
574 let mut arg_state = OpArgState::default();
575 for unit in instructions.iter().copied() {
576 let (instruction, arg) = arg_state.get(unit);
577 if matches!(instruction, Instruction::LoadFastAndClear { .. }) {
578 let index = u32::from(arg) as usize;
579 let Some(kind) = kinds.get_mut(index) else {
580 return Err(index);
581 };
582 *kind |= CO_FAST_HIDDEN;
583 }
584 }
585 }
586
587 Ok(kinds.into_boxed_slice())
588}
589
590impl MemberLayout for CodeFlags {
591 const KIND: MemberKind = MemberKind::Int;
592}
593
594impl PyCode {
595 pub fn new(code: CodeObject) -> Self {
596 let sp = code.source_path as *const PyStrInterned as *mut PyStrInterned;
597 let nlocals = i32::try_from(code.varnames.len()).unwrap_or(i32::MAX);
598 let localsplus_names = {
599 let varname_ids = code
600 .varnames
601 .iter()
602 .map(|name| name.get_id())
603 .collect::<std::collections::HashSet<_>>();
604 let names = code
605 .varnames
606 .iter()
607 .chain(
608 code.cellvars
609 .iter()
610 .filter(|name| !varname_ids.contains(&name.get_id())),
611 )
612 .chain(code.freevars.iter())
613 .copied()
614 .collect::<Box<[_]>>();
615 debug_assert_eq!(names.len(), code.localspluskinds.len());
616 names
617 };
618 let has_exc_handling = code.instructions.iter().any(|u| {
622 matches!(
623 u.op,
624 Instruction::PushExcInfo
625 | Instruction::PopExcept
626 | Instruction::CheckEgMatch
627 | Instruction::EndAsyncFor
628 | Instruction::InstrumentedEndAsyncFor
629 )
630 });
631 Self {
632 code,
633 localsplus_names,
634 source_path: AtomicPtr::new(sp),
635 nlocals,
636 instrumentation_version: AtomicU64::new(0),
637 monitoring_data: PyMutex::new(None),
638 quickened: core::sync::atomic::AtomicBool::new(false),
639 has_exc_handling,
640 }
641 }
642
643 #[inline(always)]
644 pub(crate) fn localsplus_name(&self, index: usize) -> &'static PyStrInterned {
645 self.localsplus_names[index]
649 }
650
651 pub fn source_path(&self) -> &'static PyStrInterned {
652 unsafe { &*self.source_path.load(Ordering::Relaxed) }
654 }
655
656 pub fn set_source_path(&self, new: &'static PyStrInterned) {
657 self.source_path.store(
658 new as *const PyStrInterned as *mut PyStrInterned,
659 Ordering::Relaxed,
660 );
661 }
662
663 pub fn new_ref_with_bag(vm: &VirtualMachine, code: CodeObject) -> PyRef<Self> {
664 PyRef::new_ref(Self::new(code), vm.ctx.types.code_type.to_owned(), None)
665 }
666
667 pub fn new_ref_from_bytecode(vm: &VirtualMachine, code: bytecode::CodeObject) -> PyRef<Self> {
668 Self::new_ref_with_bag(vm, code.map_bag(PyVmBag(vm)))
669 }
670
671 pub fn new_ref_from_frozen<B: AsRef<[u8]>>(
672 vm: &VirtualMachine,
673 code: frozen::FrozenCodeObject<B>,
674 ) -> PyRef<Self> {
675 let py_code = Self::new_ref_with_bag(vm, code.decode(PyVmBag(vm)));
676 apply_frozen_co_filename(&py_code, vm);
677 py_code
678 }
679
680 #[cfg(feature = "host_env")]
681 pub fn from_pyc_path(path: &std::path::Path, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
682 let name = match path.file_stem() {
683 Some(stem) => stem.display().to_string(),
684 None => "".to_owned(),
685 };
686 let content = crate::host_env::fs::read(path).map_err(|e| e.to_pyexception(vm))?;
687 Self::from_pyc(
688 &content,
689 Some(&name),
690 Some(&path.display().to_string()),
691 Some("<source>"),
692 vm,
693 )
694 }
695 #[cfg(not(feature = "host_env"))]
696 pub fn from_pyc_path(_path: &std::path::Path, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
697 Err(vm.new_runtime_error("loading a pyc file requires the `host_env` feature"))
698 }
699 pub fn from_pyc(
700 pyc_bytes: &[u8],
701 name: Option<&str>,
702 bytecode_path: Option<&str>,
703 source_path: Option<&str>,
704 vm: &VirtualMachine,
705 ) -> PyResult<PyRef<Self>> {
706 if !crate::import::check_pyc_magic_number_bytes(pyc_bytes) {
707 return Err(vm.new_value_error("pyc bytes has wrong MAGIC"));
708 }
709 let bootstrap_external = vm.import("_frozen_importlib_external", 0)?;
710 let compile_bytecode = bootstrap_external.get_attr("_compile_bytecode", vm)?;
711 let Some((_, code_bytes)) = pyc_bytes.split_at_checked(16) else {
713 return Err(vm.new_value_error(format!(
714 "pyc_bytes header is broken. 16 bytes expected but {} bytes given.",
715 pyc_bytes.len()
716 )));
717 };
718 let code_bytes_obj = vm.ctx.new_bytes(code_bytes.to_vec());
719 let compiled =
720 compile_bytecode.call((code_bytes_obj, name, bytecode_path, source_path), vm)?;
721 compiled.try_downcast(vm)
722 }
723}
724
725fn frozen_co_filename(path: &str) -> Option<&'static str> {
726 match path {
727 "_frozen_importlib" => Some("<frozen importlib._bootstrap>"),
728 "_frozen_importlib_external" => Some("<frozen importlib._bootstrap_external>"),
729 _ => None,
730 }
731}
732
733fn apply_frozen_co_filename(code: &PyCode, vm: &VirtualMachine) {
734 let Some(new) = frozen_co_filename(code.source_path().as_str()) else {
735 return;
736 };
737 set_source_path_tree(code, vm.ctx.intern_str(new));
738}
739
740fn set_source_path_tree(code: &PyCode, interned: &'static PyStrInterned) {
741 code.set_source_path(interned);
742 for constant in code.constants.iter() {
743 set_source_path_in_const(&constant.0, interned);
744 }
745}
746
747fn set_source_path_in_const(obj: &PyObject, interned: &'static PyStrInterned) {
748 if let Some(inner) = obj.downcast_ref::<PyCode>() {
749 set_source_path_tree(inner, interned);
750 } else if let Some(tup) = obj.downcast_ref::<super::PyTuple>() {
751 for item in tup {
752 set_source_path_in_const(item, interned);
753 }
754 }
755}
756
757impl fmt::Debug for PyCode {
758 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
759 write!(f, "code: {:?}", self.code)
760 }
761}
762
763impl PyCode {
764 pub fn addr2line(&self, lasti_bytes: i32) -> i32 {
766 if lasti_bytes < 0 {
767 return self.code.first_line_number.map_or(-1, |n| n.get() as i32);
768 }
769 let linetable = self.code.linetable.as_ref();
770 if linetable.is_empty() {
771 return self.code.first_line_number.map_or(-1, |n| n.get() as i32);
772 }
773 let first_line = self.code.first_line_number.map_or(0, |n| n.get() as i32);
774 let mut range = PyCodeAddressRange::new(linetable, first_line);
775 while range.ar_end <= lasti_bytes {
776 if !range.advance() {
777 return -1;
778 }
779 }
780 range.ar_line
781 }
782}
783
784impl PyPayload for PyCode {
785 #[inline]
786 fn class(ctx: &Context) -> &'static Py<PyType> {
787 ctx.types.code_type
788 }
789}
790
791impl Representable for PyCode {
792 #[inline]
793 fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
794 let code = &zelf.code;
795 Ok(format!(
796 "<code object {} at {:#x}, file \"{}\", line {}>",
797 code.obj_name,
798 zelf.get_id(),
799 zelf.source_path().as_str(),
800 code.first_line_number.map_or(-1, |n| n.get() as i32)
801 ))
802 }
803}
804
805impl Comparable for PyCode {
806 fn cmp(
807 zelf: &Py<Self>,
808 other: &PyObject,
809 op: crate::types::PyComparisonOp,
810 vm: &VirtualMachine,
811 ) -> PyResult<crate::function::PyComparisonValue> {
812 op.eq_only(|| {
813 let other = class_or_notimplemented!(Self, other);
814 let a = &zelf.code;
815 let b = &other.code;
816 let eq = a.obj_name == b.obj_name
817 && a.arg_count == b.arg_count
818 && a.posonlyarg_count == b.posonlyarg_count
819 && a.kwonlyarg_count == b.kwonlyarg_count
820 && a.flags == b.flags
821 && a.first_line_number == b.first_line_number
822 && a.instructions.original_bytes() == b.instructions.original_bytes()
823 && a.linetable == b.linetable
824 && a.exceptiontable == b.exceptiontable
825 && a.names == b.names
826 && a.varnames == b.varnames
827 && a.freevars == b.freevars
828 && a.cellvars == b.cellvars
829 && {
830 let a_consts: Vec<_> = a.constants.iter().map(|c| c.0.clone()).collect();
831 let b_consts: Vec<_> = b.constants.iter().map(|c| c.0.clone()).collect();
832 if a_consts.len() != b_consts.len() {
833 false
834 } else {
835 let mut eq = true;
836 for (ac, bc) in a_consts.iter().zip(b_consts.iter()) {
837 if !vm.bool_eq(ac, bc)? {
838 eq = false;
839 break;
840 }
841 }
842 eq
843 }
844 };
845 Ok(eq.into())
846 })
847 }
848}
849
850impl Hashable for PyCode {
851 fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<crate::common::hash::PyHash> {
852 let code = &zelf.code;
853 let tuple = vm.ctx.new_tuple(vec![
855 vm.ctx.new_str(code.obj_name.as_str()).into(),
856 vm.ctx.new_int(code.arg_count).into(),
857 vm.ctx.new_int(code.posonlyarg_count).into(),
858 vm.ctx.new_int(code.kwonlyarg_count).into(),
859 vm.ctx.new_int(code.varnames.len()).into(),
860 vm.ctx.new_int(code.flags.bits()).into(),
861 vm.ctx
862 .new_int(code.first_line_number.map_or(0, |n| n.get()) as i64)
863 .into(),
864 vm.ctx.new_bytes(code.instructions.original_bytes()).into(),
865 {
866 let consts: Vec<_> = code.constants.iter().map(|c| c.0.clone()).collect();
867 vm.ctx.new_tuple(consts).into()
868 },
869 ]);
870 tuple.as_object().hash(vm)
871 }
872}
873
874#[derive(FromArgs)]
876pub struct PyCodeNewArgs {
877 argcount: u32,
878 posonlyargcount: u32,
879 kwonlyargcount: u32,
880 nlocals: u32,
881 stacksize: u32,
882 flags: u32,
883 co_code: PyBytesRef,
884 consts: PyTupleRef,
885 names: PyTupleRef,
886 varnames: PyTupleRef,
887 filename: PyStrRef,
888 name: PyStrRef,
889 qualname: PyStrRef,
890 firstlineno: i32,
891 linetable: PyBytesRef,
892 exceptiontable: PyBytesRef,
893 freevars: PyTupleRef,
894 cellvars: PyTupleRef,
895}
896
897impl Constructor for PyCode {
898 type Args = PyCodeNewArgs;
899
900 fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
901 let names: Box<[&'static PyStrInterned]> = args
903 .names
904 .as_slice()
905 .iter()
906 .map(|obj| {
907 let s = obj
908 .downcast_ref::<super::pystr::PyStr>()
909 .ok_or_else(|| vm.new_type_error("names must be tuple of strings"))?;
910 Ok(vm.ctx.intern_str(s.as_wtf8()))
911 })
912 .collect::<PyResult<Vec<_>>>()?
913 .into_boxed_slice();
914
915 let varnames: Box<[&'static PyStrInterned]> = args
916 .varnames
917 .as_slice()
918 .iter()
919 .map(|obj| {
920 let s = obj
921 .downcast_ref::<super::pystr::PyStr>()
922 .ok_or_else(|| vm.new_type_error("varnames must be tuple of strings"))?;
923 Ok(vm.ctx.intern_str(s.as_wtf8()))
924 })
925 .collect::<PyResult<Vec<_>>>()?
926 .into_boxed_slice();
927
928 let cellvars: Box<[&'static PyStrInterned]> = args
929 .cellvars
930 .as_slice()
931 .iter()
932 .map(|obj| {
933 let s = obj
934 .downcast_ref::<super::pystr::PyStr>()
935 .ok_or_else(|| vm.new_type_error("cellvars must be tuple of strings"))?;
936 Ok(vm.ctx.intern_str(s.as_wtf8()))
937 })
938 .collect::<PyResult<Vec<_>>>()?
939 .into_boxed_slice();
940
941 let freevars: Box<[&'static PyStrInterned]> = args
942 .freevars
943 .as_slice()
944 .iter()
945 .map(|obj| {
946 let s = obj
947 .downcast_ref::<super::pystr::PyStr>()
948 .ok_or_else(|| vm.new_type_error("freevars must be tuple of strings"))?;
949 Ok(vm.ctx.intern_str(s.as_wtf8()))
950 })
951 .collect::<PyResult<Vec<_>>>()?
952 .into_boxed_slice();
953
954 if args.nlocals as usize != varnames.len() {
956 return Err(vm.new_value_error(format!(
957 "nlocals ({}) != len(varnames) ({})",
958 args.nlocals,
959 varnames.len()
960 )));
961 }
962
963 let bytecode_bytes = args.co_code.as_bytes();
965 let instructions = CodeUnits::try_from(bytecode_bytes)
966 .map_err(|e| vm.new_value_error(format!("invalid bytecode: {e}")))?;
967
968 let constants = args
970 .consts
971 .as_slice()
972 .iter()
973 .map(|obj| {
974 Literal(obj.clone())
976 })
977 .collect();
978
979 let row = if args.firstlineno > 0 {
981 OneIndexed::new(args.firstlineno as usize).unwrap_or(OneIndexed::MIN)
982 } else {
983 OneIndexed::MIN
984 };
985 let loc = rustpython_compiler_core::SourceLocation {
986 line: row,
987 character_offset: OneIndexed::from_zero_indexed(0),
988 };
989 let locations: Box<
990 [(
991 rustpython_compiler_core::SourceLocation,
992 rustpython_compiler_core::SourceLocation,
993 )],
994 > = vec![(loc, loc); instructions.len()].into_boxed_slice();
995
996 let flags = CodeFlags::from_bits_truncate(args.flags);
997 let localspluskinds = build_localspluskinds(
998 &varnames,
999 &cellvars,
1000 &freevars,
1001 (args.posonlyargcount, args.argcount, args.kwonlyargcount),
1002 flags,
1003 &instructions,
1004 )
1005 .map_err(|index| {
1006 vm.new_value_error(format!(
1007 "code: LOAD_FAST_AND_CLEAR oparg {index} out of range"
1008 ))
1009 })?;
1010
1011 let code = CodeObject {
1013 instructions,
1014 locations,
1015 flags,
1016 posonlyarg_count: args.posonlyargcount,
1017 arg_count: args.argcount,
1018 kwonlyarg_count: args.kwonlyargcount,
1019 source_path: vm.ctx.intern_str(args.filename.as_wtf8()),
1020 first_line_number: if args.firstlineno > 0 {
1021 OneIndexed::new(args.firstlineno as usize)
1022 } else {
1023 None
1024 },
1025 max_stackdepth: args.stacksize.max(1),
1027 obj_name: vm.ctx.intern_str(args.name.as_wtf8()),
1028 qualname: vm.ctx.intern_str(args.qualname.as_wtf8()),
1029 constants,
1030 names,
1031 varnames,
1032 cellvars,
1033 freevars,
1034 localspluskinds,
1035 linetable: args.linetable.as_bytes().to_vec().into_boxed_slice(),
1036 exceptiontable: args.exceptiontable.as_bytes().to_vec().into_boxed_slice(),
1037 };
1038
1039 Ok(Self::new(code))
1040 }
1041}
1042
1043impl PyCode {
1044 pub fn co_filename(&self) -> PyStrRef {
1045 self.source_path().to_owned()
1046 }
1047}
1048
1049#[pyclass(
1050 itemsize = core::mem::size_of::<u16>(),
1051 with(Representable, Constructor, Comparable, Hashable),
1052 flags(HAS_WEAKREF)
1053)]
1054impl Py<PyCode> {
1055 #[pygetset]
1056 pub fn co_cellvars(&self, vm: &VirtualMachine) -> PyTupleRef {
1057 let cellvars = self
1058 .cellvars
1059 .iter()
1060 .map(|name| name.to_pyobject(vm))
1061 .collect();
1062 vm.ctx.new_tuple(cellvars)
1063 }
1064
1065 #[pygetset]
1066 fn co_firstlineno(&self) -> u32 {
1067 self.code.first_line_number.map_or(0, |n| n.get() as _)
1068 }
1069
1070 #[pygetset]
1071 fn co_consts(&self, vm: &VirtualMachine) -> PyTupleRef {
1072 let consts = self.code.constants.iter().map(|x| x.0.clone()).collect();
1073 vm.ctx.new_tuple(consts)
1074 }
1075
1076 #[pygetset]
1077 fn co_names(&self, vm: &VirtualMachine) -> PyTupleRef {
1078 let names = self
1079 .code
1080 .names
1081 .deref()
1082 .iter()
1083 .map(|name| name.to_pyobject(vm))
1084 .collect();
1085 vm.ctx.new_tuple(names)
1086 }
1087
1088 #[pygetset]
1089 pub fn co_varnames(&self, vm: &VirtualMachine) -> PyTupleRef {
1090 let varnames = self.code.varnames.iter().map(|s| s.to_object()).collect();
1091 vm.ctx.new_tuple(varnames)
1092 }
1093
1094 #[pygetset]
1095 pub fn co_code(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
1096 vm.ctx.new_bytes(self.code.instructions.original_bytes())
1097 }
1098
1099 #[pygetset]
1100 pub fn _co_code_adaptive(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
1101 let bytes = unsafe {
1103 core::slice::from_raw_parts(
1104 self.code.instructions.as_ptr() as *const u8,
1105 self.code.instructions.len() * 2,
1106 )
1107 };
1108 vm.ctx.new_bytes(bytes.to_vec())
1109 }
1110
1111 #[pygetset]
1112 pub fn co_freevars(&self, vm: &VirtualMachine) -> PyTupleRef {
1113 let names = self
1114 .code
1115 .freevars
1116 .deref()
1117 .iter()
1118 .map(|name| name.to_pyobject(vm))
1119 .collect();
1120 vm.ctx.new_tuple(names)
1121 }
1122
1123 #[pygetset]
1124 pub fn co_linetable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
1125 vm.ctx.new_bytes(self.code.linetable.to_vec())
1127 }
1128
1129 #[pygetset]
1130 pub fn co_exceptiontable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
1131 vm.ctx.new_bytes(self.code.exceptiontable.to_vec())
1133 }
1134
1135 #[pymethod]
1141 pub fn co_lines(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
1142 let linetable = self.code.linetable.as_ref();
1147 let mut lines = Vec::new();
1148
1149 if !linetable.is_empty() {
1150 let first_line = self.code.first_line_number.map_or(0, |n| n.get() as i32);
1151 let mut range = PyCodeAddressRange::new(linetable, first_line);
1152
1153 let mut pending_entry: Option<(i32, i32, i32)> = None;
1155
1156 while range.advance() {
1157 let start = range.ar_start;
1158 let end = range.ar_end;
1159 let line = range.ar_line;
1160
1161 if let Some((prev_start, _, prev_line)) = pending_entry {
1162 if prev_line == line {
1163 pending_entry = Some((prev_start, end, prev_line));
1165 } else {
1166 let tuple = if prev_line == -1 {
1168 vm.ctx.new_tuple(vec![
1169 vm.ctx.new_int(prev_start).into(),
1170 vm.ctx.new_int(start).into(),
1171 vm.ctx.none(),
1172 ])
1173 } else {
1174 vm.ctx.new_tuple(vec![
1175 vm.ctx.new_int(prev_start).into(),
1176 vm.ctx.new_int(start).into(),
1177 vm.ctx.new_int(prev_line).into(),
1178 ])
1179 };
1180 lines.push(tuple.into());
1181 pending_entry = Some((start, end, line));
1182 }
1183 } else {
1184 pending_entry = Some((start, end, line));
1186 }
1187 }
1188
1189 if let Some((start, end, line)) = pending_entry {
1191 let tuple = if line == -1 {
1192 vm.ctx.new_tuple(vec![
1193 vm.ctx.new_int(start).into(),
1194 vm.ctx.new_int(end).into(),
1195 vm.ctx.none(),
1196 ])
1197 } else {
1198 vm.ctx.new_tuple(vec![
1199 vm.ctx.new_int(start).into(),
1200 vm.ctx.new_int(end).into(),
1201 vm.ctx.new_int(line).into(),
1202 ])
1203 };
1204 lines.push(tuple.into());
1205 }
1206 }
1207
1208 let list = vm.ctx.new_list(lines);
1209 vm.call_method(list.as_object(), "__iter__", ())
1210 }
1211
1212 #[pymethod]
1213 pub fn co_positions(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
1214 let linetable = self.code.linetable.as_ref();
1216 let mut positions = Vec::new();
1217
1218 if !linetable.is_empty() {
1219 let mut reader = LineTableReader::new(linetable);
1220 let mut line = self.code.first_line_number.map_or(0, |n| n.get() as i32);
1221
1222 while !reader.at_end() {
1223 let first_byte = match reader.read_byte() {
1224 Some(b) => b,
1225 None => break,
1226 };
1227
1228 if (first_byte & 0x80) == 0 {
1229 break; }
1231
1232 let code = (first_byte >> 3) & 0x0f;
1233 let length = ((first_byte & 0x07) + 1) as i32;
1234
1235 let kind = match PyCodeLocationInfoKind::from_code(code) {
1236 Some(k) => k,
1237 None => break, };
1239
1240 let (line_delta, end_line_delta, column, end_column): (
1241 i32,
1242 i32,
1243 Option<i32>,
1244 Option<i32>,
1245 ) = match kind {
1246 PyCodeLocationInfoKind::None => {
1247 (0, 0, None, None)
1249 }
1250 PyCodeLocationInfoKind::Long => {
1251 let delta = reader.read_signed_varint();
1253 let end_line_delta = reader.read_varint() as i32;
1254
1255 let col = reader.read_varint();
1256 let column = if col == 0 {
1257 None
1258 } else {
1259 Some((col - 1) as i32)
1260 };
1261
1262 let end_col = reader.read_varint();
1263 let end_column = if end_col == 0 {
1264 None
1265 } else {
1266 Some((end_col - 1) as i32)
1267 };
1268
1269 (delta, end_line_delta, column, end_column)
1271 }
1272 PyCodeLocationInfoKind::NoColumns => {
1273 let delta = reader.read_signed_varint();
1275 (delta, 0, None, None) }
1277 PyCodeLocationInfoKind::OneLine0
1278 | PyCodeLocationInfoKind::OneLine1
1279 | PyCodeLocationInfoKind::OneLine2 => {
1280 let col = reader.read_byte().unwrap_or(0) as i32;
1282 let end_col = reader.read_byte().unwrap_or(0) as i32;
1283 let delta = kind.one_line_delta().unwrap_or(0);
1284 (delta, 0, Some(col), Some(end_col)) }
1286 _ if kind.is_short() => {
1287 let col_data = reader.read_byte().unwrap_or(0);
1289 let col_group = kind.short_column_group().unwrap_or(0);
1290 let col = ((col_group as i32) << 3) | ((col_data >> 4) as i32);
1291 let end_col = col + (col_data & 0x0f) as i32;
1292 (0, 0, Some(col), Some(end_col)) }
1294 _ => (0, 0, None, None),
1295 };
1296
1297 line += line_delta;
1299
1300 for _ in 0..length {
1302 let final_line = if kind == PyCodeLocationInfoKind::None {
1304 None
1305 } else {
1306 Some(line)
1307 };
1308
1309 let final_endline = if kind == PyCodeLocationInfoKind::None {
1310 None
1311 } else {
1312 Some(line + end_line_delta)
1313 };
1314
1315 let line_obj = final_line.to_pyobject(vm);
1316 let end_line_obj = final_endline.to_pyobject(vm);
1317 let column_obj = column.to_pyobject(vm);
1318 let end_column_obj = end_column.to_pyobject(vm);
1319
1320 let tuple =
1321 vm.ctx
1322 .new_tuple(vec![line_obj, end_line_obj, column_obj, end_column_obj]);
1323 positions.push(tuple.into());
1324 }
1325 }
1326 }
1327
1328 let list = vm.ctx.new_list(positions);
1329 vm.call_method(list.as_object(), "__iter__", ())
1330 }
1331
1332 #[pymethod]
1333 pub fn co_branches(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
1334 let instructions = &self.code.instructions;
1335 let mut branches = Vec::new();
1336 let mut extended_arg: u32 = 0;
1337
1338 for (i, unit) in instructions.iter().enumerate() {
1339 let op = unit.op.to_base().unwrap_or(unit.op);
1341 let raw_arg = u32::from(u8::from(unit.arg));
1342
1343 if matches!(op, Instruction::ExtendedArg) {
1344 extended_arg = (extended_arg | raw_arg) << 8;
1345 continue;
1346 }
1347
1348 let oparg = extended_arg | raw_arg;
1349 extended_arg = 0;
1350
1351 let caches = op.cache_entries();
1352 let (src, left, right) = match op {
1353 Instruction::ForIter { .. } => {
1354 let after_cache = i + 1 + caches;
1357 let target = after_cache + oparg as usize;
1358 let right = if matches!(
1359 instructions.get(target).map(|u| u.op),
1360 Some(Instruction::EndFor | Instruction::InstrumentedEndFor)
1361 ) {
1362 (target + 2) * 2
1363 } else {
1364 target * 2
1365 };
1366 (i * 2, after_cache * 2, right)
1367 }
1368 Instruction::PopJumpIfFalse { .. }
1369 | Instruction::PopJumpIfTrue { .. }
1370 | Instruction::PopJumpIfNone { .. }
1371 | Instruction::PopJumpIfNotNone { .. } => {
1372 let after_cache = i + 1 + caches;
1375 let next_op = instructions
1376 .get(after_cache)
1377 .map(|u| u.op.to_base().unwrap_or(u.op));
1378 let fallthrough = if matches!(next_op, Some(Instruction::NotTaken)) {
1379 (after_cache + 1) * 2
1380 } else {
1381 after_cache * 2
1382 };
1383 let right_target = after_cache + oparg as usize;
1384 (i * 2, fallthrough, right_target * 2)
1385 }
1386 Instruction::EndAsyncFor => {
1387 let next_i = i + 1;
1389 let Some(src_i) = next_i.checked_sub(oparg as usize) else {
1390 continue;
1391 };
1392 (src_i * 2, (src_i + 2) * 2, next_i * 2)
1394 }
1395 _ => continue,
1396 };
1397
1398 let tuple = vm.ctx.new_tuple(vec![
1399 vm.ctx.new_int(src).into(),
1400 vm.ctx.new_int(left).into(),
1401 vm.ctx.new_int(right).into(),
1402 ]);
1403 branches.push(tuple.into());
1404 }
1405
1406 let list = vm.ctx.new_list(branches);
1407 vm.call_method(list.as_object(), "__iter__", ())
1408 }
1409
1410 #[pymethod]
1411 pub fn __replace__(&self, args: ReplaceArgs, vm: &VirtualMachine) -> PyResult<PyCode> {
1412 self.replace(args, vm)
1413 }
1414
1415 #[pymethod]
1416 pub fn replace(&self, args: ReplaceArgs, vm: &VirtualMachine) -> PyResult<PyCode> {
1417 let ReplaceArgs {
1418 co_posonlyargcount,
1419 co_argcount,
1420 co_kwonlyargcount,
1421 co_filename,
1422 co_firstlineno,
1423 co_consts,
1424 co_name,
1425 co_names,
1426 co_flags,
1427 co_varnames,
1428 co_nlocals,
1429 co_stacksize,
1430 co_code,
1431 co_linetable,
1432 co_exceptiontable,
1433 co_freevars,
1434 co_cellvars,
1435 co_qualname,
1436 } = args;
1437 let posonlyarg_count = match co_posonlyargcount {
1438 OptionalArg::Present(posonlyarg_count) => posonlyarg_count,
1439 OptionalArg::Missing => self.code.posonlyarg_count,
1440 };
1441
1442 let arg_count = match co_argcount {
1443 OptionalArg::Present(arg_count) => arg_count,
1444 OptionalArg::Missing => self.code.arg_count,
1445 };
1446
1447 let source_path = match co_filename {
1448 OptionalArg::Present(source_path) => source_path,
1449 OptionalArg::Missing => self.source_path().to_owned(),
1450 };
1451
1452 let first_line_number = match co_firstlineno {
1453 OptionalArg::Present(first_line_number) => OneIndexed::new(first_line_number as _),
1454 OptionalArg::Missing => self.code.first_line_number,
1455 };
1456
1457 let kwonlyarg_count = match co_kwonlyargcount {
1458 OptionalArg::Present(kwonlyarg_count) => kwonlyarg_count,
1459 OptionalArg::Missing => self.code.kwonlyarg_count,
1460 };
1461
1462 let constants = match co_consts {
1463 OptionalArg::Present(constants) => constants,
1464 OptionalArg::Missing => self.code.constants.iter().map(|x| x.0.clone()).collect(),
1465 };
1466
1467 let obj_name = match co_name {
1468 OptionalArg::Present(obj_name) => obj_name,
1469 OptionalArg::Missing => self.code.obj_name.to_owned(),
1470 };
1471
1472 let names = match co_names {
1473 OptionalArg::Present(names) => names,
1474 OptionalArg::Missing => self
1475 .code
1476 .names
1477 .deref()
1478 .iter()
1479 .map(|name| name.to_pyobject(vm))
1480 .collect(),
1481 };
1482
1483 let flags = CodeFlags::from_bits_truncate(match co_flags {
1484 OptionalArg::Present(flags) => flags,
1485 OptionalArg::Missing => self.code.flags.bits(),
1486 });
1487
1488 let varname_objects = match co_varnames {
1489 OptionalArg::Present(varnames) => varnames,
1490 OptionalArg::Missing => self.code.varnames.iter().map(|s| s.to_object()).collect(),
1491 };
1492
1493 let qualname = match co_qualname {
1494 OptionalArg::Present(qualname) => qualname,
1495 OptionalArg::Missing => self.code.qualname.to_owned(),
1496 };
1497
1498 let max_stackdepth = match co_stacksize {
1500 OptionalArg::Present(stacksize) => stacksize,
1501 OptionalArg::Missing => self.code.max_stackdepth,
1502 }
1503 .max(1);
1504
1505 let instructions = match co_code {
1506 OptionalArg::Present(code_bytes) => {
1507 CodeUnits::try_from(code_bytes.as_bytes())
1509 .map_err(|e| vm.new_value_error(format!("invalid bytecode: {e}")))?
1510 }
1511 OptionalArg::Missing => self.code.instructions.clone(),
1512 };
1513
1514 let intern_all = |objs: Vec<PyObjectRef>, field: &str| -> PyResult<Box<[_]>> {
1515 objs.into_iter()
1516 .map(|o| {
1517 let s = o.downcast_ref::<super::pystr::PyStr>().ok_or_else(|| {
1518 vm.new_type_error(format!("{field} must be a tuple of strings"))
1519 })?;
1520 Ok(vm.ctx.intern_str(s.as_wtf8()))
1521 })
1522 .collect::<PyResult<Vec<_>>>()
1523 .map(Vec::into_boxed_slice)
1524 };
1525
1526 let varnames = intern_all(varname_objects, "co_varnames")?;
1527
1528 let cellvars = match co_cellvars {
1529 OptionalArg::Present(cellvars) => intern_all(cellvars, "co_cellvars")?,
1530 OptionalArg::Missing => self.code.cellvars.clone(),
1531 };
1532
1533 let freevars = match co_freevars {
1534 OptionalArg::Present(freevars) => intern_all(freevars, "co_freevars")?,
1535 OptionalArg::Missing => self.code.freevars.clone(),
1536 };
1537
1538 let nlocals = match co_nlocals {
1539 OptionalArg::Present(nlocals) => nlocals as usize,
1540 OptionalArg::Missing => self.code.varnames.len(),
1541 };
1542 if nlocals != varnames.len() {
1543 return Err(vm.new_value_error(format!(
1544 "co_nlocals ({}) != len(co_varnames) ({})",
1545 nlocals,
1546 varnames.len()
1547 )));
1548 }
1549
1550 let localspluskinds = build_localspluskinds(
1551 &varnames,
1552 &cellvars,
1553 &freevars,
1554 (posonlyarg_count, arg_count, kwonlyarg_count),
1555 flags,
1556 &instructions,
1557 )
1558 .map_err(|index| {
1559 vm.new_value_error(format!(
1560 "code: LOAD_FAST_AND_CLEAR oparg {index} out of range"
1561 ))
1562 })?;
1563
1564 let linetable = match co_linetable {
1566 OptionalArg::Present(linetable) => linetable.as_bytes().to_vec().into_boxed_slice(),
1567 OptionalArg::Missing => self.code.linetable.clone(),
1568 };
1569
1570 let exceptiontable = match co_exceptiontable {
1571 OptionalArg::Present(exceptiontable) => {
1572 exceptiontable.as_bytes().to_vec().into_boxed_slice()
1573 }
1574 OptionalArg::Missing => self.code.exceptiontable.clone(),
1575 };
1576
1577 let new_code = CodeObject {
1578 flags,
1579 posonlyarg_count,
1580 arg_count,
1581 kwonlyarg_count,
1582 source_path: vm.ctx.intern_str(source_path.as_wtf8()),
1583 first_line_number,
1584 obj_name: vm.ctx.intern_str(obj_name.as_wtf8()),
1585 qualname: vm.ctx.intern_str(qualname.as_wtf8()),
1586
1587 max_stackdepth,
1588 instructions,
1589 locations: self.code.locations.clone(),
1592 constants: constants.into_iter().map(Literal).collect(),
1593 names: intern_all(names, "co_names")?,
1594 varnames,
1595 cellvars,
1596 freevars,
1597 localspluskinds,
1598 linetable,
1599 exceptiontable,
1600 };
1601
1602 Ok(PyCode::new(new_code))
1603 }
1604
1605 #[pymethod]
1606 fn _varname_from_oparg(&self, opcode: i32, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
1607 let idx_err = |vm: &VirtualMachine| vm.new_index_error("tuple index out of range");
1608
1609 let idx = usize::try_from(opcode).map_err(|_| idx_err(vm))?;
1610
1611 let varnames_len = self.code.varnames.len();
1612 let nonparam_cellvars: Vec<_> = self
1614 .code
1615 .cellvars
1616 .iter()
1617 .filter(|s| {
1618 let s_str: &str = s.as_ref();
1619 !self.code.varnames.iter().any(|v| {
1620 let v_str: &str = v.as_ref();
1621 v_str == s_str
1622 })
1623 })
1624 .collect();
1625 let nonparam_len = nonparam_cellvars.len();
1626
1627 let name = if idx < varnames_len {
1628 self.code.varnames.get(idx).ok_or_else(|| idx_err(vm))?
1630 } else if idx < varnames_len + nonparam_len {
1631 *nonparam_cellvars
1633 .get(idx - varnames_len)
1634 .ok_or_else(|| idx_err(vm))?
1635 } else {
1636 self.code
1638 .freevars
1639 .get(idx - varnames_len - nonparam_len)
1640 .ok_or_else(|| idx_err(vm))?
1641 };
1642 Ok(name.to_object())
1643 }
1644}
1645
1646impl ToPyObject for CodeObject {
1647 fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
1648 vm.ctx.new_code(self).into()
1649 }
1650}
1651
1652impl ToPyObject for bytecode::CodeObject {
1653 fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
1654 PyCode::new_ref_from_bytecode(vm, self).into()
1655 }
1656}
1657
1658struct LineTableReader<'a> {
1660 data: &'a [u8],
1661 pos: usize,
1662}
1663
1664impl<'a> LineTableReader<'a> {
1665 fn new(data: &'a [u8]) -> Self {
1666 Self { data, pos: 0 }
1667 }
1668
1669 fn read_byte(&mut self) -> Option<u8> {
1670 if self.pos < self.data.len() {
1671 let byte = self.data[self.pos];
1672 self.pos += 1;
1673 Some(byte)
1674 } else {
1675 None
1676 }
1677 }
1678
1679 fn peek_byte(&self) -> Option<u8> {
1680 if self.pos < self.data.len() {
1681 Some(self.data[self.pos])
1682 } else {
1683 None
1684 }
1685 }
1686
1687 fn read_varint(&mut self) -> u32 {
1688 if let Some(first) = self.read_byte() {
1689 let mut val = (first & 0x3f) as u32;
1690 let mut shift = 0;
1691 let mut byte = first;
1692 while (byte & 0x40) != 0 {
1693 if let Some(next) = self.read_byte() {
1694 shift += 6;
1695 val |= ((next & 0x3f) as u32) << shift;
1696 byte = next;
1697 } else {
1698 break;
1699 }
1700 }
1701 val
1702 } else {
1703 0
1704 }
1705 }
1706
1707 fn read_signed_varint(&mut self) -> i32 {
1708 let uval = self.read_varint();
1709 if uval & 1 != 0 {
1710 -((uval >> 1) as i32)
1711 } else {
1712 (uval >> 1) as i32
1713 }
1714 }
1715
1716 fn at_end(&self) -> bool {
1717 self.pos >= self.data.len()
1718 }
1719}
1720
1721pub(crate) fn init(ctx: &'static Context) {
1722 PyCode::extend_class(ctx, ctx.types.code_type);
1723}