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