Skip to content
Merged
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
14 changes: 9 additions & 5 deletions core/src/avm1/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ impl<'gc> Object<'gc> {
} else {
(self, Value::Object(self))
};
match search_prototype(proto, name.into(), activation, this, is_slash_path)? {
match search_prototype(proto, name.into(), activation, this, is_slash_path, true)? {
Some((value, _depth)) => Ok(value),
None => Ok(Value::Undefined),
}
Expand Down Expand Up @@ -280,7 +280,7 @@ impl<'gc> Object<'gc> {
}

let (method, depth) =
match search_prototype(Value::Object(self), name, activation, self, false)? {
match search_prototype(Value::Object(self), name, activation, self, false, true)? {
Some((Value::Object(method), depth)) => (method, depth),
_ => return Ok(Value::Undefined),
};
Expand Down Expand Up @@ -395,6 +395,7 @@ pub fn search_prototype<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
is_slash_path: bool,
call_resolve_fn: bool,
) -> Result<Option<(Value<'gc>, u8)>, Error<'gc>> {
let mut depth = 0;
let orig_proto = proto;
Expand Down Expand Up @@ -432,9 +433,12 @@ pub fn search_prototype<'gc>(
depth += 1;
}

if let Some(resolve) = find_resolve_method(orig_proto, activation)? {
let result = resolve.call(istr!("__resolve"), activation, this.into(), &[name.into()])?;
return Ok(Some((result, 0)));
if call_resolve_fn {
if let Some(resolve) = find_resolve_method(orig_proto, activation)? {
let result =
resolve.call(istr!("__resolve"), activation, this.into(), &[name.into()])?;
return Ok(Some((result, 0)));
}
}

Ok(None)
Expand Down
38 changes: 25 additions & 13 deletions core/src/avm1/object/super_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,22 @@ impl<'gc> SuperObject<'gc> {
self.depth
}

pub(super) fn base_proto(&self, activation: &mut Activation<'_, 'gc>) -> Object<'gc> {
fn base_proto(&self, activation: &mut Activation<'_, 'gc>) -> Option<Object<'gc>> {
let mut proto = self.this();
for _ in 0..self.depth() {
proto = proto.proto(activation).coerce_to_object(activation);
match proto.proto(activation) {
Value::Object(p) => proto = p,
_ => return None,
}
}
proto
Some(proto)
}

pub(super) fn proto(&self, activation: &mut Activation<'_, 'gc>) -> Value<'gc> {
self.base_proto(activation).proto(activation)
match self.base_proto(activation) {
Some(p) => p.proto(activation),
None => Value::Undefined,
}
}

pub(super) fn call(
Expand All @@ -74,10 +80,16 @@ impl<'gc> SuperObject<'gc> {
activation: &mut Activation<'_, 'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let constructor = self
.base_proto(activation)
.get(istr!("__constructor__"), activation)?
.coerce_to_object(activation);
let Some(proto) = self.base_proto(activation) else {
return Ok(Value::Undefined);
};

let constructor = istr!("__constructor__");
let Some((Value::Object(constructor), _depth)) =
search_prototype(proto.into(), constructor, activation, proto, false, false)?
else {
return Ok(Value::Undefined);
};

let NativeObject::Function(constr) = constructor.native() else {
return Ok(Value::Undefined);
Expand All @@ -102,11 +114,11 @@ impl<'gc> SuperObject<'gc> {
reason: ExecutionReason,
) -> Result<Value<'gc>, Error<'gc>> {
let this = self.this();
let (method, depth) =
match search_prototype(self.proto(activation), name, activation, this, false)? {
Some((Value::Object(method), depth)) => (method, depth),
_ => return Ok(Value::Undefined),
};
let Some((Value::Object(method), depth)) =
search_prototype(self.proto(activation), name, activation, this, false, true)?
else {
return Ok(Value::Undefined);
};

match method.as_function() {
Some(exec) => exec.exec(
Expand Down
109 changes: 109 additions & 0 deletions tests/tests/swfs/avm1/super_edge_cases/Test.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Compile with:
// mtasc -main -header 200:150:30 Test.as -swf test.swf -version 8
class Test {

static function main(current) {
var obj;

trace("#1: `super.foobar()` never does primitive-to-object coercions");

String.prototype.foobar = function() {
trace("String.prototype.foobar called!");
return "string-foobar";
};

current.__proto__ = {
foobar: function() {
trace("_root.__proto__.foobar called!");
return "_root-foobar";
}
};

obj = {
foobar: function() {
trace("obj.foobar called!");
trace("super.foobar(): " + super.foobar());
return "obj-foobar";
}
};

trace("// obj.__proto__ = _root");
obj.__proto__ = current;
trace("obj.foobar(): " + obj.foobar());

trace("// obj.__proto__ = new String('hello')");
obj.__proto__ = new String("hello");
trace("obj.foobar(): " + obj.foobar());


trace("// obj.__proto__ = 'hello'");
obj.__proto__ = "hello";
trace("obj.foobar(): " + obj.foobar());

trace("");
trace("#2: `super()` never calls `__resolve`");

var __constructor__ = function() {
trace("__constructor__ called!");
return "constructed";
};

trace("// obj.__constructor__ = ...");
obj = {
__constructor__ : __constructor__,
foobar: function() {
trace("obj.foobar called!");
var zuper = super; // to bypass MTASC checks
trace("super(): " + zuper());
}
};
obj.foobar();

trace("// __proto__.__resolve = () => __constructor__");
obj.__proto__ = {
__resolve: function(name) {
trace("__resolve called with: " + name);
return __constructor__;
}
};
obj.foobar();

// one extra level of nesting than strictly required.
trace("// __proto__.__proto__.__constructor__ = ...");
obj.__proto__.__proto__ = {
__constructor__: __constructor__
};
obj.foobar();


obj.__proto__.addProperty(
"__constructor__",
function() {
trace("__constructor__ property called!");
return __constructor__;
},
null
);
trace("// __proto__.addProperty('__constructor__', ...)");
obj.foobar();

trace("// __proto__ = makeSuperWith(__proto__)");
obj.__proto__ = makeSuperWith(obj.__proto__);
obj.foobar();

trace("// (__proto__ = _root).__constructor__ = ...");
(obj.__proto__ = _root).__constructor__ = __constructor__;
obj.foobar();


fscommand("quit");
}

static function makeSuperWith(obj) {
var helper = {
__proto__: { __proto__: obj },
getSuper: function() { return super; }
};
return helper.getSuper();
}
}
39 changes: 39 additions & 0 deletions tests/tests/swfs/avm1/super_edge_cases/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#1: `super.foobar()` never does primitive-to-object coercions
// obj.__proto__ = _root
obj.foobar called!
super.foobar(): undefined
obj.foobar(): obj-foobar
// obj.__proto__ = new String('hello')
obj.foobar called!
String.prototype.foobar called!
super.foobar(): string-foobar
obj.foobar(): obj-foobar
// obj.__proto__ = 'hello'
obj.foobar called!
super.foobar(): undefined
obj.foobar(): obj-foobar

#2: `super()` never calls `__resolve`
// obj.__constructor__ = ...
obj.foobar called!
super(): undefined
// __proto__.__resolve = () => __constructor__
obj.foobar called!
super(): undefined
// __proto__.__proto__.__constructor__ = ...
obj.foobar called!
__constructor__ called!
super(): constructed
// __proto__.addProperty('__constructor__', ...)
obj.foobar called!
__constructor__ property called!
__constructor__ called!
super(): constructed
// __proto__ = makeSuperWith(__proto__)
obj.foobar called!
__constructor__ property called!
__constructor__ called!
super(): constructed
// (__proto__ = _root).__constructor__ = ...
obj.foobar called!
super(): undefined
Binary file added tests/tests/swfs/avm1/super_edge_cases/test.swf
Binary file not shown.
1 change: 1 addition & 0 deletions tests/tests/swfs/avm1/super_edge_cases/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
num_frames = 1
Loading