Re-enable Object.observe and add enforcement for security invariants.
authorrafaelw@chromium.org <rafaelw@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 2 May 2014 13:55:11 +0000 (13:55 +0000)
committerrafaelw@chromium.org <rafaelw@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 2 May 2014 13:55:11 +0000 (13:55 +0000)
This patch reverts r21062 which disabled Object.observe and the relevant tests.

It also adds enforcement for the following three invariants:

1) No observer may receive a change record describing changes to an object which is in different security origin (context have differing security tokens)

2) No observer may receive a change record whose context's security token is different from that of the object described by the change.

3) Object.getNotifier will return null if the caller and the provided object are in differing security origins

Further, it ensures that the global object can never be observed nor a notifier retrieved for it.

Tests are included.
R=verwaest@chromium.org, rossberg
LOG=Y

Review URL: https://codereview.chromium.org/265503002

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@21122 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/api.cc
src/messages.js
src/object-observe.js
src/objects.cc
src/objects.h
src/runtime.cc
src/runtime.h
test/cctest/cctest.status
test/cctest/test-object-observe.cc
test/mjsunit/es7/object-observe.js
test/mjsunit/mjsunit.status

index ab4ded2..82e4c54 100644 (file)
@@ -3567,28 +3567,13 @@ Local<v8::Object> v8::Object::Clone() {
 }
 
 
-static i::Context* GetCreationContext(i::JSObject* object) {
-  i::Object* constructor = object->map()->constructor();
-  i::JSFunction* function;
-  if (!constructor->IsJSFunction()) {
-    // Functions have null as a constructor,
-    // but any JSFunction knows its context immediately.
-    ASSERT(object->IsJSFunction());
-    function = i::JSFunction::cast(object);
-  } else {
-    function = i::JSFunction::cast(constructor);
-  }
-  return function->context()->native_context();
-}
-
-
 Local<v8::Context> v8::Object::CreationContext() {
   i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate();
   ON_BAILOUT(isolate,
              "v8::Object::CreationContext()", return Local<v8::Context>());
   ENTER_V8(isolate);
   i::Handle<i::JSObject> self = Utils::OpenHandle(this);
-  i::Context* context = GetCreationContext(*self);
+  i::Context* context = self->GetCreationContext();
   return Utils::ToLocal(i::Handle<i::Context>(context));
 }
 
