@swf2es/runtime 0.1.16 → 0.1.18

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 +44 -1
  36. package/dist/avm2/runtime.d.ts.map +1 -1
  37. package/dist/avm2/runtime.js +119 -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 +141 -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;
@@ -1095,7 +1115,21 @@ export class Runtime {
1095
1115
  this.arrayConversion.noteArrayClass(cls);
1096
1116
  }
1097
1117
 
1118
+ /**
1119
+ * A class's prototype, whose methods compiled code calls on a primitive:
1120
+ * at every such call, so a String's, a Number's and a Boolean's are read
1121
+ * from their traits, kept once known, without the lookup by name.
1122
+ */
1098
1123
  prototypeOf(ref: TypeRef): AsObject {
1124
+ switch (ref) {
1125
+ case "String":
1126
+ return (this.stringTraits ?? this.builtinTraits("String")).proto;
1127
+ case "Number":
1128
+ return (this.numberTraits ?? this.builtinTraits("Number")).proto;
1129
+ case "Boolean":
1130
+ return (this.booleanTraits ?? this.builtinTraits("Boolean")).proto;
1131
+ }
1132
+
1099
1133
  return this.classOf(ref).$it.proto;
1100
1134
  }
1101
1135
 
@@ -1197,7 +1231,7 @@ export class Runtime {
1197
1231
  "ReferenceError",
1198
1232
  name === null && nsset ? 1081 : 1069,
1199
1233
  mn.name ?? "*",
1200
- traits.name,
1234
+ traits.errorName,
1201
1235
  );
1202
1236
  }
1203
1237
 
@@ -1273,6 +1307,31 @@ export class Runtime {
1273
1307
  this.setProperty(o, this.indexName(mn, i), v);
1274
1308
  }
1275
1309
 
