summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/structured-clone/transferable-cleanup.js
blob: e55cb480f438a9cca3163b3e2b95fc60e39c9da8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
/*
 * Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/licenses/publicdomain/
 */

// The tests expect the clone buffer to be collected at a certain point.
// I don't think collecting it sooner would break anything, but just in case:
gczeal(0);

function testBasic() {
    const desc = "basic canTransfer, writeTransfer, freeTransfer";
    const BASE = 100;
    const obj = makeSerializable(BASE + 1);
    let s = serialize(obj, [obj]);
    assertEq("" + obj.log, "" + [101, "?", 101, "W"], "serialize " + desc);
    s = null;
    gc();
    print(`serialize ${arguments.callee.name}: ${obj.log}`);
    assertEq("" + obj.log, "" + [
        // canTransfer(obj) then writeTransfer(obj), then obj is read with
        // a backreference.
        101, "?", 101, "W",
        // Then discard the serialized data, triggering freeTransfer(obj).
        101, "F"
    ], "serialize " + desc);
    obj.log = null;
}

function testErrorDuringWrite() {
    const desc = "canTransfer, write=>error";
    const BASE = 200;
    const obj = makeSerializable(BASE + 1);
    const ab = new ArrayBuffer(100);
    detachArrayBuffer(ab);
    try {
        serialize([obj, ab], [obj]);
    } catch (e) {
    }
    gc();
    print(`${arguments.callee.name}: ${obj.log}`);
    assertEq("" + obj.log, "" + [201, "?"], desc);
    obj.log = null;
}

function testErrorDuringTransfer() {
    const desc = "canTransfer, write(ab), writeTransfer(obj), writeTransfer(ab)=>error, freeTransfer";
    const BASE = 300;
    const obj = makeSerializable(BASE + 1);
    const ab = new ArrayBuffer(100);
    detachArrayBuffer(ab);
    try {
        serialize([obj, ab], [obj, ab]);
    } catch (e) {
    }
    gc();
    print(`${arguments.callee.name}: ${obj.log}`);
    assertEq("" + obj.log, "" + [
        // canTransfer(obj) then writeTransfer(obj)
        301, "?", 301, "W",
        // error reading ab, freeTransfer(obj)
        301, "F"
    ], desc);
    obj.log = null;
}

function testMultiOkHelper(g, BASE, desc) {
    const obj = makeSerializable(BASE + 1);
    const obj2 = makeSerializable(BASE + 2);
    const obj3 = makeSerializable(BASE + 3);
    serialize([obj, obj2, obj3], [obj, obj3]);
    gc();
    print(`${arguments.callee.name}(${BASE}): ${obj.log}`);
    assertEq("" + obj.log, "" + [
        // canTransfer(obj then obj3).
        BASE + 1, "?", BASE + 3, "?",
        // write(obj2), which happens before transferring.
        BASE + 2, "w",
        // writeTransfer(obj then obj3).
        BASE + 1, "W", BASE + 3, "W",
        // discard the clone buffer without deserializing, so freeTransfer(obj1+obj3).
        BASE + 1, "F", BASE + 3, "F"
    ], desc);
    obj.log = null;
}

function testMultiOk() {
    const desc = "write 3 objects, transfer obj1 and obj3 only, write obj2";
    testMultiOkHelper(globalThis, 400, desc);
}

function testMultiOkCrossRealm() {
    const desc = "write 3 objects, transfer obj1 and obj2 only, cross-realm";
    testMultiOkHelper(newGlobal({ newCompartment: true }), 500, desc);
}

function printTrace(callee, global, base, log, phase = undefined) {
    phase = phase ? `${phase} ` : "";
    const test = callee.replace("Helper", "") + (global === globalThis ? "" : "CrossRealm");
    print(`${phase}${test}(${base}): ${log}`);
}

function testMultiOkWithDeserializeHelper(g, BASE, desc) {
    const obj = makeSerializable(BASE + 1);
    const obj2 = makeSerializable(BASE + 2);
    const obj3 = makeSerializable(BASE + 3);
    let s = serialize([obj, obj2, obj3], [obj, obj3]);
    gc();
    printTrace(arguments.callee.name, g, BASE, obj.log, "serialize");
    assertEq("" + obj.log, "" + [
        // canTransfer(obj+obj3).
        BASE + 1, "?", BASE + 3, "?",
        // write(obj2).
        BASE + 2, "w",
        // writeTransfer(obj1+obj3). All good.
        BASE + 1, "W", BASE + 3, "W",
        // Do not collect the clone buffer. It owns obj1+obj3 now.
    ], "serialize " + desc);
    obj.log = null;

    let clone = deserialize(s);
    s = null;
    gc();
    printTrace(arguments.callee.name, g, BASE, obj.log, "deserialize");
    assertEq("" + obj.log, "" + [
        // readTransfer(obj1+obj3).
        BASE + 1, "R", BASE + 3, "R",
        // read(obj2). The full clone is successful.
        BASE + 2, "r",
    ], "deserialize " + desc);
    obj.log = null;
}

