Commit Graph

41 Commits

Author SHA1 Message Date
David Wendt 8f2d3315f3 Allow the construction of classes with no base class.
This is primarily used for interfaces, in case you can't guess by the previous commit.
2020-07-13 17:45:07 -04:00
David Wendt 97e005622b Invert the role of `Avm2` and it's `Activation`, similar to what was done with `Avm1` and it's `Activation`.
This also results in a far reduced role for `ReturnValue`, since I also took the liberty of removing most of it's use. Furthermore, I also made it apply equally to native and AVM2 code, which ensures all native implementations of methods don't double-borrow.

In AVM1, `ReturnValue` was actually removed entirely, because it's not needed. I attempted to do the same, but the fact that we're currently embedding `ScriptObjectData` in native objects means that we need it for virtual properties. Otherwise, virtual property implementations will see locked objects, which is bad.
2020-07-13 17:45:06 -04:00
David Wendt b4f944b37b Wrap ABC loading inside of a `TranslationUnit`. 2020-07-13 17:45:01 -04:00
David Wendt 70e9030072 Decouple the entire trait machinery from ABC-provided traits.
This commit breaks the build: we still need to tell `Avm2` how to turn ABC traits into our own internal `Trait<'gc>`, `Class<'gc>`, and `Method<'gc>` types. We also need something to track which traits have already been instantiated, because `callstatic` would otherwise reinstantiate the trait in a different scope. (In fact, I think it *does* do exactly that right now...)
2020-07-13 17:45:01 -04:00
David Wendt 15a62d31cb Add an internal representation of `Trait`, separate from `swf::avm2::types::Trait`, which is specific to the ABC file format.
The intention is to completely replace all usage of `Avm2XYZEntry` with `Class`, `Trait`, and `Method`. This will allow runtime-provided global class traits to coexist with those provided by user code.
2020-07-13 17:45:01 -04:00
David Wendt 4cd30455de Excise `ReturnValue<'gc>` from all `TObject` methods.
Inspired by Dinnerbone's PR doing the exact same thing to AVM1.

On AVM2 we have a bit of a subtle issue: the base implementation of `set_property_local` and `init_property_local` *must* return `ReturnValue`s to avoid double-borrows. Each implementation of `TObject` must resolve them before returning.
2020-07-13 17:45:00 -04:00
David Wendt f493cf954f Make `toString` and `valueOf` methods of `TObject`, called `to_string` and `value_of` respectively.
The reason for this is that, in AVM2, `toString` and `valueOf` are not defined on the classes or prototypes of `Function` or `Class`. Instead, they use the `Object.prototype` versions of those functions. Ergo, string and primitive coercion are inherent object methods (the ones that get `[[DoubleSquareBrackets]]` in the ECMA standards). In Ruffle, our equivalent to `[[DoubleSquareBrackets]]` methods are methods on the `TObject` trait, so we're adding them there.

