Make __proto__ a real JavaScript accessor property.

This turns the __proto__ callback from a foreign callback into a real
JavaScript accessor. It makes the accessor behavior of this property
explicit.

R=rossberg@chromium.org
BUG=v8:1949,v8:2606
TEST=mjsunit/regress/regress-2606

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@14139 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent cb650a51
...@@ -4301,7 +4301,7 @@ class Internals { ...@@ -4301,7 +4301,7 @@ class Internals {
static const int kNullValueRootIndex = 7; static const int kNullValueRootIndex = 7;
static const int kTrueValueRootIndex = 8; static const int kTrueValueRootIndex = 8;
static const int kFalseValueRootIndex = 9; static const int kFalseValueRootIndex = 9;
static const int kEmptyStringRootIndex = 119; static const int kEmptyStringRootIndex = 118;
static const int kNodeClassIdOffset = 1 * kApiPointerSize; static const int kNodeClassIdOffset = 1 * kApiPointerSize;
static const int kNodeFlagsOffset = 1 * kApiPointerSize + 3; static const int kNodeFlagsOffset = 1 * kApiPointerSize + 3;
......
...@@ -782,64 +782,6 @@ const AccessorDescriptor Accessors::FunctionCaller = { ...@@ -782,64 +782,6 @@ const AccessorDescriptor Accessors::FunctionCaller = {
}; };
//
// Accessors::ObjectPrototype
//
static inline Object* GetPrototypeSkipHiddenPrototypes(Isolate* isolate,
Object* receiver) {
Object* current = receiver->GetPrototype(isolate);
while (current->IsJSObject() &&
JSObject::cast(current)->map()->is_hidden_prototype()) {
current = current->GetPrototype(isolate);
}
return current;
}
MaybeObject* Accessors::ObjectGetPrototype(Object* receiver, void*) {
return GetPrototypeSkipHiddenPrototypes(Isolate::Current(), receiver);
}
MaybeObject* Accessors::ObjectSetPrototype(JSObject* receiver_raw,
Object* value_raw,
void*) {
const bool kSkipHiddenPrototypes = true;
// To be consistent with other Set functions, return the value.
if (!(FLAG_harmony_observation && receiver_raw->map()->is_observed()))
return receiver_raw->SetPrototype(value_raw, kSkipHiddenPrototypes);
Isolate* isolate = receiver_raw->GetIsolate();
HandleScope scope(isolate);
Handle<JSObject> receiver(receiver_raw);
Handle<Object> value(value_raw, isolate);
Handle<Object> old_value(GetPrototypeSkipHiddenPrototypes(isolate, *receiver),
isolate);
MaybeObject* result = receiver->SetPrototype(*value, kSkipHiddenPrototypes);
Handle<Object> hresult;
if (!result->ToHandle(&hresult, isolate)) return result;
Handle<Object> new_value(GetPrototypeSkipHiddenPrototypes(isolate, *receiver),
isolate);
if (!new_value->SameValue(*old_value)) {
JSObject::EnqueueChangeRecord(receiver, "prototype",
isolate->factory()->proto_string(),
old_value);
}
return *hresult;
}
const AccessorDescriptor Accessors::ObjectPrototype = {
ObjectGetPrototype,
ObjectSetPrototype,
0
};
// //
// Accessors::MakeModuleExport // Accessors::MakeModuleExport
// //
......
...@@ -56,8 +56,7 @@ namespace internal { ...@@ -56,8 +56,7 @@ namespace internal {
V(ScriptContextData) \ V(ScriptContextData) \
V(ScriptEvalFromScript) \ V(ScriptEvalFromScript) \
V(ScriptEvalFromScriptPosition) \ V(ScriptEvalFromScriptPosition) \
V(ScriptEvalFromFunctionName) \ V(ScriptEvalFromFunctionName)
V(ObjectPrototype)
// Accessors contains all predefined proxy accessors. // Accessors contains all predefined proxy accessors.
...@@ -111,10 +110,6 @@ class Accessors : public AllStatic { ...@@ -111,10 +110,6 @@ class Accessors : public AllStatic {
static MaybeObject* ScriptGetEvalFromScript(Object* object, void*); static MaybeObject* ScriptGetEvalFromScript(Object* object, void*);
static MaybeObject* ScriptGetEvalFromScriptPosition(Object* object, void*); static MaybeObject* ScriptGetEvalFromScriptPosition(Object* object, void*);
static MaybeObject* ScriptGetEvalFromFunctionName(Object* object, void*); static MaybeObject* ScriptGetEvalFromFunctionName(Object* object, void*);
static MaybeObject* ObjectGetPrototype(Object* receiver, void*);
static MaybeObject* ObjectSetPrototype(JSObject* receiver,
Object* value,
void*);
// Helper functions. // Helper functions.
static Object* FlattenNumber(Object* value); static Object* FlattenNumber(Object* value);
......
...@@ -477,25 +477,10 @@ Handle<JSFunction> Genesis::CreateEmptyFunction(Isolate* isolate) { ...@@ -477,25 +477,10 @@ Handle<JSFunction> Genesis::CreateEmptyFunction(Isolate* isolate) {
native_context()->set_object_function(*object_fun); native_context()->set_object_function(*object_fun);
// Allocate a new prototype for the object function. // Allocate a new prototype for the object function.
Handle<Map> object_prototype_map = Handle<JSObject> prototype = factory->NewJSObject(
factory->NewMap(JS_OBJECT_TYPE, JSObject::kHeaderSize); isolate->object_function(),
Handle<DescriptorArray> prototype_descriptors(
factory->NewDescriptorArray(0, 1));
DescriptorArray::WhitenessWitness witness(*prototype_descriptors);
Handle<Foreign> object_prototype(
factory->NewForeign(&Accessors::ObjectPrototype));
PropertyAttributes attribs = static_cast<PropertyAttributes>(DONT_ENUM);
object_prototype_map->set_instance_descriptors(*prototype_descriptors);
{ // Add __proto__.
CallbacksDescriptor d(heap->proto_string(), *object_prototype, attribs);
object_prototype_map->AppendDescriptor(&d, witness);
}
Handle<JSObject> prototype = factory->NewJSObjectFromMap(
object_prototype_map,
TENURED); TENURED);
native_context()->set_initial_object_prototype(*prototype); native_context()->set_initial_object_prototype(*prototype);
SetPrototype(object_fun, prototype); SetPrototype(object_fun, prototype);
} }
......
...@@ -2836,13 +2836,6 @@ bool Heap::CreateInitialObjects() { ...@@ -2836,13 +2836,6 @@ bool Heap::CreateInitialObjects() {
} }
hidden_string_ = String::cast(obj); hidden_string_ = String::cast(obj);
// Allocate the foreign for __proto__.
{ MaybeObject* maybe_obj =
AllocateForeign((Address) &Accessors::ObjectPrototype);
if (!maybe_obj->ToObject(&obj)) return false;
}
set_prototype_accessors(Foreign::cast(obj));
// Allocate the code_stubs dictionary. The initial size is set to avoid // Allocate the code_stubs dictionary. The initial size is set to avoid
// expanding the dictionary during bootstrapping. // expanding the dictionary during bootstrapping.
{ MaybeObject* maybe_obj = UnseededNumberDictionary::Allocate(this, 128); { MaybeObject* maybe_obj = UnseededNumberDictionary::Allocate(this, 128);
......
...@@ -150,7 +150,6 @@ namespace internal { ...@@ -150,7 +150,6 @@ namespace internal {
V(HeapNumber, minus_zero_value, MinusZeroValue) \ V(HeapNumber, minus_zero_value, MinusZeroValue) \
V(Map, neander_map, NeanderMap) \ V(Map, neander_map, NeanderMap) \
V(JSObject, message_listeners, MessageListeners) \ V(JSObject, message_listeners, MessageListeners) \
V(Foreign, prototype_accessors, PrototypeAccessors) \
V(UnseededNumberDictionary, code_stubs, CodeStubs) \ V(UnseededNumberDictionary, code_stubs, CodeStubs) \
V(UnseededNumberDictionary, non_monomorphic_cache, NonMonomorphicCache) \ V(UnseededNumberDictionary, non_monomorphic_cache, NonMonomorphicCache) \
V(PolymorphicCodeCache, polymorphic_code_cache, PolymorphicCodeCache) \ V(PolymorphicCodeCache, polymorphic_code_cache, PolymorphicCodeCache) \
......
...@@ -98,6 +98,7 @@ var kMessages = { ...@@ -98,6 +98,7 @@ var kMessages = {
observe_callback_frozen: ["Object.observe cannot deliver to a frozen function object"], observe_callback_frozen: ["Object.observe cannot deliver to a frozen function object"],
observe_type_non_string: ["Invalid changeRecord with non-string 'type' property"], observe_type_non_string: ["Invalid changeRecord with non-string 'type' property"],
observe_notify_non_notifier: ["notify called on non-notifier object"], observe_notify_non_notifier: ["notify called on non-notifier object"],
proto_poison_pill: ["Generic use of __proto__ accessor not allowed"],
// RangeError // RangeError
invalid_array_length: ["Invalid array length"], invalid_array_length: ["Invalid array length"],
invalid_array_buffer_length: ["Invalid array buffer length"], invalid_array_buffer_length: ["Invalid array buffer length"],
......
...@@ -956,8 +956,7 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_ClassOf) { ...@@ -956,8 +956,7 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_ClassOf) {
RUNTIME_FUNCTION(MaybeObject*, Runtime_GetPrototype) { RUNTIME_FUNCTION(MaybeObject*, Runtime_GetPrototype) {
NoHandleAllocation ha(isolate); NoHandleAllocation ha(isolate);
ASSERT(args.length() == 1); ASSERT(args.length() == 1);
CONVERT_ARG_CHECKED(JSReceiver, input_obj, 0); CONVERT_ARG_CHECKED(Object, obj, 0);
Object* obj = input_obj;
// We don't expect access checks to be needed on JSProxy objects. // We don't expect access checks to be needed on JSProxy objects.
ASSERT(!obj->IsAccessCheckNeeded() || obj->IsJSObject()); ASSERT(!obj->IsAccessCheckNeeded() || obj->IsJSObject());
do { do {
...@@ -975,12 +974,43 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_GetPrototype) { ...@@ -975,12 +974,43 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_GetPrototype) {
} }
static inline Object* GetPrototypeSkipHiddenPrototypes(Isolate* isolate,
Object* receiver) {
Object* current = receiver->GetPrototype(isolate);
while (current->IsJSObject() &&
JSObject::cast(current)->map()->is_hidden_prototype()) {
current = current->GetPrototype(isolate);
}
return current;
}
RUNTIME_FUNCTION(MaybeObject*, Runtime_SetPrototype) { RUNTIME_FUNCTION(MaybeObject*, Runtime_SetPrototype) {
NoHandleAllocation ha(isolate); NoHandleAllocation ha(isolate);
ASSERT(args.length() == 2); ASSERT(args.length() == 2);
CONVERT_ARG_CHECKED(JSReceiver, input_obj, 0); CONVERT_ARG_CHECKED(JSObject, obj, 0);
CONVERT_ARG_CHECKED(Object, prototype, 1); CONVERT_ARG_CHECKED(Object, prototype, 1);
return input_obj->SetPrototype(prototype, true); if (FLAG_harmony_observation && obj->map()->is_observed()) {
HandleScope scope(isolate);
Handle<JSObject> receiver(obj);
Handle<Object> value(prototype, isolate);
Handle<Object> old_value(
GetPrototypeSkipHiddenPrototypes(isolate, *receiver), isolate);
MaybeObject* result = receiver->SetPrototype(*value, true);
Handle<Object> hresult;
if (!result->ToHandle(&hresult, isolate)) return result;
Handle<Object> new_value(
GetPrototypeSkipHiddenPrototypes(isolate, *receiver), isolate);
if (!new_value->SameValue(*old_value)) {
JSObject::EnqueueChangeRecord(receiver, "prototype",
isolate->factory()->proto_string(),
old_value);
}
return *hresult;
}
return obj->SetPrototype(prototype, true);
} }
...@@ -4109,14 +4139,6 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_DefineOrRedefineDataProperty) { ...@@ -4109,14 +4139,6 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_DefineOrRedefineDataProperty) {
if (callback->IsAccessorInfo()) { if (callback->IsAccessorInfo()) {
return isolate->heap()->undefined_value(); return isolate->heap()->undefined_value();
} }
// TODO(mstarzinger): The __proto__ property should actually be a real
// JavaScript accessor instead of a foreign callback. But for now we just
// avoid changing the writability and configurability attribute of this
// property.
Handle<Name> proto_string = isolate->factory()->proto_string();
if (callback->IsForeign() && proto_string->Equals(*name)) {
attr = static_cast<PropertyAttributes>(attr & ~(READ_ONLY | DONT_DELETE));
}
// Avoid redefining foreign callback as data property, just use the stored // Avoid redefining foreign callback as data property, just use the stored
// setter to update the value instead. // setter to update the value instead.
// TODO(mstarzinger): So far this only works if property attributes don't // TODO(mstarzinger): So far this only works if property attributes don't
...@@ -12015,11 +12037,8 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_DebugConstructedBy) { ...@@ -12015,11 +12037,8 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_DebugConstructedBy) {
RUNTIME_FUNCTION(MaybeObject*, Runtime_DebugGetPrototype) { RUNTIME_FUNCTION(MaybeObject*, Runtime_DebugGetPrototype) {
NoHandleAllocation ha(isolate); NoHandleAllocation ha(isolate);
ASSERT(args.length() == 1); ASSERT(args.length() == 1);
CONVERT_ARG_CHECKED(JSObject, obj, 0); CONVERT_ARG_CHECKED(JSObject, obj, 0);
return GetPrototypeSkipHiddenPrototypes(isolate, obj);
// Use the __proto__ accessor.
return Accessors::ObjectPrototype.getter(obj, NULL);
} }
......
...@@ -61,7 +61,7 @@ function InstallFunctions(object, attributes, functions) { ...@@ -61,7 +61,7 @@ function InstallFunctions(object, attributes, functions) {
} }
// Helper function to install a getter only property. // Helper function to install a getter-only accessor property.
function InstallGetter(object, name, getter) { function InstallGetter(object, name, getter) {
%FunctionSetName(getter, name); %FunctionSetName(getter, name);
%FunctionRemovePrototype(getter); %FunctionRemovePrototype(getter);
...@@ -70,6 +70,18 @@ function InstallGetter(object, name, getter) { ...@@ -70,6 +70,18 @@ function InstallGetter(object, name, getter) {
} }
// Helper function to install a getter/setter accessor property.
function InstallGetterSetter(object, name, getter, setter) {
%FunctionSetName(getter, name);
%FunctionSetName(setter, name);
%FunctionRemovePrototype(getter);
%FunctionRemovePrototype(setter);
%DefineOrRedefineAccessorProperty(object, name, getter, setter, DONT_ENUM);
%SetNativeFlag(getter);
%SetNativeFlag(setter);
}
// Prevents changes to the prototype of a built-in function. // Prevents changes to the prototype of a built-in function.
// The "prototype" property of the function object is made non-configurable, // The "prototype" property of the function object is made non-configurable,
// and the prototype object is made non-extensible. The latter prevents // and the prototype object is made non-extensible. The latter prevents
...@@ -395,7 +407,8 @@ function FromPropertyDescriptor(desc) { ...@@ -395,7 +407,8 @@ function FromPropertyDescriptor(desc) {
} }
// Must be an AccessorDescriptor then. We never return a generic descriptor. // Must be an AccessorDescriptor then. We never return a generic descriptor.
return { get: desc.getGet(), return { get: desc.getGet(),
set: desc.getSet(), set: desc.getSet() === ObjectSetProto ? ObjectPoisonProto
: desc.getSet(),
enumerable: desc.isEnumerable(), enumerable: desc.isEnumerable(),
configurable: desc.isConfigurable() }; configurable: desc.isConfigurable() };
} }
...@@ -1326,6 +1339,24 @@ function ObjectIs(obj1, obj2) { ...@@ -1326,6 +1339,24 @@ function ObjectIs(obj1, obj2) {
} }
// Harmony __proto__ getter.
function ObjectGetProto() {
return %GetPrototype(this);
}
// Harmony __proto__ setter.
function ObjectSetProto(obj) {
return %SetPrototype(this, obj);
}
// Harmony __proto__ poison pill.
function ObjectPoisonProto(obj) {
throw MakeTypeError("proto_poison_pill", []);
}
%SetCode($Object, function(x) { %SetCode($Object, function(x) {
if (%_IsConstructCall()) { if (%_IsConstructCall()) {
if (x == null) return this; if (x == null) return this;
...@@ -1336,14 +1367,18 @@ function ObjectIs(obj1, obj2) { ...@@ -1336,14 +1367,18 @@ function ObjectIs(obj1, obj2) {
} }
}); });
%SetExpectedNumberOfProperties($Object, 4);
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Object // Object
function SetUpObject() { function SetUpObject() {
%CheckIsBootstrapping(); %CheckIsBootstrapping();
// Set Up non-enumerable functions on the Object.prototype object.
%FunctionSetName(ObjectPoisonProto, "__proto__");
%FunctionRemovePrototype(ObjectPoisonProto);
%SetExpectedNumberOfProperties($Object, 4);
// Set up non-enumerable functions on the Object.prototype object.
InstallFunctions($Object.prototype, DONT_ENUM, $Array( InstallFunctions($Object.prototype, DONT_ENUM, $Array(
"toString", ObjectToString, "toString", ObjectToString,
"toLocaleString", ObjectToLocaleString, "toLocaleString", ObjectToLocaleString,
...@@ -1356,6 +1391,10 @@ function SetUpObject() { ...@@ -1356,6 +1391,10 @@ function SetUpObject() {
"__defineSetter__", ObjectDefineSetter, "__defineSetter__", ObjectDefineSetter,
"__lookupSetter__", ObjectLookupSetter "__lookupSetter__", ObjectLookupSetter
)); ));
InstallGetterSetter($Object.prototype, "__proto__",
ObjectGetProto, ObjectSetProto);
// Set up non-enumerable functions in the Object object.
InstallFunctions($Object, DONT_ENUM, $Array( InstallFunctions($Object, DONT_ENUM, $Array(
"keys", ObjectKeys, "keys", ObjectKeys,
"create", ObjectCreate, "create", ObjectCreate,
......
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Check that the __proto__ accessor is properly poisoned when extracted
// from Object.prototype using the property descriptor.
var desc = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
assertEquals("function", typeof desc.get);
assertEquals("function", typeof desc.set);
assertDoesNotThrow("desc.get.call({})");
assertThrows("desc.set.call({})", TypeError);
// Check that any redefinition of the __proto__ accessor causes poising
// to cease and the accessor to be extracted normally.
Object.defineProperty(Object.prototype, "__proto__", { get:function(){} });
desc = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
assertDoesNotThrow("desc.get.call({})");
assertThrows("desc.set.call({})", TypeError);
Object.defineProperty(Object.prototype, "__proto__", { set:function(x){} });
desc = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
assertDoesNotThrow("desc.get.call({})");
assertDoesNotThrow("desc.set.call({})");
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Check baseline for __proto__.
var desc = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
assertFalse(desc.enumerable);
assertTrue(desc.configurable);
assertEquals("function", typeof desc.get);
assertEquals("function", typeof desc.set);
// Check redefining getter for __proto__.
function replaced_get() {};
Object.defineProperty(Object.prototype, "__proto__", { get:replaced_get });
desc = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
assertFalse(desc.enumerable);
assertTrue(desc.configurable);
assertSame(replaced_get, desc.get);
// Check redefining setter for __proto__.
function replaced_set(x) {};
Object.defineProperty(Object.prototype, "__proto__", { set:replaced_set });
desc = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
assertFalse(desc.enumerable);
assertTrue(desc.configurable);
assertSame(replaced_set, desc.set);
// Check changing configurability of __proto__.
Object.defineProperty(Object.prototype, "__proto__", { configurable:false });
desc = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
assertFalse(desc.enumerable);
assertFalse(desc.configurable);
assertSame(replaced_get, desc.get);
assertSame(replaced_set, desc.set);
// Check freezing Object.prototype completely.
Object.freeze(Object.prototype);
assertTrue(Object.isFrozen(Object.prototype));
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment