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