Revision: 21122
Author: [email protected]
Date: Fri May 2 13:55:11 2014 UTC
Log: Re-enable Object.observe and add enforcement for security
invariants.
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.
[email protected], rossberg
LOG=Y
Review URL: https://codereview.chromium.org/265503002
http://code.google.com/p/v8/source/detail?r=21122
Modified:
/branches/bleeding_edge/src/api.cc
/branches/bleeding_edge/src/messages.js
/branches/bleeding_edge/src/object-observe.js
/branches/bleeding_edge/src/objects.cc
/branches/bleeding_edge/src/objects.h
/branches/bleeding_edge/src/runtime.cc
/branches/bleeding_edge/src/runtime.h
/branches/bleeding_edge/test/cctest/cctest.status
/branches/bleeding_edge/test/cctest/test-object-observe.cc
/branches/bleeding_edge/test/mjsunit/es7/object-observe.js
/branches/bleeding_edge/test/mjsunit/mjsunit.status
=======================================
--- /branches/bleeding_edge/src/api.cc Tue Apr 29 13:51:14 2014 UTC
+++ /branches/bleeding_edge/src/api.cc Fri May 2 13:55:11 2014 UTC
@@ -3565,21 +3565,6 @@
EXCEPTION_BAILOUT_CHECK(isolate, Local<Object>());
return Utils::ToLocal(result);
}
-
-
-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() {
@@ -3588,7 +3573,7 @@
"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));
}
=======================================
--- /branches/bleeding_edge/src/messages.js Tue Apr 29 06:42:26 2014 UTC
+++ /branches/bleeding_edge/src/messages.js Fri May 2 13:55:11 2014 UTC
@@ -77,6 +77,7 @@
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"],
=======================================
--- /branches/bleeding_edge/src/object-observe.js Fri May 2 08:00:47 2014
UTC
+++ /branches/bleeding_edge/src/object-observe.js Fri May 2 13:55:11 2014
UTC
@@ -355,6 +355,8 @@
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 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 @@
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 @@
}
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 @@
var observer = objectInfo.changeObservers[priority];
if (IS_NULL(observer))
continue;
- ObserverEnqueueIfActive(observer, objectInfo, changeRecord,
- needsAccessCheck);
+ ObserverEnqueueIfActive(observer, objectInfo, changeRecord);
}
}
@@ -558,9 +551,13 @@
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 @@
));
}
-// Disable Object.observe API for M35.
-// SetupObjectObserve();
+SetupObjectObserve();
=======================================
--- /branches/bleeding_edge/src/objects.cc Fri May 2 10:27:12 2014 UTC
+++ /branches/bleeding_edge/src/objects.cc Fri May 2 13:55:11 2014 UTC
@@ -1955,6 +1955,21 @@
return value;
}
+
+
+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,
=======================================
--- /branches/bleeding_edge/src/objects.h Fri May 2 10:27:12 2014 UTC
+++ /branches/bleeding_edge/src/objects.h Fri May 2 13:55:11 2014 UTC
@@ -2654,6 +2654,8 @@
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,
=======================================
--- /branches/bleeding_edge/src/runtime.cc Fri May 2 11:30:24 2014 UTC
+++ /branches/bleeding_edge/src/runtime.cc Fri May 2 13:55:11 2014 UTC
@@ -14872,11 +14872,11 @@
}
-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_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()));
}
=======================================
--- /branches/bleeding_edge/src/runtime.h Fri May 2 06:02:00 2014 UTC
+++ /branches/bleeding_edge/src/runtime.h Fri May 2 13:55:11 2014 UTC
@@ -311,7 +311,8 @@
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 @@
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) \
=======================================
--- /branches/bleeding_edge/test/cctest/cctest.status Fri May 2 12:59:48
2014 UTC
+++ /branches/bleeding_edge/test/cctest/cctest.status Fri May 2 13:55:11
2014 UTC
@@ -84,10 +84,6 @@
'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
##############################################################################
=======================================
--- /branches/bleeding_edge/test/cctest/test-object-observe.cc Wed Apr 16
13:28:11 2014 UTC
+++ /branches/bleeding_edge/test/cctest/test-object-observe.cc Fri May 2
13:55:11 2014 UTC
@@ -36,6 +36,10 @@
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 @@
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 @@
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 @@
}
{
LocalContext context4(CcTest::isolate());
+ context4->SetSecurityToken(foo);
context4->Global()->Set(
String::NewFromUtf8(CcTest::isolate(), "observer"), observer);
context4->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "fun1"),
@@ -207,59 +214,6 @@
CompileRun("obj.foo = 'bar'");
CHECK(CompileRun("ran")->BooleanValue());
}
-
-
-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 {
@@ -430,300 +384,232 @@
}
-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);
-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);
+ Local<Value> foo = v8_str("foo");
+ contextA1->SetSecurityToken(foo);
+ contextA2->SetSecurityToken(foo);
+ contextA3->SetSecurityToken(foo);
+
+ 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);");
}
- const RecordExpectation expected_records[] = {
- { obj_no_check, "splice", "", Handle<Value>() }
- };
- EXPECT_RECORDS(CompileRun("records"), expected_records);
+
+ 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());
+ }
}
-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());
+ }
}
=======================================
--- /branches/bleeding_edge/test/mjsunit/es7/object-observe.js Mon Mar 31
12:40:32 2014 UTC
+++ /branches/bleeding_edge/test/mjsunit/es7/object-observe.js Fri May 2
13:55:11 2014 UTC
@@ -113,6 +113,8 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
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"];
=======================================
--- /branches/bleeding_edge/test/mjsunit/mjsunit.status Tue Apr 29 15:11:57
2014 UTC
+++ /branches/bleeding_edge/test/mjsunit/mjsunit.status Fri May 2 13:55:11
2014 UTC
@@ -127,14 +127,6 @@
# 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
##############################################################################
--
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
---
You received this message because you are subscribed to the Google Groups "v8-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.