#527: M1054906 M1278599 M1252228 (+ESR45 changes, see M1263778 for info)

This commit is contained in:
Cameron Kaiser 2018-10-26 21:21:20 -07:00
parent 346c972059
commit 09256df984
13 changed files with 363 additions and 14 deletions

View File

@ -8029,6 +8029,20 @@ TryAttachInstanceOfStub(JSContext* cx, BaselineFrame* frame, ICInstanceOf_Fallba
if (fun->isBoundFunction())
return true;
// If the user has supplied their own @@hasInstance method we shouldn't
// clobber it.
if (!js::FunctionHasDefaultHasInstance(fun, cx->wellKnownSymbols()))
return true;
// Refuse to optimize any function whose [[Prototype]] isn't
// Function.prototype.
if (fun->hasLazyPrototype() || fun->hasUncacheableProto())
return true;
Value funProto = cx->global()->getPrototype(JSProto_Function);
if (funProto.isObject() && fun->getProto() != &funProto.toObject())
return true;
Shape* shape = fun->lookupPure(cx->names().prototype);
if (!shape || !shape->hasSlot() || !shape->hasDefaultGetter())
return true;

View File

@ -13440,10 +13440,36 @@ IonBuilder::jsop_instanceof()
if (!rhsObject || !rhsObject->is<JSFunction>() || rhsObject->isBoundFunction())
break;
// Refuse to optimize anything whose [[Prototype]] isn't Function.prototype
// since we can't guarantee that it uses the default @@hasInstance method.
if (rhsObject->hasUncacheableProto() || rhsObject->hasLazyPrototype())
break;
Value funProto = script()->global().getPrototype(JSProto_Function);
if (!funProto.isObject() || rhsObject->getProto() != &funProto.toObject())
break;
// If the user has supplied their own @@hasInstance method we shouldn't
// clobber it.
JSFunction* fun = &rhsObject->as<JSFunction>();
const WellKnownSymbols* symbols = &compartment->runtime()->wellKnownSymbols();
if (!js::FunctionHasDefaultHasInstance(fun, *symbols))
break;
// Ensure that we will bail if the @@hasInstance property or [[Prototype]]
// change.
TypeSet::ObjectKey* rhsKey = TypeSet::ObjectKey::get(rhsObject);
if (!rhsKey->hasStableClassAndProto(constraints()))
break;
if (rhsKey->unknownProperties())
break;
HeapTypeSetKey hasInstanceObject =
rhsKey->property(SYMBOL_TO_JSID(symbols->hasInstance));
if (hasInstanceObject.isOwnProperty(constraints()))
break;
HeapTypeSetKey protoProperty =
rhsKey->property(NameToId(names().prototype));
JSObject* protoObject = protoProperty.singleton(constraints());

View File

@ -5154,7 +5154,7 @@ jit::PropertyReadNeedsTypeBarrier(JSContext* propertycx,
TypeSet::TypeList types;
if (!property.maybeTypes()->enumerateTypes(&types))
break;
if (types.length()) {
if (types.length() == 1) {
// Note: the return value here is ignored.
observed->addType(types[0], GetJitContext()->temp->lifoAlloc());
break;

View File

@ -4560,6 +4560,7 @@ GetSymbolDescription(HandleSymbol symbol);
macro(iterator) \
macro(match) \
macro(species) \
macro(hasInstance) \
macro(toPrimitive) \
macro(toStringTag) \
macro(unscopables)

View File

@ -644,23 +644,73 @@ js::XDRInterpretedFunction(XDRState<XDR_ENCODE>*, HandleObject, HandleScript, Mu
template bool
js::XDRInterpretedFunction(XDRState<XDR_DECODE>*, HandleObject, HandleScript, MutableHandleFunction);
/* ES6 (04-25-16) 19.2.3.6 Function.prototype [ @@hasInstance ] */
bool
js::fun_symbolHasInstance(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1) {
args.rval().setBoolean(false);
return true;
}
/* Step 1. */
HandleValue func = args.thisv();
// Primitives are non-callable and will always return false from
// OrdinaryHasInstance.
if (!func.isObject()) {
args.rval().setBoolean(false);
return true;
}
RootedObject obj(cx, &func.toObject());
RootedValue v(cx, args[0]);
/* Step 2. */
bool result;
if (!OrdinaryHasInstance(cx, obj, &v, &result))
return false;
args.rval().setBoolean(result);
return true;
}
/*
* [[HasInstance]] internal method for Function objects: fetch the .prototype
* property of its 'this' parameter, and walks the prototype chain of v (only
* if v is an object) returning true if .prototype is found.
* ES6 (4-25-16) 7.3.19 OrdinaryHasInstance
*/
static bool
fun_hasInstance(JSContext* cx, HandleObject objArg, MutableHandleValue v, bool* bp)
bool
js::OrdinaryHasInstance(JSContext* cx, HandleObject objArg, MutableHandleValue v, bool* bp)
{
RootedObject obj(cx, objArg);
while (obj->is<JSFunction>() && obj->isBoundFunction())
obj = obj->as<JSFunction>().getBoundFunctionTarget();
/* Step 1. */
if (!obj->isCallable()) {
*bp = false;
return true;
}
/* Step 2. */
if (obj->is<JSFunction>() && obj->isBoundFunction()) {
/* Steps 2a-b. */
obj = obj->as<JSFunction>().getBoundFunctionTarget();
return InstanceOfOperator(cx, obj, v, bp);
}
/* Step 3. */
if (!v.isObject()) {
*bp = false;
return true;
}
/* Step 4. */
RootedValue pval(cx);
if (!GetProperty(cx, obj, obj, cx->names().prototype, &pval))
return false;
/* Step 5. */
if (pval.isPrimitive()) {
/*
* Throw a runtime error if instanceof is called on a function that
@ -671,6 +721,7 @@ fun_hasInstance(JSContext* cx, HandleObject objArg, MutableHandleValue v, bool*
return false;
}
/* Step 6. */
RootedObject pobj(cx, &pval.toObject());
bool isDelegate;
if (!IsDelegate(cx, pobj, v, &isDelegate))
@ -835,7 +886,7 @@ const Class JSFunction::class_ = {
fun_mayResolve,
nullptr, /* finalize */
nullptr, /* call */
fun_hasInstance,
nullptr,
nullptr, /* construct */
fun_trace,
{
@ -1122,6 +1173,20 @@ fun_toStringHelper(JSContext* cx, HandleObject obj, unsigned indent)
return FunctionToString(cx, fun, indent != JS_DONT_PRETTY_PRINT);
}
bool
js::FunctionHasDefaultHasInstance(JSFunction* fun, const WellKnownSymbols& symbols)
{
jsid id = SYMBOL_TO_JSID(symbols.hasInstance);
Shape* shape = fun->lookupPure(id);
if (shape) {
if (!shape->hasSlot() || !shape->hasDefaultGetter())
return false;
const Value hasInstance = fun->as<NativeObject>().getSlot(shape->slot());
return IsNativeFunction(hasInstance, js::fun_symbolHasInstance);
}
return true;
}
bool
js::fun_toString(JSContext* cx, unsigned argc, Value* vp)
{
@ -1733,6 +1798,7 @@ const JSFunctionSpec js::function_methods[] = {
JS_FN(js_call_str, fun_call, 1,0),
JS_FN("bind", fun_bind, 1,0),
JS_FN("isGenerator", fun_isGenerator,0,0),
JS_SYM_FN(hasInstance, fun_symbolHasInstance, 1, JSPROP_READONLY | JSPROP_PERMANENT),
JS_FS_END
};

View File

@ -692,6 +692,17 @@ fun_toString(JSContext* cx, unsigned argc, Value* vp);
extern bool
fun_bind(JSContext* cx, unsigned argc, Value* vp);
struct WellKnownSymbols;
extern bool
FunctionHasDefaultHasInstance(JSFunction* fun, const WellKnownSymbols& symbols);
extern bool
fun_symbolHasInstance(JSContext* cx, unsigned argc, Value* vp);
extern bool
OrdinaryHasInstance(JSContext* cx, HandleObject objArg, MutableHandleValue v, bool* bp);
/*
* Function extended with reserved slots for use by various kinds of functions.
* Most functions do not have these extensions, but enough do that efficient

View File

@ -0,0 +1,96 @@
const OriginalHasInstance = Function.prototype[Symbol.hasInstance];
// Ensure that folding doesn't impact user defined @@hasInstance methods.
{
function Test() {
this.x = 1;
}
Object.defineProperty(Test, Symbol.hasInstance,
{writable: true, value: () => false});
function x(t) {
return t instanceof Test;
}
function y() {
let t = new Test;
let b = true;
for (let i = 0; i < 10; i++) {
b = b && x(t);
}
return b;
}
function z() {
let f = 0;
let t = 0;
for (let i = 0; i < 100; i++)
assertEq(y(), false);
}
z();
}
// Ensure that the jitting does not clobber user defined @@hasInstance methods.
{
function a() {
function b() {};
b.__proto__ = a.prototype;
return b;
};
let c = new a();
let t = 0;
let f = 0;
let e = 0;
for (let i = 0; i < 40000; i++) {
if (i == 20000)
Object.defineProperty(a.prototype, Symbol.hasInstance,
{writable: true, value: () => true});
if (i == 30000)
Object.setPrototypeOf(c, Function.prototype);
if (1 instanceof c) {
t++;
} else {
f++;
}
}
assertEq(t, 10000);
assertEq(f, 30000);
}
{
function a() {};
function b() {};
Object.defineProperty(a, Symbol.hasInstance, {writable: true, value: () => true});
assertEq(b instanceof a, true);
for (let _ of Array(10000))
assertEq(b instanceof a, true);
}
{
function a(){};
function b(){};
function c(){};
function d(){};
function e(){};
Object.defineProperty(a, Symbol.hasInstance, {value: () => true });
Object.defineProperty(b, Symbol.hasInstance, {value: () => true });
Object.defineProperty(c, Symbol.hasInstance, {value: () => true });
Object.defineProperty(d, Symbol.hasInstance, {value: () => true });
let funcs = [a, b, c, d];
for (let f of funcs)
assertEq(e instanceof f, true);
for (let _ of Array(10001)) {
for (let f of funcs)
assertEq(e instanceof f, true);
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,88 @@
// It is possible to override Function.prototype[@@hasInstance].
let passed = false;
let obj = { foo: true };
let C = function(){};
Object.defineProperty(C, Symbol.hasInstance, {
value: function(inst) { passed = inst.foo; return false; }
});
assertEq(obj instanceof C, false);
assertEq(passed, true);
{
let obj = {
[Symbol.hasInstance](v) { return true; },
};
let whatevs = {};
assertEq(whatevs instanceof obj, true);
}
{
function zzzz() {};
let xxxx = new zzzz();
assertEq(xxxx instanceof zzzz, true);
assertEq(zzzz[Symbol.hasInstance](xxxx), true);
}
// Non-callable objects should return false.
const nonCallables = [
1,
undefined,
null,
"nope",
]
for (let nonCallable of nonCallables) {
assertEq(nonCallable instanceof Function, false);
assertEq(nonCallable instanceof Object, false);
}
// Non-callables should throw when used on the right hand side
// of `instanceof`.
assertThrowsInstanceOf(() => {
function foo() {};
let obj = {};
foo instanceof obj;
}, TypeError);
// Non-callables do not throw for overridden methods
let o = {[Symbol.hasInstance](v) { return true; }}
assertEq(1 instanceof o, true);
// Non-callables return false instead of an exception when
// Function.prototype[Symbol.hasInstance] is called directly.
for (let nonCallable of nonCallables) {
assertEq(Function.prototype[Symbol.hasInstance].call(nonCallable, Object), false);
}
// It should be possible to call the Symbol.hasInstance method directly.
assertEq(Function.prototype[Symbol.hasInstance].call(Function, () => 1), true);
assertEq(Function.prototype[Symbol.hasInstance].call(Function, Object), true);
assertEq(Function.prototype[Symbol.hasInstance].call(Function, null), false);
assertEq(Function.prototype[Symbol.hasInstance].call(Function, Array), true);
assertEq(Function.prototype[Symbol.hasInstance].call(Object, Array), true);
assertEq(Function.prototype[Symbol.hasInstance].call(Array, Function), false);
assertEq(Function.prototype[Symbol.hasInstance].call(({}), Function), false);
assertEq(Function.prototype[Symbol.hasInstance].call(), false)
assertEq(Function.prototype[Symbol.hasInstance].call(({})), false)
// Ensure that bound functions are unwrapped properly
let bindme = {x: function() {}};
let instance = new bindme.x();
let xOuter = bindme.x;
let bound = xOuter.bind(bindme);
let doubleBound = bound.bind(bindme);
let tripleBound = bound.bind(doubleBound);
assertEq(Function.prototype[Symbol.hasInstance].call(bound, instance), true);
assertEq(Function.prototype[Symbol.hasInstance].call(doubleBound, instance), true);
assertEq(Function.prototype[Symbol.hasInstance].call(tripleBound, instance), true);
// Function.prototype[Symbol.hasInstance] is not configurable
let desc = Object.getOwnPropertyDescriptor(Function.prototype, Symbol.hasInstance)
assertEq(desc.configurable, false)
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -5,6 +5,10 @@ var names = [
"iterator",
"match",
"species",
"hasInstance",
"toPrimitive",
"toStringTag",
"unscopables"
];
for (var name of names) {

View File

@ -285,15 +285,16 @@
macro(iterator, iterator, "iterator") \
macro(match, match, "match") \
macro(species, species, "species") \
macro(hasInstance, hasInstance, "hasInstance") \
macro(toPrimitive, toPrimitive, "toPrimitive") \
macro(toStringTag, toStringTag, "toStringTag") \
macro(unscopables, unscopables, "unscopables") \
/* Same goes for the descriptions of the well-known symbols. */ \
macro(Symbol_hasInstance, Symbol_hasInstance, "Symbol.hasInstance") \
macro(Symbol_isConcatSpreadable, Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") \
macro(Symbol_iterator, Symbol_iterator, "Symbol.iterator") \
macro(Symbol_match, Symbol_match, "Symbol.match") \
macro(Symbol_species, Symbol_species, "Symbol.species") \
macro(Symbol_hasInstance, Symbol_hasInstance, "Symbol.hasInstance") \
macro(Symbol_toPrimitive, Symbol_toPrimitive, "Symbol.toPrimitive") \
macro(Symbol_toStringTag, Symbol_toStringTag, "Symbol.toStringTag") \
macro(Symbol_unscopables, Symbol_unscopables, "Symbol.unscopables") \

View File

@ -685,6 +685,44 @@ js::Execute(JSContext* cx, HandleScript script, JSObject& scopeChainArg, Value*
NullFramePtr() /* evalInFrame */, rval);
}
/*
* ES6 (4-25-16) 12.10.4 InstanceofOperator
*/
extern bool
js::InstanceOfOperator(JSContext* cx, HandleObject obj, MutableHandleValue v, bool* bp)
{
/* Step 1. is handled by caller. */
/* Step 2. */
RootedValue hasInstance(cx);
RootedId id(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().hasInstance));
if (!GetProperty(cx, obj, obj, id, &hasInstance))
return false;
if (!hasInstance.isNullOrUndefined()) {
if (!IsCallable(hasInstance))
ReportIsNotFunction(cx, hasInstance);
/* Step 3. */
RootedValue rval(cx);
RootedValue vv(cx, v);
if (!Call(cx, obj, hasInstance, HandleValueArray(vv), &rval))
return false;
*bp = ToBoolean(rval);
return true;
}
/* Step 4. */
if (!obj->isCallable()) {
RootedValue val(cx, ObjectValue(*obj));
ReportIsNotFunction(cx, val);
return false;
}
/* Step 5. */
return js::OrdinaryHasInstance(cx, obj, v, bp);
}
bool
js::HasInstance(JSContext* cx, HandleObject obj, HandleValue v, bool* bp)
{
@ -693,10 +731,7 @@ js::HasInstance(JSContext* cx, HandleObject obj, HandleValue v, bool* bp)
if (clasp->hasInstance)
return clasp->hasInstance(cx, obj, &local, bp);
RootedValue val(cx, ObjectValue(*obj));
ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS,
JSDVG_SEARCH_STACK, val, nullptr);
return false;
return js::InstanceOfOperator(cx, obj, &local, bp);
}
static inline bool

View File

@ -248,6 +248,9 @@ TypeOfObject(JSObject* obj);
extern JSType
TypeOfValue(const Value& v);
extern bool
InstanceOfOperator(JSContext* cx, HandleObject obj, MutableHandleValue v, bool* bp);
extern bool
HasInstance(JSContext* cx, HandleObject obj, HandleValue v, bool* bp);

View File

@ -456,6 +456,10 @@ struct WellKnownSymbols
const ImmutableSymbolPtr& get(JS::SymbolCode code) const {
return get(size_t(code));
}
WellKnownSymbols() {}
WellKnownSymbols(const WellKnownSymbols&) = delete;
WellKnownSymbols& operator=(const WellKnownSymbols&) = delete;
};
#define NAME_OFFSET(name) offsetof(JSAtomState, name)