Skip to content

Commit 9614ba7

Browse files
committed
avm1: Make Executable private and move its API to FunctionObject
Also adds `Function::exec_constructor` for use by `super` calls.
1 parent 9c7e4e3 commit 9614ba7

File tree

11 files changed

+119
-150
lines changed

11 files changed

+119
-150
lines changed

core/src/avm1/function.rs

Lines changed: 87 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::string::{AvmString, StringContext, SwfStrExt as _};
1414
use crate::tag_utils::SwfSlice;
1515
use gc_arena::{Collect, Gc, Mutation};
1616
use ruffle_macros::istr;
17-
use std::{borrow::Cow, fmt, num::NonZeroU8};
17+
use std::{borrow::Cow, num::NonZeroU8};
1818
use swf::{avm1::types::FunctionFlags, SwfStr};
1919

2020
/// Represents a function defined in Ruffle's code.
@@ -406,7 +406,7 @@ struct Param<'gc> {
406406
/// AVM1 bytecode itself.
407407
#[derive(Copy, Clone, Collect)]
408408
#[collect(no_drop)]
409-
pub enum Executable<'gc> {
409+
enum Executable<'gc> {
410410
/// A function provided by the Ruffle runtime and implemented in Rust.
411411
Native(#[collect(require_static)] NativeFunction),
412412

@@ -415,21 +415,6 @@ pub enum Executable<'gc> {
415415
Action(Gc<'gc, Avm1Function<'gc>>),
416416
}
417417

418-
impl fmt::Debug for Executable<'_> {
419-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
420-
match self {
421-
Executable::Native(nf) => f
422-
.debug_tuple("Executable::Native")
423-
.field(&format!("{nf:p}"))
424-
.finish(),
425-
Executable::Action(af) => f
426-
.debug_tuple("Executable::Action")
427-
.field(&Gc::as_ptr(*af))
428-
.finish(),
429-
}
430-
}
431-
}
432-
433418
/// Indicates the default name to use for this execution in debug builds.
434419
pub enum ExecutionName<'gc> {
435420
Static(&'static str),
@@ -448,46 +433,7 @@ impl<'gc> From<AvmString<'gc>> for ExecutionName<'gc> {
448433
}
449434
}
450435

451-
impl<'gc> Executable<'gc> {
452-
/// A dummy `Executable` that does nothing, and returns `undefined`.
453-
const EMPTY: Self = Self::Native(|_, _, _| Ok(Value::Undefined));
454-
455-
/// Execute the given code.
456-
#[expect(clippy::too_many_arguments)]
457-
pub fn exec(
458-
&self,
459-
name: ExecutionName<'gc>,
460-
activation: &mut Activation<'_, 'gc>,
461-
this: Value<'gc>,
462-
depth: u8,
463-
args: &[Value<'gc>],
464-
reason: ExecutionReason,
465-
callee: Object<'gc>,
466-
) -> Result<Value<'gc>, Error<'gc>> {
467-
match self {
468-
Executable::Native(nf) => {
469-
// TODO: Change NativeFunction to accept `this: Value`.
470-
let this = this.coerce_to_object(activation);
471-
nf(activation, this, args)
472-
}
473-
Executable::Action(af) => af.exec(name, activation, this, depth, args, reason, callee),
474-
}
475-
}
476-
}
477-
478-
impl From<NativeFunction> for Executable<'_> {
479-
fn from(nf: NativeFunction) -> Self {
480-
Executable::Native(nf)
481-
}
482-
}
483-
484-
impl<'gc> From<Gc<'gc, Avm1Function<'gc>>> for Executable<'gc> {
485-
fn from(af: Gc<'gc, Avm1Function<'gc>>) -> Self {
486-
Executable::Action(af)
487-
}
488-
}
489-
490-
#[derive(Collect)]
436+
#[derive(Copy, Clone, Collect)]
491437
#[collect(no_drop)]
492438
pub struct FunctionObject<'gc> {
493439
/// The code that will be invoked when this object is called.
@@ -500,12 +446,17 @@ pub struct FunctionObject<'gc> {
500446
}
501447