1310
+ /**
1311
+ * obj[k] by a runtime name with no public namespace, or an attribute's,
1312
+ * in a script's or a class's initializer, which avmplus interprets: its
1313
+ * getproperty reads an int key of at least 0 off an object by
1314
+ * getUintProperty, whatever namespaces the name has, where its JIT asks
1315
+ * for a public one (getIndexed). Any other key makes a name.
1316
+ */
1317
+ getInterpreted(o: Value, mn: Multiname, k: Value): Value {
1318
+ if (uintAtom(k) && isScriptObject(o)) {
1319
+ return this.getIndexed(o, ELEMENT_NAME, k);
1320
+ }
1321
+
1322
+ return this.getProperty(o, this.runtimeName(mn, k));
1323
+ }
1324
+
1325
+ /** obj[k] = v as getInterpreted reads it, as avmplus' interpreter's setproperty. */
1326
+ setInterpreted(o: Value, mn: Multiname, k: Value, v: Value): void {
1327
+ if (uintAtom(k) && isScriptObject(o)) {
1328
+ this.setIndexed(o, ELEMENT_NAME, k, v);
1329
+ return;
1330
+ }
1331
+
1332
+ this.setProperty(o, this.runtimeName(mn, k), v);
1333
+ }
1334
+
1276
1335
  /**
1277
1336
  * obj[k] for a key whose type the compiler does not know, by a name with
1278
1337
  * no namespace from the stack, as avmplus' getprop_index and
@@ -1578,10 +1637,10 @@ export class Runtime {
1578
1637
  return o[slot];
1579
1638
  }
1580
1639
 
1581
- throw this.error("ReferenceError", 1077, mn.name ?? "*", traits.name);
1640
+ throw this.error("ReferenceError", 1077, mn.name ?? "*", traits.errorName);
1582
1641
  }
1583
1642
  default:
1584
- throw this.error("ReferenceError", 1077, mn.name ?? "*", traits.name);
1643
+ throw this.error("ReferenceError", 1077, mn.name ?? "*", traits.errorName);
1585
1644
  }
1586
1645
  }
1587
1646
 
@@ -1683,7 +1742,7 @@ export class Runtime {
1683
1742
  switch (b & 7) {
1684
1743
  case BIND_Const:
1685
1744
  if (!init) {
1686
- throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.name);
1745
+ throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.errorName);
1687
1746
  }
1688
1747
  o[slotKey(id)] = this.coerce(v, traits.slotType(id));
1689
1748
  return;
@@ -1695,9 +1754,9 @@ export class Runtime {
1695
1754
  (traits.proto[untypedSetKey(id + 1)] ?? traits.proto[methodKey(id + 1)]).call(o, v);
1696
1755
  return;
1697
1756
  case BIND_Method:
1698
- throw this.error("ReferenceError", 1037, mn.name ?? "*", traits.name);
1757
+ throw this.error("ReferenceError", 1037, mn.name ?? "*", traits.errorName);
1699
1758
  default:
1700
- throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.name);
1759
+ throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.errorName);
1701
1760
  }
1702
1761
  }
1703
1762
 
@@ -1722,7 +1781,7 @@ export class Runtime {
1722
1781
  }
1723
1782
  }
1724
1783
 
1725
- throw this.error("ReferenceError", 1056, mn.name ?? "*", traits.name);
1784
+ throw this.error("ReferenceError", 1056, mn.name ?? "*", traits.errorName);
1726
1785
  }
1727
1786
 
1728
1787
  initProperty(o: Value, mn: Multiname, v: Value): void {
@@ -1741,7 +1800,7 @@ export class Runtime {
1741
1800
 
1742
1801
  // A primitive's property cannot be deleted, as MethodEnv's delproperty has it.
1743
1802
  if (typeof o !== "object" || o instanceof Namespace) {
1744
- throw this.error("ReferenceError", 1120, mn.name ?? "*", this.traitsOf(o).name);
1803
+ throw this.error("ReferenceError", 1120, mn.name ?? "*", this.traitsOf(o).errorName);
1745
1804
  }
1746
1805
 
1747
1806
  // E4X 11.3.1: delete x[list] is a TypeError, as in delete x.a.(b == 1).
@@ -1958,6 +2017,36 @@ export class Runtime {
1958
2017
  return qname(publicNs, this.nameString(name));
1959
2018
  }
1960
2019
 
2020
+ // Natives' typed reach into AS3 objects (as3-types.ts): getProperty,
2021
+ // setProperty and callProperty of the public name, the lookup natives
2022
+ // made before, with the name checked against the class's declarations
2023
+ // when compiled.
2024
+
2025
+ /** `o`'s public property `name`: a variable, constant or accessor its class declares. */
2026
+ prop<T, K extends PropertyName<T>>(o: Ref<T>, name: K): T[K] {
2027
+ // biome-ignore lint/style/useConst: a const is folded into a tail call (see getProperty)
2028
+ let r: Value;
2029
+ r = this.getProperty(o, this.publicName(name));
2030
+ return r;
2031
+ }
2032
+
2033
+ /** Sets `o`'s public property `name`, which its class declares writable. */
2034
+ setProp<T, K extends WritableName<T>>(o: Ref<T>, name: K, v: T[K]): void {
2035
+ this.setProperty(o, this.publicName(name), v);
2036
+ }
2037
+
2038
+ /** Calls `o`'s public method `name` with `args`. */
2039
+ callProp<T, K extends MethodName<T>>(
2040
+ o: Ref<T>,
2041
+ name: K,
2042
+ ...args: T[K] extends (...a: infer A) => unknown ? A : never
2043
+ ): T[K] extends (...a: never[]) => infer R ? R : never {
2044
+ // biome-ignore lint/style/useConst: a const is folded into a tail call (see getProperty)
2045
+ let r: Value;
2046
+ r = this.callProperty(o, this.publicName(name), ...args);
2047
+ return r;
2048
+ }
2049
+
1961
2050
  /** A property name as a string, as ToString makes one. */
