@swf2es/runtime 0.1.15 → 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 (84) 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/cache-fill.d.ts +4 -1
  14. package/dist/avm2/cache-fill.d.ts.map +1 -1
  15. package/dist/avm2/cache-fill.js +5 -6
  16. package/dist/avm2/cache-fill.js.map +1 -1
  17. package/dist/avm2/classes.d.ts.map +1 -1
  18. package/dist/avm2/classes.js +10 -3
  19. package/dist/avm2/classes.js.map +1 -1
  20. package/dist/avm2/descriptors.d.ts +6 -1
  21. package/dist/avm2/descriptors.d.ts.map +1 -1
  22. package/dist/avm2/descriptors.js.map +1 -1
  23. package/dist/avm2/errors.d.ts +14 -0
  24. package/dist/avm2/errors.d.ts.map +1 -1
  25. package/dist/avm2/errors.js +20 -1
  26. package/dist/avm2/errors.js.map +1 -1
  27. package/dist/avm2/index.d.ts +4 -0
  28. package/dist/avm2/index.d.ts.map +1 -1
  29. package/dist/avm2/index.js +3 -0
  30. package/dist/avm2/index.js.map +1 -1
  31. package/dist/avm2/names.d.ts +11 -0
  32. package/dist/avm2/names.d.ts.map +1 -1
  33. package/dist/avm2/names.js +12 -0
  34. package/dist/avm2/names.js.map +1 -1
  35. package/dist/avm2/natives/array.js +1 -1
  36. package/dist/avm2/natives/array.js.map +1 -1
  37. package/dist/avm2/natives/bytearray.d.ts.map +1 -1
  38. package/dist/avm2/natives/bytearray.js +6 -11
  39. package/dist/avm2/natives/bytearray.js.map +1 -1
  40. package/dist/avm2/natives/describe.js +6 -1
  41. package/dist/avm2/natives/describe.js.map +1 -1
  42. package/dist/avm2/natives/object.d.ts.map +1 -1
  43. package/dist/avm2/natives/object.js +5 -2
  44. package/dist/avm2/natives/object.js.map +1 -1
  45. package/dist/avm2/natives/proxy.d.ts.map +1 -1
  46. package/dist/avm2/natives/proxy.js +9 -4
  47. package/dist/avm2/natives/proxy.js.map +1 -1
  48. package/dist/avm2/natives/vector.js +1 -1
  49. package/dist/avm2/natives/vector.js.map +1 -1
  50. package/dist/avm2/options.d.ts +5 -0
  51. package/dist/avm2/options.d.ts.map +1 -1
  52. package/dist/avm2/options.js.map +1 -1
  53. package/dist/avm2/property-cache.d.ts +17 -13
  54. package/dist/avm2/property-cache.d.ts.map +1 -1
  55. package/dist/avm2/property-cache.js +50 -10
  56. package/dist/avm2/property-cache.js.map +1 -1
  57. package/dist/avm2/runtime.d.ts +101 -1
  58. package/dist/avm2/runtime.d.ts.map +1 -1
  59. package/dist/avm2/runtime.js +351 -45
  60. package/dist/avm2/runtime.js.map +1 -1
  61. package/dist/avm2/traits.d.ts +13 -0
  62. package/dist/avm2/traits.d.ts.map +1 -1
  63. package/dist/avm2/traits.js +23 -0
  64. package/dist/avm2/traits.js.map +1 -1
  65. package/package.json +2 -2
  66. package/src/avm2/amf.ts +4 -3
  67. package/src/avm2/as3-types.ts +41 -0
  68. package/src/avm2/builtin-types.generated.ts +619 -0
  69. package/src/avm2/cache-fill.ts +5 -7
  70. package/src/avm2/classes.ts +10 -3
  71. package/src/avm2/descriptors.ts +6 -2
  72. package/src/avm2/errors.ts +21 -1
  73. package/src/avm2/index.ts +6 -0
  74. package/src/avm2/names.ts +13 -0
  75. package/src/avm2/natives/array.ts +1 -1
  76. package/src/avm2/natives/bytearray.ts +6 -14
  77. package/src/avm2/natives/describe.ts +8 -1
  78. package/src/avm2/natives/object.ts +5 -2
  79. package/src/avm2/natives/proxy.ts +20 -4
  80. package/src/avm2/natives/vector.ts +1 -1
  81. package/src/avm2/options.ts +5 -0
  82. package/src/avm2/property-cache.ts +57 -11
  83. package/src/avm2/runtime.ts +407 -53
  84. package/src/avm2/traits.ts +27 -1