502448
impl<'gc> FunctionObject<'gc> {
503-
/// Construct a function sans prototype.
504-
pub fn bare_function(
449+
/// Construct a function with any combination of regular and constructor parts.
450+
///
451+
/// `fn_proto` refers to the implicit proto of the function object, and the
452+
/// `prototype` refers to the explicit prototype of the function.
453+
/// The function and its prototype will be linked to each other.
454+
fn allocate_function(
505455
context: &StringContext<'gc>,
506456
function: Executable<'gc>,
507457
constructor: Option<NativeFunction>,
508458
fn_proto: Object<'gc>,
459+
prototype: Option<Object<'gc>>,
509460
) -> Object<'gc> {
510461
let obj = Object::new(context, Some(fn_proto));
511462
let native = NativeObject::Function(Gc::new(
@@ -516,37 +467,23 @@ impl<'gc> FunctionObject<'gc> {
516467
},
517468
));
518469
obj.set_native(context.gc(), native);
519-
obj
520-
}
521470

522-
/// Construct a function with any combination of regular and constructor parts.
523-
///
524-
/// `fn_proto` refers to the implicit proto of the function object, and the
525-
/// `prototype` refers to the explicit prototype of the function.
526-
/// The function and its prototype will be linked to each other.
527-
fn allocate_function(
528-
context: &StringContext<'gc>,
529-
function: Executable<'gc>,
530-
constructor: Option<NativeFunction>,
531-
fn_proto: Object<'gc>,
532-
prototype: Object<'gc>,
533-
) -> Object<'gc> {
534-
let function = Self::bare_function(context, function, constructor, fn_proto);
535-
536-
prototype.define_value(
537-
context.gc(),
538-
istr!(context, "constructor"),
539-
Value::Object(function),
540-
Attribute::DONT_ENUM,
541-
);
542-
function.define_value(
543-
context.gc(),
544-
istr!(context, "prototype"),
545-
prototype.into(),
546-
Attribute::empty(),
547-
);
471+
if let Some(prototype) = prototype {
472+
prototype.define_value(
473+
context.gc(),
474+
istr!(context, "constructor"),
475+
Value::Object(obj),
476+
Attribute::DONT_ENUM,
477+
);
478+
obj.define_value(
479+
context.gc(),
480+
istr!(context, "prototype"),
481+
prototype.into(),
482+
Attribute::empty(),
483+
);
484+
}
548485

549-
function
486+
obj
550487
}
551488

552489
/// Constructs a function that does nothing.
@@ -557,7 +494,8 @@ impl<'gc> FunctionObject<'gc> {
557494
fn_proto: Object<'gc>,
558495
prototype: Object<'gc>,
559496
) -> Object<'gc> {
560-
Self::allocate_function(context, Executable::EMPTY, None, fn_proto, prototype)
497+
let empty = Executable::Native(|_, _, _| Ok(Value::Undefined));
498+
Self::allocate_function(context, empty, None, fn_proto, Some(prototype))
561499
}
562500

563501
/// Construct a function from AVM1 bytecode and associated protos.
@@ -567,15 +505,16 @@ impl<'gc> FunctionObject<'gc> {
567505
fn_proto: Object<'gc>,
568506
prototype: Object<'gc>,
569507
) -> Object<'gc> {
570-
Self::allocate_function(context, function.into(), None, fn_proto, prototype)
508+
let function = Executable::Action(function);
509+
Self::allocate_function(context, function, None, fn_proto, Some(prototype))
571510
}
572511

573512
/// Construct a function from a native executable and associated protos.
574513
pub fn native(
575514
context: &StringContext<'gc>,
576515
function: NativeFunction,
577516
fn_proto: Object<'gc>,
578-
prototype: Object<'gc>,
517+
prototype: Option<Object<'gc>>,
579518
) -> Object<'gc> {
580519
let function = Executable::Native(function);
581520
Self::allocate_function(context, function, None, fn_proto, prototype)
@@ -601,35 +540,71 @@ impl<'gc> FunctionObject<'gc> {
601540
Executable::Native(function.unwrap_or(|_, _, _| Ok(Value::Undefined))),
602541
Some(constructor),
603542
fn_proto,
604-
prototype,
543+
Some(prototype),
605544
)
606545
}
607546

