2020-02-15 01:30:19 +00:00
|
|
|
//! Property data structures
|
|
|
|
|
2020-02-19 19:17:33 +00:00
|
|
|
use crate::avm2::object::{Object, TObject};
|
2020-02-15 01:30:19 +00:00
|
|
|
use crate::avm2::return_value::ReturnValue;
|
|
|
|
use crate::avm2::value::Value;
|
2020-07-04 21:18:41 +00:00
|
|
|
use crate::avm2::Error;
|
2021-01-22 00:35:46 +00:00
|
|
|
use bitflags::bitflags;
|
2021-02-18 02:38:55 +00:00
|
|
|
use gc_arena::Collect;
|
2020-02-15 01:30:19 +00:00
|
|
|
|
2021-01-22 00:35:46 +00:00
|
|
|
bitflags! {
|
|
|
|
/// Attributes of properties in the AVM runtime.
|
|
|
|
///
|
|
|
|
/// TODO: Replace with AVM2 properties for traits
|
2021-02-18 02:38:55 +00:00
|
|
|
#[derive(Collect)]
|
|
|
|
#[collect(require_static)]
|
2021-01-22 00:35:46 +00:00
|
|
|
pub struct Attribute: u8 {
|
2021-06-08 22:21:00 +00:00
|
|
|
/// Property cannot be deleted in user code.
|
2021-01-22 00:35:46 +00:00
|
|
|
const DONT_DELETE = 1 << 0;
|
2021-06-08 22:21:00 +00:00
|
|
|
|
|
|
|
/// Property cannot be set.
|
2021-01-22 00:35:46 +00:00
|
|
|
const READ_ONLY = 1 << 1;
|
2021-06-08 22:21:00 +00:00
|
|
|
|
|
|
|
/// Property cannot be overridden in subclasses.
|
|
|
|
const FINAL = 1 << 2;
|
2021-01-22 00:35:46 +00:00
|
|
|
}
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::large_enum_variant)]
|
2021-02-18 02:38:55 +00:00
|
|
|
#[derive(Clone, Debug, Collect)]
|
|
|
|
#[collect(no_drop)]
|
2020-02-15 01:30:19 +00:00
|
|
|
pub enum Property<'gc> {
|
|
|
|
Virtual {
|
2020-12-11 03:21:36 +00:00
|
|
|
get: Option<Object<'gc>>,
|
|
|
|
set: Option<Object<'gc>>,
|
2021-01-22 00:35:46 +00:00
|
|
|
attributes: Attribute,
|
2020-02-15 01:30:19 +00:00
|
|
|
},
|
|
|
|
Stored {
|
|
|
|
value: Value<'gc>,
|
2021-01-22 00:35:46 +00:00
|
|
|
attributes: Attribute,
|
2020-02-15 01:30:19 +00:00
|
|
|
},
|
2020-02-19 19:17:33 +00:00
|
|
|
Slot {
|
|
|
|
slot_id: u32,
|
2021-01-22 00:35:46 +00:00
|
|
|
attributes: Attribute,
|
2020-02-19 19:17:33 +00:00
|
|
|
},
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Property<'gc> {
|
2020-02-21 02:02:49 +00:00
|
|
|
/// Create a new stored property.
|
2021-06-08 22:21:00 +00:00
|
|
|
pub fn new_stored(value: impl Into<Value<'gc>>, is_final: bool) -> Self {
|
|
|
|
let mut attributes = Attribute::DONT_DELETE;
|
|
|
|
|
|
|
|
if is_final {
|
|
|
|
attributes |= Attribute::FINAL;
|
|
|
|
}
|
|
|
|
|
2020-02-21 02:02:49 +00:00
|
|
|
Property::Stored {
|
|
|
|
value: value.into(),
|
2021-06-08 22:21:00 +00:00
|
|
|
attributes,
|
2020-02-21 02:02:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-21 04:20:46 +00:00
|
|
|
/// Create a new stored property.
|
2021-06-08 22:21:00 +00:00
|
|
|
pub fn new_const(value: impl Into<Value<'gc>>, is_final: bool) -> Self {
|
|
|
|
let mut attributes = Attribute::DONT_DELETE | Attribute::READ_ONLY;
|
|
|
|
|
|
|
|
if is_final {
|
|
|
|
attributes |= Attribute::FINAL;
|
|
|
|
}
|
|
|
|
|
2020-02-21 04:20:46 +00:00
|
|
|
Property::Stored {
|
|
|
|
value: value.into(),
|
2021-06-08 22:21:00 +00:00
|
|
|
attributes,
|
2020-02-21 04:20:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-15 01:30:19 +00:00
|
|
|
/// Convert a value into a dynamic property.
|
|
|
|
pub fn new_dynamic_property(value: impl Into<Value<'gc>>) -> Self {
|
|
|
|
Property::Stored {
|
|
|
|
value: value.into(),
|
2021-01-22 00:35:46 +00:00
|
|
|
attributes: Attribute::empty(),
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-18 00:43:23 +00:00
|
|
|
/// Convert a function into a method.
|
|
|
|
///
|
2021-01-22 00:35:46 +00:00
|
|
|
/// This applies READ_ONLY/DONT_DELETE to the property.
|
2021-06-08 22:21:00 +00:00
|
|
|
pub fn new_method(fn_obj: Object<'gc>, is_final: bool) -> Self {
|
|
|
|
let mut attributes = Attribute::DONT_DELETE | Attribute::READ_ONLY;
|
|
|
|
|
|
|
|
if is_final {
|
|
|
|
attributes |= Attribute::FINAL;
|
|
|
|
}
|
|
|
|
|
2020-02-18 00:43:23 +00:00
|
|
|
Property::Stored {
|
|
|
|
value: fn_obj.into(),
|
2021-06-08 22:21:00 +00:00
|
|
|
attributes,
|
2020-02-18 00:43:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a new, unconfigured virtual property item.
|
2021-06-08 22:21:00 +00:00
|
|
|
pub fn new_virtual(is_final: bool) -> Self {
|
|
|
|
let mut attributes = Attribute::DONT_DELETE | Attribute::READ_ONLY;
|
|
|
|
|
|
|
|
if is_final {
|
|
|
|
attributes |= Attribute::FINAL;
|
|
|
|
}
|
|
|
|
|
2020-02-18 00:43:23 +00:00
|
|
|
Property::Virtual {
|
|
|
|
get: None,
|
|
|
|
set: None,
|
2021-06-08 22:21:00 +00:00
|
|
|
attributes,
|
2020-02-18 00:43:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-21 02:02:49 +00:00
|
|
|
/// Create a new slot property.
|
2021-06-08 22:21:00 +00:00
|
|
|
pub fn new_slot(slot_id: u32, is_final: bool) -> Self {
|
|
|
|
let mut attributes = Attribute::DONT_DELETE;
|
|
|
|
|
|
|
|
if is_final {
|
|
|
|
attributes |= Attribute::FINAL;
|
|
|
|
}
|
|
|
|
|
2020-02-21 02:02:49 +00:00
|
|
|
Property::Slot {
|
|
|
|
slot_id,
|
2021-01-22 00:35:46 +00:00
|
|
|
attributes: Attribute::DONT_DELETE,
|
2020-02-21 02:02:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-18 00:43:23 +00:00
|
|
|
/// Install a getter into this property.
|
|
|
|
///
|
|
|
|
/// This function errors if attempting to install executables into a
|
|
|
|
/// non-virtual property.
|
2020-12-11 03:21:36 +00:00
|
|
|
///
|
|
|
|
/// The implementation must be a valid function, otherwise the VM will
|
|
|
|
/// panic when the property is accessed.
|
|
|
|
pub fn install_virtual_getter(&mut self, getter_impl: Object<'gc>) -> Result<(), Error> {
|
2020-02-18 00:43:23 +00:00
|
|
|
match self {
|
|
|
|
Property::Virtual { get, .. } => *get = Some(getter_impl),
|
|
|
|
Property::Stored { .. } => return Err("Not a virtual property".into()),
|
2020-02-19 19:17:33 +00:00
|
|
|
Property::Slot { .. } => return Err("Not a virtual property".into()),
|
2020-02-18 00:43:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Install a setter into this property.
|
|
|
|
///
|
|
|
|
/// This function errors if attempting to install executables into a
|
|
|
|
/// non-virtual property.
|
2020-12-11 03:21:36 +00:00
|
|
|
///
|
|
|
|
/// The implementation must be a valid function, otherwise the VM will
|
|
|
|
/// panic when the property is accessed.
|
|
|
|
pub fn install_virtual_setter(&mut self, setter_impl: Object<'gc>) -> Result<(), Error> {
|
2020-02-18 00:43:23 +00:00
|
|
|
match self {
|
|
|
|
Property::Virtual { set, .. } => *set = Some(setter_impl),
|
|
|
|
Property::Stored { .. } => return Err("Not a virtual property".into()),
|
2020-02-19 19:17:33 +00:00
|
|
|
Property::Slot { .. } => return Err("Not a virtual property".into()),
|
2020-02-18 00:43:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-02-15 01:30:19 +00:00
|
|
|
/// Get the value of a property slot.
|
|
|
|
///
|
|
|
|
/// This function yields `ReturnValue` because some properties may be
|
|
|
|
/// user-defined.
|
|
|
|
pub fn get(
|
|
|
|
&self,
|
|
|
|
this: Object<'gc>,
|
2021-06-19 23:07:55 +00:00
|
|
|
subclass_object: Option<Object<'gc>>,
|
2020-02-15 01:30:19 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
|
|
|
match self {
|
2020-07-04 21:18:41 +00:00
|
|
|
Property::Virtual { get: Some(get), .. } => Ok(ReturnValue::defer_execution(
|
2020-07-04 21:56:27 +00:00
|
|
|
*get,
|
2020-07-04 21:18:41 +00:00
|
|
|
Some(this),
|
|
|
|
vec![],
|
2021-06-19 23:07:55 +00:00
|
|
|
subclass_object,
|
2020-07-04 21:18:41 +00:00
|
|
|
)),
|
2020-02-18 00:43:23 +00:00
|
|
|
Property::Virtual { get: None, .. } => Ok(Value::Undefined.into()),
|
2020-02-15 01:30:19 +00:00
|
|
|
Property::Stored { value, .. } => Ok(value.to_owned().into()),
|
2021-04-07 22:53:34 +00:00
|
|
|
|
|
|
|
// This doesn't need the non-local version of this property because
|
|
|
|
// by the time this has called the slot was already installed
|
2021-06-08 00:56:57 +00:00
|
|
|
Property::Slot { slot_id, .. } => this.get_slot(*slot_id).map(|v| v.into()),
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set a property slot.
|
|
|
|
///
|
2020-02-29 01:55:09 +00:00
|
|
|
/// This function returns a `ReturnValue` which should be resolved. The
|
|
|
|
/// resulting `Value` is unimportant and should be discarded.
|
2020-02-23 02:28:02 +00:00
|
|
|
///
|
|
|
|
/// This function cannot set slot properties and will panic if one
|
|
|
|
/// is encountered.
|
2020-02-15 01:30:19 +00:00
|
|
|
pub fn set(
|
|
|
|
&mut self,
|
|
|
|
this: Object<'gc>,
|
2021-06-19 23:07:55 +00:00
|
|
|
subclass_object: Option<Object<'gc>>,
|
2020-02-15 01:30:19 +00:00
|
|
|
new_value: impl Into<Value<'gc>>,
|
2020-02-29 01:55:09 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
2020-02-15 01:30:19 +00:00
|
|
|
match self {
|
|
|
|
Property::Virtual { set, .. } => {
|
|
|
|
if let Some(function) = set {
|
2020-07-04 21:18:41 +00:00
|
|
|
return Ok(ReturnValue::defer_execution(
|
2020-07-04 21:56:27 +00:00
|
|
|
*function,
|
Refactor the base_proto system to more accurately record what prototype methods come from.
The previous system primarily relied on `Executable` to automatically start and continue a super chain. This works, but only for class hierarchies without *override gaps* - methods that override another method not defined by the direct superclass of the method. In that case, the override method would be called twice as the `base_class` was moved up one prototype at a time, which is wrong.
The new system relies on the call site to accurately report the prototype from which the current method was retrieved from. Super calls then start the resolution process *from the superclass of this prototype*, to ensure that the already-called method is skipped.
It should be noted that the proper `base_class` for things like `callmethod`, `callstatic`, `call`, `get`/`set` methods, and other call opcodes that don't use property look-up are best-effort guesses that may need to be amended later with better tests.
To facilitate `base_proto` resolution, a new `Object` method has been added. It's similar to `get_property`, but instead returns the closest prototype that can resolve the given `QName`, rather than the actual property's `ReturnValue`. Call operations use this to resolve the `base_proto`, and then resolve the method being called in `base_proto`. The existing `exec_super` method was removed and a `base_proto` method added to `exec` and `call`.
2020-02-28 02:58:59 +00:00
|
|
|
Some(this),
|
2020-07-04 21:18:41 +00:00
|
|
|
vec![new_value.into()],
|
2021-06-19 23:07:55 +00:00
|
|
|
subclass_object,
|
2020-07-04 21:18:41 +00:00
|
|
|
));
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
2020-02-29 01:55:09 +00:00
|
|
|
|
|
|
|
Ok(Value::Undefined.into())
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
Property::Stored {
|
|
|
|
value, attributes, ..
|
|
|
|
} => {
|
2021-01-22 00:35:46 +00:00
|
|
|
if !attributes.contains(Attribute::READ_ONLY) {
|
2020-02-22 21:21:28 +00:00
|
|
|
*value = new_value.into();
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
|
2020-02-29 01:55:09 +00:00
|
|
|
Ok(Value::Undefined.into())
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
2020-02-23 03:13:15 +00:00
|
|
|
Property::Slot { .. } => panic!("Cannot recursively set slots"),
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-22 21:21:28 +00:00
|
|
|
/// Init a property slot.
|
|
|
|
///
|
|
|
|
/// The difference between `set` and `init` is that this function does not
|
|
|
|
/// respect `ReadOnly` and will allow initializing nominally `const`
|
|
|
|
/// properties, at least once. Virtual properties with no setter cannot be
|
|
|
|
/// initialized.
|
|
|
|
///
|
2020-02-29 01:55:09 +00:00
|
|
|
/// This function returns a `ReturnValue` which should be resolved. The
|
|
|
|
/// resulting `Value` is unimportant and should be discarded.
|
2020-02-23 02:28:02 +00:00
|
|
|
///
|
|
|
|
/// This function cannot initialize slot properties and will panic if one
|
|
|
|
/// is encountered.
|
2020-02-22 21:21:28 +00:00
|
|
|
pub fn init(
|
|
|
|
&mut self,
|
|
|
|
this: Object<'gc>,
|
2021-06-19 23:07:55 +00:00
|
|
|
subclass_object: Option<Object<'gc>>,
|
2020-02-22 21:21:28 +00:00
|
|
|
new_value: impl Into<Value<'gc>>,
|
2020-02-29 01:55:09 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
2020-02-22 21:21:28 +00:00
|
|
|
match self {
|
|
|
|
Property::Virtual { set, .. } => {
|
|
|
|
if let Some(function) = set {
|
2020-07-04 21:18:41 +00:00
|
|
|
return Ok(ReturnValue::defer_execution(
|
2020-07-04 21:56:27 +00:00
|
|
|
*function,
|
Refactor the base_proto system to more accurately record what prototype methods come from.
The previous system primarily relied on `Executable` to automatically start and continue a super chain. This works, but only for class hierarchies without *override gaps* - methods that override another method not defined by the direct superclass of the method. In that case, the override method would be called twice as the `base_class` was moved up one prototype at a time, which is wrong.
The new system relies on the call site to accurately report the prototype from which the current method was retrieved from. Super calls then start the resolution process *from the superclass of this prototype*, to ensure that the already-called method is skipped.
It should be noted that the proper `base_class` for things like `callmethod`, `callstatic`, `call`, `get`/`set` methods, and other call opcodes that don't use property look-up are best-effort guesses that may need to be amended later with better tests.
To facilitate `base_proto` resolution, a new `Object` method has been added. It's similar to `get_property`, but instead returns the closest prototype that can resolve the given `QName`, rather than the actual property's `ReturnValue`. Call operations use this to resolve the `base_proto`, and then resolve the method being called in `base_proto`. The existing `exec_super` method was removed and a `base_proto` method added to `exec` and `call`.
2020-02-28 02:58:59 +00:00
|
|
|
Some(this),
|
2020-07-04 21:18:41 +00:00
|
|
|
vec![new_value.into()],
|
2021-06-19 23:07:55 +00:00
|
|
|
subclass_object,
|
2020-07-04 21:18:41 +00:00
|
|
|
));
|
2020-02-22 21:21:28 +00:00
|
|
|
}
|
2020-02-29 01:55:09 +00:00
|
|
|
|
|
|
|
Ok(Value::Undefined.into())
|
2020-02-22 21:21:28 +00:00
|
|
|
}
|
|
|
|
Property::Stored { value, .. } => {
|
|
|
|
*value = new_value.into();
|
|
|
|
|
2020-02-29 01:55:09 +00:00
|
|
|
Ok(Value::Undefined.into())
|
2020-02-22 21:21:28 +00:00
|
|
|
}
|
2020-02-23 03:13:15 +00:00
|
|
|
Property::Slot { .. } => panic!("Cannot recursively init slots"),
|
2020-02-23 02:28:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Retrieve the slot ID of a property.
|
|
|
|
///
|
|
|
|
/// This function yields `None` if this property is not a slot.
|
|
|
|
pub fn slot_id(&self) -> Option<u32> {
|
|
|
|
match self {
|
|
|
|
Property::Slot { slot_id, .. } => Some(*slot_id),
|
|
|
|
_ => None,
|
2020-02-22 21:21:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-15 01:30:19 +00:00
|
|
|
pub fn can_delete(&self) -> bool {
|
2021-01-22 00:35:46 +00:00
|
|
|
let attributes = match self {
|
|
|
|
Property::Virtual { attributes, .. } => attributes,
|
|
|
|
Property::Stored { attributes, .. } => attributes,
|
|
|
|
Property::Slot { attributes, .. } => attributes,
|
|
|
|
};
|
|
|
|
!attributes.contains(Attribute::DONT_DELETE)
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_overwritable(&self) -> bool {
|
2021-01-22 00:35:46 +00:00
|
|
|
let attributes = match self {
|
2020-02-15 01:30:19 +00:00
|
|
|
Property::Virtual {
|
|
|
|
attributes, set, ..
|
2021-01-22 00:35:46 +00:00
|
|
|
} => {
|
|
|
|
if set.is_none() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
attributes
|
|
|
|
}
|
|
|
|
Property::Stored { attributes, .. } => attributes,
|
|
|
|
Property::Slot { attributes, .. } => attributes,
|
|
|
|
};
|
|
|
|
!attributes.contains(Attribute::READ_ONLY)
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
2021-06-08 22:21:00 +00:00
|
|
|
|
|
|
|
pub fn is_final(&self) -> bool {
|
|
|
|
let attributes = match self {
|
|
|
|
Property::Virtual { attributes, .. } => attributes,
|
|
|
|
Property::Stored { attributes, .. } => attributes,
|
|
|
|
Property::Slot { attributes, .. } => attributes,
|
|
|
|
};
|
|
|
|
attributes.contains(Attribute::FINAL)
|
|
|
|
}
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|