avm2: Move top-level functions and __ruffle__ stub functions to ActionScript

This commit is contained in:
Lord-McSweeney 2023-09-24 11:39:55 -07:00 committed by Adrian Wielgosik
parent ff2814c84f
commit 4561fd3631
8 changed files with 220 additions and 281 deletions

View File

@ -1,8 +1,7 @@
use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::domain::Domain;
use crate::avm2::method::{Method, NativeMethodImpl};
use crate::avm2::object::{ClassObject, FunctionObject, Object, ScriptObject, TObject};
use crate::avm2::object::{ClassObject, Object, ScriptObject, TObject};
use crate::avm2::scope::{Scope, ScopeChain};
use crate::avm2::script::Script;
use crate::avm2::Avm2;
@ -15,6 +14,7 @@ use gc_arena::{Collect, GcCell, Mutation};
use std::sync::Arc;
use swf::TagCode;
mod __ruffle__;
mod array;
mod avmplus;
mod boolean;
@ -40,6 +40,19 @@ mod void;
mod xml;
mod xml_list;
pub use toplevel::decode_uri;
pub use toplevel::decode_uri_component;
pub use toplevel::encode_uri;
pub use toplevel::encode_uri_component;
pub use toplevel::escape;
pub use toplevel::is_finite;
pub use toplevel::is_na_n;
pub use toplevel::is_xml_name;
pub use toplevel::parse_float;
pub use toplevel::parse_int;
pub use toplevel::trace;
pub use toplevel::unescape;
/// This structure represents all system builtin classes.
#[derive(Clone, Collect)]
#[collect(no_drop)]
@ -282,27 +295,30 @@ impl<'gc> SystemClasses<'gc> {
}
}
/// Add a free-function builtin to the global scope.
fn function<'gc>(
/// Looks up a function defined in the script domain, and defines it on the global object.
///
/// This expects the looked-up value to be a function.
fn define_fn_on_global<'gc>(
activation: &mut Activation<'_, 'gc>,
package: impl Into<AvmString<'gc>>,
name: &'static str,
nf: NativeMethodImpl,
script: Script<'gc>,
) -> Result<(), Error<'gc>> {
let (_, global, mut domain) = script.init();
let mc = activation.context.gc_context;
let scope = activation.create_scopechain();
) {
let (_, global, domain) = script.init();
let qname = QName::new(
Namespace::package(package, &mut activation.borrow_gc()),
name,
);
let method = Method::from_builtin(nf, name, mc);
let as3fn = FunctionObject::from_method(activation, method, scope, None, None).into();
domain.export_definition(qname, script, mc);
global.install_const_late(mc, qname, as3fn, activation.avm2().classes().function);
let func = domain
.get_defined_value(activation, qname)
.expect("Function being defined on global should be defined in domain!");
Ok(())
global.install_const_late(
activation.context.gc_context,
qname,
func,
activation.avm2().classes().function,
);
}
/// Add a fully-formed class object builtin to the global scope.
@ -561,66 +577,6 @@ pub fn load_player_globals<'gc>(
// it should only be visible as an type for typecheck/cast purposes.
avm2_system_class!(void, activation, void::create_class(activation), script);
function(activation, "", "trace", toplevel::trace, script)?;
function(
activation,
"__ruffle__",
"log_warn",
toplevel::log_warn,
script,
)?;
function(
activation,
"__ruffle__",
"stub_method",
toplevel::stub_method,
script,
)?;
function(
activation,
"__ruffle__",
"stub_getter",
toplevel::stub_getter,
script,
)?;
function(
activation,
"__ruffle__",
"stub_setter",
toplevel::stub_setter,
script,
)?;
function(
activation,
"__ruffle__",
"stub_constructor",
toplevel::stub_constructor,
script,
)?;
function(activation, "", "isFinite", toplevel::is_finite, script)?;
function(activation, "", "isNaN", toplevel::is_nan, script)?;
function(activation, "", "isXMLName", toplevel::is_xml_name, script)?;
function(activation, "", "parseInt", toplevel::parse_int, script)?;
function(activation, "", "parseFloat", toplevel::parse_float, script)?;
function(activation, "", "escape", toplevel::escape, script)?;
function(activation, "", "encodeURI", toplevel::encode_uri, script)?;
function(
activation,
"",
"encodeURIComponent",
toplevel::encode_uri_component,
script,
)?;
function(activation, "", "decodeURI", toplevel::decode_uri, script)?;
function(
activation,
"",
"decodeURIComponent",
toplevel::decode_uri_component,
script,
)?;
function(activation, "", "unescape", toplevel::unescape, script)?;
avm2_system_class!(
generic_vector,
activation,
@ -658,10 +614,24 @@ pub fn load_player_globals<'gc>(
// Inside this call, the macro `avm2_system_classes_playerglobal`
// triggers classloading. Therefore, we run `load_playerglobal`
// relative late, so that it can access classes defined before
// relatively late, so that it can access classes defined before
// this call.
load_playerglobal(activation, domain)?;
// Except for `trace`, top-level builtin functions are defined
// on the `global` object.
define_fn_on_global(activation, "", "decodeURI", script);
define_fn_on_global(activation, "", "decodeURIComponent", script);
define_fn_on_global(activation, "", "encodeURI", script);
define_fn_on_global(activation, "", "encodeURIComponent", script);
define_fn_on_global(activation, "", "escape", script);
define_fn_on_global(activation, "", "unescape", script);
define_fn_on_global(activation, "", "isXMLName", script);
define_fn_on_global(activation, "", "isFinite", script);
define_fn_on_global(activation, "", "isNaN", script);
define_fn_on_global(activation, "", "parseFloat", script);
define_fn_on_global(activation, "", "parseInt", script);
Ok(())
}