function testMultiOkWithDeserialize() {
    const desc = "write 3 objects, transfer obj1 and obj3 only, write obj2, deserialize";
    testMultiOkWithDeserializeHelper(globalThis, 600, desc);
}

function testMultiOkWithDeserializeCrossRealm() {
    const desc = "write 3 objects, transfer obj1 and obj2 only, deserialize, cross-realm";
    testMultiOkWithDeserializeHelper(newGlobal({ newCompartment: true }), 700, desc);
}

function testMultiWithDeserializeReadTransferErrorHelper(g, BASE, desc) {
    const obj = makeSerializable(BASE + 1, 0);
    const obj2 = makeSerializable(BASE + 2, 1);
    const obj3 = makeSerializable(BASE + 3, 1);
    let s = serialize([obj, obj2, obj3], [obj, obj3]);
    gc();
    printTrace(arguments.callee.name, g, BASE, obj.log, "serialize");
    assertEq("" + obj.log, "" + [
        // Successful serialization.
        BASE + 1, "?", BASE + 3, "?",
        BASE + 2, "w",
        BASE + 1, "W", BASE + 3, "W",
    ], "serialize " + desc);
    obj.log = null;

    try {
        let clone = deserialize(s);
    } catch (e) {
        assertEq(e.message.includes("invalid transferable"), true);
    }
    s = null;
    gc();
    printTrace(arguments.callee.name, g, BASE, obj.log, "deserialize");
    assertEq("" + obj.log, "" + [
        // readTransfer(obj) then readTransfer(obj3) which fails.
        BASE + 1, "R", BASE + 3, "R",
        // obj2 has not been read at all because we errored out during readTransferMap(),
        // which comes before the main reading. obj transfer data is now owned by its
        // clone. obj3 transfer data was not successfully handed over to a new object,
        // so it is still owned by the clone buffer and must be discarded with freeTransfer.
        BASE + 3, "F",
    ], "deserialize " + desc);
    obj.log = null;
}

function testMultiWithDeserializeReadTransferError() {
    const desc = "write 3 objects, transfer obj1 and obj3 only, fail during readTransfer(obj3)";
    testMultiWithDeserializeReadTransferErrorHelper(globalThis, 800, desc);
}

function testMultiWithDeserializeReadTransferErrorCrossRealm() {
    const desc = "write 3 objects, transfer obj1 and obj2 only, fail during readTransfer(obj3), cross-realm";
    testMultiWithDeserializeReadTransferErrorHelper(newGlobal({ newCompartment: true }), 900, desc);
}

function testMultiWithDeserializeReadErrorHelper(g, BASE, desc) {
    const obj = makeSerializable(BASE + 1, 2);
    const obj2 = makeSerializable(BASE + 2, 2);
    const obj3 = makeSerializable(BASE + 3, 2);
    let s = serialize([obj, obj2, obj3], [obj, obj3]);
    gc();
    printTrace(arguments.callee.name, g, BASE, obj.log, "serialize");
    assertEq("" + obj.log, "" + [
        // Same as above. Everything is fine.
        BASE + 1, "?", BASE + 3, "?",
        BASE + 2, "w",
        BASE + 1, "W", BASE + 3, "W",
    ], "serialize " + desc);
    obj.log = null;

    try {
        let clone = deserialize(s);
    } catch (e) {
        assertEq(e.message.includes("Failed as requested"), true);
    }
    s = null;
    gc();
    printTrace(arguments.callee.name, g, BASE, obj.log, "deserialize");
    assertEq("" + obj.log, "" + [
        // The transferred objects will get restored via the readTransfer() hook
        // and thus will own their transferred data. They will get discarded during
        // the GC, but that doesn't call any hooks. When the GC collects `s`,
        // it will look for any transferrable data to release, but will
        // find that it no longer owns any such data and will do nothing.
        BASE + 1, "R", BASE + 3, "R",
        BASE + 2, "r",
    ], "deserialize " + desc);
    obj.log = null;
}

function testMultiWithDeserializeReadError() {
    const desc = "write 3 objects, transfer obj1 and obj3 only, fail during read(obj2)";
    testMultiWithDeserializeReadErrorHelper(globalThis, 1000, desc);
}

function testMultiWithDeserializeReadErrorCrossRealm() {
    const desc = "write 3 objects, transfer obj1 and obj2 only, fail during read(obj2), cross-realm";
    testMultiWithDeserializeReadErrorHelper(newGlobal({ newCompartment: true }), 1100, desc);
}

testBasic();
testErrorDuringWrite();
testErrorDuringTransfer();
testMultiOk();
testMultiOkCrossRealm();
testMultiOkWithDeserialize();
testMultiOkWithDeserializeCrossRealm();
testMultiWithDeserializeReadTransferError();
testMultiWithDeserializeReadTransferErrorCrossRealm();
testMultiWithDeserializeReadError();
testMultiWithDeserializeReadErrorCrossRealm();