avm2: Resolve function argument types once when calling function for the first time
This commit is contained in:
parent
9870754b5e
commit
9dcf0a7d70
|
@ -295,12 +295,19 @@ impl<'gc> Avm2<'gc> {
|
|||
let (method, scope, _domain) = script.init();
|
||||
match method {
|
||||
Method::Native(method) => {
|
||||
//This exists purely to check if the builtin is OK with being called with
|
||||
//no parameters.
|
||||
if method.resolved_signature.read().is_none() {
|
||||
method.resolve_signature(&mut init_activation)?;
|
||||
}
|
||||
|
||||
let resolved_signature = method.resolved_signature.read();
|
||||
let resolved_signature = resolved_signature.as_ref().unwrap();
|
||||
|
||||
// This exists purely to check if the builtin is OK with being called with
|
||||
// no parameters.
|
||||
init_activation.resolve_parameters(
|
||||
Method::Native(method),
|
||||
&[],
|
||||
&method.signature,
|
||||
resolved_signature,
|
||||
None,
|
||||
)?;
|
||||
init_activation
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::avm2::error::{
|
|||
make_error_1127, make_error_1506, make_null_or_undefined_error, make_reference_error,
|
||||
type_error, ReferenceErrorCode,
|
||||
};
|
||||
use crate::avm2::method::{BytecodeMethod, Method, ParamConfig};
|
||||
use crate::avm2::method::{BytecodeMethod, Method, ResolvedParamConfig};
|
||||
use crate::avm2::object::{
|
||||
ArrayObject, ByteArrayObject, ClassObject, FunctionObject, NamespaceObject, ScriptObject,
|
||||
XmlListObject,
|
||||
|
@ -26,7 +26,6 @@ use crate::string::{AvmAtom, AvmString};
|
|||
use crate::tag_utils::SwfMovie;
|
||||
use gc_arena::{Gc, GcCell};
|
||||
use smallvec::SmallVec;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::{min, Ordering};
|
||||
use std::sync::Arc;
|
||||
use swf::avm2::types::{
|
||||
|
@ -258,7 +257,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
None
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
let mut created_activation = Self {
|
||||
ip: 0,
|
||||
actions_since_timeout_check: 0,
|
||||
local_registers,
|
||||
|
@ -272,7 +271,16 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
max_stack_size: max_stack as usize,
|
||||
max_scope_size: max_scope as usize,
|
||||
context,
|
||||
})
|
||||
};
|
||||
|
||||
// Run verifier for bytecode methods
|
||||
if let Method::Bytecode(method) = method {
|
||||
if method.verified_info.read().is_none() {
|
||||
method.verify(&mut created_activation)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(created_activation)
|
||||
}
|
||||
|
||||
/// Finds an object on either the current or outer scope of this activation by definition.
|
||||
|
@ -337,15 +345,17 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
&mut self,
|
||||
method: Method<'gc>,
|
||||
value: Option<&Value<'gc>>,
|
||||
param_config: &ParamConfig<'gc>,
|
||||
param_config: &ResolvedParamConfig<'gc>,
|
||||
user_arguments: &[Value<'gc>],
|
||||
callee: Option<Object<'gc>>,
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let arg = if let Some(value) = value {
|
||||
Cow::Borrowed(value)
|
||||
} else if let Some(default) = ¶m_config.default_value {
|
||||
Cow::Borrowed(default)
|
||||
} else if param_config.param_type_name.is_any_name() {
|
||||
value
|
||||
} else if let Some(default_value) = ¶m_config.default_value {
|
||||
default_value
|
||||
} else if param_config.param_type.is_none() {
|
||||
// TODO: FP's system of allowing missing arguments
|
||||
// is a more complicated than this.
|
||||
return Ok(Value::Undefined);
|
||||
} else {
|
||||
return Err(Error::AvmError(make_mismatch_error(
|
||||
|
@ -356,7 +366,11 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
)?));
|
||||
};
|
||||
|
||||
arg.coerce_to_type_name(self, ¶m_config.param_type_name)
|
||||
if let Some(param_class) = param_config.param_type {
|
||||
arg.coerce_to_type(self, param_class)
|
||||
} else {
|
||||
Ok(*arg)
|
||||
}
|
||||
}
|
||||
|
||||
/// Statically resolve all of the parameters for a given method.
|
||||
|
@ -370,7 +384,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
&mut self,
|
||||
method: Method<'gc>,
|
||||
user_arguments: &[Value<'gc>],
|
||||
signature: &[ParamConfig<'gc>],
|
||||
signature: &[ResolvedParamConfig<'gc>],
|
||||
callee: Option<Object<'gc>>,
|
||||
) -> Result<Vec<Value<'gc>>, Error<'gc>> {
|
||||
let mut arguments_list = Vec::new();
|
||||
|
@ -426,13 +440,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
let body = body?;
|
||||
let num_locals = body.num_locals;
|
||||
let has_rest_or_args = method.is_variadic();
|
||||
let arg_register = if has_rest_or_args { 1 } else { 0 };
|
||||
let signature: &[ParamConfig<'_>] = method.signature();
|
||||
|
||||
let num_declared_arguments = signature.len() as u32;
|
||||
|
||||
let mut local_registers =
|
||||
RegisterSet::new(num_locals + num_declared_arguments + arg_register + 1);
|
||||
let mut local_registers = RegisterSet::new(num_locals + 1);
|
||||
*local_registers.get_unchecked_mut(0) = this.into();
|
||||
|
||||
let activation_class =
|
||||
|
@ -464,6 +473,14 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
self.max_stack_size = body.max_stack as usize;
|
||||
self.max_scope_size = (body.max_scope_depth - body.init_scope_depth) as usize;
|
||||
|
||||
// Everything is now setup for the verifier to run
|
||||
if method.verified_info.read().is_none() {
|
||||
method.verify(self)?;
|
||||
}
|
||||
|
||||
let verified_info = method.verified_info.read();
|
||||
let signature = &verified_info.as_ref().unwrap().param_config;
|
||||
|
||||
if user_arguments.len() > signature.len() && !has_rest_or_args {
|
||||
return Err(Error::AvmError(make_mismatch_error(
|
||||
self,
|
||||
|
@ -525,7 +542,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
|
||||
*self
|
||||
.local_registers
|
||||
.get_unchecked_mut(1 + num_declared_arguments) = args_object.into();
|
||||
.get_unchecked_mut(1 + signature.len() as u32) = args_object.into();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -797,9 +814,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
&mut self,
|
||||
method: Gc<'gc, BytecodeMethod<'gc>>,
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if method.verified_info.read().is_none() {
|
||||
method.verify(self)?;
|
||||
}
|
||||
// The method must be verified at this point
|
||||
|
||||
let verified_info = method.verified_info.read();
|
||||
let verified_code = verified_info.as_ref().unwrap().parsed_code.as_slice();
|
||||
|
@ -1352,7 +1367,14 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
method: Gc<'gc, BytecodeMethod<'gc>>,
|
||||
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||
let return_value = self.pop_stack();
|
||||
let coerced = return_value.coerce_to_type_name(self, &method.return_type)?;
|
||||
let return_type = method.resolved_return_type();
|
||||
|
||||
let coerced = if let Some(return_type) = return_type {
|
||||
return_value.coerce_to_type(self, return_type)?
|
||||
} else {
|
||||
return_value
|
||||
};
|
||||
|
||||
Ok(FrameControl::Return(coerced))
|
||||
}
|
||||
|
||||
|
|
|
@ -145,10 +145,17 @@ impl<'gc> Executable<'gc> {
|
|||
.into());
|
||||
}
|
||||
|
||||
if bm.method.resolved_signature.read().is_none() {
|
||||
bm.method.resolve_signature(&mut activation)?;
|
||||
}
|
||||
|
||||
let resolved_signature = bm.method.resolved_signature.read();
|
||||
let resolved_signature = resolved_signature.as_ref().unwrap();
|
||||
|
||||
let arguments = activation.resolve_parameters(
|
||||
Method::Native(bm.method),
|
||||
arguments,
|
||||
&bm.method.signature,
|
||||
resolved_signature,
|
||||
Some(callee),
|
||||
)?;
|
||||
activation
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
//! AVM2 methods
|
||||
|
||||
use crate::avm2::activation::Activation;
|
||||
use crate::avm2::class::Class;
|
||||
use crate::avm2::object::{ClassObject, Object};
|
||||
use crate::avm2::script::TranslationUnit;
|
||||
use crate::avm2::value::{abc_default_value, Value};
|
||||
use crate::avm2::verify::VerifiedMethodInfo;
|
||||
use crate::avm2::verify::{resolve_param_config, VerifiedMethodInfo};
|
||||
use crate::avm2::Error;
|
||||
use crate::avm2::Multiname;
|
||||
use crate::string::AvmString;
|
||||
|
@ -37,6 +38,21 @@ pub type NativeMethodImpl = for<'gc> fn(
|
|||
&[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>>;
|
||||
|
||||
/// Configuration of a single parameter of a method,
|
||||
/// with the parameter's type resolved.
|
||||
#[derive(Clone, Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub struct ResolvedParamConfig<'gc> {
|
||||
/// The name of the parameter.
|
||||
pub param_name: AvmString<'gc>,
|
||||
|
||||
/// The type of the parameter.
|
||||
pub param_type: Option<GcCell<'gc, Class<'gc>>>,
|
||||
|
||||
/// The default value for this parameter.
|
||||
pub default_value: Option<Value<'gc>>,
|
||||
}
|
||||
|
||||
/// Configuration of a single parameter of a method.
|
||||
#[derive(Clone, Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
|
@ -238,6 +254,12 @@ impl<'gc> BytecodeMethod<'gc> {
|
|||
&self.signature
|
||||
}
|
||||
|
||||
pub fn resolved_return_type(&self) -> Option<GcCell<'gc, Class<'gc>>> {
|
||||
let verified_info = self.verified_info.read();
|
||||
|
||||
verified_info.as_ref().unwrap().return_type
|
||||
}
|
||||
|
||||
/// Get the name of this method.
|
||||
pub fn method_name(&self) -> Cow<'_, str> {
|
||||
let name_index = self.method().name.0 as usize;
|
||||
|
@ -319,6 +341,9 @@ pub struct NativeMethod<'gc> {
|
|||
/// The parameter signature of the method.
|
||||
pub signature: Vec<ParamConfig<'gc>>,
|
||||
|
||||
/// The resolved parameter signature of the method.
|
||||
pub resolved_signature: GcCell<'gc, Option<Vec<ResolvedParamConfig<'gc>>>>,
|
||||
|
||||
/// The return type of this method.
|
||||
pub return_type: Multiname<'gc>,
|
||||
|
||||
|
@ -327,6 +352,18 @@ pub struct NativeMethod<'gc> {
|
|||
pub is_variadic: bool,
|
||||
}
|
||||
|
||||
impl<'gc> NativeMethod<'gc> {
|
||||
pub fn resolve_signature(
|
||||
&self,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
*self.resolved_signature.write(activation.context.gc_context) =
|
||||
Some(resolve_param_config(activation, &self.signature)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> fmt::Debug for NativeMethod<'gc> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("NativeMethod")
|
||||
|
@ -372,6 +409,7 @@ impl<'gc> Method<'gc> {
|
|||
method,
|
||||
name,
|
||||
signature,
|
||||
resolved_signature: GcCell::new(mc, None),
|
||||
return_type,
|
||||
is_variadic,
|
||||
},
|
||||
|
@ -386,6 +424,7 @@ impl<'gc> Method<'gc> {
|
|||
method,
|
||||
name,
|
||||
signature: Vec::new(),
|
||||
resolved_signature: GcCell::new(mc, None),
|
||||
// FIXME - take in the real return type. This is needed for 'describeType'
|
||||
return_type: Multiname::any(mc),
|
||||
is_variadic: true,
|
||||
|
|
|
@ -31,6 +31,7 @@ pub fn function_allocator<'gc>(
|
|||
method: |_, _, _| Ok(Value::Undefined),
|
||||
name: "<Empty Function>",
|
||||
signature: vec![],
|
||||
resolved_signature: GcCell::new(activation.context.gc_context, None),
|
||||
return_type: Multiname::any(activation.context.gc_context),
|
||||
is_variadic: true,
|
||||
},
|
||||
|
|
|
@ -989,30 +989,6 @@ impl<'gc> Value<'gc> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Like `coerce_to_type`, but also performs resolution of the type name.
|
||||
/// This is used to allow coercing to a class while the ClassObject is still
|
||||
/// being initialized. We should eventually be able to remove this, once
|
||||
/// our Class/ClassObject representation is refactored.
|
||||
pub fn coerce_to_type_name(
|
||||
&self,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
type_name: &Multiname<'gc>,
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if type_name.is_any_name() {
|
||||
return Ok(*self);
|
||||
}
|
||||
let param_type = activation
|
||||
.domain()
|
||||
.get_class(type_name, activation.context.gc_context)
|
||||
.ok_or_else(|| {
|
||||
Error::RustError(
|
||||
format!("Failed to lookup class {:?} during coercion", type_name).into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
self.coerce_to_type(activation, param_type)
|
||||
}
|
||||
|
||||
/// Coerce the value to another value by type name.
|
||||
///
|
||||
/// This function implements a handful of coercion rules that appear to be
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::avm2::error::{
|
|||
make_error_1014, make_error_1021, make_error_1025, make_error_1032, make_error_1054,
|
||||
make_error_1107, verify_error,
|
||||
};
|
||||
use crate::avm2::method::BytecodeMethod;
|
||||
use crate::avm2::method::{BytecodeMethod, ParamConfig, ResolvedParamConfig};
|
||||
use crate::avm2::multiname::Multiname;
|
||||
use crate::avm2::op::Op;
|
||||
use crate::avm2::script::TranslationUnit;
|
||||
|
@ -24,6 +24,9 @@ pub struct VerifiedMethodInfo<'gc> {
|
|||
pub parsed_code: Vec<Op<'gc>>,
|
||||
|
||||
pub exceptions: Vec<Exception<'gc>>,
|
||||
|
||||
pub param_config: Vec<ResolvedParamConfig<'gc>>,
|
||||
pub return_type: Option<GcCell<'gc, Class<'gc>>>,
|
||||
}
|
||||
|
||||
#[derive(Collect)]
|
||||
|
@ -64,10 +67,15 @@ pub fn verify_method<'gc>(
|
|||
|
||||
// Ensure there are enough local variables
|
||||
// to fit the parameters in.
|
||||
if (max_locals as usize) < param_count + 1 {
|
||||
if (max_locals as usize) < 1 + param_count {
|
||||
return Err(make_error_1107(activation));
|
||||
}
|
||||
|
||||
if (max_locals as usize) < 1 + param_count + if method.is_variadic() { 1 } else { 0 } {
|
||||
// This matches FP's error message
|
||||
return Err(make_error_1025(activation, 1 + param_count as u32));
|
||||
}
|
||||
|
||||
use swf::extensions::ReadSwfExt;
|
||||
|
||||
if body.code.is_empty() {
|
||||
|
@ -78,6 +86,9 @@ pub fn verify_method<'gc>(
|
|||
)?));
|
||||
}
|
||||
|
||||
let resolved_param_config = resolve_param_config(activation, method.signature())?;
|
||||
let resolved_return_type = resolve_return_type(activation, &method.return_type)?;
|
||||
|
||||
let mut worklist = vec![0];
|
||||
|
||||
let mut byte_info = vec![ByteInfo::NotYetReached; body.code.len()];
|
||||
|
@ -576,9 +587,75 @@ pub fn verify_method<'gc>(
|
|||
Ok(VerifiedMethodInfo {
|
||||
parsed_code: verified_code,
|
||||
exceptions: new_exceptions,
|
||||
param_config: resolved_param_config,
|
||||
return_type: resolved_return_type,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resolve_param_config<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
param_config: &[ParamConfig<'gc>],
|
||||
) -> Result<Vec<ResolvedParamConfig<'gc>>, Error<'gc>> {
|
||||
let mut resolved_param_config = Vec::new();
|
||||
|
||||
for param in param_config {
|
||||
if param.param_type_name.has_lazy_component() {
|
||||
return Err(make_error_1014(activation, "[]".into()));
|
||||
}
|
||||
|
||||
let resolved_class = if param.param_type_name.is_any_name() {
|
||||
None
|
||||
} else {
|
||||
let lookedup_class = activation
|
||||
.domain()
|
||||
.get_class(¶m.param_type_name, activation.context.gc_context)
|
||||
.ok_or_else(|| {
|
||||
make_error_1014(
|
||||
activation,
|
||||
param
|
||||
.param_type_name
|
||||
.to_qualified_name(activation.context.gc_context),
|
||||
)
|
||||
})?;
|
||||
|
||||
Some(lookedup_class)
|
||||
};
|
||||
|
||||
resolved_param_config.push(ResolvedParamConfig {
|
||||
param_name: param.param_name,
|
||||
param_type: resolved_class,
|
||||
default_value: param.default_value,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(resolved_param_config)
|
||||
}
|
||||
|
||||
fn resolve_return_type<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
return_type: &Multiname<'gc>,
|
||||
) -> Result<Option<GcCell<'gc, Class<'gc>>>, Error<'gc>> {
|
||||
if return_type.has_lazy_component() {
|
||||
return Err(make_error_1014(activation, "[]".into()));
|
||||
}
|
||||
|
||||
if return_type.is_any_name() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(
|
||||
activation
|
||||
.domain()
|
||||
.get_class(return_type, activation.context.gc_context)
|
||||
.ok_or_else(|| {
|
||||
make_error_1014(
|
||||
activation,
|
||||
return_type.to_qualified_name(activation.context.gc_context),
|
||||
)
|
||||
})?,
|
||||
))
|
||||
}
|
||||
|
||||
// Taken from avmplus's opcodes.tbl
|
||||
fn op_can_throw_error(op: &AbcOp) -> bool {
|
||||
!matches!(
|
||||
|
|
Loading…
Reference in New Issue