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
32 using namespace v8;
33 namespace i = v8::internal;
34
35 namespace {
36 // Need to create a new isolate when FLAG_harmony_observation is on.
37 class HarmonyIsolate {
38 public:
HarmonyIsolate()39 HarmonyIsolate() {
40 i::FLAG_harmony_observation = true;
41 isolate_ = Isolate::New();
42 isolate_->Enter();
43 }
44
~HarmonyIsolate()45 ~HarmonyIsolate() {
46 isolate_->Exit();
47 isolate_->Dispose();
48 }
49
GetIsolate() const50 Isolate* GetIsolate() const { return isolate_; }
51
52 private:
53 Isolate* isolate_;
54 };
55 }
56
57
TEST(PerIsolateState)58 TEST(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
TEST(EndOfMicrotaskDelivery)105 TEST(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
TEST(DeliveryOrdering)119 TEST(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
TEST(DeliveryOrderingReentrant)151 TEST(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
TEST(DeliveryOrderingDeliverChangeRecords)183 TEST(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
TEST(ObjectHashTableGrowth)208 TEST(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
TEST(GlobalObjectObservation)240 TEST(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
294 struct 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.
ExpectRecords(v8::Isolate * isolate,Handle<Value> records,const RecordExpectation expectations[],int num)303 static 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
TEST(APITestBasicMutation)333 TEST(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
TEST(HiddenPrototypeObservation)378 TEST(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
NumberOfElements(i::Handle<i::JSWeakMap> map)424 static int NumberOfElements(i::Handle<i::JSWeakMap> map) {
425 return i::ObjectHashTable::cast(map->table())->NumberOfElements();
426 }
427
428
TEST(ObservationWeakMap)429 TEST(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
NamedAccessAlwaysAllowed(Local<Object>,Local<Value>,AccessType,Local<Value>)460 static bool NamedAccessAlwaysAllowed(Local<Object>, Local<Value>, AccessType,
461 Local<Value>) {
462 return true;
463 }
464
465
IndexedAccessAlwaysAllowed(Local<Object>,uint32_t,AccessType,Local<Value>)466 static bool IndexedAccessAlwaysAllowed(Local<Object>, uint32_t, AccessType,
467 Local<Value>) {
468 return true;
469 }
470
471
472 static AccessType g_access_block_type = ACCESS_GET;
473 static const uint32_t kBlockedContextIndex = 1337;
474
475
NamedAccessAllowUnlessBlocked(Local<Object> host,Local<Value> key,AccessType type,Local<Value> data)476 static 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
IndexedAccessAllowUnlessBlocked(Local<Object> host,uint32_t index,AccessType type,Local<Value> data)489 static 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
BlockAccessKeys(Local<Object> host,Local<Value> key,AccessType type,Local<Value>)502 static 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
CreateAccessCheckedObject(v8::Isolate * isolate,NamedSecurityCallback namedCallback,IndexedSecurityCallback indexedCallback,Handle<Value> data=Handle<Value> ())511 static 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
TEST(NamedAccessCheck)526 TEST(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
TEST(IndexedAccessCheck)579 TEST(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
TEST(SpliceAccessCheck)630 TEST(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
TEST(DisallowAllForAccessKeys)677 TEST(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
TEST(AccessCheckDisallowApiModifications)716 TEST(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
TEST(HiddenPropertiesLeakage)746 TEST(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