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