Skip to content

Commit cdd53bd

Browse files
committed
avm1: Lookup classes in global scope in primitive-to-object coercions...
...instead of using `SystemPrototypes`. This is what FP does, as demonstrated by the added test (`avm1/coerce_to_object_monkeypatch`). (we're not 100% correct yet, but this is fairly close)
1 parent 7e8a9de commit cdd53bd

File tree

27 files changed

+596
-170
lines changed

27 files changed

+596
-170
lines changed

core/common/src/avm_string/common.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ define_common_strings! {
8484
str_bold: b"bold",
8585
str_boldItalic: b"boldItalic",
8686
str_boolean: b"boolean",
87+
str_Boolean: b"Boolean",
8788
str_broadcastMessage: b"broadcastMessage",
8889
str_builtInItems: b"builtInItems",
8990
str_bytesLoaded: b"bytesLoaded",
@@ -187,6 +188,7 @@ define_common_strings! {
187188
str_normal: b"normal",
188189
str_null: b"null",
189190
str_number: b"number",
191+
str_Number: b"Number",
190192
str_object: b"object",
191193
str_onCancel: b"onCancel",
192194
str_onChanged: b"onChanged",
@@ -272,6 +274,7 @@ define_common_strings! {
272274
str_standardExtended: b"standardExtended",
273275
str_status: b"status",
274276
str_string: b"string",
277+
str_String: b"String",
275278
str_subpixel: b"subpixel",
276279
str_subtract: b"subtract",
277280
str_success: b"success",

core/src/avm1/globals.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -504,9 +504,6 @@ pub struct SystemPrototypes<'gc> {
504504
pub array_constructor: Object<'gc>,
505505
pub xml_node_constructor: Object<'gc>,
506506
pub xml_constructor: Object<'gc>,
507-
pub string: Object<'gc>,
508-
pub number: Object<'gc>,
509-
pub boolean: Object<'gc>,
510507
pub matrix_constructor: Object<'gc>,
511508
pub point_constructor: Object<'gc>,
512509
pub rectangle: Object<'gc>,
@@ -744,9 +741,6 @@ pub fn create_globals<'gc>(
744741
array_constructor: array.constr,
745742
xml_node_constructor: xmlnode.constr,
746743
xml_constructor: xml.constr,
747-
string: string.proto,
748-
number: number.proto,
749-
boolean: boolean.proto,
750744
matrix_constructor: matrix.constr,
751745
point_constructor: point.constr,
752746
rectangle: rectangle.proto,

core/src/avm1/value.rs

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,22 @@ impl<'gc> Value<'gc> {
470470
}
471471
}
472472

473+
/// Coerce `self` to a script object, unconditionally.
474+
///
475+
/// If `self` isn't coercible, returns a fresh bare object instead.
476+
/// [MOULINS]: I suspect that most (if not all) usages of this method are incorrect,
477+
/// but we have too much code already using it to remove it right now.
478+
pub fn coerce_to_object_or_bare(
479+
self,
480+
activation: &mut Activation<'_, 'gc>,
481+
) -> Result<Object<'gc>, Error<'gc>> {
482+
if let Some(obj) = self.coerce_to_object(activation)? {
483+
Ok(obj)
484+
} else {
485+
Ok(Object::new_without_proto(activation.gc()))
486+
}
487+
}
488+
473489
/// Coerce `self` to a script object, boxing primitives if necessary.
474490
///
475491
/// This can fail in two different ways:
@@ -480,46 +496,36 @@ impl<'gc> Value<'gc> {
480496
self,
481497
activation: &mut Activation<'_, 'gc>,
482498
) -> Result<Option<Object<'gc>>, Error<'gc>> {
483-
let proto = match self {
499+
let class_name = match self {
484500
// Object coerce to themselves...
485501
Value::Object(obj) => return Ok(Some(obj)),
486502
// ...and MovieClips coerce to their underlying object.
487503
Value::MovieClip(mcr) => return Ok(mcr.coerce_to_object(activation)),
488504
// `null` and `undefined` cannot be coerced.
489505
Value::Null | Value::Undefined => return Ok(None),
490-
// Primitives need to be boxed, so select a suitable prototype.
491-
Value::Bool(_) => activation.prototypes().boolean,
492-
Value::Number(_) => activation.prototypes().number,
493-
Value::String(_) => activation.prototypes().string,
506+
// Primitives need to be boxed, so select a suitable class.
507+
Value::Bool(_) => istr!("Boolean"),
508+
Value::Number(_) => istr!("Number"),
509+
Value::String(_) => istr!("String"),
494510
};
495511

496-
let obj = Object::new(&activation.context.strings, Some(proto));
497-
498-
// Constructor populates the boxed object with the value.
499-
use crate::avm1::{function::NativeFunction, globals};
500-
let constr: NativeFunction = match self {
501-
Value::Bool(_) => globals::boolean::constructor,
502-
Value::Number(_) => globals::number::constructor,
503-
Value::String(_) => globals::string::constructor,
504-
_ => |_, _, _| Ok(Value::Undefined),
512+
// Fetch the constructor from the global scope...
513+
let Some(Value::Object(class)) = activation
514+
.global_object()
515+
.get_opt(class_name, activation, false)?
516+
else {
517+
// No valid constructor, give up.
518+
return Ok(None);
505519
};
506520

507-
constr(activation, obj, &[self]).map(|_| Some(obj))
508-
}
509-
510-
/// Coerce `self` to a script object, unconditionally.
511-
///
512-
/// If `self` isn't coercible, returns a fresh bare object instead.
513-
/// [MOULINS]: I suspect that most (if not all) usages of this method are incorrect,
514-
/// but we have too much code already using it to remove it right now.
515-
pub fn coerce_to_object_or_bare(
516-
self,
517-
activation: &mut Activation<'_, 'gc>,
518-
) -> Result<Object<'gc>, Error<'gc>> {
519-
if let Some(obj) = self.coerce_to_object(activation)? {
520-
Ok(obj)
521+
// ...and use it to box the primitive.
522+
if let Value::Object(obj) = class.construct(activation, &[self])? {
523+
// TODO(moulins): do MovieClips count as objects here?
524+
Ok(Some(obj))
521525
} else {
522-
Ok(Object::new_without_proto(activation.gc()))
526+
// The constructor returned a non-object (this can happen
527+
// with some native constructors), give up.
528+
Ok(None)
523529
}
524530
}
525531

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class MyBoolean extends Boolean {
2+
var className;
3+
4+
function MyBoolean(val) {
5+
super(val);
6+
trace(this.className + "(" + val + ") constructor called!");
7+
return "fake";
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class MyNumber extends Number {
2+
var className;
3+
4+
function MyNumber(val) {
5+
super(val);
6+
trace(this.className + "(" + val + ") constructor called!");
7+
return "fake";
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class MyString extends String {
2+
var className;
3+
4+
function MyString(val) {
5+
super(val);
6+
trace(this.className + "(" + val + ") constructor called!");
7+
return "fake";
8+
}
9+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Compile with:
2+
// mtasc -main -header 200:150:30 Test.as -swf test.swf -version 8
3+
class Test {
4+
5+
6+
static function main(current) {
7+
Number.prototype.className = "Number";
8+
Boolean.prototype.className = "Boolean";
9+
String.prototype.className = "String";
10+
flash.display.BitmapData.prototype.className = "BitmapData";
11+
_global.className = "<_global>";
12+
13+
MyNumber.prototype.className = "MyNumber";
14+
MyBoolean.prototype.className = "MyBoolean";
15+
MyString.prototype.className = "MyString";
16+
17+
trace("Monkey-patching-globals...");
18+
// __proto__ should work, so let's test it.
19+
var proto = _global.__proto__ = { __proto__: _global.__proto__ };
20+
delete _global.Boolean;
21+
delete _global.Number;
22+
delete _global.String;
23+
24+
addGetter(proto, "Boolean", MyBoolean);
25+
addGetter(proto, "Number", MyNumber);
26+
addGetter(proto, "String", MyString);
27+
28+
trace("Testing primitive coercions...");
29+
trace("");
30+
31+
testCoerced(true);
32+
testCoerced(42);
33+
testCoerced("hello");
34+
35+
trace('// "world".length');
36+
trace("result: " + "world".length);
37+
38+
var v = "callme";
39+
trace('// "callme"()');
40+
trace("result: " + v());
41+
trace('// new "callme"()');
42+
trace("result: " + new v());
43+
44+
trace("");
45+
trace("Testing loookup logic...");
46+
trace("");
47+
48+
testCoerced(undefined);
49+
testCoerced(null);
50+
51+
trace("// delete Number; __resolve = () => MyNumber");
52+
delete _global.Number;
53+
delete proto.Number;
54+
_global.__resolve = function(name) {
55+
trace("__resolve(" + name + ") called!");
56+
return MyNumber;
57+
};
58+
59+
testCoerced(42);
60+
61+
trace("// Number = MyNumber");
62+
_global.Number = MyNumber;
63+
testCoerced(42);
64+
65+
trace("// Number.prototype = true");
66+
_global.Number.prototype = true;
67+
testCoerced(42);
68+
69+
trace('// Number = "some string"');
70+
_global.Number = "some string";
71+
testCoerced(42);
72+
73+
trace('// Number = BitmapData');
74+
_global.Number = flash.display.BitmapData;
75+
testCoerced(42);
76+
77+
trace('// Number = {}');
78+
_global.Number = {};
79+
testCoerced(42);
80+
81+
trace('// Number = Boolean');
82+
_global.Number = Boolean;
83+
testCoerced(42);
84+
85+
trace("Done!");
86+
fscommand("quit");
87+
}
88+
89+
90+
static function addGetter(obj, name, val) {
91+
var getter = function() {
92+
trace(name + " getter called!");
93+
return val;
94+
};
95+
Object.addProperty.call(obj, name, getter, null);
96+
}
97+
98+
static var TEST_COERCED_INNER = function(val) {
99+
trace(" coerced: " + this);
100+
if (this === _global) {
101+
trace(" is _global!");
102+
} else {
103+
trace(" typeof: " + typeof this);
104+
trace(" typeof __proto__: " + typeof this.__proto__);
105+
trace(" className: " + this.className);
106+
}
107+
};
108+
109+
static function testCoerced(val) {
110+
trace("// " + val + " coerced to 'this'");
111+
TEST_COERCED_INNER.call(val, val);
112+
trace("// " + val + ".className");
113+
trace(" className: " + val.className);
114+
trace("");
115+
}
116+
117+
static function monkeyPatch(obj, name, cls) {
118+
// Set both as a property and a getter, for distinguishing what supports
119+
// getters and what doesn't.
120+
obj[name] = cls;
121+
var getter = function() {
122+
trace(name + " getter called!");
123+
return cls;
124+
};
125+
Object.prototype.addProperty.call(obj, name, getter, null);
126+
}
127+
128+
}

0 commit comments

Comments
 (0)