2020-02-06 04:15:03 +00:00
|
|
|
//! AVM2 executables.
|
|
|
|
|
2020-02-08 03:42:04 +00:00
|
|
|
use crate::avm2::activation::Activation;
|
2021-06-19 01:49:40 +00:00
|
|
|
use crate::avm2::method::{BytecodeMethod, Method, NativeMethod};
|
2021-09-24 20:54:36 +00:00
|
|
|
use crate::avm2::object::{ClassObject, Object};
|
2021-09-07 06:29:07 +00:00
|
|
|
use crate::avm2::scope::ScopeChain;
|
2022-08-28 16:30:20 +00:00
|
|
|
use crate::avm2::traits::TraitKind;
|
2020-02-06 04:15:03 +00:00
|
|
|
use crate::avm2::value::Value;
|
2020-07-04 21:18:41 +00:00
|
|
|
use crate::avm2::Error;
|
2022-08-28 16:30:20 +00:00
|
|
|
use crate::string::WString;
|
2021-10-26 02:11:32 +00:00
|
|
|
use gc_arena::{Collect, Gc};
|
2020-02-06 04:15:03 +00:00
|
|
|
use std::fmt;
|
2020-07-02 04:01:07 +00:00
|
|
|
|
2020-07-04 21:56:27 +00:00
|
|
|
/// 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>>,
|
|
|
|
|
2021-09-13 06:47:43 +00:00
|
|
|
/// The scope this method was defined in.
|
2021-09-07 06:29:07 +00:00
|
|
|
scope: ScopeChain<'gc>,
|
2020-07-04 21:56:27 +00:00
|
|
|
|
2020-09-19 14:27:24 +00:00
|
|
|
/// The receiver that this function is always called with.
|
2020-07-04 21:56:27 +00:00
|
|
|
///
|
2020-09-19 14:27:24 +00:00
|
|
|
/// If `None`, then the receiver provided by the caller is used. A
|
2020-07-04 21:56:27 +00:00
|
|
|
/// `Some` value indicates a bound executable.
|
2020-09-19 14:27:24 +00:00
|
|
|
receiver: Option<Object<'gc>>,
|
2021-10-24 02:48:48 +00:00
|
|
|
|
|
|
|
/// The bound superclass for this method.
|
|
|
|
///
|
|
|
|
/// The `superclass` is the class that defined this method. If `None`,
|
|
|
|
/// then there is no defining superclass and `super` operations should fall
|
|
|
|
/// back to the `receiver`.
|
|
|
|
bound_superclass: Option<ClassObject<'gc>>,
|
2020-07-04 21:56:27 +00:00
|
|
|
}
|
|
|
|
|
2021-09-21 22:27:41 +00:00
|
|
|
#[derive(Clone, Collect)]
|
|
|
|
#[collect(no_drop)]
|
2021-06-19 01:49:40 +00:00
|
|
|
pub struct NativeExecutable<'gc> {
|
|
|
|
/// The method associated with the executable.
|
|
|
|
method: Gc<'gc, NativeMethod<'gc>>,
|
2021-06-13 04:03:41 +00:00
|
|
|
|
2021-09-13 06:47:43 +00:00
|
|
|
/// The scope this method was defined in.
|
2021-09-07 06:29:07 +00:00
|
|
|
scope: ScopeChain<'gc>,
|
|
|
|
|
2021-06-19 01:49:40 +00:00
|
|
|
/// The bound reciever for this method.
|
|
|
|
bound_receiver: Option<Object<'gc>>,
|
2021-10-24 02:48:48 +00:00
|
|
|
|
|
|
|
/// The bound superclass for this method.
|
|
|
|
///
|
|
|
|
/// The `superclass` is the class that defined this method. If `None`,
|
|
|
|
/// then there is no defining superclass and `super` operations should fall
|
|
|
|
/// back to the `receiver`.
|
|
|
|
bound_superclass: Option<ClassObject<'gc>>,
|
2021-06-19 01:49:40 +00:00
|
|
|
}
|
2021-06-13 04:03:41 +00:00
|
|
|
|
2021-06-19 01:49:40 +00:00
|
|
|
/// 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.
|
2021-10-26 02:11:32 +00:00
|
|
|
Native(NativeExecutable<'gc>),
|
2020-07-01 03:44:14 +00:00
|
|
|
|
|
|
|
/// Code defined in a loaded ABC file.
|
2021-10-26 02:11:32 +00:00
|
|
|
Action(BytecodeExecutable<'gc>),
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|
|
|
|
|
2020-02-15 01:30:19 +00:00
|
|
|
impl<'gc> Executable<'gc> {
|
2020-07-01 03:44:14 +00:00
|
|
|
/// Convert a method into an executable.
|
|
|
|
pub fn from_method(
|
|
|
|
method: Method<'gc>,
|
2021-09-07 06:29:07 +00:00
|
|
|
scope: ScopeChain<'gc>,
|
2020-09-19 14:27:24 +00:00
|
|
|
receiver: Option<Object<'gc>>,
|
2021-10-24 02:48:48 +00:00
|
|
|
superclass: Option<ClassObject<'gc>>,
|
2020-07-01 03:44:14 +00:00
|
|
|
) -> Self {
|
|
|
|
match method {
|
2021-10-26 02:11:32 +00:00
|
|
|
Method::Native(method) => Self::Native(NativeExecutable {
|
|
|
|
method,
|
|
|
|
scope,
|
|
|
|
bound_receiver: receiver,
|
|
|
|
bound_superclass: superclass,
|
|
|
|
}),
|
|
|
|
Method::Bytecode(method) => Self::Action(BytecodeExecutable {
|
|
|
|
method,
|
|
|
|
scope,
|
|
|
|
receiver,
|
|
|
|
bound_superclass: superclass,
|
|
|
|
}),
|
2020-07-01 03:44:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-25 23:07:53 +00:00
|
|
|
/// Execute a method.
|
|
|
|
///
|
|
|
|
/// The function will either be called directly if it is a Rust builtin, or
|
2020-07-04 21:18:41 +00:00
|
|
|
/// executed on the same AVM2 instance as the activation passed in here.
|
|
|
|
/// The value returned in either case will be provided here.
|
|
|
|
///
|
2020-09-19 14:27:24 +00:00
|
|
|
/// It is a panicking logic error to attempt to execute user code while any
|
2020-07-04 21:18:41 +00:00
|
|
|
/// reachable object is currently under a GcCell write lock.
|
2021-06-10 01:08:13 +00:00
|
|
|
///
|
|
|
|
/// Passed-in arguments will be conformed to the set of method parameters
|
|
|
|
/// declared on the function.
|
2020-02-15 01:30:19 +00:00
|
|
|
pub fn exec(
|
|
|
|
&self,
|
2021-06-13 04:03:41 +00:00
|
|
|
unbound_receiver: Option<Object<'gc>>,
|
2021-06-11 01:40:45 +00:00
|
|
|
mut arguments: &[Value<'gc>],
|
2020-07-28 03:19:43 +00:00
|
|
|
activation: &mut Activation<'_, 'gc, '_>,
|
2020-12-11 03:21:36 +00:00
|
|
|
callee: Object<'gc>,
|
2020-07-04 21:18:41 +00:00
|
|
|
) -> Result<Value<'gc>, Error> {
|
2022-08-28 16:30:20 +00:00
|
|
|
let ret = match self {
|
2021-06-19 01:49:40 +00:00
|
|
|
Executable::Native(bm) => {
|
|
|
|
let method = bm.method.method;
|
|
|
|
let receiver = bm.bound_receiver.or(unbound_receiver);
|
2021-09-10 01:56:08 +00:00
|
|
|
let caller_domain = activation.caller_domain();
|
2021-10-24 02:48:48 +00:00
|
|
|
let subclass_object = bm.bound_superclass;
|
2020-12-19 18:03:37 +00:00
|
|
|
let mut activation = Activation::from_builtin(
|
|
|
|
activation.context.reborrow(),
|
|
|
|
receiver,
|
2021-06-19 23:07:55 +00:00
|
|
|
subclass_object,
|
2021-09-07 06:29:07 +00:00
|
|
|
bm.scope,
|
2021-09-10 01:56:08 +00:00
|
|
|
caller_domain,
|
2020-12-19 18:03:37 +00:00
|
|
|
)?;
|
2020-12-18 04:01:09 +00:00
|
|
|
|
2021-06-19 01:49:40 +00:00
|
|
|
if arguments.len() > bm.method.signature.len() && !bm.method.is_variadic {
|
2021-06-13 04:03:41 +00:00
|
|
|
return Err(format!(
|
|
|
|
"Attempted to call {:?} with {} arguments (more than {} is prohibited)",
|
2021-06-19 01:49:40 +00:00
|
|
|
bm.method.name,
|
2021-06-13 04:03:41 +00:00
|
|
|
arguments.len(),
|
2021-06-19 01:49:40 +00:00
|
|
|
bm.method.signature.len()
|
2021-06-13 04:03:41 +00:00
|
|
|
)
|
|
|
|
.into());
|
|
|
|
}
|
|
|
|
|
2021-06-19 01:49:40 +00:00
|
|
|
let arguments = activation.resolve_parameters(
|
2022-06-16 02:11:14 +00:00
|
|
|
&bm.method.name,
|
2021-06-19 01:49:40 +00:00
|
|
|
arguments,
|
|
|
|
&bm.method.signature,
|
|
|
|
)?;
|
2022-08-28 16:30:20 +00:00
|
|
|
activation
|
|
|
|
.context
|
|
|
|
.avm2
|
|
|
|
.push_call(activation.context.gc_context, self.clone());
|
2021-06-13 04:03:41 +00:00
|
|
|
method(&mut activation, receiver, &arguments)
|
2020-07-28 03:19:43 +00:00
|
|
|
}
|
2020-07-04 21:56:27 +00:00
|
|
|
Executable::Action(bm) => {
|
2021-06-24 23:12:54 +00:00
|
|
|
if bm.method.is_unchecked() {
|
2021-06-13 04:03:41 +00:00
|
|
|
let max_args = bm.method.signature().len();
|
2021-06-11 01:40:45 +00:00
|
|
|
if arguments.len() > max_args && !bm.method.is_variadic() {
|
|
|
|
arguments = &arguments[..max_args];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-13 04:03:41 +00:00
|
|
|
let receiver = bm.receiver.or(unbound_receiver);
|
2021-10-24 02:48:48 +00:00
|
|
|
let subclass_object = bm.bound_superclass;
|
|
|
|
|
2020-07-04 21:18:41 +00:00
|
|
|
let mut activation = Activation::from_method(
|
2020-07-28 03:19:43 +00:00
|
|
|
activation.context.reborrow(),
|
2020-07-04 21:56:27 +00:00
|
|
|
bm.method,
|
|
|
|
bm.scope,
|
2020-09-19 14:27:24 +00:00
|
|
|
receiver,
|
2020-07-04 21:18:41 +00:00
|
|
|
arguments,
|
2021-06-19 23:07:55 +00:00
|
|
|
subclass_object,
|
2020-12-11 03:21:36 +00:00
|
|
|
callee,
|
2020-07-08 01:28:24 +00:00
|
|
|
)?;
|
2022-08-28 16:30:20 +00:00
|
|
|
activation
|
|
|
|
.context
|
|
|
|
.avm2
|
|
|
|
.push_call(activation.context.gc_context, self.clone());
|
2020-07-28 03:19:43 +00:00
|
|
|
activation.run_actions(bm.method)
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
2022-08-28 16:30:20 +00:00
|
|
|
};
|
|
|
|
activation
|
|
|
|
.context
|
|
|
|
.avm2
|
|
|
|
.pop_call(activation.context.gc_context);
|
|
|
|
ret
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn bound_superclass(&self) -> Option<ClassObject<'gc>> {
|
|
|
|
match self {
|
|
|
|
Executable::Native(NativeExecutable {
|
|
|
|
bound_superclass, ..
|
|
|
|
}) => *bound_superclass,
|
|
|
|
Executable::Action(BytecodeExecutable {
|
|
|
|
bound_superclass, ..
|
|
|
|
}) => *bound_superclass,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn write_full_name(&self, output: &mut WString) {
|
|
|
|
let class_def = self.bound_superclass().map(|superclass| {
|
|
|
|
let class_def = superclass.inner_class_definition();
|
|
|
|
let name = class_def.read().name().to_qualified_name_no_mc();
|
|
|
|
output.push_str(&name);
|
|
|
|
class_def
|
|
|
|
});
|
|
|
|
match self {
|
|
|
|
Executable::Native(NativeExecutable { method, .. }) => {
|
|
|
|
output.push_char('/');
|
|
|
|
output.push_utf8(&method.name)
|
|
|
|
}
|
|
|
|
Executable::Action(BytecodeExecutable { method, .. }) => {
|
|
|
|
// NOTE: The name of a bytecode method refers to the name of the trait that contains the method,
|
|
|
|
// rather than the name of the method itself.
|
|
|
|
if let Some(class_def) = class_def {
|
|
|
|
if class_def
|
|
|
|
.read()
|
|
|
|
.class_init()
|
|
|
|
.into_bytecode()
|
|
|
|
.map(|b| Gc::ptr_eq(b, *method))
|
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
|
|
|
output.push_utf8("$cinit");
|
|
|
|
} else if !class_def
|
|
|
|
.read()
|
|
|
|
.instance_init()
|
|
|
|
.into_bytecode()
|
|
|
|
.map(|b| Gc::ptr_eq(b, *method))
|
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
|
|
|
// TODO: Ideally, the declaring trait of this executable should already be attached here, that way
|
|
|
|
// we can avoid needing to lookup the trait like this.
|
|
|
|
let class_def = class_def.read();
|
|
|
|
let mut method_trait = None;
|
|
|
|
// First search instance traits for the method
|
|
|
|
for t in class_def.instance_traits() {
|
|
|
|
if let Some(b) = t.as_method().and_then(|m| m.into_bytecode().ok()) {
|
|
|
|
if Gc::ptr_eq(b, *method) {
|
|
|
|
method_trait = Some(t);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if method_trait.is_none() {
|
|
|
|
// If we can't find it in instance traits, search class traits instead
|
|
|
|
for t in class_def.class_traits() {
|
|
|
|
if let Some(b) = t.as_method().and_then(|m| m.into_bytecode().ok())
|
|
|
|
{
|
|
|
|
if Gc::ptr_eq(b, *method) {
|
|
|
|
// Class traits always start with $
|
|
|
|
output.push_char('$');
|
|
|
|
method_trait = Some(t);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(method_trait) = method_trait {
|
|
|
|
output.push_char('/');
|
|
|
|
match method_trait.kind() {
|
|
|
|
TraitKind::Setter { .. } => output.push_utf8("set "),
|
|
|
|
TraitKind::Getter { .. } => output.push_utf8("get "),
|
|
|
|
_ => (),
|
|
|
|
}
|
2022-08-28 18:36:32 +00:00
|
|
|
if method_trait.name().namespace().is_namespace() {
|
|
|
|
output.push_str(&method_trait.name().to_qualified_name_no_mc());
|
|
|
|
} else {
|
|
|
|
output.push_str(&method_trait.name().local_name());
|
|
|
|
}
|
2022-08-28 16:30:20 +00:00
|
|
|
}
|
|
|
|
// TODO: What happens if we can't find the trait?
|
|
|
|
}
|
|
|
|
// We purposely do nothing for instance initializers
|
2022-08-28 18:36:32 +00:00
|
|
|
} else if method.is_function && !method.method_name().is_empty() {
|
2022-08-28 16:30:20 +00:00
|
|
|
output.push_utf8("Function/");
|
2022-08-28 18:36:32 +00:00
|
|
|
output.push_utf8(method.method_name());
|
2022-08-28 16:30:20 +00:00
|
|
|
} else {
|
|
|
|
output.push_utf8("MethodInfo-");
|
|
|
|
output.push_utf8(&method.abc_method.to_string());
|
|
|
|
}
|
|
|
|
}
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
2022-08-28 16:30:20 +00:00
|
|
|
output.push_utf8("()");
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-06 04:15:03 +00:00
|
|
|
impl<'gc> fmt::Debug for Executable<'gc> {
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
2020-07-04 21:56:27 +00:00
|
|
|
Self::Action(be) => fmt
|
2020-07-01 03:44:14 +00:00
|
|
|
.debug_struct("Executable::Action")
|
2020-07-04 21:56:27 +00:00
|
|
|
.field("method", &be.method)
|
|
|
|
.field("scope", &be.scope)
|
2020-09-19 14:27:24 +00:00
|
|
|
.field("receiver", &be.receiver)
|
2020-07-01 03:44:14 +00:00
|
|
|
.finish(),
|
2021-06-19 01:49:40 +00:00
|
|
|
Self::Native(bm) => fmt
|
2021-06-13 04:03:41 +00:00
|
|
|
.debug_struct("Executable::Native")
|
2021-06-19 01:49:40 +00:00
|
|
|
.field("method", &bm.method)
|
|
|
|
.field("bound_receiver", &bm.bound_receiver)
|
2020-02-06 04:15:03 +00:00
|
|
|
.finish(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|