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