use std::cell::{Cell, RefCell};
use std::fmt;
use indexmap::IndexMap;
use itertools::Itertools;
use crate::bytecode;
use crate::exceptions::{self, ExceptionCtor, PyBaseExceptionRef};
use crate::function::{single_or_tuple_any, PyFuncArgs};
use crate::obj::objbool;
use crate::obj::objcode::PyCodeRef;
use crate::obj::objcoroutine::PyCoroutine;
use crate::obj::objdict::{PyDict, PyDictRef};
use crate::obj::objgenerator::PyGenerator;
use crate::obj::objiter;
use crate::obj::objlist;
use crate::obj::objslice::PySlice;
use crate::obj::objstr::{self, PyString};
use crate::obj::objtraceback::PyTraceback;
use crate::obj::objtuple::PyTuple;
use crate::obj::objtype::{self, PyClassRef};
use crate::pyobject::{
IdProtocol, ItemProtocol, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol,
};
use crate::scope::{NameProtocol, Scope};
use crate::vm::VirtualMachine;
#[derive(Clone, Debug)]
struct Block {
typ: BlockType,
level: usize,
}
#[derive(Clone, Debug)]
enum BlockType {
Loop {
start: bytecode::Label,
end: bytecode::Label,
},
TryExcept {
handler: bytecode::Label,
},
Finally {
handler: bytecode::Label,
},
FinallyHandler {
reason: Option<UnwindReason>,
},
ExceptHandler,
}
pub type FrameRef = PyRef<Frame>;
#[derive(Clone, Debug)]
enum UnwindReason {
Returning { value: PyObjectRef },
Raising { exception: PyBaseExceptionRef },
Break,
Continue,
}
#[pyclass]
#[derive(Clone)]
pub struct Frame {
pub code: PyCodeRef,
stack: RefCell<Vec<PyObjectRef>>,
blocks: RefCell<Vec<Block>>,
pub scope: Scope,
pub lasti: Cell<usize>,
}
impl PyValue for Frame {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.frame_type()
}
}
pub enum ExecutionResult {
Return(PyObjectRef),
Yield(PyObjectRef),
}
impl ExecutionResult {
pub fn from_result(vm: &VirtualMachine, res: PyResult) -> PyResult<Self> {
match res {
Ok(val) => Ok(ExecutionResult::Yield(val)),
Err(err) => {
if objtype::isinstance(&err, &vm.ctx.exceptions.stop_iteration) {
objiter::stop_iter_value(vm, &err).map(ExecutionResult::Return)
} else {
Err(err)
}
}
}
}
pub fn into_result(self, vm: &VirtualMachine) -> PyResult {
match self {
ExecutionResult::Yield(value) => Ok(value),
ExecutionResult::Return(value) => {
let stop_iteration = vm.ctx.exceptions.stop_iteration.clone();
let args = if vm.is_none(&value) {
vec![]
} else {
vec![value]
};
Err(vm.new_exception(stop_iteration, args))
}
}
}
}
pub type FrameResult = PyResult<Option<ExecutionResult>>;
impl Frame {
pub fn new(code: PyCodeRef, scope: Scope) -> Frame {
Frame {
code,
stack: RefCell::new(vec![]),
blocks: RefCell::new(vec![]),
scope,
lasti: Cell::new(0),
}
}
pub fn run(&self, vm: &VirtualMachine) -> PyResult<ExecutionResult> {
flame_guard!(format!("Frame::run({})", self.code.obj_name));
loop {
let lineno = self.get_lineno();
let result = self.execute_instruction(vm);
match result {
Ok(None) => {}
Ok(Some(value)) => {
break Ok(value);
}
Err(exception) => {
let next = exception.traceback();
let new_traceback = PyTraceback::new(
next,
self.clone().into_ref(vm),
self.lasti.get(),
lineno.row(),
);
exception.set_traceback(Some(new_traceback.into_ref(vm)));
vm_trace!("Adding to traceback: {:?} {:?}", new_traceback, lineno);
match self.unwind_blocks(vm, UnwindReason::Raising { exception }) {
Ok(None) => {}
Ok(Some(result)) => {
break Ok(result);
}
Err(exception) => {
break Err(exception);
}
}
}
}
}
}
pub(crate) fn gen_throw(
&self,
vm: &VirtualMachine,
exc_type: PyObjectRef,
exc_val: PyObjectRef,
exc_tb: PyObjectRef,
) -> PyResult<ExecutionResult> {
if let bytecode::Instruction::YieldFrom = self.code.instructions[self.lasti.get()] {
let coro = self.last_value();
vm.call_method(&coro, "throw", vec![exc_type, exc_val, exc_tb])
.or_else(|err| {
self.pop_value();
self.lasti.set(self.lasti.get() + 1);
let val = objiter::stop_iter_value(vm, &err)?;
self._send(coro, val, vm)
})
.map(ExecutionResult::Yield)
} else {
let exception = exceptions::normalize(exc_type, exc_val, exc_tb, vm)?;
match self.unwind_blocks(vm, UnwindReason::Raising { exception }) {
Ok(None) => self.run(vm),
Ok(Some(result)) => Ok(result),
Err(exception) => Err(exception),
}
}
}
pub fn fetch_instruction(&self) -> &bytecode::Instruction {
let ins2 = &self.code.instructions[self.lasti.get()];
self.lasti.set(self.lasti.get() + 1);
ins2
}
fn execute_instruction(&self, vm: &VirtualMachine) -> FrameResult {
vm.check_signals()?;
let instruction = self.fetch_instruction();
flame_guard!(format!("Frame::execute_instruction({:?})", instruction));
#[cfg(feature = "vm-tracing-logging")]
{
trace!("=======");
trace!(" {:?}", self);
trace!(" Executing op code: {:?}", instruction);
trace!("=======");
}
match instruction {
bytecode::Instruction::LoadConst { ref value } => {
let obj = vm.ctx.unwrap_constant(value);
self.push_value(obj);
Ok(None)
}
bytecode::Instruction::Import {
ref name,
ref symbols,
ref level,
} => self.import(vm, name, symbols, *level),
bytecode::Instruction::ImportStar => self.import_star(vm),
bytecode::Instruction::ImportFrom { ref name } => self.import_from(vm, name),
bytecode::Instruction::LoadName {
ref name,
ref scope,
} => self.load_name(vm, name, scope),
bytecode::Instruction::StoreName {
ref name,
ref scope,
} => self.store_name(vm, name, scope),
bytecode::Instruction::DeleteName { ref name } => self.delete_name(vm, name),
bytecode::Instruction::Subscript => self.execute_subscript(vm),
bytecode::Instruction::StoreSubscript => self.execute_store_subscript(vm),
bytecode::Instruction::DeleteSubscript => self.execute_delete_subscript(vm),
bytecode::Instruction::Pop => {
self.pop_value();
Ok(None)
}
bytecode::Instruction::Duplicate => {
let value = self.pop_value();
self.push_value(value.clone());
self.push_value(value);
Ok(None)
}
bytecode::Instruction::Rotate { amount } => self.execute_rotate(*amount),
bytecode::Instruction::BuildString { size } => {
let s = self
.pop_multiple(*size)
.into_iter()
.map(|pyobj| objstr::clone_value(&pyobj))
.collect::<String>();
let str_obj = vm.ctx.new_str(s);
self.push_value(str_obj);
Ok(None)
}
bytecode::Instruction::BuildList { size, unpack } => {
let elements = self.get_elements(vm, *size, *unpack)?;
let list_obj = vm.ctx.new_list(elements);
self.push_value(list_obj);
Ok(None)
}
bytecode::Instruction::BuildSet { size, unpack } => {
let elements = self.get_elements(vm, *size, *unpack)?;
let py_obj = vm.ctx.new_set();
for item in elements {
vm.call_method(&py_obj, "add", vec![item])?;
}
self.push_value(py_obj);
Ok(None)
}
bytecode::Instruction::BuildTuple { size, unpack } => {
let elements = self.get_elements(vm, *size, *unpack)?;
let list_obj = vm.ctx.new_tuple(elements);
self.push_value(list_obj);
Ok(None)
}
bytecode::Instruction::BuildMap {
size,
unpack,
for_call,
} => self.execute_build_map(vm, *size, *unpack, *for_call),
bytecode::Instruction::BuildSlice { size } => self.execute_build_slice(vm, *size),
bytecode::Instruction::ListAppend { i } => {
let list_obj = self.nth_value(*i);
let item = self.pop_value();
objlist::PyListRef::try_from_object(vm, list_obj)?.append(item);
Ok(None)
}
bytecode::Instruction::SetAdd { i } => {
let set_obj = self.nth_value(*i);
let item = self.pop_value();
vm.call_method(&set_obj, "add", vec![item])?;
Ok(None)
}
bytecode::Instruction::MapAdd { i } => {
let dict_obj = self.nth_value(*i + 1);
let key = self.pop_value();
let value = self.pop_value();
vm.call_method(&dict_obj, "__setitem__", vec![key, value])?;
Ok(None)
}
bytecode::Instruction::BinaryOperation { ref op, inplace } => {
self.execute_binop(vm, op, *inplace)
}
bytecode::Instruction::LoadAttr { ref name } => self.load_attr(vm, name),
bytecode::Instruction::StoreAttr { ref name } => self.store_attr(vm, name),
bytecode::Instruction::DeleteAttr { ref name } => self.delete_attr(vm, name),
bytecode::Instruction::UnaryOperation { ref op } => self.execute_unop(vm, op),
bytecode::Instruction::CompareOperation { ref op } => self.execute_compare(vm, op),
bytecode::Instruction::ReturnValue => {
let value = self.pop_value();
self.unwind_blocks(vm, UnwindReason::Returning { value })
}
bytecode::Instruction::YieldValue => {
let value = self.pop_value();
Ok(Some(ExecutionResult::Yield(value)))
}
bytecode::Instruction::YieldFrom => self.execute_yield_from(vm),
bytecode::Instruction::SetupLoop { start, end } => {
self.push_block(BlockType::Loop {
start: *start,
end: *end,
});
Ok(None)
}
bytecode::Instruction::SetupExcept { handler } => {
self.push_block(BlockType::TryExcept { handler: *handler });
Ok(None)
}
bytecode::Instruction::SetupFinally { handler } => {
self.push_block(BlockType::Finally { handler: *handler });
Ok(None)
}
bytecode::Instruction::EnterFinally => {
self.push_block(BlockType::FinallyHandler { reason: None });
Ok(None)
}
bytecode::Instruction::EndFinally => {
let block = self.pop_block();
if let BlockType::FinallyHandler { reason } = block.typ {
if let Some(reason) = reason {
self.unwind_blocks(vm, reason)
} else {
Ok(None)
}
} else {
panic!(
"Block type must be finally handler when reaching EndFinally instruction!"
);
}
}
bytecode::Instruction::SetupWith { end } => {
let context_manager = self.pop_value();
let exit = vm.get_attribute(context_manager.clone(), "__exit__")?;
self.push_value(exit);
let enter_res = vm.call_method(&context_manager, "__enter__", vec![])?;
self.push_block(BlockType::Finally { handler: *end });
self.push_value(enter_res);
Ok(None)
}
bytecode::Instruction::BeforeAsyncWith => {
let mgr = self.pop_value();
let aexit = vm.get_attribute(mgr.clone(), "__aexit__")?;
self.push_value(aexit);
let aenter_res = vm.call_method(&mgr, "__aenter__", vec![])?;
self.push_value(aenter_res);
Ok(None)
}
bytecode::Instruction::SetupAsyncWith { end } => {
self.push_block(BlockType::Finally { handler: *end });
Ok(None)
}
bytecode::Instruction::WithCleanupStart => {
let block = self.current_block().unwrap();
let reason = match block.typ {
BlockType::FinallyHandler { reason } => reason,
_ => panic!("WithCleanupStart expects a FinallyHandler block on stack"),
};
let exc = reason.and_then(|reason| match reason {
UnwindReason::Raising { exception } => Some(exception),
_ => None,
});
let exit = self.pop_value();
let args = if let Some(exc) = exc {
let exc_type = exc.class().into_object();
let exc_val = exc.clone();
let exc_tb = exc.traceback().map_or(vm.get_none(), |tb| tb.into_object());
vec![exc_type, exc_val.into_object(), exc_tb]
} else {
vec![vm.ctx.none(), vm.ctx.none(), vm.ctx.none()]
};
let exit_res = vm.invoke(&exit, args)?;
self.push_value(exit_res);
Ok(None)
}
bytecode::Instruction::WithCleanupFinish => {
let block = self.pop_block();
let reason = match block.typ {
BlockType::FinallyHandler { reason } => reason,
_ => panic!("WithCleanupFinish expects a FinallyHandler block on stack"),
};
let suppress_exception = objbool::boolval(vm, self.pop_value())?;
if suppress_exception {
Ok(None)
} else if let Some(reason) = reason {
self.unwind_blocks(vm, reason)
} else {
Ok(None)
}
}
bytecode::Instruction::PopBlock => {
self.pop_block();
Ok(None)
}
bytecode::Instruction::GetIter => {
let iterated_obj = self.pop_value();
let iter_obj = objiter::get_iter(vm, &iterated_obj)?;
self.push_value(iter_obj);
Ok(None)
}
bytecode::Instruction::GetAwaitable => {
let awaited_obj = self.pop_value();
let awaitable = if awaited_obj.payload_is::<PyCoroutine>() {
awaited_obj
} else {
let await_method =
vm.get_method_or_type_error(awaited_obj.clone(), "__await__", || {
format!(
"object {} can't be used in 'await' expression",
awaited_obj.class().name,
)
})?;
vm.invoke(&await_method, vec![])?
};
self.push_value(awaitable);
Ok(None)
}
bytecode::Instruction::GetAIter => {
let aiterable = self.pop_value();
let aiter = vm.call_method(&aiterable, "__aiter__", vec![])?;
self.push_value(aiter);
Ok(None)
}
bytecode::Instruction::GetANext => {
let aiter = self.last_value();
let awaitable = vm.call_method(&aiter, "__anext__", vec![])?;
let awaitable = if awaitable.payload_is::<PyCoroutine>() {
awaitable
} else {
vm.call_method(&awaitable, "__await__", vec![])?
};
self.push_value(awaitable);
Ok(None)
}
bytecode::Instruction::ForIter { target } => self.execute_for_iter(vm, *target),
bytecode::Instruction::MakeFunction => self.execute_make_function(vm),
bytecode::Instruction::CallFunction { typ } => self.execute_call_function(vm, typ),
bytecode::Instruction::Jump { target } => {
self.jump(*target);
Ok(None)
}
bytecode::Instruction::JumpIfTrue { target } => {
let obj = self.pop_value();
let value = objbool::boolval(vm, obj)?;
if value {
self.jump(*target);
}
Ok(None)
}
bytecode::Instruction::JumpIfFalse { target } => {
let obj = self.pop_value();
let value = objbool::boolval(vm, obj)?;
if !value {
self.jump(*target);
}
Ok(None)
}
bytecode::Instruction::JumpIfTrueOrPop { target } => {
let obj = self.last_value();
let value = objbool::boolval(vm, obj)?;
if value {
self.jump(*target);
} else {
self.pop_value();
}
Ok(None)
}
bytecode::Instruction::JumpIfFalseOrPop { target } => {
let obj = self.last_value();
let value = objbool::boolval(vm, obj)?;
if !value {
self.jump(*target);
} else {
self.pop_value();
}
Ok(None)
}
bytecode::Instruction::Raise { argc } => self.execute_raise(vm, *argc),
bytecode::Instruction::Break => self.unwind_blocks(vm, UnwindReason::Break),
bytecode::Instruction::Continue => self.unwind_blocks(vm, UnwindReason::Continue),
bytecode::Instruction::PrintExpr => {
let expr = self.pop_value();
if !expr.is(&vm.get_none()) {
let repr = vm.to_repr(&expr)?;
if let Ok(ref print) = vm.get_attribute(vm.builtins.clone(), "print") {
vm.invoke(print, vec![repr.into_object()])?;
}
}
Ok(None)
}
bytecode::Instruction::LoadBuildClass => {
self.push_value(vm.get_attribute(vm.builtins.clone(), "__build_class__")?);
Ok(None)
}
bytecode::Instruction::UnpackSequence { size } => {
let value = self.pop_value();
let elements = vm.extract_elements(&value)?;
if elements.len() != *size {
Err(vm.new_value_error("Wrong number of values to unpack".to_owned()))
} else {
for element in elements.into_iter().rev() {
self.push_value(element);
}
Ok(None)
}
}
bytecode::Instruction::UnpackEx { before, after } => {
self.execute_unpack_ex(vm, *before, *after)
}
bytecode::Instruction::FormatValue { conversion } => {
use bytecode::ConversionFlag::*;
let value = match conversion {
Some(Str) => vm.to_str(&self.pop_value())?.into_object(),
Some(Repr) => vm.to_repr(&self.pop_value())?.into_object(),
Some(Ascii) => vm.to_ascii(&self.pop_value())?,
None => self.pop_value(),
};
let spec = vm.to_str(&self.pop_value())?.into_object();
let formatted = vm.call_method(&value, "__format__", vec![spec])?;
self.push_value(formatted);
Ok(None)
}
bytecode::Instruction::PopException {} => {
let block = self.pop_block();
if let BlockType::ExceptHandler = block.typ {
vm.pop_exception().expect("Should have exception in stack");
Ok(None)
} else {
panic!("Block type must be ExceptHandler here.")
}
}
bytecode::Instruction::Reverse { amount } => {
let mut stack = self.stack.borrow_mut();
let stack_len = stack.len();
stack[stack_len - amount..stack_len].reverse();
Ok(None)
}
}
}
#[cfg_attr(feature = "flame-it", flame("Frame"))]
fn get_elements(
&self,
vm: &VirtualMachine,
size: usize,
unpack: bool,
) -> PyResult<Vec<PyObjectRef>> {
let elements = self.pop_multiple(size);
if unpack {
let mut result: Vec<PyObjectRef> = vec![];
for element in elements {
result.extend(vm.extract_elements(&element)?);
}
Ok(result)
} else {
Ok(elements)
}
}
#[cfg_attr(feature = "flame-it", flame("Frame"))]
fn import(
&self,
vm: &VirtualMachine,
module: &Option<String>,
symbols: &[String],
level: usize,
) -> FrameResult {
let module = module.clone().unwrap_or_default();
let module = vm.import(&module, symbols, level)?;
self.push_value(module);
Ok(None)
}
#[cfg_attr(feature = "flame-it", flame("Frame"))]
fn import_from(&self, vm: &VirtualMachine, name: &str) -> FrameResult {
let module = self.last_value();
let obj = vm
.get_attribute(module, name)
.map_err(|_| vm.new_import_error(format!("cannot import name '{}'", name)))?;
self.push_value(obj);
Ok(None)
}
#[cfg_attr(feature = "flame-it", flame("Frame"))]
fn import_star(&self, vm: &VirtualMachine) -> FrameResult {
let module = self.pop_value();
if let Some(dict) = &module.dict {
for (k, v) in &*dict.borrow() {
let k = vm.to_str(&k)?;
let k = k.as_str();
if !k.starts_with('_') {
self.scope.store_name(&vm, k, v);
}
}
}
Ok(None)
}
#[cfg_attr(feature = "flame-it", flame("Frame"))]
fn unwind_blocks(&self, vm: &VirtualMachine, reason: UnwindReason) -> FrameResult {
while let Some(block) = self.current_block() {
match block.typ {
BlockType::Loop { start, end } => match &reason {
UnwindReason::Break => {
self.pop_block();
self.jump(end);
return Ok(None);
}
UnwindReason::Continue => {
self.jump(start);
return Ok(None);
}
_ => {
self.pop_block();
}
},
BlockType::Finally { handler } => {
self.pop_block();
self.push_block(BlockType::FinallyHandler {
reason: Some(reason),
});
self.jump(handler);
return Ok(None);
}
BlockType::TryExcept { handler } => {
self.pop_block();
if let UnwindReason::Raising { exception } = &reason {
self.push_block(BlockType::ExceptHandler {});
self.push_value(exception.clone().into_object());
vm.push_exception(exception.clone());
self.jump(handler);
return Ok(None);
}
}
BlockType::FinallyHandler { .. } => {
self.pop_block();
}
BlockType::ExceptHandler => {
self.pop_block();
vm.pop_exception().expect("Should have exception in stack");
}
}
}
match reason {
UnwindReason::Raising { exception } => Err(exception),
UnwindReason::Returning { value } => Ok(Some(ExecutionResult::Return(value))),
UnwindReason::Break | UnwindReason::Continue => {
panic!("Internal error: break or continue must occur within a loop block.")
}
}
}
fn store_name(
&self,
vm: &VirtualMachine,
name: &str,
name_scope: &bytecode::NameScope,
) -> FrameResult {
let obj = self.pop_value();
match name_scope {
bytecode::NameScope::Global => {
self.scope.store_global(vm, name, obj);
}
bytecode::NameScope::NonLocal => {
self.scope.store_cell(vm, name, obj);
}
bytecode::NameScope::Local => {
self.scope.store_name(vm, name, obj);
}
bytecode::NameScope::Free => {
self.scope.store_name(vm, name, obj);
}
}
Ok(None)
}
fn delete_name(&self, vm: &VirtualMachine, name: &str) -> FrameResult {
match self.scope.delete_name(vm, name) {
Ok(_) => Ok(None),
Err(_) => Err(vm.new_name_error(format!("name '{}' is not defined", name))),
}
}
#[cfg_attr(feature = "flame-it", flame("Frame"))]
fn load_name(
&self,
vm: &VirtualMachine,
name: &str,
name_scope: &bytecode::NameScope,
) -> FrameResult {
let optional_value = match name_scope {
bytecode::NameScope::Global => self.scope.load_global(vm, name),
bytecode::NameScope::NonLocal => self.scope.load_cell(vm, name),
bytecode::NameScope::Local => self.scope.load_local(&vm, name),
bytecode::NameScope::Free => self.scope.load_name(&vm, name),
};
let value = match optional_value {
Some(value) => value,
None => {
return Err(vm.new_name_error(format!("name '{}' is not defined", name)));
}
};
self.push_value(value);
Ok(None)
}
fn execute_rotate(&self, amount: usize) -> FrameResult {
if amount < 2 {
panic!("Can only rotate two or more values");
}
let mut values = Vec::new();
for _ in 0..amount {
values.push(self.pop_value());
}
self.push_value(values.remove(0));
values.reverse();
for value in values {
self.push_value(value);
}
Ok(None)
}
fn execute_subscript(&self, vm: &VirtualMachine) -> FrameResult {
let b_ref = self.pop_value();
let a_ref = self.pop_value();
let value = a_ref.get_item(&b_ref, vm)?;
self.push_value(value);
Ok(None)
}
fn execute_store_subscript(&self, vm: &VirtualMachine) -> FrameResult {
let idx = self.pop_value();
let obj = self.pop_value();
let value = self.pop_value();
obj.set_item(&idx, value, vm)?;
Ok(None)
}
fn execute_delete_subscript(&self, vm: &VirtualMachine) -> FrameResult {
let idx = self.pop_value();
let obj = self.pop_value();
obj.del_item(&idx, vm)?;
Ok(None)
}
#[allow(clippy::collapsible_if)]
fn execute_build_map(
&self,
vm: &VirtualMachine,
size: usize,
unpack: bool,
for_call: bool,
) -> FrameResult {
let map_obj = vm.ctx.new_dict();
if unpack {
for obj in self.pop_multiple(size) {
let dict: PyDictRef = obj.downcast().expect("Need a dictionary to build a map.");
for (key, value) in dict {
if for_call {
if map_obj.contains_key(&key, vm) {
let key_repr = vm.to_repr(&key)?;
let msg = format!(
"got multiple values for keyword argument {}",
key_repr.as_str()
);
return Err(vm.new_type_error(msg));
}
}
map_obj.set_item(&key, value, vm).unwrap();
}
}
} else {
for (key, value) in self.pop_multiple(2 * size).into_iter().tuples() {
map_obj.set_item(&key, value, vm).unwrap();
}
}
self.push_value(map_obj.into_object());
Ok(None)
}
fn execute_build_slice(&self, vm: &VirtualMachine, size: usize) -> FrameResult {
assert!(size == 2 || size == 3);
let step = if size == 3 {
Some(self.pop_value())
} else {
None
};
let stop = self.pop_value();
let start = self.pop_value();
let obj = PySlice {
start: Some(start),
stop,
step,
}
.into_ref(vm);
self.push_value(obj.into_object());
Ok(None)
}
fn execute_call_function(&self, vm: &VirtualMachine, typ: &bytecode::CallType) -> FrameResult {
let args = match typ {
bytecode::CallType::Positional(count) => {
let args: Vec<PyObjectRef> = self.pop_multiple(*count);
PyFuncArgs {
args,
kwargs: IndexMap::new(),
}
}
bytecode::CallType::Keyword(count) => {
let kwarg_names = self.pop_value();
let args: Vec<PyObjectRef> = self.pop_multiple(*count);
let kwarg_names = vm
.extract_elements(&kwarg_names)?
.iter()
.map(|pyobj| objstr::clone_value(pyobj))
.collect();
PyFuncArgs::new(args, kwarg_names)
}
bytecode::CallType::Ex(has_kwargs) => {
let kwargs = if *has_kwargs {
let kw_dict: PyDictRef =
self.pop_value().downcast().expect("Kwargs must be a dict.");
let mut kwargs = IndexMap::new();
for (key, value) in kw_dict.into_iter() {
if let Some(key) = key.payload_if_subclass::<objstr::PyString>(vm) {
kwargs.insert(key.as_str().to_owned(), value);
} else {
return Err(vm.new_type_error("keywords must be strings".to_owned()));
}
}
kwargs
} else {
IndexMap::new()
};
let args = self.pop_value();
let args = vm.extract_elements(&args)?;
PyFuncArgs { args, kwargs }
}
};
let func_ref = self.pop_value();
let value = vm.invoke(&func_ref, args)?;
self.push_value(value);
Ok(None)
}
fn execute_raise(&self, vm: &VirtualMachine, argc: usize) -> FrameResult {
let cause = match argc {
2 => {
let val = self.pop_value();
if vm.is_none(&val) {
Some(None)
} else {
Some(Some(
ExceptionCtor::try_from_object(vm, val)?.instantiate(vm)?,
))
}
}
_ => None,
};
let exception = match argc {
0 => match vm.current_exception() {
Some(exc) => exc,
None => {
return Err(vm.new_exception_msg(
vm.ctx.exceptions.runtime_error.clone(),
"No active exception to reraise".to_owned(),
))
}
},
1 | 2 => ExceptionCtor::try_from_object(vm, self.pop_value())?.instantiate(vm)?,
3 => panic!("Not implemented!"),
_ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3"),
};
let context = match argc {
0 => None,
_ => vm.current_exception(),
};
info!(
"Exception raised: {:?} with cause: {:?} and context: {:?}",
exception, cause, context
);
if let Some(cause) = cause {
exception.set_cause(cause);
}
exception.set_context(context);
Err(exception)
}
fn _send(&self, coro: PyObjectRef, val: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if let Some(gen) = coro.payload::<PyGenerator>() {
gen.send(val, vm)
} else if let Some(coro) = coro.payload::<PyCoroutine>() {
coro.send(val, vm)
} else if vm.is_none(&val) {
objiter::call_next(vm, &coro)
} else {
vm.call_method(&coro, "send", vec![val])
}
}
fn execute_yield_from(&self, vm: &VirtualMachine) -> FrameResult {
let val = self.pop_value();
let coro = self.last_value();
let result = self._send(coro, val, vm);
let result = ExecutionResult::from_result(vm, result)?;
match result {
ExecutionResult::Yield(value) => {
self.lasti.set(self.lasti.get() - 1);
Ok(Some(ExecutionResult::Yield(value)))
}
ExecutionResult::Return(value) => {
self.pop_value();
self.push_value(value);
Ok(None)
}
}
}
fn execute_unpack_ex(&self, vm: &VirtualMachine, before: usize, after: usize) -> FrameResult {
let value = self.pop_value();
let elements = vm.extract_elements::<PyObjectRef>(&value)?;
let min_expected = before + after;
if elements.len() < min_expected {
Err(vm.new_value_error(format!(
"Not enough values to unpack (expected at least {}, got {}",
min_expected,
elements.len()
)))
} else {
let middle = elements.len() - before - after;
for element in elements[before + middle..].iter().rev() {
self.push_value(element.clone());
}
let middle_elements = elements.iter().skip(before).take(middle).cloned().collect();
let t = vm.ctx.new_list(middle_elements);
self.push_value(t);
for element in elements[..before].iter().rev() {
self.push_value(element.clone());
}
Ok(None)
}
}
fn jump(&self, label: bytecode::Label) {
let target_pc = self.code.label_map[&label];
#[cfg(feature = "vm-tracing-logging")]
trace!("jump from {:?} to {:?}", self.lasti, target_pc);
self.lasti.set(target_pc);
}
fn execute_for_iter(&self, vm: &VirtualMachine, target: bytecode::Label) -> FrameResult {
let top_of_stack = self.last_value();
let next_obj = objiter::get_next_object(vm, &top_of_stack);
match next_obj {
Ok(Some(value)) => {
self.push_value(value);
Ok(None)
}
Ok(None) => {
self.pop_value();
self.jump(target);
Ok(None)
}
Err(next_error) => {
self.pop_value();
Err(next_error)
}
}
}
fn execute_make_function(&self, vm: &VirtualMachine) -> FrameResult {
let qualified_name = self
.pop_value()
.downcast::<PyString>()
.expect("qualified name to be a string");
let code_obj: PyCodeRef = self
.pop_value()
.downcast()
.expect("Second to top value on the stack must be a code object");
let flags = code_obj.flags;
let annotations = if flags.contains(bytecode::CodeFlags::HAS_ANNOTATIONS) {
self.pop_value()
} else {
vm.ctx.new_dict().into_object()
};
let kw_only_defaults = if flags.contains(bytecode::CodeFlags::HAS_KW_ONLY_DEFAULTS) {
Some(
self.pop_value()
.downcast::<PyDict>()
.expect("Stack value for keyword only defaults expected to be a dict"),
)
} else {
None
};
let defaults = if flags.contains(bytecode::CodeFlags::HAS_DEFAULTS) {
Some(
self.pop_value()
.downcast::<PyTuple>()
.expect("Stack value for defaults expected to be a tuple"),
)
} else {
None
};
let scope = self.scope.clone();
let func_obj = vm
.ctx
.new_pyfunction(code_obj, scope, defaults, kw_only_defaults);
let name = qualified_name.as_str().split('.').next_back().unwrap();
vm.set_attr(&func_obj, "__name__", vm.new_str(name.to_owned()))?;
vm.set_attr(&func_obj, "__qualname__", qualified_name)?;
let module = self
.scope
.globals
.get_item_option("__name__", vm)?
.unwrap_or_else(|| vm.get_none());
vm.set_attr(&func_obj, "__module__", module)?;
vm.set_attr(&func_obj, "__annotations__", annotations)?;
self.push_value(func_obj);
Ok(None)
}
#[cfg_attr(feature = "flame-it", flame("Frame"))]
fn execute_binop(
&self,
vm: &VirtualMachine,
op: &bytecode::BinaryOperator,
inplace: bool,
) -> FrameResult {
let b_ref = self.pop_value();
let a_ref = self.pop_value();
let value = if inplace {
match *op {
bytecode::BinaryOperator::Subtract => vm._isub(a_ref, b_ref),
bytecode::BinaryOperator::Add => vm._iadd(a_ref, b_ref),
bytecode::BinaryOperator::Multiply => vm._imul(a_ref, b_ref),
bytecode::BinaryOperator::MatrixMultiply => vm._imatmul(a_ref, b_ref),
bytecode::BinaryOperator::Power => vm._ipow(a_ref, b_ref),
bytecode::BinaryOperator::Divide => vm._itruediv(a_ref, b_ref),
bytecode::BinaryOperator::FloorDivide => vm._ifloordiv(a_ref, b_ref),
bytecode::BinaryOperator::Modulo => vm._imod(a_ref, b_ref),
bytecode::BinaryOperator::Lshift => vm._ilshift(a_ref, b_ref),
bytecode::BinaryOperator::Rshift => vm._irshift(a_ref, b_ref),
bytecode::BinaryOperator::Xor => vm._ixor(a_ref, b_ref),
bytecode::BinaryOperator::Or => vm._ior(a_ref, b_ref),
bytecode::BinaryOperator::And => vm._iand(a_ref, b_ref),
}?
} else {
match *op {
bytecode::BinaryOperator::Subtract => vm._sub(a_ref, b_ref),
bytecode::BinaryOperator::Add => vm._add(a_ref, b_ref),
bytecode::BinaryOperator::Multiply => vm._mul(a_ref, b_ref),
bytecode::BinaryOperator::MatrixMultiply => vm._matmul(a_ref, b_ref),
bytecode::BinaryOperator::Power => vm._pow(a_ref, b_ref),
bytecode::BinaryOperator::Divide => vm._truediv(a_ref, b_ref),
bytecode::BinaryOperator::FloorDivide => vm._floordiv(a_ref, b_ref),
bytecode::BinaryOperator::Modulo => vm._mod(a_ref, b_ref),
bytecode::BinaryOperator::Lshift => vm._lshift(a_ref, b_ref),
bytecode::BinaryOperator::Rshift => vm._rshift(a_ref, b_ref),
bytecode::BinaryOperator::Xor => vm._xor(a_ref, b_ref),
bytecode::BinaryOperator::Or => vm._or(a_ref, b_ref),
bytecode::BinaryOperator::And => vm._and(a_ref, b_ref),
}?
};
self.push_value(value);
Ok(None)
}
#[cfg_attr(feature = "flame-it", flame("Frame"))]
fn execute_unop(&self, vm: &VirtualMachine, op: &bytecode::UnaryOperator) -> FrameResult {
let a = self.pop_value();
let value = match *op {
bytecode::UnaryOperator::Minus => vm.call_method(&a, "__neg__", vec![])?,
bytecode::UnaryOperator::Plus => vm.call_method(&a, "__pos__", vec![])?,
bytecode::UnaryOperator::Invert => vm.call_method(&a, "__invert__", vec![])?,
bytecode::UnaryOperator::Not => {
let value = objbool::boolval(vm, a)?;
vm.ctx.new_bool(!value)
}
};
self.push_value(value);
Ok(None)
}
fn _id(&self, a: PyObjectRef) -> usize {
a.get_id()
}
fn _in(
&self,
vm: &VirtualMachine,
needle: PyObjectRef,
haystack: PyObjectRef,
) -> PyResult<bool> {
let found = vm._membership(haystack.clone(), needle)?;
Ok(objbool::boolval(vm, found)?)
}
fn _not_in(
&self,
vm: &VirtualMachine,
needle: PyObjectRef,
haystack: PyObjectRef,
) -> PyResult<bool> {
let found = vm._membership(haystack.clone(), needle)?;
Ok(!objbool::boolval(vm, found)?)
}
fn _is(&self, a: PyObjectRef, b: PyObjectRef) -> bool {
a.is(&b)
}
fn _is_not(&self, a: PyObjectRef, b: PyObjectRef) -> bool {
!a.is(&b)
}
fn exc_match(
&self,
vm: &VirtualMachine,
exc: PyObjectRef,
exc_type: PyObjectRef,
) -> PyResult<bool> {
single_or_tuple_any(
exc_type,
|cls: PyClassRef| vm.isinstance(&exc, &cls),
|o| {
format!(
"isinstance() arg 2 must be a type or tuple of types, not {}",
o.class()
)
},
vm,
)
}
#[cfg_attr(feature = "flame-it", flame("Frame"))]
fn execute_compare(
&self,
vm: &VirtualMachine,
op: &bytecode::ComparisonOperator,
) -> FrameResult {
let b = self.pop_value();
let a = self.pop_value();
let value = match *op {
bytecode::ComparisonOperator::Equal => vm._eq(a, b)?,
bytecode::ComparisonOperator::NotEqual => vm._ne(a, b)?,
bytecode::ComparisonOperator::Less => vm._lt(a, b)?,
bytecode::ComparisonOperator::LessOrEqual => vm._le(a, b)?,
bytecode::ComparisonOperator::Greater => vm._gt(a, b)?,
bytecode::ComparisonOperator::GreaterOrEqual => vm._ge(a, b)?,
bytecode::ComparisonOperator::Is => vm.new_bool(self._is(a, b)),
bytecode::ComparisonOperator::IsNot => vm.new_bool(self._is_not(a, b)),
bytecode::ComparisonOperator::In => vm.new_bool(self._in(vm, a, b)?),
bytecode::ComparisonOperator::NotIn => vm.new_bool(self._not_in(vm, a, b)?),
bytecode::ComparisonOperator::ExceptionMatch => vm.new_bool(self.exc_match(vm, a, b)?),
};
self.push_value(value);
Ok(None)
}
fn load_attr(&self, vm: &VirtualMachine, attr_name: &str) -> FrameResult {
let parent = self.pop_value();
let obj = vm.get_attribute(parent, attr_name)?;
self.push_value(obj);
Ok(None)
}
fn store_attr(&self, vm: &VirtualMachine, attr_name: &str) -> FrameResult {
let parent = self.pop_value();
let value = self.pop_value();
vm.set_attr(&parent, vm.new_str(attr_name.to_owned()), value)?;
Ok(None)
}
fn delete_attr(&self, vm: &VirtualMachine, attr_name: &str) -> FrameResult {
let parent = self.pop_value();
let name = vm.ctx.new_str(attr_name.to_owned());
vm.del_attr(&parent, name)?;
Ok(None)
}
pub fn get_lineno(&self) -> bytecode::Location {
self.code.locations[self.lasti.get()].clone()
}
fn push_block(&self, typ: BlockType) {
self.blocks.borrow_mut().push(Block {
typ,
level: self.stack.borrow().len(),
});
}
fn pop_block(&self) -> Block {
let block = self
.blocks
.borrow_mut()
.pop()
.expect("No more blocks to pop!");
self.stack.borrow_mut().truncate(block.level);
block
}
fn current_block(&self) -> Option<Block> {
self.blocks.borrow().last().cloned()
}
pub fn push_value(&self, obj: PyObjectRef) {
self.stack.borrow_mut().push(obj);
}
fn pop_value(&self) -> PyObjectRef {
self.stack
.borrow_mut()
.pop()
.expect("Tried to pop value but there was nothing on the stack")
}
fn pop_multiple(&self, count: usize) -> Vec<PyObjectRef> {
let mut stack = self.stack.borrow_mut();
let stack_len = stack.len();
stack.drain(stack_len - count..stack_len).collect()
}
fn last_value(&self) -> PyObjectRef {
self.stack.borrow().last().unwrap().clone()
}
fn nth_value(&self, depth: usize) -> PyObjectRef {
let stack = self.stack.borrow();
stack[stack.len() - depth - 1].clone()
}
}
impl fmt::Debug for Frame {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let stack_str = self
.stack
.borrow()
.iter()
.map(|elem| {
if elem.payload.as_any().is::<Frame>() {
"\n > {frame}".to_owned()
} else {
format!("\n > {:?}", elem)
}
})
.collect::<String>();
let block_str = self
.blocks
.borrow()
.iter()
.map(|elem| format!("\n > {:?}", elem))
.collect::<String>();
let dict = self.scope.get_locals();
let local_str = dict
.into_iter()
.map(|elem| format!("\n {:?} = {:?}", elem.0, elem.1))
.collect::<String>();
write!(
f,
"Frame Object {{ \n Stack:{}\n Blocks:{}\n Locals:{}\n}}",
stack_str, block_str, local_str
)
}
}