View File

@ -6,4 +6,23 @@ package {
public const Infinity: Number = 1 / 0;
public const undefined = void 0;
public native function encodeURI(uri:String = "undefined"):String;
public native function encodeURIComponent(uri:String = "undefined"):String;
public native function decodeURI(uri:String = "undefined"):String;
public native function decodeURIComponent(uri:String = "undefined"):String;
public native function escape(string:String = "undefined"):String;
public native function unescape(string:String = "undefined"):String;
public native function isXMLName(string:* = undefined):Boolean;
public native function isFinite(value:Number = undefined):Boolean;
public native function isNaN(value:Number = undefined):Boolean;
public native function parseFloat(number:String = "NaN"):Number;
public native function parseInt(string:String = "NaN", base:int = 0):Number;
public native function trace(... rest):void;
}

View File

@ -0,0 +1,122 @@
use crate::avm2::activation::Activation;
use crate::avm2::error::Error;
use crate::avm2::object::Object;
use crate::avm2::value::Value;
use crate::stub::Stub;
use std::borrow::Cow;
pub fn stub_method<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
match args {
[class, method] => {
let class = class.coerce_to_string(activation)?;
let method = method.coerce_to_string(activation)?;
activation
.context
.stub_tracker
.encounter(&Stub::Avm2Method {
class: Cow::Owned(class.to_utf8_lossy().to_string()),
method: Cow::Owned(method.to_utf8_lossy().to_string()),
specifics: None,
});
}
[class, method, specifics] => {
let class = class.coerce_to_string(activation)?;
let method = method.coerce_to_string(activation)?;
let specifics = specifics.coerce_to_string(activation)?;
activation
.context
.stub_tracker
.encounter(&Stub::Avm2Method {
class: Cow::Owned(class.to_utf8_lossy().to_string()),
method: Cow::Owned(method.to_utf8_lossy().to_string()),
specifics: Some(Cow::Owned(specifics.to_utf8_lossy().to_string())),
});
}
_ => tracing::warn!("(__ruffle__.stub_method called with wrong args)"),
}
Ok(Value::Undefined)
}
pub fn stub_getter<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
match args {
[class, property] => {
let class = class.coerce_to_string(activation)?;
let property = property.coerce_to_string(activation)?;
activation
.context
.stub_tracker
.encounter(&Stub::Avm2Getter {
class: Cow::Owned(class.to_utf8_lossy().to_string()),
property: Cow::Owned(property.to_utf8_lossy().to_string()),
});
}
_ => tracing::warn!("(__ruffle__.stub_getter called with wrong args)"),
}
Ok(Value::Undefined)
}
pub fn stub_setter<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
match args {
[class, property] => {
let class = class.coerce_to_string(activation)?;
let property = property.coerce_to_string(activation)?;
activation
.context
.stub_tracker
.encounter(&Stub::Avm2Setter {
class: Cow::Owned(class.to_utf8_lossy().to_string()),
property: Cow::Owned(property.to_utf8_lossy().to_string()),
});
}
_ => tracing::warn!("(__ruffle__.stub_setter called with wrong args)"),
}
Ok(Value::Undefined)
}
pub fn stub_constructor<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
match args {
[class] => {
let class = class.coerce_to_string(activation)?;
activation
.context
.stub_tracker
.encounter(&Stub::Avm2Constructor {
class: Cow::Owned(class.to_utf8_lossy().to_string()),
specifics: None,
});
}
[class, specifics] => {
let class = class.coerce_to_string(activation)?;
let specifics = specifics.coerce_to_string(activation)?;
activation
.context
.stub_tracker
.encounter(&Stub::Avm2Constructor {
class: Cow::Owned(class.to_utf8_lossy().to_string()),
specifics: Some(Cow::Owned(specifics.to_utf8_lossy().to_string())),
});
}
_ => tracing::warn!("(__ruffle__.stub_constructor called with wrong args)"),
}
Ok(Value::Undefined)
}

