@swf2es/runtime 0.1.16 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/avm2/amf.d.ts +1 -1
  2. package/dist/avm2/amf.d.ts.map +1 -1
  3. package/dist/avm2/amf.js +4 -3
  4. package/dist/avm2/amf.js.map +1 -1
  5. package/dist/avm2/as3-types.d.ts +25 -0
  6. package/dist/avm2/as3-types.d.ts.map +1 -0
  7. package/dist/avm2/as3-types.js +9 -0
  8. package/dist/avm2/as3-types.js.map +1 -0
  9. package/dist/avm2/builtin-types.generated.d.ts +553 -0
  10. package/dist/avm2/builtin-types.generated.d.ts.map +1 -0
  11. package/dist/avm2/builtin-types.generated.js +11 -0
  12. package/dist/avm2/builtin-types.generated.js.map +1 -0
  13. package/dist/avm2/classes.d.ts.map +1 -1
  14. package/dist/avm2/classes.js +10 -3
  15. package/dist/avm2/classes.js.map +1 -1
  16. package/dist/avm2/errors.d.ts.map +1 -1
  17. package/dist/avm2/errors.js +3 -1
  18. package/dist/avm2/errors.js.map +1 -1
  19. package/dist/avm2/index.d.ts +3 -0
  20. package/dist/avm2/index.d.ts.map +1 -1
  21. package/dist/avm2/index.js +2 -0
  22. package/dist/avm2/index.js.map +1 -1
  23. package/dist/avm2/natives/array.js +1 -1
  24. package/dist/avm2/natives/array.js.map +1 -1
  25. package/dist/avm2/natives/bytearray.d.ts.map +1 -1
  26. package/dist/avm2/natives/bytearray.js +6 -11
  27. package/dist/avm2/natives/bytearray.js.map +1 -1
  28. package/dist/avm2/natives/describe.js +6 -1
  29. package/dist/avm2/natives/describe.js.map +1 -1
  30. package/dist/avm2/natives/object.d.ts.map +1 -1
  31. package/dist/avm2/natives/object.js +5 -2
  32. package/dist/avm2/natives/object.js.map +1 -1
  33. package/dist/avm2/natives/vector.js +1 -1
  34. package/dist/avm2/natives/vector.js.map +1 -1
  35. package/dist/avm2/runtime.d.ts +39 -1
  36. package/dist/avm2/runtime.d.ts.map +1 -1
  37. package/dist/avm2/runtime.js +106 -20
  38. package/dist/avm2/runtime.js.map +1 -1
  39. package/dist/avm2/traits.d.ts +6 -0
  40. package/dist/avm2/traits.d.ts.map +1 -1
  41. package/dist/avm2/traits.js +17 -0
  42. package/dist/avm2/traits.js.map +1 -1
  43. package/package.json +2 -2
  44. package/src/avm2/amf.ts +4 -3
  45. package/src/avm2/as3-types.ts +41 -0
  46. package/src/avm2/builtin-types.generated.ts +619 -0
  47. package/src/avm2/classes.ts +10 -3
  48. package/src/avm2/errors.ts +3 -1
  49. package/src/avm2/index.ts +5 -0
  50. package/src/avm2/natives/array.ts +1 -1
  51. package/src/avm2/natives/bytearray.ts +6 -14
  52. package/src/avm2/natives/describe.ts +8 -1
  53. package/src/avm2/natives/object.ts +5 -2
  54. package/src/avm2/natives/vector.ts +1 -1
  55. package/src/avm2/runtime.ts +127 -27
  56. package/src/avm2/traits.ts +20 -0
@@ -760,13 +760,11 @@ export function byteArrayNatives(rt: Runtime): Natives {
760
760
  return rt.defaultObjectEncoding;
761
761
  }
762
762
 
