@agoric/swingset-liveslots 0.10.3-mainnet1B-dev-b0c1f78.0 → 0.10.3-orchestration-dev-096c4e8.0

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 (58) hide show
  1. package/README.md +2 -0
  2. package/package.json +25 -17
  3. package/src/cache.js +5 -3
  4. package/src/collectionManager.js +147 -69
  5. package/src/index.js +2 -1
  6. package/src/liveslots.js +52 -79
  7. package/src/message.js +4 -4
  8. package/src/types.js +8 -2
  9. package/src/vatDataTypes.d.ts +234 -0
  10. package/src/vatDataTypes.js +2 -0
  11. package/src/vatstore-iterators.js +2 -0
  12. package/src/virtualObjectManager.js +107 -63
  13. package/src/virtualReferences.js +51 -0
  14. package/src/watchedPromises.js +50 -15
  15. package/test/gc-and-finalize.js +30 -1
  16. package/test/gc-helpers.js +2 -1
  17. package/test/liveslots-helpers.js +6 -6
  18. package/test/mock-gc.js +1 -0
  19. package/test/storeGC/test-lifecycle.js +2 -2
  20. package/test/storeGC/test-refcount-management.js +1 -2
  21. package/test/storeGC/test-scalar-store-kind.js +0 -1
  22. package/test/storeGC/test-weak-key.js +1 -2
  23. package/test/test-baggage.js +1 -2
  24. package/test/test-cache.js +0 -1
  25. package/test/test-collection-schema-refcount.js +1 -2
  26. package/test/test-collection-upgrade.js +1 -3
  27. package/test/test-collections.js +117 -14
  28. package/test/test-dropped-collection-weakrefs.js +1 -2
  29. package/test/test-durabilityChecks.js +3 -3
  30. package/test/test-facetiousness.js +1 -2
  31. package/test/test-gc-sensitivity.js +2 -2
  32. package/test/test-handled-promises.js +5 -7
  33. package/test/test-initial-vrefs.js +2 -3
  34. package/test/test-liveslots-mock-gc.js +2 -2
  35. package/test/test-liveslots-real-gc.js +44 -35
  36. package/test/test-liveslots.js +13 -14
  37. package/test/test-vo-test-harness.js +0 -1
  38. package/test/test-vpid-liveslots.js +4 -5
  39. package/test/util.js +2 -2
  40. package/test/vat-util.js +1 -1
  41. package/test/virtual-objects/test-cease-recognition.js +2 -2
  42. package/test/virtual-objects/test-cross-facet.js +1 -2
  43. package/test/virtual-objects/test-empty-data.js +1 -2
  44. package/test/virtual-objects/test-facets.js +1 -2
  45. package/test/virtual-objects/test-kind-changes.js +2 -2
  46. package/test/virtual-objects/test-reachable-vrefs.js +2 -2
  47. package/test/virtual-objects/test-rep-tostring.js +2 -3
  48. package/test/virtual-objects/test-retain-remotable.js +25 -24
  49. package/test/virtual-objects/test-state-shape.js +2 -2
  50. package/test/virtual-objects/test-virtualObjectGC.js +2 -2
  51. package/test/virtual-objects/test-virtualObjectManager.js +126 -8
  52. package/test/virtual-objects/test-vo-real-gc.js +8 -8
  53. package/test/virtual-objects/test-weakcollections-vref-handling.js +1 -2
  54. package/tools/fakeVirtualSupport.js +48 -21
  55. package/tools/prepare-test-env.js +13 -0
  56. package/tools/setup-vat-data.js +62 -0
  57. package/CHANGELOG.md +0 -85
  58. 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';
