mirror of
https://github.com/erg-lang/erg.git
synced 2025-07-08 13:45:20 +00:00
883 lines
31 KiB
Rust
883 lines
31 KiB
Rust
use std::fmt;
|
|
use std::fmt::Write as _;
|
|
use std::fs::File;
|
|
use std::io::{BufReader, Read, Write as _};
|
|
use std::path::Path;
|
|
use std::process::ExitStatus;
|
|
|
|
use erg_common::config::ErgConfig;
|
|
use erg_common::impl_display_from_debug;
|
|
#[allow(unused_imports)]
|
|
use erg_common::log;
|
|
use erg_common::opcode::CommonOpcode;
|
|
use erg_common::opcode308::Opcode308;
|
|
use erg_common::opcode309::Opcode309;
|
|
use erg_common::opcode310::Opcode310;
|
|
use erg_common::opcode311::{BinOpCode, Opcode311};
|
|
use erg_common::python_util::{env_magic_number, exec_pyc_code, PythonVersion};
|
|
use erg_common::serialize::*;
|
|
use erg_common::Str;
|
|
|
|
use super::deserialize::{DeserializeResult, Deserializer};
|
|
use super::value::ValueObj;
|
|
use super::{HasType, Type, TypePair};
|
|
|
|
pub fn consts_into_bytes(consts: Vec<ValueObj>, python_ver: PythonVersion) -> Vec<u8> {
|
|
let mut tuple = vec![];
|
|
if consts.len() > u8::MAX as usize {
|
|
tuple.push(DataTypePrefix::Tuple as u8);
|
|
tuple.append(&mut (consts.len() as u32).to_le_bytes().to_vec());
|
|
} else {
|
|
tuple.push(DataTypePrefix::SmallTuple as u8);
|
|
tuple.push(consts.len() as u8);
|
|
}
|
|
for obj in consts {
|
|
tuple.append(&mut obj.into_bytes(python_ver));
|
|
}
|
|
tuple
|
|
}
|
|
|
|
pub fn tuple_into_bytes(tup: &[ValueObj], python_ver: PythonVersion) -> Vec<u8> {
|
|
let mut bytes = Vec::with_capacity(tup.len());
|
|
if tup.len() <= u8::MAX as usize {
|
|
bytes.push(DataTypePrefix::SmallTuple as u8);
|
|
bytes.push(tup.len() as u8);
|
|
for obj in tup.iter().cloned() {
|
|
bytes.append(&mut obj.into_bytes(python_ver));
|
|
}
|
|
} else {
|
|
bytes.push(DataTypePrefix::Tuple as u8);
|
|
bytes.append(&mut (tup.len() as u32).to_le_bytes().to_vec());
|
|
for obj in tup.iter().cloned() {
|
|
bytes.append(&mut obj.into_bytes(python_ver));
|
|
}
|
|
}
|
|
bytes
|
|
}
|
|
|
|
pub fn jump_abs_addr(minor_ver: u8, op: u8, idx: usize, arg: usize) -> usize {
|
|
match minor_ver {
|
|
7..=9 => jump_abs_addr_309(Opcode309::try_from(op).unwrap(), idx, arg),
|
|
10 => jump_abs_addr_310(Opcode310::try_from(op).unwrap(), idx, arg),
|
|
11 => jump_abs_addr_311(Opcode311::try_from(op).unwrap(), idx, arg),
|
|
n => todo!("unsupported version: {n}"),
|
|
}
|
|
}
|
|
|
|
fn jump_abs_addr_309(op: Opcode309, idx: usize, arg: usize) -> usize {
|
|
match op {
|
|
Opcode309::FOR_ITER | Opcode309::JUMP_FORWARD => idx + arg + 2,
|
|
Opcode309::JUMP_ABSOLUTE | Opcode309::POP_JUMP_IF_FALSE | Opcode309::POP_JUMP_IF_TRUE => {
|
|
arg
|
|
}
|
|
Opcode309::SETUP_WITH => idx + arg + 2,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn jump_abs_addr_310(op: Opcode310, idx: usize, arg: usize) -> usize {
|
|
match op {
|
|
Opcode310::FOR_ITER | Opcode310::JUMP_FORWARD => idx + arg * 2 + 2,
|
|
Opcode310::JUMP_ABSOLUTE | Opcode310::POP_JUMP_IF_FALSE | Opcode310::POP_JUMP_IF_TRUE => {
|
|
arg * 2
|
|
}
|
|
Opcode310::SETUP_WITH => idx + arg * 2 + 2,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn jump_abs_addr_311(op: Opcode311, idx: usize, arg: usize) -> usize {
|
|
match op {
|
|
Opcode311::POP_JUMP_FORWARD_IF_FALSE
|
|
| Opcode311::POP_JUMP_FORWARD_IF_TRUE
|
|
| Opcode311::JUMP_BACKWARD
|
|
| Opcode311::FOR_ITER
|
|
| Opcode311::JUMP_FORWARD => idx + arg * 2 + 2,
|
|
Opcode311::POP_JUMP_BACKWARD_IF_FALSE | Opcode311::POP_JUMP_BACKWARD_IF_TRUE => {
|
|
idx - arg * 2 + 2
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
/// Kind can be multiple (e.g. Local + Cell = 0x60)
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
#[repr(u8)]
|
|
pub enum FastKind {
|
|
Local = 0x20,
|
|
Cell = 0x40,
|
|
Free = 0x80,
|
|
}
|
|
|
|
impl TryFrom<u8> for FastKind {
|
|
type Error = &'static str;
|
|
fn try_from(kind: u8) -> Result<Self, Self::Error> {
|
|
match kind {
|
|
0x20 => Ok(Self::Local),
|
|
0x40 => Ok(Self::Cell),
|
|
0x80 => Ok(Self::Free),
|
|
_ => Err("invalid kind"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Bit masks for CodeObj.flags
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[repr(u32)]
|
|
pub enum CodeObjFlags {
|
|
Optimized = 0x0001,
|
|
NewLocals = 0x0002,
|
|
VarArgs = 0x0004,
|
|
VarKeywords = 0x0008,
|
|
Nested = 0x0010,
|
|
Generator = 0x0020,
|
|
NoFree = 0x0040,
|
|
Coroutine = 0x0080,
|
|
IterableCoroutine = 0x0100,
|
|
AsyncGenerator = 0x0200,
|
|
// CO_GENERATOR_ALLOWED = 0x0400,
|
|
FutureDivision = 0x2000,
|
|
FutureAbsoluteImport = 0x4000,
|
|
FutureWithStatement = 0x8000,
|
|
FuturePrintFunction = 0x1_0000,
|
|
FutureUnicodeLiterals = 0x2_0000,
|
|
FutureBarryAsBDFL = 0x4_0000,
|
|
FutureGeneratorStop = 0x8_0000,
|
|
FutureAnnotations = 0x10_0000,
|
|
// Erg-specific flags
|
|
EvmDynParam = 0x1000_0000,
|
|
EvmNoGC = 0x4000_0000,
|
|
Illegal = 0x0000,
|
|
}
|
|
|
|
impl From<u32> for CodeObjFlags {
|
|
fn from(flags: u32) -> Self {
|
|
match flags {
|
|
0x0001 => Self::Optimized,
|
|
0x0002 => Self::NewLocals,
|
|
0x0004 => Self::VarArgs,
|
|
0x0008 => Self::VarKeywords,
|
|
0x0010 => Self::Nested,
|
|
0x0020 => Self::Generator,
|
|
0x0040 => Self::NoFree,
|
|
0x0080 => Self::Coroutine,
|
|
0x0100 => Self::IterableCoroutine,
|
|
0x0200 => Self::AsyncGenerator,
|
|
// CO_GENERATOR_ALLOWED,
|
|
0x2000 => Self::FutureDivision,
|
|
0x4000 => Self::FutureAbsoluteImport,
|
|
0x8000 => Self::FutureWithStatement,
|
|
0x1_0000 => Self::FuturePrintFunction,
|
|
0x2_0000 => Self::FutureUnicodeLiterals,
|
|
0x4_0000 => Self::FutureBarryAsBDFL,
|
|
0x8_0000 => Self::FutureGeneratorStop,
|
|
0x10_0000 => Self::FutureAnnotations,
|
|
// EVM flags
|
|
0x1000_0000 => Self::EvmDynParam,
|
|
0x4000_0000 => Self::EvmNoGC,
|
|
_ => Self::Illegal,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CodeObjFlags {
|
|
pub const fn is_in(&self, flags: u32) -> bool {
|
|
(flags & *self as u32) != 0
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
#[repr(u8)]
|
|
pub enum MakeFunctionFlags {
|
|
None = 0,
|
|
Defaults = 1,
|
|
KwDefaults = 2,
|
|
Annotations = 4,
|
|
Closure = 8,
|
|
}
|
|
|
|
impl From<u8> for MakeFunctionFlags {
|
|
fn from(flags: u8) -> Self {
|
|
match flags {
|
|
0 => Self::None,
|
|
1 => Self::Defaults,
|
|
2 => Self::KwDefaults,
|
|
4 => Self::Annotations,
|
|
8 => Self::Closure,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Implementation of `PyCodeObject`, see Include/cpython/code.h in CPython for details.
|
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
|
pub struct CodeObj {
|
|
pub argcount: u32,
|
|
pub posonlyargcount: u32,
|
|
pub kwonlyargcount: u32,
|
|
pub nlocals: u32, // == params + local vars
|
|
pub stacksize: u32,
|
|
pub flags: u32,
|
|
pub code: Vec<u8>,
|
|
pub consts: Vec<ValueObj>, // objects used in the code (literal)
|
|
pub names: Vec<Str>, // names used in the code object
|
|
pub varnames: Vec<Str>, // names defined in the code object
|
|
pub freevars: Vec<Str>, // names captured from the outer scope
|
|
pub cellvars: Vec<Str>, // names used in the inner function (closure)
|
|
pub filename: Str,
|
|
pub name: Str,
|
|
pub qualname: Str,
|
|
pub firstlineno: u32,
|
|
// lnotab (line number table): see Object/lnotab_notes.txt in CPython for details
|
|
// e.g. +12bytes, +3line -> [.., 0x1C, 0x03, ..]
|
|
// ([sdelta, ldelta, sdelta, ldelta, ..])
|
|
// if delta > 255 -> [255, 0, 255-delta, ...]
|
|
pub lnotab: Vec<u8>,
|
|
pub exceptiontable: Vec<u8>,
|
|
}
|
|
|
|
impl HasType for CodeObj {
|
|
fn ref_t(&self) -> &Type {
|
|
&Type::Code
|
|
}
|
|
fn ref_mut_t(&mut self) -> Option<&mut Type> {
|
|
None
|
|
}
|
|
fn signature_t(&self) -> Option<&Type> {
|
|
None
|
|
}
|
|
fn signature_mut_t(&mut self) -> Option<&mut Type> {
|
|
None
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for CodeObj {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"<code object {} at {:p}, file \"{}\", line {}>",
|
|
self.name, self, self.filename, self.firstlineno
|
|
)
|
|
}
|
|
}
|
|
|
|
impl_display_from_debug!(CodeObj);
|
|
|
|
impl Default for CodeObj {
|
|
fn default() -> Self {
|
|
Self {
|
|
argcount: 0,
|
|
posonlyargcount: 0,
|
|
kwonlyargcount: 0,
|
|
nlocals: 0,
|
|
stacksize: 2, // Seems to be the default in CPython, but not sure why
|
|
flags: 0, //CodeObjFlags::NoFree as u32,
|
|
code: Vec::new(),
|
|
consts: Vec::new(),
|
|
names: Vec::new(),
|
|
varnames: Vec::new(),
|
|
freevars: Vec::new(),
|
|
cellvars: Vec::new(),
|
|
filename: "<dummy>".into(),
|
|
name: "<dummy>".into(),
|
|
qualname: "<dummy>".into(),
|
|
firstlineno: 1,
|
|
lnotab: Vec::new(),
|
|
exceptiontable: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CodeObj {
|
|
pub fn empty<S: Into<Str>, T: Into<Str>>(
|
|
params: Vec<Str>,
|
|
kwonlyargcount: u32,
|
|
filename: S,
|
|
name: T,
|
|
firstlineno: u32,
|
|
flags: u32,
|
|
) -> Self {
|
|
let name = name.into();
|
|
let var_args_defined = (flags & CodeObjFlags::VarArgs as u32 != 0) as u32;
|
|
let kw_var_args_defined = (flags & CodeObjFlags::VarKeywords as u32 != 0) as u32;
|
|
Self {
|
|
argcount: params.len() as u32 - var_args_defined - kw_var_args_defined - kwonlyargcount,
|
|
posonlyargcount: 0,
|
|
kwonlyargcount,
|
|
nlocals: params.len() as u32,
|
|
stacksize: 2, // Seems to be the default in CPython, but not sure why
|
|
flags, // CodeObjFlags::NoFree as u32,
|
|
code: Vec::with_capacity(8),
|
|
consts: Vec::with_capacity(4),
|
|
names: Vec::with_capacity(3),
|
|
varnames: params,
|
|
freevars: Vec::new(),
|
|
cellvars: Vec::new(),
|
|
filename: filename.into(),
|
|
name: name.clone(),
|
|
qualname: name,
|
|
firstlineno,
|
|
lnotab: Vec::with_capacity(4),
|
|
exceptiontable: Vec::with_capacity(0),
|
|
}
|
|
}
|
|
|
|
pub fn from_pyc<P: AsRef<Path>>(path: P) -> DeserializeResult<(Self, PythonVersion)> {
|
|
let mut f = BufReader::new(File::open(path)?);
|
|
let v = &mut Vec::with_capacity(16);
|
|
f.read_to_end(v)?;
|
|
let magic_num = get_magic_num_from_bytes(&Deserializer::consume::<4>(v));
|
|
let python_ver = get_ver_from_magic_num(magic_num);
|
|
let _padding = Deserializer::deserialize_u32(v);
|
|
let _timestamp = Deserializer::deserialize_u32(v);
|
|
let _padding = Deserializer::deserialize_u32(v);
|
|
let code = Self::from_bytes(v, python_ver)?;
|
|
Ok((code, python_ver))
|
|
}
|
|
|
|
pub fn from_bytes(v: &mut Vec<u8>, python_ver: PythonVersion) -> DeserializeResult<Self> {
|
|
assert_eq!(v.remove(0), DataTypePrefix::Code as u8, "not a code object");
|
|
let mut des = Deserializer::new();
|
|
let argcount = Deserializer::deserialize_u32(v);
|
|
let posonlyargcount = if python_ver.minor >= Some(8) {
|
|
Deserializer::deserialize_u32(v)
|
|
} else {
|
|
0
|
|
};
|
|
let kwonlyargcount = Deserializer::deserialize_u32(v);
|
|
let nlocals = if python_ver.minor >= Some(11) {
|
|
0
|
|
} else {
|
|
Deserializer::deserialize_u32(v)
|
|
};
|
|
let stacksize = Deserializer::deserialize_u32(v);
|
|
let flags = Deserializer::deserialize_u32(v);
|
|
let code = des.deserialize_bytes(v)?;
|
|
let consts = des.deserialize_const_vec(v, python_ver, Some("consts"))?;
|
|
let names = des.deserialize_str_vec(v, python_ver, Some("names"))?;
|
|
let (varnames, freevars, cellvars) = des.deserialize_locals(v, python_ver)?;
|
|
let filename = des.deserialize_str(v, python_ver, Some("filename"))?;
|
|
let name = des.deserialize_str(v, python_ver, Some("name"))?;
|
|
let qualname = if python_ver.minor >= Some(11) {
|
|
des.deserialize_str(v, python_ver, Some("qualname"))?
|
|
} else {
|
|
name.clone()
|
|
};
|
|
let firstlineno = Deserializer::deserialize_u32(v);
|
|
let lnotab = des.deserialize_bytes(v)?;
|
|
let exceptiontable = if python_ver.minor >= Some(11) {
|
|
des.deserialize_bytes(v)?
|
|
} else {
|
|
vec![]
|
|
};
|
|
Ok(CodeObj {
|
|
argcount,
|
|
posonlyargcount,
|
|
kwonlyargcount,
|
|
nlocals,
|
|
stacksize,
|
|
flags,
|
|
code,
|
|
consts,
|
|
names,
|
|
varnames,
|
|
freevars,
|
|
cellvars,
|
|
filename,
|
|
name,
|
|
qualname,
|
|
firstlineno,
|
|
lnotab,
|
|
exceptiontable,
|
|
})
|
|
}
|
|
|
|
pub fn into_bytes(self, python_ver: PythonVersion) -> Vec<u8> {
|
|
let mut bytes = vec![DataTypePrefix::Code as u8];
|
|
bytes.append(&mut self.argcount.to_le_bytes().to_vec());
|
|
if python_ver.minor >= Some(8) {
|
|
bytes.append(&mut self.posonlyargcount.to_le_bytes().to_vec());
|
|
}
|
|
bytes.append(&mut self.kwonlyargcount.to_le_bytes().to_vec());
|
|
if python_ver.minor < Some(11) {
|
|
bytes.append(&mut self.nlocals.to_le_bytes().to_vec());
|
|
}
|
|
bytes.append(&mut self.stacksize.to_le_bytes().to_vec());
|
|
bytes.append(&mut self.flags.to_le_bytes().to_vec());
|
|
// co_code is represented as PyStrObject (Not Ascii, Unicode)
|
|
bytes.append(&mut raw_string_into_bytes(self.code));
|
|
bytes.append(&mut consts_into_bytes(self.consts, python_ver)); // write as PyTupleObject
|
|
bytes.append(&mut strs_into_bytes(self.names));
|
|
Self::dump_locals(
|
|
self.varnames,
|
|
self.freevars,
|
|
self.cellvars,
|
|
&mut bytes,
|
|
python_ver,
|
|
);
|
|
bytes.append(&mut str_into_bytes(self.filename, false));
|
|
bytes.append(&mut str_into_bytes(self.name, true));
|
|
if python_ver.minor >= Some(11) {
|
|
bytes.append(&mut str_into_bytes(self.qualname, true));
|
|
}
|
|
bytes.append(&mut self.firstlineno.to_le_bytes().to_vec());
|
|
// lnotab is represented as PyStrObject
|
|
bytes.append(&mut raw_string_into_bytes(self.lnotab));
|
|
if python_ver.minor >= Some(11) {
|
|
bytes.append(&mut raw_string_into_bytes(self.exceptiontable));
|
|
}
|
|
bytes
|
|
}
|
|
|
|
fn dump_locals(
|
|
varnames: Vec<Str>,
|
|
freevars: Vec<Str>,
|
|
cellvars: Vec<Str>,
|
|
bytes: &mut Vec<u8>,
|
|
python_ver: PythonVersion,
|
|
) {
|
|
if python_ver.minor >= Some(11) {
|
|
let varnames = varnames
|
|
.into_iter()
|
|
.filter(|n| !freevars.contains(n) && !cellvars.contains(n))
|
|
.collect::<Vec<_>>();
|
|
let localspluskinds = [
|
|
vec![FastKind::Local as u8; varnames.len()],
|
|
vec![FastKind::Free as u8; freevars.len()],
|
|
vec![FastKind::Cell as u8 + FastKind::Local as u8; cellvars.len()],
|
|
]
|
|
.concat();
|
|
let localsplusnames = [varnames, freevars, cellvars].concat();
|
|
bytes.append(&mut strs_into_bytes(localsplusnames));
|
|
bytes.append(&mut raw_string_into_bytes(localspluskinds));
|
|
} else {
|
|
bytes.append(&mut strs_into_bytes(varnames));
|
|
bytes.append(&mut strs_into_bytes(freevars));
|
|
bytes.append(&mut strs_into_bytes(cellvars));
|
|
}
|
|
}
|
|
|
|
pub fn dump_as_pyc<P: AsRef<Path>>(
|
|
self,
|
|
path: P,
|
|
py_magic_num: Option<u32>,
|
|
) -> std::io::Result<()> {
|
|
let mut file = File::create(path)?;
|
|
let bytes = self.into_bytecode(py_magic_num);
|
|
file.write_all(&bytes[..])?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn into_bytecode(self, py_magic_num: Option<u32>) -> Vec<u8> {
|
|
let mut bytes = Vec::with_capacity(16);
|
|
let py_magic_num = py_magic_num.unwrap_or_else(env_magic_number);
|
|
let python_ver = get_ver_from_magic_num(py_magic_num);
|
|
bytes.append(&mut get_magic_num_bytes(py_magic_num).to_vec());
|
|
bytes.append(&mut vec![0; 4]); // padding
|
|
bytes.append(&mut get_timestamp_bytes().to_vec());
|
|
bytes.append(&mut vec![0; 4]); // padding
|
|
bytes.append(&mut self.into_bytes(python_ver));
|
|
bytes
|
|
}
|
|
|
|
/// Embed bytecode in a Python script.
|
|
/// This may generate a huge script, so don't pass it to `python -c`, but dump the bytecode and exec it.
|
|
pub fn into_script(self, py_magic_num: Option<u32>) -> String {
|
|
let mut bytes = Vec::with_capacity(16);
|
|
let py_magic_num = py_magic_num.unwrap_or_else(env_magic_number);
|
|
let python_ver = get_ver_from_magic_num(py_magic_num);
|
|
bytes.append(&mut self.into_bytes(python_ver));
|
|
let mut bytecode = "".to_string();
|
|
for b in bytes {
|
|
write!(bytecode, "\\x{b:0>2x}").unwrap();
|
|
}
|
|
format!("import marshal; exec(marshal.loads(b'{bytecode}'))")
|
|
}
|
|
|
|
pub fn exec(self, cfg: &ErgConfig) -> std::io::Result<ExitStatus> {
|
|
exec_pyc_code(
|
|
&self.into_bytecode(cfg.py_magic_num),
|
|
&cfg.runtime_args,
|
|
cfg.output.clone(),
|
|
)
|
|
}
|
|
|
|
fn tables_info(&self) -> String {
|
|
let mut tables = "".to_string();
|
|
if !self.consts.is_empty() {
|
|
tables += "Constants:\n";
|
|
}
|
|
for (i, obj) in self.consts.iter().enumerate() {
|
|
writeln!(tables, " {i}: {obj}").unwrap();
|
|
}
|
|
if !self.names.is_empty() {
|
|
tables += "Names:\n";
|
|
}
|
|
for (i, name) in self.names.iter().enumerate() {
|
|
writeln!(tables, " {i}: {name}").unwrap();
|
|
}
|
|
if !self.varnames.is_empty() {
|
|
tables += "Varnames:\n";
|
|
}
|
|
for (i, varname) in self.varnames.iter().enumerate() {
|
|
writeln!(tables, " {i}: {varname}").unwrap();
|
|
}
|
|
if !self.cellvars.is_empty() {
|
|
tables += "Cellvars:\n";
|
|
}
|
|
for (i, cellvar) in self.cellvars.iter().enumerate() {
|
|
writeln!(tables, " {i}: {cellvar}").unwrap();
|
|
}
|
|
if !self.freevars.is_empty() {
|
|
tables += "Freevars:\n";
|
|
}
|
|
for (i, freevar) in self.freevars.iter().enumerate() {
|
|
writeln!(tables, " {i}: {freevar}\n").unwrap();
|
|
}
|
|
tables
|
|
}
|
|
|
|
fn attrs_info(&self) -> String {
|
|
let mut attrs = "".to_string();
|
|
writeln!(attrs, "Name: {}", self.name).unwrap();
|
|
writeln!(attrs, "FileName: {}", self.filename).unwrap();
|
|
writeln!(attrs, "Argument count: {}", self.argcount).unwrap();
|
|
writeln!(attrs, "Positional-only arguments: {}", self.posonlyargcount).unwrap();
|
|
writeln!(attrs, "Kw-only arguments: {}", self.kwonlyargcount).unwrap();
|
|
writeln!(attrs, "Number of locals: {}", self.nlocals).unwrap();
|
|
writeln!(attrs, "Stack size: {}", self.stacksize).unwrap();
|
|
let mut flagged = "".to_string();
|
|
for i in 0..32 {
|
|
if (self.flags & (1 << i)) != 0 {
|
|
let flag: CodeObjFlags = 2u32.pow(i).into();
|
|
write!(flagged, "{flag:?}, ").unwrap();
|
|
}
|
|
}
|
|
flagged.pop();
|
|
flagged.pop();
|
|
writeln!(attrs, "Flags: {flagged}").unwrap();
|
|
attrs
|
|
}
|
|
|
|
fn instr_info(&self, py_ver: Option<PythonVersion>) -> String {
|
|
let mut lnotab_iter = self.lnotab.iter();
|
|
let mut code_iter = self.code.iter();
|
|
let mut idx = 0usize;
|
|
let mut line_offset = 0usize;
|
|
let mut lineno = self.firstlineno;
|
|
let mut sdelta = lnotab_iter.next().unwrap_or(&0);
|
|
let mut ldelta = lnotab_iter.next().unwrap_or(&0);
|
|
let mut instrs = "".to_string();
|
|
writeln!(instrs, "lnotab: {:?}", self.lnotab).unwrap();
|
|
if *sdelta != 0 {
|
|
writeln!(instrs, "{lineno}:").unwrap();
|
|
}
|
|
let mut extended_arg = vec![];
|
|
loop {
|
|
if *sdelta as usize == line_offset {
|
|
line_offset = 0;
|
|
lineno += *ldelta as u32;
|
|
writeln!(instrs, "{lineno}:").unwrap();
|
|
sdelta = lnotab_iter.next().unwrap_or(&0);
|
|
ldelta = lnotab_iter.next().unwrap_or(&0);
|
|
}
|
|
if let (Some(op), Some(arg)) = (code_iter.next(), code_iter.next()) {
|
|
let pushed = if CommonOpcode::try_from(*op) == Ok(CommonOpcode::EXTENDED_ARG) {
|
|
extended_arg.push(*arg);
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
let arg = if extended_arg.is_empty() || pushed {
|
|
*arg as usize
|
|
} else {
|
|
let arg = match extended_arg.len() {
|
|
1 => u16::from_be_bytes([extended_arg[0], *arg]) as usize,
|
|
3 => u32::from_be_bytes([
|
|
extended_arg[0],
|
|
extended_arg[1],
|
|
extended_arg[2],
|
|
*arg,
|
|
]) as usize,
|
|
n => unreachable!("{n}"),
|
|
};
|
|
extended_arg.clear();
|
|
arg
|
|
};
|
|
match py_ver.and_then(|pv| pv.minor) {
|
|
Some(7 | 8) => self.read_instr_308(op, arg, idx, &mut instrs),
|
|
Some(9) => self.read_instr_309(op, arg, idx, &mut instrs),
|
|
Some(10) => self.read_instr_310(op, arg, idx, &mut instrs),
|
|
Some(11) => self.read_instr_311(op, arg, idx, &mut instrs),
|
|
_ => {}
|
|
}
|
|
idx += 2;
|
|
line_offset += 2;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
instrs
|
|
}
|
|
|
|
fn read_instr_308(&self, op: &u8, arg: usize, idx: usize, instrs: &mut String) {
|
|
let op308 = Opcode308::try_from(*op).unwrap();
|
|
let s_op = op308.to_string();
|
|
write!(instrs, "{idx:>15} {s_op:<25}").unwrap();
|
|
if let Ok(op) = CommonOpcode::try_from(*op) {
|
|
self.dump_additional_info(op, arg, idx, instrs);
|
|
}
|
|
match op308 {
|
|
Opcode308::STORE_DEREF | Opcode308::LOAD_DEREF => {
|
|
write!(
|
|
instrs,
|
|
"{arg} ({})",
|
|
self.freevars
|
|
.get(arg)
|
|
.unwrap_or_else(|| &self.cellvars[arg])
|
|
)
|
|
.unwrap();
|
|
}
|
|
Opcode308::LOAD_CLOSURE => {
|
|
write!(instrs, "{arg} ({})", self.cellvars[arg]).unwrap();
|
|
}
|
|
Opcode308::JUMP_ABSOLUTE => {
|
|
write!(instrs, "{arg} (to {})", arg).unwrap();
|
|
}
|
|
Opcode308::JUMP_FORWARD => {
|
|
write!(instrs, "{arg} (to {})", idx + arg + 2).unwrap();
|
|
}
|
|
// REVIEW: *2?
|
|
Opcode308::POP_JUMP_IF_FALSE | Opcode308::POP_JUMP_IF_TRUE => {
|
|
write!(instrs, "{arg} (to {})", arg).unwrap();
|
|
}
|
|
Opcode308::BINARY_ADD
|
|
| Opcode308::BINARY_SUBTRACT
|
|
| Opcode308::BINARY_MULTIPLY
|
|
| Opcode308::BINARY_TRUE_DIVIDE => {
|
|
write!(instrs, "{arg} ({:?})", TypePair::from(arg as u8)).unwrap();
|
|
}
|
|
Opcode308::CALL_FUNCTION
|
|
| Opcode308::CALL_FUNCTION_EX
|
|
| Opcode308::CALL_FUNCTION_KW
|
|
| Opcode308::CALL_METHOD => {
|
|
write!(instrs, "{arg}").unwrap();
|
|
}
|
|
_ => {}
|
|
}
|
|
instrs.push('\n');
|
|
}
|
|
|
|
fn read_instr_309(&self, op: &u8, arg: usize, idx: usize, instrs: &mut String) {
|
|
let op309 = Opcode309::try_from(*op).unwrap();
|
|
let s_op = op309.to_string();
|
|
write!(instrs, "{idx:>15} {s_op:<25}").unwrap();
|
|
if let Ok(op) = CommonOpcode::try_from(*op) {
|
|
self.dump_additional_info(op, arg, idx, instrs);
|
|
}
|
|
match op309 {
|
|
Opcode309::STORE_DEREF | Opcode309::LOAD_DEREF => {
|
|
write!(
|
|
instrs,
|
|
"{arg} ({})",
|
|
self.freevars
|
|
.get(arg)
|
|
.unwrap_or_else(|| &self.cellvars[arg])
|
|
)
|
|
.unwrap();
|
|
}
|
|
Opcode309::LOAD_CLOSURE => {
|
|
write!(instrs, "{arg} ({})", self.cellvars[arg]).unwrap();
|
|
}
|
|
Opcode309::JUMP_ABSOLUTE => {
|
|
write!(instrs, "{arg} (to {})", arg).unwrap();
|
|
}
|
|
Opcode309::JUMP_FORWARD => {
|
|
write!(instrs, "{arg} (to {})", idx + arg + 2).unwrap();
|
|
}
|
|
// REVIEW: *2?
|
|
Opcode309::POP_JUMP_IF_FALSE | Opcode309::POP_JUMP_IF_TRUE => {
|
|
write!(instrs, "{arg} (to {})", arg).unwrap();
|
|
}
|
|
Opcode309::BINARY_ADD
|
|
| Opcode309::BINARY_SUBTRACT
|
|
| Opcode309::BINARY_MULTIPLY
|
|
| Opcode309::BINARY_TRUE_DIVIDE => {
|
|
write!(instrs, "{arg} ({:?})", TypePair::from(arg as u8)).unwrap();
|
|
}
|
|
Opcode309::CALL_FUNCTION
|
|
| Opcode309::CALL_FUNCTION_EX
|
|
| Opcode309::CALL_FUNCTION_KW
|
|
| Opcode309::CALL_METHOD => {
|
|
write!(instrs, "{arg}").unwrap();
|
|
}
|
|
_ => {}
|
|
}
|
|
instrs.push('\n');
|
|
}
|
|
|
|
fn read_instr_310(&self, op: &u8, arg: usize, idx: usize, instrs: &mut String) {
|
|
let op310 = Opcode310::try_from(*op).unwrap();
|
|
let s_op = op310.to_string();
|
|
write!(instrs, "{idx:>15} {s_op:<25}").unwrap();
|
|
if let Ok(op) = CommonOpcode::try_from(*op) {
|
|
self.dump_additional_info(op, arg, idx, instrs);
|
|
}
|
|
match op310 {
|
|
Opcode310::STORE_DEREF | Opcode310::LOAD_DEREF => {
|
|
write!(
|
|
instrs,
|
|
"{arg} ({})",
|
|
self.freevars
|
|
.get(arg)
|
|
.unwrap_or_else(|| &self.cellvars[arg])
|
|
)
|
|
.unwrap();
|
|
}
|
|
Opcode310::LOAD_CLOSURE => {
|
|
write!(instrs, "{arg} ({})", self.cellvars[arg]).unwrap();
|
|
}
|
|
Opcode310::JUMP_ABSOLUTE => {
|
|
write!(instrs, "{arg} (to {})", arg * 2).unwrap();
|
|
}
|
|
Opcode310::JUMP_FORWARD => {
|
|
write!(instrs, "{arg} (to {})", idx + arg * 2 + 2).unwrap();
|
|
}
|
|
Opcode310::POP_JUMP_IF_FALSE | Opcode310::POP_JUMP_IF_TRUE => {
|
|
write!(instrs, "{arg} (to {})", arg * 2).unwrap();
|
|
}
|
|
Opcode310::BINARY_ADD
|
|
| Opcode310::BINARY_SUBTRACT
|
|
| Opcode310::BINARY_MULTIPLY
|
|
| Opcode310::BINARY_TRUE_DIVIDE => {
|
|
write!(instrs, "{arg} ({:?})", TypePair::from(arg as u8)).unwrap();
|
|
}
|
|
Opcode310::CALL_FUNCTION
|
|
| Opcode310::CALL_FUNCTION_EX
|
|
| Opcode310::CALL_FUNCTION_KW
|
|
| Opcode310::CALL_METHOD => {
|
|
write!(instrs, "{arg}").unwrap();
|
|
}
|
|
Opcode310::SETUP_WITH => {
|
|
write!(instrs, "{arg} (to {})", idx + arg * 2 + 2).unwrap();
|
|
}
|
|
_ => {}
|
|
}
|
|
instrs.push('\n');
|
|
}
|
|
|
|
fn read_instr_311(&self, op: &u8, arg: usize, idx: usize, instrs: &mut String) {
|
|
let op311 = Opcode311::try_from(*op).unwrap();
|
|
let s_op = op311.to_string();
|
|
write!(instrs, "{idx:>15} {s_op:<26}").unwrap();
|
|
if let Ok(op) = CommonOpcode::try_from(*op) {
|
|
self.dump_additional_info(op, arg, idx, instrs);
|
|
}
|
|
match op311 {
|
|
Opcode311::STORE_DEREF | Opcode311::LOAD_DEREF => {
|
|
write!(instrs, "{arg} ({})", self.varnames[arg]).unwrap();
|
|
}
|
|
Opcode311::MAKE_CELL | Opcode311::LOAD_CLOSURE => {
|
|
write!(instrs, "{arg} ({})", self.varnames[arg]).unwrap();
|
|
}
|
|
Opcode311::POP_JUMP_FORWARD_IF_FALSE | Opcode311::POP_JUMP_FORWARD_IF_TRUE => {
|
|
write!(instrs, "{arg} (to {})", idx + arg * 2 + 2).unwrap();
|
|
}
|
|
Opcode311::POP_JUMP_BACKWARD_IF_FALSE | Opcode311::POP_JUMP_BACKWARD_IF_TRUE => {
|
|
write!(instrs, "{arg} (to {})", idx - arg * 2 + 2).unwrap();
|
|
}
|
|
Opcode311::JUMP_FORWARD => {
|
|
write!(instrs, "{arg} (to {})", idx + arg * 2 + 2).unwrap();
|
|
}
|
|
Opcode311::JUMP_BACKWARD => {
|
|
write!(instrs, "{arg} (to {})", idx - arg * 2 + 2).unwrap();
|
|
}
|
|
Opcode311::PRECALL
|
|
| Opcode311::CALL
|
|
| Opcode311::COPY
|
|
| Opcode311::SWAP
|
|
| Opcode311::COPY_FREE_VARS => {
|
|
write!(instrs, "{arg}").unwrap();
|
|
}
|
|
Opcode311::KW_NAMES => {
|
|
write!(instrs, "{arg} ({})", self.consts[arg]).unwrap();
|
|
}
|
|
Opcode311::BINARY_OP => {
|
|
write!(
|
|
instrs,
|
|
"{arg} ({:?})",
|
|
BinOpCode::try_from(arg as u8).unwrap()
|
|
)
|
|
.unwrap();
|
|
}
|
|
_ => {}
|
|
}
|
|
instrs.push('\n');
|
|
}
|
|
|
|
fn dump_additional_info(&self, op: CommonOpcode, arg: usize, idx: usize, instrs: &mut String) {
|
|
match op {
|
|
CommonOpcode::COMPARE_OP => {
|
|
let op = match arg {
|
|
0 => "<",
|
|
1 => "<=",
|
|
2 => "==",
|
|
3 => "!=",
|
|
4 => ">",
|
|
5 => ">=",
|
|
_ => "?",
|
|
};
|
|
write!(instrs, "{arg} ({op})").unwrap();
|
|
}
|
|
CommonOpcode::STORE_NAME
|
|
| CommonOpcode::LOAD_NAME
|
|
| CommonOpcode::STORE_GLOBAL
|
|
| CommonOpcode::LOAD_GLOBAL
|
|
| CommonOpcode::STORE_ATTR
|
|
| CommonOpcode::LOAD_ATTR
|
|
| CommonOpcode::LOAD_METHOD
|
|
| CommonOpcode::IMPORT_NAME
|
|
| CommonOpcode::IMPORT_FROM => {
|
|
write!(instrs, "{arg} ({})", self.names[arg]).unwrap();
|
|
}
|
|
CommonOpcode::STORE_FAST | CommonOpcode::LOAD_FAST => {
|
|
write!(instrs, "{arg} ({})", self.varnames[arg]).unwrap();
|
|
}
|
|
CommonOpcode::LOAD_CONST => {
|
|
write!(instrs, "{arg} ({})", self.consts[arg]).unwrap();
|
|
}
|
|
CommonOpcode::FOR_ITER => {
|
|
write!(instrs, "{arg} (to {})", idx + arg * 2 + 2).unwrap();
|
|
}
|
|
CommonOpcode::MAKE_FUNCTION => {
|
|
let flag = match arg {
|
|
8 => "(closure)",
|
|
4 => "(annotations)",
|
|
2 => "(kwdefaults)",
|
|
1 => "(defaults)",
|
|
_ => "",
|
|
};
|
|
write!(instrs, "{arg} {flag}").unwrap();
|
|
}
|
|
other if other.take_arg() => {
|
|
write!(instrs, "{arg}").unwrap();
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
pub fn code_info(&self, py_ver: Option<PythonVersion>) -> String {
|
|
let mut info = "".to_string();
|
|
writeln!(info, "Disassembly of {self:?}:").unwrap();
|
|
info += &self.attrs_info();
|
|
info += &self.tables_info();
|
|
info += &self.instr_info(py_ver);
|
|
info.push('\n');
|
|
for cons in self.consts.iter() {
|
|
if let ValueObj::Code(c) = cons {
|
|
info += &c.code_info(py_ver);
|
|
}
|
|
}
|
|
info
|
|
}
|
|
}
|