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 "v8.h" 29 30 #include "cctest.h" 31 32 using namespace v8; 33 namespace i = v8::internal; 34 35 namespace { 36 // Need to create a new isolate when FLAG_harmony_observation is on. 37 class HarmonyIsolate { 38 public: 39 HarmonyIsolate() { 40 i::FLAG_harmony_observation = true; 41 isolate_ = Isolate::New(); 42 isolate_->Enter(); 43 } 44 45 ~HarmonyIsolate() { 46 isolate_->Exit(); 47 isolate_->Dispose(); 48 } 49 50 Isolate* GetIsolate() const { return isolate_; } 51 52 private: 53 Isolate* isolate_; 54 }; 55 } 56 57 58 TEST(PerIsolateState) { 59 HarmonyIsolate isolate; 60 HandleScope scope(isolate.GetIsolate()); 61 LocalContext context1(isolate.GetIsolate()); 62 CompileRun( 63 "var count = 0;" 64 "var calls = 0;" 65 "var observer = function(records) { count = records.length; calls++ };" 66 "var obj = {};" 67 "Object.observe(obj, observer);"); 68 Handle<Value> observer = CompileRun("observer"); 69 Handle<Value> obj = CompileRun("obj"); 70 Handle<Value> notify_fun1 = CompileRun( 71 "(function() { obj.foo = 'bar'; })"); 72 Handle<Value> notify_fun2; 73 { 74 LocalContext context2(isolate.GetIsolate()); 75 context2->Global()->Set(String::NewFromUtf8(isolate.GetIsolate(), "obj"), 76 obj); 77 notify_fun2 = CompileRun( 78 "(function() { obj.foo = 'baz'; })"); 79 } 80 Handle<Value> notify_fun3; 81 { 82 LocalContext context3(isolate.GetIsolate()); 83 context3->Global()->Set(String::NewFromUtf8(isolate.GetIsolate(), "obj"), 84 obj); 85 notify_fun3 = CompileRun( 86 "(function() { obj.foo = 'bat'; })"); 87 } 88 { 89 LocalContext context4(isolate.GetIsolate()); 90 context4->Global()->Set( 91 String::NewFromUtf8(isolate.GetIsolate(), "observer"), observer); 92 context4->Global()->Set(String::NewFromUtf8(isolate.GetIsolate(), "fun1"), 93 notify_fun1); 94 context4->Global()->Set(String::NewFromUtf8(isolate.GetIsolate(), "fun2"), 95 notify_fun2); 96 context4->Global()->Set(String::NewFromUtf8(isolate.GetIsolate(), "fun3"), 97 notify_fun3); 98 CompileRun("fun1(); fun2(); fun3(); Object.deliverChangeRecords(observer)"); 99 } 100 CHECK_EQ(1, CompileRun("calls")->Int32Value()); 101 CHECK_EQ(3, CompileRun("count")->Int32Value()); 102 } 103 104 105 TEST(EndOfMicrotaskDelivery) { 106 HarmonyIsolate isolate; 107 HandleScope scope(isolate.GetIsolate()); 108 LocalContext context(isolate.GetIsolate()); 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, CompileRun("count")->Int32Value()); 116 } 117 118 119 TEST(DeliveryOrdering) { 120 HarmonyIsolate isolate; 121 HandleScope scope(isolate.GetIsolate()); 122 LocalContext context(isolate.GetIsolate()); 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, CompileRun("ordering.length")->Int32Value()); 135 CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value()); 136 CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value()); 137 CHECK_EQ(3, CompileRun("ordering[2]")->Int32Value()); 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, CompileRun("ordering.length")->Int32Value()); 145 CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value()); 146 CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value()); 147 CHECK_EQ(3, CompileRun("ordering[2]")->Int32Value()); 148 } 149 150 151 TEST(DeliveryOrderingReentrant) { 152 HarmonyIsolate isolate; 153 HandleScope scope(isolate.GetIsolate()); 154 LocalContext context(isolate.GetIsolate()); 155 CompileRun( 156 "var obj = {};" 157 "var reentered = false;" 158 "var ordering = [];" 159 "function observer1() { ordering.push(1); };" 160 "function observer2() {" 161 " if (!reentered) {" 162 " obj.foo = 'baz';" 163 " reentered = true;" 164 " }" 165 " ordering.push(2);" 166 "};" 167 "function observer3() { ordering.push(3); };" 168 "Object.observe(obj, observer1);" 169 "Object.observe(obj, observer2);" 170 "Object.observe(obj, observer3);" 171 "obj.foo = 'bar';"); 172 CHECK_EQ(5, CompileRun("ordering.length")->Int32Value()); 173 CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value()); 174 CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value()); 175 CHECK_EQ(3, CompileRun("ordering[2]")->Int32Value()); 176 // Note that we re-deliver to observers 1 and 2, while observer3 177 // already received the second record during the first round. 178 CHECK_EQ(1, CompileRun("ordering[3]")->Int32Value()); 179 CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value()); 180 } 181 182 183 TEST(DeliveryOrderingDeliverChangeRecords) { 184 HarmonyIsolate isolate; 185 HandleScope scope(isolate.GetIsolate()); 186 LocalContext context(isolate.GetIsolate()); 187 CompileRun( 188 "var obj = {};" 189 "var ordering = [];" 190 "function observer1() { ordering.push(1); if (!obj.b) obj.b = true };" 191 "function observer2() { ordering.push(2); };" 192 "Object.observe(obj, observer1);" 193 "Object.observe(obj, observer2);" 194 "obj.a = 1;" 195 "Object.deliverChangeRecords(observer2);"); 196 CHECK_EQ(4, CompileRun("ordering.length")->Int32Value()); 197 // First, observer2 is called due to deliverChangeRecords 198 CHECK_EQ(2, CompileRun("ordering[0]")->Int32Value()); 199 // Then, observer1 is called when the stack unwinds 200 CHECK_EQ(1, CompileRun("ordering[1]")->Int32Value()); 201 // observer1's mutation causes both 1 and 2 to be reactivated, 202 // with 1 having priority. 203 CHECK_EQ(1, CompileRun("ordering[2]")->Int32Value()); 204 CHECK_EQ(2, CompileRun("ordering[3]")->Int32Value()); 205 } 206 207 208 TEST(ObjectHashTableGrowth) { 209 HarmonyIsolate isolate; 210 HandleScope scope(isolate.GetIsolate()); 211 // Initializing this context sets up initial hash tables. 212 LocalContext context(isolate.GetIsolate()); 213 Handle<Value> obj = CompileRun("obj = {};"); 214 Handle<Value> observer = CompileRun( 215 "var ran = false;" 216 "(function() { ran = true })"); 217 { 218 // As does initializing this context. 219 LocalContext context2(isolate.GetIsolate()); 220 context2->Global()->Set(String::NewFromUtf8(isolate.GetIsolate(), "obj"), 221 obj); 222 context2->Global()->Set( 223 String::NewFromUtf8(isolate.GetIsolate(), "observer"), observer); 224 CompileRun( 225 "var objArr = [];" 226 // 100 objects should be enough to make the hash table grow 227 // (and thus relocate). 228 "for (var i = 0; i < 100; ++i) {" 229 " objArr.push({});" 230 " Object.observe(objArr[objArr.length-1], function(){});" 231 "}" 232 "Object.observe(obj, observer);"); 233 } 234 // obj is now marked "is_observed", but our map has moved. 235 CompileRun("obj.foo = 'bar'"); 236 CHECK(CompileRun("ran")->BooleanValue()); 237 } 238 239 240 TEST(GlobalObjectObservation) { 241 HarmonyIsolate isolate; 242 LocalContext context(isolate.GetIsolate()); 243 HandleScope scope(isolate.GetIsolate()); 244 Handle<Object> global_proxy = context->Global(); 245 CompileRun( 246 "var records = [];" 247 "var global = this;" 248 "Object.observe(global, function(r) { [].push.apply(records, r) });" 249 "global.foo = 'hello';"); 250 CHECK_EQ(1, CompileRun("records.length")->Int32Value()); 251 CHECK(global_proxy->StrictEquals(CompileRun("records[0].object"))); 252 253 // Detached, mutating the proxy has no effect. 254 context->DetachGlobal(); 255 CompileRun("global.bar = 'goodbye';"); 256 CHECK_EQ(1, CompileRun("records.length")->Int32Value()); 257 CompileRun("this.baz = 'goodbye';"); 258 CHECK_EQ(1, CompileRun("records.length")->Int32Value()); 259 260 // Attached to a different context, should not leak mutations 261 // to the old context. 262 context->DetachGlobal(); 263 { 264 LocalContext context2(isolate.GetIsolate()); 265 CompileRun( 266 "var records2 = [];" 267 "var global = this;" 268 "Object.observe(this, function(r) { [].push.apply(records2, r) });" 269 "this.v1 = 'context2';"); 270 context2->DetachGlobal(); 271 CompileRun( 272 "global.v2 = 'context2';" 273 "this.v3 = 'context2';"); 274 CHECK_EQ(1, CompileRun("records2.length")->Int32Value()); 275 } 276 CHECK_EQ(1, CompileRun("records.length")->Int32Value()); 277 278 // Attaching by passing to Context::New 279 { 280 // Delegates to Context::New 281 LocalContext context3( 282 isolate.GetIsolate(), NULL, Handle<ObjectTemplate>(), global_proxy); 283 CompileRun( 284 "var records3 = [];" 285 "Object.observe(this, function(r) { [].push.apply(records3, r) });" 286 "this.qux = 'context3';"); 287 CHECK_EQ(1, CompileRun("records3.length")->Int32Value()); 288 CHECK(global_proxy->StrictEquals(CompileRun("records3[0].object"))); 289 } 290 CHECK_EQ(1, CompileRun("records.length")->Int32Value()); 291 } 292 293 294 struct RecordExpectation { 295 Handle<Value> object; 296 const char* type; 297 const char* name; 298 Handle<Value> old_value; 299 }; 300 301 302 // TODO(adamk): Use this helper elsewhere in this file. 303 static void ExpectRecords(v8::Isolate* isolate, 304 Handle<Value> records, 305 const RecordExpectation expectations[], 306 int num) { 307 CHECK(records->IsArray()); 308 Handle<Array> recordArray = records.As<Array>(); 309 CHECK_EQ(num, static_cast<int>(recordArray->Length())); 310 for (int i = 0; i < num; ++i) { 311 Handle<Value> record = recordArray->Get(i); 312 CHECK(record->IsObject()); 313 Handle<Object> recordObj = record.As<Object>(); 314 CHECK(expectations[i].object->StrictEquals( 315 recordObj->Get(String::NewFromUtf8(isolate, "object")))); 316 CHECK(String::NewFromUtf8(isolate, expectations[i].type)->Equals( 317 recordObj->Get(String::NewFromUtf8(isolate, "type")))); 318 if (strcmp("splice", expectations[i].type) != 0) { 319 CHECK(String::NewFromUtf8(isolate, expectations[i].name)->Equals( 320 recordObj->Get(String::NewFromUtf8(isolate, "name")))); 321 if (!expectations[i].old_value.IsEmpty()) { 322 CHECK(expectations[i].old_value->Equals( 323 recordObj->Get(String::NewFromUtf8(isolate, "oldValue")))); 324 } 325 } 326 } 327 } 328 329 #define EXPECT_RECORDS(records, expectations) \ 330 ExpectRecords(isolate.GetIsolate(), records, expectations, \ 331 ARRAY_SIZE(expectations)) 332 333 TEST(APITestBasicMutation) { 334 HarmonyIsolate isolate; 335 HandleScope scope(isolate.GetIsolate()); 336 LocalContext context(isolate.GetIsolate()); 337 Handle<Object> obj = Handle<Object>::Cast(CompileRun( 338 "var records = [];" 339 "var obj = {};" 340 "function observer(r) { [].push.apply(records, r); };" 341 "Object.observe(obj, observer);" 342 "obj")); 343 obj->Set(String::NewFromUtf8(isolate.GetIsolate(), "foo"), Number::New(7)); 344 obj->Set(1, Number::New(2)); 345 // ForceSet should work just as well as Set 346 obj->ForceSet(String::NewFromUtf8(isolate.GetIsolate(), "foo"), 347 Number::New(3)); 348 obj->ForceSet(Number::New(1), Number::New(4)); 349 // Setting an indexed element via the property setting method 350 obj->Set(Number::New(1), Number::New(5)); 351 // Setting with a non-String, non-uint32 key 352 obj->Set(Number::New(1.1), Number::New(6), DontDelete); 353 obj->Delete(String::NewFromUtf8(isolate.GetIsolate(), "foo")); 354 obj->Delete(1); 355 obj->ForceDelete(Number::New(1.1)); 356 357 // Force delivery 358 // TODO(adamk): Should the above set methods trigger delivery themselves? 359 CompileRun("void 0"); 360 CHECK_EQ(9, CompileRun("records.length")->Int32Value()); 361 const RecordExpectation expected_records[] = { 362 { obj, "add", "foo", Handle<Value>() }, 363 { obj, "add", "1", Handle<Value>() }, 364 // Note: use 7 not 1 below, as the latter triggers a nifty VS10 compiler bug 365 // where instead of 1.0, a garbage value would be passed into Number::New. 366 { obj, "update", "foo", Number::New(7) }, 367 { obj, "update", "1", Number::New(2) }, 368 { obj, "update", "1", Number::New(4) }, 369 { obj, "add", "1.1", Handle<Value>() }, 370 { obj, "delete", "foo", Number::New(3) }, 371 { obj, "delete", "1", Number::New(5) }, 372 { obj, "delete", "1.1", Number::New(6) } 373 }; 374 EXPECT_RECORDS(CompileRun("records"), expected_records); 375 } 376 377 378 TEST(HiddenPrototypeObservation) { 379 HarmonyIsolate isolate; 380 HandleScope scope(isolate.GetIsolate()); 381 LocalContext context(isolate.GetIsolate()); 382 Handle<FunctionTemplate> tmpl = FunctionTemplate::New(); 383 tmpl->SetHiddenPrototype(true); 384 tmpl->InstanceTemplate()->Set( 385 String::NewFromUtf8(isolate.GetIsolate(), "foo"), Number::New(75)); 386 Handle<Object> proto = tmpl->GetFunction()->NewInstance(); 387 Handle<Object> obj = Object::New(); 388 obj->SetPrototype(proto); 389 context->Global()->Set(String::NewFromUtf8(isolate.GetIsolate(), "obj"), obj); 390 context->Global()->Set(String::NewFromUtf8(isolate.GetIsolate(), "proto"), 391 proto); 392 CompileRun( 393 "var records;" 394 "function observer(r) { records = r; };" 395 "Object.observe(obj, observer);" 396 "obj.foo = 41;" // triggers a notification 397 "proto.foo = 42;"); // does not trigger a notification 398 const RecordExpectation expected_records[] = { 399 { obj, "update", "foo", Number::New(75) } 400 }; 401 EXPECT_RECORDS(CompileRun("records"), expected_records); 402 obj->SetPrototype(Null(isolate.GetIsolate())); 403 CompileRun("obj.foo = 43"); 404 const RecordExpectation expected_records2[] = { 405 { obj, "add", "foo", Handle<Value>() } 406 }; 407 EXPECT_RECORDS(CompileRun("records"), expected_records2); 408 obj->SetPrototype(proto); 409 CompileRun( 410 "Object.observe(proto, observer);" 411 "proto.bar = 1;" 412 "Object.unobserve(obj, observer);" 413 "obj.foo = 44;"); 414 const RecordExpectation expected_records3[] = { 415 { proto, "add", "bar", Handle<Value>() } 416 // TODO(adamk): The below record should be emitted since proto is observed 417 // and has been modified. Not clear if this happens in practice. 418 // { proto, "update", "foo", Number::New(43) } 419 }; 420 EXPECT_RECORDS(CompileRun("records"), expected_records3); 421 } 422 423 424 static int NumberOfElements(i::Handle<i::JSWeakMap> map) { 425 return i::ObjectHashTable::cast(map->table())->NumberOfElements(); 426 } 427 428 429 TEST(ObservationWeakMap) { 430 HarmonyIsolate isolate; 431 HandleScope scope(isolate.GetIsolate()); 432 LocalContext context(isolate.GetIsolate()); 433 CompileRun( 434 "var obj = {};" 435 "Object.observe(obj, function(){});" 436 "Object.getNotifier(obj);" 437 "obj = null;"); 438 i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate.GetIsolate()); 439 i::Handle<i::JSObject> observation_state = 440 i_isolate->factory()->observation_state(); 441 i::Handle<i::JSWeakMap> callbackInfoMap = 442 i::Handle<i::JSWeakMap>::cast( 443 i::GetProperty(observation_state, "callbackInfoMap")); 444 i::Handle<i::JSWeakMap> objectInfoMap = 445 i::Handle<i::JSWeakMap>::cast( 446 i::GetProperty(observation_state, "objectInfoMap")); 447 i::Handle<i::JSWeakMap> notifierObjectInfoMap = 448 i::Handle<i::JSWeakMap>::cast( 449 i::GetProperty(observation_state, "notifierObjectInfoMap")); 450 CHECK_EQ(1, NumberOfElements(callbackInfoMap)); 451 CHECK_EQ(1, NumberOfElements(objectInfoMap)); 452 CHECK_EQ(1, NumberOfElements(notifierObjectInfoMap)); 453 i_isolate->heap()->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask); 454 CHECK_EQ(0, NumberOfElements(callbackInfoMap)); 455 CHECK_EQ(0, NumberOfElements(objectInfoMap)); 456 CHECK_EQ(0, NumberOfElements(notifierObjectInfoMap)); 457 } 458 459 460 static bool NamedAccessAlwaysAllowed(Local<Object>, Local<Value>, AccessType, 461 Local<Value>) { 462 return true; 463 } 464 465 466 static bool IndexedAccessAlwaysAllowed(Local<Object>, uint32_t, AccessType, 467 Local<Value>) { 468 return true; 469 } 470 471 472 static AccessType g_access_block_type = ACCESS_GET; 473 static const uint32_t kBlockedContextIndex = 1337; 474 475 476 static bool NamedAccessAllowUnlessBlocked(Local<Object> host, 477 Local<Value> key, 478 AccessType type, 479 Local<Value> data) { 480 if (type != g_access_block_type) return true; 481 v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>( 482 Utils::OpenHandle(*host)->GetIsolate()); 483 Handle<Object> global = isolate->GetCurrentContext()->Global(); 484 if (!global->Has(kBlockedContextIndex)) return true; 485 return !key->IsString() || !key->Equals(data); 486 } 487 488 489 static bool IndexedAccessAllowUnlessBlocked(Local<Object> host, 490 uint32_t index, 491 AccessType type, 492 Local<Value> data) { 493 if (type != g_access_block_type) return true; 494 v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>( 495 Utils::OpenHandle(*host)->GetIsolate()); 496 Handle<Object> global = isolate->GetCurrentContext()->Global(); 497 if (!global->Has(kBlockedContextIndex)) return true; 498 return index != data->Uint32Value(); 499 } 500 501 502 static bool BlockAccessKeys(Local<Object> host, Local<Value> key, 503 AccessType type, Local<Value>) { 504 v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>( 505 Utils::OpenHandle(*host)->GetIsolate()); 506 Handle<Object> global = isolate->GetCurrentContext()->Global(); 507 return type != ACCESS_KEYS || !global->Has(kBlockedContextIndex); 508 } 509 510 511 static Handle<Object> CreateAccessCheckedObject( 512 v8::Isolate* isolate, 513 NamedSecurityCallback namedCallback, 514 IndexedSecurityCallback indexedCallback, 515 Handle<Value> data = Handle<Value>()) { 516 Handle<ObjectTemplate> tmpl = ObjectTemplate::New(); 517 tmpl->SetAccessCheckCallbacks(namedCallback, indexedCallback, data); 518 Handle<Object> instance = tmpl->NewInstance(); 519 Handle<Object> global = instance->CreationContext()->Global(); 520 global->Set(String::NewFromUtf8(isolate, "obj"), instance); 521 global->Set(kBlockedContextIndex, v8::True(isolate)); 522 return instance; 523 } 524 525 526 TEST(NamedAccessCheck) { 527 HarmonyIsolate isolate; 528 const AccessType types[] = { ACCESS_GET, ACCESS_HAS }; 529 for (size_t i = 0; i < ARRAY_SIZE(types); ++i) { 530 HandleScope scope(isolate.GetIsolate()); 531 LocalContext context(isolate.GetIsolate()); 532 g_access_block_type = types[i]; 533 Handle<Object> instance = CreateAccessCheckedObject( 534 isolate.GetIsolate(), 535 NamedAccessAllowUnlessBlocked, 536 IndexedAccessAlwaysAllowed, 537 String::NewFromUtf8(isolate.GetIsolate(), "foo")); 538 CompileRun("var records = null;" 539 "var objNoCheck = {};" 540 "var observer = function(r) { records = r };" 541 "Object.observe(obj, observer);" 542 "Object.observe(objNoCheck, observer);"); 543 Handle<Value> obj_no_check = CompileRun("objNoCheck"); 544 { 545 LocalContext context2(isolate.GetIsolate()); 546 context2->Global()->Set(String::NewFromUtf8(isolate.GetIsolate(), "obj"), 547 instance); 548 context2->Global()->Set( 549 String::NewFromUtf8(isolate.GetIsolate(), "objNoCheck"), 550 obj_no_check); 551 CompileRun("var records2 = null;" 552 "var observer2 = function(r) { records2 = r };" 553 "Object.observe(obj, observer2);" 554 "Object.observe(objNoCheck, observer2);" 555 "obj.foo = 'bar';" 556 "Object.defineProperty(obj, 'foo', {value: 5});" 557 "Object.defineProperty(obj, 'foo', {get: function(){}});" 558 "obj.bar = 'baz';" 559 "objNoCheck.baz = 'quux'"); 560 const RecordExpectation expected_records2[] = { 561 { instance, "add", "foo", Handle<Value>() }, 562 { instance, "update", "foo", 563 String::NewFromUtf8(isolate.GetIsolate(), "bar") }, 564 { instance, "reconfigure", "foo", Number::New(5) }, 565 { instance, "add", "bar", Handle<Value>() }, 566 { obj_no_check, "add", "baz", Handle<Value>() }, 567 }; 568 EXPECT_RECORDS(CompileRun("records2"), expected_records2); 569 } 570 const RecordExpectation expected_records[] = { 571 { instance, "add", "bar", Handle<Value>() }, 572 { obj_no_check, "add", "baz", Handle<Value>() } 573 }; 574 EXPECT_RECORDS(CompileRun("records"), expected_records); 575 } 576 } 577 578 579 TEST(IndexedAccessCheck) { 580 HarmonyIsolate isolate; 581 const AccessType types[] = { ACCESS_GET, ACCESS_HAS }; 582 for (size_t i = 0; i < ARRAY_SIZE(types); ++i) { 583 HandleScope scope(isolate.GetIsolate()); 584 LocalContext context(isolate.GetIsolate()); 585 g_access_block_type = types[i]; 586 Handle<Object> instance = CreateAccessCheckedObject( 587 isolate.GetIsolate(), NamedAccessAlwaysAllowed, 588 IndexedAccessAllowUnlessBlocked, Number::New(7)); 589 CompileRun("var records = null;" 590 "var objNoCheck = {};" 591 "var observer = function(r) { records = r };" 592 "Object.observe(obj, observer);" 593 "Object.observe(objNoCheck, observer);"); 594 Handle<Value> obj_no_check = CompileRun("objNoCheck"); 595 { 596 LocalContext context2(isolate.GetIsolate()); 597 context2->Global()->Set(String::NewFromUtf8(isolate.GetIsolate(), "obj"), 598 instance); 599 context2->Global()->Set( 600 String::NewFromUtf8(isolate.GetIsolate(), "objNoCheck"), 601 obj_no_check); 602 CompileRun("var records2 = null;" 603 "var observer2 = function(r) { records2 = r };" 604 "Object.observe(obj, observer2);" 605 "Object.observe(objNoCheck, observer2);" 606 "obj[7] = 'foo';" 607 "Object.defineProperty(obj, '7', {value: 5});" 608 "Object.defineProperty(obj, '7', {get: function(){}});" 609 "obj[8] = 'bar';" 610 "objNoCheck[42] = 'quux'"); 611 const RecordExpectation expected_records2[] = { 612 { instance, "add", "7", Handle<Value>() }, 613 { instance, "update", "7", 614 String::NewFromUtf8(isolate.GetIsolate(), "foo") }, 615 { instance, "reconfigure", "7", Number::New(5) }, 616 { instance, "add", "8", Handle<Value>() }, 617 { obj_no_check, "add", "42", Handle<Value>() } 618 }; 619 EXPECT_RECORDS(CompileRun("records2"), expected_records2); 620 } 621 const RecordExpectation expected_records[] = { 622 { instance, "add", "8", Handle<Value>() }, 623 { obj_no_check, "add", "42", Handle<Value>() } 624 }; 625 EXPECT_RECORDS(CompileRun("records"), expected_records); 626 } 627 } 628 629 630 TEST(SpliceAccessCheck) { 631 HarmonyIsolate isolate; 632 HandleScope scope(isolate.GetIsolate()); 633 LocalContext context(isolate.GetIsolate()); 634 g_access_block_type = ACCESS_GET; 635 Handle<Object> instance = CreateAccessCheckedObject( 636 isolate.GetIsolate(), NamedAccessAlwaysAllowed, 637 IndexedAccessAllowUnlessBlocked, Number::New(1)); 638 CompileRun("var records = null;" 639 "obj[1] = 'foo';" 640 "obj.length = 2;" 641 "var objNoCheck = {1: 'bar', length: 2};" 642 "observer = function(r) { records = r };" 643 "Array.observe(obj, observer);" 644 "Array.observe(objNoCheck, observer);"); 645 Handle<Value> obj_no_check = CompileRun("objNoCheck"); 646 { 647 LocalContext context2(isolate.GetIsolate()); 648 context2->Global()->Set(String::NewFromUtf8(isolate.GetIsolate(), "obj"), 649 instance); 650 context2->Global()->Set( 651 String::NewFromUtf8(isolate.GetIsolate(), "objNoCheck"), obj_no_check); 652 CompileRun("var records2 = null;" 653 "var observer2 = function(r) { records2 = r };" 654 "Array.observe(obj, observer2);" 655 "Array.observe(objNoCheck, observer2);" 656 // No one should hear about this: no splice records are emitted 657 // for access-checked objects 658 "[].push.call(obj, 5);" 659 "[].splice.call(obj, 1, 1);" 660 "[].pop.call(obj);" 661 "[].pop.call(objNoCheck);"); 662 // TODO(adamk): Extend EXPECT_RECORDS to be able to assert more things 663 // about splice records. For this test it's not so important since 664 // we just want to guarantee the machinery is in operation at all. 665 const RecordExpectation expected_records2[] = { 666 { obj_no_check, "splice", "", Handle<Value>() } 667 }; 668 EXPECT_RECORDS(CompileRun("records2"), expected_records2); 669 } 670 const RecordExpectation expected_records[] = { 671 { obj_no_check, "splice", "", Handle<Value>() } 672 }; 673 EXPECT_RECORDS(CompileRun("records"), expected_records); 674 } 675 676 677 TEST(DisallowAllForAccessKeys) { 678 HarmonyIsolate isolate; 679 HandleScope scope(isolate.GetIsolate()); 680 LocalContext context(isolate.GetIsolate()); 681 Handle<Object> instance = CreateAccessCheckedObject( 682 isolate.GetIsolate(), BlockAccessKeys, IndexedAccessAlwaysAllowed); 683 CompileRun("var records = null;" 684 "var objNoCheck = {};" 685 "var observer = function(r) { records = r };" 686 "Object.observe(obj, observer);" 687 "Object.observe(objNoCheck, observer);"); 688 Handle<Value> obj_no_check = CompileRun("objNoCheck"); 689 { 690 LocalContext context2(isolate.GetIsolate()); 691 context2->Global()->Set(String::NewFromUtf8(isolate.GetIsolate(), "obj"), 692 instance); 693 context2->Global()->Set( 694 String::NewFromUtf8(isolate.GetIsolate(), "objNoCheck"), obj_no_check); 695 CompileRun("var records2 = null;" 696 "var observer2 = function(r) { records2 = r };" 697 "Object.observe(obj, observer2);" 698 "Object.observe(objNoCheck, observer2);" 699 "obj.foo = 'bar';" 700 "obj[5] = 'baz';" 701 "objNoCheck.baz = 'quux'"); 702 const RecordExpectation expected_records2[] = { 703 { instance, "add", "foo", Handle<Value>() }, 704 { instance, "add", "5", Handle<Value>() }, 705 { obj_no_check, "add", "baz", Handle<Value>() }, 706 }; 707 EXPECT_RECORDS(CompileRun("records2"), expected_records2); 708 } 709 const RecordExpectation expected_records[] = { 710 { obj_no_check, "add", "baz", Handle<Value>() } 711 }; 712 EXPECT_RECORDS(CompileRun("records"), expected_records); 713 } 714 715 716 TEST(AccessCheckDisallowApiModifications) { 717 HarmonyIsolate isolate; 718 HandleScope scope(isolate.GetIsolate()); 719 LocalContext context(isolate.GetIsolate()); 720 Handle<Object> instance = CreateAccessCheckedObject( 721 isolate.GetIsolate(), BlockAccessKeys, IndexedAccessAlwaysAllowed); 722 CompileRun("var records = null;" 723 "var observer = function(r) { records = r };" 724 "Object.observe(obj, observer);"); 725 { 726 LocalContext context2(isolate.GetIsolate()); 727 context2->Global()->Set(String::NewFromUtf8(isolate.GetIsolate(), "obj"), 728 instance); 729 CompileRun("var records2 = null;" 730 "var observer2 = function(r) { records2 = r };" 731 "Object.observe(obj, observer2);"); 732 instance->Set(5, String::NewFromUtf8(isolate.GetIsolate(), "bar")); 733 instance->Set(String::NewFromUtf8(isolate.GetIsolate(), "foo"), 734 String::NewFromUtf8(isolate.GetIsolate(), "bar")); 735 CompileRun(""); // trigger delivery 736 const RecordExpectation expected_records2[] = { 737 { instance, "add", "5", Handle<Value>() }, 738 { instance, "add", "foo", Handle<Value>() } 739 }; 740 EXPECT_RECORDS(CompileRun("records2"), expected_records2); 741 } 742 CHECK(CompileRun("records")->IsNull()); 743 } 744 745 746 TEST(HiddenPropertiesLeakage) { 747 HarmonyIsolate isolate; 748 HandleScope scope(isolate.GetIsolate()); 749 LocalContext context(isolate.GetIsolate()); 750 CompileRun("var obj = {};" 751 "var records = null;" 752 "var observer = function(r) { records = r };" 753 "Object.observe(obj, observer);"); 754 Handle<Value> obj = 755 context->Global()->Get(String::NewFromUtf8(isolate.GetIsolate(), "obj")); 756 Handle<Object>::Cast(obj) 757 ->SetHiddenValue(String::NewFromUtf8(isolate.GetIsolate(), "foo"), 758 Null(isolate.GetIsolate())); 759 CompileRun(""); // trigger delivery 760 CHECK(CompileRun("records")->IsNull()); 761 } 762