763
+ // Unchecked, unlike an instance's: avmshell and Flash take any uint, a
764
+ // new ByteArray starts with it, and writeObject reads an encoding below
765
+ // 3 as AMF0 (#1508 in avmshell), any other as AMF3.
763
766
  static set defaultObjectEncoding(v: Value) {
764
- const e = rt.toUint(v);
765
- if (e !== kAMF0 && e !== kAMF3) {
766
- throw rt.error("ArgumentError", 2008, "objectEncoding");
767
- }
768
-
769
- rt.defaultObjectEncoding = e;
767
+ rt.defaultObjectEncoding = rt.toUint(v);
770
768
  }
771
769
 
772
770
  get length() {
@@ -1215,13 +1213,11 @@ export function byteArrayNatives(rt: Runtime): Natives {
1215
1213
  },
1216
1214
  };
1217
1215
 
1216
+ // AMF3 whatever their objectEncoding, as ObjectOutputObject::writeObject
1217
+ // and ObjectInputObject::readObject: avmshell and Flash alike.
1218
1218
  class ObjectOutputNatives {
1219
1219
  writeObject(this: AsObject, v: Value): void {
1220
1220
  const stream: Writer = this.$amf;
1221
- if (stream.objectEncoding !== 3) {
1222
- throw rt.unsupported("AMF0");
1223
- }
1224
-
1225
1221
  stream.value(v);
1226
1222
  }
1227
1223
  }