@@ -16,15 +22,15 @@ import {
16
22
 
17
23
  /** @template T @typedef {import('@agoric/vat-data').DefineKindOptions<T>} DefineKindOptions */
18
24
 
19
- const { hasOwn, defineProperty, getOwnPropertyNames } = Object;
20
- const { ownKeys } = Reflect;
21
- const { quote: q } = assert;
22
-
23
- // import { kdebug } from './kdebug.js';
25
+ /**
26
+ * @typedef {import('@endo/exo/src/exo-tools.js').ClassContextProvider } ClassContextProvider
27
+ *
28
+ * @typedef {import('@endo/exo/src/exo-tools.js').KitContextProvider } KitContextProvider
29
+ */
24
30
 
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 || {};
31
+ const { hasOwn, defineProperty, getOwnPropertyNames, entries, fromEntries } =
32
+ Object;
33
+ const { ownKeys } = Reflect;
28
34
 
29
35
  // Turn on to give each exo instance its own toStringTag value which exposes
30
36
  // the SwingSet vref.
@@ -33,9 +39,7 @@ const env = (globalThis.process || {}).env || {};
33
39
  // confidential object-creation activity, so this must not be something
34
40
  // that unprivileged vat code (including unprivileged contracts) can do
35
41
  // for themselves.
36
- const LABEL_INSTANCES = (env.DEBUG || '')
37
- .split(':')
38
- .includes('label-instances');
42
+ const LABEL_INSTANCES = environmentOptionsListHas('DEBUG', 'label-instances');
39
43
 
40
44
  // This file implements the "Virtual Objects" system, currently documented in
41
45
  // {@link https://github.com/Agoric/agoric-sdk/blob/master/packages/SwingSet/docs/virtual-objects.md})
@@ -132,40 +136,6 @@ const makeContextCache = (makeState, makeContext) => {
132
136
  return makeCache(readBacking, writeBacking, deleteBacking);
133
137
  };
134
138
 
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
139
  // The management of single Representatives (i.e. defineKind) is very similar
170
140
  // to that of a cohort of facets (i.e. defineKindMulti). In this description,
171
141
  // we use "self/facets" to refer to either 'self' or 'facets', as appropriate
@@ -260,15 +230,15 @@ const makeFacets = (
260
230
  };
261
231
 
262
232
  const insistDurableCapdata = (vrm, what, capdata, valueFor) => {
263
- capdata.slots.forEach((vref, idx) => {
233
+ for (const [idx, vref] of entries(capdata.slots)) {
264
234
  if (!vrm.isDurable(vref)) {
265
235
  if (valueFor) {
266
- Fail`value for ${what} is not durable: slot ${q(idx)} of ${capdata}`;
236
+ Fail`value for ${what} is not durable: slot ${b(idx)} of ${capdata}`;
267
237
  } else {
268
- Fail`${what} is not durable: slot ${q(idx)} of ${capdata}`;
238
+ Fail`${what} is not durable: slot ${b(idx)} of ${capdata}`;
269
239
  }
270
240
  }
271
- });
241
+ }
272
242
  };
273
243
 
274
244
  const insistSameCapData = (oldCD, newCD) => {
@@ -281,11 +251,11 @@ const insistSameCapData = (oldCD, newCD) => {
281
251
  if (oldCD.slots.length !== newCD.slots.length) {
282
252
  Fail`durable Kind stateShape mismatch (slots.length)`;
283
253
  }
284
- oldCD.slots.forEach((oldVref, idx) => {
254
+ for (const [idx, oldVref] of entries(oldCD.slots)) {
285
255
  if (newCD.slots[idx] !== oldVref) {
286
256
  Fail`durable Kind stateShape mismatch (slot[${idx}])`;
287
257
  }
288
- });
258
+ }
289
259
  };
290
260
 
291
261
  /**
@@ -306,7 +276,7 @@ const insistSameCapData = (oldCD, newCD) => {
306
276
  * @param {import('@endo/marshal').FromCapData<string>} unserialize Unserializer for this vat
307
277
  * @param {*} assertAcceptableSyscallCapdataSize Function to check for oversized
308
278
  * syscall params
309
- * @param {import('./types').LiveSlotsOptions} [liveSlotsOptions]
279
+ * @param {import('./types.js').LiveSlotsOptions} [liveSlotsOptions]
310
280
  * @param {{ WeakMap: typeof WeakMap, WeakSet: typeof WeakSet }} [powers]
311
281
  * Specifying the underlying WeakMap/WeakSet objects to wrap with
312
282
  * VirtualObjectAwareWeakMap/Set. By default, capture the ones currently
@@ -314,7 +284,7 @@ const insistSameCapData = (oldCD, newCD) => {
314
284
  * recursion if our returned WeakMap/WeakSet wrappers are subsequently installed
315
285
  * on globalThis.
316
286
  *
317
- * @returns {object} a new virtual object manager.
287
+ * @returns a new virtual object manager.
318
288
  *
319
289
  * The virtual object manager allows the creation of persistent objects that do
320
290
  * not need to occupy memory when they are not in use. It provides five
@@ -707,10 +677,16 @@ export const makeVirtualObjectManager = (
707
677
  durableKindDescriptor = undefined, // only for durables
708
678
  ) => {
709
679
  const {
710
- finish,
680
+ finish = undefined,
711
681
  stateShape = undefined,
712
682
  thisfulMethods = false,
683
+ } = options;
684
+ let {
685
+ // These are "let" rather than "const" only to accommodate code
686
+ // below that tolerates an old version of the vat-data package.
687
+ // See there for more explanation.
713
688
  interfaceGuard = undefined,
689
+ interfaceGuardKit = undefined,
714
690
  } = options;
715
691
 
716
692
  const statePrototype = {}; // Not frozen yet
@@ -731,11 +707,38 @@ export const makeVirtualObjectManager = (
731
707
  switch (assessFacetiousness(behavior)) {
732
708
  case 'one': {
733
709
  assert(!multifaceted);
710
+ interfaceGuardKit === undefined ||
711
+ Fail`Use an interfaceGuard, not interfaceGuardKit, to protect class ${q(
712
+ tag,
713
+ )}`;
734
714
  proposedFacetNames = undefined;
735
715
  break;
736
716
  }
737
717
  case 'many': {
738
718
  assert(multifaceted);
719
+
720
+ if (interfaceGuard && interfaceGuardKit === undefined) {
721
+ // This if clause is for the purpose of tolerating versions
722
+ // of the vata-data package that precede
723
+ // https://github.com/Agoric/agoric-sdk/pull/8220 .
724
+ // Before that PR, the options name `interfaceGuard` would
725
+ // actually carry the InterfaceGuardKit.
726
+ //
727
+ // Tolerating the old vat-data with the new types.
728
+ // at-expect-error here causes inconsistent reports, so
729
+ // doing the at-ts-ignore-error ritual instead.
730
+ // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
731
+ // @ts-ignore
732
+ interfaceGuardKit = interfaceGuard;
733
+ interfaceGuard = undefined;
734
+ // The rest of the code from here makes no further compromise
735
+ // for that old version of the vat-data package.
736
+ }
737
+
738
+ interfaceGuard === undefined ||
739
+ Fail`Use an interfaceGuardKit, not an interfaceGuard, to protect class kit ${q(
740
+ tag,
741
+ )}`;
739
742
  proposedFacetNames = ownKeys(behavior).sort();
740
743
  break;
741
744
  }
@@ -850,7 +853,9 @@ export const makeVirtualObjectManager = (
850
853
  return harden({
851
854
  get() {
852
855
  const baseRef = getBaseRef(this);
853
- const { valueMap, capdatas } = dataCache.get(baseRef);
856
+ const record = dataCache.get(baseRef);
857
+ assert(record !== undefined);
858
+ const { valueMap, capdatas } = record;
854
859
  if (!valueMap.has(prop)) {
855
860
  const value = harden(unserialize(capdatas[prop]));
856
861
  checkStatePropertyValue(value, prop);
@@ -867,6 +872,7 @@ export const makeVirtualObjectManager = (
867
872
  insistDurableCapdata(vrm, prop, capdata, true);
868
873
  }
869
874
  const record = dataCache.get(baseRef); // mutable
875
+ assert(record !== undefined);
870
876
  const oldSlots = record.capdatas[prop].slots;
871
877
  const newSlots = capdata.slots;
872
878
  vrm.updateReferenceCounts(oldSlots, newSlots);
@@ -890,7 +896,9 @@ export const makeVirtualObjectManager = (
890
896
  const makeState = baseRef => {
891
897
  const state = { __proto__: statePrototype };
892
898
  if (stateShape === undefined) {
893
- for (const prop of ownKeys(dataCache.get(baseRef).capdatas)) {
899
+ const record = dataCache.get(baseRef);
900
+ assert(record !== undefined);
901
+ for (const prop of ownKeys(record.capdatas)) {
894
902
  assert(typeof prop === 'string');
895
903
  checkStateProperty(prop);
896
904
  defineProperty(state, prop, makeFieldDescriptor(prop));
@@ -941,17 +949,47 @@ export const makeVirtualObjectManager = (
941
949
 
942
950
  let proto;
943
951
  if (multifaceted) {
952
+ /** @type { Record<string, KitContextProvider> } */
953
+ const contextProviderKit = fromEntries(
954
+ facetNames.map((name, index) => [
955
+ name,
956
+ rep => {
957
+ const vref = getSlotForVal(rep);
958
+ assert(vref !== undefined);
959
+ const { baseRef, facet } = parseVatSlot(vref);
960
+
961
+ // Without this check, an attacker (with access to both
962
+ // cohort1.facetA and cohort2.facetB)
963
+ // could effectively forge access to
964
+ // cohort1.facetB and cohort2.facetA.
965
+ // They could not forge the identity of those two
966
+ // objects, but they could invoke all their equivalent methods,
967
+ // by using e.g.
968
+ // cohort1.facetA.foo.apply(cohort2.facetB, [...args])
969
+ Number(facet) === index || Fail`illegal cross-facet access`;
970
+
971
+ return harden(contextCache.get(baseRef));
972
+ },
973
+ ]),
974
+ );
975
+
944
976
  proto = defendPrototypeKit(
945
977
  tag,
946
- makeContextProviderKit(contextCache, getSlotForVal, facetNames),
978
+ harden(contextProviderKit),
947
979
  behavior,
948
980
  thisfulMethods,
949
- interfaceGuard,
981
+ interfaceGuardKit,
950
982
  );
951
983
  } else {
984
+ /** @type {ClassContextProvider} */
985
+ const contextProvider = rep => {
986
+ const slot = getSlotForVal(rep);
987
+ assert(slot !== undefined);
988
+ return harden(contextCache.get(slot));
989
+ };
952
990
  proto = defendPrototype(
953
991
  tag,
954
- makeContextProvider(contextCache, getSlotForVal),
992
+ harden(contextProvider),
955
993
  behavior,
956
994
  thisfulMethods,
957
995
  interfaceGuard,
@@ -973,10 +1011,11 @@ export const makeVirtualObjectManager = (
973
1011
  const deleteStoredVO = baseRef => {
974
1012
  let doMoreGC = false;
975
1013
  const record = dataCache.get(baseRef);
1014
+ assert(record !== undefined);
976
1015
  for (const valueCD of Object.values(record.capdatas)) {
977
- valueCD.slots.forEach(vref => {
1016
+ for (const vref of valueCD.slots) {
978
1017
  doMoreGC = vrm.removeReachableVref(vref) || doMoreGC;
979
- });
1018
+ }
980
1019
  }
981
1020
  dataCache.delete(baseRef);
982
1021
  return doMoreGC;
@@ -1015,6 +1054,7 @@ export const makeVirtualObjectManager = (
1015
1054
  if (isDurable) {
1016
1055
  insistDurableCapdata(vrm, prop, valueCD, true);
1017
1056
  }
1057
+ // eslint-disable-next-line github/array-foreach
1018
1058
  valueCD.slots.forEach(vrm.addReachableVref);
1019
1059
  capdatas[prop] = valueCD;
1020
1060
  valueMap.set(prop, value);
@@ -1030,7 +1070,7 @@ export const makeVirtualObjectManager = (
1030
1070
  val = makeRepresentative(proto, baseRef);
1031
1071
  }
1032
1072
  registerValue(baseRef, val, multifaceted);
1033
- finish?.(contextCache.get(baseRef));
1073
+ finish && finish(contextCache.get(baseRef));
1034
1074
  return val;
1035
1075
  };
1036
1076
 
@@ -1082,6 +1122,7 @@ export const makeVirtualObjectManager = (
1082
1122
  return id;
1083
1123
  };
1084
1124
 
1125
+ /** @type {import('./vatDataTypes').VatData['defineKind']} */
1085
1126
  const defineKind = (tag, init, behavior, options) => {
1086
1127
  const kindID = `${allocateExportID()}`;
1087
1128
  saveVirtualKindDescriptor(kindID, { kindID, tag });
@@ -1097,6 +1138,7 @@ export const makeVirtualObjectManager = (
1097
1138
  );
1098
1139
  };
1099
1140
 
1141
+ /** @type {import('./vatDataTypes').VatData['defineKindMulti']} */
1100
1142
  const defineKindMulti = (tag, init, behavior, options) => {
1101
1143
  const kindID = `${allocateExportID()}`;
1102
1144
  saveVirtualKindDescriptor(kindID, { kindID, tag });
@@ -1136,6 +1178,7 @@ export const makeVirtualObjectManager = (
1136
1178
  return kindHandle;
1137
1179
  };
1138
1180
 
1181
+ /** @type {import('./vatDataTypes').VatData['defineDurableKind']} */
1139
1182
  const defineDurableKind = (kindHandle, init, behavior, options) => {
1140
1183
  kindHandleToID.has(kindHandle) || Fail`unknown handle ${kindHandle}`;
1141
1184
  const kindID = kindHandleToID.get(kindHandle);
@@ -1158,6 +1201,7 @@ export const makeVirtualObjectManager = (
1158
1201
  return maker;
1159
1202
  };
1160
1203
 
1204
+ /** @type {import('./vatDataTypes').VatData['defineDurableKindMulti']} */
1161
1205
  const defineDurableKindMulti = (kindHandle, init, behavior, options) => {
1162
1206
  kindHandleToID.has(kindHandle) || Fail`unknown handle ${kindHandle}`;
1163
1207
  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,3 +1,4 @@
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
 
@@ -6,6 +7,12 @@ import { initEmpty, M } from '@agoric/store';
6
7
  import { E } from '@endo/eventual-send';
7
8
  import { parseVatSlot } from './parseVatSlots.js';
8
9
 
10
+ /**
11
+ * @template V
12
+ * @template {any[]} [A=unknown[]]
13
+ * @typedef {[watcher: import('./types.js').PromiseWatcher<V, A>, ...args: A]} PromiseWatcherTuple
14
+ */
15
+
9
16
  /**
10
17
  * @param {object} options
11
18
  * @param {*} options.syscall
@@ -28,17 +35,28 @@ export function makeWatchedPromiseManager({
28
35
  const { makeScalarBigMapStore } = collectionManager;
29
36
  const { defineDurableKind } = vom;
30
37
 
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.
38
+ /**
39
+ * virtual Store (not durable) mapping vpid to Promise objects, to
40
+ * maintain the slotToVal registration until resolution. Without
41
+ * this, slotToVal would forget local Promises that aren't exported.
42
+ *
43
+ * @type {MapStore<string, Promise<unknown>>}
44
+ */
34
45
  let promiseRegistrations;
35
46
 
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`.
47
+ /**
48
+ * watched promises by vpid: each entry is an array of watches on the
49
+ * corresponding vpid; each of these is in turn an array of a watcher object
50
+ * and the arguments associated with it by `watchPromise`.
51
+ * @type {MapStore<string, PromiseWatcherTuple<unknown>[]>}
52
+ */
39
53
  let watchedPromiseTable;
40
54
 
41
- // defined promise watcher objects indexed by kindHandle
55
+ /**
56
+ * defined promise watcher objects indexed by kindHandle
57
+ *
58
+ * @type {MapStore<import('./vatDataTypes.js').DurableKindHandle, import('./types.js').PromiseWatcher<unknown>>}
59
+ */
42
60
  let promiseWatcherByKindTable;
43
61
 
44
62
  function preparePromiseWatcherTables() {
@@ -73,11 +91,17 @@ export function makeWatchedPromiseManager({
73
91
  }
74
92
 
75
93
  /**
76
- *
77
- * @param {Promise<unknown>} p
94
+ * @template T
95
+ * @param {Promise<T>} p
78
96
  * @param {string} vpid
97
+ * @returns {void}
79
98
  */
80
99
  function pseudoThen(p, vpid) {
100
+ /**
101
+ *
102
+ * @param {T} value
103
+ * @param {boolean} wasFulfilled
104
+ */
81
105
  function settle(value, wasFulfilled) {
82
106
  const watches = watchedPromiseTable.get(vpid);
83
107
  watchedPromiseTable.delete(vpid);
@@ -121,20 +145,28 @@ export function makeWatchedPromiseManager({
121
145
  }
122
146
  }
123
147
 
148
+ /**
149
+ * @template V
150
+ * @template {any[]} A]
151
+ * @param {import('./vatDataTypes.js').DurableKindHandle} kindHandle
152
+ * @param {(value: V, ...args: A) => void} fulfillHandler
153
+ * @param {(reason: any, ...args: A) => void} rejectHandler
154
+ * @returns {import('./types.js').PromiseWatcher<V, A>}
155
+ */
124
156
  function providePromiseWatcher(
125
157
  kindHandle,
126
- fulfillHandler = x => x,
127
- rejectHandler = x => {
128
- throw x;
129
- },
158
+ // @ts-expect-error xxx rest params in typedef
159
+ fulfillHandler = _value => {},
160
+ // @ts-expect-error xxx rest params in typedef
161
+ rejectHandler = _reason => {},
130
162
  ) {
131
163
  assert.typeof(fulfillHandler, 'function');
132
164
  assert.typeof(rejectHandler, 'function');
133
165
 
134
166
  const makeWatcher = defineDurableKind(kindHandle, initEmpty, {
135
- // @ts-expect-error TS is confused by the spread operator
167
+ /** @type {(context: unknown, res: V, ...args: A) => void} */
136
168
  onFulfilled: (_context, res, ...args) => fulfillHandler(res, ...args),
137
- // @ts-expect-error
169
+ /** @type {(context: unknown, rej: unknown, ...args: A) => void} */
138
170
  onRejected: (_context, rej, ...args) => rejectHandler(rej, ...args),
139
171
  });
140
172
 
@@ -147,6 +179,9 @@ export function makeWatchedPromiseManager({
147
179
  }
148
180
  }
149
181
 
182
+ /**
183
+ * @type {<P extends Promise<any>, A extends any[]>(p: P, watcher: import('./types.js').PromiseWatcher<Awaited<P>, A>, ...args: A) => void}
184
+ */
150
185
  function watchPromise(p, watcher, ...args) {
151
186
  // The following wrapping defers setting up the promise watcher itself to a
152
187
  // later turn so that if the promise to be watched was the return value from
@@ -1,4 +1,4 @@
1
- /* global setImmediate */
1
+ /* global setImmediate, FinalizationRegistry */
2
2
 
3
3
  /* A note on our GC terminology:
4
4
  *
@@ -89,3 +89,32 @@ export function makeGcAndFinalize(gcPower) {
89
89
  await new Promise(setImmediate);
90
90
  };
91
91
  }
92
+
93
+ const fr = new FinalizationRegistry(({ promise, resolve }) => {
94
+ promise.result = true;
95
+ resolve(true);
96
+ });
97
+
98
+ const makeCollectedResultKit = () => {
99
+ /** @type {(val: true) => void} */
100
+ let resolve;
101
+
102
+ const promise = /** @type {Promise<true> & {result: boolean}} */ (
103
+ new Promise(r => {
104
+ resolve = r;
105
+ })
106
+ );
107
+ promise.result = false;
108
+ return {
109
+ promise,
110
+ // @ts-expect-error
111
+ resolve,
112
+ };
113
+ };
114
+
115
+ export function watchCollected(target) {
116
+ const kit = makeCollectedResultKit();
117
+ fr.register(target, kit);
118
+
119
+ return kit.promise;
120
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { Far } from '@endo/marshal';
4
4
  import { M } from '@agoric/store';
5
- import { kslot, kser } from './kmarshal.js';
5
+ import { kslot, kser } from '@agoric/kmarshal';
6
6
  import { parseVatSlot } from '../src/parseVatSlots.js';
7
7
 
8
8
  // These tests follow the model described in
@@ -168,6 +168,7 @@ export function* enumerateKeysWithPrefix(fakestore, prefix) {
168
168
  }
169
169
  }
170
170
  }
171
+ harden(enumerateKeysWithPrefix);
171
172
 
172
173
  export function recognizersOf(v, baseref) {
173
174
  // the | is followed by the collectionID that can recognize baseref
@@ -1,6 +1,7 @@
1
1
  /* global WeakRef, FinalizationRegistry */
2
- import engineGC from './engine-gc.js';
2
+ import { kser } from '@agoric/kmarshal';
3
3
 
4
+ import engineGC from './engine-gc.js';
4
5
  import { waitUntilQuiescent } from './waitUntilQuiescent.js';
5
6
  import { makeGcAndFinalize } from './gc-and-finalize.js';
6
7
  import { makeDummyMeterControl } from './dummyMeterControl.js';
@@ -12,12 +13,11 @@ import {
12
13
  makeRetireExports,
13
14
  makeBringOutYourDead,
14
15
  } from './util.js';
15
- import { kser } from './kmarshal.js';
16
16
 
17
17
  /**
18
18
  * @param {object} [options]
19
- * @param {boolean} [options.skipLogging = false]
20
- * @param {Map<string, string>} [options.kvStore = new Map()]
19
+ * @param {boolean} [options.skipLogging]
20
+ * @param {Map<string, string>} [options.kvStore]
21
21
  */
22
22
  export function buildSyscall(options = {}) {
23
23
  const { skipLogging = false, kvStore: fakestore = new Map() } = options;
@@ -168,9 +168,9 @@ function makeRPMaker(nextNumber = 1) {
168
168
  * @param {string} vatName
169
169
  * @param {object} [options]
170
170
  * @param {boolean} [options.forceGC]
171
- * @param {Map<string, string>} [options.kvStore = new Map()]
171
+ * @param {Map<string, string>} [options.kvStore]
172
172
  * @param {number} [options.nextPromiseImportNumber]
173
- * @param {boolean} [options.skipLogging = false]
173
+ * @param {boolean} [options.skipLogging]
174
174
  */
175
175
  export async function setupTestLiveslots(
176
176
  t,
package/test/mock-gc.js CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  import { waitUntilQuiescent } from './waitUntilQuiescent.js';
2
3
  import { makeDummyMeterControl } from './dummyMeterControl.js';
3
4
 
@@ -1,12 +1,12 @@
1
+ // @ts-nocheck
1
2
  import test from 'ava';
2
- import '@endo/init/debug.js';
3
3
 
4
+ import { kslot, kunser } from '@agoric/kmarshal';
4
5
  import {
5
6
  setupTestLiveslots,
6
7
  findSyscallsByType,
7
8
  } from '../liveslots-helpers.js';
8
9
  import { buildRootObject, mainHeldIdx, mapRef } from '../gc-helpers.js';
9
- import { kslot, kunser } from '../kmarshal.js';
10
10
  import { parseVatSlot } from '../../src/parseVatSlots.js';
11
11
 
12
12
  // These tests follow the model described in
@@ -1,6 +1,6 @@
1
1
  import test from 'ava';
2
- import '@endo/init/debug.js';
3
2
 
3
+ import { kslot } from '@agoric/kmarshal';
4
4
  import {
5
5
  findSyscallsByType,
6
6
  setupTestLiveslots,
@@ -12,7 +12,6 @@ import {
12
12
  refValString,
13
13
  assertCollectionDeleted,
14
14
  } from '../gc-helpers.js';
15
- import { kslot } from '../kmarshal.js';
16
15
  import { vstr } from '../util.js';
17
16
 
18
17
  // These tests follow the model described in
@@ -1,5 +1,4 @@
1
1
  import test from 'ava';
2
- import '@endo/init/debug.js';
3
2
 
4
3
  import { setupTestLiveslots } from '../liveslots-helpers.js';
5
4
  import { buildRootObject, mapRef } from '../gc-helpers.js';
@@ -1,6 +1,6 @@
1
1
  import test from 'ava';
2
- import '@endo/init/debug.js';
3
2
 
3
+ import { kslot } from '@agoric/kmarshal';
4
4
  import { setupTestLiveslots } from '../liveslots-helpers.js';
5
5
  import {
6
6
  buildRootObject,
@@ -8,7 +8,6 @@ import {
8
8
  assertCollectionDeleted,
9
9
  deduceCollectionID,
10
10
  } from '../gc-helpers.js';
11
- import { kslot } from '../kmarshal.js';
12
11
  import { vstr } from '../util.js';
13
12
 
14
13
  // These tests follow the model described in test-virtualObjectGC.js