//! Property data structures use crate::avm2::object::TObject; use crate::avm2::Activation; use crate::avm2::ClassObject; use crate::avm2::Error; use crate::avm2::Multiname; use crate::avm2::TranslationUnit; use crate::avm2::Value; use gc_arena::MutationContext; use gc_arena::{Collect, Gc}; #[derive(Debug, Collect, Clone, Copy)] #[collect(no_drop)] pub enum Property { Virtual { get: Option, set: Option }, Method { disp_id: u32 }, Slot { slot_id: u32 }, ConstSlot { slot_id: u32 }, } /// The type of a `Slot`/`ConstSlot` property, represented /// as a lazily-resolved class. This also implements the /// property-specific coercion logic applied when setting /// or initializing a property. /// /// The class resolution needs to be lazy, since we can have /// a cycle of property type references between classes /// (e.g. Class1 has `var prop1:Class2`, and Class2 /// has `var prop2:Class1`). /// /// Additionally, property class resolution uses special /// logic, different from normal "runtime" class resolution, /// that allows private types to be referenced. #[derive(Collect, Clone)] #[collect(no_drop)] pub enum PropertyClass<'gc> { /// The type `*` (Multiname::is_any()). This allows /// `Value::Undefined`, so it needs to be distinguished /// from the `Object` class Any, Class(ClassObject<'gc>), Name(Gc<'gc, (Multiname<'gc>, Option>)>), } impl<'gc> PropertyClass<'gc> { pub fn name( mc: MutationContext<'gc, '_>, name: Multiname<'gc>, unit: Option>, ) -> Self { PropertyClass::Name(Gc::allocate(mc, (name, unit))) } /// Returns `value` coerced to the type of this `PropertyClass`. /// The bool is `true` if this `PropertyClass` was just modified /// to cache a class resolution, and `false` if it was not modified. pub fn coerce( &mut self, activation: &mut Activation<'_, 'gc>, value: Value<'gc>, ) -> Result<(Value<'gc>, bool), Error<'gc>> { let (class, changed) = match self { PropertyClass::Class(class) => (Some(*class), false), PropertyClass::Name(gc) => { let (name, unit) = &**gc; let outcome = resolve_class_private(name, *unit, activation)?; let class = match outcome { ResolveOutcome::Class(class) => { *self = PropertyClass::Class(class); (Some(class), true) } ResolveOutcome::Any => { *self = PropertyClass::Any; (None, true) } ResolveOutcome::NotFound => { // FP allows a class to reference its own type in a static initializer: // `class Foo { static var INSTANCE: Foo = new Foo(); }` // When this happens, the `ClassObject` for `Foo` will not yet // be available when we perform the coercion, since we're still // in the process of constructing it. // // Fortunately, a coercion to a non-primitive class either // succeeds with the value unchanged, or fails (if the object // is not an instance of the class). Therefore, we can just check // if the class name and domain match our property name, without // actually needing to perform resolution. This does not handle subclasses, // but that's fine - a superclass cannot reference a subclass from a class // initializer. // // We should eventually be able to remove this when we refactor // `Class`/`ClassObject` to be closer to what avmplus does. if let Some(object) = value.as_object() { if let Some(class) = object.instance_of() { if name.contains_name(&class.inner_class_definition().read().name()) && unit.map(|u| u.domain()) == Some(class.class_scope().domain()) { // Even though resolution succeeded, we haven't modified // this `PropertyClass`, so return `false` return Ok((value, false)); } } } else if matches!(value, Value::Null) || matches!(value, Value::Undefined) { //AVM2 properties are nullable, so null is always an instance of the class return Ok((Value::Null, false)); } return Err(Error::from(format!( "Attempted to perform (private) resolution of nonexistent type {name:?}", ))); } }; class } PropertyClass::Any => (None, false), }; if let Some(class) = class { Ok((value.coerce_to_type(activation, class)?, changed)) } else { // We have a type of '*' ("any"), so don't // perform any coercions Ok((value, changed)) } } pub fn get_name(&self, mc: MutationContext<'gc, '_>) -> Multiname<'gc> { match self { PropertyClass::Class(class) => class.inner_class_definition().read().name().into(), PropertyClass::Name(gc) => gc.0.clone(), PropertyClass::Any => Multiname::any(mc), } } } enum ResolveOutcome<'gc> { Class(ClassObject<'gc>), Any, NotFound, } /// Resolves a class definition referenced by the type of a property. /// This supports private (`Namespace::Private`) classes, /// and does not use the `ScopeStack`/`ScopeChain`. /// /// This is an internal operation used to resolve property type names. /// It does not correspond to any opcode or native method. fn resolve_class_private<'gc>( name: &Multiname<'gc>, unit: Option>, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { // A Property may have a type of '*' (which corresponds to 'Multiname::any()') // We don't want to perform any coercions in this case - in particular, // this means that the property can have a value of `Undefined`. // If the type is `Object`, then a value of `Undefind` gets coerced // to `Null` if name.is_any_name() { Ok(ResolveOutcome::Any) } else { // First, check the domain for an exported (non-private) class. // If the property we're resolving for lacks a `TranslationUnit`, // use the stage domain let domain = unit.map_or(activation.avm2().stage_domain, |u| u.domain()); let globals = if let Some((_, mut script)) = domain.get_defining_script(name)? { script.globals(&mut activation.context)? } else if unit.is_some() { return Err(format!("Could not find script for class trait {name:?}").into()); } else { return Err(format!("Missing script and translation unit for class {name:?}").into()); }; Ok(globals .get_property(name, activation)? .as_object() .and_then(|o| o.as_class_object()) .map_or(ResolveOutcome::NotFound, ResolveOutcome::Class)) } } impl Property { pub fn new_method(disp_id: u32) -> Self { Property::Method { disp_id } } pub fn new_getter(disp_id: u32) -> Self { Property::Virtual { get: Some(disp_id), set: None, } } pub fn new_setter(disp_id: u32) -> Self { Property::Virtual { get: None, set: Some(disp_id), } } pub fn new_slot(slot_id: u32) -> Self { Property::Slot { slot_id } } pub fn new_const_slot(slot_id: u32) -> Self { Property::ConstSlot { slot_id } } }