Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion core/src/avm1/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,23 @@ impl<'gc> FunctionObject<'gc> {
let native = NativeObject::Function(Gc::new(context.gc(), self));
obj.set_native(context.gc(), native);

// In swfv5, __proto__ property of function objects is undefined by default.
obj.define_value(
context.gc(),
istr!(context, "__proto__"),
fn_proto.into(),
Attribute::DONT_ENUM | Attribute::DONT_DELETE | Attribute::VERSION_6,
);

// This should be correct, because f.hasOwnProperty("constructor") returns true in flashplayer.
let constr_val = fn_proto.get_data_raw(istr!(context, "constructor"));
obj.define_value(
context.gc(),
istr!(context, "constructor"),
constr_val,
Attribute::DONT_ENUM | Attribute::DONT_DELETE,
);

if let Some(prototype) = prototype {
prototype.define_value(
context.gc(),
Expand All @@ -490,7 +507,7 @@ impl<'gc> FunctionObject<'gc> {
context.gc(),
istr!(context, "prototype"),
prototype.into(),
Attribute::empty(),
Attribute::DONT_ENUM,
);
}

Expand Down
28 changes: 28 additions & 0 deletions core/src/avm1/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use crate::display_object::{DisplayObject, TDisplayObject, TDisplayObjectContainer};
use crate::string::{AvmString, StringContext, WStr, WString};
use gc_arena::Collect;
use ruffle_macros::istr;
use std::str;

mod accessibility;
Expand Down Expand Up @@ -546,6 +547,33 @@

let object = object::create_class(context);
let function = function::create_class(context);

let patch_constructor = |obj: Object<'gc>| {
obj.define_value(
context.gc(),
istr!(context.strings, "constructor"),
function.constr.into(),
Attribute::DONT_ENUM | Attribute::DONT_DELETE,
);
};

let patch_proto_methods = |proto: Object<'gc>| {
for val in proto.get_all_property_data() {
if let Value::Object(o) = val {
if o.as_function().is_some() {
patch_constructor(o);
}
}

Check warning on line 566 in core/src/avm1/globals.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (566)
}
};

// function.proto does not need to be patched because its constructor was set in function::build.
patch_constructor(object.constr);
patch_constructor(function.constr);

patch_proto_methods(object.proto);
patch_proto_methods(function.proto);

let (broadcaster_fns, as_broadcaster) = as_broadcaster::create_class(context, object.proto);

let flash = Object::new(context.strings, Some(object.proto));
Expand Down
4 changes: 2 additions & 2 deletions core/src/avm1/globals/as_broadcaster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use gc_arena::Collect;
use ruffle_macros::istr;

