Fix the receiver when calling parenthesized optional chains

Correctly passing the receiver depends on the Call AST node's type.
Calling a parenthesized optional chain expression is parsed as a Call of
an OptionalChain of a Property.  Currently the computation of the type
does not take optional chains of property loads into consideration, so
calls of parenthesized optional chain expressions always get passed an
undefined receiver.

Bug: v8:10024
Change-Id: I904b0eeca2df30160def674fb32adf821403aef9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1938571
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65252}
This commit is contained in:
Shu-yu Guo 2019-11-27 12:29:18 -08:00 committed by Commit Bot
parent f57ce0f4c8
commit 80fd0b3d8a
5 changed files with 70 additions and 7 deletions

View File

@ -835,15 +835,27 @@ Call::CallType Call::GetCallType() const {
if (expression()->IsSuperCallReference()) return SUPER_CALL;
Property* property = expression()->AsProperty();
bool is_optional_chain = false;
if (V8_UNLIKELY(property == nullptr && expression()->IsOptionalChain())) {
is_optional_chain = true;
property = expression()->AsOptionalChain()->expression()->AsProperty();
}
if (property != nullptr) {
if (property->IsPrivateReference()) {
return PRIVATE_CALL;
}
bool is_super = property->IsSuperAccess();
// `super?.` is not syntactically valid, so a property load cannot be both
// super and an optional chain.
DCHECK(!is_super || !is_optional_chain);
if (property->key()->IsPropertyName()) {
return is_super ? NAMED_SUPER_PROPERTY_CALL : NAMED_PROPERTY_CALL;
if (is_super) return NAMED_SUPER_PROPERTY_CALL;
if (is_optional_chain) return NAMED_OPTIONAL_CHAIN_PROPERTY_CALL;
return NAMED_PROPERTY_CALL;
} else {
return is_super ? KEYED_SUPER_PROPERTY_CALL : KEYED_PROPERTY_CALL;
if (is_super) return KEYED_SUPER_PROPERTY_CALL;
if (is_optional_chain) return KEYED_OPTIONAL_CHAIN_PROPERTY_CALL;
return KEYED_PROPERTY_CALL;
}
}

View File

@ -1717,13 +1717,23 @@ class Call final : public Expression {
WITH_CALL,
NAMED_PROPERTY_CALL,
KEYED_PROPERTY_CALL,
NAMED_OPTIONAL_CHAIN_PROPERTY_CALL,
KEYED_OPTIONAL_CHAIN_PROPERTY_CALL,
NAMED_SUPER_PROPERTY_CALL,
KEYED_SUPER_PROPERTY_CALL,
PRIVATE_CALL,
SUPER_CALL,
OTHER_CALL
OTHER_CALL,
FIRST_NON_SUPER_PROPERTY_CALL = NAMED_PROPERTY_CALL,
LAST_NON_SUPER_PROPERTY_CALL = KEYED_OPTIONAL_CHAIN_PROPERTY_CALL
};
static bool IsNonSuperPropertyCall(CallType call_type) {
return base::IsInRange(call_type, FIRST_NON_SUPER_PROPERTY_CALL,
LAST_NON_SUPER_PROPERTY_CALL);
}
enum PossiblyEval {
IS_POSSIBLY_EVAL,
NOT_EVAL,

View File

@ -4636,16 +4636,21 @@ void BytecodeGenerator::VisitKeyedSuperPropertyLoad(Property* property,
}
}
void BytecodeGenerator::VisitOptionalChain(OptionalChain* expr) {
template <typename ExpressionFunc>
void BytecodeGenerator::BuildOptionalChain(ExpressionFunc expression_func) {
BytecodeLabel done;
OptionalChainNullLabelScope label_scope(this);
VisitForAccumulatorValue(expr->expression());
expression_func();
builder()->Jump(&done);
label_scope.labels()->Bind(builder());
builder()->LoadUndefined();
builder()->Bind(&done);
}
void BytecodeGenerator::VisitOptionalChain(OptionalChain* expr) {
BuildOptionalChain([&]() { VisitForAccumulatorValue(expr->expression()); });
}
void BytecodeGenerator::VisitProperty(Property* expr) {
AssignType property_kind = Property::GetAssignType(expr);
if (property_kind != NAMED_SUPER_PROPERTY &&
@ -4763,6 +4768,16 @@ void BytecodeGenerator::VisitCall(Call* expr) {
builder()->StoreAccumulatorInRegister(callee);
break;
}
case Call::NAMED_OPTIONAL_CHAIN_PROPERTY_CALL:
case Call::KEYED_OPTIONAL_CHAIN_PROPERTY_CALL: {
OptionalChain* chain = callee_expr->AsOptionalChain();
Property* property = chain->expression()->AsProperty();
BuildOptionalChain([&]() {
VisitAndPushIntoRegisterList(property->obj(), &args);
VisitPropertyLoadForRegister(args.last_register(), property, callee);
});
break;
}
case Call::SUPER_CALL:
UNREACHABLE();
}
@ -4815,8 +4830,7 @@ void BytecodeGenerator::VisitCall(Call* expr) {
} else if (optimize_as_one_shot) {
DCHECK(!implicit_undefined_receiver);
builder()->CallNoFeedback(callee, args);
} else if (call_type == Call::NAMED_PROPERTY_CALL ||
call_type == Call::KEYED_PROPERTY_CALL) {
} else if (Call::IsNonSuperPropertyCall(call_type)) {
DCHECK(!implicit_undefined_receiver);
builder()->CallProperty(callee, args,
feedback_index(feedback_spec()->AddCallICSlot()));

View File

@ -379,6 +379,9 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
HandlerTable::CatchPrediction catch_prediction,
TryFinallyStatement* stmt_for_coverage = nullptr);
template <typename ExpressionFunc>
void BuildOptionalChain(ExpressionFunc expression_func);
// Visitors for obtaining expression result in the accumulator, in a
// register, or just getting the effect. Some visitors return a TypeHint which
// specifies the type of the result of the visited expression.

View File

@ -0,0 +1,24 @@
// Copyright 2019 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-optional-chaining
const o = { m() { return this; }, p: 42 };
const p = { o: o };
function id(x) { return x; }
assertEquals(o?.m().p, 42);
assertEquals((o?.m)().p, 42);
assertEquals(o?.m(), (o?.m)());
assertEquals(p?.o?.m().p, 42);
assertEquals((p?.o?.m)().p, 42);
assertEquals(p?.o?.m(), (p?.o?.m)());
assertEquals(o?.[id('m')]().p, 42);
assertEquals((o?.[id('m')])().p, 42);
assertEquals(o?.[id('m')](), (o?.[id('m')])());
assertEquals(p?.[id('o')]?.[id('m')]().p, 42);
assertEquals((p?.[id('o')]?.[id('m')])().p, 42);
assertEquals(p?.[id('o')]?.[id('m')](), (p?.[id('o')]?.[id('m')])());