608-
pub fn as_executable(&self) -> Executable<'gc> {
609-
self.function
610-
}
611-
612-
pub fn as_constructor(&self) -> Executable<'gc> {
613-
if let Some(constr) = self.constructor {
614-
Executable::Native(constr)
615-
} else {
616-
self.function
547+
/// Execute the given code.
548+
///
549+
/// This is fairly low-level; prefer using other call methods if possible.
550+
#[expect(clippy::too_many_arguments)]
551+
pub fn exec(
552+
self,
553+
name: ExecutionName<'gc>,
554+
activation: &mut Activation<'_, 'gc>,
555+
this: Value<'gc>,
556+
depth: u8,
557+
args: &[Value<'gc>],
558+
reason: ExecutionReason,
559+
callee: Object<'gc>,
560+
) -> Result<Value<'gc>, Error<'gc>> {
561+
match self.function {
562+
Executable::Native(nf) => {
563+
// TODO: Change NativeFunction to accept `this: Value`.
564+
let this = this.coerce_to_object(activation);
565+
nf(activation, this, args)
566+
}
567+
Executable::Action(af) => af.exec(name, activation, this, depth, args, reason, callee),
617568
}
618569
}
619570

620-
pub fn is_native_constructor(&self) -> bool {
621-
self.constructor.is_some()
571+
/// Execute the given code as a constructor.
572+
///
573+
/// This is fairly low-level; prefer using other call methods if possible.
574+
#[expect(clippy::too_many_arguments)]
575+
pub fn exec_constructor(
576+
self,
577+
name: ExecutionName<'gc>,
578+
activation: &mut Activation<'_, 'gc>,
579+
this: Value<'gc>,
580+
depth: u8,
581+
args: &[Value<'gc>],
582+
reason: ExecutionReason,
583+
callee: Object<'gc>,
584+
) -> Result<Value<'gc>, Error<'gc>> {
585+
let constr = match self.constructor {
586+
Some(constr) => Executable::Native(constr),
587+
None => self.function,
588+
};
589+
match constr {
590+
Executable::Native(nf) => {
591+
// TODO: Change NativeFunction to accept `this: Value`.
592+
let this = this.coerce_to_object(activation);
593+
nf(activation, this, args)
594+
}
595+
Executable::Action(af) => af.exec(name, activation, this, depth, args, reason, callee),
596+
}
622597
}
623598

624599
pub fn call(
625-
&self,
600+
self,
626601
name: impl Into<ExecutionName<'gc>>,
627602
activation: &mut Activation<'_, 'gc>,
628603
callee: Object<'gc>,
629604
this: Value<'gc>,
630605
args: &[Value<'gc>],
631606
) -> Result<Value<'gc>, Error<'gc>> {
632-
self.function.exec(
607+
self.exec(
633608
name.into(),
634609
activation,
635610
this,
@@ -641,7 +616,7 @@ impl<'gc> FunctionObject<'gc> {
641616
}
642617

643618
pub fn construct_on_existing(
644-
&self,
619+
self,
645620
activation: &mut Activation<'_, 'gc>,
646621
callee: Object<'gc>,
647622
this: Object<'gc>,
@@ -650,7 +625,7 @@ impl<'gc> FunctionObject<'gc> {
650625
Self::define_constructor_props(activation, this, callee.into());
651626

652627
// Always ignore the constructor's return value.
653-
let _ = self.as_constructor().exec(
628+
let _ = self.exec_constructor(
654629
ExecutionName::Static("[ctor]"),
655630
activation,
656631
this.into(),
@@ -664,7 +639,7 @@ impl<'gc> FunctionObject<'gc> {
664639
}
665640

666641
pub fn construct(
667-
&self,
642+
self,
668643
activation: &mut Activation<'_, 'gc>,
669644
callee: Object<'gc>,
670645
args: &[Value<'gc>],
@@ -676,7 +651,9 @@ impl<'gc> FunctionObject<'gc> {
676651

677652
Self::define_constructor_props(activation, this, callee.into());
678653

679-
let result = self.as_constructor().exec(
654+
// Propagate the return value only for native constructors.
655+
let propagate = self.constructor.is_some();
656+
let ret = self.exec_constructor(
680657
ExecutionName::Static("[ctor]"),
681658
activation,
682659
this.into(),
@@ -685,13 +662,7 @@ impl<'gc> FunctionObject<'gc> {
685662
ExecutionReason::FunctionCall,
686663
callee,
687664
)?;
688-
689-
if self.is_native_constructor() {
690-
// Propagate the native method's return value.
691-
Ok(result)
692-
} else {
693-
Ok(this.into())
694-
}
665+
Ok(if propagate { ret } else { this.into() })
695666
}
696667

697668
fn define_constructor_props(

core/src/avm1/globals.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ pub fn create_timer<'gc>(
297297
use crate::timer::TimerCallback;
298298

299299
let (callback, interval) = match args.get(0) {
300-
Some(Value::Object(o)) if o.as_executable().is_some() => (
300+
Some(Value::Object(o)) if o.as_function().is_some() => (
301301
TimerCallback::Avm1Function {
302302
func: *o,
303303
params: args.get(2..).unwrap_or_default().to_vec(),

core/src/avm1/globals/function.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub fn call<'gc>(
6262
};
6363

6464
// NOTE: does not use `Object::call`, as `super()` only works with direct calls.
65-
match func.as_executable() {
65+
match func.as_function() {
6666
Some(exec) => exec.exec(
6767
ExecutionName::Static("[Anonymous]"),
6868
activation,
@@ -103,7 +103,7 @@ pub fn apply<'gc>(
103103
}
104104

105105
// NOTE: does not use `Object::call`, as `super()` only works with direct calls.
106-
match func.as_executable() {
106+
match func.as_function() {
107107
Some(exec) => exec.exec(
108108
ExecutionName::Static("[Anonymous]"),
109109
activation,

core/src/avm1/globals/object.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ fn to_string<'gc>(
124124
this: Object<'gc>,
125125
_args: &[Value<'gc>],
126126
) -> Result<Value<'gc>, Error<'gc>> {
127-
if this.as_executable().is_some() {
127+
if this.as_function().is_some() {
128128
Ok(AvmString::new_ascii_static(activation.gc(), b"[type Function]").into())
129129
} else {
130130
Ok(AvmString::new_ascii_static(activation.gc(), b"[object Object]").into())
@@ -183,7 +183,7 @@ pub fn register_class<'gc>(
183183

184184
let constructor = match constructor {
185185
Value::Null | Value::Undefined => None,
186-
Value::Object(obj) if obj.as_executable().is_some() => Some(*obj),
186+
Value::Object(obj) if obj.as_function().is_some() => Some(*obj),
187187
_ => return Ok(false.into()),
188188
};
189189

@@ -212,7 +212,7 @@ fn watch<'gc>(
212212
.get(1)
213213
.unwrap_or(&Value::Undefined)
214214
.coerce_to_object(activation);
215-
if callback.as_executable().is_none() {
215+
if callback.as_function().is_none() {
216216
return Ok(false.into());
217217
}
218218
let user_data = args.get(2).cloned().unwrap_or(Value::Undefined);

core/src/avm1/globals/shared_object.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ fn recursive_serialize<'gc>(
104104

105105
match elem {
106106
Value::Object(o) => {
107-
if o.as_executable().is_some() {
107+
if o.as_function().is_some() {
108108
} else if o.as_display_object().is_some() {
109109
writer.undefined(name.as_ref())
110110
} else if let NativeObject::Array(_) = o.native() {

0 commit comments

Comments
 (0)