@@ -1229,10 +1225,6 @@ export function byteArrayNatives(rt: Runtime): Natives {
1229
1225
  class ObjectInputNatives {
1230
1226
  readObject(this: AsObject): Value {
1231
1227
  const stream: Reader = this.$amf;
1232
- if (stream.objectEncoding !== 3) {
1233
- throw rt.unsupported("AMF0");
1234
- }
1235
-
1236
1228
  return stream.value();
1237
1229
  }
1238
1230
 
@@ -121,7 +121,14 @@ function chooseTraits(rt: Runtime, v: Value): Traits | string {
121
121
  return "null";
122
122
  }
123
123
 
124
- if (typeof v === "number" && (v | 0) === v && v >= -(1 << 28) && v < 1 << 28) {
124
+ // An int atom: -0 is a double.
125
+ if (
126
+ typeof v === "number" &&
127
+ (v | 0) === v &&
128
+ v >= -(1 << 28) &&
129
+ v < 1 << 28 &&
130
+ !Object.is(v, -0)
131
+ ) {
125
132
  return rt.traitsOfType("int");
126
133
  }
127
134
 
@@ -191,9 +191,12 @@ function shortName(qualified: string): string {
191
191
 
192
192
  export function qualifiedClassName(rt: Runtime, v: Value): string {
193
193
  switch (typeof v) {
194
- // As TypeDescriber::chooseTraits: an int is one that fits avmplus' 29-bit int atom.
194
+ // As TypeDescriber::chooseTraits: an int is one that fits avmplus' 29-bit
195
+ // int atom, which -0, always a double, never is.
195
196
  case "number":
196
- return (v | 0) === v && v >= -(1 << 28) && v < 1 << 28 ? "int" : "Number";
197
+ return (v | 0) === v && v >= -(1 << 28) && v < 1 << 28 && !Object.is(v, -0)
198
+ ? "int"
199
+ : "Number";
197
200
  case "string":
198
201
  return "String";
199
202
  case "boolean":
@@ -330,7 +330,7 @@ for (const [kind, convert, fill, elementsOf, store] of VECTORS) {
330
330
 
331
331
  const source = args[0];
332
332
  if (!isObject(source)) {
333
- throw rt.error("TypeError", 1034, rt.describe(source), cls.$it.name);
333
+ throw rt.error("TypeError", 1034, rt.describe(source), cls.$it.errorName);
334
334
  }
335
335
 
336
336
  if (source.$traits === cls.$it) {
@@ -14,6 +14,7 @@
14
14
  import { API_VERSION, checkApiVersion } from "@swf2es/format";
15
15
  import { AmfClasses } from "./amf-classes.js";
16
16
  import { ArrayConversion } from "./array-conversion.js";
17
+ import type { MethodName, PropertyName, Ref, WritableName } from "./as3-types.js";
17
18
  import { fillCache } from "./cache-fill.js";
18
19
  import { newClass, specializeVector, withId } from "./classes.js";
19
20
  import { classNamed, classNamedByCode, definingAbc, definitionNamed } from "./definitions.js";
@@ -168,6 +169,21 @@ function hasWith(scopes: Value[], w: number): boolean {
168
169
  }
169
170
 
170
171
  /** The value of `o`'s dynamic property `name`, or NOT_FOUND. */
172
+ /** Whether k is an int atom avmplus can take as a uint: an integer of 0 to 2^32-1, -0 not, as it is a double's. */
173
+ function uintAtom(k: Value): k is number {
174
+ return typeof k === "number" && k >>> 0 === k && (k !== 0 || 1 / k > 0);
175
+ }
176
+
177
+ /** Whether v is an object's atom to avmplus: not null, nor a Namespace or float, whose atoms are their own. */
178
+ function isScriptObject(v: Value): boolean {
179
+ return (
180
+ typeof v === "object" && v !== null && !(v instanceof Namespace) && !(v instanceof FloatValue)
181
+ );
182
+ }
183
+
184
+ /** The name avmplus' getUintProperty looks a number up by: public, at every version. */
185
+ const ELEMENT_NAME = new Multiname(CONSTANT_MultinameL, [publicNs], [255], null, false);
186
+
171
187
  function dynamicValue(o: AsObject, name: string): Value {
172
188
  const d: Map<string, Value> | null | undefined = o.$d;
173
189
  if (!d) {
@@ -244,6 +260,13 @@ export class Runtime {
244
260
  /** The builtin classes' traits, made before their classes so the bootstrap can refer to them. */
245
261
  readonly objectTraits: Traits;
246
262
  readonly classTraits: Traits;
263
+ /**
264
+ * What the super operations of a script's methods and of a class's
265
+ * static ones look in, as avmplus' script traits extend Object and a
266
+ * class object's traits Class: their `super` is that class's instance.
267
+ */
268
+ readonly scriptSuper: AsObject;
269
+ readonly staticSuper: AsObject;
247
270
  readonly functionTraits: Traits;
248
271
  /**
249
272
  * Method closures, by receiver and method, so that o.f === o.f; by the
@@ -299,6 +322,8 @@ export class Runtime {
299
322
  this.objectTraits.dynamic = true;
300
323
  this.objectTraits.testsValues = true;
301
324
  this.classTraits = new Traits("Class", this.objectTraits);
325
+ this.scriptSuper = { $it: this.objectTraits };
326
+ this.staticSuper = { $it: this.classTraits };
302
327
  this.functionTraits = new Traits("Function", this.objectTraits);
303
328
  this.functionTraits.dynamic = true;
304
329
  this.natives = typeof natives === "function" ? natives(this) : natives;
@@ -640,14 +665,9 @@ export class Runtime {
640
665
  // indexes the scopes their closures capture: the global object is
641
666
  // only what they push.
642
667
  for (const [d, factory, id, , params] of script.desc.traits.methods) {
643
- installMethod(
644
- this,
645
- traits.methodHome(3),
646
- d,
647
- withId(factory(this.empty, null), id),
648
- factory,
649
- params,
650
- );
668
+ const method = withId(factory(this.empty, this.scriptSuper), id);
669
+ this.keepStatic(factory, method);
670
+ installMethod(this, traits.methodHome(3), d, method, factory, params);
651
671
  }
652
672
 
653
673
  // Caches keep the methods.
@@ -677,7 +697,7 @@ export class Runtime {
677
697
  if (script.state === 0 || (retry && script.state === 3)) {
678
698
  script.state = 1;
679
699
  try {
680
- script.desc.init(this.empty, null).call(g);
700
+ script.desc.init(this.empty, this.scriptSuper).call(g);
681
701
  } catch (e) {
682
702
  script.state = 3;
683
703
  throw e;
@@ -1197,7 +1217,7 @@ export class Runtime {
1197
1217
  "ReferenceError",
1198
1218
  name === null && nsset ? 1081 : 1069,
1199
1219
  mn.name ?? "*",
1200
- traits.name,
1220
+ traits.errorName,
1201
1221
  );
1202
1222
  }
1203
1223
 
@@ -1273,6 +1293,31 @@ export class Runtime {
1273
1293
  this.setProperty(o, this.indexName(mn, i), v);
1274
1294
  }
1275
1295
 
1296
+ /**
1297
+ * obj[k] by a runtime name with no public namespace, or an attribute's,
1298
+ * in a script's or a class's initializer, which avmplus interprets: its
1299
+ * getproperty reads an int key of at least 0 off an object by
1300
+ * getUintProperty, whatever namespaces the name has, where its JIT asks
1301
+ * for a public one (getIndexed). Any other key makes a name.
1302
+ */
1303
+ getInterpreted(o: Value, mn: Multiname, k: Value): Value {
1304
+ if (uintAtom(k) && isScriptObject(o)) {
1305
+ return this.getIndexed(o, ELEMENT_NAME, k);
1306
+ }
1307
+
1308
+ return this.getProperty(o, this.runtimeName(mn, k));
1309
+ }
1310
+
1311
+ /** obj[k] = v as getInterpreted reads it, as avmplus' interpreter's setproperty. */
1312
+ setInterpreted(o: Value, mn: Multiname, k: Value, v: Value): void {
1313
+ if (uintAtom(k) && isScriptObject(o)) {
1314
+ this.setIndexed(o, ELEMENT_NAME, k, v);
1315
+ return;
1316
+ }
1317
+
1318
+ this.setProperty(o, this.runtimeName(mn, k), v);
1319
+ }
1320
+
1276
1321
  /**
1277
1322
  * obj[k] for a key whose type the compiler does not know, by a name with
1278
1323
  * no namespace from the stack, as avmplus' getprop_index and
@@ -1578,10 +1623,10 @@ export class Runtime {
1578
1623
  return o[slot];
1579
1624
  }
1580
1625
 
1581
- throw this.error("ReferenceError", 1077, mn.name ?? "*", traits.name);
1626
+ throw this.error("ReferenceError", 1077, mn.name ?? "*", traits.errorName);
1582
1627
  }
1583
1628
  default:
1584
- throw this.error("ReferenceError", 1077, mn.name ?? "*", traits.name);
1629
+ throw this.error("ReferenceError", 1077, mn.name ?? "*", traits.errorName);
1585
1630
  }
1586
1631
  }
1587
1632
 
@@ -1683,7 +1728,7 @@ export class Runtime {
1683
1728
  switch (b & 7) {
1684
1729
  case BIND_Const:
1685
1730
  if (!init) {
1686
- throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.name);
1731
+ throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.errorName);
1687
1732
  }
1688
1733
  o[slotKey(id)] = this.coerce(v, traits.slotType(id));
1689
1734
  return;
@@ -1695,9 +1740,9 @@ export class Runtime {
1695
1740
  (traits.proto[untypedSetKey(id + 1)] ?? traits.proto[methodKey(id + 1)]).call(o, v);
1696
1741
  return;
1697
1742
  case BIND_Method:
1698
- throw this.error("ReferenceError", 1037, mn.name ?? "*", traits.name);
1743
+ throw this.error("ReferenceError", 1037, mn.name ?? "*", traits.errorName);
1699
1744
  default:
1700
- throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.name);
1745
+ throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.errorName);
1701
1746
  }
1702
1747
  }
1703
1748
 
@@ -1722,7 +1767,7 @@ export class Runtime {
1722
1767
  }
1723
1768
  }
1724
1769
 
1725
- throw this.error("ReferenceError", 1056, mn.name ?? "*", traits.name);
1770
+ throw this.error("ReferenceError", 1056, mn.name ?? "*", traits.errorName);
1726
1771
  }
1727
1772
 
1728
1773
  initProperty(o: Value, mn: Multiname, v: Value): void {
@@ -1741,7 +1786,7 @@ export class Runtime {
1741
1786
 
1742
1787
  // A primitive's property cannot be deleted, as MethodEnv's delproperty has it.
1743
1788
  if (typeof o !== "object" || o instanceof Namespace) {
1744
- throw this.error("ReferenceError", 1120, mn.name ?? "*", this.traitsOf(o).name);
1789
+ throw this.error("ReferenceError", 1120, mn.name ?? "*", this.traitsOf(o).errorName);
1745
1790
  }
1746
1791
 
1747
1792
  // E4X 11.3.1: delete x[list] is a TypeError, as in delete x.a.(b == 1).
@@ -1958,6 +2003,36 @@ export class Runtime {
1958
2003
  return qname(publicNs, this.nameString(name));
1959
2004
  }
1960
2005
 
2006
+ // Natives' typed reach into AS3 objects (as3-types.ts): getProperty,
2007
+ // setProperty and callProperty of the public name, the lookup natives
2008
+ // made before, with the name checked against the class's declarations
2009
+ // when compiled.
2010
+
2011
+ /** `o`'s public property `name`: a variable, constant or accessor its class declares. */
2012
+ prop<T, K extends PropertyName<T>>(o: Ref<T>, name: K): T[K] {
2013
+ // biome-ignore lint/style/useConst: a const is folded into a tail call (see getProperty)
2014
+ let r: Value;
2015
+ r = this.getProperty(o, this.publicName(name));
2016
+ return r;
2017
+ }
2018
+
2019
+ /** Sets `o`'s public property `name`, which its class declares writable. */
2020
+ setProp<T, K extends WritableName<T>>(o: Ref<T>, name: K, v: T[K]): void {
2021
+ this.setProperty(o, this.publicName(name), v);
2022
+ }
2023
+
2024
+ /** Calls `o`'s public method `name` with `args`. */
2025
+ callProp<T, K extends MethodName<T>>(
2026
+ o: Ref<T>,
2027
+ name: K,
2028
+ ...args: T[K] extends (...a: infer A) => unknown ? A : never
2029
+ ): T[K] extends (...a: never[]) => infer R ? R : never {
2030
+ // biome-ignore lint/style/useConst: a const is folded into a tail call (see getProperty)
2031
+ let r: Value;
2032
+ r = this.callProperty(o, this.publicName(name), ...args);
2033
+ return r;
2034
+ }
2035
+
1961
2036
  /** A property name as a string, as ToString makes one. */
1962
2037
  private nameString(name: Value): string {
1963
2038
  return typeof name === "string" ? name : this.toString(name);
@@ -2208,8 +2283,33 @@ export class Runtime {
2208
2283
  return key;
2209
2284
  }
2210
2285
 
2211
- callStatic(_abc: Abc, m: number, ..._args: Value[]): Value {
2212
- throw this.unsupported(`callstatic ${m}`);
2286
+ /**
2287
+ * The first method each factory made for a class's or a script's
2288
+ * traits, as avmplus' AbcEnv keeps the first MethodEnv of each method
2289
+ * a vtable makes (VTable::makeMethodEnv): what callstatic calls.
2290
+ */
2291
+ private readonly staticMethods = new WeakMap<Factory, Method>();
2292
+
2293
+ /** Keep `method`, made by `factory`, for callstatic, unless it made one before. */
2294
+ keepStatic(factory: Factory, method: Method): void {
2295
+ if (!this.staticMethods.has(factory)) {
2296
+ this.staticMethods.set(factory, method);
2297
+ }
2298
+ }
2299
+
2300
+ /**
2301
+ * callstatic: method `factory` made, called on `o` with its arguments,
2302
+ * which the compiler coerced. avmplus verifies a method when it first
2303
+ * runs and refuses one that calls a method no vtable has made yet
2304
+ * (1107); that is found here only once the call is reached.
2305
+ */
2306
+ callStatic(factory: Factory, o: Value, ...args: Value[]): Value {
2307
+ const method = this.staticMethods.get(factory);
2308
+ if (method === undefined) {
2309
+ throw this.error("VerifyError", 1107);
2310
+ }
2311
+
2312
+ return method.apply(o, args);
2213
2313
  }
2214
2314
 
2215
2315
  // The super operations, as MethodEnv's: by the base class's traits
@@ -2225,7 +2325,7 @@ export class Runtime {
2225
2325
  }
2226
2326
 
2227
2327
  if (b === 0) {
2228
- throw this.error("ReferenceError", 1070, mn.name ?? "*", traits.name);
2328
+ throw this.error("ReferenceError", 1070, mn.name ?? "*", traits.errorName);
2229
2329
  }
2230
2330
 
2231
2331
  r = this.callValue(this.getBound(o, traits, b, mn), o, args, mn);
@@ -2236,7 +2336,7 @@ export class Runtime {
2236
2336
  const traits: Traits = sup.$it;
2237
2337
  const b = traits.find(mn);
2238
2338
  if (b === 0) {
2239
- throw this.error("ReferenceError", 1069, mn.name ?? "*", traits.name);
2339
+ throw this.error("ReferenceError", 1069, mn.name ?? "*", traits.errorName);
2240
2340
  }
2241
2341
 
2242
2342
  return this.getBound(o, traits, b, mn);
@@ -2255,11 +2355,11 @@ export class Runtime {
2255
2355
  o[slotKey(id)] = this.coerce(v, traits.slotType(id));
2256
2356
  return;
2257
2357
  case BIND_Method:
2258
- throw this.error("ReferenceError", 1037, mn.name ?? "*", traits.name);
2358
+ throw this.error("ReferenceError", 1037, mn.name ?? "*", traits.errorName);
2259
2359
  case 0:
2260
- throw this.error("ReferenceError", 1056, mn.name ?? "*", traits.name);
2360
+ throw this.error("ReferenceError", 1056, mn.name ?? "*", traits.errorName);
2261
2361
  default:
2262
- throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.name);
2362
+ throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.errorName);
2263
2363
  }
2264
2364
  }
2265
2365
 
@@ -2734,7 +2834,7 @@ export class Runtime {
2734
2834
  return v;
2735
2835
  }
2736
2836
 
2737
- throw this.error("TypeError", 1034, this.describe(v), traits.name);
2837
+ throw this.error("TypeError", 1034, this.describe(v), traits.errorName);
2738
2838
  }
2739
2839
  }
2740
2840
  }
@@ -2759,7 +2859,7 @@ export class Runtime {
2759
2859
  return v;
2760
2860
  }
2761
2861
 
2762
- throw this.error("TypeError", 1034, this.describe(v), traits.name);
2862
+ throw this.error("TypeError", 1034, this.describe(v), traits.errorName);
2763
2863
  }
2764
2864
 
2765
2865
  coerceString(v: Value): string | null {
@@ -3000,7 +3100,7 @@ export class Runtime {
3000
3100
  }
3001
3101
  }
3002
3102
 
3003
- throw this.error("TypeError", 1050, o.$traits.name);
3103
+ throw this.error("TypeError", 1050, o.$traits.errorName);
3004
3104
  }
3005
3105
 
3006
3106
  add(a: Value, b: Value): Value {
@@ -187,6 +187,26 @@ export class Traits {
187
187
  }
188
188
  }
189
189
 
190
+ /**
191
+ * Its name as Flash's debugger player writes it in an error message
192
+ * (AvmCore::toErrorString): the package and the name joined by a dot,
193
+ * "flash.text.TextField"; a class object's as "class " and its class's.
194
+ */
195
+ get errorName(): string {
196
+ let name = this.name;
197
+ const statics = this.base?.name === "Class" && name.endsWith("$");
198
+ if (statics) {
199
+ name = name.slice(0, -1);
200
+ }
201
+
202
+ const colons = name.lastIndexOf("::");
203
+ if (colons >= 0) {
204
+ name = `${name.slice(0, colons)}.${name.slice(colons + 2)}`;
205
+ }
206
+
207
+ return statics ? `class ${name}` : name;
208
+ }
209
+
190
210
  /**
191
211
  * The object a method and its entries, `count` properties, go into:
192
212
  * the prototype, till it holds HOME_PROPERTIES, then an object chained