@@ -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";
@@ -47,7 +48,7 @@ import {
47
48
  pairOf,
48
49
  walkless,
49
50
  } from "./enumeration.js";
50
- import { AsError, describe, errorMessage, stackOf } from "./errors.js";
51
+ import { AsError, describe, detailOf, errorMessage, stackOf, UnsupportedError } from "./errors.js";
51
52
  import { FloatValue, floatValue } from "./float.js";
52
53
  import { type ClassHook, hookedBinding, type NativesProvider, type PropertyHook } from "./hooks.js";
53
54
  import {
@@ -70,6 +71,8 @@ import {
70
71
  CONSTANT_RTQnameA,
71
72
  CONSTANT_RTQnameL,
72
73
  CONSTANT_RTQnameLA,
74
+ MADE_Number,
75
+ MADE_Value,
73
76
  Multiname,
74
77
  Namespace,
75
78
  NS_Private,
@@ -113,7 +116,7 @@ import {
113
116
  IC_Slot,
114
117
  invalidate,
115
118
  NO_CACHE,
116
- REPLACEMENTS,
119
+ ScopeFinds,
117
120
  UNFILLED,
118
121
  } from "./property-cache.js";
119
122
  import {
@@ -153,7 +156,34 @@ class ScopePack {
153
156
  ) {}
154
157
  }
155
158
 
159
+ /** Whether a chain or a pack, `scopes` with bits `w`, has a with scope, in a pack in it too. */
160
+ function hasWith(scopes: Value[], w: number): boolean {
161
+ for (let i = 0; i < scopes.length; i++) {
162
+ const o = scopes[i];
163
+ if (o instanceof ScopePack ? hasWith(o.scopes, o.w) : (w >> i) & 1) {
164
+ return true;
165
+ }
166
+ }
167
+
168
+ return false;
169
+ }
170
+
156
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
+
157
187
  function dynamicValue(o: AsObject, name: string): Value {
158
188
  const d: Map<string, Value> | null | undefined = o.$d;
159
189
  if (!d) {
@@ -167,6 +197,7 @@ function dynamicValue(o: AsObject, name: string): Value {
167
197
  export class Runtime {
168
198
  readonly print: (line: string) => void;
169
199
  readonly debugger: boolean;
200
+ private readonly onUnsupported: ((message: string) => void) | undefined;
170
201
  readonly compileAbc:
171
202
  | ((abc: Uint8Array, unit: CompileUnit) => ((rt: Runtime) => Abc) | number)
172
203
  | null;
@@ -229,6 +260,13 @@ export class Runtime {
229
260
  /** The builtin classes' traits, made before their classes so the bootstrap can refer to them. */
230
261
  readonly objectTraits: Traits;
231
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;
232
270
  readonly functionTraits: Traits;
233
271
  /**
234
272
  * Method closures, by receiver and method, so that o.f === o.f; by the
@@ -249,7 +287,7 @@ export class Runtime {
249
287
  /** Special traits: an activation's or a catch scope's, by descriptor. */
250
288
  private readonly scopeTraits = new WeakMap<object, Traits>();
251
289
  readonly specialized = new Map<AsObject, AsObject>();
252
- readonly empty: Scope = Object.assign([], { w: 0 });
290
+ readonly empty: Scope = Object.assign([], { w: 0, found: undefined });
253
291
 
254
292
  /**
255
293
  * `natives` are the natives by name, or a provider that makes them for
@@ -264,6 +302,7 @@ export class Runtime {
264
302
  ) {
265
303
  this.print = options.print ?? defaultPrint;
266
304
  this.debugger = options.debugger ?? false;
305
+ this.onUnsupported = options.onUnsupported;
267
306
  this.compileAbc = options.compileAbc ?? null;
268
307
  this.files = options.files ?? memoryFiles();
269
308
  this.workerHost = options.workers ?? null;
@@ -283,6 +322,8 @@ export class Runtime {
283
322
  this.objectTraits.dynamic = true;
284
323
  this.objectTraits.testsValues = true;
285
324
  this.classTraits = new Traits("Class", this.objectTraits);
325
+ this.scriptSuper = { $it: this.objectTraits };
326
+ this.staticSuper = { $it: this.classTraits };
286
327
  this.functionTraits = new Traits("Function", this.objectTraits);
287
328
  this.functionTraits.dynamic = true;
288
329
  this.natives = typeof natives === "function" ? natives(this) : natives;
@@ -429,6 +470,7 @@ export class Runtime {
429
470
  // Its string only once a lookup needs it: a Dictionary does not.
430
471
  const keyed = Multiname.keyed(kind, namespaces, versions, part, mn.attribute, this.keyName);
431
472
  keyed.domain = mn.domain;
473
+ keyed.made = MADE_Value;
432
474
  return keyed;
433
475
  } else {
434
476
  name = typeof part === "string" ? part : this.toString(part);
@@ -437,6 +479,42 @@ export class Runtime {
437
479
 
438
480
  const named = new Multiname(kind, namespaces, versions, name, mn.attribute);
439
481
  named.domain = mn.domain;
482
+ // A number here is one no element access made: a Proxy tells it by its string.
483
+ if (mn.runtimeName && typeof last !== "number") {
484
+ named.made = MADE_Value;
485
+ }
486
+
487
+ return named;
488
+ }
489
+
490
+ /**
491
+ * The name runtimeName makes of `mn`, a MultinameL, and a string the
492
+ * code itself holds, `obj["name"]`: a module makes it once as it loads
493
+ * and keeps it as it keeps `mn`, so it has a cache as a module's own
494
+ * names have, however many such names the module has and however long
495
+ * the string is.
496
+ */
497
+ literalName(mn: Multiname, name: string): Multiname {
498
+ const named = new Multiname(mn.kind, mn.namespaces, mn.versions, name, mn.attribute);
499
+ named.domain = mn.domain;
500
+ named.made = MADE_Value;
501
+ named.cache = UNFILLED;
502
+
503
+ return named;
504
+ }
505
+
506
+ /**
507
+ * The name number `i` makes of `mn` in an element's read or write, which
508
+ * avmplus' JIT hands an object as a number (getpropertylate_i and _d), a
509
+ * Proxy as its string: where the name has no public namespace, it is a
510
+ * name as any other.
511
+ */
512
+ private indexName(mn: Multiname, i: number): Multiname {
513
+ const named = this.runtimeName(mn, i);
514
+ if (mn.kind === CONSTANT_MultinameL && mn.elementName) {
515
+ named.made = MADE_Number;
516
+ }
517
+
440
518
  return named;
441
519
  }
442
520
 
@@ -462,6 +540,7 @@ export class Runtime {
462
540
 
463
541
  const named = new Multiname(base.kind, base.namespaces, base.versions, name, base.attribute);
464
542
  named.domain = base.domain;
543
+ named.made = MADE_Value;
465
544
  if (!cache) {
466
545
  cache = { names: new Map(), misses: 0 };
467
546
  this.stringNames.set(base, cache);
@@ -578,6 +657,7 @@ export class Runtime {
578
657
  const traits = new Traits("global", this.objectTraits);
579
658
  traits.dynamic = true;
580
659
  traits.isGlobal = true;
660
+ traits.abc = script.abc;
581
661
  traits.describe(script.desc.traits);
582
662
  const g = traits.instance();
583
663
  // As the script's initializer, its methods run in an empty scope chain,
@@ -585,14 +665,9 @@ export class Runtime {
585
665
  // indexes the scopes their closures capture: the global object is
586
666
  // only what they push.
587
667
  for (const [d, factory, id, , params] of script.desc.traits.methods) {
588
- installMethod(
589
- this,
590
- traits.methodHome(3),
591
- d,
592
- withId(factory(this.empty, null), id),
593
- factory,
594
- params,
595
- );
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);
596
671
  }
597
672
 
598
673
  // Caches keep the methods.
@@ -622,7 +697,7 @@ export class Runtime {
622
697
  if (script.state === 0 || (retry && script.state === 3)) {
623
698
  script.state = 1;
624
699
  try {
625
- script.desc.init(this.empty, null).call(g);
700
+ script.desc.init(this.empty, this.scriptSuper).call(g);
626
701
  } catch (e) {
627
702
  script.state = 3;
628
703
  throw e;
@@ -671,6 +746,7 @@ export class Runtime {
671
746
  scope(outer: Scope, locals: Value[], withs: number): Scope {
672
747
  const chain = outer.concat(locals) as Scope;
673
748
  chain.w = outer.w | (withs << outer.length);
749
+ chain.found = undefined;
674
750
  return chain;
675
751
  }
676
752
 
@@ -686,6 +762,7 @@ export class Runtime {
686
762
  const packed = chain.slice(0, at) as Scope;
687
763
  packed.push(new ScopePack(chain.slice(at), chain.w >>> at) as never);
688
764
  packed.w = (chain.w & ((1 << at) - 1)) | (1 << at);
765
+ packed.found = undefined;
689
766
  return packed;
690
767
  }
691
768
 
@@ -724,6 +801,47 @@ export class Runtime {
724
801
  }
725
802
  }
726
803
 
804
+ const found = this.outerScopeOf(outer, mn);
805
+ if (found !== null) {
806
+ return found;
807
+ }
808
+
809
+ return this.global(mn, outer.length ? outer[0] : locals[0], strict);
810
+ }
811
+
812
+ /**
813
+ * The innermost object of a captured chain that has `mn`, or null: kept
814
+ * on a chain without a with scope (ScopeFinds), as a function whose
815
+ * chain the compiler could not type, such as one made where chains of
816
+ * different lengths meet, looks its names up each time it runs.
817
+ */
818
+ private outerScopeOf(outer: Scope, mn: Multiname): AsObject | null {
819
+ let finds = outer.found;
820
+ if (finds === undefined) {
821
+ finds = hasWith(outer, outer.w) ? null : new ScopeFinds();
822
+ outer.found = finds;
823
+ }
824
+
825
+ // A name made at run time is a new one each time: keeping it would only grow the map.
826
+ if (finds === null || mn.cache === NO_CACHE) {
827
+ return this.searchOuter(outer, mn);
828
+ }
829
+
830
+ if (finds.epoch !== epoch()) {
831
+ finds.clear();
832
+ finds.epoch = epoch();
833
+ }
834
+
835
+ let o = finds.get(mn);
836
+ if (o === undefined) {
837
+ o = this.searchOuter(outer, mn);
838
+ finds.set(mn, o);
839
+ }
840
+
841
+ return o;
842
+ }
843
+
844
+ private searchOuter(outer: Scope, mn: Multiname): AsObject | null {
727
845
  for (let i = outer.length - 1; i >= 0; i--) {
728
846
  const o = outer[i];
729
847
  if (o instanceof ScopePack) {
@@ -736,7 +854,7 @@ export class Runtime {
736
854
  }
737
855
  }
738
856
 
739
- return this.global(mn, outer.length ? outer[0] : locals[0], strict);
857
+ return null;
740
858
  }
741
859
 
742
860
  findPropertyStrict(mn: Multiname, outer: Scope, locals: Value[], withs: number): AsObject {
@@ -887,16 +1005,24 @@ export class Runtime {
887
1005
  traitsOf(v: Value): Traits {
888
1006
  switch (typeof v) {
889
1007
  case "object":
890
- case "function":
1008
+ case "function": {
891
1009
  if (v === null) {
892
1010
  throw this.error("TypeError", 1009);
893
1011
  }
894
1012
 
1013
+ // An object's own first: instanceof walks the whole prototype
1014
+ // chain of an object, and a class's is as deep as its bases.
1015
+ const traits = v.$traits;
1016
+ if (traits !== undefined) {
1017
+ return traits;
1018
+ }
1019
+
895
1020
  return v instanceof FloatValue
896
1021
  ? this.builtinTraits("float")
897
1022
  : v instanceof Namespace
898
1023
  ? this.builtinTraits("Namespace")
899
- : v.$traits;
1024
+ : traits;
1025
+ }
900
1026
  // As avmplus' toVTable: every number is a Number at run time, int or
901
1027
  // not; int's and uint's traits only bind early, from static types.
902
1028
  case "number":
@@ -918,12 +1044,19 @@ export class Runtime {
918
1044
  private receiverTraits(v: Value): Traits | undefined {
919
1045
  switch (typeof v) {
920
1046
  case "object":
921
- case "function":
922
- return v instanceof FloatValue
923
- ? this.builtinTraits("float")
924
- : v === null
925
- ? undefined
926
- : v.$traits;
1047
+ case "function": {
1048
+ if (v === null) {
1049
+ return undefined;
1050
+ }
1051
+
1052
+ // A float and a Namespace have none of their own (see traitsOf).
1053
+ const traits: Traits | undefined = v.$traits;
1054
+ if (traits !== undefined) {
1055
+ return traits;
1056
+ }
1057
+
1058
+ return v instanceof FloatValue ? this.builtinTraits("float") : undefined;
1059
+ }
927
1060
  case "string":
928
1061
  return this.stringTraits;
929
1062
  case "number":
@@ -1084,7 +1217,7 @@ export class Runtime {
1084
1217
  "ReferenceError",
1085
1218
  name === null && nsset ? 1081 : 1069,
1086
1219
  mn.name ?? "*",
1087
- traits.name,
1220
+ traits.errorName,
1088
1221
  );
1089
1222
  }
1090
1223
 
@@ -1126,7 +1259,7 @@ export class Runtime {
1126
1259
  }
1127
1260
  }
1128
1261
 
1129
- return this.getProperty(o, this.runtimeName(mn, i));
1262
+ return this.getProperty(o, this.indexName(mn, i));
1130
1263
  }
1131
1264
 
1132
1265
  /** obj[i] = v for a number i, as avmplus' setUintProperty. */
@@ -1140,13 +1273,128 @@ export class Runtime {
1140
1273
  return;
1141
1274
  }
1142
1275
 
1143
- if (o.$a !== undefined && o.$a !== SEALED_ELEMENTS && i !== 0xffffffff) {
1144
- o.$a[i] = v;
1145
- return;
1276
+ const a: Value[] | undefined = o.$a;
1277
+ if (a !== undefined) {
1278
+ if (a !== SEALED_ELEMENTS && i !== 0xffffffff) {
1279
+ a[i] = v;
1280
+ return;
1281
+ }
1282
+ } else if (traits?.dynamic && traits.properties === null && !traits.refusesNames) {
1283
+ // As ScriptObject::setUintProperty: an object with no elements of its own keeps the
1284
+ // number among its dynamic properties, with no binding asked for.
1285
+ const d: Map<string, Value> | null | undefined = o.$d;
1286
+ if (d) {
1287
+ d.set(`${i}`, v);
1288
+ return;
1289
+ }
1146
1290
  }
1147
1291
  }
1148
1292
 
1149
- this.setProperty(o, this.runtimeName(mn, i), v);
1293
+ this.setProperty(o, this.indexName(mn, i), v);
1294
+ }
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
+
1321
+ /**
1322
+ * obj[k] for a key whose type the compiler does not know, by a name with
1323
+ * no namespace from the stack, as avmplus' getprop_index and
1324
+ * getpropertyHelper: a number is an index (getIndexed), an object a
1325
+ * Dictionary has as a key is read by itself, and anything else, a QName
1326
+ * and a Dictionary's absent key among them, is looked up by the name it
1327
+ * makes.
1328
+ */
1329
+ getKeyed(o: Value, mn: Multiname, k: Value): Value {
1330
+ let r: Value;
1331
+ if (typeof k === "number") {
1332
+ r = this.getIndexed(o, mn, k);
1333
+ return r;
1334
+ }
1335
+
1336
+ if (typeof k === "object" && k !== null) {
1337
+ const keys: DictionaryKeys | undefined = o?.$keys;
1338
+ if (keys !== undefined) {
1339
+ if (!mn.elementName) {
1340
+ r = this.getProperty(o, this.runtimeName(mn, this.unkeyed(k)));
1341
+ return r;
1342
+ }
1343
+
1344
+ // What is there was put there by a name with the object as its key, which a QName's is not.
1345
+ const v = keys.get(k);
1346
+ if (v !== undefined) {
1347
+ return v;
1348
+ }
1349
+ }
1350
+ }
1351
+
1352
+ r = this.getProperty(o, this.runtimeName(mn, k));
1353
+ return r;
1354
+ }
1355
+
1356
+ /**
1357
+ * An object given as the key of a Dictionary by a name with no public
1358
+ * namespace, as the name is made of it: avmplus' JIT asks a Dictionary
1359
+ * for an object only through getprop_index and setprop_index, which such
1360
+ * a name does not reach, so the object is its string, as on any other
1361
+ * receiver (initMultinameLate); a QName names what it names. Its
1362
+ * interpreter asks the Dictionary by any name, as a name made without
1363
+ * this does: the compiler leaves script and class initializers to that.
1364
+ */
1365
+ private unkeyed(k: AsObject): Value {
1366
+ return k.$traits !== undefined && k.$local === undefined ? this.toString(k) : k;
1367
+ }
1368
+
1369
+ /** obj[k] = v for a key whose type the compiler does not know, as avmplus' setprop_index and setpropertyHelper. */
1370
+ setKeyed(o: Value, mn: Multiname, k: Value, v: Value): void {
1371
+ if (typeof k === "number") {
1372
+ this.setIndexed(o, mn, k, v);
1373
+ return;
1374
+ }
1375
+
1376
+ if (typeof k === "object" && k !== null) {
1377
+ const keys: DictionaryKeys | undefined = o?.$keys;
1378
+ if (keys !== undefined) {
1379
+ if (!mn.elementName) {
1380
+ this.setProperty(o, this.runtimeName(mn, this.unkeyed(k)), v);
1381
+ return;
1382
+ }
1383
+
1384
+ if (keys.replace(k, v)) {
1385
+ return;
1386
+ }
1387
+
1388
+ // A new key: an object of traits, which a float and a Namespace are not, and no QName,
1389
+ // which names what it names. Those are added, or not, by the name they make.
1390
+ if (k.$traits !== undefined && k.$local === undefined) {
1391
+ keys.add(k, v);
1392
+ return;
1393
+ }
1394
+ }
1395
+ }
1396
+
1397
+ this.setProperty(o, this.runtimeName(mn, k), v);
1150
1398
  }
1151
1399
 
1152
1400
  /**
@@ -1375,21 +1623,17 @@ export class Runtime {
1375
1623
  return o[slot];
1376
1624
  }
1377
1625
 
1378
- throw this.error("ReferenceError", 1077, mn.name ?? "*", traits.name);
1626
+ throw this.error("ReferenceError", 1077, mn.name ?? "*", traits.errorName);
1379
1627
  }
1380
1628
  default:
1381
- throw this.error("ReferenceError", 1077, mn.name ?? "*", traits.name);
1629
+ throw this.error("ReferenceError", 1077, mn.name ?? "*", traits.errorName);
1382
1630
  }
1383
1631
  }
1384
1632
 
1385
1633
  /** fillCache (cache-fill.ts), for a name that caches on traits a cache knows `o` by. */
1386
1634
  private fill(o: Value, traits: Traits, b: number, mn: Multiname): void {
1387
1635
  const head = mn.cache;
1388
- if (
1389
- head === NO_CACHE ||
1390
- (head.replaced === REPLACEMENTS && head.epoch === epoch()) ||
1391
- this.receiverTraits(o) !== traits
1392
- ) {
1636
+ if (head === NO_CACHE || this.receiverTraits(o) !== traits) {
1393
1637
  return;
1394
1638
  }
1395
1639
 
@@ -1424,6 +1668,8 @@ export class Runtime {
1424
1668
  f.$length = (method as CountedMethod).$length ?? method.length;
1425
1669
  f.$id = (method as Method & { $id?: number }).$id ?? 0;
1426
1670
  f.$closure = true;
1671
+ // Whose method it is, for moduleOf.
1672
+ f.$of = traits;
1427
1673
  if (typeof o === "object") {
1428
1674
  byMethod.set(method, f);
1429
1675
  }
@@ -1482,7 +1728,7 @@ export class Runtime {
1482
1728
  switch (b & 7) {
1483
1729
  case BIND_Const:
1484
1730
  if (!init) {
1485
- throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.name);
1731
+ throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.errorName);
1486
1732
  }
1487
1733
  o[slotKey(id)] = this.coerce(v, traits.slotType(id));
1488
1734
  return;
@@ -1494,9 +1740,9 @@ export class Runtime {
1494
1740
  (traits.proto[untypedSetKey(id + 1)] ?? traits.proto[methodKey(id + 1)]).call(o, v);
1495
1741
  return;
1496
1742
  case BIND_Method:
1497
- throw this.error("ReferenceError", 1037, mn.name ?? "*", traits.name);
1743
+ throw this.error("ReferenceError", 1037, mn.name ?? "*", traits.errorName);
1498
1744
  default:
1499
- throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.name);
1745
+ throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.errorName);
1500
1746
  }
1501
1747
  }
1502
1748
 
@@ -1521,7 +1767,7 @@ export class Runtime {
1521
1767
  }
1522
1768
  }
1523
1769
 
1524
- throw this.error("ReferenceError", 1056, mn.name ?? "*", traits.name);
1770
+ throw this.error("ReferenceError", 1056, mn.name ?? "*", traits.errorName);
1525
1771
  }
1526
1772
 
1527
1773
  initProperty(o: Value, mn: Multiname, v: Value): void {
@@ -1540,7 +1786,7 @@ export class Runtime {
1540
1786
 
1541
1787
  // A primitive's property cannot be deleted, as MethodEnv's delproperty has it.
1542
1788
  if (typeof o !== "object" || o instanceof Namespace) {
1543
- throw this.error("ReferenceError", 1120, mn.name ?? "*", this.traitsOf(o).name);
1789
+ throw this.error("ReferenceError", 1120, mn.name ?? "*", this.traitsOf(o).errorName);
1544
1790
  }
1545
1791
 
1546
1792
  // E4X 11.3.1: delete x[list] is a TypeError, as in delete x.a.(b == 1).
@@ -1740,8 +1986,7 @@ export class Runtime {
1740
1986
  if (
1741
1987
  typeof o === "object" &&
1742
1988
  o !== null &&
1743
- !(o instanceof Namespace) &&
1744
- !(o instanceof FloatValue)
1989
+ (o.$traits !== undefined || !(o instanceof Namespace || o instanceof FloatValue))
1745
1990
  ) {
1746
1991
  return o.$p;
1747
1992
  }
@@ -1758,6 +2003,36 @@ export class Runtime {
1758
2003
  return qname(publicNs, this.nameString(name));
1759
2004
  }
1760
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
+
1761
2036
  /** A property name as a string, as ToString makes one. */
1762
2037
  private nameString(name: Value): string {
1763
2038
  return typeof name === "string" ? name : this.toString(name);
@@ -2008,8 +2283,33 @@ export class Runtime {
2008
2283
  return key;
2009
2284
  }
2010
2285
 
2011
- callStatic(_abc: Abc, m: number, ..._args: Value[]): Value {
2012
- 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);
2013
2313
  }
2014
2314
 
2015
2315
  // The super operations, as MethodEnv's: by the base class's traits
@@ -2025,7 +2325,7 @@ export class Runtime {
2025
2325
  }
2026
2326
 
2027
2327
  if (b === 0) {
2028
- throw this.error("ReferenceError", 1070, mn.name ?? "*", traits.name);
2328
+ throw this.error("ReferenceError", 1070, mn.name ?? "*", traits.errorName);
2029
2329
  }
2030
2330
 
2031
2331
  r = this.callValue(this.getBound(o, traits, b, mn), o, args, mn);
@@ -2036,7 +2336,7 @@ export class Runtime {
2036
2336
  const traits: Traits = sup.$it;
2037
2337
  const b = traits.find(mn);
2038
2338
  if (b === 0) {
2039
- throw this.error("ReferenceError", 1069, mn.name ?? "*", traits.name);
2339
+ throw this.error("ReferenceError", 1069, mn.name ?? "*", traits.errorName);
2040
2340
  }
2041
2341
 
2042
2342
  return this.getBound(o, traits, b, mn);
@@ -2055,11 +2355,11 @@ export class Runtime {
2055
2355
  o[slotKey(id)] = this.coerce(v, traits.slotType(id));
2056
2356
  return;
2057
2357
  case BIND_Method:
2058
- throw this.error("ReferenceError", 1037, mn.name ?? "*", traits.name);
2358
+ throw this.error("ReferenceError", 1037, mn.name ?? "*", traits.errorName);
2059
2359
  case 0:
2060
- throw this.error("ReferenceError", 1056, mn.name ?? "*", traits.name);
2360
+ throw this.error("ReferenceError", 1056, mn.name ?? "*", traits.errorName);
2061
2361
  default:
2062
- throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.name);
2362
+ throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.errorName);
2063
2363
  }
2064
2364
  }
2065
2365
 
@@ -2242,6 +2542,21 @@ export class Runtime {
2242
2542
  return f;
2243
2543
  }
2244
2544
 
2545
+ /**
2546
+ * The module a function's code is from, as avmplus has a method's code
2547
+ * context: a function's by the global object its scope begins with, a
2548
+ * bound method's by its receiver's class; null for a native, a class's
2549
+ * own method, or anything but a function.
2550
+ */
2551
+ moduleOf(f: Value): object | null {
2552
+ const o = f as AsObject | null;
2553
+ if (o?.$of) {
2554
+ return (o.$of as Traits).abc;
2555
+ }
2556
+
2557
+ return o?.$global ? this.traitsOf(o.$global).abc : null;
2558
+ }
2559
+
2245
2560
  /** A Function object calling `f`, with `global` as its receiver when it has none. */
2246
2561
  newFunctionObject(f: Method, global: AsObject | null): AsObject {
2247
2562
  const o = this.functionTraits.instance();
@@ -2519,7 +2834,7 @@ export class Runtime {
2519
2834
  return v;
2520
2835
  }
2521
2836
 
2522
- throw this.error("TypeError", 1034, this.describe(v), traits.name);
2837
+ throw this.error("TypeError", 1034, this.describe(v), traits.errorName);
2523
2838
  }
2524
2839
  }
2525
2840
  }
@@ -2544,7 +2859,7 @@ export class Runtime {
2544
2859
  return v;
2545
2860
  }
2546
2861
 
2547
- throw this.error("TypeError", 1034, this.describe(v), traits.name);
2862
+ throw this.error("TypeError", 1034, this.describe(v), traits.errorName);
2548
2863
  }
2549
2864
 
2550
2865
  coerceString(v: Value): string | null {
@@ -2785,7 +3100,7 @@ export class Runtime {
2785
3100
  }
2786
3101
  }
2787
3102
 
2788
- throw this.error("TypeError", 1050, o.$traits.name);
3103
+ throw this.error("TypeError", 1050, o.$traits.errorName);
2789
3104
  }
2790
3105
 
2791
3106
  add(a: Value, b: Value): Value {
@@ -3124,7 +3439,13 @@ export class Runtime {
3124
3439
  return new AsError(`${name}: ${message}`);
3125
3440
  }
3126
3441
 
3127
- return this.constructClass(cls, [message, id]);
3442
+ const made = this.constructClass(cls, [message, id]);
3443
+ // What the number stands for, kept beside the message for a host's report (detailOf).
3444
+ if (!this.debugger) {
3445
+ Object.defineProperty(made, "$detail", { value: errorMessage(true, id, args) });
3446
+ }
3447
+
3448
+ return made;
3128
3449
  }
3129
3450
 
3130
3451
  /** See stackOf in errors.ts. */
@@ -3132,6 +3453,11 @@ export class Runtime {
3132
3453
  return stackOf(error);
3133
3454
  }
3134
3455
 
3456
+ /** See detailOf in errors.ts. */
3457
+ detailOf(error: Value): string | null {
3458
+ return detailOf(error);
3459
+ }
3460
+
3135
3461
  /** See errorMessage in errors.ts. */
3136
3462
  errorMessage(id: number, args: Value[] = []): string {
3137
3463
  return errorMessage(this.debugger, id, args);
@@ -3162,6 +3488,10 @@ export class Runtime {
3162
3488
  return this.error("RangeError", 1506);
3163
3489
  }
3164
3490
 
3491
+ if (e instanceof UnsupportedError) {
3492
+ return this.unsupportedCaught(e);
3493
+ }
3494
+
3165
3495
  if (e instanceof Error && !(e instanceof AsError)) {
3166
3496
  throw e;
3167
3497
  }
@@ -3169,6 +3499,30 @@ export class Runtime {
3169
3499
  return e;
3170
3500
  }
3171
3501
 
3502
+ /** What a catch was told of already (unsupportedCaught). */
3503
+ private readonly unsupportedTold = new Set<string>();
3504
+
3505
+ /**
3506
+ * What is not supported as the AS3 error a catch gets: an
3507
+ * IllegalOperationError with its message, or an Error where the player's
3508
+ * classes are not loaded; said once to the host, as the code may say nothing.
3509
+ */
3510
+ private unsupportedCaught(e: UnsupportedError): Value {
3511
+ if (!this.unsupportedTold.has(e.message)) {
3512
+ this.unsupportedTold.add(e.message);
3513
+ this.onUnsupported?.(e.message);
3514
+ }
3515
+
3516
+ let cls: AsObject;
3517
+ try {
3518
+ cls = this.resolve(this.cls(namespace(NS_Public, "flash.errors"), "IllegalOperationError"));
3519
+ } catch {
3520
+ cls = this.builtinClass("Error");
3521
+ }
3522
+
3523
+ return this.constructClass(cls, [e.message, 0]);
3524
+ }
3525
+
3172
3526
  catches(e: Value, type: TypeRef): boolean {
3173
3527
  return this.isInstanceOf(e, this.traitsOfType(type));
3174
3528
  }
@@ -3178,7 +3532,7 @@ export class Runtime {
3178
3532
  }
3179
3533
 
3180
3534
  unsupported(what: string): Error {
3181
- return new Error(`swf2es: ${what} is not supported yet`);
3535
+ return new UnsupportedError(`swf2es: ${what} is not supported yet`);
3182
3536
  }
3183
3537
 
3184
3538
  // Class aliases and the classes AMF makes (amf-classes.ts).