1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #include "builtin/TestingFunctions.h"
6 #include "js/ArrayBuffer.h" // JS::{IsArrayBufferObject,GetArrayBufferLengthAndData,NewExternalArrayBuffer}
7 #include "js/StructuredClone.h"
8
9 #include "jsapi-tests/tests.h"
10
11 using namespace js;
12
13 #ifdef DEBUG
14 // Skip test, since it will abort with an assert in buf->Init(7).
15 #else
BEGIN_TEST(testStructuredClone_invalidLength)16 BEGIN_TEST(testStructuredClone_invalidLength) {
17 auto buf = js::MakeUnique<JSStructuredCloneData>(
18 JS::StructuredCloneScope::DifferentProcess);
19 CHECK(buf);
20 CHECK(buf->Init(7));
21 RootedValue clone(cx);
22 JS::CloneDataPolicy policy;
23 CHECK(!JS_ReadStructuredClone(cx, *buf, JS_STRUCTURED_CLONE_VERSION,
24 JS::StructuredCloneScope::DifferentProcess,
25 &clone, policy, nullptr, nullptr));
26 return true;
27 }
28 END_TEST(testStructuredClone_invalidLength)
29 #endif
30
BEGIN_TEST(testStructuredClone_object)31 BEGIN_TEST(testStructuredClone_object) {
32 JS::RootedObject g1(cx, createGlobal());
33 JS::RootedObject g2(cx, createGlobal());
34 CHECK(g1);
35 CHECK(g2);
36
37 JS::RootedValue v1(cx);
38
39 {
40 JSAutoRealm ar(cx, g1);
41 JS::RootedValue prop(cx, JS::Int32Value(1337));
42
43 JS::RootedObject obj(cx, JS_NewPlainObject(cx));
44 v1 = JS::ObjectOrNullValue(obj);
45 CHECK(v1.isObject());
46 CHECK(JS_SetProperty(cx, obj, "prop", prop));
47 }
48
49 {
50 JSAutoRealm ar(cx, g2);
51 JS::RootedValue v2(cx);
52
53 CHECK(JS_StructuredClone(cx, v1, &v2, nullptr, nullptr));
54 CHECK(v2.isObject());
55 JS::RootedObject obj(cx, &v2.toObject());
56
57 JS::RootedValue prop(cx);
58 CHECK(JS_GetProperty(cx, obj, "prop", &prop));
59 CHECK(prop.isInt32());
60 CHECK(&v1.toObject() != obj);
61 CHECK_EQUAL(prop.toInt32(), 1337);
62 }
63
64 return true;
65 }
66 END_TEST(testStructuredClone_object)
67
BEGIN_TEST(testStructuredClone_string)68 BEGIN_TEST(testStructuredClone_string) {
69 JS::RootedObject g1(cx, createGlobal());
70 JS::RootedObject g2(cx, createGlobal());
71 CHECK(g1);
72 CHECK(g2);
73
74 JS::RootedValue v1(cx);
75
76 {
77 JSAutoRealm ar(cx, g1);
78 JS::RootedValue prop(cx, JS::Int32Value(1337));
79
80 v1 = JS::StringValue(JS_NewStringCopyZ(cx, "Hello World!"));
81 CHECK(v1.isString());
82 CHECK(v1.toString());
83 }
84
85 {
86 JSAutoRealm ar(cx, g2);
87 JS::RootedValue v2(cx);
88
89 CHECK(JS_StructuredClone(cx, v1, &v2, nullptr, nullptr));
90 CHECK(v2.isString());
91 CHECK(v2.toString());
92
93 JS::RootedValue expected(
94 cx, JS::StringValue(JS_NewStringCopyZ(cx, "Hello World!")));
95 CHECK_SAME(v2, expected);
96 }
97
98 return true;
99 }
100 END_TEST(testStructuredClone_string)
101
BEGIN_TEST(testStructuredClone_externalArrayBuffer)102 BEGIN_TEST(testStructuredClone_externalArrayBuffer) {
103 ExternalData data("One two three four");
104 JS::RootedObject g1(cx, createGlobal());
105 JS::RootedObject g2(cx, createGlobal());
106 CHECK(g1);
107 CHECK(g2);
108
109 JS::RootedValue v1(cx);
110
111 {
112 JSAutoRealm ar(cx, g1);
113
114 JS::RootedObject obj(
115 cx, JS::NewExternalArrayBuffer(cx, data.len(), data.contents(),
116 &ExternalData::freeCallback, &data));
117 CHECK(!data.wasFreed());
118
119 v1 = JS::ObjectOrNullValue(obj);
120 CHECK(v1.isObject());
121 }
122
123 {
124 JSAutoRealm ar(cx, g2);
125 JS::RootedValue v2(cx);
126
127 CHECK(JS_StructuredClone(cx, v1, &v2, nullptr, nullptr));
128 CHECK(v2.isObject());
129
130 JS::RootedObject obj(cx, &v2.toObject());
131 CHECK(&v1.toObject() != obj);
132
133 size_t len;
134 bool isShared;
135 uint8_t* clonedData;
136 JS::GetArrayBufferLengthAndData(obj, &len, &isShared, &clonedData);
137
138 // The contents of the two array buffers should be equal, but not the
139 // same pointer.
140 CHECK_EQUAL(len, data.len());
141 CHECK(clonedData != data.contents());
142 CHECK(strcmp(reinterpret_cast<char*>(clonedData), data.asString()) == 0);
143 CHECK(!data.wasFreed());
144 }
145
146 // GC the array buffer before data goes out of scope
147 v1.setNull();
148 JS_GC(cx);
149 JS_GC(cx); // Trigger another to wait for background finalization to end
150
151 CHECK(data.wasFreed());
152
153 return true;
154 }
155 END_TEST(testStructuredClone_externalArrayBuffer)
156
BEGIN_TEST(testStructuredClone_externalArrayBufferDifferentThreadOrProcess)157 BEGIN_TEST(testStructuredClone_externalArrayBufferDifferentThreadOrProcess) {
158 CHECK(testStructuredCloneCopy(JS::StructuredCloneScope::SameProcess));
159 CHECK(testStructuredCloneCopy(JS::StructuredCloneScope::DifferentProcess));
160 return true;
161 }
162
testStructuredCloneCopy(JS::StructuredCloneScope scope)163 bool testStructuredCloneCopy(JS::StructuredCloneScope scope) {
164 ExternalData data("One two three four");
165 JS::RootedObject buffer(
166 cx, JS::NewExternalArrayBuffer(cx, data.len(), data.contents(),
167 &ExternalData::freeCallback, &data));
168 CHECK(buffer);
169 CHECK(!data.wasFreed());
170
171 JS::RootedValue v1(cx, JS::ObjectValue(*buffer));
172 JS::RootedValue v2(cx);
173 CHECK(clone(scope, v1, &v2));
174 JS::RootedObject bufferOut(cx, v2.toObjectOrNull());
175 CHECK(bufferOut);
176 CHECK(JS::IsArrayBufferObject(bufferOut));
177
178 size_t len;
179 bool isShared;
180 uint8_t* clonedData;
181 JS::GetArrayBufferLengthAndData(bufferOut, &len, &isShared, &clonedData);
182
183 // Cloning should copy the data, so the contents of the two array buffers
184 // should be equal, but not the same pointer.
185 CHECK_EQUAL(len, data.len());
186 CHECK(clonedData != data.contents());
187 CHECK(strcmp(reinterpret_cast<char*>(clonedData), data.asString()) == 0);
188 CHECK(!data.wasFreed());
189
190 buffer = nullptr;
191 bufferOut = nullptr;
192 v1.setNull();
193 v2.setNull();
194 JS_GC(cx);
195 JS_GC(cx);
196 CHECK(data.wasFreed());
197
198 return true;
199 }
200
clone(JS::StructuredCloneScope scope,JS::HandleValue v1,JS::MutableHandleValue v2)201 bool clone(JS::StructuredCloneScope scope, JS::HandleValue v1,
202 JS::MutableHandleValue v2) {
203 JSAutoStructuredCloneBuffer clonedBuffer(scope, nullptr, nullptr);
204 CHECK(clonedBuffer.write(cx, v1));
205 CHECK(clonedBuffer.read(cx, v2));
206 return true;
207 }
208 END_TEST(testStructuredClone_externalArrayBufferDifferentThreadOrProcess)
209
210 struct StructuredCloneTestPrincipals final : public JSPrincipals {
211 uint32_t rank;
212
StructuredCloneTestPrincipalsStructuredCloneTestPrincipals213 explicit StructuredCloneTestPrincipals(uint32_t rank, int32_t rc = 1)
214 : rank(rank) {
215 this->refcount = rc;
216 }
217
writeStructuredCloneTestPrincipals218 bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
219 return JS_WriteUint32Pair(writer, rank, 0);
220 }
221
isSystemOrAddonPrincipalStructuredCloneTestPrincipals222 bool isSystemOrAddonPrincipal() override { return true; }
223
readStructuredCloneTestPrincipals224 static bool read(JSContext* cx, JSStructuredCloneReader* reader,
225 JSPrincipals** outPrincipals) {
226 uint32_t rank;
227 uint32_t unused;
228 if (!JS_ReadUint32Pair(reader, &rank, &unused)) {
229 return false;
230 }
231
232 *outPrincipals = new StructuredCloneTestPrincipals(rank);
233 return !!*outPrincipals;
234 }
235
destroyStructuredCloneTestPrincipals236 static void destroy(JSPrincipals* p) {
237 auto p1 = static_cast<StructuredCloneTestPrincipals*>(p);
238 delete p1;
239 }
240
getRankStructuredCloneTestPrincipals241 static uint32_t getRank(JSPrincipals* p) {
242 if (!p) {
243 return 0;
244 }
245 return static_cast<StructuredCloneTestPrincipals*>(p)->rank;
246 }
247
subsumesStructuredCloneTestPrincipals248 static bool subsumes(JSPrincipals* a, JSPrincipals* b) {
249 return getRank(a) > getRank(b);
250 }
251
252 static JSSecurityCallbacks securityCallbacks;
253
254 static StructuredCloneTestPrincipals testPrincipals;
255 };
256
257 JSSecurityCallbacks StructuredCloneTestPrincipals::securityCallbacks = {
258 nullptr, // contentSecurityPolicyAllows
259 subsumes};
260
BEGIN_TEST(testStructuredClone_SavedFrame)261 BEGIN_TEST(testStructuredClone_SavedFrame) {
262 JS_SetSecurityCallbacks(cx,
263 &StructuredCloneTestPrincipals::securityCallbacks);
264 JS_InitDestroyPrincipalsCallback(cx, StructuredCloneTestPrincipals::destroy);
265 JS_InitReadPrincipalsCallback(cx, StructuredCloneTestPrincipals::read);
266
267 auto testPrincipals = new StructuredCloneTestPrincipals(42, 0);
268 CHECK(testPrincipals);
269
270 auto DONE = (JSPrincipals*)0xDEADBEEF;
271
272 struct {
273 const char* name;
274 JSPrincipals* principals;
275 } principalsToTest[] = {
276 {"IsSystem", &js::ReconstructedSavedFramePrincipals::IsSystem},
277 {"IsNotSystem", &js::ReconstructedSavedFramePrincipals::IsNotSystem},
278 {"testPrincipals", testPrincipals},
279 {"nullptr principals", nullptr},
280 {"DONE", DONE}};
281
282 const char* FILENAME = "filename.js";
283
284 for (auto* pp = principalsToTest; pp->principals != DONE; pp++) {
285 fprintf(stderr, "Testing with principals '%s'\n", pp->name);
286
287 JS::RealmOptions options;
288 JS::RootedObject g(cx,
289 JS_NewGlobalObject(cx, getGlobalClass(), pp->principals,
290 JS::FireOnNewGlobalHook, options));
291 CHECK(g);
292 JSAutoRealm ar(cx, g);
293
294 CHECK(js::DefineTestingFunctions(cx, g, false, false));
295
296 JS::RootedValue srcVal(cx);
297 CHECK(
298 evaluate("(function one() { \n" // 1
299 " return (function two() { \n" // 2
300 " return (function three() { \n" // 3
301 " return saveStack(); \n" // 4
302 " }()); \n" // 5
303 " }()); \n" // 6
304 "}()); \n", // 7
305 FILENAME, 1, &srcVal));
306
307 CHECK(srcVal.isObject());
308 JS::RootedObject srcObj(cx, &srcVal.toObject());
309
310 CHECK(srcObj->is<js::SavedFrame>());
311 js::RootedSavedFrame srcFrame(cx, &srcObj->as<js::SavedFrame>());
312
313 CHECK(srcFrame->getPrincipals() == pp->principals);
314
315 JS::RootedValue destVal(cx);
316 CHECK(JS_StructuredClone(cx, srcVal, &destVal, nullptr, nullptr));
317
318 CHECK(destVal.isObject());
319 JS::RootedObject destObj(cx, &destVal.toObject());
320
321 CHECK(destObj->is<js::SavedFrame>());
322 JS::Handle<js::SavedFrame*> destFrame = destObj.as<js::SavedFrame>();
323
324 size_t framesCopied = 0;
325 for (JS::Handle<js::SavedFrame*> f :
326 js::SavedFrame::RootedRange(cx, destFrame)) {
327 framesCopied++;
328
329 CHECK(f != srcFrame);
330
331 if (pp->principals == testPrincipals) {
332 // We shouldn't get a pointer to the same
333 // StructuredCloneTestPrincipals instance since we should have
334 // serialized and then deserialized it into a new instance.
335 CHECK(f->getPrincipals() != pp->principals);
336
337 // But it should certainly have the same rank.
338 CHECK(StructuredCloneTestPrincipals::getRank(f->getPrincipals()) ==
339 StructuredCloneTestPrincipals::getRank(pp->principals));
340 } else {
341 // For our singleton principals, we should always get the same
342 // pointer back.
343 CHECK(js::ReconstructedSavedFramePrincipals::is(pp->principals) ||
344 pp->principals == nullptr);
345 CHECK(f->getPrincipals() == pp->principals);
346 }
347
348 CHECK(EqualStrings(f->getSource(), srcFrame->getSource()));
349 CHECK(f->getLine() == srcFrame->getLine());
350 CHECK(f->getColumn() == srcFrame->getColumn());
351 CHECK(EqualStrings(f->getFunctionDisplayName(),
352 srcFrame->getFunctionDisplayName()));
353
354 srcFrame = srcFrame->getParent();
355 }
356
357 // Four function frames + one global frame.
358 CHECK(framesCopied == 4);
359 }
360
361 return true;
362 }
363 END_TEST(testStructuredClone_SavedFrame)
364