@agoric/swingset-liveslots 0.10.3-upgrade-14-dev-c8f9e7b.0 → 0.10.3-upgrade-16-dev-8879538.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.
- package/README.md +2 -0
- package/package.json +27 -19
- package/src/cache.js +5 -3
- package/src/capdata.js +0 -1
- package/src/collectionManager.js +154 -72
- package/src/index.js +4 -1
- package/src/liveslots.js +52 -81
- package/src/message.js +4 -4
- package/src/types.js +8 -2
- package/src/vatDataTypes.d.ts +271 -0
- package/src/vatDataTypes.js +2 -0
- package/src/vatstore-iterators.js +2 -0
- package/src/virtualObjectManager.js +189 -70
- package/src/virtualReferences.js +51 -0
- package/src/watchedPromises.js +56 -17
- package/test/{test-baggage.js → baggage.test.js} +1 -2
- package/test/{test-cache.js → cache.test.js} +0 -1
- package/test/{test-collection-schema-refcount.js → collection-schema-refcount.test.js} +1 -2
- package/test/{test-collection-upgrade.js → collection-upgrade.test.js} +1 -3
- package/test/{test-collections.js → collections.test.js} +117 -14
- package/test/{test-dropped-collection-weakrefs.js → dropped-collection-weakrefs.test.js} +1 -2
- package/test/{test-durabilityChecks.js → durabilityChecks.test.js} +3 -3
- package/test/{test-facetiousness.js → facetiousness.test.js} +1 -2
- package/test/gc-and-finalize.js +30 -1
- package/test/gc-helpers.js +2 -3
- package/test/{test-gc-sensitivity.js → gc-sensitivity.test.js} +2 -2
- package/test/{test-handled-promises.js → handled-promises.test.js} +5 -7
- package/test/{test-initial-vrefs.js → initial-vrefs.test.js} +2 -3
- package/test/liveslots-helpers.js +6 -6
- package/test/{test-liveslots-mock-gc.js → liveslots-mock-gc.test.js} +2 -2
- package/test/{test-liveslots-real-gc.js → liveslots-real-gc.test.js} +44 -35
- package/test/{test-liveslots.js → liveslots.test.js} +13 -14
- package/test/mock-gc.js +1 -0
- package/test/storeGC/{test-lifecycle.js → lifecycle.test.js} +2 -2
- package/test/storeGC/{test-refcount-management.js → refcount-management.test.js} +1 -2
- package/test/storeGC/{test-scalar-store-kind.js → scalar-store-kind.test.js} +0 -1
- package/test/storeGC/{test-weak-key.js → weak-key.test.js} +1 -2
- package/test/util.js +2 -2
- package/test/vat-util.js +1 -1
- package/test/virtual-objects/{test-cease-recognition.js → cease-recognition.test.js} +2 -2
- package/test/virtual-objects/{test-cross-facet.js → cross-facet.test.js} +5 -4
- package/test/virtual-objects/{test-empty-data.js → empty-data.test.js} +1 -2
- package/test/virtual-objects/{test-facets.js → facets.test.js} +1 -2
- package/test/virtual-objects/{test-kind-changes.js → kind-changes.test.js} +2 -2
- package/test/virtual-objects/{test-reachable-vrefs.js → reachable-vrefs.test.js} +2 -2
- package/test/virtual-objects/{test-rep-tostring.js → rep-tostring.test.js} +2 -3
- package/test/virtual-objects/{test-retain-remotable.js → retain-remotable.test.js} +25 -24
- package/test/virtual-objects/{test-state-shape.js → state-shape.test.js} +2 -2
- package/test/virtual-objects/{test-virtualObjectGC.js → virtualObjectGC.test.js} +2 -2
- package/test/virtual-objects/{test-virtualObjectManager.js → virtualObjectManager.test.js} +6 -2
- package/test/virtual-objects/{test-vo-real-gc.js → vo-real-gc.test.js} +8 -8
- package/test/virtual-objects/{test-weakcollections-vref-handling.js → weakcollections-vref-handling.test.js} +1 -2
- package/test/{test-vo-test-harness.js → vo-test-harness.test.js} +0 -1
- package/test/{test-vpid-liveslots.js → vpid-liveslots.test.js} +4 -5
- package/test/waitUntilQuiescent.js +2 -1
- package/tools/fakeVirtualSupport.js +41 -24
- package/tools/prepare-test-env.js +13 -0
- package/tools/setup-vat-data.js +62 -0
- package/CHANGELOG.md +0 -85
- 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 {
|
|
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
|
-
/**
|
|
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 {
|
|
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 = (
|
|
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
|
|
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 ${
|
|
241
|
+
Fail`value for ${what} is not durable: slot ${b(idx)} of ${capdata}`;
|
|
267
242
|
} else {
|
|
268
|
-
Fail`${what} is not durable: slot ${
|
|
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
|
|
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 {
|
|
306
|
-
* @param {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
995
|
+
harden(contextProviderKitVar),
|
|
947
996
|
behavior,
|
|
948
997
|
thisfulMethods,
|
|
949
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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 {
|
|
1130
|
-
|
|
1131
|
-
// @ts-
|
|
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);
|
package/src/virtualReferences.js
CHANGED
|
@@ -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
|
}
|
package/src/watchedPromises.js
CHANGED
|
@@ -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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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<
|
|
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,28 @@ 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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
},
|
|
159
|
+
// @ts-expect-error xxx rest params in typedef
|
|
160
|
+
fulfillHandler = _value => {},
|
|
161
|
+
// @ts-expect-error xxx rest params in typedef
|
|
162
|
+
rejectHandler = _reason => {},
|
|
130
163
|
) {
|
|
131
164
|
assert.typeof(fulfillHandler, 'function');
|
|
132
165
|
assert.typeof(rejectHandler, 'function');
|
|
133
166
|
|
|
134
167
|
const makeWatcher = defineDurableKind(kindHandle, initEmpty, {
|
|
135
|
-
|
|
168
|
+
/** @type {(context: unknown, res: V, ...args: A) => void} */
|
|
136
169
|
onFulfilled: (_context, res, ...args) => fulfillHandler(res, ...args),
|
|
137
|
-
|
|
170
|
+
/** @type {(context: unknown, rej: unknown, ...args: A) => void} */
|
|
138
171
|
onRejected: (_context, rej, ...args) => rejectHandler(rej, ...args),
|
|
139
172
|
});
|
|
140
173
|
|
|
@@ -147,6 +180,9 @@ export function makeWatchedPromiseManager({
|
|
|
147
180
|
}
|
|
148
181
|
}
|
|
149
182
|
|
|
183
|
+
/**
|
|
184
|
+
* @type {<P extends Promise<any>, A extends any[]>(p: P, watcher: import('./types.js').PromiseWatcher<Awaited<P>, A>, ...args: A) => void}
|
|
185
|
+
*/
|
|
150
186
|
function watchPromise(p, watcher, ...args) {
|
|
151
187
|
// The following wrapping defers setting up the promise watcher itself to a
|
|
152
188
|
// later turn so that if the promise to be watched was the return value from
|
|
@@ -163,7 +199,10 @@ export function makeWatchedPromiseManager({
|
|
|
163
199
|
const watcherVref = convertValToSlot(watcher);
|
|
164
200
|
assert(watcherVref, 'invalid watcher');
|
|
165
201
|
const { virtual, durable } = parseVatSlot(watcherVref);
|
|
166
|
-
|
|
202
|
+
virtual ||
|
|
203
|
+
durable ||
|
|
204
|
+
// separate line so easy to breakpoint on
|
|
205
|
+
Fail`promise watcher must be a virtual object`;
|
|
167
206
|
if (watcher.onFulfilled) {
|
|
168
207
|
assert.typeof(watcher.onFulfilled, 'function');
|
|
169
208
|
}
|
|
@@ -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) {
|