Commit deb7d5b0 authored by mvstanton's avatar mvstanton Committed by Commit bot

ES6: Desugaring of instanceof to support @@hasInstance

This is a rework of the instanceof operator to support ES6 semantics
(as per section 12.10.4 of the spec:
https://tc39.github.io/ecma262/#sec-instanceofoperator).

It's behind flag --harmony-instanceof for now, which is turned on for staging.

BUG=v8:4447
LOG=N

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

Cr-Commit-Position: refs/heads/master@{#34170}
parent a509b105
......@@ -172,6 +172,8 @@ void AstValue::Internalize(Isolate* isolate) {
if (symbol_name_[0] == 'i') {
DCHECK_EQ(0, strcmp(symbol_name_, "iterator_symbol"));
value_ = isolate->factory()->iterator_symbol();
} else if (strcmp(symbol_name_, "hasInstance_symbol") == 0) {
value_ = isolate->factory()->has_instance_symbol();
} else {
DCHECK_EQ(0, strcmp(symbol_name_, "home_object_symbol"));
value_ = isolate->factory()->home_object_symbol();
......
......@@ -1162,6 +1162,9 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
// Set the length for the function to satisfy ECMA-262.
has_instance->shared()->set_length(1);
// Install in the native context
native_context()->set_ordinary_has_instance(*has_instance);
// Install the "constructor" property on the %FunctionPrototype%.
JSObject::AddProperty(prototype, factory->constructor_string(),
function_fun, DONT_ENUM);
......@@ -2358,6 +2361,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_function_name)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_function_sent)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(promise_extra)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_tailcalls)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_instanceof)
void InstallPublicSymbol(Factory* factory, Handle<Context> native_context,
const char* name, Handle<Symbol> value) {
......@@ -3000,6 +3004,7 @@ bool Genesis::InstallExperimentalNatives() {
static const char* harmony_do_expressions_natives[] = {nullptr};
static const char* harmony_regexp_subclass_natives[] = {nullptr};
static const char* harmony_regexp_lookbehind_natives[] = {nullptr};
static const char* harmony_instanceof_natives[] = {nullptr};
static const char* harmony_regexp_property_natives[] = {nullptr};
static const char* harmony_function_name_natives[] = {nullptr};
static const char* harmony_function_sent_natives[] = {nullptr};
......
......@@ -93,8 +93,8 @@ enum BindingFlags {
V(REFLECT_DEFINE_PROPERTY_INDEX, JSFunction, reflect_define_property) \
V(REFLECT_DELETE_PROPERTY_INDEX, JSFunction, reflect_delete_property) \
V(SPREAD_ARGUMENTS_INDEX, JSFunction, spread_arguments) \
V(SPREAD_ITERABLE_INDEX, JSFunction, spread_iterable)
V(SPREAD_ITERABLE_INDEX, JSFunction, spread_iterable) \
V(ORDINARY_HAS_INSTANCE_INDEX, JSFunction, ordinary_has_instance)
#define NATIVE_CONTEXT_IMPORTED_FIELDS(V) \
V(ARRAY_CONCAT_INDEX, JSFunction, array_concat) \
......
......@@ -221,7 +221,8 @@ DEFINE_IMPLICATION(es_staging, harmony_tailcalls)
#define HARMONY_STAGED(V) \
V(harmony_function_name, "harmony Function name inference") \
V(harmony_regexp_lookbehind, "harmony regexp lookbehind") \
V(harmony_species, "harmony Symbol.species")
V(harmony_species, "harmony Symbol.species") \
V(harmony_instanceof, "harmony instanceof support")
// Features that are shipping (turned on by default, but internal flag remains).
#define HARMONY_SHIPPING(V) \
......
......@@ -94,6 +94,7 @@ class CallSite {
T(ArrayFunctionsOnSealed, "Cannot add/remove sealed array elements") \
T(ArrayNotSubclassable, "Subclassing Arrays is not currently supported.") \
T(CalledNonCallable, "% is not a function") \
T(CalledNonCallableInstanceOf, "right-hand side is not a function") \
T(CalledOnNonObject, "% called on non-object") \
T(CalledOnNullOrUndefined, "% called on null or undefined") \
T(CallSiteExpectsFunction, \
......@@ -516,6 +517,8 @@ class CallSite {
T(UnterminatedRegExp, "Invalid regular expression: missing /") \
T(UnterminatedTemplate, "Unterminated template literal") \
T(UnterminatedTemplateExpr, "Missing } in template expression") \
T(FoundNonCallableHasInstance, "Found non-callable @@hasInstance") \
T(NonObjectInInstanceOfCheck, "Expecting an object in instanceof check") \
/* EvalError */ \
T(CodeGenFromStrings, "%") \
/* URIError */ \
......
......@@ -2241,13 +2241,15 @@ ParserBase<Traits>::ParseBinaryExpression(int prec, bool accept_IN,
ReportMessageAt(op_location, MessageTemplate::kStrongEqual);
*ok = false;
return this->EmptyExpression();
} else if (FLAG_harmony_instanceof && cmp == Token::INSTANCEOF) {
x = Traits::RewriteInstanceof(x, y, pos);
} else {
x = factory()->NewCompareOperation(cmp, x, y, pos);
if (cmp != op) {
// The comparison was negated - add a NOT.
x = factory()->NewUnaryOperation(Token::NOT, x, pos);
}
}
x = factory()->NewCompareOperation(cmp, x, y, pos);
if (cmp != op) {
// The comparison was negated - add a NOT.
x = factory()->NewUnaryOperation(Token::NOT, x, pos);
}
} else {
// We have a "normal" binary operation.
x = factory()->NewBinaryOperation(op, x, y, pos);
......
......@@ -6292,6 +6292,185 @@ Expression* ParserTraits::RewriteYieldStar(
return yield_star;
}
// Desugaring of (lhs) instanceof (rhs)
// ====================================
//
// We desugar instanceof into a load of property @@hasInstance on the rhs.
// We end up with roughly the following code (O, C):
//
// do {
// let O = lhs;
// let C = rhs;
// if (!IS_RECEIVER(C)) throw MakeTypeError(kNonObjectInInstanceOfCheck);
// let handler_result = C[Symbol.hasInstance];
// if (handler_result === undefined) {
// if (!IS_CALLABLE(C)) {
// throw MakeTypeError(kCalledNonCallableInstanceOf);
// }
// handler_result = %ordinary_has_instance(C, O);
// } else {
// handler_result = !!(%_Call(handler_result, C, O));
// }
// handler_result;
// }
//
Expression* ParserTraits::RewriteInstanceof(Expression* lhs, Expression* rhs,
int pos) {
const int nopos = RelocInfo::kNoPosition;
auto factory = parser_->factory();
auto avfactory = parser_->ast_value_factory();
auto scope = parser_->scope_;
auto zone = parser_->zone();
// let O = lhs;
Variable* var_O = scope->NewTemporary(avfactory->empty_string());
Statement* get_O;
{
Expression* O_proxy = factory->NewVariableProxy(var_O);
Expression* assignment =
factory->NewAssignment(Token::ASSIGN, O_proxy, lhs, nopos);
get_O = factory->NewExpressionStatement(assignment, nopos);
}
// let C = lhs;
Variable* var_C = scope->NewTemporary(avfactory->empty_string());
Statement* get_C;
{
Expression* C_proxy = factory->NewVariableProxy(var_C);
Expression* assignment =
factory->NewAssignment(Token::ASSIGN, C_proxy, rhs, nopos);
get_C = factory->NewExpressionStatement(assignment, nopos);
}
// if (!IS_RECEIVER(C)) throw MakeTypeError(kNonObjectInInstanceOfCheck);
Statement* validate_C;
{
auto args = new (zone) ZoneList<Expression*>(1, zone);
args->Add(factory->NewVariableProxy(var_C), zone);
Expression* is_receiver_call =
factory->NewCallRuntime(Runtime::kInlineIsJSReceiver, args, nopos);
Expression* call =
NewThrowTypeError(MessageTemplate::kNonObjectInInstanceOfCheck,
avfactory->empty_string(), nopos);
Statement* throw_call = factory->NewExpressionStatement(call, nopos);
validate_C =
factory->NewIfStatement(is_receiver_call,
factory->NewEmptyStatement(nopos),
throw_call,
nopos);
}
// let handler_result = C[Symbol.hasInstance];
Variable* var_handler_result = scope->NewTemporary(avfactory->empty_string());
Statement* initialize_handler;
{
Expression* hasInstance_symbol_literal =
factory->NewSymbolLiteral("hasInstance_symbol", RelocInfo::kNoPosition);
Expression* prop = factory->NewProperty(factory->NewVariableProxy(var_C),
hasInstance_symbol_literal, pos);
Expression* handler_proxy = factory->NewVariableProxy(var_handler_result);
Expression* assignment =
factory->NewAssignment(Token::ASSIGN, handler_proxy, prop, nopos);
initialize_handler = factory->NewExpressionStatement(assignment, nopos);
}
// if (handler_result === undefined) {
// if (!IS_CALLABLE(C)) {
// throw MakeTypeError(kCalledNonCallableInstanceOf);
// }
// result = %ordinary_has_instance(C, O);
// } else {
// handler_result = !!%_Call(handler_result, C, O);
// }
Statement* call_handler;
{
Expression* condition = factory->NewCompareOperation(
Token::EQ_STRICT, factory->NewVariableProxy(var_handler_result),
factory->NewUndefinedLiteral(nopos), nopos);
Block* then_side = factory->NewBlock(nullptr, 2, false, nopos);
{
Expression* throw_expr =
NewThrowTypeError(MessageTemplate::kCalledNonCallableInstanceOf,
avfactory->empty_string(), nopos);
Statement* validate_C = CheckCallable(var_C, throw_expr);
ZoneList<Expression*>* args = new (zone) ZoneList<Expression*>(2, zone);
args->Add(factory->NewVariableProxy(var_C), zone);
args->Add(factory->NewVariableProxy(var_O), zone);
CallRuntime* call = factory->NewCallRuntime(
Context::ORDINARY_HAS_INSTANCE_INDEX, args, pos);
Expression* result_proxy = factory->NewVariableProxy(var_handler_result);
Expression* assignment =
factory->NewAssignment(Token::ASSIGN, result_proxy, call, nopos);
Statement* assignment_return =
factory->NewExpressionStatement(assignment, nopos);
then_side->statements()->Add(validate_C, zone);
then_side->statements()->Add(assignment_return, zone);
}
Statement* else_side;
{
auto args = new (zone) ZoneList<Expression*>(3, zone);
args->Add(factory->NewVariableProxy(var_handler_result), zone);
args->Add(factory->NewVariableProxy(var_C), zone);
args->Add(factory->NewVariableProxy(var_O), zone);
Expression* call =
factory->NewCallRuntime(Runtime::kInlineCall, args, nopos);
Expression* inner_not =
factory->NewUnaryOperation(Token::NOT, call, nopos);
Expression* outer_not =
factory->NewUnaryOperation(Token::NOT, inner_not, nopos);
Expression* result_proxy = factory->NewVariableProxy(var_handler_result);
Expression* assignment =
factory->NewAssignment(Token::ASSIGN, result_proxy, outer_not, nopos);
else_side = factory->NewExpressionStatement(assignment, nopos);
}
call_handler =
factory->NewIfStatement(condition, then_side, else_side, nopos);
}
// do { ... }
DoExpression* instanceof;
{
Block* block = factory->NewBlock(nullptr, 5, true, nopos);
block->statements()->Add(get_O, zone);
block->statements()->Add(get_C, zone);
block->statements()->Add(validate_C, zone);
block->statements()->Add(initialize_handler, zone);
block->statements()->Add(call_handler, zone);
// Here is the desugared instanceof.
instanceof = factory->NewDoExpression(block, var_handler_result, nopos);
Rewriter::Rewrite(parser_, instanceof, avfactory);
}
return instanceof;
}
Statement* ParserTraits::CheckCallable(Variable* var, Expression* error) {
auto factory = parser_->factory();
auto avfactory = parser_->ast_value_factory();
const int nopos = RelocInfo::kNoPosition;
Statement* validate_var;
{
Expression* type_of = factory->NewUnaryOperation(
Token::TYPEOF, factory->NewVariableProxy(var), nopos);
Expression* function_literal =
factory->NewStringLiteral(avfactory->function_string(), nopos);
Expression* condition = factory->NewCompareOperation(
Token::EQ_STRICT, type_of, function_literal, nopos);
Statement* throw_call = factory->NewExpressionStatement(error, nopos);
validate_var = factory->NewIfStatement(
condition, factory->NewEmptyStatement(nopos), throw_call, nopos);
}
return validate_var;
}
void ParserTraits::BuildIteratorClose(ZoneList<Statement*>* statements,
Variable* iterator,
......@@ -6440,20 +6619,10 @@ void ParserTraits::BuildIteratorCloseForCompletion(
// }
Statement* check_return_callable;
{
Expression* type_of = factory->NewUnaryOperation(
Token::TYPEOF, factory->NewVariableProxy(var_return), nopos);
Expression* function_literal = factory->NewStringLiteral(
avfactory->function_string(), nopos);
Expression* condition = factory->NewCompareOperation(
Token::EQ_STRICT, type_of, function_literal, nopos);
Expression* call = NewThrowTypeError(
Expression* throw_expr = NewThrowTypeError(
MessageTemplate::kReturnMethodNotCallable,
avfactory->empty_string(), nopos);
Statement* throw_call = factory->NewExpressionStatement(call, nopos);
check_return_callable = factory->NewIfStatement(
condition, factory->NewEmptyStatement(nopos), throw_call, nopos);
check_return_callable = CheckCallable(var_return, throw_expr);
}
// output = %_Call(iteratorReturn, iterator);
......
......@@ -665,6 +665,8 @@ class ParserTraits {
Expression* RewriteYieldStar(
Expression* generator, Expression* expression, int pos);
Expression* RewriteInstanceof(Expression* lhs, Expression* rhs, int pos);
private:
Parser* parser_;
......@@ -674,6 +676,7 @@ class ParserTraits {
void BuildIteratorCloseForCompletion(
ZoneList<Statement*>* statements, Variable* iterator,
Variable* body_threw);
Statement* CheckCallable(Variable* var, Expression* error);
};
......
......@@ -938,6 +938,9 @@ class PreParserTraits {
inline PreParserExpression RewriteYieldStar(
PreParserExpression generator, PreParserExpression expr, int pos);
inline PreParserExpression RewriteInstanceof(PreParserExpression lhs,
PreParserExpression rhs,
int pos);
private:
PreParser* pre_parser_;
......@@ -1141,6 +1144,11 @@ PreParserExpression PreParserTraits::RewriteYieldStar(
generator, expression, Yield::kDelegating, pos);
}
PreParserExpression PreParserTraits::RewriteInstanceof(PreParserExpression lhs,
PreParserExpression rhs,
int pos) {
return PreParserExpression::Default();
}
PreParserStatementList PreParser::ParseEagerFunctionBody(
PreParserIdentifier function_name, int pos,
......
......@@ -236,7 +236,6 @@ RUNTIME_FUNCTION(Runtime_IsConstructor) {
return isolate->heap()->ToBoolean(object->IsConstructor());
}
RUNTIME_FUNCTION(Runtime_SetForceInlineFlag) {
SealHandleScope shs(isolate);
RUNTIME_ASSERT(args.length() == 1);
......
......@@ -247,7 +247,6 @@ namespace internal {
F(IsFunction, 1, 1) \
F(FunctionToString, 1, 1)
#define FOR_EACH_INTRINSIC_GENERATOR(F) \
F(CreateJSGeneratorObject, 0, 1) \
F(SuspendJSGeneratorObject, 1, 1) \
......
// Copyright 2016 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.
// Flags: --harmony-instanceof
// Make sure it's an error if @@hasInstance isn't a function.
(function() {
var F = {};
F[Symbol.hasInstance] = null;
assertThrows(function() { 0 instanceof F; }, TypeError);
})();
// Make sure the result is coerced to boolean.
(function() {
var F = {};
F[Symbol.hasInstance] = function() { return undefined; };
assertEquals(0 instanceof F, false);
F[Symbol.hasInstance] = function() { return null; };
assertEquals(0 instanceof F, false);
F[Symbol.hasInstance] = function() { return true; };
assertEquals(0 instanceof F, true);
})();
// Make sure if @@hasInstance throws, we catch it.
(function() {
var F = {};
F[Symbol.hasInstance] = function() { throw new Error("always throws"); }
try {
0 instanceof F;
} catch (e) {
assertEquals(e.message, "always throws");
}
})();
// @@hasInstance works for bound functions.
(function() {
var BC = function() {};
var bc = new BC();
var bound = BC.bind();
assertEquals(bound[Symbol.hasInstance](bc), true);
assertEquals(bound[Symbol.hasInstance]([]), false);
})();
// if OrdinaryHasInstance is passed a non-callable receiver, return false.
assertEquals(Function.prototype[Symbol.hasInstance].call(Array, []), true);
assertEquals(Function.prototype[Symbol.hasInstance].call({}, {}), false);
// OrdinaryHasInstance passed a non-object argument returns false.
assertEquals(Function.prototype[Symbol.hasInstance].call(Array, 0), false);
......@@ -150,7 +150,7 @@ TypeError);
// kInstanceofFunctionExpected
test(function() {
1 instanceof 1;
}, "Expecting a function in instanceof check, but got 1", TypeError);
}, "Expecting an object in instanceof check", TypeError);
// kInstanceofNonobjectProto
test(function() {
......
......@@ -866,6 +866,9 @@
'regress/regress-2318': [FAIL],
'regress/regress-crbug-582051': [FAIL],
# TODO(rmcilroy, 4680): new ES6 instanceof support
'harmony/instanceof-es6': [SKIP],
# TODO(rmcilroy,4680): Test timeouts.
'array-literal-transitions': [SKIP],
'regress/regress-crbug-517592': [SKIP],
......
......@@ -192,14 +192,6 @@
# https://code.google.com/p/v8/issues/detail?id=4361
'intl402/Collator/10.1.1_a': [FAIL],
# https://code.google.com/p/v8/issues/detail?id=4447
'language/expressions/instanceof/prototype-getter-with-object-throws': [FAIL],
'language/expressions/instanceof/prototype-getter-with-object': [FAIL],
'language/expressions/instanceof/primitive-prototype-with-object': [FAIL],
'language/expressions/instanceof/symbol-hasinstance-get-err': [FAIL],
'language/expressions/instanceof/symbol-hasinstance-invocation': [FAIL],
'language/expressions/instanceof/symbol-hasinstance-to-boolean': [FAIL],
# https://code.google.com/p/v8/issues/detail?id=4476
'built-ins/String/prototype/toLocaleLowerCase/special_casing_conditional': [FAIL],
'built-ins/String/prototype/toLocaleLowerCase/supplementary_plane': [FAIL],
......@@ -536,6 +528,7 @@
'language/object-literal/concise-generator': [SKIP],
'language/statements/do-while/S12.6.1_A4_T5': [SKIP],
'language/statements/while/S12.6.2_A4_T5': [SKIP],
'language/expressions/instanceof/symbol-hasinstance-not-callable': [SKIP],
}], # ignition == True
......
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