ruffle/core/src/avm2/scope.rs

179 lines
5.3 KiB
Rust
Raw Normal View History

2020-02-10 19:54:55 +00:00
//! Represents AVM2 scope chain resolution.
use crate::avm2::activation::Activation;
use crate::avm2::names::Multiname;
2020-02-10 19:54:55 +00:00
use crate::avm2::object::{Object, TObject};
use crate::avm2::value::Value;
use crate::avm2::Error;
2020-02-10 19:54:55 +00:00
use gc_arena::{Collect, GcCell, MutationContext};
use std::cell::Ref;
/// Indicates what kind of scope a scope is.
#[derive(Copy, Clone, Debug, PartialEq, Collect)]
#[collect(no_drop)]
pub enum ScopeClass {
/// Scope represents global or closure scope.
GlobalOrClosure,
/// Scope represents an object added to the scope chain with `with`.
/// It is not inherited when closures are defined. Furthermore, a `with`
/// scope gains the ability to be searched for dynamic properties.
With,
}
/// Represents a scope chain for an AVM2 activation.
#[derive(Debug, Collect)]
#[collect(no_drop)]
pub struct Scope<'gc> {
parent: Option<GcCell<'gc, Scope<'gc>>>,
class: ScopeClass,
values: Object<'gc>,
}
impl<'gc> Scope<'gc> {
/// Push a scope onto the stack, producing a new scope chain that's one
/// item longer.
pub fn push_scope(
scope_stack: Option<GcCell<'gc, Scope<'gc>>>,
object: Object<'gc>,
mc: MutationContext<'gc, '_>,
) -> GcCell<'gc, Self> {
GcCell::allocate(
mc,
Self {
parent: scope_stack,
class: ScopeClass::GlobalOrClosure,
values: object,
},
)
}
/// Construct a with scope to be used as the scope during a with block.
///
/// A with block adds an object to the top of the scope chain, so unqualified
/// references will try to resolve on that object first.
pub fn push_with(
scope_stack: Option<GcCell<'gc, Scope<'gc>>>,
with_object: Object<'gc>,
mc: MutationContext<'gc, '_>,
) -> GcCell<'gc, Self> {
GcCell::allocate(
mc,
Scope {
parent: scope_stack,
class: ScopeClass::With,
values: with_object,
},
)
}
pub fn pop_scope(&self) -> Option<GcCell<'gc, Scope<'gc>>> {
self.parent
}
2020-02-10 19:54:55 +00:00
/// Returns a reference to the current local scope object.
pub fn locals(&self) -> &Object<'gc> {
&self.values
}
/// Returns a reference to the current global scope object.
///
/// By convention, the global scope is at the bottom of the scope stack.
pub fn globals(&self) -> Object<'gc> {
if let Some(parent) = self.parent {
parent.read().globals()
} else {
self.values
}
}
2020-02-10 19:54:55 +00:00
/// Returns a reference to the current local scope object for mutation.
pub fn locals_mut(&mut self) -> &mut Object<'gc> {
&mut self.values
}
/// Returns a reference to the parent scope object.
pub fn parent(&self) -> Option<Ref<Scope<'gc>>> {
match self.parent {
Some(ref p) => Some(p.read()),
None => None,
}
}
2020-02-22 04:31:30 +00:00
pub fn parent_cell(&self) -> Option<GcCell<'gc, Scope<'gc>>> {
self.parent
}
/// Find an object that contains a given property in the scope stack.
///
/// This function yields `None` if no such scope exists.
pub fn find(
&self,
name: &Multiname<'gc>,
2020-07-28 03:19:43 +00:00
activation: &mut Activation<'_, 'gc, '_>,
Completely overhaul the way traits are defined on objects. Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal. This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property. However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon. The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests. We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly. `super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
) -> Result<Option<Object<'gc>>, Error> {
if let Some(qname) = self.locals().resolve_multiname(name)? {
if self.locals().has_property(&qname)? {
return Ok(Some(*self.locals()));
}
}
if let Some(scope) = self.parent() {
2020-07-28 03:19:43 +00:00
return scope.find(name, activation);
}
if let Some(domain) = self.locals().as_application_domain() {
let script = domain.get_defining_script(name)?;
if let Some((_qname, mut script)) = script {
return Ok(Some(script.globals(&mut activation.context)?));
}
}
Completely overhaul the way traits are defined on objects. Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal. This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property. However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon. The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests. We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly. `super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
Ok(None)
}
2020-02-10 19:54:55 +00:00
/// Resolve a particular value in the scope chain.
///
/// This function yields `None` if no such scope exists to provide the
/// property's value.
2020-02-10 19:54:55 +00:00
pub fn resolve(
Completely overhaul the way traits are defined on objects. Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal. This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property. However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon. The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests. We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly. `super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
&mut self,
name: &Multiname<'gc>,
2020-07-28 03:19:43 +00:00
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Option<Value<'gc>>, Error> {
Completely overhaul the way traits are defined on objects. Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal. This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property. However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon. The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests. We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly. `super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
if let Some(qname) = self.locals().resolve_multiname(name)? {
if self.locals().has_property(&qname)? {
return Ok(Some(self.values.get_property(
self.values,
&qname,
activation,
)?));
}
2020-02-10 19:54:55 +00:00
}
Completely overhaul the way traits are defined on objects. Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal. This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property. However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon. The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests. We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly. `super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
if let Some(parent) = self.parent {
return parent
2020-07-28 03:19:43 +00:00
.write(activation.context.gc_context)
.resolve(name, activation);
2020-02-10 19:54:55 +00:00
}
if let Some(domain) = self.locals().as_application_domain() {
let script = domain.get_defining_script(name)?;
if let Some((qname, mut script)) = script {
let mut script_scope = script.globals(&mut activation.context)?;
return Ok(Some(script_scope.get_property(
script_scope,
&qname,
activation,
)?));
}
}
2020-02-10 19:54:55 +00:00
//TODO: Should undefined variables halt execution?
Completely overhaul the way traits are defined on objects. Previously, we were treating ES4 classes like syntactic sugar over a prototype chain (like ES6 classes); e.g. each declared trait was set in the given prototype and then property look-ups happened as normal. This already caused problems with virtual properties, which could be partially-defined in subclasses and required careful checks to make sure we stopped checking the prototype chain on the *correct* half of the property. However, this is a hint of a larger problem, which is that ES4 classes don't actually define anything on the prototype chain. Instead, the instance itself constructs class properties and methods on itself. This allows things like methods automatically binding `this`, which isn't included in this commit but will be implemented really soon. The prototype chain still exists even on pure ES4 classes, due to the need for backwards compatibility with ES3 code. Object, for example, still defines it's methods as prototype methods and thus there needs to be a prototype chain to reach them. I actually could have gotten away with using the prototype chain if AS3 *hadn't* retained this "legacy" detail of ES3 allowing this class/prototype distinction to leak out into upcoming tests. We still actually use the prototype chain for one other thing: trait resolution. When we look for a trait to install onto an object, we pull traits from the prototype chain using a special set of `TObject` methods. This happens in opposite order from normal prototype lookups so that subclassing and verification can proceed correctly. `super` somehow became even harder to implement: we now actually construct the parent class so we can get traits from it, which is going to complicate method binding as mentioned above.
2020-03-04 00:39:49 +00:00
Ok(None)
2020-02-10 19:54:55 +00:00
}
}