ES6: Make function name configurable
This is partially based on r21609 but that CL was incomplete. Function name is still non writable so one has to use defineProperty to change the actual value. BUG=v8:3333 LOG=N R=adamk, mstarzinger@chromium.org Review URL: https://codereview.chromium.org/960343002 Cr-Commit-Position: refs/heads/master@{#26924}
This commit is contained in:
parent
1f0af25b60
commit
f7790f7670
@ -1131,12 +1131,44 @@ void Accessors::FunctionNameGetter(
|
||||
}
|
||||
|
||||
|
||||
MUST_USE_RESULT static MaybeHandle<Object> SetFunctionName(
|
||||
Isolate* isolate, Handle<JSFunction> function, Handle<Object> value) {
|
||||
Handle<Object> old_value;
|
||||
bool is_observed = function->map()->is_observed();
|
||||
if (is_observed) {
|
||||
old_value = handle(function->shared()->name(), isolate);
|
||||
}
|
||||
|
||||
Handle<Name> name = isolate->factory()->name_string();
|
||||
LookupIterator it(function, name);
|
||||
CHECK_EQ(LookupIterator::ACCESSOR, it.state());
|
||||
DCHECK(it.HolderIsReceiverOrHiddenPrototype());
|
||||
it.ReconfigureDataProperty(value, it.property_details().attributes());
|
||||
value = it.WriteDataValue(value);
|
||||
|
||||
if (is_observed && !old_value->SameValue(*value)) {
|
||||
return JSObject::EnqueueChangeRecord(function, "update", name, old_value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
void Accessors::FunctionNameSetter(
|
||||
v8::Local<v8::Name> name,
|
||||
v8::Local<v8::Value> val,
|
||||
const v8::PropertyCallbackInfo<void>& info) {
|
||||
// Function name is non writable, non configurable.
|
||||
UNREACHABLE();
|
||||
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(info.GetIsolate());
|
||||
HandleScope scope(isolate);
|
||||
Handle<Object> value = Utils::OpenHandle(*val);
|
||||
|
||||
if (SetPropertyOnInstanceIfInherited(isolate, info, name, value)) return;
|
||||
|
||||
Handle<JSFunction> object =
|
||||
Handle<JSFunction>::cast(Utils::OpenHandle(*info.Holder()));
|
||||
if (SetFunctionName(isolate, object, value).is_null()) {
|
||||
isolate->OptionalRescheduleException(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -382,45 +382,47 @@ void Genesis::SetFunctionInstanceDescriptor(
|
||||
int size = IsFunctionModeWithPrototype(function_mode) ? 5 : 4;
|
||||
Map::EnsureDescriptorSlack(map, size);
|
||||
|
||||
PropertyAttributes attribs = static_cast<PropertyAttributes>(
|
||||
DONT_ENUM | DONT_DELETE | READ_ONLY);
|
||||
PropertyAttributes ro_attribs =
|
||||
static_cast<PropertyAttributes>(DONT_ENUM | DONT_DELETE | READ_ONLY);
|
||||
PropertyAttributes roc_attribs =
|
||||
static_cast<PropertyAttributes>(DONT_ENUM | READ_ONLY);
|
||||
|
||||
Handle<AccessorInfo> length =
|
||||
Accessors::FunctionLengthInfo(isolate(), attribs);
|
||||
Accessors::FunctionLengthInfo(isolate(), ro_attribs);
|
||||
{ // Add length.
|
||||
AccessorConstantDescriptor d(Handle<Name>(Name::cast(length->name())),
|
||||
length, attribs);
|
||||
length, ro_attribs);
|
||||
map->AppendDescriptor(&d);
|
||||
}
|
||||
Handle<AccessorInfo> name =
|
||||
Accessors::FunctionNameInfo(isolate(), attribs);
|
||||
Accessors::FunctionNameInfo(isolate(), ro_attribs);
|
||||
{ // Add name.
|
||||
AccessorConstantDescriptor d(Handle<Name>(Name::cast(name->name())), name,
|
||||
attribs);
|
||||
roc_attribs);
|
||||
map->AppendDescriptor(&d);
|
||||
}
|
||||
Handle<AccessorInfo> args =
|
||||
Accessors::FunctionArgumentsInfo(isolate(), attribs);
|
||||
Accessors::FunctionArgumentsInfo(isolate(), ro_attribs);
|
||||
{ // Add arguments.
|
||||
AccessorConstantDescriptor d(Handle<Name>(Name::cast(args->name())), args,
|
||||
attribs);
|
||||
ro_attribs);
|
||||
map->AppendDescriptor(&d);
|
||||
}
|
||||
Handle<AccessorInfo> caller =
|
||||
Accessors::FunctionCallerInfo(isolate(), attribs);
|
||||
Accessors::FunctionCallerInfo(isolate(), ro_attribs);
|
||||
{ // Add caller.
|
||||
AccessorConstantDescriptor d(Handle<Name>(Name::cast(caller->name())),
|
||||
caller, attribs);
|
||||
caller, ro_attribs);
|
||||
map->AppendDescriptor(&d);
|
||||
}
|
||||
if (IsFunctionModeWithPrototype(function_mode)) {
|
||||
if (function_mode == FUNCTION_WITH_WRITEABLE_PROTOTYPE) {
|
||||
attribs = static_cast<PropertyAttributes>(attribs & ~READ_ONLY);
|
||||
ro_attribs = static_cast<PropertyAttributes>(ro_attribs & ~READ_ONLY);
|
||||
}
|
||||
Handle<AccessorInfo> prototype =
|
||||
Accessors::FunctionPrototypeInfo(isolate(), attribs);
|
||||
Accessors::FunctionPrototypeInfo(isolate(), ro_attribs);
|
||||
AccessorConstantDescriptor d(Handle<Name>(Name::cast(prototype->name())),
|
||||
prototype, attribs);
|
||||
prototype, ro_attribs);
|
||||
map->AppendDescriptor(&d);
|
||||
}
|
||||
}
|
||||
@ -540,6 +542,8 @@ void Genesis::SetStrictFunctionInstanceDescriptor(
|
||||
static_cast<PropertyAttributes>(DONT_ENUM | DONT_DELETE);
|
||||
PropertyAttributes ro_attribs =
|
||||
static_cast<PropertyAttributes>(DONT_ENUM | DONT_DELETE | READ_ONLY);
|
||||
PropertyAttributes roc_attribs =
|
||||
static_cast<PropertyAttributes>(DONT_ENUM | READ_ONLY);
|
||||
|
||||
// Add length.
|
||||
if (function_mode == BOUND_FUNCTION) {
|
||||
@ -557,10 +561,10 @@ void Genesis::SetStrictFunctionInstanceDescriptor(
|
||||
map->AppendDescriptor(&d);
|
||||
}
|
||||
Handle<AccessorInfo> name =
|
||||
Accessors::FunctionNameInfo(isolate(), ro_attribs);
|
||||
Accessors::FunctionNameInfo(isolate(), roc_attribs);
|
||||
{ // Add name.
|
||||
AccessorConstantDescriptor d(Handle<Name>(Name::cast(name->name())), name,
|
||||
ro_attribs);
|
||||
roc_attribs);
|
||||
map->AppendDescriptor(&d);
|
||||
}
|
||||
{ // Add arguments.
|
||||
|
115
test/mjsunit/es6/function-name-configurable.js
Normal file
115
test/mjsunit/es6/function-name-configurable.js
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright 2015 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
function getStrictF() {
|
||||
'use strict';
|
||||
return function f() {};
|
||||
}
|
||||
|
||||
|
||||
function getSloppyF() {
|
||||
return function f() {};
|
||||
}
|
||||
|
||||
|
||||
function test(testFunction) {
|
||||
testFunction(getStrictF());
|
||||
testFunction(getSloppyF());
|
||||
}
|
||||
|
||||
|
||||
function testDescriptor(f) {
|
||||
var descr = Object.getOwnPropertyDescriptor(f, 'name');
|
||||
assertTrue(descr.configurable);
|
||||
assertFalse(descr.enumerable);
|
||||
assertEquals('f', descr.value);
|
||||
assertFalse(descr.writable);
|
||||
}
|
||||
test(testDescriptor);
|
||||
|
||||
|
||||
function testSet(f) {
|
||||
f.name = 'g';
|
||||
assertEquals('f', f.name);
|
||||
}
|
||||
test(testSet);
|
||||
|
||||
|
||||
function testSetStrict(f) {
|
||||
'use strict';
|
||||
assertThrows(function() {
|
||||
f.name = 'g';
|
||||
}, TypeError);
|
||||
}
|
||||
test(testSetStrict);
|
||||
|
||||
|
||||
function testReconfigureAsDataProperty(f) {
|
||||
Object.defineProperty(f, 'name', {
|
||||
value: 'g',
|
||||
});
|
||||
assertEquals('g', f.name);
|
||||
Object.defineProperty(f, 'name', {
|
||||
writable: true
|
||||
});
|
||||
f.name = 'h';
|
||||
assertEquals('h', f.name);
|
||||
|
||||
f.name = 42;
|
||||
assertEquals(42, f.name);
|
||||
}
|
||||
test(testReconfigureAsDataProperty);
|
||||
|
||||
|
||||
function testReconfigureAsAccessorProperty(f) {
|
||||
var name = 'g';
|
||||
Object.defineProperty(f, 'name', {
|
||||
get: function() { return name; },
|
||||
set: function(v) { name = v; }
|
||||
});
|
||||
assertEquals('g', f.name);
|
||||
f.name = 'h';
|
||||
assertEquals('h', f.name);
|
||||
}
|
||||
test(testReconfigureAsAccessorProperty);
|
||||
|
||||
|
||||
function testFunctionToString(f) {
|
||||
Object.defineProperty(f, 'name', {
|
||||
value: {toString: function() { assertUnreachable(); }},
|
||||
});
|
||||
assertEquals('function f() {}', f.toString());
|
||||
}
|
||||
test(testFunctionToString);
|
||||
|
||||
|
||||
(function testSetOnInstance() {
|
||||
// This needs to come before testDelete below
|
||||
assertTrue(Function.prototype.hasOwnProperty('name'));
|
||||
|
||||
function f() {}
|
||||
delete f.name;
|
||||
assertEquals('Empty', f.name);
|
||||
|
||||
f.name = 42;
|
||||
assertEquals('Empty', f.name); // non writable prototype property.
|
||||
assertFalse(f.hasOwnProperty('name'));
|
||||
|
||||
Object.defineProperty(Function.prototype, 'name', {writable: true});
|
||||
|
||||
f.name = 123;
|
||||
assertTrue(f.hasOwnProperty('name'));
|
||||
assertEquals(123, f.name);
|
||||
})();
|
||||
|
||||
|
||||
(function testDelete() {
|
||||
function f() {}
|
||||
assertTrue(delete f.name);
|
||||
assertFalse(f.hasOwnProperty('name'));
|
||||
assertEquals('Empty', f.name);
|
||||
|
||||
assertTrue(delete Function.prototype.name);
|
||||
assertEquals(undefined, f.name);
|
||||
})();
|
@ -1142,7 +1142,8 @@ var properties = ["a", "1", 1, "length", "setPrototype", "name", "caller"];
|
||||
function blacklisted(obj, prop) {
|
||||
return (obj instanceof Int32Array && prop == 1) ||
|
||||
(obj instanceof Int32Array && prop === "length") ||
|
||||
(obj instanceof ArrayBuffer && prop == 1)
|
||||
(obj instanceof ArrayBuffer && prop == 1) ||
|
||||
(obj instanceof Function && prop === "name"); // Has its own test.
|
||||
}
|
||||
|
||||
for (var i in objects) for (var j in properties) {
|
||||
@ -1798,3 +1799,28 @@ for (var b1 = 0; b1 < 2; ++b1)
|
||||
for (var n = 0; n < 3; ++n)
|
||||
for (var i in mutationByIncr)
|
||||
TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1);
|
||||
|
||||
|
||||
(function TestFunctionName() {
|
||||
reset();
|
||||
|
||||
function fun() {}
|
||||
Object.observe(fun, observer.callback);
|
||||
fun.name = 'x'; // No change. Not writable.
|
||||
Object.defineProperty(fun, 'name', {value: 'a'});
|
||||
Object.defineProperty(fun, 'name', {writable: true});
|
||||
fun.name = 'b';
|
||||
delete fun.name;
|
||||
fun.name = 'x'; // No change. Function.prototype.name is non writable
|
||||
Object.defineProperty(Function.prototype, 'name', {writable: true});
|
||||
fun.name = 'c';
|
||||
fun.name = 'c'; // Same, no update.
|
||||
Object.deliverChangeRecords(observer.callback);
|
||||
observer.assertCallbackRecords([
|
||||
{ object: fun, type: 'update', name: 'name', oldValue: 'fun' },
|
||||
{ object: fun, type: 'reconfigure', name: 'name'},
|
||||
{ object: fun, type: 'update', name: 'name', oldValue: 'a' },
|
||||
{ object: fun, type: 'delete', name: 'name', oldValue: 'b' },
|
||||
{ object: fun, type: 'add', name: 'name' },
|
||||
]);
|
||||
})();
|
||||
|
@ -80,8 +80,9 @@ assertFalse(Object.getOwnPropertyDescriptor(f, 'prototype').writable);
|
||||
assertThrows("'use strict'; f.prototype = {}");
|
||||
assertThrows("Object.defineProperty(f, 'prototype', { value: {} })");
|
||||
|
||||
// Verify that non-writability of other properties is respected.
|
||||
assertThrows("Object.defineProperty(f, 'name', { value: {} })");
|
||||
// Verify that non-configurability of other properties is respected, but
|
||||
// non-writability is ignored by Object.defineProperty().
|
||||
Object.defineProperty(f, 'name', { value: {} });
|
||||
assertThrows("Object.defineProperty(f, 'length', { value: {} })");
|
||||
assertThrows("Object.defineProperty(f, 'caller', { value: {} })");
|
||||
assertThrows("Object.defineProperty(f, 'arguments', { value: {} })");
|
||||
|
@ -39,7 +39,7 @@ function g(x) {
|
||||
|
||||
function checkNameDescriptor(f) {
|
||||
var descriptor = Object.getOwnPropertyDescriptor(f, "name");
|
||||
assertFalse(descriptor.configurable);
|
||||
assertTrue(descriptor.configurable);
|
||||
assertFalse(descriptor.enumerable);
|
||||
assertFalse(descriptor.writable);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user