@agoric/swingset-liveslots 0.10.3-u14.0 → 0.10.3-u16.1

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 (60) hide show
  1. package/README.md +2 -0
  2. package/package.json +27 -19
  3. package/src/cache.js +2 -1
  4. package/src/capdata.js +0 -1
  5. package/src/collectionManager.js +154 -72
  6. package/src/index.js +4 -1
  7. package/src/liveslots.js +52 -81
  8. package/src/message.js +4 -4
  9. package/src/types.js +8 -2
  10. package/src/vatDataTypes.d.ts +271 -0
  11. package/src/vatDataTypes.js +2 -0
  12. package/src/vatstore-iterators.js +2 -0
  13. package/src/virtualObjectManager.js +189 -70
  14. package/src/virtualReferences.js +51 -0
  15. package/src/watchedPromises.js +61 -16
  16. package/test/{test-baggage.js → baggage.test.js} +1 -2
  17. package/test/{test-cache.js → cache.test.js} +0 -1
  18. package/test/{test-collection-schema-refcount.js → collection-schema-refcount.test.js} +1 -2
  19. package/test/{test-collection-upgrade.js → collection-upgrade.test.js} +1 -3
  20. package/test/{test-collections.js → collections.test.js} +117 -14
  21. package/test/{test-dropped-collection-weakrefs.js → dropped-collection-weakrefs.test.js} +1 -2
  22. package/test/{test-durabilityChecks.js → durabilityChecks.test.js} +3 -3
  23. package/test/{test-facetiousness.js → facetiousness.test.js} +1 -2
  24. package/test/gc-and-finalize.js +30 -1
  25. package/test/gc-helpers.js +2 -3
  26. package/test/{test-gc-sensitivity.js → gc-sensitivity.test.js} +2 -2
  27. package/test/{test-handled-promises.js → handled-promises.test.js} +5 -7
  28. package/test/{test-initial-vrefs.js → initial-vrefs.test.js} +2 -3
  29. package/test/liveslots-helpers.js +6 -6
  30. package/test/{test-liveslots-mock-gc.js → liveslots-mock-gc.test.js} +2 -2
  31. package/test/{test-liveslots-real-gc.js → liveslots-real-gc.test.js} +44 -35
  32. package/test/{test-liveslots.js → liveslots.test.js} +13 -14
  33. package/test/mock-gc.js +1 -0
  34. package/test/storeGC/{test-lifecycle.js → lifecycle.test.js} +2 -2
  35. package/test/storeGC/{test-refcount-management.js → refcount-management.test.js} +1 -2
  36. package/test/storeGC/{test-scalar-store-kind.js → scalar-store-kind.test.js} +0 -1
  37. package/test/storeGC/{test-weak-key.js → weak-key.test.js} +1 -2
  38. package/test/util.js +2 -2
  39. package/test/vat-util.js +1 -1
  40. package/test/virtual-objects/{test-cease-recognition.js → cease-recognition.test.js} +2 -2
  41. package/test/virtual-objects/{test-cross-facet.js → cross-facet.test.js} +5 -4
  42. package/test/virtual-objects/{test-empty-data.js → empty-data.test.js} +1 -2
  43. package/test/virtual-objects/{test-facets.js → facets.test.js} +1 -2
  44. package/test/virtual-objects/{test-kind-changes.js → kind-changes.test.js} +2 -2
  45. package/test/virtual-objects/{test-reachable-vrefs.js → reachable-vrefs.test.js} +2 -2
  46. package/test/virtual-objects/{test-rep-tostring.js → rep-tostring.test.js} +2 -3
  47. package/test/virtual-objects/{test-retain-remotable.js → retain-remotable.test.js} +25 -24
  48. package/test/virtual-objects/{test-state-shape.js → state-shape.test.js} +2 -2
  49. package/test/virtual-objects/{test-virtualObjectGC.js → virtualObjectGC.test.js} +2 -2
  50. package/test/virtual-objects/{test-virtualObjectManager.js → virtualObjectManager.test.js} +6 -2
  51. package/test/virtual-objects/{test-vo-real-gc.js → vo-real-gc.test.js} +8 -8
  52. package/test/virtual-objects/{test-weakcollections-vref-handling.js → weakcollections-vref-handling.test.js} +1 -2
  53. package/test/{test-vo-test-harness.js → vo-test-harness.test.js} +0 -1
  54. package/test/{test-vpid-liveslots.js → vpid-liveslots.test.js} +4 -5
  55. package/test/waitUntilQuiescent.js +2 -1
  56. package/tools/fakeVirtualSupport.js +41 -24
  57. package/tools/prepare-test-env.js +13 -0
  58. package/tools/setup-vat-data.js +62 -0
  59. package/CHANGELOG.md +0 -93
  60. package/test/kmarshal.js +0 -79