const OBJECT_DECLS: &[Declaration] = declare_properties! {
"initialize" => method(initialize; DONT_ENUM | DONT_DELETE);
"initialize" => function(initialize; DONT_ENUM | DONT_DELETE);
"addListener" => function(add_listener; DONT_ENUM | DONT_DELETE);
"removeListener" => function(remove_listener; DONT_ENUM | DONT_DELETE);
"broadcastMessage" => function(broadcast_message; DONT_ENUM | DONT_DELETE);
Expand All @@ -27,7 +27,7 @@ pub fn create_class<'gc>(
let class = context.empty_class(super_proto);

let mut define_as_object = |index: usize| -> Object<'gc> {
match OBJECT_DECLS[index].define_on(context.strings, class.constr, context.fn_proto) {
match OBJECT_DECLS[index].define_on(context, class.constr) {
Value::Object(o) => o,
_ => panic!("expected object for broadcaster function"),
}
Expand Down
17 changes: 16 additions & 1 deletion core/src/avm1/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
use crate::avm1::globals::xml_socket::XmlSocket;
use crate::avm1::object::super_object::SuperObject;
use crate::avm1::xml::XmlNode;
use crate::avm1::{Activation, Error, Value};
use crate::avm1::{Activation, Attribute, Error, Value};
use crate::bitmap::bitmap_data::BitmapData;
use crate::display_object::{
Avm1Button, DisplayObject, EditText, MovieClip, TDisplayObject as _, Video,
Expand Down Expand Up @@ -214,6 +214,21 @@
return Ok(());
}

if name == istr!("__proto__") {
if let NativeObject::Function(_) = self.native() {
let attributes = if activation.swf_version() < 6 {

Check warning on line 219 in core/src/avm1/object.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (219)
// Explicit overwriting in swfv5 makes __proto__ of function objects visible to swfv5.
Attribute::DONT_ENUM | Attribute::DONT_DELETE

Check warning on line 221 in core/src/avm1/object.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (221)
} else {
// After explicit overwriting in swfv6 and above, __proto__ of function objects remains invisible to swfv5.
Attribute::DONT_ENUM | Attribute::DONT_DELETE | Attribute::VERSION_6

Check warning on line 224 in core/src/avm1/object.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (224)
};

self.define_value(activation.gc(), name, value, attributes);
return Ok(());

Check warning on line 228 in core/src/avm1/object.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (227–228)
}
}

let mut value = value;
let (this, mut proto) = if let Some(super_object) = self.as_super_object() {
(super_object.this(), super_object.proto(activation))
Expand Down
34 changes: 34 additions & 0 deletions core/src/avm1/object/script_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,26 @@
.map_or(Value::Undefined, |property| property.data())
}

/// Gets the value of a data property without activation, ignoring attributes.
pub fn get_data_raw(self, name: AvmString<'gc>) -> Value<'gc> {
self.0
.borrow()
.properties
.get(name, false)
.map(|p| p.data())
.unwrap_or(Value::Undefined)
}

/// Gets values of all data properties stored on this object, ignoring attributes.
pub fn get_all_property_data(self) -> Vec<Value<'gc>> {
self.0
.borrow()
.properties
.iter()
.map(|(_, p)| p.data())
.collect()
}

/// Sets a data property on this object, ignoring attributes.
///
/// Doesn't look up the prototype chain and ignores virtual properties, but still might
Expand Down Expand Up @@ -660,6 +680,20 @@
return zuper.proto(activation);
}

// In swfv5, __proto__ property of function objects is undefined by default.
if activation.swf_version() < 6 {
if let NativeObject::Function(_) = self.native_no_super() {
let read = self.0.borrow();
if let Some(prop) = read.properties.get(istr!("__proto__"), false) {
if !prop.allow_swf_version(activation.swf_version()) {
return Value::Undefined;
}
return prop.data();
}
return Value::Undefined;

Check warning on line 693 in core/src/avm1/object/script_object.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (692–693)
}
}

self.get_data(istr!("__proto__"), activation)
}

Expand Down
48 changes: 33 additions & 15 deletions core/src/avm1/property_decl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
#[inline(never)]
pub fn define_properties_on(&mut self, this: Object<'gc>, decls: &[Declaration]) {
for decl in decls {
decl.define_on(self.strings, this, self.fn_proto);
decl.define_on(self, this);
}
}

Expand Down Expand Up @@ -130,19 +130,20 @@
/// defined a property.
pub fn define_on<'gc>(
&self,
context: &mut StringContext<'gc>,
context: &mut DeclContext<'_, 'gc>,
this: Object<'gc>,
fn_proto: Object<'gc>,
) -> Value<'gc> {
let mc = context.gc();

