Commit 1e5b2358 authored by Matt Gardner's avatar Matt Gardner Committed by Commit Bot

Check for "SuperNotCalled" on "delete this" in a constructor

V8 implements "delete this" as "LdaTrue", but an error needs to be thrown
if done in a constructor before calling super. ThrowIfHole checks the
accumulator, so we need to load 'this' into the accumulator. The check is
inserted by the load since it has HoleCheckMode::kRequired

Bug: https://bugs.chromium.org/p/v8/issues/detail?id=6711

Change-Id: I9f2ce4439505cec4327d88d1195898782edea721
Reviewed-on: https://chromium-review.googlesource.com/c/1419084Reviewed-by: 's avatarRoss McIlroy <rmcilroy@chromium.org>
Commit-Queue: Matt Gardner <magardn@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#59007}
parent fa43dd91
...@@ -4573,56 +4573,51 @@ void BytecodeGenerator::VisitUnaryOperation(UnaryOperation* expr) { ...@@ -4573,56 +4573,51 @@ void BytecodeGenerator::VisitUnaryOperation(UnaryOperation* expr) {
} }
} }
void BytecodeGenerator::VisitDelete(UnaryOperation* expr) { void BytecodeGenerator::VisitDelete(UnaryOperation* unary) {
if (expr->expression()->IsProperty()) { Expression* expr = unary->expression();
if (expr->IsProperty()) {
// Delete of an object property is allowed both in sloppy // Delete of an object property is allowed both in sloppy
// and strict modes. // and strict modes.
Property* property = expr->expression()->AsProperty(); Property* property = expr->AsProperty();
Register object = VisitForRegisterValue(property->obj()); Register object = VisitForRegisterValue(property->obj());
VisitForAccumulatorValue(property->key()); VisitForAccumulatorValue(property->key());
builder()->Delete(object, language_mode()); builder()->Delete(object, language_mode());
} else if (expr->expression()->IsVariableProxy()) { } else if (expr->IsVariableProxy() && !expr->AsVariableProxy()->is_this() &&
!expr->AsVariableProxy()->is_new_target()) {
// Delete of an unqualified identifier is allowed in sloppy mode but is // Delete of an unqualified identifier is allowed in sloppy mode but is
// not allowed in strict mode. Deleting 'this' and 'new.target' is allowed // not allowed in strict mode.
// in both modes. DCHECK(is_sloppy(language_mode()));
VariableProxy* proxy = expr->expression()->AsVariableProxy(); Variable* variable = expr->AsVariableProxy()->var();
DCHECK(is_sloppy(language_mode()) || proxy->is_this() || switch (variable->location()) {
proxy->is_new_target()); case VariableLocation::PARAMETER:
if (proxy->is_this() || proxy->is_new_target()) { case VariableLocation::LOCAL:
builder()->LoadTrue(); case VariableLocation::CONTEXT: {
} else { // Deleting local var/let/const, context variables, and arguments
Variable* variable = proxy->var(); // does not have any effect.
switch (variable->location()) { builder()->LoadFalse();
case VariableLocation::PARAMETER: break;
case VariableLocation::LOCAL: }
case VariableLocation::CONTEXT: { case VariableLocation::UNALLOCATED:
// Deleting local var/let/const, context variables, and arguments // TODO(adamk): Falling through to the runtime results in correct
// does not have any effect. // behavior, but does unnecessary context-walking (since scope
builder()->LoadFalse(); // analysis has already proven that the variable doesn't exist in
break; // any non-global scope). Consider adding a DeleteGlobal bytecode
} // that knows how to deal with ScriptContexts as well as global
case VariableLocation::UNALLOCATED: // object properties.
// TODO(adamk): Falling through to the runtime results in correct case VariableLocation::LOOKUP: {
// behavior, but does unnecessary context-walking (since scope Register name_reg = register_allocator()->NewRegister();
// analysis has already proven that the variable doesn't exist in builder()
// any non-global scope). Consider adding a DeleteGlobal bytecode ->LoadLiteral(variable->raw_name())
// that knows how to deal with ScriptContexts as well as global .StoreAccumulatorInRegister(name_reg)
// object properties. .CallRuntime(Runtime::kDeleteLookupSlot, name_reg);
case VariableLocation::LOOKUP: { break;
Register name_reg = register_allocator()->NewRegister();
builder()
->LoadLiteral(variable->raw_name())
.StoreAccumulatorInRegister(name_reg)
.CallRuntime(Runtime::kDeleteLookupSlot, name_reg);
break;
}
default:
UNREACHABLE();
} }
default:
UNREACHABLE();
} }
} else { } else {
// Delete of an unresolvable reference returns true. // Delete of an unresolvable reference, new.target, and this returns true.
VisitForEffect(expr->expression()); VisitForEffect(expr);
builder()->LoadTrue(); builder()->LoadTrue();
} }
} }
......
...@@ -137,3 +137,20 @@ constant pool: [ ...@@ -137,3 +137,20 @@ constant pool: [
handlers: [ handlers: [
] ]
---
snippet: "
return delete this;
"
frame size: 0
parameter count: 1
bytecode array length: 3
bytecodes: [
/* 30 E> */ B(StackCheck),
/* 34 S> */ B(LdaTrue),
/* 53 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
...@@ -1499,6 +1499,8 @@ TEST(Delete) { ...@@ -1499,6 +1499,8 @@ TEST(Delete) {
"return delete a[1];\n", "return delete a[1];\n",
"return delete 'test';\n", "return delete 'test';\n",
"return delete this;\n",
}; };
CHECK(CompareTexts(BuildActual(printer, snippets), CHECK(CompareTexts(BuildActual(printer, snippets),
......
// 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.
// ensure `delete this` throws before `super` is called.
assertThrows(()=>{
new class extends Object {
constructor() {
delete this;
super();
}
}
}, ReferenceError);
// ensure `delete this` doesn't throw after `super` is called.
new class extends Object {
constructor() {
super();
delete this;
}
}
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