- Revision
- 196956
- Author
- keith_mil...@apple.com
- Date
- 2016-02-22 14:07:16 -0800 (Mon, 22 Feb 2016)
Log Message
Bound functions should use the prototype of the function being bound
https://bugs.webkit.org/show_bug.cgi?id=154195
Reviewed by Geoffrey Garen.
Per ES6, the result of Function.prototype.bind should have the same
prototype as the the function being bound. In order to avoid creating
a new structure each time a function is bound we store the new
structure in our structure map. However, we cannot currently store
structures that have a different GlobalObject than their prototype.
In the rare case that the GlobalObject differs or the prototype of
the bindee is null we create a new structure each time. To further
minimize new structures, as well as making structure lookup faster,
we also store the structure in the RareData of the function we
are binding.
* runtime/FunctionRareData.cpp:
(JSC::FunctionRareData::visitChildren):
* runtime/FunctionRareData.h:
(JSC::FunctionRareData::getBoundFunctionStructure):
(JSC::FunctionRareData::setBoundFunctionStructure):
* runtime/JSBoundFunction.cpp:
(JSC::getBoundFunctionStructure):
(JSC::JSBoundFunction::create):
* tests/es6.yaml:
* tests/stress/bound-function-uses-prototype.js: Added.
(testChangeProto.foo):
(testChangeProto):
(testBuiltins):
* tests/stress/class-subclassing-function.js:
Modified Paths
Added Paths
Diff
Modified: trunk/Source/_javascript_Core/ChangeLog (196955 => 196956)
--- trunk/Source/_javascript_Core/ChangeLog 2016-02-22 22:02:24 UTC (rev 196955)
+++ trunk/Source/_javascript_Core/ChangeLog 2016-02-22 22:07:16 UTC (rev 196956)
@@ -1,5 +1,38 @@
2016-02-22 Keith Miller <keith_mil...@apple.com>
+ Bound functions should use the prototype of the function being bound
+ https://bugs.webkit.org/show_bug.cgi?id=154195
+
+ Reviewed by Geoffrey Garen.
+
+ Per ES6, the result of Function.prototype.bind should have the same
+ prototype as the the function being bound. In order to avoid creating
+ a new structure each time a function is bound we store the new
+ structure in our structure map. However, we cannot currently store
+ structures that have a different GlobalObject than their prototype.
+ In the rare case that the GlobalObject differs or the prototype of
+ the bindee is null we create a new structure each time. To further
+ minimize new structures, as well as making structure lookup faster,
+ we also store the structure in the RareData of the function we
+ are binding.
+
+ * runtime/FunctionRareData.cpp:
+ (JSC::FunctionRareData::visitChildren):
+ * runtime/FunctionRareData.h:
+ (JSC::FunctionRareData::getBoundFunctionStructure):
+ (JSC::FunctionRareData::setBoundFunctionStructure):
+ * runtime/JSBoundFunction.cpp:
+ (JSC::getBoundFunctionStructure):
+ (JSC::JSBoundFunction::create):
+ * tests/es6.yaml:
+ * tests/stress/bound-function-uses-prototype.js: Added.
+ (testChangeProto.foo):
+ (testChangeProto):
+ (testBuiltins):
+ * tests/stress/class-subclassing-function.js:
+
+2016-02-22 Keith Miller <keith_mil...@apple.com>
+
Unreviewed, fix stress test to not print on success.
* tests/stress/call-apply-builtin-functions-dont-use-iterators.js:
Modified: trunk/Source/_javascript_Core/runtime/FunctionRareData.cpp (196955 => 196956)
--- trunk/Source/_javascript_Core/runtime/FunctionRareData.cpp 2016-02-22 22:02:24 UTC (rev 196955)
+++ trunk/Source/_javascript_Core/runtime/FunctionRareData.cpp 2016-02-22 22:07:16 UTC (rev 196956)
@@ -56,6 +56,7 @@
rareData->m_objectAllocationProfile.visitAggregate(visitor);
rareData->m_internalFunctionAllocationProfile.visitAggregate(visitor);
+ visitor.append(&rareData->m_boundFunctionStructure);
}
FunctionRareData::FunctionRareData(VM& vm)
Modified: trunk/Source/_javascript_Core/runtime/FunctionRareData.h (196955 => 196956)
--- trunk/Source/_javascript_Core/runtime/FunctionRareData.h 2016-02-22 22:02:24 UTC (rev 196955)
+++ trunk/Source/_javascript_Core/runtime/FunctionRareData.h 2016-02-22 22:07:16 UTC (rev 196956)
@@ -90,6 +90,9 @@
return m_internalFunctionAllocationProfile.createAllocationStructureFromBase(vm, this, prototype, baseStructure);
}
+ Structure* getBoundFunctionStructure() { return m_boundFunctionStructure.get(); }
+ void setBoundFunctionStructure(VM& vm, Structure* structure) { m_boundFunctionStructure.set(vm, this, structure); }
+
protected:
FunctionRareData(VM&);
~FunctionRareData();
@@ -114,6 +117,7 @@
ObjectAllocationProfile m_objectAllocationProfile;
InlineWatchpointSet m_objectAllocationProfileWatchpoint;
InternalFunctionAllocationProfile m_internalFunctionAllocationProfile;
+ WriteBarrier<Structure> m_boundFunctionStructure;
};
} // namespace JSC
Modified: trunk/Source/_javascript_Core/runtime/JSBoundFunction.cpp (196955 => 196956)
--- trunk/Source/_javascript_Core/runtime/JSBoundFunction.cpp 2016-02-22 22:02:24 UTC (rev 196955)
+++ trunk/Source/_javascript_Core/runtime/JSBoundFunction.cpp 2016-02-22 22:07:16 UTC (rev 196956)
@@ -87,13 +87,44 @@
return JSValue::encode(jsBoolean(boundObject->targetFunction()->hasInstance(exec, value)));
}
+inline Structure* getBoundFunctionStructure(VM& vm, JSGlobalObject* globalObject, JSObject* targetFunction)
+{
+ JSValue prototype = targetFunction->structure(vm)->storedPrototype();
+ JSFunction* targetJSFunction = jsDynamicCast<JSFunction*>(targetFunction);
+
+ // We only cache the structure of the bound function if the bindee is a JSFunction since there
+ // isn't any good place to put the structure on Internal Functions.
+ if (targetJSFunction) {
+ Structure* structure = targetJSFunction->rareData(vm)->getBoundFunctionStructure();
+ if (structure && structure->storedPrototype() == prototype && structure->globalObject() == globalObject)
+ return structure;
+ }
+
+ Structure* result = globalObject->boundFunctionStructure();
+
+ // It would be nice if the structure map was keyed global objects in addition to the other things. Unfortunately, it is not
+ // currently. Whoever works on caching structure changes for prototype transistions should consider this problem as well.
+ // See: https://bugs.webkit.org/show_bug.cgi?id=152738
+ if (prototype.isObject() && prototype.getObject()->globalObject() == globalObject) {
+ result = vm.prototypeMap.emptyStructureForPrototypeFromBaseStructure(prototype.getObject(), result);
+ ASSERT_WITH_SECURITY_IMPLICATION(result->globalObject() == globalObject);
+ } else
+ result = Structure::create(vm, globalObject, prototype, result->typeInfo(), result->classInfo());
+
+ if (targetJSFunction)
+ targetJSFunction->rareData(vm)->setBoundFunctionStructure(vm, result);
+
+ return result;
+}
+
JSBoundFunction* JSBoundFunction::create(VM& vm, JSGlobalObject* globalObject, JSObject* targetFunction, JSValue boundThis, JSValue boundArgs, int length, const String& name)
{
ConstructData constructData;
ConstructType constructType = JSC::getConstructData(targetFunction, constructData);
bool canConstruct = constructType != ConstructTypeNone;
NativeExecutable* executable = vm.getHostFunction(boundFunctionCall, canConstruct ? boundFunctionConstruct : callHostFunctionAsConstructor, ASCIILiteral("Function.prototype.bind result"));
- JSBoundFunction* function = new (NotNull, allocateCell<JSBoundFunction>(vm.heap)) JSBoundFunction(vm, globalObject, globalObject->boundFunctionStructure(), targetFunction, boundThis, boundArgs);
+ Structure* structure = getBoundFunctionStructure(vm, globalObject, targetFunction);
+ JSBoundFunction* function = new (NotNull, allocateCell<JSBoundFunction>(vm.heap)) JSBoundFunction(vm, globalObject, structure, targetFunction, boundThis, boundArgs);
function->finishCreation(vm, executable, length, makeString("bound ", name));
return function;
Modified: trunk/Source/_javascript_Core/tests/es6.yaml (196955 => 196956)
--- trunk/Source/_javascript_Core/tests/es6.yaml 2016-02-22 22:02:24 UTC (rev 196955)
+++ trunk/Source/_javascript_Core/tests/es6.yaml 2016-02-22 22:07:16 UTC (rev 196956)
@@ -787,7 +787,7 @@
- path: es6/Function_is_subclassable_Function.prototype.apply.js
cmd: runES6 :normal
- path: es6/Function_is_subclassable_Function.prototype.bind.js
- cmd: runES6 :fail
+ cmd: runES6 :normal
- path: es6/Function_is_subclassable_Function.prototype.call.js
cmd: runES6 :normal
- path: es6/function_name_property_accessor_properties.js
@@ -897,15 +897,15 @@
- path: es6/proper_tail_calls_tail_call_optimisation_mutual_recursion.js
cmd: runES6 :normal
- path: es6/prototype_of_bound_functions_arrow_functions.js
- cmd: runES6 :fail
+ cmd: runES6 :normal
- path: es6/prototype_of_bound_functions_basic_functions.js
- cmd: runES6 :fail
+ cmd: runES6 :normal
- path: es6/prototype_of_bound_functions_classes.js
- cmd: runES6 :fail
+ cmd: runES6 :normal
- path: es6/prototype_of_bound_functions_generator_functions.js
- cmd: runES6 :fail
+ cmd: runES6 :normal
- path: es6/prototype_of_bound_functions_subclasses.js
- cmd: runES6 :fail
+ cmd: runES6 :normal
- path: es6/Proxy_apply_handler.js
cmd: runES6 :normal
- path: es6/Proxy_Array.isArray_support.js
Added: trunk/Source/_javascript_Core/tests/stress/bound-function-uses-prototype.js (0 => 196956)
--- trunk/Source/_javascript_Core/tests/stress/bound-function-uses-prototype.js (rev 0)
+++ trunk/Source/_javascript_Core/tests/stress/bound-function-uses-prototype.js 2016-02-22 22:07:16 UTC (rev 196956)
@@ -0,0 +1,31 @@
+// Test ES6 feature of using the bindee's prototype when binding a function.
+
+bind = Function.prototype.bind;
+
+function testChangeProto() {
+ function foo() { }
+
+ foo.__proto__ = Object.prototype;
+
+ let bar = bind.call(foo);
+ if (bar.__proto__ !== foo.__proto__)
+ throw "incorrect prototype";
+
+ foo.__proto__ = null;
+ bar = bind.call(foo);
+ if (bar.__proto__ !== foo.__proto__)
+ throw "cached prototype incorrectly"
+}
+testChangeProto();
+
+function testBuiltins() {
+ let bar = bind.call(Array);
+ if (bar.__proto__ !== Array.__proto__)
+ throw "builtin prototype incorrect";
+
+ Array.__proto__ = null;
+ bar = bind.call(Array);
+ if (bar.__proto__ !== Array.__proto__)
+ throw "builtin prototype did not change correctly.";
+}
+testBuiltins();
Modified: trunk/Source/_javascript_Core/tests/stress/class-subclassing-function.js (196955 => 196956)
--- trunk/Source/_javascript_Core/tests/stress/class-subclassing-function.js 2016-02-22 22:02:24 UTC (rev 196955)
+++ trunk/Source/_javascript_Core/tests/stress/class-subclassing-function.js 2016-02-22 22:07:16 UTC (rev 196956)
@@ -1,4 +1,5 @@
F = class extends Function { }
+N = class extends null { }
function test(i) {
@@ -20,12 +21,15 @@
throw "function was not .callable";
let g2 = g.bind({foo:1}, 1);
- if (g2 instanceof F)
- throw "the binding of a subclass should not inherit from the original function";
+ if (!(g2 instanceof F))
+ throw "the binding of a subclass should inherit from the bound function's class";
if (g2(1) !== 3)
throw "binding didn't work";
+ let bound = C.bind(null)
+ if (bound.__proto__ !== C.__proto__)
+ throw "binding with null as prototype didn't work";
}
noInline(test);