2020-07-03 01:49:53 +00:00
|
|
|
//! AVM2 methods
|
|
|
|
|
2020-07-04 21:18:41 +00:00
|
|
|
use crate::avm2::activation::Activation;
|
2021-06-13 04:03:41 +00:00
|
|
|
use crate::avm2::names::Multiname;
|
2020-07-03 01:49:53 +00:00
|
|
|
use crate::avm2::object::Object;
|
|
|
|
use crate::avm2::script::TranslationUnit;
|
2021-06-13 04:03:41 +00:00
|
|
|
use crate::avm2::value::{abc_default_value, Value};
|
2020-07-04 21:18:41 +00:00
|
|
|
use crate::avm2::Error;
|
2021-09-12 10:20:51 +00:00
|
|
|
use crate::string::AvmString;
|
2020-07-04 21:56:27 +00:00
|
|
|
use gc_arena::{Collect, CollectionContext, Gc, MutationContext};
|
2020-07-03 01:49:53 +00:00
|
|
|
use std::fmt;
|
|
|
|
use std::rc::Rc;
|
2021-06-10 01:08:13 +00:00
|
|
|
use swf::avm2::types::{
|
|
|
|
AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody, MethodParam as AbcMethodParam,
|
|
|
|
};
|
2020-07-03 01:49:53 +00:00
|
|
|
|
|
|
|
/// Represents a function defined in Ruffle's code.
|
|
|
|
///
|
|
|
|
/// Parameters are as follows:
|
|
|
|
///
|
|
|
|
/// * The AVM2 runtime
|
|
|
|
/// * The action context
|
|
|
|
/// * The current `this` object
|
|
|
|
/// * The arguments this function was called with
|
|
|
|
///
|
|
|
|
/// Native functions are allowed to return a value or `None`. `None` indicates
|
|
|
|
/// that the given value will not be returned on the stack and instead will
|
|
|
|
/// resolve on the AVM stack, as if you had called a non-native function. If
|
|
|
|
/// your function yields `None`, you must ensure that the top-most activation
|
|
|
|
/// in the AVM1 runtime will return with the value of this function.
|
2021-06-19 01:49:40 +00:00
|
|
|
pub type NativeMethodImpl = for<'gc> fn(
|
2021-05-04 17:52:40 +00:00
|
|
|
&mut Activation<'_, 'gc, '_>,
|
|
|
|
Option<Object<'gc>>,
|
|
|
|
&[Value<'gc>],
|
|
|
|
) -> Result<Value<'gc>, Error>;
|
|
|
|
|
2021-06-19 19:12:35 +00:00
|
|
|
/// Configuration of a single parameter of a method.
|
2021-06-13 04:03:41 +00:00
|
|
|
#[derive(Clone, Collect, Debug)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct ParamConfig<'gc> {
|
|
|
|
/// The name of the parameter.
|
|
|
|
pub param_name: AvmString<'gc>,
|
|
|
|
|
|
|
|
/// The name of the type of the parameter.
|
|
|
|
pub param_type_name: Multiname<'gc>,
|
|
|
|
|
|
|
|
/// The default value for this parameter.
|
|
|
|
pub default_value: Option<Value<'gc>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> ParamConfig<'gc> {
|
|
|
|
fn from_abc_param(
|
|
|
|
config: &AbcMethodParam,
|
|
|
|
txunit: TranslationUnit<'gc>,
|
|
|
|
activation: &mut Activation<'_, 'gc, '_>,
|
|
|
|
) -> Result<Self, Error> {
|
|
|
|
let param_name = if let Some(name) = &config.name {
|
|
|
|
txunit.pool_string(name.0, activation.context.gc_context)?
|
|
|
|
} else {
|
|
|
|
"<Unnamed Parameter>".into()
|
|
|
|
};
|
|
|
|
let param_type_name = if config.kind.0 == 0 {
|
|
|
|
Multiname::any()
|
|
|
|
} else {
|
|
|
|
Multiname::from_abc_multiname_static(
|
|
|
|
txunit,
|
2021-12-17 20:49:40 +00:00
|
|
|
config.kind,
|
2021-06-13 04:03:41 +00:00
|
|
|
activation.context.gc_context,
|
|
|
|
)?
|
|
|
|
};
|
|
|
|
let default_value = if let Some(dv) = &config.default_value {
|
2021-06-16 22:15:13 +00:00
|
|
|
Some(abc_default_value(txunit, dv, activation)?)
|
2021-06-13 04:03:41 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
param_name,
|
|
|
|
param_type_name,
|
|
|
|
default_value,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn of_type(name: impl Into<AvmString<'gc>>, param_type_name: Multiname<'gc>) -> Self {
|
|
|
|
Self {
|
|
|
|
param_name: name.into(),
|
|
|
|
param_type_name,
|
|
|
|
default_value: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn optional(
|
|
|
|
name: impl Into<AvmString<'gc>>,
|
|
|
|
param_type_name: Multiname<'gc>,
|
|
|
|
default_value: impl Into<Value<'gc>>,
|
|
|
|
) -> Self {
|
|
|
|
Self {
|
|
|
|
param_name: name.into(),
|
|
|
|
param_type_name,
|
|
|
|
default_value: Some(default_value.into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-03 01:49:53 +00:00
|
|
|
/// Represents a reference to an AVM2 method and body.
|
|
|
|
#[derive(Collect, Clone, Debug)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct BytecodeMethod<'gc> {
|
|
|
|
/// The translation unit this function was defined in.
|
|
|
|
pub txunit: TranslationUnit<'gc>,
|
|
|
|
|
|
|
|
/// The underlying ABC file of the above translation unit.
|
2021-05-14 05:59:57 +00:00
|
|
|
#[collect(require_static)]
|
|
|
|
pub abc: Rc<AbcFile>,
|
2020-07-03 01:49:53 +00:00
|
|
|
|
|
|
|
/// The ABC method this function uses.
|
|
|
|
pub abc_method: u32,
|
|
|
|
|
|
|
|
/// The ABC method body this function uses.
|
2020-07-08 01:28:24 +00:00
|
|
|
pub abc_method_body: Option<u32>,
|
2021-06-13 04:03:41 +00:00
|
|
|
|
|
|
|
/// The parameter signature of this method.
|
|
|
|
pub signature: Vec<ParamConfig<'gc>>,
|
2021-06-24 23:12:54 +00:00
|
|
|
|
|
|
|
/// The return type of this method.
|
|
|
|
pub return_type: Multiname<'gc>,
|
|
|
|
|
|
|
|
/// Whether or not this method was declared as a free-standing function.
|
|
|
|
///
|
|
|
|
/// A free-standing function corresponds to the `Function` trait type, and
|
|
|
|
/// is instantiated with the `newfunction` opcode.
|
|
|
|
pub is_function: bool,
|
2020-07-03 01:49:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> BytecodeMethod<'gc> {
|
|
|
|
/// Construct an `BytecodeMethod` from an `AbcFile` and method index.
|
|
|
|
pub fn from_method_index(
|
|
|
|
txunit: TranslationUnit<'gc>,
|
|
|
|
abc_method: Index<AbcMethod>,
|
2021-06-24 23:12:54 +00:00
|
|
|
is_function: bool,
|
2021-06-13 04:03:41 +00:00
|
|
|
activation: &mut Activation<'_, 'gc, '_>,
|
|
|
|
) -> Result<Gc<'gc, Self>, Error> {
|
2020-07-03 01:49:53 +00:00
|
|
|
let abc = txunit.abc();
|
2021-06-13 04:03:41 +00:00
|
|
|
let mut signature = Vec::new();
|
2020-07-03 01:49:53 +00:00
|
|
|
|
|
|
|
if abc.methods.get(abc_method.0 as usize).is_some() {
|
2021-06-13 04:03:41 +00:00
|
|
|
let method = &abc.methods[abc_method.0 as usize];
|
|
|
|
for param in &method.params {
|
2021-06-16 22:15:13 +00:00
|
|
|
signature.push(ParamConfig::from_abc_param(param, txunit, activation)?);
|
2021-06-13 04:03:41 +00:00
|
|
|
}
|
|
|
|
|
2021-06-24 23:12:54 +00:00
|
|
|
let return_type = if method.return_type.0 == 0 {
|
|
|
|
Multiname::any()
|
|
|
|
} else {
|
|
|
|
Multiname::from_abc_multiname_static(
|
|
|
|
txunit,
|
2021-12-17 20:49:40 +00:00
|
|
|
method.return_type,
|
2021-06-24 23:12:54 +00:00
|
|
|
activation.context.gc_context,
|
|
|
|
)?
|
|
|
|
};
|
|
|
|
|
2020-07-03 01:49:53 +00:00
|
|
|
for (index, method_body) in abc.method_bodies.iter().enumerate() {
|
|
|
|
if method_body.method.0 == abc_method.0 {
|
2021-06-13 04:03:41 +00:00
|
|
|
return Ok(Gc::allocate(
|
|
|
|
activation.context.gc_context,
|
2020-07-04 21:56:27 +00:00
|
|
|
Self {
|
|
|
|
txunit,
|
2021-05-14 05:59:57 +00:00
|
|
|
abc: txunit.abc(),
|
2020-07-04 21:56:27 +00:00
|
|
|
abc_method: abc_method.0,
|
2020-07-08 01:28:24 +00:00
|
|
|
abc_method_body: Some(index as u32),
|
2021-06-13 04:03:41 +00:00
|
|
|
signature,
|
2021-06-24 23:12:54 +00:00
|
|
|
return_type,
|
|
|
|
is_function,
|
2020-07-04 21:56:27 +00:00
|
|
|
},
|
|
|
|
));
|
2020-07-03 01:49:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-13 04:03:41 +00:00
|
|
|
Ok(Gc::allocate(
|
|
|
|
activation.context.gc_context,
|
2020-07-08 01:28:24 +00:00
|
|
|
Self {
|
|
|
|
txunit,
|
2021-05-14 05:59:57 +00:00
|
|
|
abc: txunit.abc(),
|
2020-07-08 01:28:24 +00:00
|
|
|
abc_method: abc_method.0,
|
|
|
|
abc_method_body: None,
|
2021-06-13 04:03:41 +00:00
|
|
|
signature,
|
2021-06-24 23:12:54 +00:00
|
|
|
return_type: Multiname::any(),
|
|
|
|
is_function,
|
2020-07-08 01:28:24 +00:00
|
|
|
},
|
|
|
|
))
|
2020-07-03 01:49:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the underlying ABC file.
|
|
|
|
pub fn abc(&self) -> Rc<AbcFile> {
|
|
|
|
self.txunit.abc()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the underlying translation unit this method was defined in.
|
|
|
|
pub fn translation_unit(&self) -> TranslationUnit<'gc> {
|
|
|
|
self.txunit
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a reference to the ABC method entry this refers to.
|
|
|
|
pub fn method(&self) -> &AbcMethod {
|
2021-06-05 10:53:23 +00:00
|
|
|
self.abc.methods.get(self.abc_method as usize).unwrap()
|
2020-07-03 01:49:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a reference to the ABC method body entry this refers to.
|
2020-07-08 01:28:24 +00:00
|
|
|
///
|
|
|
|
/// Some methods do not have bodies; this returns `None` in that case.
|
|
|
|
pub fn body(&self) -> Option<&AbcMethodBody> {
|
|
|
|
if let Some(abc_method_body) = self.abc_method_body {
|
2021-05-14 05:59:57 +00:00
|
|
|
self.abc.method_bodies.get(abc_method_body as usize)
|
2020-07-08 01:28:24 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2020-07-03 01:49:53 +00:00
|
|
|
}
|
2021-06-10 01:08:13 +00:00
|
|
|
|
|
|
|
/// Get the list of method params for this method.
|
2021-06-13 04:03:41 +00:00
|
|
|
pub fn signature(&self) -> &[ParamConfig<'gc>] {
|
|
|
|
&self.signature
|
2021-06-10 01:08:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the name of this method.
|
|
|
|
pub fn method_name(&self) -> &str {
|
|
|
|
let name_index = self.method().name.0 as usize;
|
|
|
|
if name_index == 0 {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
self.abc
|
|
|
|
.constant_pool
|
|
|
|
.strings
|
|
|
|
.get(name_index - 1)
|
|
|
|
.map(|s| s.as_str())
|
|
|
|
.unwrap_or("")
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Determine if a given method is variadic.
|
|
|
|
///
|
2021-06-24 23:12:54 +00:00
|
|
|
/// Variadic methods shove excess parameters into a final register.
|
2021-06-10 01:08:13 +00:00
|
|
|
pub fn is_variadic(&self) -> bool {
|
|
|
|
self.method().needs_arguments_object || self.method().needs_rest
|
|
|
|
}
|
2021-06-24 23:12:54 +00:00
|
|
|
|
|
|
|
/// Determine if a given method is unchecked.
|
|
|
|
///
|
|
|
|
/// A method is unchecked if all of the following are true:
|
|
|
|
///
|
|
|
|
/// * The method was declared as a free-standing function
|
|
|
|
/// * The function does not use rest-parameters
|
|
|
|
/// * The function's parameters have no declared types or default values
|
|
|
|
/// * The function does not declare a return type
|
|
|
|
pub fn is_unchecked(&self) -> bool {
|
|
|
|
if !self.is_function {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for param in self.signature() {
|
|
|
|
if !param.param_type_name.is_any() || param.default_value.is_some() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
!self.method().needs_rest && self.return_type.is_any()
|
|
|
|
}
|
2020-07-03 01:49:53 +00:00
|
|
|
}
|
|
|
|
|
2021-06-19 01:49:40 +00:00
|
|
|
/// An uninstantiated method
|
2020-07-03 01:49:53 +00:00
|
|
|
#[derive(Clone)]
|
2021-06-19 01:49:40 +00:00
|
|
|
pub struct NativeMethod<'gc> {
|
|
|
|
/// The function to call to execute the method.
|
|
|
|
pub method: NativeMethodImpl,
|
2021-06-13 04:03:41 +00:00
|
|
|
|
2021-06-19 01:49:40 +00:00
|
|
|
/// The name of the method.
|
|
|
|
pub name: &'static str,
|
2021-06-13 04:03:41 +00:00
|
|
|
|
2021-06-19 01:49:40 +00:00
|
|
|
/// The parameter signature of the method.
|
|
|
|
pub signature: Vec<ParamConfig<'gc>>,
|
2020-07-03 01:49:53 +00:00
|
|
|
|
2021-06-19 01:49:40 +00:00
|
|
|
/// Whether or not this method accepts parameters beyond those
|
|
|
|
/// mentioned in the parameter list.
|
|
|
|
pub is_variadic: bool,
|
2020-07-03 01:49:53 +00:00
|
|
|
}
|
|
|
|
|
2021-06-19 01:49:40 +00:00
|
|
|
unsafe impl<'gc> Collect for NativeMethod<'gc> {
|
2020-07-03 01:49:53 +00:00
|
|
|
fn trace(&self, cc: CollectionContext) {
|
2021-06-19 01:49:40 +00:00
|
|
|
self.signature.trace(cc);
|
2020-07-03 01:49:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-19 01:49:40 +00:00
|
|
|
impl<'gc> fmt::Debug for NativeMethod<'gc> {
|
2020-07-03 01:49:53 +00:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2021-06-19 01:49:40 +00:00
|
|
|
f.debug_struct("NativeMethod")
|
|
|
|
.field("method", &format!("{:p}", &self.method))
|
|
|
|
.field("name", &self.name)
|
|
|
|
.field("signature", &self.signature)
|
|
|
|
.field("is_variadic", &self.is_variadic)
|
|
|
|
.finish()
|
2020-07-03 01:49:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-19 01:49:40 +00:00
|
|
|
/// An uninstantiated method that can either be natively implemented or sourced
|
|
|
|
/// from an ABC file.
|
|
|
|
#[derive(Clone, Collect, Debug)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub enum Method<'gc> {
|
|
|
|
/// A native method.
|
|
|
|
Native(Gc<'gc, NativeMethod<'gc>>),
|
|
|
|
|
|
|
|
/// An ABC-provided method entry.
|
2021-06-19 20:35:44 +00:00
|
|
|
Bytecode(Gc<'gc, BytecodeMethod<'gc>>),
|
2021-06-19 01:49:40 +00:00
|
|
|
}
|
|
|
|
|
2020-07-04 21:56:27 +00:00
|
|
|
impl<'gc> From<Gc<'gc, BytecodeMethod<'gc>>> for Method<'gc> {
|
|
|
|
fn from(bm: Gc<'gc, BytecodeMethod<'gc>>) -> Self {
|
2021-06-19 20:35:44 +00:00
|
|
|
Self::Bytecode(bm)
|
2020-07-03 01:49:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Method<'gc> {
|
2021-06-13 04:03:41 +00:00
|
|
|
/// Define a builtin method with a particular param configuration.
|
|
|
|
pub fn from_builtin_and_params(
|
2021-06-19 01:49:40 +00:00
|
|
|
method: NativeMethodImpl,
|
2021-06-13 04:03:41 +00:00
|
|
|
name: &'static str,
|
|
|
|
signature: Vec<ParamConfig<'gc>>,
|
|
|
|
is_variadic: bool,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Self {
|
2021-06-19 01:49:40 +00:00
|
|
|
Self::Native(Gc::allocate(
|
|
|
|
mc,
|
|
|
|
NativeMethod {
|
|
|
|
method,
|
|
|
|
name,
|
|
|
|
signature,
|
|
|
|
is_variadic,
|
|
|
|
},
|
|
|
|
))
|
2021-06-13 04:03:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Define a builtin with no parameter constraints.
|
2021-06-19 20:28:04 +00:00
|
|
|
pub fn from_builtin(
|
2021-06-19 01:49:40 +00:00
|
|
|
method: NativeMethodImpl,
|
2021-06-13 04:03:41 +00:00
|
|
|
name: &'static str,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Self {
|
2021-06-19 01:49:40 +00:00
|
|
|
Self::Native(Gc::allocate(
|
|
|
|
mc,
|
|
|
|
NativeMethod {
|
|
|
|
method,
|
|
|
|
name,
|
|
|
|
signature: Vec::new(),
|
|
|
|
is_variadic: true,
|
|
|
|
},
|
|
|
|
))
|
2020-07-07 04:20:57 +00:00
|
|
|
}
|
|
|
|
|
2020-07-04 21:56:27 +00:00
|
|
|
/// Access the bytecode of this method.
|
|
|
|
///
|
|
|
|
/// This function returns `Err` if there is no bytecode for this method.
|
|
|
|
pub fn into_bytecode(self) -> Result<Gc<'gc, BytecodeMethod<'gc>>, Error> {
|
2020-07-03 01:49:53 +00:00
|
|
|
match self {
|
2021-06-13 04:03:41 +00:00
|
|
|
Method::Native { .. } => {
|
2020-07-03 01:49:53 +00:00
|
|
|
Err("Attempted to unwrap a native method as a user-defined one".into())
|
|
|
|
}
|
2021-06-19 20:35:44 +00:00
|
|
|
Method::Bytecode(bm) => Ok(bm),
|
2020-07-03 01:49:53 +00:00
|
|
|
}
|
|
|
|
}
|
2021-10-23 23:25:22 +00:00
|
|
|
|
|
|
|
/// Check if this method needs `arguments`.
|
|
|
|
pub fn needs_arguments_object(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
Method::Native { .. } => false,
|
|
|
|
Method::Bytecode(bm) => bm.method().needs_arguments_object,
|
|
|
|
}
|
|
|
|
}
|
2020-07-03 01:49:53 +00:00
|
|
|
}
|