2020-02-06 04:15:03 +00:00
|
|
|
//! AVM2 executables.
|
|
|
|
|
2020-02-08 03:42:04 +00:00
|
|
|
use crate::avm2::activation::Activation;
|
2020-07-03 01:49:53 +00:00
|
|
|
use crate::avm2::method::{BytecodeMethod, Method, NativeMethod};
|
2020-08-08 21:10:05 +00:00
|
|
|
use crate::avm2::object::Object;
|
2020-02-12 23:52:00 +00:00
|
|
|
use crate::avm2::scope::Scope;
|
2020-02-06 04:15:03 +00:00
|
|
|
use crate::avm2::value::Value;
|
2020-07-04 21:18:41 +00:00
|
|
|
use crate::avm2::Error;
|
2020-07-04 21:56:27 +00:00
|
|
|
use gc_arena::{Collect, CollectionContext, Gc, GcCell, MutationContext};
|
2020-02-06 04:15:03 +00:00
|
|
|
use std::fmt;
|
2020-07-02 04:01:07 +00:00
|
|
|
|
2020-07-04 21:56:27 +00:00
|
|
|
/// Represents code written in AVM2 bytecode that can be executed by some
|
|
|
|
/// means.
|
|
|
|
#[derive(Clone, Collect)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct BytecodeExecutable<'gc> {
|
|
|
|
/// The method code to execute from a given ABC file.
|
|
|
|
method: Gc<'gc, BytecodeMethod<'gc>>,
|
|
|
|
|
|
|
|
/// The scope stack to pull variables from.
|
|
|
|
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
|
|
|
|
2020-09-19 14:27:24 +00:00
|
|
|
/// The receiver that this function is always called with.
|
2020-07-04 21:56:27 +00:00
|
|
|
///
|
2020-09-19 14:27:24 +00:00
|
|
|
/// If `None`, then the receiver provided by the caller is used. A
|
2020-07-04 21:56:27 +00:00
|
|
|
/// `Some` value indicates a bound executable.
|
2020-09-19 14:27:24 +00:00
|
|
|
receiver: Option<Object<'gc>>,
|
2020-07-04 21:56:27 +00:00
|
|
|
}
|
|
|
|
|
2020-02-06 04:15:03 +00:00
|
|
|
/// Represents code that can be executed by some means.
|
2020-07-04 21:56:27 +00:00
|
|
|
#[derive(Copy, Clone)]
|
2020-02-06 04:15:03 +00:00
|
|
|
pub enum Executable<'gc> {
|
2020-07-01 03:44:14 +00:00
|
|
|
/// Code defined in Ruffle's binary.
|
|
|
|
///
|
2020-09-19 14:27:24 +00:00
|
|
|
/// The second parameter stores the bound receiver for this function.
|
2021-05-05 12:26:20 +00:00
|
|
|
Native(NativeMethod, Option<Object<'gc>>),
|
2020-07-01 03:44:14 +00:00
|
|
|
|
|
|
|
/// Code defined in a loaded ABC file.
|
2020-07-04 21:56:27 +00:00
|
|
|
Action(Gc<'gc, BytecodeExecutable<'gc>>),
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl<'gc> Collect for Executable<'gc> {
|
|
|
|
fn trace(&self, cc: CollectionContext) {
|
|
|
|
match self {
|
2020-07-04 21:56:27 +00:00
|
|
|
Self::Action(be) => be.trace(cc),
|
2020-09-19 14:27:24 +00:00
|
|
|
Self::Native(_nf, receiver) => receiver.trace(cc),
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-15 01:30:19 +00:00
|
|
|
impl<'gc> Executable<'gc> {
|
2020-07-01 03:44:14 +00:00
|
|
|
/// Convert a method into an executable.
|
|
|
|
pub fn from_method(
|
|
|
|
method: Method<'gc>,
|
|
|
|
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
2020-09-19 14:27:24 +00:00
|
|
|
receiver: Option<Object<'gc>>,
|
2020-07-04 21:56:27 +00:00
|
|
|
mc: MutationContext<'gc, '_>,
|
2020-07-01 03:44:14 +00:00
|
|
|
) -> Self {
|
|
|
|
match method {
|
2020-09-19 14:27:24 +00:00
|
|
|
Method::Native(nf) => Self::Native(nf, receiver),
|
2020-07-18 21:02:32 +00:00
|
|
|
Method::Entry(method) => Self::Action(Gc::allocate(
|
2020-07-04 21:56:27 +00:00
|
|
|
mc,
|
|
|
|
BytecodeExecutable {
|
2020-07-18 21:02:32 +00:00
|
|
|
method,
|
2020-07-04 21:56:27 +00:00
|
|
|
scope,
|
2020-09-19 14:27:24 +00:00
|
|
|
receiver,
|
2020-07-04 21:56:27 +00:00
|
|
|
},
|
|
|
|
)),
|
2020-07-01 03:44:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-25 23:07:53 +00:00
|
|
|
/// Execute a method.
|
|
|
|
///
|
|
|
|
/// The function will either be called directly if it is a Rust builtin, or
|
2020-07-04 21:18:41 +00:00
|
|
|
/// executed on the same AVM2 instance as the activation passed in here.
|
|
|
|
/// The value returned in either case will be provided here.
|
|
|
|
///
|
2020-09-19 14:27:24 +00:00
|
|
|
/// It is a panicking logic error to attempt to execute user code while any
|
2020-07-04 21:18:41 +00:00
|
|
|
/// reachable object is currently under a GcCell write lock.
|
2020-02-15 01:30:19 +00:00
|
|
|
pub fn exec(
|
|
|
|
&self,
|
Allow binding a reciever to a function, and make all method traits bind themselves to the object they were constructed on.
Our already odd `super` handling throws up another subtlety regarding bound recievers. Since we have to construct an instance of a parent class in order to get traits on it, we also have to make sure that we initialize traits with the correct reciever. I'll demonstrate here:
```let mut base = base_proto.construct(avm, context, &[])?;
let name = base.resolve_multiname(&multiname).unwrap();
let value = base.get_property(object, &name, avm, context)?.resolve(avm, context)?```
In this case, if `name` is the name of a method, getter, or setter trait, then `get_property` will instantiate that trait on `base` but bound to `reciever`. This is correct behavior for this case, but more generally, trait instantiation is permenant and therefore there's potential for confusing shenanigans if you `get_property` with the wrong reciever.
To be very clear, `reciever` should *always* be the same object that is getting `get_property` et. all called on it. In the event that you need to instantiate traits with a different `reciever`, you should construct a one-off object and retrieve prototypes from that.
2020-03-04 03:10:27 +00:00
|
|
|
unbound_reciever: Option<Object<'gc>>,
|
2020-02-15 01:30:19 +00:00
|
|
|
arguments: &[Value<'gc>],
|
2020-07-28 03:19:43 +00:00
|
|
|
activation: &mut Activation<'_, 'gc, '_>,
|
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
|
|
|
base_proto: Option<Object<'gc>>,
|
2020-12-11 03:21:36 +00:00
|
|
|
callee: Object<'gc>,
|
2020-07-04 21:18:41 +00:00
|
|
|
) -> Result<Value<'gc>, Error> {
|
2020-02-25 23:07:53 +00:00
|
|
|
match self {
|
2020-09-19 14:27:24 +00:00
|
|
|
Executable::Native(nf, receiver) => {
|
2020-12-18 04:01:09 +00:00
|
|
|
let receiver = receiver.or(unbound_reciever);
|
2020-12-19 18:03:37 +00:00
|
|
|
let scope = activation.scope();
|
|
|
|
let mut activation = Activation::from_builtin(
|
|
|
|
activation.context.reborrow(),
|
|
|
|
scope,
|
|
|
|
receiver,
|
|
|
|
base_proto,
|
|
|
|
)?;
|
2020-12-18 04:01:09 +00:00
|
|
|
|
|
|
|
nf(&mut activation, receiver, arguments)
|
2020-07-28 03:19:43 +00:00
|
|
|
}
|
2020-07-04 21:56:27 +00:00
|
|
|
Executable::Action(bm) => {
|
2020-09-19 14:27:24 +00:00
|
|
|
let receiver = bm.receiver.or(unbound_reciever);
|
2020-07-04 21:18:41 +00:00
|
|
|
let mut activation = Activation::from_method(
|
2020-07-28 03:19:43 +00:00
|
|
|
activation.context.reborrow(),
|
2020-07-04 21:56:27 +00:00
|
|
|
bm.method,
|
|
|
|
bm.scope,
|
2020-09-19 14:27:24 +00:00
|
|
|
receiver,
|
2020-07-04 21:18:41 +00:00
|
|
|
arguments,
|
|
|
|
base_proto,
|
2020-12-11 03:21:36 +00:00
|
|
|
callee,
|
2020-07-08 01:28:24 +00:00
|
|
|
)?;
|
2020-02-15 01:30:19 +00:00
|
|
|
|
2020-07-28 03:19:43 +00:00
|
|
|
activation.run_actions(bm.method)
|
2020-02-15 01:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-06 04:15:03 +00:00
|
|
|
impl<'gc> fmt::Debug for Executable<'gc> {
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
2020-07-04 21:56:27 +00:00
|
|
|
Self::Action(be) => fmt
|
2020-07-01 03:44:14 +00:00
|
|
|
.debug_struct("Executable::Action")
|
2020-07-04 21:56:27 +00:00
|
|
|
.field("method", &be.method)
|
|
|
|
.field("scope", &be.scope)
|
2020-09-19 14:27:24 +00:00
|
|
|
.field("receiver", &be.receiver)
|
2020-07-01 03:44:14 +00:00
|
|
|
.finish(),
|
2020-09-19 14:27:24 +00:00
|
|
|
Self::Native(nf, receiver) => fmt
|
2020-02-06 04:15:03 +00:00
|
|
|
.debug_tuple("Executable::Native")
|
|
|
|
.field(&format!("{:p}", nf))
|
2020-09-19 14:27:24 +00:00
|
|
|
.field(receiver)
|
2020-02-06 04:15:03 +00:00
|
|
|
.finish(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|