Skip to content

Commit 08b9de8

Browse files
committed
avm1: Fix some edge cases in super logic
- `super` accesses shouldn't coerce primitives to objects while crawling the prototype chain; - `super()` calls shouldn't call `__resolve` when resolving the `__constructor__` property.
1 parent cb781b8 commit 08b9de8

File tree

6 files changed

+183
-18
lines changed

6 files changed

+183
-18
lines changed

core/src/avm1/object.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ impl<'gc> Object<'gc> {
173173
} else {
174174
(self, Value::Object(self))
175175
};
176-
match search_prototype(proto, name.into(), activation, this, is_slash_path)? {
176+
match search_prototype(proto, name.into(), activation, this, is_slash_path, true)? {
177177
Some((value, _depth)) => Ok(value),
178178
None => Ok(Value::Undefined),
179179
}
@@ -280,7 +280,7 @@ impl<'gc> Object<'gc> {
280280
}
281281

282282
let (method, depth) =
283-
match search_prototype(Value::Object(self), name, activation, self, false)? {
283+
match search_prototype(Value::Object(self), name, activation, self, false, true)? {
284284
Some((Value::Object(method), depth)) => (method, depth),
285285
_ => return Ok(Value::Undefined),
286286
};
@@ -395,6 +395,7 @@ pub fn search_prototype<'gc>(
395395
activation: &mut Activation<'_, 'gc>,
396396
this: Object<'gc>,
397397
is_slash_path: bool,
398+
call_resolve_fn: bool,
398399
) -> Result<Option<(Value<'gc>, u8)>, Error<'gc>> {
399400
let mut depth = 0;
400401
let orig_proto = proto;
@@ -432,9 +433,12 @@ pub fn search_prototype<'gc>(
432433
depth += 1;
433434
}
434435

435-
if let Some(resolve) = find_resolve_method(orig_proto, activation)? {
436-
let result = resolve.call(istr!("__resolve"), activation, this.into(), &[name.into()])?;
437-
return Ok(Some((result, 0)));
436+
if call_resolve_fn {
437+
if let Some(resolve) = find_resolve_method(orig_proto, activation)? {
438+
let result =
439+
resolve.call(istr!("__resolve"), activation, this.into(), &[name.into()])?;
440+
return Ok(Some((result, 0)));
441+
}
438442
}
439443

440444
Ok(None)

core/src/avm1/object/super_object.rs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,22 @@ impl<'gc> SuperObject<'gc> {
5656
self.depth
5757
}
5858

59-
pub(super) fn base_proto(&self, activation: &mut Activation<'_, 'gc>) -> Object<'gc> {
59+
fn base_proto(&self, activation: &mut Activation<'_, 'gc>) -> Option<Object<'gc>> {
6060
let mut proto = self.this();
6161
for _ in 0..self.depth() {
62-
proto = proto.proto(activation).coerce_to_object(activation);
62+
match proto.proto(activation) {
63+
Value::Object(p) => proto = p,
64+
_ => return None,
65+
}
6366
}
64-
proto
67+
Some(proto)
6568
}
6669

6770
pub(super) fn proto(&self, activation: &mut Activation<'_, 'gc>) -> Value<'gc> {
68-
self.base_proto(activation).proto(activation)
71+
match self.base_proto(activation) {
72+
Some(p) => p.proto(activation),
73+
None => Value::Undefined,
74+
}
6975
}
7076

7177
pub(super) fn call(
@@ -74,10 +80,16 @@ impl<'gc> SuperObject<'gc> {
7480
activation: &mut Activation<'_, 'gc>,
7581
args: &[Value<'gc>],
7682
) -> Result<Value<'gc>, Error<'gc>> {
77-
let constructor = self
78-
.base_proto(activation)
79-
.get(istr!("__constructor__"), activation)?
80-
.coerce_to_object(activation);
83+
let Some(proto) = self.base_proto(activation) else {
84+
return Ok(Value::Undefined);
85+
};
86+
87+
let constructor = istr!("__constructor__");
88+
let Some((Value::Object(constructor), _depth)) =
89+
search_prototype(proto.into(), constructor, activation, proto, false, false)?
90+
else {
91+
return Ok(Value::Undefined);
92+
};
8193

8294
let NativeObject::Function(constr) = constructor.native() else {
8395
return Ok(Value::Undefined);
@@ -102,11 +114,11 @@ impl<'gc> SuperObject<'gc> {
102114
reason: ExecutionReason,
103115
) -> Result<Value<'gc>, Error<'gc>> {
104116
let this = self.this();
105-
let (method, depth) =
106-
match search_prototype(self.proto(activation), name, activation, this, false)? {
107-
Some((Value::Object(method), depth)) => (method, depth),
108-
_ => return Ok(Value::Undefined),
109-
};
117+
let Some((Value::Object(method), depth)) =
118+
search_prototype(self.proto(activation), name, activation, this, false, true)?
119+
else {
120+
return Ok(Value::Undefined);
121+
};
110122

111123
match method.as_function() {
112124
Some(exec) => exec.exec(
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Compile with:
2+
// mtasc -main -header 200:150:30 Test.as -swf test.swf -version 8
3+
class Test {
4+
5+
static function main(current) {
6+
var obj;
7+
8+
trace("#1: `super.foobar()` never does primitive-to-object coercions");
9+
10+
String.prototype.foobar = function() {
11+
trace("String.prototype.foobar called!");
12+
return "string-foobar";
13+
};
14+
15+
current.__proto__ = {
16+
foobar: function() {
17+
trace("_root.__proto__.foobar called!");
18+
return "_root-foobar";
19+
}
20+
};
21+
22+
obj = {
23+
foobar: function() {
24+
trace("obj.foobar called!");
25+
trace("super.foobar(): " + super.foobar());
26+
return "obj-foobar";
27+
}
28+
};
29+
30+
trace("// obj.__proto__ = _root");
31+
obj.__proto__ = current;
32+
trace("obj.foobar(): " + obj.foobar());
33+
34+
trace("// obj.__proto__ = new String('hello')");
35+
obj.__proto__ = new String("hello");
36+
trace("obj.foobar(): " + obj.foobar());
37+
38+
39+
trace("// obj.__proto__ = 'hello'");
40+
obj.__proto__ = "hello";
41+
trace("obj.foobar(): " + obj.foobar());
42+
43+
trace("");
44+
trace("#2: `super()` never calls `__resolve`");
45+
46+
var __constructor__ = function() {
47+
trace("__constructor__ called!");
48+
return "constructed";
49+
};
50+
51+
trace("// obj.__constructor__ = ...");
52+
obj = {
53+
__constructor__ : __constructor__,
54+
foobar: function() {
55+
trace("obj.foobar called!");
56+
var zuper = super; // to bypass MTASC checks
57+
trace("super(): " + zuper());
58+
}
59+
};
60+
obj.foobar();
61+
62+
trace("// __proto__.__resolve = () => __constructor__");
63+
obj.__proto__ = {
64+
__resolve: function(name) {
65+
trace("__resolve called with: " + name);
66+
return __constructor__;
67+
}
68+
};
69+
obj.foobar();
70+
71+
// one extra level of nesting than strictly required.
72+
trace("// __proto__.__proto__.__constructor__ = ...");
73+
obj.__proto__.__proto__ = {
74+
__constructor__: __constructor__
75+
};
76+
obj.foobar();
77+
78+
79+
obj.__proto__.addProperty(
80+
"__constructor__",
81+
function() {
82+
trace("__constructor__ property called!");
83+
return __constructor__;
84+
},
85+
null
86+
);
87+
trace("// __proto__.addProperty('__constructor__', ...)");
88+
obj.foobar();
89+
90+
trace("// __proto__ = makeSuperWith(__proto__)");
91+
obj.__proto__ = makeSuperWith(obj.__proto__);
92+
obj.foobar();
93+
94+
trace("// (__proto__ = _root).__constructor__ = ...");
95+
(obj.__proto__ = _root).__constructor__ = __constructor__;
96+
obj.foobar();
97+
98+
99+
fscommand("quit");
100+
}
101+
102+
static function makeSuperWith(obj) {
103+
var helper = {
104+
__proto__: { __proto__: obj },
105+
getSuper: function() { return super; }
106+
};
107+
return helper.getSuper();
108+
}
109+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#1: `super.foobar()` never does primitive-to-object coercions
2+
// obj.__proto__ = _root
3+
obj.foobar called!
4+
super.foobar(): undefined
5+
obj.foobar(): obj-foobar
6+
// obj.__proto__ = new String('hello')
7+
obj.foobar called!
8+
String.prototype.foobar called!
9+
super.foobar(): string-foobar
10+
obj.foobar(): obj-foobar
11+
// obj.__proto__ = 'hello'
12+
obj.foobar called!
13+
super.foobar(): undefined
14+
obj.foobar(): obj-foobar
15+
16+
#2: `super()` never calls `__resolve`
17+
// obj.__constructor__ = ...
18+
obj.foobar called!
19+
super(): undefined
20+
// __proto__.__resolve = () => __constructor__
21+
obj.foobar called!
22+
super(): undefined
23+
// __proto__.__proto__.__constructor__ = ...
24+
obj.foobar called!
25+
__constructor__ called!
26+
super(): constructed
27+
// __proto__.addProperty('__constructor__', ...)
28+
obj.foobar called!
29+
__constructor__ property called!
30+
__constructor__ called!
31+
super(): constructed
32+
// __proto__ = makeSuperWith(__proto__)
33+
obj.foobar called!
34+
__constructor__ property called!
35+
__constructor__ called!
36+
super(): constructed
37+
// (__proto__ = _root).__constructor__ = ...
38+
obj.foobar called!
39+
super(): undefined
865 Bytes
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
num_frames = 1

0 commit comments

Comments
 (0)