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, ...@@ -718,6 +718,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kObjectIsCallable: case IrOpcode::kObjectIsCallable:
result = LowerObjectIsCallable(node); result = LowerObjectIsCallable(node);
break; break;
case IrOpcode::kObjectIsConstructor:
result = LowerObjectIsConstructor(node);
break;
case IrOpcode::kObjectIsDetectableCallable: case IrOpcode::kObjectIsDetectableCallable:
result = LowerObjectIsDetectableCallable(node); result = LowerObjectIsDetectableCallable(node);
break; break;
...@@ -1931,6 +1934,31 @@ Node* EffectControlLinearizer::LowerObjectIsCallable(Node* node) { ...@@ -1931,6 +1934,31 @@ Node* EffectControlLinearizer::LowerObjectIsCallable(Node* node) {
return done.PhiAt(0); 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* EffectControlLinearizer::LowerObjectIsDetectableCallable(Node* node) {
Node* value = node->InputAt(0); Node* value = node->InputAt(0);
......
...@@ -86,6 +86,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer { ...@@ -86,6 +86,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* LowerCheckedTruncateTaggedToWord32(Node* node, Node* frame_state); Node* LowerCheckedTruncateTaggedToWord32(Node* node, Node* frame_state);
Node* LowerObjectIsArrayBufferView(Node* node); Node* LowerObjectIsArrayBufferView(Node* node);
Node* LowerObjectIsCallable(Node* node); Node* LowerObjectIsCallable(Node* node);
Node* LowerObjectIsConstructor(Node* node);
Node* LowerObjectIsDetectableCallable(Node* node); Node* LowerObjectIsDetectableCallable(Node* node);
Node* LowerObjectIsMinusZero(Node* node); Node* LowerObjectIsMinusZero(Node* node);
Node* LowerObjectIsNaN(Node* node); Node* LowerObjectIsNaN(Node* node);
......
...@@ -1299,6 +1299,56 @@ Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread( ...@@ -1299,6 +1299,56 @@ Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
} else { } else {
NodeProperties::ChangeOp( NodeProperties::ChangeOp(
node, javascript()->Construct(arity + 2, frequency, feedback)); 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); Reduction const reduction = ReduceJSConstruct(node);
return reduction.Changed() ? reduction : Changed(node); return reduction.Changed() ? reduction : Changed(node);
} }
......
...@@ -355,6 +355,7 @@ ...@@ -355,6 +355,7 @@
V(TransitionAndStoreElement) \ V(TransitionAndStoreElement) \
V(ObjectIsArrayBufferView) \ V(ObjectIsArrayBufferView) \
V(ObjectIsCallable) \ V(ObjectIsCallable) \
V(ObjectIsConstructor) \
V(ObjectIsDetectableCallable) \ V(ObjectIsDetectableCallable) \
V(ObjectIsMinusZero) \ V(ObjectIsMinusZero) \
V(ObjectIsNaN) \ V(ObjectIsNaN) \
......
...@@ -2683,6 +2683,11 @@ class RepresentationSelector { ...@@ -2683,6 +2683,11 @@ class RepresentationSelector {
VisitObjectIs(node, Type::Callable(), lowering); VisitObjectIs(node, Type::Callable(), lowering);
return; return;
} }
case IrOpcode::kObjectIsConstructor: {
// TODO(turbofan): Introduce a Type::Constructor?
VisitUnop(node, UseInfo::AnyTagged(), MachineRepresentation::kBit);
return;
}
case IrOpcode::kObjectIsDetectableCallable: { case IrOpcode::kObjectIsDetectableCallable: {
VisitObjectIs(node, Type::DetectableCallable(), lowering); VisitObjectIs(node, Type::DetectableCallable(), lowering);
return; return;
......
...@@ -506,6 +506,7 @@ DeoptimizeReason DeoptimizeReasonOf(const Operator* op) { ...@@ -506,6 +506,7 @@ DeoptimizeReason DeoptimizeReasonOf(const Operator* op) {
V(TruncateTaggedToFloat64, Operator::kNoProperties, 1, 0) \ V(TruncateTaggedToFloat64, Operator::kNoProperties, 1, 0) \
V(ObjectIsArrayBufferView, Operator::kNoProperties, 1, 0) \ V(ObjectIsArrayBufferView, Operator::kNoProperties, 1, 0) \
V(ObjectIsCallable, Operator::kNoProperties, 1, 0) \ V(ObjectIsCallable, Operator::kNoProperties, 1, 0) \
V(ObjectIsConstructor, Operator::kNoProperties, 1, 0) \
V(ObjectIsDetectableCallable, Operator::kNoProperties, 1, 0) \ V(ObjectIsDetectableCallable, Operator::kNoProperties, 1, 0) \
V(ObjectIsMinusZero, Operator::kNoProperties, 1, 0) \ V(ObjectIsMinusZero, Operator::kNoProperties, 1, 0) \
V(ObjectIsNaN, Operator::kNoProperties, 1, 0) \ V(ObjectIsNaN, Operator::kNoProperties, 1, 0) \
......
...@@ -440,6 +440,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final ...@@ -440,6 +440,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* ObjectIsArrayBufferView(); const Operator* ObjectIsArrayBufferView();
const Operator* ObjectIsCallable(); const Operator* ObjectIsCallable();
const Operator* ObjectIsConstructor();
const Operator* ObjectIsDetectableCallable(); const Operator* ObjectIsDetectableCallable();
const Operator* ObjectIsMinusZero(); const Operator* ObjectIsMinusZero();
const Operator* ObjectIsNaN(); const Operator* ObjectIsNaN();
......
...@@ -289,6 +289,7 @@ class Typer::Visitor : public Reducer { ...@@ -289,6 +289,7 @@ class Typer::Visitor : public Reducer {
static Type* ObjectIsArrayBufferView(Type*, Typer*); static Type* ObjectIsArrayBufferView(Type*, Typer*);
static Type* ObjectIsCallable(Type*, Typer*); static Type* ObjectIsCallable(Type*, Typer*);
static Type* ObjectIsConstructor(Type*, Typer*);
static Type* ObjectIsDetectableCallable(Type*, Typer*); static Type* ObjectIsDetectableCallable(Type*, Typer*);
static Type* ObjectIsMinusZero(Type*, Typer*); static Type* ObjectIsMinusZero(Type*, Typer*);
static Type* ObjectIsNaN(Type*, Typer*); static Type* ObjectIsNaN(Type*, Typer*);
...@@ -523,6 +524,12 @@ Type* Typer::Visitor::ObjectIsCallable(Type* type, Typer* t) { ...@@ -523,6 +524,12 @@ Type* Typer::Visitor::ObjectIsCallable(Type* type, Typer* t) {
return Type::Boolean(); 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) { Type* Typer::Visitor::ObjectIsDetectableCallable(Type* type, Typer* t) {
if (type->Is(Type::DetectableCallable())) return t->singleton_true_; if (type->Is(Type::DetectableCallable())) return t->singleton_true_;
if (!type->Maybe(Type::DetectableCallable())) return t->singleton_false_; if (!type->Maybe(Type::DetectableCallable())) return t->singleton_false_;
...@@ -1986,6 +1993,10 @@ Type* Typer::Visitor::TypeObjectIsCallable(Node* node) { ...@@ -1986,6 +1993,10 @@ Type* Typer::Visitor::TypeObjectIsCallable(Node* node) {
return TypeUnaryOp(node, ObjectIsCallable); return TypeUnaryOp(node, ObjectIsCallable);
} }
Type* Typer::Visitor::TypeObjectIsConstructor(Node* node) {
return TypeUnaryOp(node, ObjectIsConstructor);
}
Type* Typer::Visitor::TypeObjectIsDetectableCallable(Node* node) { Type* Typer::Visitor::TypeObjectIsDetectableCallable(Node* node) {
return TypeUnaryOp(node, ObjectIsDetectableCallable); return TypeUnaryOp(node, ObjectIsDetectableCallable);
} }
......
...@@ -1007,6 +1007,7 @@ void Verifier::Visitor::Check(Node* node) { ...@@ -1007,6 +1007,7 @@ void Verifier::Visitor::Check(Node* node) {
case IrOpcode::kObjectIsArrayBufferView: case IrOpcode::kObjectIsArrayBufferView:
case IrOpcode::kObjectIsCallable: case IrOpcode::kObjectIsCallable:
case IrOpcode::kObjectIsConstructor:
case IrOpcode::kObjectIsDetectableCallable: case IrOpcode::kObjectIsDetectableCallable:
case IrOpcode::kObjectIsMinusZero: case IrOpcode::kObjectIsMinusZero:
case IrOpcode::kObjectIsNaN: 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