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