//! AVM2 executables. use crate::avm2::activation::Activation; use crate::avm2::method::{BytecodeMethod, Method, NativeMethod}; use crate::avm2::object::{ClassObject, Object}; use crate::avm2::scope::Scope; use crate::avm2::value::Value; use crate::avm2::Error; use gc_arena::{Collect, CollectionContext, Gc, GcCell, MutationContext}; use std::fmt; /// Represents code written in AVM2 bytecode that can be executed by some /// means. #[derive(Clone, Collect)] #[collect(no_drop)] pub struct BytecodeExecutable<'gc> { /// The method code to execute from a given ABC file. method: Gc<'gc, BytecodeMethod<'gc>>, /// The scope stack to pull variables from. scope: Option>>, /// The receiver that this function is always called with. /// /// If `None`, then the receiver provided by the caller is used. A /// `Some` value indicates a bound executable. receiver: Option>, } #[derive(Clone)] pub struct NativeExecutable<'gc> { /// The method associated with the executable. method: Gc<'gc, NativeMethod<'gc>>, /// The bound reciever for this method. bound_receiver: Option>, } unsafe impl<'gc> Collect for NativeExecutable<'gc> { fn trace(&self, cc: CollectionContext) { self.method.trace(cc); self.bound_receiver.trace(cc); } } /// Represents code that can be executed by some means. #[derive(Clone, Collect)] #[collect(no_drop)] pub enum Executable<'gc> { /// Code defined in Ruffle's binary. Native(Gc<'gc, NativeExecutable<'gc>>), /// Code defined in a loaded ABC file. Action(Gc<'gc, BytecodeExecutable<'gc>>), } impl<'gc> Executable<'gc> { /// Convert a method into an executable. pub fn from_method( method: Method<'gc>, scope: Option>>, receiver: Option>, mc: MutationContext<'gc, '_>, ) -> Self { match method { Method::Native(method) => Self::Native(Gc::allocate( mc, NativeExecutable { method, bound_receiver: receiver, }, )), Method::Bytecode(method) => Self::Action(Gc::allocate( mc, BytecodeExecutable { method, scope, receiver, }, )), } } /// Execute a method. /// /// The function will either be called directly if it is a Rust builtin, or /// executed on the same AVM2 instance as the activation passed in here. /// The value returned in either case will be provided here. /// /// It is a panicking logic error to attempt to execute user code while any /// reachable object is currently under a GcCell write lock. /// /// Passed-in arguments will be conformed to the set of method parameters /// declared on the function. pub fn exec( &self, unbound_receiver: Option>, mut arguments: &[Value<'gc>], activation: &mut Activation<'_, 'gc, '_>, subclass_object: Option>, callee: Object<'gc>, ) -> Result, Error> { match self { Executable::Native(bm) => { let method = bm.method.method; let receiver = bm.bound_receiver.or(unbound_receiver); let scope = activation.scope(); let mut activation = Activation::from_builtin( activation.context.reborrow(), scope, receiver, subclass_object, )?; if arguments.len() > bm.method.signature.len() && !bm.method.is_variadic { return Err(format!( "Attempted to call {:?} with {} arguments (more than {} is prohibited)", bm.method.name, arguments.len(), bm.method.signature.len() ) .into()); } let arguments = activation.resolve_parameters( bm.method.name, arguments, &bm.method.signature, )?; method(&mut activation, receiver, &arguments) } Executable::Action(bm) => { if bm.method.is_unchecked() { let max_args = bm.method.signature().len(); if arguments.len() > max_args && !bm.method.is_variadic() { arguments = &arguments[..max_args]; } } let receiver = bm.receiver.or(unbound_receiver); let mut activation = Activation::from_method( activation.context.reborrow(), bm.method, bm.scope, receiver, arguments, subclass_object, callee, )?; activation.run_actions(bm.method) } } } } impl<'gc> fmt::Debug for Executable<'gc> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Action(be) => fmt .debug_struct("Executable::Action") .field("method", &be.method) .field("scope", &be.scope) .field("receiver", &be.receiver) .finish(), Self::Native(bm) => fmt .debug_struct("Executable::Native") .field("method", &bm.method) .field("bound_receiver", &bm.bound_receiver) .finish(), } } }