@@ -1,7 +1,13 @@
1
1
  /* global globalThis */
2
2
  /* eslint-disable no-use-before-define, jsdoc/require-returns-type */
3
3
 
4
- import { assert, Fail } from '@agoric/assert';
4
+ import { environmentOptionsListHas } from '@endo/env-options';
5
+ import {
6
+ assert,
7
+ throwRedacted as Fail,
8
+ quote as q,
9
+ bare as b,
10
+ } from '@endo/errors';
5
11
  import { assertPattern, mustMatch } from '@agoric/store';
6
12
  import { defendPrototype, defendPrototypeKit } from '@endo/exo/tools.js';
7
13
  import { Far, passStyleOf } from '@endo/marshal';
@@ -14,17 +20,22 @@ import {
14
20
  checkAndUpdateFacetiousness,
15
21
  } from './facetiousness.js';
16
22
 
17
- /** @template T @typedef {import('@agoric/vat-data').DefineKindOptions<T>} DefineKindOptions */
23
+ /**
24
+ * @import {DurableKindHandle} from '@agoric/swingset-liveslots'
25
+ * @import {DefineKindOptions} from '@agoric/swingset-liveslots'
26
+ * @import {ClassContextProvider, KitContextProvider} from '@endo/exo'
27
+ * @import {ToCapData, FromCapData} from '@endo/marshal';
28
+ */
18
29
 
19
- const { hasOwn, defineProperty, getOwnPropertyNames } = Object;
30
+ const {
31
+ hasOwn,
32
+ defineProperty,
33
+ getOwnPropertyNames,
34
+ values,
35
+ entries,
36
+ fromEntries,
37
+ } = Object;
20
38
  const { ownKeys } = Reflect;
21
- const { quote: q } = assert;
22
-
23
- // import { kdebug } from './kdebug.js';
24
-
25
- // TODO Use environment-options.js currently in ses/src after factoring it out
26
- // to a new package.
27
- const env = (globalThis.process || {}).env || {};
28
39
 
29
40
  // Turn on to give each exo instance its own toStringTag value which exposes
30
41
  // the SwingSet vref.
@@ -33,9 +44,7 @@ const env = (globalThis.process || {}).env || {};
33
44
  // confidential object-creation activity, so this must not be something
34
45
  // that unprivileged vat code (including unprivileged contracts) can do
35
46
  // for themselves.
36
- const LABEL_INSTANCES = (env.DEBUG || '')
37
- .split(':')
38
- .includes('label-instances');
47
+ const LABEL_INSTANCES = environmentOptionsListHas('DEBUG', 'label-instances');
39
48
 
40
49
  // This file implements the "Virtual Objects" system, currently documented in
41
50
  // {@link https://github.com/Agoric/agoric-sdk/blob/master/packages/SwingSet/docs/virtual-objects.md})
@@ -132,40 +141,6 @@ const makeContextCache = (makeState, makeContext) => {
132
141
  return makeCache(readBacking, writeBacking, deleteBacking);
133
142
  };
134
143
 
135
- /**
136
- * @typedef {import('@endo/exo/src/exo-tools.js').ContextProvider } ContextProvider
137
- */
138
-
139
- /**
140
- * @param {*} contextCache
141
- * @param {*} getSlotForVal
142
- * @returns {ContextProvider}
143
- */
144
- const makeContextProvider = (contextCache, getSlotForVal) => {
145
- return harden(rep => contextCache.get(getSlotForVal(rep)));
146
- };
147
-
148
- const makeContextProviderKit = (contextCache, getSlotForVal, facetNames) => {
149
- /** @type { Record<string, any> } */
150
- const contextProviderKit = {};
151
- for (const [index, name] of facetNames.entries()) {
152
- contextProviderKit[name] = rep => {
153
- const vref = getSlotForVal(rep);
154
- const { baseRef, facet } = parseVatSlot(vref);
155
-
156
- // Without this check, an attacker (with access to both cohort1.facetA
157
- // and cohort2.facetB) could effectively forge access to cohort1.facetB
158
- // and cohort2.facetA. They could not forge the identity of those two
159
- // objects, but they could invoke all their equivalent methods, by using
160
- // e.g. cohort1.facetA.foo.apply(cohort2.facetB, [...args])
161
- Number(facet) === index || Fail`illegal cross-facet access`;
162
-
163
- return harden(contextCache.get(baseRef));
164
- };
165
- }
166
- return harden(contextProviderKit);
167
- };
168
-
169
144
  // The management of single Representatives (i.e. defineKind) is very similar