This mechanism will make implementing boxed value types (ala AVM1's `BoxedObject`) easier, too.

We also add some reasonable defaults for `ScriptObject` and `FunctionObject` which will appear on objects, functions, and classes.
2020-07-13 17:44:53 -04:00
David Wendt f13e2ea3c4 Implement `setPropertyIsEnumerable` 2020-07-13 17:44:37 -04:00
David Wendt 2afbcf450a Impl `propertyIsEnumerable` 2020-07-13 17:44:36 -04:00
David Wendt a0ca5891e4 Prevent instance traits from being accessible directly from prototypes. 2020-07-13 17:44:36 -04:00
David Wendt 6e2508a79d Fix `any` name resolution, at least enough for the `has_own_property` test to work.
Private names now return `false`, and we run any names through trait lookups. This also means any namespace resolution can fail now, in case we need to throw a `VerifyError`.
2020-07-13 17:44:34 -04:00
David Wendt c014b40109 Implement `hasnext`, `hasnext2`, `nextname`, `nextvalue`, and the underlying enumeration machinery that powers it.
I have... significant reservations with the way object enumeration happens in AVM2. For comparison, AVM1 enumeration works like this: You enumerate the entire object at once, producing a list of property names, which are then pushed onto the stack after a sentinel value. This is a properly abstract way to handle property enumeration.

In AVM2, they completely replaced this with index-based enumeration. What this means is that you hand the object an index and it gives you back a name or value. There's also an instruction that will give you the next index in the object.

The only advantage I can think of is that it results in less stack manipulation if you want to bail out of iteration early. You just jump out of your loop and kill the registers you don't care about. The disadvantage is that it locks the object representation down pretty hard. They also screwed up the definition of `hasnext`, and thus the VM is stuck enumerating properties from 1. This is because `hasnext` and `hasnext2` increment the index value before checking the object. Code generated by Animate 2020 (which I suspect to be the final version of that software that generates AVM2 code) initializes the index at hero, and then does `hasnext2`, hence we have to start from one.

I actually cheated a little and added a separate `Vec` for storing enumerant names. I strongly suspect that Adobe's implementation has objects be inherently slot-oriented, and named properties are just hashmap lookups to slots. This would allow enumerating the slots to get names out of the object.
2020-07-13 17:44:32 -04:00
David Wendt b33c246713 Implement `is_property_overwritable`. 2020-07-13 17:44:29 -04:00
David Wendt 2f95a7a81b 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-07-13 17:44:27 -04:00
David Wendt 353017576a `ScriptObject` now holds a reference to a class and allows retrieving traits from it. 2020-07-13 17:44:27 -04:00
David Wendt f10920adc0 Implement `Object.prototype.hasOwnProperty` and resolution of `Namespace::Any`. 2020-07-13 17:44:26 -04:00
David Wendt c5e3af2053 When resolving `get_property`, skip over virtual properties that do not have a defined getter. 2020-07-13 17:44:23 -04:00
David Wendt 54b792ef3a Ensure that called setters are properly resolved so that errors in setters propagate up the Rust stack correctly.
The previous system for handling setters would execute the setter and then return a value to indicate whether or not the caller needed to resolve a stack frame. However, no caller of `Property.set` actually did this. Ergo, errors in setters and getters would not resolve up the stack at the correct time.

This problem also exists in AVM1 but is far less noticable as AVM1 only has two very uncommon runtime errors and very few movies use `throw`.
2020-07-13 17:44:23 -04:00
David Wendt b8106d24d2 Ensure virtual setters are run when defined on a prototype.
Normally, `set_property` only affects the object it was called on, which makes sense: otherwise, we couldn't override values that originate from a class prototype without accidentally monkey-patching the prototype. However, virtual setters only exist in prototypes and need to be accessible from child objects.

The solution to this is to have a specific method to check if a virtual setter exists. Virtual setters are then resolved through the prototype chain. If no virtual setter exists, then the reciever object is handed the value.

Note that we always use the `reciever` object rather than `self` so that `setsuper` can work correctly. In `setsuper`, we resolve the base class, and then set properties on it with the actual object in question as it's reciever. If a virtual setter is called, it will get the actual object it should be manipulating; and otherwise, prototypes will not be modified or consulted.
2020-07-13 17:44:22 -04:00
David Wendt 665d7a4342 Implement `getsuper` and `setsuper`.
This required the reintroduction of dedicated reciever parameters to `Object.get_property_local` and `Object.set_property`, which I had removed from the AVM1 code I copied it from. It turns out being able to change the reciever was actually necessary in order to make super set/get work.
2020-07-13 17:44:22 -04:00
David Wendt 1c3b9c50fe Implement prototype awareness for `get_property`, `has_property`, and `resolve_multiname`.
Furthermore, implement `has_own_property`.
2020-07-13 17:44:19 -04:00
David Wendt a0ab978bed Impl `callmethod`, `callproperty`, `callproplex`, `callpropvoid`, and `callstatic`.
Also, implement a method table that method traits can optionally add themselves to.

Also also, add the ability to invoke a method without a `this` object. This required a non-trivial refactoring of the activation machinery, and changes to the signature of `NativeFunction`, and all native AVM2 functions.
2020-07-13 17:43:49 -04:00
David Wendt bf45f7f161 Fix crash when reading or writing a property that redirects to a slot. 2020-07-13 17:43:48 -04:00
David Wendt 0ff1c04697 Impl `initproperty` 2020-07-13 17:43:45 -04:00
David Wendt cbce8660bc Implement `deleteproperty`. 2020-07-13 17:43:37 -04:00
David Wendt fd275bdcf3 Implement constant slots and traits.
Class and Function traits now generate const slots, too.
2020-07-13 17:43:33 -04:00
David Wendt 200c10b4a2 Classes can fit in slots, so let's stick them in there. 2020-07-13 17:43:27 -04:00
David Wendt 04879fc419 Implement class traits.
This allows the AVM to declare classes, which necessitated some refactoring to avoid double-borrows or having to do something "magic" that would dodge virtual properties.
2020-07-13 17:43:25 -04:00
David Wendt ecfd5abb41 Impl `construct` and `constructprop`. 2020-07-13 17:43:24 -04:00
David Wendt 1ab4091050 Implement slots and related opcodes. 2020-07-13 17:43:24 -04:00
David Wendt 88957b2b3d Add stub builtins for Object and Function. These are more-or-less identical to the way we did it in AVM1 (e.g. no fancy player globals file) 2020-07-13 17:43:24 -04:00
David Wendt 1945f36dc0 When running the initial script, also install it's traits onto the global scope. 2020-07-13 17:43:23 -04:00
David Wendt 502936f0fe Implement non-slot trait properties (Method, Getter, and Setter) 2020-07-13 17:43:22 -04:00
David Wendt 12e9fbbffb Impl virtual property slots 2020-07-13 17:43:22 -04:00
David Wendt eb0c9dcaec Allow constructing a function around a particular class definition.
I'm writing all this code assuming that classes and traits are syntactic sugar around ES3-style prototype chains on function objects. Hence, `FunctionObject` is still our workhorse object type for implementing typing.
2020-07-13 17:43:21 -04:00
David Wendt cf490bedfb Unstub `proto`. 2020-07-13 17:43:20 -04:00
David Wendt 984e701142 Swap out `has_property`'s stub impl. 2020-07-13 17:43:19 -04:00
David Wendt e5142e85e9 Replace `get_property` and `set_property` with slightly-less-stub impls. 2020-07-13 17:43:17 -04:00
David Wendt 376d1a8ca6 Add scope support 2020-07-13 17:43:13 -04:00
David Wendt 7f60fab1e5 Add the bare minimum necessary to get opcodes out of an ABC and into an interpreter loop.
Surprisingly enough, the "bare minimum" includes a stack, object model, and values already.
2020-07-13 17:42:45 -04:00
David Wendt e80c887261 Add a very basic object model to the AVM2 interpreter. 2020-07-13 17:42:44 -04:00