Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avm2: Various Object / ClassObject cleanups #5325

Merged
merged 15 commits into from
Sep 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions core/src/avm2/activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
.ok_or_else(|| format!("Could not resolve parameter type {:?}", type_name))?
.coerce_to_object(self)?;

if class.as_class().is_none() {
if class.as_class_object().is_none() {
return Err(format!("Resolved parameter type {:?} is not a class", type_name).into());
}

Expand Down Expand Up @@ -502,6 +502,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
) -> Result<Value<'gc>, Error> {
let superclass_object: Result<Object<'gc>, Error> = self
.subclass_object()
.and_then(|c| c.as_class_object())
.and_then(|c| c.superclass_object())
.ok_or_else(|| {
"Attempted to call super constructor without a superclass."
Expand Down Expand Up @@ -1106,7 +1107,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
.resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let name = name?;
let superclass_object = if let Some(c) = receiver.as_class_object() {
let superclass_object = if let Some(c) = receiver.instance_of() {
c.find_class_for_trait(&name)?
} else {
None
Expand Down Expand Up @@ -1156,7 +1157,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
.resolve_multiname(&multiname)?
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let name = name?;
let superclass_object = if let Some(c) = receiver.as_class_object() {
let superclass_object = if let Some(c) = receiver.instance_of() {
c.find_class_for_trait(&name)?
} else {
None
Expand All @@ -1181,7 +1182,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let method = self.table_method(method, index, false)?;
let scope = self.scope(); //TODO: Is this correct?
let function = FunctionObject::from_method(self, method.into(), scope, None);
let value = function.call(Some(receiver), &args, self, receiver.as_class_object())?;
let value = function.call(Some(receiver), &args, self, receiver.instance_of())?;

self.context.avm2.push(value);

Expand All @@ -1205,6 +1206,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {

let superclass_object: Result<Object<'gc>, Error> = self
.subclass_object()
.and_then(|c| c.as_class_object())
.and_then(|bc| bc.superclass_object())
.ok_or_else(|| {
"Attempted to call super method without a superclass."
Expand Down Expand Up @@ -1237,6 +1239,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {

let superclass_object: Result<Object<'gc>, Error> = self
.subclass_object()
.and_then(|c| c.as_class_object())
.and_then(|bc| bc.superclass_object())
.ok_or_else(|| {
"Attempted to call super method without a superclass."
Expand Down Expand Up @@ -1276,7 +1279,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// dynamic properties not yet set
if name.is_err()
&& !object
.as_class()
.instance_of_class_definition()
.map(|c| c.read().is_sealed())
.unwrap_or(false)
{
Expand Down Expand Up @@ -1354,7 +1357,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// Unknown properties on a dynamic class delete successfully.
self.context.avm2.push(
!object
.as_class()
.instance_of_class_definition()
.map(|c| c.read().is_sealed())
.unwrap_or(false),
)
Expand All @@ -1378,6 +1381,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {

let superclass_object: Result<Object<'gc>, Error> = self
.subclass_object()
.and_then(|c| c.as_class_object())
.and_then(|bc| bc.superclass_object())
.ok_or_else(|| {
"Attempted to call super method without a superclass."
Expand Down Expand Up @@ -1409,6 +1413,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {

let superclass_object: Result<Object<'gc>, Error> = self
.subclass_object()
.and_then(|c| c.as_class_object())
.and_then(|bc| bc.superclass_object())
.ok_or_else(|| {
"Attempted to call super method without a superclass."
Expand Down Expand Up @@ -2554,7 +2559,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
});
let class = found?.coerce_to_object(self)?;

if class.as_class().is_none() {
if class.as_class_object().is_none() {
return Err("TypeError: The right-hand side of operator must be a class.".into());
}

Expand All @@ -2571,7 +2576,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let class = self.context.avm2.pop().coerce_to_object(self)?;
let value = self.context.avm2.pop().coerce_to_object(self)?;

if class.as_class().is_none() {
if class.as_class_object().is_none() {
return Err("TypeError: The right-hand side of operator must be a class.".into());
}

Expand Down
135 changes: 78 additions & 57 deletions core/src/avm2/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,17 +279,17 @@ impl<'gc> SystemClasses<'gc> {

/// Add a free-function builtin to the global scope.
fn function<'gc>(
mc: MutationContext<'gc, '_>,
activation: &mut Activation<'_, 'gc, '_>,
package: impl Into<AvmString<'gc>>,
name: &'static str,
nf: NativeMethodImpl,
fn_proto: Object<'gc>,
mut domain: Domain<'gc>,
script: Script<'gc>,
) -> Result<(), Error> {
let mc = activation.context.gc_context;
let qname = QName::new(Namespace::package(package), name);
let method = Method::from_builtin(nf, name, mc);
let as3fn = FunctionObject::from_method_and_proto(mc, method, None, fn_proto, None).into();
let as3fn = FunctionObject::from_method(activation, method, None, None).into();
domain.export_definition(qname.clone(), script, mc)?;
script
.init()
Expand All @@ -311,7 +311,7 @@ fn dynamic_class<'gc>(
script: Script<'gc>,
) -> Result<(), Error> {
let class = class_object
.as_class()
.as_class_definition()
.ok_or("Attempted to create builtin dynamic class without class on it's constructor!")?;
let name = class.read().name().clone();

Expand Down Expand Up @@ -422,23 +422,53 @@ pub fn load_player_globals<'gc>(

// public / root package
//
// We have to do this particular dance so that we have Object methods whose
// functions have call/apply in their prototypes, and that Function is also
// a subclass of Object.
let object_proto = object::create_proto(activation);
let fn_proto = function::create_proto(activation, object_proto);
// This part of global initialization is very complicated, because
// everything has to circularly reference everything else:
//
// - Object is an instance of itself, as well as it's prototype
// - All other types are instances of Class, which is an instance of
// itself
// - Function's prototype is an instance of itself
// - All methods created by the above-mentioned classes are also instances
// of Function
//
// Hence, this ridiculously complicated dance of classdef, type allocation,
// and partial initialization.
let object_scope = Some(Scope::push_scope(gs.get_scope(), gs, mc));
let object_classdef = object::create_class(mc);
let object_class =
ClassObject::from_class_partial(activation, object_classdef, None, object_scope)?;
let object_proto = ScriptObject::bare_object(mc);

let (mut object_class, object_cinit) =
object::fill_proto(activation, gs, object_proto, fn_proto)?;
let (mut function_class, function_cinit) =
function::fill_proto(activation, gs, fn_proto, object_class)?;
let fn_scope = Some(Scope::push_scope(gs.get_scope(), gs, mc));
let fn_classdef = function::create_class(mc);
let fn_class = ClassObject::from_class_partial(
activation,
fn_classdef,
Some(object_class.into()),
fn_scope,
)?;
let fn_proto = ScriptObject::object(mc, object_proto);

let (mut class_class, class_proto, class_cinit) =
class::create_class(activation, gs, object_class, object_proto, fn_proto)?;
let class_scope = Some(Scope::push_scope(gs.get_scope(), gs, mc));
let class_classdef = class::create_class(mc);
let class_class = ClassObject::from_class_partial(
activation,
class_classdef,
Some(object_class.into()),
class_scope,
)?;
let class_proto = ScriptObject::object(mc, object_proto);

dynamic_class(mc, object_class, domain, script)?;
dynamic_class(mc, function_class, domain, script)?;
dynamic_class(mc, class_class, domain, script)?;
// Now to weave the Gordian knot...
object_class.link_prototype(activation, object_proto)?;
object_class.link_type(activation, class_proto, class_class.into());

fn_class.link_prototype(activation, fn_proto)?;
fn_class.link_type(activation, class_proto, class_class.into());

class_class.link_prototype(activation, class_proto)?;
class_class.link_type(activation, class_proto, class_class.into());

// At this point, we need at least a partial set of system prototypes in
// order to continue initializing the player. The rest of the prototypes
Expand All @@ -451,32 +481,26 @@ pub fn load_player_globals<'gc>(
));

activation.context.avm2.system_classes = Some(SystemClasses::new(
object_class,
function_class,
class_class,
object_class.into(),
fn_class.into(),
class_class.into(),
ScriptObject::bare_object(mc),
));

// We can now run all of the steps that would ordinarily be run
// automatically had we not been so early in VM setup. This means things
// like installing class traits and running class initializers, which
// usually are done in the associated constructor for `ClassObject`.
object_class.install_traits(
activation,
object_class.as_class().unwrap().read().class_traits(),
)?;
function_class.install_traits(
activation,
function_class.as_class().unwrap().read().class_traits(),
)?;
class_class.install_traits(
activation,
class_class.as_class().unwrap().read().class_traits(),
)?;
// Our activation environment is now functional enough to finish
// initializing the core class weave. The order of initialization shouldn't
// matter here, as long as all the initialization machinery can see and
// link the various system types together correctly.
let object_class = object_class.into_finished_class(activation)?;
let fn_class = fn_class.into_finished_class(activation)?;
let class_class = class_class.into_finished_class(activation)?;

object_cinit.call(Some(object_class), &[], activation, Some(object_class))?;
function_cinit.call(Some(function_class), &[], activation, Some(function_class))?;
class_cinit.call(Some(class_class), &[], activation, Some(class_class))?;
dynamic_class(mc, object_class, domain, script)?;
dynamic_class(mc, fn_class, domain, script)?;
dynamic_class(mc, class_class, domain, script)?;

// After this point, it is safe to initialize any other classes.
// Make sure to initialize superclasses *before* their subclasses!

avm2_system_class!(
global,
Expand All @@ -485,6 +509,12 @@ pub fn load_player_globals<'gc>(
domain,
script
);

// Oh, one more small hitch: the domain everything gets put into was
// actually made *before* the core class weave, so let's fix that up now
// that the global class actually exists.
gs.set_proto(mc, activation.avm2().prototypes().global);

avm2_system_class!(string, activation, string::create_class(mc), domain, script);
avm2_system_class!(
boolean,
Expand All @@ -505,13 +535,9 @@ pub fn load_player_globals<'gc>(
);
avm2_system_class!(array, activation, array::create_class(mc), domain, script);

// At this point we have to hide the fact that we had to create the player
// globals scope *before* the `Object` class
gs.set_proto(mc, activation.avm2().prototypes().global);

function(mc, "", "trace", trace, fn_proto, domain, script)?;
function(mc, "", "isFinite", is_finite, fn_proto, domain, script)?;
function(mc, "", "isNaN", is_nan, fn_proto, domain, script)?;
function(activation, "", "trace", trace, domain, script)?;
function(activation, "", "isFinite", is_finite, domain, script)?;
function(activation, "", "isNaN", is_nan, domain, script)?;
constant(mc, "", "undefined", Value::Undefined, domain, script)?;
constant(mc, "", "null", Value::Null, domain, script)?;
constant(mc, "", "NaN", f64::NAN.into(), domain, script)?;
Expand Down Expand Up @@ -623,41 +649,37 @@ pub fn load_player_globals<'gc>(
)?;

function(
mc,
activation,
"flash.utils",
"getTimer",
flash::utils::get_timer,
fn_proto,
domain,
script,
)?;

function(
mc,
activation,
"flash.utils",
"getQualifiedClassName",
flash::utils::get_qualified_class_name,
fn_proto,
domain,
script,
)?;

function(
mc,
activation,
"flash.utils",
"getQualifiedSuperclassName",
flash::utils::get_qualified_super_class_name,
fn_proto,
domain,
script,
)?;

function(
mc,
activation,
"flash.utils",
"getDefinitionByName",
flash::utils::get_definition_by_name,
fn_proto,
domain,
script,
)?;
Expand Down Expand Up @@ -913,11 +935,10 @@ pub fn load_player_globals<'gc>(

// package `flash.crypto`
function(
mc,
activation,
"flash.crypto",
"generateRandomBytes",
flash::crypto::generate_random_bytes,
fn_proto,
domain,
script,
)?;
Expand Down
Loading