170
145
  // to that of a cohort of facets (i.e. defineKindMulti). In this description,
171
146
  // we use "self/facets" to refer to either 'self' or 'facets', as appropriate
@@ -260,15 +235,15 @@ const makeFacets = (
260
235
  };
261
236
 
262
237
  const insistDurableCapdata = (vrm, what, capdata, valueFor) => {
263
- capdata.slots.forEach((vref, idx) => {
238
+ for (const [idx, vref] of entries(capdata.slots)) {
264
239
  if (!vrm.isDurable(vref)) {
265
240
  if (valueFor) {
266
- Fail`value for ${what} is not durable: slot ${q(idx)} of ${capdata}`;
241
+ Fail`value for ${what} is not durable: slot ${b(idx)} of ${capdata}`;
267
242
  } else {
268
- Fail`${what} is not durable: slot ${q(idx)} of ${capdata}`;
243
+ Fail`${what} is not durable: slot ${b(idx)} of ${capdata}`;
269
244
  }
270
245
  }
271
- });
246
+ }
272
247
  };
273
248
 
274
249
  const insistSameCapData = (oldCD, newCD) => {
@@ -281,11 +256,11 @@ const insistSameCapData = (oldCD, newCD) => {
281
256
  if (oldCD.slots.length !== newCD.slots.length) {
282
257
  Fail`durable Kind stateShape mismatch (slots.length)`;
283
258
  }
284
- oldCD.slots.forEach((oldVref, idx) => {
259
+ for (const [idx, oldVref] of entries(oldCD.slots)) {
285
260
  if (newCD.slots[idx] !== oldVref) {
286
261
  Fail`durable Kind stateShape mismatch (slot[${idx}])`;
287
262
  }
288
- });
263
+ }
289
264
  };
290
265
 
291
266
  /**
@@ -302,11 +277,11 @@ const insistSameCapData = (oldCD, newCD) => {
302
277
  * @param {(slot: string) => object} requiredValForSlot
303
278
  * @param {*} registerValue Function to register a new slot+value in liveSlot's
304
279
  * various tables
305
- * @param {import('@endo/marshal').ToCapData<string>} serialize Serializer for this vat
306
- * @param {import('@endo/marshal').FromCapData<string>} unserialize Unserializer for this vat
280
+ * @param {ToCapData<string>} serialize Serializer for this vat
281
+ * @param {FromCapData<string>} unserialize Unserializer for this vat
307
282
  * @param {*} assertAcceptableSyscallCapdataSize Function to check for oversized
308
283
  * syscall params
309
- * @param {import('./types').LiveSlotsOptions} [liveSlotsOptions]
284
+ * @param {import('./types.js').LiveSlotsOptions} [liveSlotsOptions]
310
285
  * @param {{ WeakMap: typeof WeakMap, WeakSet: typeof WeakSet }} [powers]
311
286
  * Specifying the underlying WeakMap/WeakSet objects to wrap with
312
287
  * VirtualObjectAwareWeakMap/Set. By default, capture the ones currently
@@ -314,7 +289,7 @@ const insistSameCapData = (oldCD, newCD) => {
314
289
  * recursion if our returned WeakMap/WeakSet wrappers are subsequently installed
315
290
  * on globalThis.
316
291
  *
317
- * @returns {object} a new virtual object manager.
292
+ * @returns a new virtual object manager.
318
293
  *
319
294
  * The virtual object manager allows the creation of persistent objects that do
320
295
  * not need to occupy memory when they are not in use. It provides five
@@ -707,10 +682,18 @@ export const makeVirtualObjectManager = (
707
682
  durableKindDescriptor = undefined, // only for durables
708
683
  ) => {
709
684
  const {
710
- finish,
685
+ finish = undefined,
711
686
  stateShape = undefined,
687
+ receiveAmplifier = undefined,
688
+ receiveInstanceTester = undefined,
712
689
  thisfulMethods = false,
690
+ } = options;
691
+ let {
692
+ // These are "let" rather than "const" only to accommodate code
693
+ // below that tolerates an old version of the vat-data package.
694
+ // See there for more explanation.
713
695
  interfaceGuard = undefined,
696
+ interfaceGuardKit = undefined,
714
697
  } = options;
715
698
 
716
699
  const statePrototype = {}; // Not frozen yet
@@ -731,11 +714,35 @@ export const makeVirtualObjectManager = (
731
714
  switch (assessFacetiousness(behavior)) {
732
715
  case 'one': {
733
716
  assert(!multifaceted);
717
+ interfaceGuardKit === undefined ||
718
+ Fail`Use an interfaceGuard, not interfaceGuardKit, to protect class ${q(
719
+ tag,
720
+ )}`;
734
721
  proposedFacetNames = undefined;
735
722
  break;
736
723
  }
737
724
  case 'many': {
738
725
  assert(multifaceted);
726
+
727
+ if (interfaceGuard && interfaceGuardKit === undefined) {
728
+ // This if clause is for the purpose of tolerating versions
729
+ // of the vata-data package that precede
730
+ // https://github.com/Agoric/agoric-sdk/pull/8220 .
731
+ // Before that PR, the options name `interfaceGuard` would
732
+ // actually carry the InterfaceGuardKit.
733
+ //
734
+ // Tolerating the old vat-data with the new types.
735
+ // @ts-expect-error
736
+ interfaceGuardKit = interfaceGuard;
737
+ interfaceGuard = undefined;
738
+ // The rest of the code from here makes no further compromise
739
+ // for that old version of the vat-data package.
740
+ }
741
+
742
+ interfaceGuard === undefined ||
743
+ Fail`Use an interfaceGuardKit, not an interfaceGuard, to protect class kit ${q(
744
+ tag,
745
+ )}`;
739
746
  proposedFacetNames = ownKeys(behavior).sort();
740
747
  break;
741
748
  }
@@ -763,6 +770,11 @@ export const makeVirtualObjectManager = (
763
770
  Fail`A stateShape must be a copyRecord: ${q(stateShape)}`;
764
771
  assertPattern(stateShape);
765
772
 
773
+ if (!multifaceted) {
774
+ receiveAmplifier === undefined ||
775
+ Fail`Only facets of an exo class kit can be amplified, not ${q(tag)}`;
776
+ }
777
+
766
778
  let facetNames;
767
779
 
768
780
  if (isDurable) {
@@ -850,7 +862,9 @@ export const makeVirtualObjectManager = (
850
862
  return harden({
851
863
  get() {
852
864
  const baseRef = getBaseRef(this);
853
- const { valueMap, capdatas } = dataCache.get(baseRef);
865
+ const record = dataCache.get(baseRef);
866
+ assert(record !== undefined);
867
+ const { valueMap, capdatas } = record;
854
868
  if (!valueMap.has(prop)) {
855
869
  const value = harden(unserialize(capdatas[prop]));
856
870
  checkStatePropertyValue(value, prop);
@@ -867,6 +881,7 @@ export const makeVirtualObjectManager = (
867
881
  insistDurableCapdata(vrm, prop, capdata, true);
868
882
  }
869
883
  const record = dataCache.get(baseRef); // mutable
884
+ assert(record !== undefined);
870
885
  const oldSlots = record.capdatas[prop].slots;
871
886
  const newSlots = capdata.slots;
872
887
  vrm.updateReferenceCounts(oldSlots, newSlots);
@@ -890,7 +905,9 @@ export const makeVirtualObjectManager = (
890
905
  const makeState = baseRef => {
891
906
  const state = { __proto__: statePrototype };
892
907
  if (stateShape === undefined) {
893
- for (const prop of ownKeys(dataCache.get(baseRef).capdatas)) {
908
+ const record = dataCache.get(baseRef);
909
+ assert(record !== undefined);
910
+ for (const prop of ownKeys(record.capdatas)) {
894
911
  assert(typeof prop === 'string');
895
912
  checkStateProperty(prop);
896
913
  defineProperty(state, prop, makeFieldDescriptor(prop));
@@ -940,18 +957,57 @@ export const makeVirtualObjectManager = (
940
957
  // and into method-invocation time (which is not).
941
958
 
942
959
  let proto;
960
+ /** @type {ClassContextProvider | undefined} */
961
+ let contextProviderVar;
962
+ /** @type { Record<string, KitContextProvider> | undefined } */
963
+ let contextProviderKitVar;
964
+
943
965
  if (multifaceted) {
966
+ contextProviderKitVar = fromEntries(
967
+ facetNames.map((name, index) => [
968
+ name,
969
+ rep => {
970
+ const vref = getSlotForVal(rep);
971
+ if (vref === undefined) {
972
+ return undefined;
973
+ }
974
+ const { baseRef, facet } = parseVatSlot(vref);
975
+
976
+ // Without this check, an attacker (with access to both
977
+ // cohort1.facetA and cohort2.facetB)
978
+ // could effectively forge access to
979
+ // cohort1.facetB and cohort2.facetA.
980
+ // They could not forge the identity of those two
981
+ // objects, but they could invoke all their equivalent methods,
982
+ // by using e.g.
983
+ // cohort1.facetA.foo.apply(cohort2.facetB, [...args])
984
+ if (Number(facet) !== index) {
985
+ return undefined;
986
+ }
987
+
988
+ return harden(contextCache.get(baseRef));
989
+ },
990
+ ]),
991
+ );
992
+
944
993
  proto = defendPrototypeKit(
945
994
  tag,
946
- makeContextProviderKit(contextCache, getSlotForVal, facetNames),
995
+ harden(contextProviderKitVar),
947
996
  behavior,
948
997
  thisfulMethods,
949
- interfaceGuard,
998
+ interfaceGuardKit,
950
999
  );
951
1000
  } else {
1001
+ contextProviderVar = rep => {
1002
+ const slot = getSlotForVal(rep);
1003
+ if (slot === undefined) {
1004
+ return undefined;
1005
+ }
1006
+ return harden(contextCache.get(slot));
1007
+ };
952
1008
  proto = defendPrototype(
953
1009
  tag,
954
- makeContextProvider(contextCache, getSlotForVal),
1010
+ harden(contextProviderVar),
955
1011
  behavior,
956
1012
  thisfulMethods,
957
1013
  interfaceGuard,
@@ -959,6 +1015,10 @@ export const makeVirtualObjectManager = (
959
1015
  }
960
1016
  harden(proto);
961
1017
 
1018
+ // All this to let typescript know that it won't vary during a closure
1019
+ const contextProvider = contextProviderVar;
1020
+ const contextProviderKit = contextProviderKitVar;
1021
+
962
1022
  // this builds new Representatives, both when creating a new instance and
963
1023
  // for reanimating an existing one when the old rep gets GCed
964
1024
 
@@ -973,10 +1033,11 @@ export const makeVirtualObjectManager = (
973
1033
  const deleteStoredVO = baseRef => {
974
1034
  let doMoreGC = false;
975
1035
  const record = dataCache.get(baseRef);
1036
+ assert(record !== undefined);
976
1037
  for (const valueCD of Object.values(record.capdatas)) {
977
- valueCD.slots.forEach(vref => {
1038
+ for (const vref of valueCD.slots) {
978
1039
  doMoreGC = vrm.removeReachableVref(vref) || doMoreGC;
979
- });
1040
+ }
980
1041
  }
981
1042
  dataCache.delete(baseRef);
982
1043
  return doMoreGC;
@@ -1015,6 +1076,7 @@ export const makeVirtualObjectManager = (
1015
1076
  if (isDurable) {
1016
1077
  insistDurableCapdata(vrm, prop, valueCD, true);
1017
1078
  }
1079
+ // eslint-disable-next-line github/array-foreach
1018
1080
  valueCD.slots.forEach(vrm.addReachableVref);
1019
1081
  capdatas[prop] = valueCD;
1020
1082
  valueMap.set(prop, value);
@@ -1030,10 +1092,63 @@ export const makeVirtualObjectManager = (
1030
1092
  val = makeRepresentative(proto, baseRef);
1031
1093
  }
1032
1094
  registerValue(baseRef, val, multifaceted);
1033
- finish?.(contextCache.get(baseRef));
1095
+ finish && finish(contextCache.get(baseRef));
1034
1096
  return val;
1035
1097
  };
1036
1098
 
1099
+ if (receiveAmplifier) {
1100
+ assert(contextProviderKit);
1101
+
1102
+ // Amplify a facet to a cohort
1103
+ const amplify = exoFacet => {
1104
+ for (const cp of values(contextProviderKit)) {
1105
+ const context = cp(exoFacet);
1106
+ if (context !== undefined) {
1107
+ return context.facets;
1108
+ }
1109
+ }
1110
+ throw Fail`Must be a facet of ${q(tag)}: ${exoFacet}`;
1111
+ };
1112
+ harden(amplify);
1113
+ receiveAmplifier(amplify);
1114
+ }
1115
+
1116
+ if (receiveInstanceTester) {
1117
+ if (multifaceted) {
1118
+ assert(contextProviderKit);
1119
+
1120
+ const isInstance = (exoFacet, facetName = undefined) => {
1121
+ if (facetName === undefined) {
1122
+ // Is exoFacet and instance of any facet of this class kit?
1123
+ return values(contextProviderKit).some(
1124
+ cp => cp(exoFacet) !== undefined,
1125
+ );
1126
+ }
1127
+ // Is this exoFacet an instance of this specific facet column
1128
+ // of this class kit?
1129
+ assert.typeof(facetName, 'string');
1130
+ const cp = contextProviderKit[facetName];
1131
+ cp !== undefined ||
1132
+ Fail`exo class kit ${q(tag)} has no facet named ${q(facetName)}`;
1133
+ return cp(exoFacet) !== undefined;
1134
+ };
1135
+ harden(isInstance);
1136
+ receiveInstanceTester(isInstance);
1137
+ } else {
1138
+ assert(contextProvider);
1139
+ // Is this exo an instance of this class?
1140
+ const isInstance = (exo, facetName = undefined) => {
1141
+ facetName === undefined ||
1142
+ Fail`facetName can only be used with an exo class kit: ${q(
1143
+ tag,
1144
+ )} has no facet ${q(facetName)}`;
1145
+ return contextProvider(exo) !== undefined;
1146
+ };
1147
+ harden(isInstance);
1148
+ receiveInstanceTester(isInstance);
1149
+ }
1150
+ }
1151
+
1037
1152
  return makeNewInstance;
1038
1153
  };
1039
1154
 
@@ -1082,6 +1197,7 @@ export const makeVirtualObjectManager = (
1082
1197
  return id;
1083
1198
  };
1084
1199
 
1200
+ /** @type {import('./vatDataTypes').VatData['defineKind']} */
1085
1201
  const defineKind = (tag, init, behavior, options) => {
1086
1202
  const kindID = `${allocateExportID()}`;
1087
1203
  saveVirtualKindDescriptor(kindID, { kindID, tag });
@@ -1097,6 +1213,7 @@ export const makeVirtualObjectManager = (
1097
1213
  );
1098
1214
  };
1099
1215
 
1216
+ /** @type {import('./vatDataTypes').VatData['defineKindMulti']} */
1100
1217
  const defineKindMulti = (tag, init, behavior, options) => {
1101
1218
  const kindID = `${allocateExportID()}`;
1102
1219
  saveVirtualKindDescriptor(kindID, { kindID, tag });
@@ -1115,7 +1232,7 @@ export const makeVirtualObjectManager = (
1115
1232
  /**
1116
1233
  *
1117
1234
  * @param {string} tag
1118
- * @returns {import('@agoric/vat-data').DurableKindHandle}
1235
+ * @returns {DurableKindHandle}
1119
1236
  */
1120
1237
  const makeKindHandle = tag => {
1121
1238
  assert(kindIDID, 'initializeKindHandleKind not called yet');
@@ -1126,9 +1243,9 @@ export const makeVirtualObjectManager = (
1126
1243
  nextInstanceIDs.set(kindID, nextInstanceID);
1127
1244
  saveDurableKindDescriptor(durableKindDescriptor);
1128
1245
  saveNextInstanceID(kindID);
1129
- /** @type {import('@agoric/vat-data').DurableKindHandle} */
1130
- // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- https://github.com/Agoric/agoric-sdk/issues/4620
1131
- // @ts-ignore cast
1246
+ /** @type {DurableKindHandle} */
1247
+
1248
+ // @ts-expect-error cast
1132
1249
  const kindHandle = Far('kind', {});
1133
1250
  kindHandleToID.set(kindHandle, kindID);
1134
1251
  const kindIDvref = makeBaseRef(kindIDID, kindID, true);
@@ -1136,6 +1253,7 @@ export const makeVirtualObjectManager = (
1136
1253
  return kindHandle;
1137
1254
  };
1138
1255
 
1256
+ /** @type {import('./vatDataTypes').VatData['defineDurableKind']} */
1139
1257
  const defineDurableKind = (kindHandle, init, behavior, options) => {
1140
1258
  kindHandleToID.has(kindHandle) || Fail`unknown handle ${kindHandle}`;
1141
1259
  const kindID = kindHandleToID.get(kindHandle);
@@ -1158,6 +1276,7 @@ export const makeVirtualObjectManager = (
1158
1276
  return maker;
1159
1277
  };
1160
1278
 
1279
+ /** @type {import('./vatDataTypes').VatData['defineDurableKindMulti']} */
1161
1280
  const defineDurableKindMulti = (kindHandle, init, behavior, options) => {
1162
1281
  kindHandleToID.has(kindHandle) || Fail`unknown handle ${kindHandle}`;
1163
1282
  const kindID = kindHandleToID.get(kindHandle);
@@ -682,6 +682,54 @@ export function makeVirtualReferenceManager(
682
682
  return size;
683
683
  }
684
684
 
685
+ /**
686
+ * Counters to track the next number for various categories of allocation.
687
+ * `exportID` starts at 1 because 'o+0' is always automatically
688
+ * pre-assigned to the root object.
689
+ * `promiseID` starts at 5 as a very minor aid to debugging: when puzzling
690
+ * over trace logs and the like, it helps for the numbers in various species
691
+ * of IDs that are jumbled together to be a little out of sync and thus a
692
+ * little less similar to each other.
693
+ */
694
+ const initialIDCounters = { exportID: 1, collectionID: 1, promiseID: 5 };
695
+ /** @type {Record<string, number>} */
696
+ let idCounters;
697
+ let idCountersAreDirty = false;
698
+
699
+ function initializeIDCounters() {
700
+ if (!idCounters) {
701
+ // the saved value might be missing, or from an older liveslots
702
+ // (with fewer counters), so merge it with our initial values
703
+ const saved = JSON.parse(syscall.vatstoreGet('idCounters') || '{}');
704
+ idCounters = { ...initialIDCounters, ...saved };
705
+ idCountersAreDirty = true;
706
+ }
707
+ }
708
+
709
+ function allocateNextID(name) {
710
+ if (!idCounters) {
711
+ // Normally `initializeIDCounters` would be called from startVat, but some
712
+ // tests bypass that so this is a backstop. Note that the invocation from
713
+ // startVat is there to make vatStore access patterns a bit more
714
+ // consistent from one vat to another, principally as a confusion
715
+ // reduction measure in service of debugging; it is not a correctness
716
+ // issue.
717
+ initializeIDCounters();
718
+ }
719
+ const result = idCounters[name];
720
+ result !== undefined || Fail`unknown idCounters[${name}]`;
721
+ idCounters[name] += 1;
722
+ idCountersAreDirty = true;
723
+ return result;
724
+ }
725
+
726
+ function flushIDCounters() {
727
+ if (idCountersAreDirty) {
728
+ syscall.vatstoreSet('idCounters', JSON.stringify(idCounters));
729
+ idCountersAreDirty = false;
730
+ }
731
+ }
732
+
685
733
  const testHooks = {
686
734
  getReachableRefCount,
687
735
  countCollectionsForWeakKey,
@@ -726,6 +774,9 @@ export function makeVirtualReferenceManager(
726
774
  ceaseRecognition,
727
775
  setDeleteCollectionEntry,
728
776
  getRetentionStats,
777
+ initializeIDCounters,
778
+ allocateNextID,
779
+ flushIDCounters,
729
780
  testHooks,
730
781
  });
731
782
  }
@@ -1,11 +1,19 @@
1
+ // @ts-check
1
2
  // no-lonely-if is a stupid rule that really should be disabled globally
2
3
  /* eslint-disable no-lonely-if */
3
4
 
5
+ import { Fail } from '@endo/errors';
6
+ import { E } from '@endo/eventual-send';
4
7
  import { assert } from '@agoric/assert';
5
8
  import { initEmpty, M } from '@agoric/store';
6
- import { E } from '@endo/eventual-send';
7
9
  import { parseVatSlot } from './parseVatSlots.js';
8
10
 
11
+ /**
12
+ * @template V
13
+ * @template {any[]} [A=unknown[]]
14
+ * @typedef {[watcher: import('./types.js').PromiseWatcher<V, A>, ...args: A]} PromiseWatcherTuple
15
+ */
16
+
9
17
  /**
10
18
  * @param {object} options
11
19
  * @param {*} options.syscall
@@ -28,17 +36,28 @@ export function makeWatchedPromiseManager({
28
36
  const { makeScalarBigMapStore } = collectionManager;
29
37
  const { defineDurableKind } = vom;
30
38
 
31
- // virtual Store (not durable) mapping vpid to Promise objects, to
32
- // maintain the slotToVal registration until resolution. Without
33
- // this, slotToVal would forget local Promises that aren't exported.
39
+ /**
40
+ * virtual Store (not durable) mapping vpid to Promise objects, to
41
+ * maintain the slotToVal registration until resolution. Without
42
+ * this, slotToVal would forget local Promises that aren't exported.
43
+ *
44
+ * @type {MapStore<string, Promise<unknown>>}
45
+ */
34
46
  let promiseRegistrations;
35
47
 
36
- // watched promises by vpid: each entry is an array of watches on the
37
- // corresponding vpid; each of these is in turn an array of a watcher object
38
- // and the arguments associated with it by `watchPromise`.
48
+ /**
49
+ * watched promises by vpid: each entry is an array of watches on the
50
+ * corresponding vpid; each of these is in turn an array of a watcher object
51
+ * and the arguments associated with it by `watchPromise`.
52
+ * @type {MapStore<string, PromiseWatcherTuple<unknown>[]>}
53
+ */
39
54
  let watchedPromiseTable;
40
55
 
41
- // defined promise watcher objects indexed by kindHandle
56
+ /**
57
+ * defined promise watcher objects indexed by kindHandle
58
+ *
59
+ * @type {MapStore<import('./vatDataTypes.js').DurableKindHandle, import('./types.js').PromiseWatcher<unknown>>}
60
+ */
42
61
  let promiseWatcherByKindTable;
43
62
 
44
63
  function preparePromiseWatcherTables() {
@@ -73,11 +92,17 @@ export function makeWatchedPromiseManager({
73
92
  }
74
93
 
75
94
  /**
76
- *
77
- * @param {Promise<unknown>} p
95
+ * @template T
96
+ * @param {Promise<T>} p
78
97
  * @param {string} vpid
98
+ * @returns {void}
79
99
  */
80
100
  function pseudoThen(p, vpid) {
101
+ /**
102
+ *
103
+ * @param {T} value
104
+ * @param {boolean} wasFulfilled
105
+ */
81
106
  function settle(value, wasFulfilled) {
82
107
  const watches = watchedPromiseTable.get(vpid);
83
108
  watchedPromiseTable.delete(vpid);
@@ -121,20 +146,34 @@ export function makeWatchedPromiseManager({
121
146
  }
122
147
  }
123
148
 
149
+ /**
150
+ * @template V
151
+ * @template {any[]} A]
152
+ * @param {import('./vatDataTypes.js').DurableKindHandle} kindHandle
153
+ * @param {(value: V, ...args: A) => void} fulfillHandler
154
+ * @param {(reason: any, ...args: A) => void} rejectHandler
155
+ * @returns {import('./types.js').PromiseWatcher<V, A>}
156
+ */
124
157
  function providePromiseWatcher(
125
158
  kindHandle,
126
- fulfillHandler = x => x,
127
- rejectHandler = x => {
128
- throw x;
159
+ // @ts-expect-error xxx rest params in typedef
160
+ fulfillHandler = _value => {
161
+ // It's fine to not pass the value through since promise watchers are not chainable
162
+ },
163
+ // @ts-expect-error xxx rest params in typedef
164
+ rejectHandler = reason => {
165
+ // Replicate the unhandled rejection that would have happened if the
166
+ // watcher had not implemented an `onRejected` method. See `settle` above
167
+ throw reason;
129
168
  },
130
169
  ) {
131
170
  assert.typeof(fulfillHandler, 'function');
132
171
  assert.typeof(rejectHandler, 'function');
133
172
 
134
173
  const makeWatcher = defineDurableKind(kindHandle, initEmpty, {
135
- // @ts-expect-error TS is confused by the spread operator
174
+ /** @type {(context: unknown, res: V, ...args: A) => void} */
136
175
  onFulfilled: (_context, res, ...args) => fulfillHandler(res, ...args),
137
- // @ts-expect-error
176
+ /** @type {(context: unknown, rej: unknown, ...args: A) => void} */
138
177
  onRejected: (_context, rej, ...args) => rejectHandler(rej, ...args),
139
178
  });
140
179
 
@@ -147,6 +186,9 @@ export function makeWatchedPromiseManager({
147
186
  }
148
187
  }
149
188
 
189
+ /**
190
+ * @type {<P extends Promise<any>, A extends any[]>(p: P, watcher: import('./types.js').PromiseWatcher<Awaited<P>, A>, ...args: A) => void}
191
+ */
150
192
  function watchPromise(p, watcher, ...args) {
151
193
  // The following wrapping defers setting up the promise watcher itself to a
152
194
  // later turn so that if the promise to be watched was the return value from
@@ -163,7 +205,10 @@ export function makeWatchedPromiseManager({
163
205
  const watcherVref = convertValToSlot(watcher);
164
206
  assert(watcherVref, 'invalid watcher');
165
207
  const { virtual, durable } = parseVatSlot(watcherVref);
166
- assert(virtual || durable, 'promise watcher must be a virtual object');
208
+ virtual ||
209
+ durable ||
210
+ // separate line so easy to breakpoint on
211
+ Fail`promise watcher must be a virtual object`;
167
212
  if (watcher.onFulfilled) {
168
213
  assert.typeof(watcher.onFulfilled, 'function');
169
214
  }
@@ -1,10 +1,9 @@
1
1
  import test from 'ava';
2
- import '@endo/init/debug.js';
3
2
 
4
3
  import { Far } from '@endo/marshal';
4
+ import { kunser } from '@agoric/kmarshal';
5
5
  import { setupTestLiveslots } from './liveslots-helpers.js';
6
6
  import { vstr } from './util.js';
7
- import { kunser } from './kmarshal.js';
8
7
  import { parseVatSlot } from '../src/parseVatSlots.js';
9
8
 
10
9
  function buildRootObject(vatPowers, vatParameters, baggage) {
@@ -1,5 +1,4 @@
1
1
  import test from 'ava';
2
- import '@endo/init/debug.js';
3
2
 
4
3
  import { makeCache } from '../src/cache.js';
5
4