[Interpreter] Add New, CallRuntime and CallJSRuntime support to BytecodeGraphBuilder.
Adds support for the New, CallRuntime and CallJSRuntime bytecodes in BytecodeGraphBuilder. Also adds BuildLoadObjectField, BuildLoadGlobalObject and BuildLoadNativeContextField helpers. Landed on behalf of rmcilroy. BUG=v8:4280 LOG=N Review URL: https://codereview.chromium.org/1456483002 Cr-Commit-Position: refs/heads/master@{#32136}
This commit is contained in:
parent
1e03334e76
commit
8cfa73ac38
@ -134,6 +134,12 @@ Node* BytecodeGraphBuilder::GetFunctionClosure() {
|
||||
}
|
||||
|
||||
|
||||
Node* BytecodeGraphBuilder::BuildLoadObjectField(Node* object, int offset) {
|
||||
return NewNode(jsgraph()->machine()->Load(kMachAnyTagged), object,
|
||||
jsgraph()->IntPtrConstant(offset - kHeapObjectTag));
|
||||
}
|
||||
|
||||
|
||||
Node* BytecodeGraphBuilder::BuildLoadImmutableObjectField(Node* object,
|
||||
int offset) {
|
||||
return graph()->NewNode(jsgraph()->machine()->Load(kMachAnyTagged), object,
|
||||
@ -142,6 +148,21 @@ Node* BytecodeGraphBuilder::BuildLoadImmutableObjectField(Node* object,
|
||||
}
|
||||
|
||||
|
||||
Node* BytecodeGraphBuilder::BuildLoadGlobalObject() {
|
||||
const Operator* load_op =
|
||||
javascript()->LoadContext(0, Context::GLOBAL_OBJECT_INDEX, true);
|
||||
return NewNode(load_op, GetFunctionContext());
|
||||
}
|
||||
|
||||
|
||||
Node* BytecodeGraphBuilder::BuildLoadNativeContextField(int index) {
|
||||
Node* global = BuildLoadGlobalObject();
|
||||
Node* native_context =
|
||||
BuildLoadObjectField(global, JSGlobalObject::kNativeContextOffset);
|
||||
return NewNode(javascript()->LoadContext(0, index, true), native_context);
|
||||
}
|
||||
|
||||
|
||||
Node* BytecodeGraphBuilder::BuildLoadFeedbackVector() {
|
||||
if (!feedback_vector_.is_set()) {
|
||||
Node* closure = GetFunctionClosure();
|
||||
@ -650,11 +671,11 @@ void BytecodeGraphBuilder::VisitCreateObjectLiteral(
|
||||
|
||||
|
||||
Node* BytecodeGraphBuilder::ProcessCallArguments(const Operator* call_op,
|
||||
interpreter::Register callee,
|
||||
Node* callee,
|
||||
interpreter::Register receiver,
|
||||
size_t arity) {
|
||||
Node** all = info()->zone()->NewArray<Node*>(arity);
|
||||
all[0] = environment()->LookupRegister(callee);
|
||||
Node** all = info()->zone()->NewArray<Node*>(static_cast<int>(arity));
|
||||
all[0] = callee;
|
||||
all[1] = environment()->LookupRegister(receiver);
|
||||
int receiver_index = receiver.index();
|
||||
for (int i = 2; i < static_cast<int>(arity); ++i) {
|
||||
@ -672,7 +693,7 @@ void BytecodeGraphBuilder::BuildCall(
|
||||
// register has been loaded with null / undefined explicitly or we are sure it
|
||||
// is not null / undefined.
|
||||
ConvertReceiverMode receiver_hint = ConvertReceiverMode::kAny;
|
||||
interpreter::Register callee = iterator.GetRegisterOperand(0);
|
||||
Node* callee = environment()->LookupRegister(iterator.GetRegisterOperand(0));
|
||||
interpreter::Register receiver = iterator.GetRegisterOperand(1);
|
||||
size_t arg_count = iterator.GetCountOperand(2);
|
||||
VectorSlotPair feedback = CreateVectorSlotPair(iterator.GetIndexOperand(3));
|
||||
@ -697,21 +718,78 @@ void BytecodeGraphBuilder::VisitCallWide(
|
||||
}
|
||||
|
||||
|
||||
void BytecodeGraphBuilder::VisitCallRuntime(
|
||||
void BytecodeGraphBuilder::VisitCallJSRuntime(
|
||||
const interpreter::BytecodeArrayIterator& iterator) {
|
||||
UNIMPLEMENTED();
|
||||
Node* callee = BuildLoadNativeContextField(iterator.GetIndexOperand(0));
|
||||
interpreter::Register receiver = iterator.GetRegisterOperand(1);
|
||||
size_t arg_count = iterator.GetCountOperand(2);
|
||||
|
||||
// Create node to perform the JS runtime call.
|
||||
const Operator* call =
|
||||
javascript()->CallFunction(arg_count + 2, language_mode());
|
||||
Node* value = ProcessCallArguments(call, callee, receiver, arg_count + 2);
|
||||
AddEmptyFrameStateInputs(value);
|
||||
environment()->BindAccumulator(value);
|
||||
}
|
||||
|
||||
|
||||
void BytecodeGraphBuilder::VisitCallJSRuntime(
|
||||
Node* BytecodeGraphBuilder::ProcessCallRuntimeArguments(
|
||||
const Operator* call_runtime_op, interpreter::Register first_arg,
|
||||
size_t arity) {
|
||||
Node** all = info()->zone()->NewArray<Node*>(arity);
|
||||
int first_arg_index = first_arg.index();
|
||||
for (int i = 0; i < static_cast<int>(arity); ++i) {
|
||||
all[i] = environment()->LookupRegister(
|
||||
interpreter::Register(first_arg_index + i));
|
||||
}
|
||||
Node* value = MakeNode(call_runtime_op, static_cast<int>(arity), all, false);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
void BytecodeGraphBuilder::VisitCallRuntime(
|
||||
const interpreter::BytecodeArrayIterator& iterator) {
|
||||
UNIMPLEMENTED();
|
||||
Runtime::FunctionId functionId =
|
||||
static_cast<Runtime::FunctionId>(iterator.GetIndexOperand(0));
|
||||
interpreter::Register first_arg = iterator.GetRegisterOperand(1);
|
||||
size_t arg_count = iterator.GetCountOperand(2);
|
||||
|
||||
// Create node to perform the runtime call.
|
||||
const Operator* call = javascript()->CallRuntime(functionId, arg_count);
|
||||
Node* value = ProcessCallRuntimeArguments(call, first_arg, arg_count);
|
||||
AddEmptyFrameStateInputs(value);
|
||||
environment()->BindAccumulator(value);
|
||||
}
|
||||
|
||||
|
||||
Node* BytecodeGraphBuilder::ProcessCallNewArguments(
|
||||
const Operator* call_new_op, interpreter::Register callee,
|
||||
interpreter::Register first_arg, size_t arity) {
|
||||
Node** all = info()->zone()->NewArray<Node*>(arity);
|
||||
all[0] = environment()->LookupRegister(callee);
|
||||
int first_arg_index = first_arg.index();
|
||||
for (int i = 1; i < static_cast<int>(arity) - 1; ++i) {
|
||||
all[i] = environment()->LookupRegister(
|
||||
interpreter::Register(first_arg_index + i - 1));
|
||||
}
|
||||
// Original constructor is the same as the callee.
|
||||
all[arity - 1] = environment()->LookupRegister(callee);
|
||||
Node* value = MakeNode(call_new_op, static_cast<int>(arity), all, false);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
void BytecodeGraphBuilder::VisitNew(
|
||||
const interpreter::BytecodeArrayIterator& iterator) {
|
||||
UNIMPLEMENTED();
|
||||
interpreter::Register callee = iterator.GetRegisterOperand(0);
|
||||
interpreter::Register first_arg = iterator.GetRegisterOperand(1);
|
||||
size_t arg_count = iterator.GetCountOperand(2);
|
||||
|
||||
const Operator* call =
|
||||
javascript()->CallConstruct(static_cast<int>(arg_count) + 2);
|
||||
Node* value = ProcessCallNewArguments(call, callee, first_arg, arg_count + 2);
|
||||
AddEmptyFrameStateInputs(value);
|
||||
environment()->BindAccumulator(value);
|
||||
}
|
||||
|
||||
|
||||
|
@ -40,12 +40,19 @@ class BytecodeGraphBuilder {
|
||||
// Get or create the node that represents the outer function context.
|
||||
Node* GetFunctionContext();
|
||||
|
||||
// Builders for accessing an immutable object field.
|
||||
// Builder for accessing a (potentially immutable) object field.
|
||||
Node* BuildLoadObjectField(Node* object, int offset);
|
||||
Node* BuildLoadImmutableObjectField(Node* object, int offset);
|
||||
|
||||
// Builder for accessing type feedback vector.
|
||||
Node* BuildLoadFeedbackVector();
|
||||
|
||||
// Builder for loading the global object.
|
||||
Node* BuildLoadGlobalObject();
|
||||
|
||||
// Builder for loading the a native context field.
|
||||
Node* BuildLoadNativeContextField(int index);
|
||||
|
||||
// Helper function for creating a pair containing type feedback vector and
|
||||
// a feedback slot.
|
||||
VectorSlotPair CreateVectorSlotPair(int slot_id);
|
||||
@ -91,9 +98,14 @@ class BytecodeGraphBuilder {
|
||||
|
||||
void UpdateControlDependencyToLeaveFunction(Node* exit);
|
||||
|
||||
Node* ProcessCallArguments(const Operator* call_op,
|
||||
interpreter::Register callee,
|
||||
Node* ProcessCallArguments(const Operator* call_op, Node* callee,
|
||||
interpreter::Register receiver, size_t arity);
|
||||
Node* ProcessCallNewArguments(const Operator* call_new_op,
|
||||
interpreter::Register callee,
|
||||
interpreter::Register first_arg, size_t arity);
|
||||
Node* ProcessCallRuntimeArguments(const Operator* call_runtime_op,
|
||||
interpreter::Register first_arg,
|
||||
size_t arity);
|
||||
|
||||
void BuildLoadGlobal(const interpreter::BytecodeArrayIterator& iterator,
|
||||
TypeofMode typeof_mode);
|
||||
|
@ -953,7 +953,7 @@ void Interpreter::DoCallJSRuntime(compiler::InterpreterAssembler* assembler) {
|
||||
}
|
||||
|
||||
|
||||
// New <constructor> <arg_count>
|
||||
// New <constructor> <first_arg> <arg_count>
|
||||
//
|
||||
// Call operator new with |constructor| and the first argument in
|
||||
// register |first_arg| and |arg_count| arguments in subsequent
|
||||
|
@ -64,6 +64,7 @@ class BytecodeGraphTester {
|
||||
: isolate_(isolate), zone_(zone), script_(script) {
|
||||
i::FLAG_ignition = true;
|
||||
i::FLAG_always_opt = false;
|
||||
i::FLAG_allow_natives_syntax = true;
|
||||
// Set ignition filter flag via SetFlagsFromString to avoid double-free
|
||||
// (or potential leak with StrDup() based on ownership confusion).
|
||||
ScopedVector<char> ignition_filter(64);
|
||||
@ -535,6 +536,74 @@ TEST(BytecodeGraphBuilderPropertyCall) {
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderCallNew) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
Zone* zone = scope.main_zone();
|
||||
Factory* factory = isolate->factory();
|
||||
|
||||
ExpectedSnippet<0> snippets[] = {
|
||||
{"function counter() { this.count = 20; }\n"
|
||||
"function f() {\n"
|
||||
" var c = new counter();\n"
|
||||
" return c.count;\n"
|
||||
"}; f()",
|
||||
{factory->NewNumberFromInt(20)}},
|
||||
{"function counter(arg0) { this.count = 17; this.x = arg0; }\n"
|
||||
"function f() {\n"
|
||||
" var c = new counter(6);\n"
|
||||
" return c.count + c.x;\n"
|
||||
"}; f()",
|
||||
{factory->NewNumberFromInt(23)}},
|
||||
{"function counter(arg0, arg1) {\n"
|
||||
" this.count = 17; this.x = arg0; this.y = arg1;\n"
|
||||
"}\n"
|
||||
"function f() {\n"
|
||||
" var c = new counter(3, 5);\n"
|
||||
" return c.count + c.x + c.y;\n"
|
||||
"}; f()",
|
||||
{factory->NewNumberFromInt(25)}},
|
||||
};
|
||||
|
||||
size_t num_snippets = sizeof(snippets) / sizeof(snippets[0]);
|
||||
for (size_t i = 0; i < num_snippets; i++) {
|
||||
BytecodeGraphTester tester(isolate, zone, snippets[i].code_snippet);
|
||||
auto callable = tester.GetCallable<>();
|
||||
Handle<Object> return_value = callable().ToHandleChecked();
|
||||
CHECK(return_value->SameValue(*snippets[i].return_value()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderCallRuntime) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
Zone* zone = scope.main_zone();
|
||||
Factory* factory = isolate->factory();
|
||||
|
||||
ExpectedSnippet<1> snippets[] = {
|
||||
{"function f(arg0) { return %MaxSmi(); }\nf()",
|
||||
{factory->NewNumberFromInt(Smi::kMaxValue), factory->undefined_value()}},
|
||||
{"function f(arg0) { return %IsArray(arg0) }\nf(undefined)",
|
||||
{factory->true_value(), BytecodeGraphTester::NewObject("[1, 2, 3]")}},
|
||||
{"function f(arg0) { return %Add(arg0, 2) }\nf(1)",
|
||||
{factory->NewNumberFromInt(5), factory->NewNumberFromInt(3)}},
|
||||
{"function f(arg0) { return %spread_arguments(arg0).length }\nf([])",
|
||||
{factory->NewNumberFromInt(3),
|
||||
BytecodeGraphTester::NewObject("[1, 2, 3]")}},
|
||||
};
|
||||
|
||||
size_t num_snippets = sizeof(snippets) / sizeof(snippets[0]);
|
||||
for (size_t i = 0; i < num_snippets; i++) {
|
||||
BytecodeGraphTester tester(isolate, zone, snippets[i].code_snippet);
|
||||
auto callable = tester.GetCallable<Handle<Object>>();
|
||||
Handle<Object> return_value =
|
||||
callable(snippets[i].parameter(0)).ToHandleChecked();
|
||||
CHECK(return_value->SameValue(*snippets[i].return_value()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(BytecodeGraphBuilderGlobals) {
|
||||
HandleAndZoneScope scope;
|
||||
Isolate* isolate = scope.main_isolate();
|
||||
|
@ -400,7 +400,6 @@ TEST(LogicalExpressions) {
|
||||
InitializedHandleScope handle_scope;
|
||||
BytecodeGeneratorHelper helper;
|
||||
|
||||
|
||||
ExpectedSnippet<int> snippets[] = {
|
||||
{"var x = 0; return x || 3;",
|
||||
1 * kPointerSize,
|
||||
|
@ -721,6 +721,92 @@ TEST_F(BytecodeGraphBuilderTest, KeyedStore) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_F(BytecodeGraphBuilderTest, CallRuntime) {
|
||||
interpreter::BytecodeArrayBuilder array_builder(isolate(), zone());
|
||||
array_builder.set_locals_count(2);
|
||||
array_builder.set_context_count(0);
|
||||
array_builder.set_parameter_count(3);
|
||||
|
||||
array_builder.LoadAccumulatorWithRegister(array_builder.Parameter(1))
|
||||
.StoreAccumulatorInRegister(interpreter::Register(0))
|
||||
.LoadAccumulatorWithRegister(array_builder.Parameter(2))
|
||||
.StoreAccumulatorInRegister(interpreter::Register(1))
|
||||
.CallRuntime(Runtime::kAdd, interpreter::Register(0), 2)
|
||||
.Return();
|
||||
|
||||
Graph* graph = GetCompletedGraph(array_builder.ToBytecodeArray());
|
||||
Node* start = graph->start();
|
||||
Node* ret = graph->end()->InputAt(0);
|
||||
std::vector<Matcher<Node*>> call_inputs;
|
||||
call_inputs.push_back(IsParameter(1));
|
||||
call_inputs.push_back(IsParameter(2));
|
||||
Matcher<Node*> call_js_runtime = IsJSCallRuntime(call_inputs, start, start);
|
||||
|
||||
EXPECT_THAT(ret, IsReturn(call_js_runtime, call_js_runtime, IsIfSuccess(_)));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(BytecodeGraphBuilderTest, CallJSRuntime) {
|
||||
interpreter::BytecodeArrayBuilder array_builder(isolate(), zone());
|
||||
array_builder.set_locals_count(1);
|
||||
array_builder.set_context_count(1);
|
||||
array_builder.set_parameter_count(2);
|
||||
|
||||
// function f(arg) { return %spread_arguments(arg0); }
|
||||
interpreter::Register reg0 = interpreter::Register(0);
|
||||
array_builder.LoadAccumulatorWithRegister(array_builder.Parameter(1))
|
||||
.StoreAccumulatorInRegister(reg0)
|
||||
.CallJSRuntime(Context::SPREAD_ARGUMENTS_INDEX, reg0, 1)
|
||||
.Return();
|
||||
|
||||
Graph* graph = GetCompletedGraph(array_builder.ToBytecodeArray());
|
||||
Node* ret = graph->end()->InputAt(0);
|
||||
Matcher<Node*> load_context =
|
||||
IsLoadContext(ContextAccess(0, Context::SPREAD_ARGUMENTS_INDEX, true), _);
|
||||
std::vector<Matcher<Node*>> call_inputs;
|
||||
call_inputs.push_back(load_context);
|
||||
call_inputs.push_back(IsParameter(1));
|
||||
Matcher<Node*> call_js_function =
|
||||
IsJSCallFunction(call_inputs, load_context, graph->start());
|
||||
|
||||
EXPECT_THAT(ret,
|
||||
IsReturn(call_js_function, call_js_function, IsIfSuccess(_)));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(BytecodeGraphBuilderTest, New) {
|
||||
interpreter::BytecodeArrayBuilder array_builder(isolate(), zone());
|
||||
array_builder.set_locals_count(4);
|
||||
array_builder.set_context_count(0);
|
||||
array_builder.set_parameter_count(5);
|
||||
|
||||
array_builder.LoadAccumulatorWithRegister(array_builder.Parameter(1))
|
||||
.StoreAccumulatorInRegister(interpreter::Register(0))
|
||||
.LoadAccumulatorWithRegister(array_builder.Parameter(2))
|
||||
.StoreAccumulatorInRegister(interpreter::Register(1))
|
||||
.LoadAccumulatorWithRegister(array_builder.Parameter(3))
|
||||
.StoreAccumulatorInRegister(interpreter::Register(2))
|
||||
.LoadAccumulatorWithRegister(array_builder.Parameter(4))
|
||||
.StoreAccumulatorInRegister(interpreter::Register(3))
|
||||
.New(interpreter::Register(0), interpreter::Register(1), 3)
|
||||
.Return();
|
||||
auto bytecode_array = array_builder.ToBytecodeArray();
|
||||
Graph* graph = GetCompletedGraph(bytecode_array);
|
||||
|
||||
Node* start = graph->start();
|
||||
Node* ret = graph->end()->InputAt(0);
|
||||
std::vector<Matcher<Node*>> construct_inputs;
|
||||
construct_inputs.push_back(IsParameter(1));
|
||||
construct_inputs.push_back(IsParameter(2));
|
||||
construct_inputs.push_back(IsParameter(3));
|
||||
construct_inputs.push_back(IsParameter(4));
|
||||
construct_inputs.push_back(IsParameter(1));
|
||||
Matcher<Node*> call_construct =
|
||||
IsJSCallConstruct(construct_inputs, start, start);
|
||||
EXPECT_THAT(ret, IsReturn(call_construct, call_construct, IsIfSuccess(_)));
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -1805,12 +1805,13 @@ class IsJSStorePropertyMatcher final : public NodeMatcher {
|
||||
};
|
||||
|
||||
|
||||
class IsJSCallFunctionMatcher final : public NodeMatcher {
|
||||
class IsJSCallMatcher final : public NodeMatcher {
|
||||
public:
|
||||
IsJSCallFunctionMatcher(const std::vector<Matcher<Node*>>& value_matchers,
|
||||
const Matcher<Node*>& effect_matcher,
|
||||
const Matcher<Node*>& control_matcher)
|
||||
: NodeMatcher(IrOpcode::kJSCallFunction),
|
||||
IsJSCallMatcher(IrOpcode::Value op_code,
|
||||
const std::vector<Matcher<Node*>>& value_matchers,
|
||||
const Matcher<Node*>& effect_matcher,
|
||||
const Matcher<Node*>& control_matcher)
|
||||
: NodeMatcher(op_code),
|
||||
value_matchers_(value_matchers),
|
||||
effect_matcher_(effect_matcher),
|
||||
control_matcher_(control_matcher) {}
|
||||
@ -2472,11 +2473,21 @@ Matcher<Node*> IsJSStoreGlobal(const Handle<Name> name,
|
||||
}
|
||||
|
||||
|
||||
Matcher<Node*> IsJSCallConstruct(std::vector<Matcher<Node*>> value_matchers,
|
||||
const Matcher<Node*>& effect_matcher,
|
||||
const Matcher<Node*>& control_matcher) {
|
||||
return MakeMatcher(new IsJSCallMatcher(IrOpcode::kJSCallConstruct,
|
||||
value_matchers, effect_matcher,
|
||||
control_matcher));
|
||||
}
|
||||
|
||||
|
||||
Matcher<Node*> IsJSCallFunction(std::vector<Matcher<Node*>> value_matchers,
|
||||
const Matcher<Node*>& effect_matcher,
|
||||
const Matcher<Node*>& control_matcher) {
|
||||
return MakeMatcher(new IsJSCallFunctionMatcher(value_matchers, effect_matcher,
|
||||
control_matcher));
|
||||
return MakeMatcher(new IsJSCallMatcher(IrOpcode::kJSCallFunction,
|
||||
value_matchers, effect_matcher,
|
||||
control_matcher));
|
||||
}
|
||||
|
||||
|
||||
@ -2514,6 +2525,16 @@ Matcher<Node*> IsJSStoreProperty(const Matcher<Node*>& object_matcher,
|
||||
effect_matcher, control_matcher));
|
||||
}
|
||||
|
||||
|
||||
Matcher<Node*> IsJSCallRuntime(std::vector<Matcher<Node*>> value_matchers,
|
||||
const Matcher<Node*>& effect_matcher,
|
||||
const Matcher<Node*>& control_matcher) {
|
||||
return MakeMatcher(new IsJSCallMatcher(IrOpcode::kJSCallRuntime,
|
||||
value_matchers, effect_matcher,
|
||||
control_matcher));
|
||||
}
|
||||
|
||||
|
||||
#define IS_BINOP_MATCHER(Name) \
|
||||
Matcher<Node*> Is##Name(const Matcher<Node*>& lhs_matcher, \
|
||||
const Matcher<Node*>& rhs_matcher) { \
|
||||
|
@ -390,9 +390,15 @@ Matcher<Node*> IsJSStoreProperty(const Matcher<Node*>& object_matcher,
|
||||
const Matcher<Node*>& feedback_vector_matcher,
|
||||
const Matcher<Node*>& effect_matcher,
|
||||
const Matcher<Node*>& control_matcher);
|
||||
Matcher<Node*> IsJSCallConstruct(std::vector<Matcher<Node*>> value_matchers,
|
||||
const Matcher<Node*>& effect_matcher,
|
||||
const Matcher<Node*>& control_matcher);
|
||||
Matcher<Node*> IsJSCallFunction(std::vector<Matcher<Node*>> value_matchers,
|
||||
const Matcher<Node*>& effect_matcher,
|
||||
const Matcher<Node*>& control_matcher);
|
||||
Matcher<Node*> IsJSCallRuntime(std::vector<Matcher<Node*>> value_matchers,
|
||||
const Matcher<Node*>& effect_matcher,
|
||||
const Matcher<Node*>& control_matcher);
|
||||
Matcher<Node*> IsJSUnaryNot(const Matcher<Node*>& value_matcher);
|
||||
Matcher<Node*> IsJSTypeOf(const Matcher<Node*>& value_matcher);
|
||||
Matcher<Node*> IsJSDeleteProperty(const Matcher<Node*>& object_value_matcher,
|
||||
|
Loading…
Reference in New Issue
Block a user