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 
32 using namespace v8;
33 namespace i = v8::internal;
34 
ToInt32(v8::Local<v8::Value> value)35 inline int32_t ToInt32(v8::Local<v8::Value> value) {
36   return value->Int32Value(v8::Isolate::GetCurrent()->GetCurrentContext())
37       .FromJust();
38 }
39 
40 
TEST(PerIsolateState)41 TEST(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 
TEST(EndOfMicrotaskDelivery)105 TEST(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 
TEST(DeliveryOrdering)119 TEST(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 
TEST(DeliveryCallbackThrows)151 TEST(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 
TEST(DeliveryChangesMutationInCallback)180 TEST(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 
TEST(DeliveryOrderingReentrant)206 TEST(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 
TEST(DeliveryOrderingDeliverChangeRecords)238 TEST(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 
TEST(ObjectHashTableGrowth)263 TEST(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 
301 struct 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.
ExpectRecords(v8::Isolate * isolate,Local<Value> records,const RecordExpectation expectations[],int num)310 static 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 
TEST(APITestBasicMutation)359 TEST(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 
TEST(HiddenPrototypeObservation)419 TEST(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 
NumberOfElements(i::Handle<i::JSWeakMap> map)477 static int NumberOfElements(i::Handle<i::JSWeakMap> map) {
478   return i::ObjectHashTable::cast(map->table())->NumberOfElements();
479 }
480 
481 
TEST(ObservationWeakMap)482 TEST(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 
TestObserveSecurity(Local<Context> observer_context,Local<Context> object_context,Local<Context> mutation_context)514 static 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 
TEST(ObserverSecurityAAA)553 TEST(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 
TEST(ObserverSecurityA1A2A3)562 TEST(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 
TEST(ObserverSecurityAAB)580 TEST(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 
TEST(ObserverSecurityA1A2B)590 TEST(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 
TEST(ObserverSecurityABA)607 TEST(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 
TEST(ObserverSecurityA1BA2)617 TEST(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 
TEST(ObserverSecurityBAA)633 TEST(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 
TEST(ObserverSecurityBA1A2)643 TEST(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 
TEST(ObserverSecurityNotify)659 TEST(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 
TEST(HiddenPropertiesLeakage)695 TEST(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 
TEST(GetNotifierFromOtherContext)717 TEST(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 
TEST(GetNotifierFromOtherOrigin)734 TEST(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 
TEST(GetNotifierFromSameOrigin)755 TEST(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 
GetGlobalObjectsCount()775 static 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 
CheckSurvivingGlobalObjectsCount(int expected)791 static 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 
TEST(DontLeakContextOnObserve)807 TEST(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 
TEST(DontLeakContextOnGetNotifier)833 TEST(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 
TEST(DontLeakContextOnNotifierPerformChange)857 TEST(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 
ObserverCallback(const FunctionCallbackInfo<Value> & args)889 static 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 
TEST(ObjectObserveCallsCppFunction)895 TEST(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 
TEST(ObjectObserveCallsFunctionTemplateInstance)918 TEST(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 
AccessorGetter(Local<Name> property,const PropertyCallbackInfo<Value> & info)942 static void AccessorGetter(Local<Name> property,
943                            const PropertyCallbackInfo<Value>& info) {
944   info.GetReturnValue().Set(Integer::New(info.GetIsolate(), 42));
945 }
946 
947 
AccessorSetter(Local<Name> property,Local<Value> value,const PropertyCallbackInfo<void> & info)948 static void AccessorSetter(Local<Name> property, Local<Value> value,
949                            const PropertyCallbackInfo<void>& info) {
950   info.GetReturnValue().SetUndefined();
951 }
952 
953 
TEST(APIAccessorsShouldNotNotify)954 TEST(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 
977 namespace {
978 
979 int* global_use_counts = NULL;
980 
MockUseCounterCallback(v8::Isolate * isolate,v8::Isolate::UseCounterFeature feature)981 void MockUseCounterCallback(v8::Isolate* isolate,
982                             v8::Isolate::UseCounterFeature feature) {
983   ++global_use_counts[feature];
984 }
985 }
986 
987 
TEST(UseCountObjectObserve)988 TEST(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 
TEST(UseCountObjectGetNotifier)1016 TEST(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 
NamedAccessCheckAlwaysAllow(Local<v8::Context> accessing_context,Local<v8::Object> accessed_object)1030 static bool NamedAccessCheckAlwaysAllow(Local<v8::Context> accessing_context,
1031                                         Local<v8::Object> accessed_object) {
1032   return true;
1033 }
1034 
1035 
TEST(DisallowObserveAccessCheckedObject)1036 TEST(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 
TEST(DisallowGetNotifierAccessCheckedObject)1058 TEST(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