avm2: Resolve function argument types once when calling function for the first time

This commit is contained in:
Lord-McSweeney 2024-04-04 16:24:11 -07:00 committed by Lord-McSweeney
parent 9870754b5e
commit 9dcf0a7d70
7 changed files with 182 additions and 53 deletions

View File

@ -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

View File

@ -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) = &param_config.default_value {
Cow::Borrowed(default)
} else if param_config.param_type_name.is_any_name() {
value
} else if let Some(default_value) = &param_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, &param_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))
}

View File

@ -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

View File

@ -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,

View File

@ -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,
},

View File

@ -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

View File

@ -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(&param.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!(