Skip to content

Commit c7bf11e

Browse files
moulinsHerschel
authored andcommitted
avm1: More accurate handling of preload/suppress flags in functions
- Handle the case where both preload aud suppress flags are set for the same variable; - Remove `arguments` field in `Activation`; instead use a normal local definition; - When `suppress_this` is set, inherit the `this` value from parent activation. (This isn't entirely correct, as FP's `this` is mutable and seems to be part of the scope chain, but this would require a larger refactoring)
1 parent aa1e53e commit c7bf11e

File tree

4 files changed

+98
-81
lines changed

4 files changed

+98
-81
lines changed

core/src/avm1.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,6 @@ impl<'gc> Avm1<'gc> {
211211
active_clip,
212212
clip_obj.into(),
213213
None,
214-
None,
215214
);
216215
if let Err(e) = child_activation.run_actions(code) {
217216
root_error_handler(&mut child_activation, e);
@@ -251,7 +250,6 @@ impl<'gc> Avm1<'gc> {
251250
active_clip,
252251
clip_obj.into(),
253252
None,
254-
None,
255253
);
256254
function(&mut activation)
257255
}
@@ -300,7 +298,6 @@ impl<'gc> Avm1<'gc> {
300298
active_clip,
301299
clip_obj.into(),
302300
None,
303-
None,
304301
);
305302
if let Err(e) = child_activation.run_actions(code) {
306303
root_error_handler(&mut child_activation, e);

core/src/avm1/activation.rs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -194,14 +194,20 @@ pub struct Activation<'a, 'gc: 'a, 'gc_context: 'a> {
194194
constant_pool: GcCell<'gc, Vec<Value<'gc>>>,
195195

196196
/// The immutable value of `this`.
197+
///
198+
/// This differs from Flash Player, where `this` is mutable and seems
199+
/// to be part of the scope chain (e.g. a function with the `suppress_this` flag
200+
/// set can modify the `this` value of its closure).
201+
///
202+
/// Fortunately, ActionScript syntax prevents mutating `this` altogether, so
203+
/// observing this behavior requires manually-crafted bytecode.
204+
///
205+
/// TODO: implement correct semantics for mutable `this`.
197206
this: Value<'gc>,
198207

199208
/// The function object being called.
200209
pub callee: Option<Object<'gc>>,
201210

202-
/// The arguments this function was called by.
203-
pub arguments: Option<Object<'gc>>,
204-
205211
/// Local registers, if any.
206212
///
207213
/// None indicates a function executing out of the global register set.
@@ -254,7 +260,6 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
254260
base_clip: DisplayObject<'gc>,
255261
this: Value<'gc>,
256262
callee: Option<Object<'gc>>,
257-
arguments: Option<Object<'gc>>,
258263
) -> Self {
259264
avm_debug!(context.avm1, "START {}", id);
260265
Self {
@@ -268,7 +273,6 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
268273
base_clip_unloaded: base_clip.removed(),
269274
this,
270275
callee,
271-
arguments,
272276
local_registers: None,
273277
actions_since_timeout_check: 0,
274278
}
@@ -293,7 +297,6 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
293297
base_clip_unloaded: self.base_clip_unloaded,
294298
this: self.this,
295299
callee: self.callee,
296-
arguments: self.arguments,
297300
local_registers: self.local_registers,
298301
actions_since_timeout_check: 0,
299302
}
@@ -329,7 +332,6 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
329332
base_clip_unloaded: base_clip.removed(),
330333
this: globals.into(),
331334
callee: None,
332-
arguments: None,
333335
local_registers: None,
334336
actions_since_timeout_check: 0,
335337
}
@@ -383,7 +385,6 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
383385
active_clip,
384386
clip_obj.into(),
385387
None,
386-
None,
387388
);
388389
child_activation.run_actions(code)
389390
}
@@ -421,7 +422,6 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
421422
active_clip,
422423
clip_obj.into(),
423424
None,
424-
None,
425425
);
426426
function(&mut activation)
427427
}
@@ -2131,7 +2131,6 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
21312131
self.base_clip,
21322132
self.this,
21332133
self.callee,
2134-
self.arguments,
21352134
);
21362135

21372136
match catch_vars {
@@ -2737,12 +2736,6 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
27372736
return Ok(CallableValue::UnCallable(self.this_cell()));
27382737
}
27392738

2740-
if &name == b"arguments" && self.arguments.is_some() {
2741-
return Ok(CallableValue::UnCallable(Value::Object(
2742-
self.arguments.unwrap(),
2743-
)));
2744-
}
2745-
27462739
self.scope_cell().read().resolve(name, self)
27472740
}
27482741

