• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
HarmonyIsolate()39   HarmonyIsolate() {
40     i::FLAG_harmony_observation = true;
41     isolate_ = Isolate::New();
42     isolate_->Enter();
43   }
44 
~HarmonyIsolate()45   ~HarmonyIsolate() {
46     isolate_->Exit();
47     isolate_->Dispose();
48   }
49 
GetIsolate() const50   Isolate* GetIsolate() const { return isolate_; }
51 
52  private:
53   Isolate* isolate_;
54 };
55 }
56 
57 
TEST(PerIsolateState)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 
TEST(EndOfMicrotaskDelivery)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 
TEST(DeliveryOrdering)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 
TEST(DeliveryOrderingReentrant)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 
TEST(DeliveryOrderingDeliverChangeRecords)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 
TEST(ObjectHashTableGrowth)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 
TEST(GlobalObjectObservation)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.
ExpectRecords(v8::Isolate * isolate,Handle<Value> records,const RecordExpectation expectations[],int num)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 
TEST(APITestBasicMutation)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 
TEST(HiddenPrototypeObservation)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 
NumberOfElements(i::Handle<i::JSWeakMap> map)424 static int NumberOfElements(i::Handle<i::JSWeakMap> map) {
425   return i::ObjectHashTable::cast(map->table())->NumberOfElements();
426 }
427 
428 
TEST(ObservationWeakMap)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 
NamedAccessAlwaysAllowed(Local<Object>,Local<Value>,AccessType,Local<Value>)460 static bool NamedAccessAlwaysAllowed(Local<Object>, Local<Value>, AccessType,
461                                      Local<Value>) {
462   return true;
463 }
464 
465 
IndexedAccessAlwaysAllowed(Local<Object>,uint32_t,AccessType,Local<Value>)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 
NamedAccessAllowUnlessBlocked(Local<Object> host,Local<Value> key,AccessType type,Local<Value> data)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 
IndexedAccessAllowUnlessBlocked(Local<Object> host,uint32_t index,AccessType type,Local<Value> data)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 
BlockAccessKeys(Local<Object> host,Local<Value> key,AccessType type,Local<Value>)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 
CreateAccessCheckedObject(v8::Isolate * isolate,NamedSecurityCallback namedCallback,IndexedSecurityCallback indexedCallback,Handle<Value> data=Handle<Value> ())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 
TEST(NamedAccessCheck)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 
TEST(IndexedAccessCheck)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 
TEST(SpliceAccessCheck)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 
TEST(DisallowAllForAccessKeys)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 
TEST(AccessCheckDisallowApiModifications)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 
TEST(HiddenPropertiesLeakage)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