index bea681a..1965da1 100644 (file)
@@ -77,6 +77,7 @@ var kMessages = {
   observe_perform_non_string:    ["Invalid non-string changeType"],
   observe_perform_non_function:  ["Cannot perform non-function"],
   observe_notify_non_notifier:   ["notify called on non-notifier object"],
+  observe_global_proxy:          ["%0", " cannot be called on the global proxy object"],
   not_typed_array:               ["this is not a typed array."],
   invalid_argument:              ["invalid_argument"],
   data_view_not_array_buffer:    ["First argument to DataView constructor must be an ArrayBuffer"],
index 1d1be93..9d66254 100644 (file)
@@ -355,6 +355,8 @@ function CallbackInfoNormalize(callback) {
 function ObjectObserve(object, callback, acceptList) {
   if (!IS_SPEC_OBJECT(object))
     throw MakeTypeError("observe_non_object", ["observe"]);
+  if (%IsJSGlobalProxy(object))
+    throw MakeTypeError("observe_global_proxy", ["observe"]);
   if (!IS_SPEC_FUNCTION(callback))
     throw MakeTypeError("observe_non_function", ["observe"]);
   if (ObjectIsFrozen(callback))
@@ -370,6 +372,8 @@ function ObjectObserve(object, callback, acceptList) {
 function ObjectUnobserve(object, callback) {
   if (!IS_SPEC_OBJECT(object))
     throw MakeTypeError("observe_non_object", ["unobserve"]);
+  if (%IsJSGlobalProxy(object))
+    throw MakeTypeError("observe_global_proxy", ["unobserve"]);
   if (!IS_SPEC_FUNCTION(callback))
     throw MakeTypeError("observe_non_function", ["unobserve"]);
 
@@ -392,19 +396,15 @@ function ArrayUnobserve(object, callback) {
   return ObjectUnobserve(object, callback);
 }
 
-function ObserverEnqueueIfActive(observer, objectInfo, changeRecord,
-                                 needsAccessCheck) {
+function ObserverEnqueueIfActive(observer, objectInfo, changeRecord) {
   if (!ObserverIsActive(observer, objectInfo) ||
       !TypeMapHasType(ObserverGetAcceptTypes(observer), changeRecord.type)) {
     return;
   }
 
   var callback = ObserverGetCallback(observer);
-  if (needsAccessCheck &&
-      // Drop all splice records on the floor for access-checked objects
-      (changeRecord.type == 'splice' ||
-       !%IsAccessAllowedForObserver(
-           callback, changeRecord.object, changeRecord.name))) {
+  if (!%ObserverObjectAndRecordHaveSameOrigin(callback, changeRecord.object,
+                                              changeRecord)) {
     return;
   }
 
@@ -433,22 +433,16 @@ function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) {
   }
   ObjectFreeze(newRecord);
 
-  ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord,
-                                        true /* skip access check */);
+  ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord);
 }
 
-function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord,
-                                               skipAccessCheck) {
+function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord) {
   // TODO(rossberg): adjust once there is a story for symbols vs proxies.
   if (IS_SYMBOL(changeRecord.name)) return;
 
-  var needsAccessCheck = !skipAccessCheck &&
-      %IsAccessCheckNeeded(changeRecord.object);
-
   if (ChangeObserversIsOptimized(objectInfo.changeObservers)) {
     var observer = objectInfo.changeObservers;
-    ObserverEnqueueIfActive(observer, objectInfo, changeRecord,
-                            needsAccessCheck);
+    ObserverEnqueueIfActive(observer, objectInfo, changeRecord);
     return;
   }
 
@@ -456,8 +450,7 @@ function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord,
     var observer = objectInfo.changeObservers[priority];
     if (IS_NULL(observer))
       continue;
-    ObserverEnqueueIfActive(observer, objectInfo, changeRecord,
-                            needsAccessCheck);
+    ObserverEnqueueIfActive(observer, objectInfo, changeRecord);
   }
 }
 
@@ -558,9 +551,13 @@ function ObjectNotifierPerformChange(changeType, changeFn) {
 function ObjectGetNotifier(object) {
   if (!IS_SPEC_OBJECT(object))
     throw MakeTypeError("observe_non_object", ["getNotifier"]);
+  if (%IsJSGlobalProxy(object))
+    throw MakeTypeError("observe_global_proxy", ["getNotifier"]);
 
   if (ObjectIsFrozen(object)) return null;
 
+  if (!%ObjectWasCreatedInCurrentOrigin(object)) return null;
+
   var objectInfo = ObjectInfoGetOrCreate(object);
   return ObjectInfoGetNotifier(objectInfo);
 }
@@ -622,5 +619,4 @@ function SetupObjectObserve() {
   ));
 }
 
-// Disable Object.observe API for M35.
-// SetupObjectObserve();
+SetupObjectObserve();
index 6f30640..6089558 100644 (file)
@@ -1957,6 +1957,21 @@ MaybeHandle<Object> JSObject::AddProperty(
 }
 
 
+Context* JSObject::GetCreationContext() {
+  Object* constructor = this->map()->constructor();
+  JSFunction* function;
+  if (!constructor->IsJSFunction()) {
+    // Functions have null as a constructor,
+    // but any JSFunction knows its context immediately.
+    function = JSFunction::cast(this);
+  } else {
+    function = JSFunction::cast(constructor);
+  }
+
+  return function->context()->native_context();
+}
+
+
 void JSObject::EnqueueChangeRecord(Handle<JSObject> object,
                                    const char* type_str,
                                    Handle<Name> name,
index 4be5184..34aff97 100644 (file)
@@ -2654,6 +2654,8 @@ class JSObject: public JSReceiver {
     static inline int SizeOf(Map* map, HeapObject* object);
   };
 
+  Context* GetCreationContext();
+
   // Enqueue change record for Object.observe. May cause GC.
   static void EnqueueChangeRecord(Handle<JSObject> object,
                                   const char* type,
index e17346a..a62a0f9 100644 (file)
@@ -14872,11 +14872,11 @@ RUNTIME_FUNCTION(Runtime_HaveSameMap) {
 }
 
 
-RUNTIME_FUNCTION(Runtime_IsAccessCheckNeeded) {
+RUNTIME_FUNCTION(Runtime_IsJSGlobalProxy) {
   SealHandleScope shs(isolate);
   ASSERT(args.length() == 1);
-  CONVERT_ARG_CHECKED(HeapObject, obj, 0);
-  return isolate->heap()->ToBoolean(obj->IsAccessCheckNeeded());
+  CONVERT_ARG_CHECKED(Object, obj, 0);
+  return isolate->heap()->ToBoolean(obj->IsJSGlobalProxy());
 }
 
 
@@ -14961,32 +14961,38 @@ RUNTIME_FUNCTION(Runtime_ObservationWeakMapCreate) {
 }
 
 
-RUNTIME_FUNCTION(Runtime_IsAccessAllowedForObserver) {
+static bool ContextsHaveSameOrigin(Handle<Context> context1,
+                                   Handle<Context> context2) {
+  return context1->security_token() == context2->security_token();
+}
+
+
+RUNTIME_FUNCTION(Runtime_ObserverObjectAndRecordHaveSameOrigin) {
   HandleScope scope(isolate);
   ASSERT(args.length() == 3);
   CONVERT_ARG_HANDLE_CHECKED(JSFunction, observer, 0);
   CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 1);
-  RUNTIME_ASSERT(object->map()->is_access_check_needed());
-  CONVERT_ARG_HANDLE_CHECKED(Object, key, 2);
-  SaveContext save(isolate);
-  isolate->set_context(observer->context());
-  if (!isolate->MayNamedAccess(
-          object, isolate->factory()->undefined_value(), v8::ACCESS_KEYS)) {
-    return isolate->heap()->false_value();
-  }
-  bool access_allowed = false;
-  uint32_t index = 0;
-  if (key->ToArrayIndex(&index) ||
-      (key->IsString() && String::cast(*key)->AsArrayIndex(&index))) {
-    access_allowed =
-        isolate->MayIndexedAccess(object, index, v8::ACCESS_GET) &&
-        isolate->MayIndexedAccess(object, index, v8::ACCESS_HAS);
-  } else {
-    access_allowed =
-        isolate->MayNamedAccess(object, key, v8::ACCESS_GET) &&
-        isolate->MayNamedAccess(object, key, v8::ACCESS_HAS);
-  }
-  return isolate->heap()->ToBoolean(access_allowed);
+  CONVERT_ARG_HANDLE_CHECKED(JSObject, record, 2);
+
+  Handle<Context> observer_context(observer->context()->native_context(),
+      isolate);
+  Handle<Context> object_context(object->GetCreationContext());
+  Handle<Context> record_context(record->GetCreationContext());
+
+  return isolate->heap()->ToBoolean(
+      ContextsHaveSameOrigin(object_context, observer_context) &&
+      ContextsHaveSameOrigin(object_context, record_context));
+}
+
+
+RUNTIME_FUNCTION(Runtime_ObjectWasCreatedInCurrentOrigin) {
+  HandleScope scope(isolate);
+  ASSERT(args.length() == 1);
+  CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0);
+
+  Handle<Context> creation_context(object->GetCreationContext(), isolate);
+  return isolate->heap()->ToBoolean(
+      ContextsHaveSameOrigin(creation_context, isolate->native_context()));
 }
 
 
index a52811f..1b1bd8d 100644 (file)
@@ -311,7 +311,8 @@ namespace internal {
   F(SetIsObserved, 1, 1) \
   F(GetObservationState, 0, 1) \
   F(ObservationWeakMapCreate, 0, 1) \
-  F(IsAccessAllowedForObserver, 3, 1) \
+  F(ObserverObjectAndRecordHaveSameOrigin, 3, 1) \
+  F(ObjectWasCreatedInCurrentOrigin, 1, 1) \
   \
   /* Harmony typed arrays */ \
   F(ArrayBufferInitialize, 2, 1)\
@@ -397,7 +398,7 @@ namespace internal {
   F(HasFastProperties, 1, 1) \
   F(TransitionElementsKind, 2, 1) \
   F(HaveSameMap, 2, 1) \
-  F(IsAccessCheckNeeded, 1, 1)
+  F(IsJSGlobalProxy, 1, 1)
 
 
 #define RUNTIME_FUNCTION_LIST_DEBUGGER(F) \
index 2027141..5fa3156 100644 (file)
   'test-api/Threading3': [PASS, ['mode == debug', SLOW]],
   'test-api/Threading4': [PASS, ['mode == debug', SLOW]],
   'test-strings/StringOOM*': [PASS, ['mode == debug', SKIP]],
-
-  # Object.observe is disabled.
-  'test-object-observe/*': [SKIP],
-  'test-microtask-delivery/*': [SKIP],
 }],  # ALWAYS
 
 ##############################################################################
index a8cbaf6..a096828 100644 (file)
@@ -36,6 +36,10 @@ namespace i = v8::internal;
 TEST(PerIsolateState) {
   HandleScope scope(CcTest::isolate());
   LocalContext context1(CcTest::isolate());
+
+  Local<Value> foo = v8_str("foo");
+  context1->SetSecurityToken(foo);
+
   CompileRun(
       "var count = 0;"
       "var calls = 0;"
@@ -49,6 +53,7 @@ TEST(PerIsolateState) {
   Handle<Value> notify_fun2;
   {
     LocalContext context2(CcTest::isolate());
+    context2->SetSecurityToken(foo);
     context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
                             obj);
     notify_fun2 = CompileRun(
@@ -57,6 +62,7 @@ TEST(PerIsolateState) {
   Handle<Value> notify_fun3;
   {
     LocalContext context3(CcTest::isolate());
+    context3->SetSecurityToken(foo);
     context3->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
                             obj);
     notify_fun3 = CompileRun(
@@ -64,6 +70,7 @@ TEST(PerIsolateState) {
   }
   {
     LocalContext context4(CcTest::isolate());
+    context4->SetSecurityToken(foo);
     context4->Global()->Set(
         String::NewFromUtf8(CcTest::isolate(), "observer"), observer);
     context4->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "fun1"),
@@ -209,59 +216,6 @@ TEST(ObjectHashTableGrowth) {
 }
 
 
-TEST(GlobalObjectObservation) {
-  LocalContext context(CcTest::isolate());
-  HandleScope scope(CcTest::isolate());
-  Handle<Object> global_proxy = context->Global();
-  CompileRun(
-      "var records = [];"
-      "var global = this;"
-      "Object.observe(global, function(r) { [].push.apply(records, r) });"
-      "global.foo = 'hello';");
-  CHECK_EQ(1, CompileRun("records.length")->Int32Value());
-  CHECK(global_proxy->StrictEquals(CompileRun("records[0].object")));
-
-  // Detached, mutating the proxy has no effect.
-  context->DetachGlobal();
-  CompileRun("global.bar = 'goodbye';");
-  CHECK_EQ(1, CompileRun("records.length")->Int32Value());
-  CompileRun("this.baz = 'goodbye';");
-  CHECK_EQ(1, CompileRun("records.length")->Int32Value());
-
-  // Attached to a different context, should not leak mutations
-  // to the old context.
-  context->DetachGlobal();
-  {
-    LocalContext context2(CcTest::isolate());
-    CompileRun(
-        "var records2 = [];"
-        "var global = this;"
-        "Object.observe(this, function(r) { [].push.apply(records2, r) });"
-        "this.v1 = 'context2';");
-    context2->DetachGlobal();
-    CompileRun(
-        "global.v2 = 'context2';"
-        "this.v3 = 'context2';");
-    CHECK_EQ(1, CompileRun("records2.length")->Int32Value());
-  }
-  CHECK_EQ(1, CompileRun("records.length")->Int32Value());
-
-  // Attaching by passing to Context::New
-  {
-    // Delegates to Context::New
-    LocalContext context3(
-        CcTest::isolate(), NULL, Handle<ObjectTemplate>(), global_proxy);
-    CompileRun(
-        "var records3 = [];"
-        "Object.observe(this, function(r) { [].push.apply(records3, r) });"
-        "this.qux = 'context3';");
-    CHECK_EQ(1, CompileRun("records3.length")->Int32Value());
-    CHECK(global_proxy->StrictEquals(CompileRun("records3[0].object")));
-  }
-  CHECK_EQ(1, CompileRun("records.length")->Int32Value());
-}
-
-
 struct RecordExpectation {
   Handle<Value> object;
   const char* type;
@@ -430,300 +384,232 @@ TEST(ObservationWeakMap) {
 }
 
 
-static bool NamedAccessAlwaysAllowed(Local<Object>, Local<Value>, AccessType,
-                                     Local<Value>) {
-  return true;
+static int TestObserveSecurity(Handle<Context> observer_context,
+                               Handle<Context> object_context,
+                               Handle<Context> mutation_context) {
+  Context::Scope observer_scope(observer_context);
+  CompileRun("var records = null;"
+             "var observer = function(r) { records = r };");
+  Handle<Value> observer = CompileRun("observer");
+  {
+    Context::Scope object_scope(object_context);
+    object_context->Global()->Set(
+        String::NewFromUtf8(CcTest::isolate(), "observer"), observer);
+    CompileRun("var obj = {};"
+               "obj.length = 0;"
+               "Object.observe(obj, observer,"
+                   "['add', 'update', 'delete','reconfigure','splice']"
+               ");");
+    Handle<Value> obj = CompileRun("obj");
+    {
+      Context::Scope mutation_scope(mutation_context);
+      mutation_context->Global()->Set(
+          String::NewFromUtf8(CcTest::isolate(), "obj"), obj);
+      CompileRun("obj.foo = 'bar';"
+                 "obj.foo = 'baz';"
+                 "delete obj.foo;"
+                 "Object.defineProperty(obj, 'bar', {value: 'bot'});"
+                 "Array.prototype.push.call(obj, 1, 2, 3);"
+                 "Array.prototype.splice.call(obj, 1, 2, 2, 4);"
+                 "Array.prototype.pop.call(obj);"
+                 "Array.prototype.shift.call(obj);");
+    }
+  }
+  return CompileRun("records ? records.length : 0")->Int32Value();
 }
 
 
-static bool IndexedAccessAlwaysAllowed(Local<Object>, uint32_t, AccessType,
-                                       Local<Value>) {
-  return true;
+TEST(ObserverSecurityAAA) {
+  v8::Isolate* isolate = CcTest::isolate();
+  v8::HandleScope scope(isolate);
+  v8::Local<Context> contextA = Context::New(isolate);
+  CHECK_EQ(8, TestObserveSecurity(contextA, contextA, contextA));
 }
 
 
-static AccessType g_access_block_type = ACCESS_GET;
-static const uint32_t kBlockedContextIndex = 1337;
+TEST(ObserverSecurityA1A2A3) {
+  v8::Isolate* isolate = CcTest::isolate();
+  v8::HandleScope scope(isolate);
+
+  v8::Local<Context> contextA1 = Context::New(isolate);
+  v8::Local<Context> contextA2 = Context::New(isolate);
+  v8::Local<Context> contextA3 = Context::New(isolate);
 
+  Local<Value> foo = v8_str("foo");
+  contextA1->SetSecurityToken(foo);
+  contextA2->SetSecurityToken(foo);
+  contextA3->SetSecurityToken(foo);
 
-static bool NamedAccessAllowUnlessBlocked(Local<Object> host,
-                                          Local<Value> key,
-                                          AccessType type,
-                                          Local<Value> data) {
-  if (type != g_access_block_type) return true;
-  v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(
-      Utils::OpenHandle(*host)->GetIsolate());
-  Handle<Object> global = isolate->GetCurrentContext()->Global();
-  if (!global->Has(kBlockedContextIndex)) return true;
-  return !key->IsString() || !key->Equals(data);
+  CHECK_EQ(8, TestObserveSecurity(contextA1, contextA2, contextA3));
 }
 
 
-static bool IndexedAccessAllowUnlessBlocked(Local<Object> host,
-                                            uint32_t index,
-                                            AccessType type,
-                                            Local<Value> data) {
-  if (type != g_access_block_type) return true;
-  v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(
-      Utils::OpenHandle(*host)->GetIsolate());
-  Handle<Object> global = isolate->GetCurrentContext()->Global();
-  if (!global->Has(kBlockedContextIndex)) return true;
-  return index != data->Uint32Value();
+TEST(ObserverSecurityAAB) {
+  v8::Isolate* isolate = CcTest::isolate();
+  v8::HandleScope scope(isolate);
+  v8::Local<Context> contextA = Context::New(isolate);
+  v8::Local<Context> contextB = Context::New(isolate);
+  CHECK_EQ(0, TestObserveSecurity(contextA, contextA, contextB));
 }
 
 
-static bool BlockAccessKeys(Local<Object> host, Local<Value> key,
-                            AccessType type, Local<Value>) {
-  v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(
-      Utils::OpenHandle(*host)->GetIsolate());
-  Handle<Object> global = isolate->GetCurrentContext()->Global();
-  return type != ACCESS_KEYS || !global->Has(kBlockedContextIndex);
+TEST(ObserverSecurityA1A2B) {
+  v8::Isolate* isolate = CcTest::isolate();
+  v8::HandleScope scope(isolate);
+
+  v8::Local<Context> contextA1 = Context::New(isolate);
+  v8::Local<Context> contextA2 = Context::New(isolate);
+  v8::Local<Context> contextB = Context::New(isolate);
+
+  Local<Value> foo = v8_str("foo");
+  contextA1->SetSecurityToken(foo);
+  contextA2->SetSecurityToken(foo);
+
+  CHECK_EQ(0, TestObserveSecurity(contextA1, contextA2, contextB));
 }
 
 
-static Handle<Object> CreateAccessCheckedObject(
-    v8::Isolate* isolate,
-    NamedSecurityCallback namedCallback,
-    IndexedSecurityCallback indexedCallback,
-    Handle<Value> data = Handle<Value>()) {
-  Handle<ObjectTemplate> tmpl = ObjectTemplate::New(isolate);
-  tmpl->SetAccessCheckCallbacks(namedCallback, indexedCallback, data);
-  Handle<Object> instance = tmpl->NewInstance();
-  Handle<Object> global = instance->CreationContext()->Global();
-  global->Set(String::NewFromUtf8(isolate, "obj"), instance);
-  global->Set(kBlockedContextIndex, v8::True(isolate));
-  return instance;
+TEST(ObserverSecurityABA) {
+  v8::Isolate* isolate = CcTest::isolate();
+  v8::HandleScope scope(isolate);
+  v8::Local<Context> contextA = Context::New(isolate);
+  v8::Local<Context> contextB = Context::New(isolate);
+  CHECK_EQ(0, TestObserveSecurity(contextA, contextB, contextA));
 }
 
 
-TEST(NamedAccessCheck) {
-  const AccessType types[] = { ACCESS_GET, ACCESS_HAS };
-  for (size_t i = 0; i < ARRAY_SIZE(types); ++i) {
-    HandleScope scope(CcTest::isolate());
-    LocalContext context(CcTest::isolate());
-    g_access_block_type = types[i];
-    Handle<Object> instance = CreateAccessCheckedObject(
-        CcTest::isolate(),
-        NamedAccessAllowUnlessBlocked,
-        IndexedAccessAlwaysAllowed,
-        String::NewFromUtf8(CcTest::isolate(), "foo"));
-    CompileRun("var records = null;"
-               "var objNoCheck = {};"
-               "var observer = function(r) { records = r };"
-               "Object.observe(obj, observer);"
-               "Object.observe(objNoCheck, observer);");
-    Handle<Value> obj_no_check = CompileRun("objNoCheck");
-    {
-      LocalContext context2(CcTest::isolate());
-      context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
-                              instance);
-      context2->Global()->Set(
-          String::NewFromUtf8(CcTest::isolate(), "objNoCheck"),
-          obj_no_check);
-      CompileRun("var records2 = null;"
-                 "var observer2 = function(r) { records2 = r };"
-                 "Object.observe(obj, observer2);"
-                 "Object.observe(objNoCheck, observer2);"
-                 "obj.foo = 'bar';"
-                 "Object.defineProperty(obj, 'foo', {value: 5});"
-                 "Object.defineProperty(obj, 'foo', {get: function(){}});"
-                 "obj.bar = 'baz';"
-                 "objNoCheck.baz = 'quux'");
-      const RecordExpectation expected_records2[] = {
-        { instance, "add", "foo", Handle<Value>() },
-        { instance, "update", "foo",
-          String::NewFromUtf8(CcTest::isolate(), "bar") },
-        { instance, "reconfigure", "foo",
-          Number::New(CcTest::isolate(), 5) },
-        { instance, "add", "bar", Handle<Value>() },
-        { obj_no_check, "add", "baz", Handle<Value>() },
-      };
-      EXPECT_RECORDS(CompileRun("records2"), expected_records2);
-    }
-    const RecordExpectation expected_records[] = {
-      { instance, "add", "bar", Handle<Value>() },
-      { obj_no_check, "add", "baz", Handle<Value>() }
-    };
-    EXPECT_RECORDS(CompileRun("records"), expected_records);
-  }
+TEST(ObserverSecurityA1BA2) {
+  v8::Isolate* isolate = CcTest::isolate();
+  v8::HandleScope scope(isolate);
+  v8::Local<Context> contextA1 = Context::New(isolate);
+  v8::Local<Context> contextA2 = Context::New(isolate);
+  v8::Local<Context> contextB = Context::New(isolate);
+
+  Local<Value> foo = v8_str("foo");
+  contextA1->SetSecurityToken(foo);
+  contextA2->SetSecurityToken(foo);
+
+  CHECK_EQ(0, TestObserveSecurity(contextA1, contextB, contextA2));
 }
 
 
-TEST(IndexedAccessCheck) {
-  const AccessType types[] = { ACCESS_GET, ACCESS_HAS };
-  for (size_t i = 0; i < ARRAY_SIZE(types); ++i) {
-    HandleScope scope(CcTest::isolate());
-    LocalContext context(CcTest::isolate());
-    g_access_block_type = types[i];
-    Handle<Object> instance = CreateAccessCheckedObject(
-        CcTest::isolate(), NamedAccessAlwaysAllowed,
-        IndexedAccessAllowUnlessBlocked, Number::New(CcTest::isolate(), 7));
-    CompileRun("var records = null;"
-               "var objNoCheck = {};"
-               "var observer = function(r) { records = r };"
-               "Object.observe(obj, observer);"
-               "Object.observe(objNoCheck, observer);");
-    Handle<Value> obj_no_check = CompileRun("objNoCheck");
-    {
-      LocalContext context2(CcTest::isolate());
-      context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
-                              instance);
-      context2->Global()->Set(
-          String::NewFromUtf8(CcTest::isolate(), "objNoCheck"),
-          obj_no_check);
-      CompileRun("var records2 = null;"
-                 "var observer2 = function(r) { records2 = r };"
-                 "Object.observe(obj, observer2);"
-                 "Object.observe(objNoCheck, observer2);"
-                 "obj[7] = 'foo';"
-                 "Object.defineProperty(obj, '7', {value: 5});"
-                 "Object.defineProperty(obj, '7', {get: function(){}});"
-                 "obj[8] = 'bar';"
-                 "objNoCheck[42] = 'quux'");
-      const RecordExpectation expected_records2[] = {
-        { instance, "add", "7", Handle<Value>() },
-        { instance, "update", "7",
-          String::NewFromUtf8(CcTest::isolate(), "foo") },
-        { instance, "reconfigure", "7", Number::New(CcTest::isolate(), 5) },
-        { instance, "add", "8", Handle<Value>() },
-        { obj_no_check, "add", "42", Handle<Value>() }
-      };
-      EXPECT_RECORDS(CompileRun("records2"), expected_records2);
-    }
-    const RecordExpectation expected_records[] = {
-      { instance, "add", "8", Handle<Value>() },
-      { obj_no_check, "add", "42", Handle<Value>() }
-    };
-    EXPECT_RECORDS(CompileRun("records"), expected_records);
-  }
+TEST(ObserverSecurityBAA) {
+  v8::Isolate* isolate = CcTest::isolate();
+  v8::HandleScope scope(isolate);
+  v8::Local<Context> contextA = Context::New(isolate);
+  v8::Local<Context> contextB = Context::New(isolate);
+  CHECK_EQ(0, TestObserveSecurity(contextB, contextA, contextA));
 }
 
 
-TEST(SpliceAccessCheck) {
-  HandleScope scope(CcTest::isolate());
-  LocalContext context(CcTest::isolate());
-  g_access_block_type = ACCESS_GET;
-  Handle<Object> instance = CreateAccessCheckedObject(
-      CcTest::isolate(), NamedAccessAlwaysAllowed,
-      IndexedAccessAllowUnlessBlocked, Number::New(CcTest::isolate(), 1));
-  CompileRun("var records = null;"
-             "obj[1] = 'foo';"
-             "obj.length = 2;"
-             "var objNoCheck = {1: 'bar', length: 2};"
-             "observer = function(r) { records = r };"
-             "Array.observe(obj, observer);"
-             "Array.observe(objNoCheck, observer);");
-  Handle<Value> obj_no_check = CompileRun("objNoCheck");
+TEST(ObserverSecurityBA1A2) {
+  v8::Isolate* isolate = CcTest::isolate();
+  v8::HandleScope scope(isolate);
+  v8::Local<Context> contextA1 = Context::New(isolate);
+  v8::Local<Context> contextA2 = Context::New(isolate);
+  v8::Local<Context> contextB = Context::New(isolate);
+
+  Local<Value> foo = v8_str("foo");
+  contextA1->SetSecurityToken(foo);
+  contextA2->SetSecurityToken(foo);
+
+  CHECK_EQ(0, TestObserveSecurity(contextB, contextA1, contextA2));
+}
+
+
+TEST(ObserverSecurityNotify) {
+  v8::Isolate* isolate = CcTest::isolate();
+  v8::HandleScope scope(isolate);
+  v8::Local<Context> contextA = Context::New(isolate);
+  v8::Local<Context> contextB = Context::New(isolate);
+
+  Context::Scope scopeA(contextA);
+  CompileRun("var obj = {};"
+             "var recordsA = null;"
+             "var observerA = function(r) { recordsA = r };"
+             "Object.observe(obj, observerA);");
+  Handle<Value> obj = CompileRun("obj");
+
   {
-    LocalContext context2(CcTest::isolate());
-    context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
-                            instance);
-    context2->Global()->Set(
-        String::NewFromUtf8(CcTest::isolate(), "objNoCheck"), obj_no_check);
-    CompileRun("var records2 = null;"
-               "var observer2 = function(r) { records2 = r };"
-               "Array.observe(obj, observer2);"
-               "Array.observe(objNoCheck, observer2);"
-               // No one should hear about this: no splice records are emitted
-               // for access-checked objects
-               "[].push.call(obj, 5);"
-               "[].splice.call(obj, 1, 1);"
-               "[].pop.call(obj);"
-               "[].pop.call(objNoCheck);");
-    // TODO(adamk): Extend EXPECT_RECORDS to be able to assert more things
-    // about splice records. For this test it's not so important since
-    // we just want to guarantee the machinery is in operation at all.
-    const RecordExpectation expected_records2[] = {
-      { obj_no_check, "splice", "", Handle<Value>() }
-    };
-    EXPECT_RECORDS(CompileRun("records2"), expected_records2);
+    Context::Scope scopeB(contextB);
+    contextB->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"), obj);
+    CompileRun("var recordsB = null;"
+               "var observerB = function(r) { recordsB = r };"
+               "Object.observe(obj, observerB);");
+  }
+
+  CompileRun("var notifier = Object.getNotifier(obj);"
+             "notifier.notify({ type: 'update' });");
+  CHECK_EQ(1, CompileRun("recordsA ? recordsA.length : 0")->Int32Value());
+
+  {
+    Context::Scope scopeB(contextB);
+    CHECK_EQ(0, CompileRun("recordsB ? recordsB.length : 0")->Int32Value());
   }
-  const RecordExpectation expected_records[] = {
-    { obj_no_check, "splice", "", Handle<Value>() }
-  };
-  EXPECT_RECORDS(CompileRun("records"), expected_records);
 }
 
 
-TEST(DisallowAllForAccessKeys) {
+TEST(HiddenPropertiesLeakage) {
   HandleScope scope(CcTest::isolate());
   LocalContext context(CcTest::isolate());
-  Handle<Object> instance = CreateAccessCheckedObject(
-      CcTest::isolate(), BlockAccessKeys, IndexedAccessAlwaysAllowed);
-  CompileRun("var records = null;"
-             "var objNoCheck = {};"
+  CompileRun("var obj = {};"
+             "var records = null;"
              "var observer = function(r) { records = r };"
-             "Object.observe(obj, observer);"
-             "Object.observe(objNoCheck, observer);");
-  Handle<Value> obj_no_check = CompileRun("objNoCheck");
+             "Object.observe(obj, observer);");
+  Handle<Value> obj =
+      context->Global()->Get(String::NewFromUtf8(CcTest::isolate(), "obj"));
+  Handle<Object>::Cast(obj)
+      ->SetHiddenValue(String::NewFromUtf8(CcTest::isolate(), "foo"),
+                       Null(CcTest::isolate()));
+  CompileRun("");  // trigger delivery
+  CHECK(CompileRun("records")->IsNull());
+}
+
+
+TEST(GetNotifierFromOtherContext) {
+  HandleScope scope(CcTest::isolate());
+  LocalContext context(CcTest::isolate());
+  CompileRun("var obj = {};");
+  Handle<Value> instance = CompileRun("obj");
   {
     LocalContext context2(CcTest::isolate());
     context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
                             instance);
-    context2->Global()->Set(
-        String::NewFromUtf8(CcTest::isolate(), "objNoCheck"), obj_no_check);
-    CompileRun("var records2 = null;"
-               "var observer2 = function(r) { records2 = r };"
-               "Object.observe(obj, observer2);"
-               "Object.observe(objNoCheck, observer2);"
-               "obj.foo = 'bar';"
-               "obj[5] = 'baz';"
-               "objNoCheck.baz = 'quux'");
-    const RecordExpectation expected_records2[] = {
-      { instance, "add", "foo", Handle<Value>() },
-      { instance, "add", "5", Handle<Value>() },
-      { obj_no_check, "add", "baz", Handle<Value>() },
-    };
-    EXPECT_RECORDS(CompileRun("records2"), expected_records2);
+    CHECK(CompileRun("Object.getNotifier(obj)")->IsNull());
   }
-  const RecordExpectation expected_records[] = {
-    { obj_no_check, "add", "baz", Handle<Value>() }
-  };
-  EXPECT_RECORDS(CompileRun("records"), expected_records);
 }
 
 
-TEST(AccessCheckDisallowApiModifications) {
+TEST(GetNotifierFromOtherOrigin) {
   HandleScope scope(CcTest::isolate());
+  Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
+  Handle<Value> bar = String::NewFromUtf8(CcTest::isolate(), "bar");
   LocalContext context(CcTest::isolate());
-  Handle<Object> instance = CreateAccessCheckedObject(
-      CcTest::isolate(), BlockAccessKeys, IndexedAccessAlwaysAllowed);
-  CompileRun("var records = null;"
-             "var observer = function(r) { records = r };"
-             "Object.observe(obj, observer);");
+  context->SetSecurityToken(foo);
+  CompileRun("var obj = {};");
+  Handle<Value> instance = CompileRun("obj");
   {
     LocalContext context2(CcTest::isolate());
+    context2->SetSecurityToken(bar);
     context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
                             instance);
-    CompileRun("var records2 = null;"
-               "var observer2 = function(r) { records2 = r };"
-               "Object.observe(obj, observer2);");
-    instance->Set(5, String::NewFromUtf8(CcTest::isolate(), "bar"));
-    instance->Set(String::NewFromUtf8(CcTest::isolate(), "foo"),
-                  String::NewFromUtf8(CcTest::isolate(), "bar"));
-    CompileRun("");  // trigger delivery
-    const RecordExpectation expected_records2[] = {
-      { instance, "add", "5", Handle<Value>() },
-      { instance, "add", "foo", Handle<Value>() }
-    };
-    EXPECT_RECORDS(CompileRun("records2"), expected_records2);
+    CHECK(CompileRun("Object.getNotifier(obj)")->IsNull());
   }
-  CHECK(CompileRun("records")->IsNull());
 }
 
 
-TEST(HiddenPropertiesLeakage) {
+TEST(GetNotifierFromSameOrigin) {
   HandleScope scope(CcTest::isolate());
+  Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
   LocalContext context(CcTest::isolate());
-  CompileRun("var obj = {};"
-             "var records = null;"
-             "var observer = function(r) { records = r };"
-             "Object.observe(obj, observer);");
-  Handle<Value> obj =
-      context->Global()->Get(String::NewFromUtf8(CcTest::isolate(), "obj"));
-  Handle<Object>::Cast(obj)
-      ->SetHiddenValue(String::NewFromUtf8(CcTest::isolate(), "foo"),
-                       Null(CcTest::isolate()));
-  CompileRun("");  // trigger delivery
-  CHECK(CompileRun("records")->IsNull());
+  context->SetSecurityToken(foo);
+  CompileRun("var obj = {};");
+  Handle<Value> instance = CompileRun("obj");
+  {
+    LocalContext context2(CcTest::isolate());
+    context2->SetSecurityToken(foo);
+    context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
+                            instance);
+    CHECK(CompileRun("Object.getNotifier(obj)")->IsObject());
+  }
 }
index 0aa3601..8ec22bc 100644 (file)
@@ -113,6 +113,8 @@ Object.defineProperty(changeRecordWithAccessor, 'name', {
 // Object.observe
 assertThrows(function() { Object.observe("non-object", observer.callback); },
              TypeError);
+assertThrows(function() { Object.observe(this, observer.callback); },
+             TypeError);
 assertThrows(function() { Object.observe(obj, nonFunction); }, TypeError);
 assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError);
 assertEquals(obj, Object.observe(obj, observer.callback, [1]));
@@ -127,6 +129,8 @@ assertEquals(obj, Object.observe(obj, observer.callback));
 
 // Object.unobserve
 assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError);
+assertThrows(function() { Object.unobserve(this, observer.callback); },
+             TypeError);
 assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError);
 assertEquals(obj, Object.unobserve(obj, observer.callback));
 
@@ -135,6 +139,7 @@ assertEquals(obj, Object.unobserve(obj, observer.callback));
 var notifier = Object.getNotifier(obj);
 assertSame(notifier, Object.getNotifier(obj));
 assertEquals(null, Object.getNotifier(Object.freeze({})));
+assertThrows(function() { Object.getNotifier(this) }, TypeError);
 assertFalse(notifier.hasOwnProperty('notify'));
 assertEquals([], Object.keys(notifier));
 var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify');
@@ -1074,6 +1079,8 @@ function TestObserveNonConfigurable(obj, prop, desc) {
   Object.unobserve(obj, observer.callback);
 }
 
+// TODO(rafaelw) Enable when ES6 Proxies are implemented
+/*
 function createProxy(create, x) {
   var handler = {
     getPropertyDescriptor: function(k) {
@@ -1113,11 +1120,11 @@ function createProxy(create, x) {
   Object.observe(handler.target, handler.callback);
   return handler.proxy = create(handler, x);
 }
+*/
 
 var objects = [
   {},
   [],
-  this,  // global object
   function(){},
   (function(){ return arguments })(),
   (function(){ "use strict"; return arguments })(),
@@ -1125,9 +1132,10 @@ var objects = [
   new Date(),
   Object, Function, Date, RegExp,
   new Set, new Map, new WeakMap,
-  new ArrayBuffer(10), new Int32Array(5),
-  createProxy(Proxy.create, null),
-  createProxy(Proxy.createFunction, function(){}),
+  new ArrayBuffer(10), new Int32Array(5)
+// TODO(rafaelw) Enable when ES6 Proxies are implemented.
+//  createProxy(Proxy.create, null),
+//  createProxy(Proxy.createFunction, function(){}),
 ];
 var properties = ["a", "1", 1, "length", "setPrototype", "name", "caller"];
 
index e65beea..036fb48 100644 (file)
   # array buffer.
   'nans': [PASS, ['arch == mips', SKIP]],
 
-  # Object.observe is disabled.
-  'es6/promises': [SKIP],
-  'array-push7': [SKIP],
-  'harmony/microtask-delivery': [SKIP],
-  'es7/object-observe': [SKIP],
-  'harmony/regress/regress-observe-empty-double-array': [SKIP],
-  'regress/regress-356589': [SKIP],
-  'regress/regress-observe-map-cache': [SKIP],
 }],  # ALWAYS
 
 ##############################################################################