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
35
36TEST(PerIsolateState) {
37  HandleScope scope(CcTest::isolate());
38  LocalContext context1(CcTest::isolate());
39
40  Local<Value> foo = v8_str("foo");
41  context1->SetSecurityToken(foo);
42
43  CompileRun(
44      "var count = 0;"
45      "var calls = 0;"
46      "var observer = function(records) { count = records.length; calls++ };"
47      "var obj = {};"
48      "Object.observe(obj, observer);");
49  Handle<Value> observer = CompileRun("observer");
50  Handle<Value> obj = CompileRun("obj");
51  Handle<Value> notify_fun1 = CompileRun(
52      "(function() { obj.foo = 'bar'; })");
53  Handle<Value> notify_fun2;
54  {
55    LocalContext context2(CcTest::isolate());
56    context2->SetSecurityToken(foo);
57    context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
58                            obj);
59    notify_fun2 = CompileRun(
60        "(function() { obj.foo = 'baz'; })");
61  }
62  Handle<Value> notify_fun3;
63  {
64    LocalContext context3(CcTest::isolate());
65    context3->SetSecurityToken(foo);
66    context3->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
67                            obj);
68    notify_fun3 = CompileRun(
69        "(function() { obj.foo = 'bat'; })");
70  }
71  {
72    LocalContext context4(CcTest::isolate());
73    context4->SetSecurityToken(foo);
74    context4->Global()->Set(
75        String::NewFromUtf8(CcTest::isolate(), "observer"), observer);
76    context4->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "fun1"),
77                            notify_fun1);
78    context4->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "fun2"),
79                            notify_fun2);
80    context4->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "fun3"),
81                            notify_fun3);
82    CompileRun("fun1(); fun2(); fun3(); Object.deliverChangeRecords(observer)");
83  }
84  CHECK_EQ(1, CompileRun("calls")->Int32Value());
85  CHECK_EQ(3, CompileRun("count")->Int32Value());
86}
87
88
89TEST(EndOfMicrotaskDelivery) {
90  HandleScope scope(CcTest::isolate());
91  LocalContext context(CcTest::isolate());
92  CompileRun(
93      "var obj = {};"
94      "var count = 0;"
95      "var observer = function(records) { count = records.length };"
96      "Object.observe(obj, observer);"
97      "obj.foo = 'bar';");
98  CHECK_EQ(1, CompileRun("count")->Int32Value());
99}
100
101
102TEST(DeliveryOrdering) {
103  HandleScope scope(CcTest::isolate());
104  LocalContext context(CcTest::isolate());
105  CompileRun(
106      "var obj1 = {};"
107      "var obj2 = {};"
108      "var ordering = [];"
109      "function observer2() { ordering.push(2); };"
110      "function observer1() { ordering.push(1); };"
111      "function observer3() { ordering.push(3); };"
112      "Object.observe(obj1, observer1);"
113      "Object.observe(obj1, observer2);"
114      "Object.observe(obj1, observer3);"
115      "obj1.foo = 'bar';");
116  CHECK_EQ(3, CompileRun("ordering.length")->Int32Value());
117  CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value());
118  CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
119  CHECK_EQ(3, CompileRun("ordering[2]")->Int32Value());
120  CompileRun(
121      "ordering = [];"
122      "Object.observe(obj2, observer3);"
123      "Object.observe(obj2, observer2);"
124      "Object.observe(obj2, observer1);"
125      "obj2.foo = 'baz'");
126  CHECK_EQ(3, CompileRun("ordering.length")->Int32Value());
127  CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value());
128  CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
129  CHECK_EQ(3, CompileRun("ordering[2]")->Int32Value());
130}
131
132
133TEST(DeliveryOrderingReentrant) {
134  HandleScope scope(CcTest::isolate());
135  LocalContext context(CcTest::isolate());
136  CompileRun(
137      "var obj = {};"
138      "var reentered = false;"
139      "var ordering = [];"
140      "function observer1() { ordering.push(1); };"
141      "function observer2() {"
142      "  if (!reentered) {"
143      "    obj.foo = 'baz';"
144      "    reentered = true;"
145      "  }"
146      "  ordering.push(2);"
147      "};"
148      "function observer3() { ordering.push(3); };"
149      "Object.observe(obj, observer1);"
150      "Object.observe(obj, observer2);"
151      "Object.observe(obj, observer3);"
152      "obj.foo = 'bar';");
153  CHECK_EQ(5, CompileRun("ordering.length")->Int32Value());
154  CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value());
155  CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
156  CHECK_EQ(3, CompileRun("ordering[2]")->Int32Value());
157  // Note that we re-deliver to observers 1 and 2, while observer3
158  // already received the second record during the first round.
159  CHECK_EQ(1, CompileRun("ordering[3]")->Int32Value());
160  CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
161}
162
163
164TEST(DeliveryOrderingDeliverChangeRecords) {
165  HandleScope scope(CcTest::isolate());
166  LocalContext context(CcTest::isolate());
167  CompileRun(
168      "var obj = {};"
169      "var ordering = [];"
170      "function observer1() { ordering.push(1); if (!obj.b) obj.b = true };"
171      "function observer2() { ordering.push(2); };"
172      "Object.observe(obj, observer1);"
173      "Object.observe(obj, observer2);"
174      "obj.a = 1;"
175      "Object.deliverChangeRecords(observer2);");
176  CHECK_EQ(4, CompileRun("ordering.length")->Int32Value());
177  // First, observer2 is called due to deliverChangeRecords
178  CHECK_EQ(2, CompileRun("ordering[0]")->Int32Value());
179  // Then, observer1 is called when the stack unwinds
180  CHECK_EQ(1, CompileRun("ordering[1]")->Int32Value());
181  // observer1's mutation causes both 1 and 2 to be reactivated,
182  // with 1 having priority.
183  CHECK_EQ(1, CompileRun("ordering[2]")->Int32Value());
184  CHECK_EQ(2, CompileRun("ordering[3]")->Int32Value());
185}
186
187
188TEST(ObjectHashTableGrowth) {
189  HandleScope scope(CcTest::isolate());
190  // Initializing this context sets up initial hash tables.
191  LocalContext context(CcTest::isolate());
192  Handle<Value> obj = CompileRun("obj = {};");
193  Handle<Value> observer = CompileRun(
194      "var ran = false;"
195      "(function() { ran = true })");
196  {
197    // As does initializing this context.
198    LocalContext context2(CcTest::isolate());
199    context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
200                            obj);
201    context2->Global()->Set(
202        String::NewFromUtf8(CcTest::isolate(), "observer"), observer);
203    CompileRun(
204        "var objArr = [];"
205        // 100 objects should be enough to make the hash table grow
206        // (and thus relocate).
207        "for (var i = 0; i < 100; ++i) {"
208        "  objArr.push({});"
209        "  Object.observe(objArr[objArr.length-1], function(){});"
210        "}"
211        "Object.observe(obj, observer);");
212  }
213  // obj is now marked "is_observed", but our map has moved.
214  CompileRun("obj.foo = 'bar'");
215  CHECK(CompileRun("ran")->BooleanValue());
216}
217
218
219struct RecordExpectation {
220  Handle<Value> object;
221  const char* type;
222  const char* name;
223  Handle<Value> old_value;
224};
225
226
227// TODO(adamk): Use this helper elsewhere in this file.
228static void ExpectRecords(v8::Isolate* isolate,
229                          Handle<Value> records,
230                          const RecordExpectation expectations[],
231                          int num) {
232  CHECK(records->IsArray());
233  Handle<Array> recordArray = records.As<Array>();
234  CHECK_EQ(num, static_cast<int>(recordArray->Length()));
235  for (int i = 0; i < num; ++i) {
236    Handle<Value> record = recordArray->Get(i);
237    CHECK(record->IsObject());
238    Handle<Object> recordObj = record.As<Object>();
239    CHECK(expectations[i].object->StrictEquals(
240        recordObj->Get(String::NewFromUtf8(isolate, "object"))));
241    CHECK(String::NewFromUtf8(isolate, expectations[i].type)->Equals(
242        recordObj->Get(String::NewFromUtf8(isolate, "type"))));
243    if (strcmp("splice", expectations[i].type) != 0) {
244      CHECK(String::NewFromUtf8(isolate, expectations[i].name)->Equals(
245          recordObj->Get(String::NewFromUtf8(isolate, "name"))));
246      if (!expectations[i].old_value.IsEmpty()) {
247        CHECK(expectations[i].old_value->Equals(
248            recordObj->Get(String::NewFromUtf8(isolate, "oldValue"))));
249      }
250    }
251  }
252}
253
254#define EXPECT_RECORDS(records, expectations)                \
255  ExpectRecords(CcTest::isolate(), records, expectations, \
256                arraysize(expectations))
257
258TEST(APITestBasicMutation) {
259  v8::Isolate* v8_isolate = CcTest::isolate();
260  HandleScope scope(v8_isolate);
261  LocalContext context(v8_isolate);
262  Handle<Object> obj = Handle<Object>::Cast(CompileRun(
263      "var records = [];"
264      "var obj = {};"
265      "function observer(r) { [].push.apply(records, r); };"
266      "Object.observe(obj, observer);"
267      "obj"));
268  obj->Set(String::NewFromUtf8(v8_isolate, "foo"),
269           Number::New(v8_isolate, 7));
270  obj->Set(1, Number::New(v8_isolate, 2));
271  // ForceSet should work just as well as Set
272  obj->ForceSet(String::NewFromUtf8(v8_isolate, "foo"),
273                Number::New(v8_isolate, 3));
274  obj->ForceSet(Number::New(v8_isolate, 1), Number::New(v8_isolate, 4));
275  // Setting an indexed element via the property setting method
276  obj->Set(Number::New(v8_isolate, 1), Number::New(v8_isolate, 5));
277  // Setting with a non-String, non-uint32 key
278  obj->ForceSet(Number::New(v8_isolate, 1.1), Number::New(v8_isolate, 6),
279                DontDelete);
280  obj->Delete(String::NewFromUtf8(v8_isolate, "foo"));
281  obj->Delete(1);
282  obj->ForceDelete(Number::New(v8_isolate, 1.1));
283
284  // Force delivery
285  // TODO(adamk): Should the above set methods trigger delivery themselves?
286  CompileRun("void 0");
287  CHECK_EQ(9, CompileRun("records.length")->Int32Value());
288  const RecordExpectation expected_records[] = {
289    { obj, "add", "foo", Handle<Value>() },
290    { obj, "add", "1", Handle<Value>() },
291    // Note: use 7 not 1 below, as the latter triggers a nifty VS10 compiler bug
292    // where instead of 1.0, a garbage value would be passed into Number::New.
293    { obj, "update", "foo", Number::New(v8_isolate, 7) },
294    { obj, "update", "1", Number::New(v8_isolate, 2) },
295    { obj, "update", "1", Number::New(v8_isolate, 4) },
296    { obj, "add", "1.1", Handle<Value>() },
297    { obj, "delete", "foo", Number::New(v8_isolate, 3) },
298    { obj, "delete", "1", Number::New(v8_isolate, 5) },
299    { obj, "delete", "1.1", Number::New(v8_isolate, 6) }
300  };
301  EXPECT_RECORDS(CompileRun("records"), expected_records);
302}
303
304
305TEST(HiddenPrototypeObservation) {
306  v8::Isolate* v8_isolate = CcTest::isolate();
307  HandleScope scope(v8_isolate);
308  LocalContext context(v8_isolate);
309  Handle<FunctionTemplate> tmpl = FunctionTemplate::New(v8_isolate);
310  tmpl->SetHiddenPrototype(true);
311  tmpl->InstanceTemplate()->Set(
312      String::NewFromUtf8(v8_isolate, "foo"), Number::New(v8_isolate, 75));
313  Handle<Object> proto = tmpl->GetFunction()->NewInstance();
314  Handle<Object> obj = Object::New(v8_isolate);
315  obj->SetPrototype(proto);
316  context->Global()->Set(String::NewFromUtf8(v8_isolate, "obj"), obj);
317  context->Global()->Set(String::NewFromUtf8(v8_isolate, "proto"),
318                         proto);
319  CompileRun(
320      "var records;"
321      "function observer(r) { records = r; };"
322      "Object.observe(obj, observer);"
323      "obj.foo = 41;"  // triggers a notification
324      "proto.foo = 42;");  // does not trigger a notification
325  const RecordExpectation expected_records[] = {
326    { obj, "update", "foo", Number::New(v8_isolate, 75) }
327  };
328  EXPECT_RECORDS(CompileRun("records"), expected_records);
329  obj->SetPrototype(Null(v8_isolate));
330  CompileRun("obj.foo = 43");
331  const RecordExpectation expected_records2[] = {
332    { obj, "add", "foo", Handle<Value>() }
333  };
334  EXPECT_RECORDS(CompileRun("records"), expected_records2);
335  obj->SetPrototype(proto);
336  CompileRun(
337      "Object.observe(proto, observer);"
338      "proto.bar = 1;"
339      "Object.unobserve(obj, observer);"
340      "obj.foo = 44;");
341  const RecordExpectation expected_records3[] = {
342    { proto, "add", "bar", Handle<Value>() }
343    // TODO(adamk): The below record should be emitted since proto is observed
344    // and has been modified. Not clear if this happens in practice.
345    // { proto, "update", "foo", Number::New(43) }
346  };
347  EXPECT_RECORDS(CompileRun("records"), expected_records3);
348}
349
350
351static int NumberOfElements(i::Handle<i::JSWeakMap> map) {
352  return i::ObjectHashTable::cast(map->table())->NumberOfElements();
353}
354
355
356TEST(ObservationWeakMap) {
357  HandleScope scope(CcTest::isolate());
358  LocalContext context(CcTest::isolate());
359  CompileRun(
360      "var obj = {};"
361      "Object.observe(obj, function(){});"
362      "Object.getNotifier(obj);"
363      "obj = null;");
364  i::Isolate* i_isolate = CcTest::i_isolate();
365  i::Handle<i::JSObject> observation_state =
366      i_isolate->factory()->observation_state();
367  i::Handle<i::JSWeakMap> callbackInfoMap =
368      i::Handle<i::JSWeakMap>::cast(i::Object::GetProperty(
369          i_isolate, observation_state, "callbackInfoMap").ToHandleChecked());
370  i::Handle<i::JSWeakMap> objectInfoMap =
371      i::Handle<i::JSWeakMap>::cast(i::Object::GetProperty(
372          i_isolate, observation_state, "objectInfoMap").ToHandleChecked());
373  i::Handle<i::JSWeakMap> notifierObjectInfoMap =
374      i::Handle<i::JSWeakMap>::cast(i::Object::GetProperty(
375          i_isolate, observation_state, "notifierObjectInfoMap")
376              .ToHandleChecked());
377  CHECK_EQ(1, NumberOfElements(callbackInfoMap));
378  CHECK_EQ(1, NumberOfElements(objectInfoMap));
379  CHECK_EQ(1, NumberOfElements(notifierObjectInfoMap));
380  i_isolate->heap()->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask);
381  CHECK_EQ(0, NumberOfElements(callbackInfoMap));
382  CHECK_EQ(0, NumberOfElements(objectInfoMap));
383  CHECK_EQ(0, NumberOfElements(notifierObjectInfoMap));
384}
385
386
387static int TestObserveSecurity(Handle<Context> observer_context,
388                               Handle<Context> object_context,
389                               Handle<Context> mutation_context) {
390  Context::Scope observer_scope(observer_context);
391  CompileRun("var records = null;"
392             "var observer = function(r) { records = r };");
393  Handle<Value> observer = CompileRun("observer");
394  {
395    Context::Scope object_scope(object_context);
396    object_context->Global()->Set(
397        String::NewFromUtf8(CcTest::isolate(), "observer"), observer);
398    CompileRun("var obj = {};"
399               "obj.length = 0;"
400               "Object.observe(obj, observer,"
401                   "['add', 'update', 'delete','reconfigure','splice']"
402               ");");
403    Handle<Value> obj = CompileRun("obj");
404    {
405      Context::Scope mutation_scope(mutation_context);
406      mutation_context->Global()->Set(
407          String::NewFromUtf8(CcTest::isolate(), "obj"), obj);
408      CompileRun("obj.foo = 'bar';"
409                 "obj.foo = 'baz';"
410                 "delete obj.foo;"
411                 "Object.defineProperty(obj, 'bar', {value: 'bot'});"
412                 "Array.prototype.push.call(obj, 1, 2, 3);"
413                 "Array.prototype.splice.call(obj, 1, 2, 2, 4);"
414                 "Array.prototype.pop.call(obj);"
415                 "Array.prototype.shift.call(obj);");
416    }
417  }
418  return CompileRun("records ? records.length : 0")->Int32Value();
419}
420
421
422TEST(ObserverSecurityAAA) {
423  v8::Isolate* isolate = CcTest::isolate();
424  v8::HandleScope scope(isolate);
425  v8::Local<Context> contextA = Context::New(isolate);
426  CHECK_EQ(8, TestObserveSecurity(contextA, contextA, contextA));
427}
428
429
430TEST(ObserverSecurityA1A2A3) {
431  v8::Isolate* isolate = CcTest::isolate();
432  v8::HandleScope scope(isolate);
433
434  v8::Local<Context> contextA1 = Context::New(isolate);
435  v8::Local<Context> contextA2 = Context::New(isolate);
436  v8::Local<Context> contextA3 = Context::New(isolate);
437
438  Local<Value> foo = v8_str("foo");
439  contextA1->SetSecurityToken(foo);
440  contextA2->SetSecurityToken(foo);
441  contextA3->SetSecurityToken(foo);
442
443  CHECK_EQ(8, TestObserveSecurity(contextA1, contextA2, contextA3));
444}
445
446
447TEST(ObserverSecurityAAB) {
448  v8::Isolate* isolate = CcTest::isolate();
449  v8::HandleScope scope(isolate);
450  v8::Local<Context> contextA = Context::New(isolate);
451  v8::Local<Context> contextB = Context::New(isolate);
452  CHECK_EQ(0, TestObserveSecurity(contextA, contextA, contextB));
453}
454
455
456TEST(ObserverSecurityA1A2B) {
457  v8::Isolate* isolate = CcTest::isolate();
458  v8::HandleScope scope(isolate);
459
460  v8::Local<Context> contextA1 = Context::New(isolate);
461  v8::Local<Context> contextA2 = Context::New(isolate);
462  v8::Local<Context> contextB = Context::New(isolate);
463
464  Local<Value> foo = v8_str("foo");
465  contextA1->SetSecurityToken(foo);
466  contextA2->SetSecurityToken(foo);
467
468  CHECK_EQ(0, TestObserveSecurity(contextA1, contextA2, contextB));
469}
470
471
472TEST(ObserverSecurityABA) {
473  v8::Isolate* isolate = CcTest::isolate();
474  v8::HandleScope scope(isolate);
475  v8::Local<Context> contextA = Context::New(isolate);
476  v8::Local<Context> contextB = Context::New(isolate);
477  CHECK_EQ(0, TestObserveSecurity(contextA, contextB, contextA));
478}
479
480
481TEST(ObserverSecurityA1BA2) {
482  v8::Isolate* isolate = CcTest::isolate();
483  v8::HandleScope scope(isolate);
484  v8::Local<Context> contextA1 = Context::New(isolate);
485  v8::Local<Context> contextA2 = Context::New(isolate);
486  v8::Local<Context> contextB = Context::New(isolate);
487
488  Local<Value> foo = v8_str("foo");
489  contextA1->SetSecurityToken(foo);
490  contextA2->SetSecurityToken(foo);
491
492  CHECK_EQ(0, TestObserveSecurity(contextA1, contextB, contextA2));
493}
494
495
496TEST(ObserverSecurityBAA) {
497  v8::Isolate* isolate = CcTest::isolate();
498  v8::HandleScope scope(isolate);
499  v8::Local<Context> contextA = Context::New(isolate);
500  v8::Local<Context> contextB = Context::New(isolate);
501  CHECK_EQ(0, TestObserveSecurity(contextB, contextA, contextA));
502}
503
504
505TEST(ObserverSecurityBA1A2) {
506  v8::Isolate* isolate = CcTest::isolate();
507  v8::HandleScope scope(isolate);
508  v8::Local<Context> contextA1 = Context::New(isolate);
509  v8::Local<Context> contextA2 = Context::New(isolate);
510  v8::Local<Context> contextB = Context::New(isolate);
511
512  Local<Value> foo = v8_str("foo");
513  contextA1->SetSecurityToken(foo);
514  contextA2->SetSecurityToken(foo);
515
516  CHECK_EQ(0, TestObserveSecurity(contextB, contextA1, contextA2));
517}
518
519
520TEST(ObserverSecurityNotify) {
521  v8::Isolate* isolate = CcTest::isolate();
522  v8::HandleScope scope(isolate);
523  v8::Local<Context> contextA = Context::New(isolate);
524  v8::Local<Context> contextB = Context::New(isolate);
525
526  Context::Scope scopeA(contextA);
527  CompileRun("var obj = {};"
528             "var recordsA = null;"
529             "var observerA = function(r) { recordsA = r };"
530             "Object.observe(obj, observerA);");
531  Handle<Value> obj = CompileRun("obj");
532
533  {
534    Context::Scope scopeB(contextB);
535    contextB->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"), obj);
536    CompileRun("var recordsB = null;"
537               "var observerB = function(r) { recordsB = r };"
538               "Object.observe(obj, observerB);");
539  }
540
541  CompileRun("var notifier = Object.getNotifier(obj);"
542             "notifier.notify({ type: 'update' });");
543  CHECK_EQ(1, CompileRun("recordsA ? recordsA.length : 0")->Int32Value());
544
545  {
546    Context::Scope scopeB(contextB);
547    CHECK_EQ(0, CompileRun("recordsB ? recordsB.length : 0")->Int32Value());
548  }
549}
550
551
552TEST(HiddenPropertiesLeakage) {
553  HandleScope scope(CcTest::isolate());
554  LocalContext context(CcTest::isolate());
555  CompileRun("var obj = {};"
556             "var records = null;"
557             "var observer = function(r) { records = r };"
558             "Object.observe(obj, observer);");
559  Handle<Value> obj =
560      context->Global()->Get(String::NewFromUtf8(CcTest::isolate(), "obj"));
561  Handle<Object>::Cast(obj)
562      ->SetHiddenValue(String::NewFromUtf8(CcTest::isolate(), "foo"),
563                       Null(CcTest::isolate()));
564  CompileRun("");  // trigger delivery
565  CHECK(CompileRun("records")->IsNull());
566}
567
568
569TEST(GetNotifierFromOtherContext) {
570  HandleScope scope(CcTest::isolate());
571  LocalContext context(CcTest::isolate());
572  CompileRun("var obj = {};");
573  Handle<Value> instance = CompileRun("obj");
574  {
575    LocalContext context2(CcTest::isolate());
576    context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
577                            instance);
578    CHECK(CompileRun("Object.getNotifier(obj)")->IsNull());
579  }
580}
581
582
583TEST(GetNotifierFromOtherOrigin) {
584  HandleScope scope(CcTest::isolate());
585  Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
586  Handle<Value> bar = String::NewFromUtf8(CcTest::isolate(), "bar");
587  LocalContext context(CcTest::isolate());
588  context->SetSecurityToken(foo);
589  CompileRun("var obj = {};");
590  Handle<Value> instance = CompileRun("obj");
591  {
592    LocalContext context2(CcTest::isolate());
593    context2->SetSecurityToken(bar);
594    context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
595                            instance);
596    CHECK(CompileRun("Object.getNotifier(obj)")->IsNull());
597  }
598}
599
600
601TEST(GetNotifierFromSameOrigin) {
602  HandleScope scope(CcTest::isolate());
603  Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
604  LocalContext context(CcTest::isolate());
605  context->SetSecurityToken(foo);
606  CompileRun("var obj = {};");
607  Handle<Value> instance = CompileRun("obj");
608  {
609    LocalContext context2(CcTest::isolate());
610    context2->SetSecurityToken(foo);
611    context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
612                            instance);
613    CHECK(CompileRun("Object.getNotifier(obj)")->IsObject());
614  }
615}
616
617
618static int GetGlobalObjectsCount() {
619  int count = 0;
620  i::HeapIterator it(CcTest::heap());
621  for (i::HeapObject* object = it.next(); object != NULL; object = it.next())
622    if (object->IsJSGlobalObject()) count++;
623  return count;
624}
625
626
627static void CheckSurvivingGlobalObjectsCount(int expected) {
628  // We need to collect all garbage twice to be sure that everything
629  // has been collected.  This is because inline caches are cleared in
630  // the first garbage collection but some of the maps have already
631  // been marked at that point.  Therefore some of the maps are not
632  // collected until the second garbage collection.
633  CcTest::heap()->CollectAllGarbage(i::Heap::kNoGCFlags);
634  CcTest::heap()->CollectAllGarbage(i::Heap::kMakeHeapIterableMask);
635  int count = GetGlobalObjectsCount();
636#ifdef DEBUG
637  if (count != expected) CcTest::heap()->TracePathToGlobal();
638#endif
639  CHECK_EQ(expected, count);
640}
641
642
643TEST(DontLeakContextOnObserve) {
644  HandleScope scope(CcTest::isolate());
645  Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
646  LocalContext context(CcTest::isolate());
647  context->SetSecurityToken(foo);
648  CompileRun("var obj = {};");
649  Handle<Value> object = CompileRun("obj");
650  {
651    HandleScope scope(CcTest::isolate());
652    LocalContext context2(CcTest::isolate());
653    context2->SetSecurityToken(foo);
654    context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
655                            object);
656    CompileRun("function observer() {};"
657               "Object.observe(obj, observer, ['foo', 'bar', 'baz']);"
658               "Object.unobserve(obj, observer);");
659  }
660
661  CcTest::isolate()->ContextDisposedNotification();
662  CheckSurvivingGlobalObjectsCount(1);
663}
664
665
666TEST(DontLeakContextOnGetNotifier) {
667  HandleScope scope(CcTest::isolate());
668  Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
669  LocalContext context(CcTest::isolate());
670  context->SetSecurityToken(foo);
671  CompileRun("var obj = {};");
672  Handle<Value> object = CompileRun("obj");
673  {
674    HandleScope scope(CcTest::isolate());
675    LocalContext context2(CcTest::isolate());
676    context2->SetSecurityToken(foo);
677    context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
678                            object);
679    CompileRun("Object.getNotifier(obj);");
680  }
681
682  CcTest::isolate()->ContextDisposedNotification();
683  CheckSurvivingGlobalObjectsCount(1);
684}
685
686
687TEST(DontLeakContextOnNotifierPerformChange) {
688  HandleScope scope(CcTest::isolate());
689  Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
690  LocalContext context(CcTest::isolate());
691  context->SetSecurityToken(foo);
692  CompileRun("var obj = {};");
693  Handle<Value> object = CompileRun("obj");
694  Handle<Value> notifier = CompileRun("Object.getNotifier(obj)");
695  {
696    HandleScope scope(CcTest::isolate());
697    LocalContext context2(CcTest::isolate());
698    context2->SetSecurityToken(foo);
699    context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
700                            object);
701    context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "notifier"),
702                            notifier);
703    CompileRun("var obj2 = {};"
704               "var notifier2 = Object.getNotifier(obj2);"
705               "notifier2.performChange.call("
706                   "notifier, 'foo', function(){})");
707  }
708
709  CcTest::isolate()->ContextDisposedNotification();
710  CheckSurvivingGlobalObjectsCount(1);
711}
712