Commit afd2f580 authored by Michael Starzinger's avatar Michael Starzinger Committed by Commit Bot

[turbofan] Fix new.target check in Reflect.construct.

This adds and explicit check for the constructability of the new.target
value in the lowering of {JSCall} nodes known to call Reflect.construct.
The {JSConstruct} operator does not perform this check and relies on the
implicit validity of new.target in all other use cases.

R=jarin@chromium.org
TEST=mjsunit/regress/regress-crbug-768080
BUG=chromium:768080

Change-Id: I7c1921e787bae64ba83de3eb08aa00fc5523e251
Reviewed-on: https://chromium-review.googlesource.com/718100Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Michael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48543}
parent 50f58ea6
......@@ -718,6 +718,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kObjectIsCallable:
result = LowerObjectIsCallable(node);
break;
case IrOpcode::kObjectIsConstructor:
result = LowerObjectIsConstructor(node);
break;
case IrOpcode::kObjectIsDetectableCallable:
result = LowerObjectIsDetectableCallable(node);
break;
......@@ -1931,6 +1934,31 @@ Node* EffectControlLinearizer::LowerObjectIsCallable(Node* node) {
return done.PhiAt(0);
}
Node* EffectControlLinearizer::LowerObjectIsConstructor(Node* node) {
Node* value = node->InputAt(0);
auto if_smi = __ MakeDeferredLabel();
auto done = __ MakeLabel(MachineRepresentation::kBit);
Node* check = ObjectIsSmi(value);
__ GotoIf(check, &if_smi);
Node* value_map = __ LoadField(AccessBuilder::ForMap(), value);
Node* value_bit_field =
__ LoadField(AccessBuilder::ForMapBitField(), value_map);
Node* vfalse =
__ Word32Equal(__ Int32Constant(1 << Map::kIsConstructor),
__ Word32And(value_bit_field,
__ Int32Constant(1 << Map::kIsConstructor)));
__ Goto(&done, vfalse);
__ Bind(&if_smi);
__ Goto(&done, __ Int32Constant(0));
__ Bind(&done);
return done.PhiAt(0);
}
Node* EffectControlLinearizer::LowerObjectIsDetectableCallable(Node* node) {
Node* value = node->InputAt(0);
......
......@@ -86,6 +86,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* LowerCheckedTruncateTaggedToWord32(Node* node, Node* frame_state);
Node* LowerObjectIsArrayBufferView(Node* node);
Node* LowerObjectIsCallable(Node* node);
Node* LowerObjectIsConstructor(Node* node);
Node* LowerObjectIsDetectableCallable(Node* node);
Node* LowerObjectIsMinusZero(Node* node);
Node* LowerObjectIsNaN(Node* node);
......
......@@ -1299,6 +1299,56 @@ Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
} else {
NodeProperties::ChangeOp(
node, javascript()->Construct(arity + 2, frequency, feedback));
Node* new_target = NodeProperties::GetValueInput(node, arity + 1);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* context = NodeProperties::GetContextInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Check whether the given new target value is a constructor function. The
// replacement {JSConstruct} operator only checks the passed target value
// but relies on the new target value to be implicitly valid.
Node* check =
graph()->NewNode(simplified()->ObjectIsConstructor(), new_target);
Node* check_branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
Node* check_fail = graph()->NewNode(common()->IfFalse(), check_branch);
Node* check_throw = check_fail =
graph()->NewNode(javascript()->CallRuntime(Runtime::kThrowTypeError, 2),
jsgraph()->Constant(MessageTemplate::kNotConstructor),
new_target, context, frame_state, effect, check_fail);
control = graph()->NewNode(common()->IfTrue(), check_branch);
NodeProperties::ReplaceControlInput(node, control);
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
// Create appropriate {IfException} and {IfSuccess} nodes.
Node* if_exception =
graph()->NewNode(common()->IfException(), check_throw, check_fail);
check_fail = graph()->NewNode(common()->IfSuccess(), check_fail);
// Join the exception edges.
Node* merge =
graph()->NewNode(common()->Merge(2), if_exception, on_exception);
Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception,
on_exception, merge);
Node* phi =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
if_exception, on_exception, merge);
ReplaceWithValue(on_exception, phi, ephi, merge);
merge->ReplaceInput(1, on_exception);
ephi->ReplaceInput(1, on_exception);
phi->ReplaceInput(1, on_exception);
}
// The above %ThrowTypeError runtime call is an unconditional throw, making
// it impossible to return a successful completion in this case. We simply
// connect the successful completion to the graph end.
Node* terminate =
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), terminate);
Reduction const reduction = ReduceJSConstruct(node);
return reduction.Changed() ? reduction : Changed(node);
}
......
......@@ -355,6 +355,7 @@
V(TransitionAndStoreElement) \
V(ObjectIsArrayBufferView) \
V(ObjectIsCallable) \
V(ObjectIsConstructor) \
V(ObjectIsDetectableCallable) \
V(ObjectIsMinusZero) \
V(ObjectIsNaN) \
......
......@@ -2683,6 +2683,11 @@ class RepresentationSelector {
VisitObjectIs(node, Type::Callable(), lowering);
return;
}
case IrOpcode::kObjectIsConstructor: {
// TODO(turbofan): Introduce a Type::Constructor?
VisitUnop(node, UseInfo::AnyTagged(), MachineRepresentation::kBit);
return;
}
case IrOpcode::kObjectIsDetectableCallable: {
VisitObjectIs(node, Type::DetectableCallable(), lowering);
return;
......
......@@ -506,6 +506,7 @@ DeoptimizeReason DeoptimizeReasonOf(const Operator* op) {
V(TruncateTaggedToFloat64, Operator::kNoProperties, 1, 0) \
V(ObjectIsArrayBufferView, Operator::kNoProperties, 1, 0) \
V(ObjectIsCallable, Operator::kNoProperties, 1, 0) \
V(ObjectIsConstructor, Operator::kNoProperties, 1, 0) \
V(ObjectIsDetectableCallable, Operator::kNoProperties, 1, 0) \
V(ObjectIsMinusZero, Operator::kNoProperties, 1, 0) \
V(ObjectIsNaN, Operator::kNoProperties, 1, 0) \
......
......@@ -440,6 +440,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* ObjectIsArrayBufferView();
const Operator* ObjectIsCallable();
const Operator* ObjectIsConstructor();
const Operator* ObjectIsDetectableCallable();
const Operator* ObjectIsMinusZero();
const Operator* ObjectIsNaN();
......
......@@ -289,6 +289,7 @@ class Typer::Visitor : public Reducer {
static Type* ObjectIsArrayBufferView(Type*, Typer*);
static Type* ObjectIsCallable(Type*, Typer*);
static Type* ObjectIsConstructor(Type*, Typer*);
static Type* ObjectIsDetectableCallable(Type*, Typer*);
static Type* ObjectIsMinusZero(Type*, Typer*);
static Type* ObjectIsNaN(Type*, Typer*);
......@@ -523,6 +524,12 @@ Type* Typer::Visitor::ObjectIsCallable(Type* type, Typer* t) {
return Type::Boolean();
}
Type* Typer::Visitor::ObjectIsConstructor(Type* type, Typer* t) {
// TODO(turbofan): Introduce a Type::Constructor?
if (!type->Maybe(Type::Callable())) return t->singleton_false_;
return Type::Boolean();
}
Type* Typer::Visitor::ObjectIsDetectableCallable(Type* type, Typer* t) {
if (type->Is(Type::DetectableCallable())) return t->singleton_true_;
if (!type->Maybe(Type::DetectableCallable())) return t->singleton_false_;
......@@ -1986,6 +1993,10 @@ Type* Typer::Visitor::TypeObjectIsCallable(Node* node) {
return TypeUnaryOp(node, ObjectIsCallable);
}
Type* Typer::Visitor::TypeObjectIsConstructor(Node* node) {
return TypeUnaryOp(node, ObjectIsConstructor);
}
Type* Typer::Visitor::TypeObjectIsDetectableCallable(Node* node) {
return TypeUnaryOp(node, ObjectIsDetectableCallable);
}
......
......@@ -1007,6 +1007,7 @@ void Verifier::Visitor::Check(Node* node) {
case IrOpcode::kObjectIsArrayBufferView:
case IrOpcode::kObjectIsCallable:
case IrOpcode::kObjectIsConstructor:
case IrOpcode::kObjectIsDetectableCallable:
case IrOpcode::kObjectIsMinusZero:
case IrOpcode::kObjectIsNaN:
......
// Copyright 2017 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: --allow-natives-syntax
(function TestReflectConstructBogusNewTarget1() {
class C {}
function g() {
Reflect.construct(C, arguments, 23);
}
function f() {
return new g();
}
assertThrows(f, TypeError);
assertThrows(f, TypeError);
%OptimizeFunctionOnNextCall(f);
assertThrows(f, TypeError);
})();
(function TestReflectConstructBogusNewTarget2() {
class C {}
// Note that {unescape} is an example of a non-constructable function. If that
// ever changes and this test needs to be adapted, make sure to choose another
// non-constructable {JSFunction} object instead.
function g() {
Reflect.construct(C, arguments, unescape);
}
function f() {
return new g();
}
assertThrows(f, TypeError);
assertThrows(f, TypeError);
%OptimizeFunctionOnNextCall(f);
assertThrows(f, TypeError);
})();
(function TestReflectConstructBogusTarget() {
function g() {
Reflect.construct(23, arguments);
}
function f() {
return new g();
}
assertThrows(f, TypeError);
assertThrows(f, TypeError);
%OptimizeFunctionOnNextCall(f);
assertThrows(f, TypeError);
})();
(function TestReflectApplyBogusTarget() {
function g() {
Reflect.apply(23, this, arguments);
}
function f() {
return g();
}
assertThrows(f, TypeError);
assertThrows(f, TypeError);
%OptimizeFunctionOnNextCall(f);
assertThrows(f, TypeError);
})();
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