1// Copyright 2012 the V8 project authors. All rights reserved. 2// Redistribution and use in source and binary forms, with or without 3// modification, are permitted provided that the following conditions are 4// met: 5// 6// * Redistributions of source code must retain the above copyright 7// notice, this list of conditions and the following disclaimer. 8// * Redistributions in binary form must reproduce the above 9// copyright notice, this list of conditions and the following 10// disclaimer in the documentation and/or other materials provided 11// with the distribution. 12// * Neither the name of Google Inc. nor the names of its 13// contributors may be used to endorse or promote products derived 14// from this software without specific prior written permission. 15// 16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28#include "src/v8.h" 29 30#include "test/cctest/cctest.h" 31 32using namespace v8; 33namespace i = v8::internal; 34 35inline int32_t ToInt32(v8::Local<v8::Value> value) { 36 return value->Int32Value(v8::Isolate::GetCurrent()->GetCurrentContext()) 37 .FromJust(); 38} 39 40 41TEST(PerIsolateState) { 42 i::FLAG_harmony_object_observe = true; 43 HandleScope scope(CcTest::isolate()); 44 LocalContext context1(CcTest::isolate()); 45 46 Local<Value> foo = v8_str("foo"); 47 context1->SetSecurityToken(foo); 48 49 CompileRun( 50 "var count = 0;" 51 "var calls = 0;" 52 "var observer = function(records) { count = records.length; calls++ };" 53 "var obj = {};" 54 "Object.observe(obj, observer);"); 55 Local<Value> observer = CompileRun("observer"); 56 Local<Value> obj = CompileRun("obj"); 57 Local<Value> notify_fun1 = CompileRun("(function() { obj.foo = 'bar'; })"); 58 Local<Value> notify_fun2; 59 { 60 LocalContext context2(CcTest::isolate()); 61 context2->SetSecurityToken(foo); 62 context2->Global() 63 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 64 obj) 65 .FromJust(); 66 notify_fun2 = CompileRun( 67 "(function() { obj.foo = 'baz'; })"); 68 } 69 Local<Value> notify_fun3; 70 { 71 LocalContext context3(CcTest::isolate()); 72 context3->SetSecurityToken(foo); 73 context3->Global() 74 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 75 obj) 76 .FromJust(); 77 notify_fun3 = CompileRun("(function() { obj.foo = 'bat'; })"); 78 } 79 { 80 LocalContext context4(CcTest::isolate()); 81 context4->SetSecurityToken(foo); 82 context4->Global() 83 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 84 v8_str("observer"), observer) 85 .FromJust(); 86 context4->Global() 87 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("fun1"), 88 notify_fun1) 89 .FromJust(); 90 context4->Global() 91 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("fun2"), 92 notify_fun2) 93 .FromJust(); 94 context4->Global() 95 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("fun3"), 96 notify_fun3) 97 .FromJust(); 98 CompileRun("fun1(); fun2(); fun3(); Object.deliverChangeRecords(observer)"); 99 } 100 CHECK_EQ(1, ToInt32(CompileRun("calls"))); 101 CHECK_EQ(3, ToInt32(CompileRun("count"))); 102} 103 104 105TEST(EndOfMicrotaskDelivery) { 106 i::FLAG_harmony_object_observe = true; 107 HandleScope scope(CcTest::isolate()); 108 LocalContext context(CcTest::isolate()); 109 CompileRun( 110 "var obj = {};" 111 "var count = 0;" 112 "var observer = function(records) { count = records.length };" 113 "Object.observe(obj, observer);" 114 "obj.foo = 'bar';"); 115 CHECK_EQ(1, ToInt32(CompileRun("count"))); 116} 117 118 119TEST(DeliveryOrdering) { 120 i::FLAG_harmony_object_observe = true; 121 HandleScope scope(CcTest::isolate()); 122 LocalContext context(CcTest::isolate()); 123 CompileRun( 124 "var obj1 = {};" 125 "var obj2 = {};" 126 "var ordering = [];" 127 "function observer2() { ordering.push(2); };" 128 "function observer1() { ordering.push(1); };" 129 "function observer3() { ordering.push(3); };" 130 "Object.observe(obj1, observer1);" 131 "Object.observe(obj1, observer2);" 132 "Object.observe(obj1, observer3);" 133 "obj1.foo = 'bar';"); 134 CHECK_EQ(3, ToInt32(CompileRun("ordering.length"))); 135 CHECK_EQ(1, ToInt32(CompileRun("ordering[0]"))); 136 CHECK_EQ(2, ToInt32(CompileRun("ordering[1]"))); 137 CHECK_EQ(3, ToInt32(CompileRun("ordering[2]"))); 138 CompileRun( 139 "ordering = [];" 140 "Object.observe(obj2, observer3);" 141 "Object.observe(obj2, observer2);" 142 "Object.observe(obj2, observer1);" 143 "obj2.foo = 'baz'"); 144 CHECK_EQ(3, ToInt32(CompileRun("ordering.length"))); 145 CHECK_EQ(1, ToInt32(CompileRun("ordering[0]"))); 146 CHECK_EQ(2, ToInt32(CompileRun("ordering[1]"))); 147 CHECK_EQ(3, ToInt32(CompileRun("ordering[2]"))); 148} 149 150 151TEST(DeliveryCallbackThrows) { 152 i::FLAG_harmony_object_observe = true; 153 HandleScope scope(CcTest::isolate()); 154 LocalContext context(CcTest::isolate()); 155 CompileRun( 156 "var obj = {};" 157 "var ordering = [];" 158 "function observer1() { ordering.push(1); };" 159 "function observer2() { ordering.push(2); };" 160 "function observer_throws() {" 161 " ordering.push(0);" 162 " throw new Error();" 163 " ordering.push(-1);" 164 "};" 165 "Object.observe(obj, observer_throws.bind());" 166 "Object.observe(obj, observer1);" 167 "Object.observe(obj, observer_throws.bind());" 168 "Object.observe(obj, observer2);" 169 "Object.observe(obj, observer_throws.bind());" 170 "obj.foo = 'bar';"); 171 CHECK_EQ(5, ToInt32(CompileRun("ordering.length"))); 172 CHECK_EQ(0, ToInt32(CompileRun("ordering[0]"))); 173 CHECK_EQ(1, ToInt32(CompileRun("ordering[1]"))); 174 CHECK_EQ(0, ToInt32(CompileRun("ordering[2]"))); 175 CHECK_EQ(2, ToInt32(CompileRun("ordering[3]"))); 176 CHECK_EQ(0, ToInt32(CompileRun("ordering[4]"))); 177} 178 179 180TEST(DeliveryChangesMutationInCallback) { 181 i::FLAG_harmony_object_observe = true; 182 HandleScope scope(CcTest::isolate()); 183 LocalContext context(CcTest::isolate()); 184 CompileRun( 185 "var obj = {};" 186 "var ordering = [];" 187 "function observer1(records) {" 188 " ordering.push(100 + records.length);" 189 " records.push(11);" 190 " records.push(22);" 191 "};" 192 "function observer2(records) {" 193 " ordering.push(200 + records.length);" 194 " records.push(33);" 195 " records.push(44);" 196 "};" 197 "Object.observe(obj, observer1);" 198 "Object.observe(obj, observer2);" 199 "obj.foo = 'bar';"); 200 CHECK_EQ(2, ToInt32(CompileRun("ordering.length"))); 201 CHECK_EQ(101, ToInt32(CompileRun("ordering[0]"))); 202 CHECK_EQ(201, ToInt32(CompileRun("ordering[1]"))); 203} 204 205 206TEST(DeliveryOrderingReentrant) { 207 i::FLAG_harmony_object_observe = true; 208 HandleScope scope(CcTest::isolate()); 209 LocalContext context(CcTest::isolate()); 210 CompileRun( 211 "var obj = {};" 212 "var reentered = false;" 213 "var ordering = [];" 214 "function observer1() { ordering.push(1); };" 215 "function observer2() {" 216 " if (!reentered) {" 217 " obj.foo = 'baz';" 218 " reentered = true;" 219 " }" 220 " ordering.push(2);" 221 "};" 222 "function observer3() { ordering.push(3); };" 223 "Object.observe(obj, observer1);" 224 "Object.observe(obj, observer2);" 225 "Object.observe(obj, observer3);" 226 "obj.foo = 'bar';"); 227 CHECK_EQ(5, ToInt32(CompileRun("ordering.length"))); 228 CHECK_EQ(1, ToInt32(CompileRun("ordering[0]"))); 229 CHECK_EQ(2, ToInt32(CompileRun("ordering[1]"))); 230 CHECK_EQ(3, ToInt32(CompileRun("ordering[2]"))); 231 // Note that we re-deliver to observers 1 and 2, while observer3 232 // already received the second record during the first round. 233 CHECK_EQ(1, ToInt32(CompileRun("ordering[3]"))); 234 CHECK_EQ(2, ToInt32(CompileRun("ordering[1]"))); 235} 236 237 238TEST(DeliveryOrderingDeliverChangeRecords) { 239 i::FLAG_harmony_object_observe = true; 240 HandleScope scope(CcTest::isolate()); 241 LocalContext context(CcTest::isolate()); 242 CompileRun( 243 "var obj = {};" 244 "var ordering = [];" 245 "function observer1() { ordering.push(1); if (!obj.b) obj.b = true };" 246 "function observer2() { ordering.push(2); };" 247 "Object.observe(obj, observer1);" 248 "Object.observe(obj, observer2);" 249 "obj.a = 1;" 250 "Object.deliverChangeRecords(observer2);"); 251 CHECK_EQ(4, ToInt32(CompileRun("ordering.length"))); 252 // First, observer2 is called due to deliverChangeRecords 253 CHECK_EQ(2, ToInt32(CompileRun("ordering[0]"))); 254 // Then, observer1 is called when the stack unwinds 255 CHECK_EQ(1, ToInt32(CompileRun("ordering[1]"))); 256 // observer1's mutation causes both 1 and 2 to be reactivated, 257 // with 1 having priority. 258 CHECK_EQ(1, ToInt32(CompileRun("ordering[2]"))); 259 CHECK_EQ(2, ToInt32(CompileRun("ordering[3]"))); 260} 261 262 263TEST(ObjectHashTableGrowth) { 264 i::FLAG_harmony_object_observe = true; 265 HandleScope scope(CcTest::isolate()); 266 // Initializing this context sets up initial hash tables. 267 LocalContext context(CcTest::isolate()); 268 Local<Value> obj = CompileRun("obj = {};"); 269 Local<Value> observer = CompileRun( 270 "var ran = false;" 271 "(function() { ran = true })"); 272 { 273 // As does initializing this context. 274 LocalContext context2(CcTest::isolate()); 275 context2->Global() 276 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 277 obj) 278 .FromJust(); 279 context2->Global() 280 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 281 v8_str("observer"), observer) 282 .FromJust(); 283 CompileRun( 284 "var objArr = [];" 285 // 100 objects should be enough to make the hash table grow 286 // (and thus relocate). 287 "for (var i = 0; i < 100; ++i) {" 288 " objArr.push({});" 289 " Object.observe(objArr[objArr.length-1], function(){});" 290 "}" 291 "Object.observe(obj, observer);"); 292 } 293 // obj is now marked "is_observed", but our map has moved. 294 CompileRun("obj.foo = 'bar'"); 295 CHECK(CompileRun("ran") 296 ->BooleanValue(v8::Isolate::GetCurrent()->GetCurrentContext()) 297 .FromJust()); 298} 299 300 301struct RecordExpectation { 302 Local<Value> object; 303 const char* type; 304 const char* name; 305 Local<Value> old_value; 306}; 307 308 309// TODO(adamk): Use this helper elsewhere in this file. 310static void ExpectRecords(v8::Isolate* isolate, Local<Value> records, 311 const RecordExpectation expectations[], int num) { 312 CHECK(records->IsArray()); 313 Local<Array> recordArray = records.As<Array>(); 314 CHECK_EQ(num, static_cast<int>(recordArray->Length())); 315 for (int i = 0; i < num; ++i) { 316 Local<Value> record = 317 recordArray->Get(v8::Isolate::GetCurrent()->GetCurrentContext(), i) 318 .ToLocalChecked(); 319 CHECK(record->IsObject()); 320 Local<Object> recordObj = record.As<Object>(); 321 Local<Value> value = 322 recordObj->Get(v8::Isolate::GetCurrent()->GetCurrentContext(), 323 v8_str("object")) 324 .ToLocalChecked(); 325 CHECK(expectations[i].object->StrictEquals(value)); 326 value = recordObj->Get(v8::Isolate::GetCurrent()->GetCurrentContext(), 327 v8_str("type")) 328 .ToLocalChecked(); 329 CHECK(v8_str(expectations[i].type) 330 ->Equals(v8::Isolate::GetCurrent()->GetCurrentContext(), value) 331 .FromJust()); 332 if (strcmp("splice", expectations[i].type) != 0) { 333 Local<Value> name = 334 recordObj->Get(v8::Isolate::GetCurrent()->GetCurrentContext(), 335 v8_str("name")) 336 .ToLocalChecked(); 337 CHECK(v8_str(expectations[i].name) 338 ->Equals(v8::Isolate::GetCurrent()->GetCurrentContext(), name) 339 .FromJust()); 340 if (!expectations[i].old_value.IsEmpty()) { 341 Local<Value> old_value = 342 recordObj->Get(v8::Isolate::GetCurrent()->GetCurrentContext(), 343 v8_str("oldValue")) 344 .ToLocalChecked(); 345 CHECK(expectations[i] 346 .old_value->Equals( 347 v8::Isolate::GetCurrent()->GetCurrentContext(), 348 old_value) 349 .FromJust()); 350 } 351 } 352 } 353} 354 355#define EXPECT_RECORDS(records, expectations) \ 356 ExpectRecords(CcTest::isolate(), records, expectations, \ 357 arraysize(expectations)) 358 359TEST(APITestBasicMutation) { 360 i::FLAG_harmony_object_observe = true; 361 v8::Isolate* v8_isolate = CcTest::isolate(); 362 HandleScope scope(v8_isolate); 363 LocalContext context(v8_isolate); 364 Local<Object> obj = Local<Object>::Cast( 365 CompileRun("var records = [];" 366 "var obj = {};" 367 "function observer(r) { [].push.apply(records, r); };" 368 "Object.observe(obj, observer);" 369 "obj")); 370 obj->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("foo"), 371 Number::New(v8_isolate, 7)) 372 .FromJust(); 373 obj->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 1, 374 Number::New(v8_isolate, 2)) 375 .FromJust(); 376 // CreateDataProperty should work just as well as Set 377 obj->CreateDataProperty(v8::Isolate::GetCurrent()->GetCurrentContext(), 378 v8_str("foo"), Number::New(v8_isolate, 3)) 379 .FromJust(); 380 obj->CreateDataProperty(v8::Isolate::GetCurrent()->GetCurrentContext(), 1, 381 Number::New(v8_isolate, 4)) 382 .FromJust(); 383 // Setting an indexed element via the property setting method 384 obj->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 385 Number::New(v8_isolate, 1), Number::New(v8_isolate, 5)) 386 .FromJust(); 387 // Setting with a non-String, non-uint32 key 388 obj->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 389 Number::New(v8_isolate, 1.1), Number::New(v8_isolate, 6)) 390 .FromJust(); 391 obj->Delete(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("foo")) 392 .FromJust(); 393 obj->Delete(v8::Isolate::GetCurrent()->GetCurrentContext(), 1).FromJust(); 394 obj->Delete(v8::Isolate::GetCurrent()->GetCurrentContext(), 395 Number::New(v8_isolate, 1.1)) 396 .FromJust(); 397 398 // Force delivery 399 // TODO(adamk): Should the above set methods trigger delivery themselves? 400 CompileRun("void 0"); 401 CHECK_EQ(9, ToInt32(CompileRun("records.length"))); 402 const RecordExpectation expected_records[] = { 403 {obj, "add", "foo", Local<Value>()}, 404 {obj, "add", "1", Local<Value>()}, 405 // Note: use 7 not 1 below, as the latter triggers a nifty VS10 compiler 406 // bug 407 // where instead of 1.0, a garbage value would be passed into Number::New. 408 {obj, "update", "foo", Number::New(v8_isolate, 7)}, 409 {obj, "update", "1", Number::New(v8_isolate, 2)}, 410 {obj, "update", "1", Number::New(v8_isolate, 4)}, 411 {obj, "add", "1.1", Local<Value>()}, 412 {obj, "delete", "foo", Number::New(v8_isolate, 3)}, 413 {obj, "delete", "1", Number::New(v8_isolate, 5)}, 414 {obj, "delete", "1.1", Number::New(v8_isolate, 6)}}; 415 EXPECT_RECORDS(CompileRun("records"), expected_records); 416} 417 418 419TEST(HiddenPrototypeObservation) { 420 i::FLAG_harmony_object_observe = true; 421 v8::Isolate* v8_isolate = CcTest::isolate(); 422 HandleScope scope(v8_isolate); 423 LocalContext context(v8_isolate); 424 Local<FunctionTemplate> tmpl = FunctionTemplate::New(v8_isolate); 425 tmpl->SetHiddenPrototype(true); 426 tmpl->InstanceTemplate()->Set(v8_str("foo"), Number::New(v8_isolate, 75)); 427 Local<Function> function = 428 tmpl->GetFunction(v8::Isolate::GetCurrent()->GetCurrentContext()) 429 .ToLocalChecked(); 430 Local<Object> proto = 431 function->NewInstance(v8::Isolate::GetCurrent()->GetCurrentContext()) 432 .ToLocalChecked(); 433 Local<Object> obj = Object::New(v8_isolate); 434 obj->SetPrototype(v8::Isolate::GetCurrent()->GetCurrentContext(), proto) 435 .FromJust(); 436 context->Global() 437 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), obj) 438 .FromJust(); 439 context->Global() 440 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("proto"), 441 proto) 442 .FromJust(); 443 CompileRun( 444 "var records;" 445 "function observer(r) { records = r; };" 446 "Object.observe(obj, observer);" 447 "obj.foo = 41;" // triggers a notification 448 "proto.foo = 42;"); // does not trigger a notification 449 const RecordExpectation expected_records[] = { 450 { obj, "update", "foo", Number::New(v8_isolate, 75) } 451 }; 452 EXPECT_RECORDS(CompileRun("records"), expected_records); 453 obj->SetPrototype(v8::Isolate::GetCurrent()->GetCurrentContext(), 454 Null(v8_isolate)) 455 .FromJust(); 456 CompileRun("obj.foo = 43"); 457 const RecordExpectation expected_records2[] = { 458 {obj, "add", "foo", Local<Value>()}}; 459 EXPECT_RECORDS(CompileRun("records"), expected_records2); 460 obj->SetPrototype(v8::Isolate::GetCurrent()->GetCurrentContext(), proto) 461 .FromJust(); 462 CompileRun( 463 "Object.observe(proto, observer);" 464 "proto.bar = 1;" 465 "Object.unobserve(obj, observer);" 466 "obj.foo = 44;"); 467 const RecordExpectation expected_records3[] = { 468 {proto, "add", "bar", Local<Value>()} 469 // TODO(adamk): The below record should be emitted since proto is observed 470 // and has been modified. Not clear if this happens in practice. 471 // { proto, "update", "foo", Number::New(43) } 472 }; 473 EXPECT_RECORDS(CompileRun("records"), expected_records3); 474} 475 476 477static int NumberOfElements(i::Handle<i::JSWeakMap> map) { 478 return i::ObjectHashTable::cast(map->table())->NumberOfElements(); 479} 480 481 482TEST(ObservationWeakMap) { 483 i::FLAG_harmony_object_observe = true; 484 HandleScope scope(CcTest::isolate()); 485 LocalContext context(CcTest::isolate()); 486 CompileRun( 487 "var obj = {};" 488 "Object.observe(obj, function(){});" 489 "Object.getNotifier(obj);" 490 "obj = null;"); 491 i::Isolate* i_isolate = CcTest::i_isolate(); 492 i::Handle<i::JSObject> observation_state = 493 i_isolate->factory()->observation_state(); 494 i::Handle<i::JSWeakMap> callbackInfoMap = 495 i::Handle<i::JSWeakMap>::cast(i::Object::GetProperty( 496 i_isolate, observation_state, "callbackInfoMap").ToHandleChecked()); 497 i::Handle<i::JSWeakMap> objectInfoMap = 498 i::Handle<i::JSWeakMap>::cast(i::Object::GetProperty( 499 i_isolate, observation_state, "objectInfoMap").ToHandleChecked()); 500 i::Handle<i::JSWeakMap> notifierObjectInfoMap = 501 i::Handle<i::JSWeakMap>::cast(i::Object::GetProperty( 502 i_isolate, observation_state, "notifierObjectInfoMap") 503 .ToHandleChecked()); 504 CHECK_EQ(1, NumberOfElements(callbackInfoMap)); 505 CHECK_EQ(1, NumberOfElements(objectInfoMap)); 506 CHECK_EQ(1, NumberOfElements(notifierObjectInfoMap)); 507 i_isolate->heap()->CollectAllGarbage(); 508 CHECK_EQ(0, NumberOfElements(callbackInfoMap)); 509 CHECK_EQ(0, NumberOfElements(objectInfoMap)); 510 CHECK_EQ(0, NumberOfElements(notifierObjectInfoMap)); 511} 512 513 514static int TestObserveSecurity(Local<Context> observer_context, 515 Local<Context> object_context, 516 Local<Context> mutation_context) { 517 Context::Scope observer_scope(observer_context); 518 CompileRun("var records = null;" 519 "var observer = function(r) { records = r };"); 520 Local<Value> observer = CompileRun("observer"); 521 { 522 Context::Scope object_scope(object_context); 523 object_context->Global() 524 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 525 v8_str("observer"), observer) 526 .FromJust(); 527 CompileRun("var obj = {};" 528 "obj.length = 0;" 529 "Object.observe(obj, observer," 530 "['add', 'update', 'delete','reconfigure','splice']" 531 ");"); 532 Local<Value> obj = CompileRun("obj"); 533 { 534 Context::Scope mutation_scope(mutation_context); 535 mutation_context->Global() 536 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 537 obj) 538 .FromJust(); 539 CompileRun("obj.foo = 'bar';" 540 "obj.foo = 'baz';" 541 "delete obj.foo;" 542 "Object.defineProperty(obj, 'bar', {value: 'bot'});" 543 "Array.prototype.push.call(obj, 1, 2, 3);" 544 "Array.prototype.splice.call(obj, 1, 2, 2, 4);" 545 "Array.prototype.pop.call(obj);" 546 "Array.prototype.shift.call(obj);"); 547 } 548 } 549 return ToInt32(CompileRun("records ? records.length : 0")); 550} 551 552 553TEST(ObserverSecurityAAA) { 554 i::FLAG_harmony_object_observe = true; 555 v8::Isolate* isolate = CcTest::isolate(); 556 v8::HandleScope scope(isolate); 557 v8::Local<Context> contextA = Context::New(isolate); 558 CHECK_EQ(8, TestObserveSecurity(contextA, contextA, contextA)); 559} 560 561 562TEST(ObserverSecurityA1A2A3) { 563 i::FLAG_harmony_object_observe = true; 564 v8::Isolate* isolate = CcTest::isolate(); 565 v8::HandleScope scope(isolate); 566 567 v8::Local<Context> contextA1 = Context::New(isolate); 568 v8::Local<Context> contextA2 = Context::New(isolate); 569 v8::Local<Context> contextA3 = Context::New(isolate); 570 571 Local<Value> foo = v8_str("foo"); 572 contextA1->SetSecurityToken(foo); 573 contextA2->SetSecurityToken(foo); 574 contextA3->SetSecurityToken(foo); 575 576 CHECK_EQ(8, TestObserveSecurity(contextA1, contextA2, contextA3)); 577} 578 579 580TEST(ObserverSecurityAAB) { 581 i::FLAG_harmony_object_observe = true; 582 v8::Isolate* isolate = CcTest::isolate(); 583 v8::HandleScope scope(isolate); 584 v8::Local<Context> contextA = Context::New(isolate); 585 v8::Local<Context> contextB = Context::New(isolate); 586 CHECK_EQ(0, TestObserveSecurity(contextA, contextA, contextB)); 587} 588 589 590TEST(ObserverSecurityA1A2B) { 591 i::FLAG_harmony_object_observe = true; 592 v8::Isolate* isolate = CcTest::isolate(); 593 v8::HandleScope scope(isolate); 594 595 v8::Local<Context> contextA1 = Context::New(isolate); 596 v8::Local<Context> contextA2 = Context::New(isolate); 597 v8::Local<Context> contextB = Context::New(isolate); 598 599 Local<Value> foo = v8_str("foo"); 600 contextA1->SetSecurityToken(foo); 601 contextA2->SetSecurityToken(foo); 602 603 CHECK_EQ(0, TestObserveSecurity(contextA1, contextA2, contextB)); 604} 605 606 607TEST(ObserverSecurityABA) { 608 i::FLAG_harmony_object_observe = true; 609 v8::Isolate* isolate = CcTest::isolate(); 610 v8::HandleScope scope(isolate); 611 v8::Local<Context> contextA = Context::New(isolate); 612 v8::Local<Context> contextB = Context::New(isolate); 613 CHECK_EQ(0, TestObserveSecurity(contextA, contextB, contextA)); 614} 615 616 617TEST(ObserverSecurityA1BA2) { 618 i::FLAG_harmony_object_observe = true; 619 v8::Isolate* isolate = CcTest::isolate(); 620 v8::HandleScope scope(isolate); 621 v8::Local<Context> contextA1 = Context::New(isolate); 622 v8::Local<Context> contextA2 = Context::New(isolate); 623 v8::Local<Context> contextB = Context::New(isolate); 624 625 Local<Value> foo = v8_str("foo"); 626 contextA1->SetSecurityToken(foo); 627 contextA2->SetSecurityToken(foo); 628 629 CHECK_EQ(0, TestObserveSecurity(contextA1, contextB, contextA2)); 630} 631 632 633TEST(ObserverSecurityBAA) { 634 i::FLAG_harmony_object_observe = true; 635 v8::Isolate* isolate = CcTest::isolate(); 636 v8::HandleScope scope(isolate); 637 v8::Local<Context> contextA = Context::New(isolate); 638 v8::Local<Context> contextB = Context::New(isolate); 639 CHECK_EQ(0, TestObserveSecurity(contextB, contextA, contextA)); 640} 641 642 643TEST(ObserverSecurityBA1A2) { 644 i::FLAG_harmony_object_observe = true; 645 v8::Isolate* isolate = CcTest::isolate(); 646 v8::HandleScope scope(isolate); 647 v8::Local<Context> contextA1 = Context::New(isolate); 648 v8::Local<Context> contextA2 = Context::New(isolate); 649 v8::Local<Context> contextB = Context::New(isolate); 650 651 Local<Value> foo = v8_str("foo"); 652 contextA1->SetSecurityToken(foo); 653 contextA2->SetSecurityToken(foo); 654 655 CHECK_EQ(0, TestObserveSecurity(contextB, contextA1, contextA2)); 656} 657 658 659TEST(ObserverSecurityNotify) { 660 i::FLAG_harmony_object_observe = true; 661 v8::Isolate* isolate = CcTest::isolate(); 662 v8::HandleScope scope(isolate); 663 v8::Local<Context> contextA = Context::New(isolate); 664 v8::Local<Context> contextB = Context::New(isolate); 665 666 Context::Scope scopeA(contextA); 667 CompileRun("var obj = {};" 668 "var recordsA = null;" 669 "var observerA = function(r) { recordsA = r };" 670 "Object.observe(obj, observerA);"); 671 Local<Value> obj = CompileRun("obj"); 672 673 { 674 Context::Scope scopeB(contextB); 675 contextB->Global() 676 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 677 obj) 678 .FromJust(); 679 CompileRun("var recordsB = null;" 680 "var observerB = function(r) { recordsB = r };" 681 "Object.observe(obj, observerB);"); 682 } 683 684 CompileRun("var notifier = Object.getNotifier(obj);" 685 "notifier.notify({ type: 'update' });"); 686 CHECK_EQ(1, ToInt32(CompileRun("recordsA ? recordsA.length : 0"))); 687 688 { 689 Context::Scope scopeB(contextB); 690 CHECK_EQ(0, ToInt32(CompileRun("recordsB ? recordsB.length : 0"))); 691 } 692} 693 694 695TEST(HiddenPropertiesLeakage) { 696 i::FLAG_harmony_object_observe = true; 697 HandleScope scope(CcTest::isolate()); 698 LocalContext context(CcTest::isolate()); 699 CompileRun("var obj = {};" 700 "var records = null;" 701 "var observer = function(r) { records = r };" 702 "Object.observe(obj, observer);"); 703 Local<Value> obj = 704 context->Global() 705 ->Get(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj")) 706 .ToLocalChecked(); 707 Local<Object>::Cast(obj) 708 ->SetPrivate(v8::Isolate::GetCurrent()->GetCurrentContext(), 709 v8::Private::New(CcTest::isolate(), v8_str("foo")), 710 Null(CcTest::isolate())) 711 .FromJust(); 712 CompileRun(""); // trigger delivery 713 CHECK(CompileRun("records")->IsNull()); 714} 715 716 717TEST(GetNotifierFromOtherContext) { 718 i::FLAG_harmony_object_observe = true; 719 HandleScope scope(CcTest::isolate()); 720 LocalContext context(CcTest::isolate()); 721 CompileRun("var obj = {};"); 722 Local<Value> instance = CompileRun("obj"); 723 { 724 LocalContext context2(CcTest::isolate()); 725 context2->Global() 726 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 727 instance) 728 .FromJust(); 729 CHECK(CompileRun("Object.getNotifier(obj)")->IsNull()); 730 } 731} 732 733 734TEST(GetNotifierFromOtherOrigin) { 735 i::FLAG_harmony_object_observe = true; 736 HandleScope scope(CcTest::isolate()); 737 Local<Value> foo = v8_str("foo"); 738 Local<Value> bar = v8_str("bar"); 739 LocalContext context(CcTest::isolate()); 740 context->SetSecurityToken(foo); 741 CompileRun("var obj = {};"); 742 Local<Value> instance = CompileRun("obj"); 743 { 744 LocalContext context2(CcTest::isolate()); 745 context2->SetSecurityToken(bar); 746 context2->Global() 747 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 748 instance) 749 .FromJust(); 750 CHECK(CompileRun("Object.getNotifier(obj)")->IsNull()); 751 } 752} 753 754 755TEST(GetNotifierFromSameOrigin) { 756 i::FLAG_harmony_object_observe = true; 757 HandleScope scope(CcTest::isolate()); 758 Local<Value> foo = v8_str("foo"); 759 LocalContext context(CcTest::isolate()); 760 context->SetSecurityToken(foo); 761 CompileRun("var obj = {};"); 762 Local<Value> instance = CompileRun("obj"); 763 { 764 LocalContext context2(CcTest::isolate()); 765 context2->SetSecurityToken(foo); 766 context2->Global() 767 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 768 instance) 769 .FromJust(); 770 CHECK(CompileRun("Object.getNotifier(obj)")->IsObject()); 771 } 772} 773 774 775static int GetGlobalObjectsCount() { 776 int count = 0; 777 i::HeapIterator it(CcTest::heap()); 778 for (i::HeapObject* object = it.next(); object != NULL; object = it.next()) 779 if (object->IsJSGlobalObject()) { 780 i::JSGlobalObject* g = i::JSGlobalObject::cast(object); 781 // Skip dummy global object. 782 if (i::GlobalDictionary::cast(g->properties())->NumberOfElements() != 0) { 783 count++; 784 } 785 } 786 // Subtract one to compensate for the code stub context that is always present 787 return count - 1; 788} 789 790 791static void CheckSurvivingGlobalObjectsCount(int expected) { 792 // We need to collect all garbage twice to be sure that everything 793 // has been collected. This is because inline caches are cleared in 794 // the first garbage collection but some of the maps have already 795 // been marked at that point. Therefore some of the maps are not 796 // collected until the second garbage collection. 797 CcTest::heap()->CollectAllGarbage(); 798 CcTest::heap()->CollectAllGarbage(i::Heap::kMakeHeapIterableMask); 799 int count = GetGlobalObjectsCount(); 800#ifdef DEBUG 801 if (count != expected) CcTest::heap()->TracePathToGlobal(); 802#endif 803 CHECK_EQ(expected, count); 804} 805 806 807TEST(DontLeakContextOnObserve) { 808 i::FLAG_harmony_object_observe = true; 809 HandleScope scope(CcTest::isolate()); 810 Local<Value> foo = v8_str("foo"); 811 LocalContext context(CcTest::isolate()); 812 context->SetSecurityToken(foo); 813 CompileRun("var obj = {};"); 814 Local<Value> object = CompileRun("obj"); 815 { 816 HandleScope scope(CcTest::isolate()); 817 LocalContext context2(CcTest::isolate()); 818 context2->SetSecurityToken(foo); 819 context2->Global() 820 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 821 object) 822 .FromJust(); 823 CompileRun("function observer() {};" 824 "Object.observe(obj, observer, ['foo', 'bar', 'baz']);" 825 "Object.unobserve(obj, observer);"); 826 } 827 828 CcTest::isolate()->ContextDisposedNotification(); 829 CheckSurvivingGlobalObjectsCount(0); 830} 831 832 833TEST(DontLeakContextOnGetNotifier) { 834 i::FLAG_harmony_object_observe = true; 835 HandleScope scope(CcTest::isolate()); 836 Local<Value> foo = v8_str("foo"); 837 LocalContext context(CcTest::isolate()); 838 context->SetSecurityToken(foo); 839 CompileRun("var obj = {};"); 840 Local<Value> object = CompileRun("obj"); 841 { 842 HandleScope scope(CcTest::isolate()); 843 LocalContext context2(CcTest::isolate()); 844 context2->SetSecurityToken(foo); 845 context2->Global() 846 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 847 object) 848 .FromJust(); 849 CompileRun("Object.getNotifier(obj);"); 850 } 851 852 CcTest::isolate()->ContextDisposedNotification(); 853 CheckSurvivingGlobalObjectsCount(0); 854} 855 856 857TEST(DontLeakContextOnNotifierPerformChange) { 858 i::FLAG_harmony_object_observe = true; 859 HandleScope scope(CcTest::isolate()); 860 Local<Value> foo = v8_str("foo"); 861 LocalContext context(CcTest::isolate()); 862 context->SetSecurityToken(foo); 863 CompileRun("var obj = {};"); 864 Local<Value> object = CompileRun("obj"); 865 Local<Value> notifier = CompileRun("Object.getNotifier(obj)"); 866 { 867 HandleScope scope(CcTest::isolate()); 868 LocalContext context2(CcTest::isolate()); 869 context2->SetSecurityToken(foo); 870 context2->Global() 871 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 872 object) 873 .FromJust(); 874 context2->Global() 875 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 876 v8_str("notifier"), notifier) 877 .FromJust(); 878 CompileRun("var obj2 = {};" 879 "var notifier2 = Object.getNotifier(obj2);" 880 "notifier2.performChange.call(" 881 "notifier, 'foo', function(){})"); 882 } 883 884 CcTest::isolate()->ContextDisposedNotification(); 885 CheckSurvivingGlobalObjectsCount(0); 886} 887 888 889static void ObserverCallback(const FunctionCallbackInfo<Value>& args) { 890 *static_cast<int*>(Local<External>::Cast(args.Data())->Value()) = 891 Local<Array>::Cast(args[0])->Length(); 892} 893 894 895TEST(ObjectObserveCallsCppFunction) { 896 i::FLAG_harmony_object_observe = true; 897 Isolate* isolate = CcTest::isolate(); 898 HandleScope scope(isolate); 899 LocalContext context(isolate); 900 int numRecordsSent = 0; 901 Local<Function> observer = 902 Function::New(CcTest::isolate()->GetCurrentContext(), ObserverCallback, 903 External::New(isolate, &numRecordsSent)) 904 .ToLocalChecked(); 905 context->Global() 906 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("observer"), 907 observer) 908 .FromJust(); 909 CompileRun( 910 "var obj = {};" 911 "Object.observe(obj, observer);" 912 "obj.foo = 1;" 913 "obj.bar = 2;"); 914 CHECK_EQ(2, numRecordsSent); 915} 916 917 918TEST(ObjectObserveCallsFunctionTemplateInstance) { 919 i::FLAG_harmony_object_observe = true; 920 Isolate* isolate = CcTest::isolate(); 921 HandleScope scope(isolate); 922 LocalContext context(isolate); 923 int numRecordsSent = 0; 924 Local<FunctionTemplate> tmpl = FunctionTemplate::New( 925 isolate, ObserverCallback, External::New(isolate, &numRecordsSent)); 926 Local<Function> function = 927 tmpl->GetFunction(v8::Isolate::GetCurrent()->GetCurrentContext()) 928 .ToLocalChecked(); 929 context->Global() 930 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("observer"), 931 function) 932 .FromJust(); 933 CompileRun( 934 "var obj = {};" 935 "Object.observe(obj, observer);" 936 "obj.foo = 1;" 937 "obj.bar = 2;"); 938 CHECK_EQ(2, numRecordsSent); 939} 940 941 942static void AccessorGetter(Local<Name> property, 943 const PropertyCallbackInfo<Value>& info) { 944 info.GetReturnValue().Set(Integer::New(info.GetIsolate(), 42)); 945} 946 947 948static void AccessorSetter(Local<Name> property, Local<Value> value, 949 const PropertyCallbackInfo<void>& info) { 950 info.GetReturnValue().SetUndefined(); 951} 952 953 954TEST(APIAccessorsShouldNotNotify) { 955 i::FLAG_harmony_object_observe = true; 956 Isolate* isolate = CcTest::isolate(); 957 HandleScope handle_scope(isolate); 958 LocalContext context(isolate); 959 Local<Object> object = Object::New(isolate); 960 object->SetAccessor(v8::Isolate::GetCurrent()->GetCurrentContext(), 961 v8_str("accessor"), &AccessorGetter, &AccessorSetter) 962 .FromJust(); 963 context->Global() 964 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 965 object) 966 .FromJust(); 967 CompileRun( 968 "var records = null;" 969 "Object.observe(obj, function(r) { records = r });" 970 "obj.accessor = 43;"); 971 CHECK(CompileRun("records")->IsNull()); 972 CompileRun("Object.defineProperty(obj, 'accessor', { value: 44 });"); 973 CHECK(CompileRun("records")->IsNull()); 974} 975 976 977namespace { 978 979int* global_use_counts = NULL; 980 981void MockUseCounterCallback(v8::Isolate* isolate, 982 v8::Isolate::UseCounterFeature feature) { 983 ++global_use_counts[feature]; 984} 985} 986 987 988TEST(UseCountObjectObserve) { 989 i::FLAG_harmony_object_observe = true; 990 i::Isolate* isolate = CcTest::i_isolate(); 991 i::HandleScope scope(isolate); 992 LocalContext env; 993 int use_counts[v8::Isolate::kUseCounterFeatureCount] = {}; 994 global_use_counts = use_counts; 995 CcTest::isolate()->SetUseCounterCallback(MockUseCounterCallback); 996 CompileRun( 997 "var obj = {};" 998 "Object.observe(obj, function(){})"); 999 CHECK_EQ(1, use_counts[v8::Isolate::kObjectObserve]); 1000 CompileRun( 1001 "var obj2 = {};" 1002 "Object.observe(obj2, function(){})"); 1003 // Only counts the first use of observe in a given context. 1004 CHECK_EQ(1, use_counts[v8::Isolate::kObjectObserve]); 1005 { 1006 LocalContext env2; 1007 CompileRun( 1008 "var obj = {};" 1009 "Object.observe(obj, function(){})"); 1010 } 1011 // Counts different contexts separately. 1012 CHECK_EQ(2, use_counts[v8::Isolate::kObjectObserve]); 1013} 1014 1015 1016TEST(UseCountObjectGetNotifier) { 1017 i::FLAG_harmony_object_observe = true; 1018 i::Isolate* isolate = CcTest::i_isolate(); 1019 i::HandleScope scope(isolate); 1020 LocalContext env; 1021 int use_counts[v8::Isolate::kUseCounterFeatureCount] = {}; 1022 global_use_counts = use_counts; 1023 CcTest::isolate()->SetUseCounterCallback(MockUseCounterCallback); 1024 CompileRun("var obj = {}"); 1025 CompileRun("Object.getNotifier(obj)"); 1026 CHECK_EQ(1, use_counts[v8::Isolate::kObjectObserve]); 1027} 1028 1029 1030static bool NamedAccessCheckAlwaysAllow(Local<v8::Context> accessing_context, 1031 Local<v8::Object> accessed_object) { 1032 return true; 1033} 1034 1035 1036TEST(DisallowObserveAccessCheckedObject) { 1037 i::FLAG_harmony_object_observe = true; 1038 v8::Isolate* isolate = CcTest::isolate(); 1039 v8::HandleScope scope(isolate); 1040 LocalContext env; 1041 v8::Local<v8::ObjectTemplate> object_template = 1042 v8::ObjectTemplate::New(isolate); 1043 object_template->SetAccessCheckCallback(NamedAccessCheckAlwaysAllow); 1044 Local<Object> new_instance = 1045 object_template->NewInstance( 1046 v8::Isolate::GetCurrent()->GetCurrentContext()) 1047 .ToLocalChecked(); 1048 env->Global() 1049 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 1050 new_instance) 1051 .FromJust(); 1052 v8::TryCatch try_catch(isolate); 1053 CompileRun("Object.observe(obj, function(){})"); 1054 CHECK(try_catch.HasCaught()); 1055} 1056 1057 1058TEST(DisallowGetNotifierAccessCheckedObject) { 1059 i::FLAG_harmony_object_observe = true; 1060 v8::Isolate* isolate = CcTest::isolate(); 1061 v8::HandleScope scope(isolate); 1062 LocalContext env; 1063 v8::Local<v8::ObjectTemplate> object_template = 1064 v8::ObjectTemplate::New(isolate); 1065 object_template->SetAccessCheckCallback(NamedAccessCheckAlwaysAllow); 1066 Local<Object> new_instance = 1067 object_template->NewInstance( 1068 v8::Isolate::GetCurrent()->GetCurrentContext()) 1069 .ToLocalChecked(); 1070 env->Global() 1071 ->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str("obj"), 1072 new_instance) 1073 .FromJust(); 1074 v8::TryCatch try_catch(isolate); 1075 CompileRun("Object.getNotifier(obj)"); 1076 CHECK(try_catch.HasCaught()); 1077} 1078