let name = context.intern_static(WStr::from_units(self.name));
let name = context.strings.intern_static(WStr::from_units(self.name));
let value = match self.kind {
DeclKind::Property { getter, setter } => {
// Property objects are unobservable by user code, so a bare function is enough.
let getter = FunctionObject::native(getter).build(context, fn_proto, None);
let setter = setter
.map(|setter| FunctionObject::native(setter).build(context, fn_proto, None));
let getter =
FunctionObject::native(getter).build(context.strings, context.fn_proto, None);
let setter = setter.map(|setter| {
FunctionObject::native(setter).build(context.strings, context.fn_proto, None)
});
this.add_property(mc, name.into(), getter, setter, self.attributes);
return Value::Undefined;
}
Expand All @@ -152,25 +153,42 @@
setter,
} => {
// Property objects are unobservable by user code, so a bare function is enough.
let getter =
FunctionObject::table_native(native, getter).build(context, fn_proto, None);
let getter = FunctionObject::table_native(native, getter).build(
context.strings,
context.fn_proto,
None,
);
let setter = setter.map(|setter| {
FunctionObject::table_native(native, setter).build(context, fn_proto, None)
FunctionObject::table_native(native, setter).build(
context.strings,
context.fn_proto,
None,
)
});
this.add_property(mc, name.into(), getter, setter, self.attributes);
return Value::Undefined;
}
DeclKind::Method(f) | DeclKind::Function(f) => {
let p = matches!(self.kind, DeclKind::Function(_)).then_some(fn_proto);
FunctionObject::native(f).build(context, fn_proto, p).into()
let p = if matches!(self.kind, DeclKind::Function(_)) {
Some(Object::new(context.strings, Some(context.object_proto)))
} else {
None
};
FunctionObject::native(f)
.build(context.strings, context.fn_proto, p)
.into()
}
DeclKind::TableMethod(f, index) | DeclKind::TableFunction(f, index) => {
let p = matches!(self.kind, DeclKind::Function(_)).then_some(fn_proto);
let p = if matches!(self.kind, DeclKind::TableFunction(_, _)) {
Some(Object::new(context.strings, Some(context.object_proto)))

Check warning on line 183 in core/src/avm1/property_decl.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (183)
} else {
None
};
FunctionObject::table_native(f, index)
.build(context, fn_proto, p)
.build(context.strings, context.fn_proto, p)
.into()
}
DeclKind::String(s) => context.intern_static(WStr::from_units(s)).into(),
DeclKind::String(s) => context.strings.intern_static(WStr::from_units(s)).into(),
DeclKind::Bool(b) => b.into(),
DeclKind::Int(i) => i.into(),
DeclKind::Float(f) => f.into(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ PASSED: typeof(Date) == 'function' [./Date.as:29]
PASSED: typeof(Date.prototype) == 'object' [./Date.as:30]
PASSED: typeof(Date.prototype.__proto__) == 'object' [./Date.as:31]
PASSED: Date.prototype.__proto__ == Object.prototype [./Date.as:32]
FAILED: expected: 'undefined' obtained: object [./Date.as:79]
PASSED: typeof(Date.__proto__) == 'undefined' [./Date.as:79]
PASSED: Date.UTC(2000,0,1).valueOf() == 946684800000.0 [./Date.as:86]
PASSED: d [./Date.as:91]
PASSED: d.getDate [./Date.as:94]
Expand Down Expand Up @@ -105,7 +105,7 @@ PASSED: d.getUTCSeconds() == 0 [./Date.as:253]
PASSED: d.getUTCMilliseconds() == 0 [./Date.as:254]
PASSED: d.getSeconds() == 0 [./Date.as:265]
PASSED: d.getMilliseconds() == 0 [./Date.as:266]
FAILED: expected: Date.toString() obtained: [./Date.as:269]
PASSED: Date.toLocaleString() == Date.toString() [./Date.as:269]
PASSED: d.valueOf() == 1 [./Date.as:274]
PASSED: d.valueOf() == 0 [./Date.as:276]
PASSED: d.valueOf() == 12345.0 [./Date.as:279]
Expand Down Expand Up @@ -310,6 +310,6 @@ PASSED: o.valueOf().toString() == "NaN" [./Date.as:703]
PASSED: o.valueOf().toString() == "NaN" [./Date.as:703]
PASSED: o.valueOf().toString() == "NaN" [./Date.as:703]
PASSED: o.valueOf().toString() == "NaN" [./Date.as:703]
#passed: 300
#failed: 7
#passed: 302
#failed: 5
#total tests run: 307
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ PASSED: typeof(Date) == 'function' [./Date.as:29]
PASSED: typeof(Date.prototype) == 'object' [./Date.as:30]
PASSED: typeof(Date.prototype.__proto__) == 'object' [./Date.as:31]
PASSED: Date.prototype.__proto__ == Object.prototype [./Date.as:32]
FAILED: expected: 'undefined' obtained: object [./Date.as:79]
PASSED: typeof(Date.__proto__) == 'undefined' [./Date.as:79]
PASSED: Date.UTC(2000,0,1).valueOf() == 946684800000.0 [./Date.as:86]
PASSED: d [./Date.as:91]
PASSED: d.getDate [./Date.as:94]
Expand Down Expand Up @@ -105,7 +105,7 @@ PASSED: d.getUTCSeconds() == 0 [./Date.as:253]
PASSED: d.getUTCMilliseconds() == 0 [./Date.as:254]
PASSED: d.getSeconds() == 0 [./Date.as:265]
PASSED: d.getMilliseconds() == 0 [./Date.as:266]
FAILED: expected: Date.toString() obtained: [./Date.as:269]
PASSED: Date.toLocaleString() == Date.toString() [./Date.as:269]
PASSED: d.valueOf() == 1 [./Date.as:274]
PASSED: d.valueOf() == 0 [./Date.as:276]
PASSED: d.valueOf() == 12345.0 [./Date.as:279]
Expand Down Expand Up @@ -310,6 +310,6 @@ PASSED: o.valueOf().toString() == "NaN" [./Date.as:703]
PASSED: o.valueOf().toString() == "NaN" [./Date.as:703]
PASSED: o.valueOf().toString() == "NaN" [./Date.as:703]
PASSED: o.valueOf().toString() == "NaN" [./Date.as:703]
#passed: 300
#failed: 7
#passed: 302
#failed: 5
#total tests run: 307
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ SWF5

[Function.as debug-22403-05c7ba106]
PASSED: getThisName.prototype.__proto__ == Object.prototype [./Function.as:47]
FAILED: expected: undefined obtained: [type Function] [./Function.as:53]
FAILED: expected: null obtained: [type Function] [./Function.as:54]
PASSED: getThisName == undefined [./Function.as:53]
PASSED: getThisName == null [./Function.as:54]
PASSED: getThisName != 0 [./Function.as:55]
PASSED: getThisName != 1 [./Function.as:56]
FAILED: ! isNaN(getThisName) [./Function.as:57]
PASSED: ! isNaN(getThisName) [./Function.as:57]
PASSED: getThisName != "" [./Function.as:58]
PASSED: getThisName != "[type Function]" [./Function.as:59]
PASSED: typeof(getThisName) == "function" [./Function.as:61]
FAILED: expected: 'undefined' obtained: function [./Function.as:150]
FAILED: expected: 'undefined' obtained: function [./Function.as:227]
PASSED: typeOf(getThisName.apply) == 'undefined' [./Function.as:150]
PASSED: typeOf(getThisName.call) == 'undefined' [./Function.as:227]
PASSED: typeOf(TestClass) == 'function' [./Function.as:243]
PASSED: typeOf(TestClass.prototype) == 'object' [./Function.as:256]
PASSED: testInstance != undefined [./Function.as:265]
Expand All @@ -23,7 +23,7 @@ PASSED: testInstance.__proto__ != undefined [./Function.as:275]
PASSED: testInstance.__proto__ == TestClass.prototype [./Function.as:276]
PASSED: testInstance instanceOf TestClass [./Function.as:277]
PASSED: testInstance instanceOf Object [./Function.as:278]
FAILED: expected: Function obtained: [type Function] [./Function.as:292]
PASSED: TestClass.constructor == Function [./Function.as:292]
PASSED: typeOf(TestClass.prototype.constructor) == 'function' [./Function.as:293]
PASSED: TestClass.prototype.constructor == TestClass [./Function.as:295]
PASSED: testInstance.__proto__.constructor == TestClass [./Function.as:296]
Expand Down Expand Up @@ -93,7 +93,7 @@ PASSED: argsCounter(a,b,c,d) == 4 [./Function.as:545]
PASSED: argsCounter([a,b]) == 1 [./Function.as:546]
PASSED: factorial(3) == 6 [./Function.as:551]
PASSED: factorial(4) == 24 [./Function.as:552]
FAILED: expected: 'undefined' obtained: function [./Function.as:589]
PASSED: typeof(textOutFunc.toString) == 'undefined' [./Function.as:589]
PASSED: textOutFunc.toString() == 'custom text rep' [./Function.as:595]
PASSED: typeof(textOutFunc.toString()) == 'string' [./Function.as:596]
custom text rep
Expand All @@ -103,7 +103,7 @@ PASSED: typeof(a.constructor) == 'function' [./Function.as:614]
PASSED: typeof(a.constructor) == 'function' [./Function.as:630]
PASSED: typeof(a.constructor) == 'function' [./Function.as:646]
PASSED: typeof(Email.prototype.__constructor__) == 'undefined' [./Function.as:683]
FAILED: expected: undefined obtained: [type Function] [./Function.as:689]
PASSED: Email.constructor.toString() == undefined [./Function.as:689]
PASSED: Function == undefined [./Function.as:690]
PASSED: typeof(Email.constructor) == 'function' [./Function.as:692]
PASSED: typeof(Email.constructor.constructor) == 'function' [./Function.as:693]
Expand Down Expand Up @@ -135,7 +135,7 @@ PASSED: delete o.sub.getThis [./Function.as:788]
PASSED: typeof(ret) == 'object' [./Function.as:793]
PASSED: getThis() == o [./Function.as:794]
PASSED: typeof(ret) == 'object' [./Function.as:802]
FAILED: ret == testInFunctionContext [./Function.as:804]
PASSED: ret == testInFunctionContext [./Function.as:804]
PASSED: ret != this [./Function.as:808]
PASSED: typeof(ret) == 'object' [./Function.as:814]
PASSED: ret == o [./Function.as:815]
Expand All @@ -153,6 +153,6 @@ PASSED: testvar2 == 8 [./Function.as:1016]
PASSED: testvar3 == 7 [./Function.as:1017]
PASSED: a.count == 2 [./Function.as:1032]
PASSED: b.count == 1 [./Function.as:1033]
#passed: 130
#failed: 20
#passed: 139
#failed: 11
#total tests run: 150
Loading
Loading