1962
2051
  private nameString(name: Value): string {
1963
2052
  return typeof name === "string" ? name : this.toString(name);
@@ -2208,8 +2297,33 @@ export class Runtime {
2208
2297
  return key;
2209
2298
  }
2210
2299
 
2211
- callStatic(_abc: Abc, m: number, ..._args: Value[]): Value {
2212
- throw this.unsupported(`callstatic ${m}`);
2300
+ /**
2301
+ * The first method each factory made for a class's or a script's
2302
+ * traits, as avmplus' AbcEnv keeps the first MethodEnv of each method
2303
+ * a vtable makes (VTable::makeMethodEnv): what callstatic calls.
2304
+ */
2305
+ private readonly staticMethods = new WeakMap<Factory, Method>();
2306
+
2307
+ /** Keep `method`, made by `factory`, for callstatic, unless it made one before. */
2308
+ keepStatic(factory: Factory, method: Method): void {
2309
+ if (!this.staticMethods.has(factory)) {
2310
+ this.staticMethods.set(factory, method);
2311
+ }
2312
+ }
2313
+
2314
+ /**
2315
+ * callstatic: method `factory` made, called on `o` with its arguments,
2316
+ * which the compiler coerced. avmplus verifies a method when it first
2317
+ * runs and refuses one that calls a method no vtable has made yet
2318
+ * (1107); that is found here only once the call is reached.
2319
+ */
2320
+ callStatic(factory: Factory, o: Value, ...args: Value[]): Value {
2321
+ const method = this.staticMethods.get(factory);
2322
+ if (method === undefined) {
2323
+ throw this.error("VerifyError", 1107);
2324
+ }
2325
+
2326
+ return method.apply(o, args);
2213
2327
  }
2214
2328
 
2215
2329
  // The super operations, as MethodEnv's: by the base class's traits
@@ -2225,7 +2339,7 @@ export class Runtime {
2225
2339
  }
2226
2340
 
2227
2341
  if (b === 0) {
2228
- throw this.error("ReferenceError", 1070, mn.name ?? "*", traits.name);
2342
+ throw this.error("ReferenceError", 1070, mn.name ?? "*", traits.errorName);
2229
2343
  }
2230
2344
 
2231
2345
  r = this.callValue(this.getBound(o, traits, b, mn), o, args, mn);
@@ -2236,7 +2350,7 @@ export class Runtime {
2236
2350
  const traits: Traits = sup.$it;
2237
2351
  const b = traits.find(mn);
2238
2352
  if (b === 0) {
2239
- throw this.error("ReferenceError", 1069, mn.name ?? "*", traits.name);
2353
+ throw this.error("ReferenceError", 1069, mn.name ?? "*", traits.errorName);
2240
2354
  }
2241
2355
 
2242
2356
  return this.getBound(o, traits, b, mn);
@@ -2255,11 +2369,11 @@ export class Runtime {
2255
2369
  o[slotKey(id)] = this.coerce(v, traits.slotType(id));
2256
2370
  return;
2257
2371
  case BIND_Method:
2258
- throw this.error("ReferenceError", 1037, mn.name ?? "*", traits.name);
2372
+ throw this.error("ReferenceError", 1037, mn.name ?? "*", traits.errorName);
2259
2373
  case 0:
2260
- throw this.error("ReferenceError", 1056, mn.name ?? "*", traits.name);
2374
+ throw this.error("ReferenceError", 1056, mn.name ?? "*", traits.errorName);
2261
2375
  default:
2262
- throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.name);
2376
+ throw this.error("ReferenceError", 1074, mn.name ?? "*", traits.errorName);
2263
2377
  }
2264
2378
  }
2265
2379
 
@@ -2734,7 +2848,7 @@ export class Runtime {
2734
2848
  return v;
2735
2849
  }
2736
2850
 
2737
- throw this.error("TypeError", 1034, this.describe(v), traits.name);
2851
+ throw this.error("TypeError", 1034, this.describe(v), traits.errorName);
2738
2852
  }
2739
2853
  }
2740
2854
  }
@@ -2759,7 +2873,7 @@ export class Runtime {
2759
2873
  return v;
2760
2874
  }
2761
2875
 
2762
- throw this.error("TypeError", 1034, this.describe(v), traits.name);
2876
+ throw this.error("TypeError", 1034, this.describe(v), traits.errorName);
2763
2877
  }
2764
2878
 
2765
2879
  coerceString(v: Value): string | null {
@@ -3000,7 +3114,7 @@ export class Runtime {
3000
3114
  }
3001
3115
  }
3002
3116
 
3003
- throw this.error("TypeError", 1050, o.$traits.name);
3117
+ throw this.error("TypeError", 1050, o.$traits.errorName);
3004
3118
  }
3005
3119
 
3006
3120
  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