core/src/avm1/function.rs

Lines changed: 88 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -161,33 +161,83 @@ impl<'gc> Avm1Function<'gc> {
161161
}
162162

163163
fn load_this(&self, frame: &mut Activation<'_, 'gc, '_>, this: Value<'gc>, preload_r: &mut u8) {
164-
if self.flags.contains(FunctionFlags::PRELOAD_THIS) {
165-
//TODO: What happens if you specify both suppress and
166-
//preload for this?
164+
let preload = self.flags.contains(FunctionFlags::PRELOAD_THIS);
165+
let suppress = self.flags.contains(FunctionFlags::SUPPRESS_THIS);
166+
167+
if preload {
168+
// The register is set to undefined if both flags are set.
169+
let this = if suppress { Value::Undefined } else { this };
167170
frame.set_local_register(*preload_r, this);
168171
*preload_r += 1;
169172
}
170173
}
171174

172-
fn load_arguments(&self, frame: &mut Activation<'_, 'gc, '_>, arguments: Value<'gc>, preload_r: &mut u8) {
173-
if self.flags.contains(FunctionFlags::PRELOAD_ARGUMENTS) {
174-
//TODO: What happens if you specify both suppress and
175-
//preload for arguments?
175+
fn load_arguments(
176+
&self,
177+
frame: &mut Activation<'_, 'gc, '_>,
178+
args: &[Value<'gc>],
179+
caller: Option<Object<'gc>>,
180+
preload_r: &mut u8,
181+
) {
182+
let preload = self.flags.contains(FunctionFlags::PRELOAD_ARGUMENTS);
183+
let suppress = self.flags.contains(FunctionFlags::SUPPRESS_ARGUMENTS);
184+
185+
if suppress && !preload {
186+
return;
187+
}
188+
189+
let arguments = ArrayObject::new(
190+
frame.context.gc_context,
191+
frame.context.avm1.prototypes().array,
192+
args.iter().cloned(),
193+
);
194+
195+
arguments.define_value(
196+
frame.context.gc_context,
197+
"callee",
198+
frame.callee.unwrap().into(),
199+
Attribute::DONT_ENUM,
200+
);
201+
202+
arguments.define_value(
203+
frame.context.gc_context,
204+
"caller",
205+
caller.map(Value::from).unwrap_or(Value::Null),
206+
Attribute::DONT_ENUM,
207+
);
208+
209+
let arguments = Value::from(arguments);
210+
211+
// Contrarily to `this` and `super`, setting both flags is equivalent to just setting `preload`.
212+
if preload {
176213
frame.set_local_register(*preload_r, arguments);
177214
*preload_r += 1;
215+
} else {
216+
frame.force_define_local("arguments".into(), arguments);
178217
}
179218
}
180219

181-
fn load_super(&self, frame: &mut Activation<'_, 'gc, '_>, super_object: Option<Object<'gc>>, preload_r: &mut u8) {
182-
if let Some(super_object) = super_object {
183-
if self.flags.contains(FunctionFlags::PRELOAD_SUPER) {
184-
frame.set_local_register(*preload_r, super_object.into());
185-
//TODO: What happens if you specify both suppress and
186-
//preload for super?
187-
*preload_r += 1;
188-
} else {
189-
frame.force_define_local("super".into(), super_object.into());
190-
}
220+
fn load_super(
221+
&self,
222+
frame: &mut Activation<'_, 'gc, '_>,
223+
this: Option<Object<'gc>>,
224+
depth: u8,
225+
preload_r: &mut u8,
226+
) {
227+
let preload = self.flags.contains(FunctionFlags::PRELOAD_SUPER);
228+
let suppress = self.flags.contains(FunctionFlags::SUPPRESS_SUPER);
229+
230+
// TODO: `super` should only be defined if this was a method call (depth > 0?)
231+
// `f[""]()` emits a CallMethod op, causing `this` to be undefined, but `super` is a function; what is it?
232+
let zuper = this
233+
.filter(|_| !suppress)
234+
.map(|this| SuperObject::new(frame, this, depth).into());
235+
236+
if preload {
237+
// The register is set to undefined if both flags are set.
238+
frame.set_local_register(*preload_r, zuper.unwrap_or(Value::Undefined));
239+
} else if let Some(zuper) = zuper {
240+
frame.force_define_local("super".into(), zuper);
191241
}
192242
}
193243

@@ -309,15 +359,14 @@ impl<'gc> Executable<'gc> {
309359

310360
let target = activation.target_clip_or_root();
311361
let is_closure = activation.swf_version() >= 6;
312-
let base_clip = if (is_closure || reason == ExecutionReason::Special)
313-
&& !af.base_clip.removed()
314-
{
315-
af.base_clip
316-
} else {
317-
this_obj
318-
.and_then(|this| this.as_display_object())
319-
.unwrap_or(target)
320-
};
362+
let base_clip =
363+
if (is_closure || reason == ExecutionReason::Special) && !af.base_clip.removed() {
364+
af.base_clip
365+
} else {
366+
this_obj
367+
.and_then(|this| this.as_display_object())
368+
.unwrap_or(target)
369+
};
321370
let (swf_version, parent_scope) = if is_closure {
322371
// Function calls in a v6+ SWF are proper closures, and "close" over the scope that defined the function:
323372
// * Use the SWF version from the SWF that defined the function.
@@ -355,45 +404,24 @@ impl<'gc> Executable<'gc> {
355404
Scope::new_local_scope(parent_scope, activation.context.gc_context),
356405
);
357406

358-
let arguments = if af.flags.contains(FunctionFlags::SUPPRESS_ARGUMENTS) {
359-
ArrayObject::empty(activation)
360-
} else {
361-
ArrayObject::new(
362-
activation.context.gc_context,
363-
activation.context.avm1.prototypes().array,
364-
args.iter().cloned(),
365-
)
366-
};
367-
arguments.define_value(
368-
activation.context.gc_context,
369-
"callee",
370-
callee.into(),
371-
Attribute::DONT_ENUM,
372-
);
373407
// The caller is the previous callee.
374-
arguments.define_value(
375-
activation.context.gc_context,
376-
"caller",
377-
activation.callee.map(Value::from).unwrap_or(Value::Null),
378-
Attribute::DONT_ENUM,
379-
);
380-
381-
// TODO: `super` should only be defined if this was a method call (depth > 0?)
382-
// `f[""]()` emits a CallMethod op, causing `this` to be undefined, but `super` is a function; what is it?
383-
let super_object: Option<Object<'gc>> = this_obj.and_then(|this| {
384-
if !af.flags.contains(FunctionFlags::SUPPRESS_SUPER) {
385-
Some(SuperObject::new(activation, this, depth).into())
386-
} else {
387-
None
388-
}
389-
});
408+
let arguments_caller = activation.callee;
390409

391410
let name = if cfg!(feature = "avm_debug") {
392411
Cow::Owned(af.debug_string_for_call(name, args))
393412
} else {
394413
Cow::Borrowed("[Anonymous]")
395414
};
396415

416+
let is_this_inherited = af
417+
.flags
418+
.intersects(FunctionFlags::PRELOAD_THIS | FunctionFlags::SUPPRESS_THIS);
419+
let local_this = if is_this_inherited {
420+
activation.this_cell()
421+
} else {
422+
this
423+
};
424+
397425
let max_recursion_depth = activation.context.avm1.max_recursion_depth();
398426
let mut frame = Activation::from_action(
399427
activation.context.reborrow(),
@@ -402,17 +430,16 @@ impl<'gc> Executable<'gc> {
402430
child_scope,
403431
af.constant_pool,
404432
base_clip,
405-
this,
433+
local_this,
406434
Some(callee),
407-
Some(arguments.into()),
408435
);
409436

410437
frame.allocate_local_registers(af.register_count(), frame.context.gc_context);
411438

412439
let mut preload_r = 1;
413440
af.load_this(&mut frame, this, &mut preload_r);
414-
af.load_arguments(&mut frame, arguments.into(), &mut preload_r);
415-
af.load_super(&mut frame, super_object, &mut preload_r);
441+
af.load_arguments(&mut frame, args, arguments_caller, &mut preload_r);
442+
af.load_super(&mut frame, this_obj, depth, &mut preload_r);
416443
af.load_root(&mut frame, &mut preload_r);
417444
af.load_parent(&mut frame, &mut preload_r);
418445
af.load_global(&mut frame, &mut preload_r);

tests/tests/regression_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ swf_tests! {
577577
(function_as_function, "avm1/function_as_function", 1),
578578
(function_base_clip_removed, "avm1/function_base_clip_removed", 3),
579579
(function_base_clip, "avm1/function_base_clip", 2),
580-
#[ignore] (function_suppress_and_preload, "avm1/function_suppress_and_preload", 1),
580+
(function_suppress_and_preload, "avm1/function_suppress_and_preload", 1),
581581
(funky_function_calls, "avm1/funky_function_calls", 1),
582582
(get_bytes_total, "avm1/get_bytes_total", 1),
583583
(getproperty_swf4, "avm1/getproperty_swf4", 1),

0 commit comments

Comments
 (0)