View File

@ -1,11 +0,0 @@
package __ruffle__ {
public native function log_warn(...arguments);
public native function stub_method(class_name, method);
public native function stub_getter(class_name, method);
public native function stub_setter(class_name, method);
public native function stub_constructor(class_name, method);
}

View File

@ -0,0 +1,9 @@
package __ruffle__ {
public native function stub_method(... rest):void;
public native function stub_getter(... rest):void;
public native function stub_setter(... rest):void;
public native function stub_constructor(... rest):void;
}

View File

@ -1,6 +1,8 @@
// List is ordered alphabetically, except where superclasses/interfaces
// need to come before subclasses and implementations.
include "__ruffle__/stubs.as"
include "Error.as"
include "ArgumentError.as"

View File

@ -16,4 +16,3 @@ include "String.as"
include "int.as"
include "uint.as"
include "Vector.as"
include "__ruffle__/logging.as"

View File

@ -5,11 +5,10 @@ use ruffle_wstr::Units;
use crate::avm2::activation::Activation;
use crate::avm2::error::{uri_error, Error};
use crate::avm2::object::Object;
use crate::avm2::parameters::ParametersExt;
use crate::avm2::value::Value;
use crate::string::{AvmString, WStr, WString};
use crate::stub::Stub;
use ruffle_wstr::Integer;
use std::borrow::Cow;
use std::fmt::Write;
pub fn trace<'gc>(
@ -36,168 +35,24 @@ pub fn trace<'gc>(
Ok(Value::Undefined)
}
pub fn log_warn<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
match args {
[] => tracing::warn!("(__ruffle__.log_warn called with no arg)"),
[arg] => {
let msg = arg.coerce_to_string(activation)?;
tracing::warn!("{}", &msg.to_utf8_lossy());
}
args => {
let strings = args
.iter()
.map(|a| a.coerce_to_string(activation))
.collect::<Result<Vec<_>, _>>()?;
let msg = crate::string::join(&strings, &WStr::from_units(b" "));
tracing::warn!("{}", &msg.to_utf8_lossy());
}
}
Ok(Value::Undefined)
}
pub fn stub_method<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
match args {
[class, method] => {
let class = class.coerce_to_string(activation)?;
let method = method.coerce_to_string(activation)?;
activation
.context
.stub_tracker
.encounter(&Stub::Avm2Method {
class: Cow::Owned(class.to_utf8_lossy().to_string()),
method: Cow::Owned(method.to_utf8_lossy().to_string()),
specifics: None,
});
}
[class, method, specifics] => {
let class = class.coerce_to_string(activation)?;
let method = method.coerce_to_string(activation)?;
let specifics = specifics.coerce_to_string(activation)?;
activation
.context
.stub_tracker
.encounter(&Stub::Avm2Method {
class: Cow::Owned(class.to_utf8_lossy().to_string()),
method: Cow::Owned(method.to_utf8_lossy().to_string()),
specifics: Some(Cow::Owned(specifics.to_utf8_lossy().to_string())),
});
}
_ => tracing::warn!("(__ruffle__.stub_method called with wrong args)"),
}
Ok(Value::Undefined)
}
pub fn stub_getter<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
match args {
[class, property] => {
let class = class.coerce_to_string(activation)?;
let property = property.coerce_to_string(activation)?;
activation
.context
.stub_tracker
.encounter(&Stub::Avm2Getter {
class: Cow::Owned(class.to_utf8_lossy().to_string()),
property: Cow::Owned(property.to_utf8_lossy().to_string()),
});
}
_ => tracing::warn!("(__ruffle__.stub_getter called with wrong args)"),
}
Ok(Value::Undefined)
}
pub fn stub_setter<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
match args {
[class, property] => {
let class = class.coerce_to_string(activation)?;
let property = property.coerce_to_string(activation)?;
activation
.context
.stub_tracker
.encounter(&Stub::Avm2Setter {
class: Cow::Owned(class.to_utf8_lossy().to_string()),
property: Cow::Owned(property.to_utf8_lossy().to_string()),
});
}
_ => tracing::warn!("(__ruffle__.stub_setter called with wrong args)"),
}
Ok(Value::Undefined)
}
pub fn stub_constructor<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
match args {
[class] => {
let class = class.coerce_to_string(activation)?;
activation
.context
.stub_tracker
.encounter(&Stub::Avm2Constructor {
class: Cow::Owned(class.to_utf8_lossy().to_string()),
specifics: None,
});
}
[class, specifics] => {
let class = class.coerce_to_string(activation)?;
let specifics = specifics.coerce_to_string(activation)?;
activation
.context
.stub_tracker
.encounter(&Stub::Avm2Constructor {
class: Cow::Owned(class.to_utf8_lossy().to_string()),
specifics: Some(Cow::Owned(specifics.to_utf8_lossy().to_string())),
});
}
_ => tracing::warn!("(__ruffle__.stub_constructor called with wrong args)"),
}
Ok(Value::Undefined)
}
pub fn is_finite<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(val) = args.get(0) {
Ok(val.coerce_to_number(activation)?.is_finite().into())
} else {
Ok(false.into())
}
let val = args.get_f64(activation, 0)?;
Ok(val.is_finite().into())
}
pub fn is_nan<'gc>(
pub fn is_na_n<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(val) = args.get(0) {
Ok(val.coerce_to_number(activation)?.is_nan().into())
} else {
Ok(true.into())
}
let val = args.get_f64(activation, 0)?;
Ok(val.is_nan().into())
}
pub fn parse_int<'gc>(
@ -205,15 +60,8 @@ pub fn parse_int<'gc>(
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let string = match args.get(0).unwrap_or(&Value::Undefined) {
Value::Undefined => "null".into(),
value => value.coerce_to_string(activation)?,
};
let radix = match args.get(1) {
Some(value) => value.coerce_to_i32(activation)?,
None => 0,
};
let string = args.get_string(activation, 0)?;
let radix = args.get_i32(activation, 1)?;
let result = crate::avm2::value::string_to_int(&string, radix, false);
Ok(result.into())
@ -224,15 +72,14 @@ pub fn parse_float<'gc>(
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(value) = args.get(0) {
let string = value.coerce_to_string(activation)?;
let swf_version = activation.context.swf.version();
if let Some(result) = crate::avm2::value::string_to_f64(&string, swf_version, false) {
return Ok(result.into());
}
}
let string = args.get_string(activation, 0)?;
let swf_version = activation.context.swf.version();
Ok(f64::NAN.into())
if let Some(result) = crate::avm2::value::string_to_f64(&string, swf_version, false) {
Ok(result.into())
} else {
Ok(f64::NAN.into())
}
}
pub fn is_xml_name<'gc>(
@ -240,7 +87,7 @@ pub fn is_xml_name<'gc>(
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let name = args.get(0).unwrap_or(&Value::Undefined);
let name = args.get_value(0);
if matches!(name, Value::Undefined | Value::Null) {
return Ok(false.into());
}
@ -255,11 +102,7 @@ pub fn escape<'gc>(
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let value = match args.first() {
None => return Ok("undefined".into()),
Some(Value::Undefined) => return Ok("null".into()),
Some(value) => value,
};
let value = args.get_string(activation, 0)?;
let mut output = WString::new();
@ -267,7 +110,7 @@ pub fn escape<'gc>(
let not_converted =
WStr::from_units(b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@-_.*+/");
for x in value.coerce_to_string(activation)?.iter() {
for x in value.iter() {
if not_converted.contains(x) {
output.push(x);
} else {
@ -288,11 +131,7 @@ pub fn unescape<'gc>(
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let value = match args.first() {
None => return Ok("undefined".into()),
Some(Value::Undefined) => return Ok("null".into()),
Some(value) => value.coerce_to_string(activation)?,
};
let value = args.get_string(activation, 0)?;
let mut output = WString::new();
let mut index = 0;
@ -361,15 +200,9 @@ fn encode_utf8_with_exclusions<'gc>(
args: &[Value<'gc>],
not_converted: &str,
) -> Result<Value<'gc>, Error<'gc>> {
let value = match args.first() {
None => return Ok("undefined".into()),
Some(Value::Undefined) => return Ok("null".into()),
Some(value) => value,
};
let input = args.get_string(activation, 0)?;
let mut output = String::new();
let input = value.coerce_to_string(activation)?;
let input_string = match input.units() {
// Latin-1 values map directly to unicode codepoints,
// so we can directly convert to a `char`
@ -434,11 +267,7 @@ fn decode<'gc>(
reserved_set: &str,
func_name: &str,
) -> Result<Value<'gc>, Error<'gc>> {
let value = match args.first() {
None => return Ok("undefined".into()),
Some(Value::Undefined) => return Ok("null".into()),
Some(value) => value.coerce_to_string(activation)?,
};
let value = args.get_string(activation, 0)?;
let mut output = WString::new();
let mut chars = value.chars();