summaryrefslogtreecommitdiff
path: root/deps/v8/test/cctest/wasm/test-wasm-breakpoints.cc
diff options
context:
space:
mode:
Diffstat (limited to 'deps/v8/test/cctest/wasm/test-wasm-breakpoints.cc')
-rw-r--r--deps/v8/test/cctest/wasm/test-wasm-breakpoints.cc221
1 files changed, 220 insertions, 1 deletions
diff --git a/deps/v8/test/cctest/wasm/test-wasm-breakpoints.cc b/deps/v8/test/cctest/wasm/test-wasm-breakpoints.cc
index d2374a44c0..78e949b085 100644
--- a/deps/v8/test/cctest/wasm/test-wasm-breakpoints.cc
+++ b/deps/v8/test/cctest/wasm/test-wasm-breakpoints.cc
@@ -2,7 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "src/assembler-inl.h"
#include "src/debug/debug-interface.h"
+#include "src/frames-inl.h"
+#include "src/property-descriptor.h"
+#include "src/utils.h"
#include "src/wasm/wasm-macro-gen.h"
#include "src/wasm/wasm-objects.h"
@@ -50,21 +54,236 @@ void CheckLocationsFail(WasmCompiledModule *compiled_module,
CHECK(!success);
}
+class BreakHandler {
+ public:
+ enum Action {
+ Continue = StepAction::LastStepAction + 1,
+ StepNext = StepAction::StepNext,
+ StepIn = StepAction::StepIn,
+ StepOut = StepAction::StepOut
+ };
+ struct BreakPoint {
+ int position;
+ Action action;
+ BreakPoint(int position, Action action)
+ : position(position), action(action) {}
+ };
+
+ explicit BreakHandler(Isolate* isolate,
+ std::initializer_list<BreakPoint> expected_breaks)
+ : isolate_(isolate), expected_breaks_(expected_breaks) {
+ current_handler = this;
+ v8::Debug::SetDebugEventListener(reinterpret_cast<v8::Isolate*>(isolate),
+ DebugEventListener);
+ }
+ ~BreakHandler() {
+ // Check that all expected breakpoints have been hit.
+ CHECK_EQ(count_, expected_breaks_.size());
+ // BreakHandlers must be correctly stacked.
+ CHECK_EQ(this, current_handler);
+ current_handler = nullptr;
+ v8::Debug::SetDebugEventListener(reinterpret_cast<v8::Isolate*>(isolate_),
+ nullptr);
+ }
+
+ int count() const { return count_; }
+
+ private:
+ Isolate* isolate_;
+ int count_ = 0;
+ std::vector<BreakPoint> expected_breaks_;
+
+ static BreakHandler* current_handler;
+
+ void HandleBreak() {
+ printf("Break #%d\n", count_);
+ CHECK_GT(expected_breaks_.size(), count_);
+
+ // Check the current position.
+ StackTraceFrameIterator frame_it(isolate_);
+ auto summ = FrameSummary::GetTop(frame_it.frame()).AsWasmInterpreted();
+ CHECK_EQ(expected_breaks_[count_].position, summ.byte_offset());
+
+ Action next_action = expected_breaks_[count_].action;
+ switch (next_action) {
+ case Continue:
+ break;
+ case StepNext:
+ case StepIn:
+ case StepOut:
+ isolate_->debug()->PrepareStep(static_cast<StepAction>(next_action));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ ++count_;
+ }
+
+ static void DebugEventListener(const v8::Debug::EventDetails& event_details) {
+ if (event_details.GetEvent() != v8::DebugEvent::Break) return;
+
+ CHECK_NOT_NULL(current_handler);
+ current_handler->HandleBreak();
+ }
+};
+
+// static
+BreakHandler* BreakHandler::current_handler = nullptr;
+
+Handle<JSObject> MakeFakeBreakpoint(Isolate* isolate, int position) {
+ Handle<JSObject> obj =
+ isolate->factory()->NewJSObject(isolate->object_function());
+ // Generate an "isTriggered" method that always returns true.
+ // This can/must be refactored once we remove remaining JS parts from the
+ // debugger (bug 5530).
+ Handle<String> source = isolate->factory()->NewStringFromStaticChars("true");
+ Handle<Context> context(isolate->context(), isolate);
+ Handle<JSFunction> triggered_fun =
+ Compiler::GetFunctionFromString(context, source, NO_PARSE_RESTRICTION,
+ kNoSourcePosition)
+ .ToHandleChecked();
+ PropertyDescriptor desc;
+ desc.set_value(triggered_fun);
+ Handle<String> name =
+ isolate->factory()->InternalizeUtf8String(CStrVector("isTriggered"));
+ CHECK(
+ JSObject::DefineOwnProperty(isolate, obj, name, &desc, Object::DONT_THROW)
+ .FromMaybe(false));
+ return obj;
+}
+
+void SetBreakpoint(WasmRunnerBase& runner, int function_index, int byte_offset,
+ int expected_set_byte_offset = -1) {
+ int func_offset =
+ runner.module().module->functions[function_index].code_start_offset;
+ int code_offset = func_offset + byte_offset;
+ if (expected_set_byte_offset == -1) expected_set_byte_offset = byte_offset;
+ Handle<WasmInstanceObject> instance = runner.module().instance_object();
+ Handle<WasmCompiledModule> compiled_module(instance->compiled_module());
+ Handle<JSObject> fake_breakpoint_object =
+ MakeFakeBreakpoint(runner.main_isolate(), code_offset);
+ CHECK(WasmCompiledModule::SetBreakPoint(compiled_module, &code_offset,
+ fake_breakpoint_object));
+ int set_byte_offset = code_offset - func_offset;
+ CHECK_EQ(expected_set_byte_offset, set_byte_offset);
+ // Also set breakpoint on the debug info of the instance directly, since the
+ // instance chain is not setup properly in tests.
+ Handle<WasmDebugInfo> debug_info =
+ WasmInstanceObject::GetOrCreateDebugInfo(instance);
+ WasmDebugInfo::SetBreakpoint(debug_info, function_index, set_byte_offset);
+}
+
} // namespace
-TEST(CollectPossibleBreakpoints) {
+TEST(WasmCollectPossibleBreakpoints) {
WasmRunner<int> runner(kExecuteCompiled);
BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_ZERO, WASM_ONE));
Handle<WasmInstanceObject> instance = runner.module().instance_object();
std::vector<debug::Location> locations;
+ // Check all locations for function 0.
CheckLocations(instance->compiled_module(), {0, 0}, {1, 0},
{{0, 1}, {0, 2}, {0, 4}, {0, 6}, {0, 7}});
+ // Check a range ending at an instruction.
CheckLocations(instance->compiled_module(), {0, 2}, {0, 4}, {{0, 2}});
+ // Check a range ending one behind an instruction.
CheckLocations(instance->compiled_module(), {0, 2}, {0, 5}, {{0, 2}, {0, 4}});
+ // Check a range starting at an instruction.
CheckLocations(instance->compiled_module(), {0, 7}, {0, 8}, {{0, 7}});
+ // Check from an instruction to beginning of next function.
CheckLocations(instance->compiled_module(), {0, 7}, {1, 0}, {{0, 7}});
+ // Check from end of one function (no valid instruction position) to beginning
+ // of next function. Must be empty, but not fail.
CheckLocations(instance->compiled_module(), {0, 8}, {1, 0}, {});
+ // Check from one after the end of the function. Must fail.
CheckLocationsFail(instance->compiled_module(), {0, 9}, {1, 0});
}
+
+TEST(WasmSimpleBreak) {
+ WasmRunner<int> runner(kExecuteCompiled);
+ Isolate* isolate = runner.main_isolate();
+
+ BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3)));
+
+ Handle<JSFunction> main_fun_wrapper =
+ runner.module().WrapCode(runner.function_index());
+ SetBreakpoint(runner, runner.function_index(), 4, 4);
+
+ BreakHandler count_breaks(isolate, {{4, BreakHandler::Continue}});
+
+ Handle<Object> global(isolate->context()->global_object(), isolate);
+ MaybeHandle<Object> retval =
+ Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr);
+ CHECK(!retval.is_null());
+ int result;
+ CHECK(retval.ToHandleChecked()->ToInt32(&result));
+ CHECK_EQ(14, result);
+}
+
+TEST(WasmSimpleStepping) {
+ WasmRunner<int> runner(kExecuteCompiled);
+ BUILD(runner, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3)));
+
+ Isolate* isolate = runner.main_isolate();
+ Handle<JSFunction> main_fun_wrapper =
+ runner.module().WrapCode(runner.function_index());
+
+ // Set breakpoint at the first I32Const.
+ SetBreakpoint(runner, runner.function_index(), 1, 1);
+
+ BreakHandler count_breaks(isolate,
+ {
+ {1, BreakHandler::StepNext}, // I32Const
+ {3, BreakHandler::StepNext}, // I32Const
+ {5, BreakHandler::Continue} // I32Add
+ });
+
+ Handle<Object> global(isolate->context()->global_object(), isolate);
+ MaybeHandle<Object> retval =
+ Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr);
+ CHECK(!retval.is_null());
+ int result;
+ CHECK(retval.ToHandleChecked()->ToInt32(&result));
+ CHECK_EQ(14, result);
+}
+
+TEST(WasmStepInAndOut) {
+ WasmRunner<int, int> runner(kExecuteCompiled);
+ WasmFunctionCompiler& f2 = runner.NewFunction<void>();
+ f2.AllocateLocal(ValueType::kWord32);
+
+ // Call f2 via indirect call, because a direct call requires f2 to exist when
+ // we compile main, but we need to compile main first so that the order of
+ // functions in the code section matches the function indexes.
+
+ // return arg0
+ BUILD(runner, WASM_RETURN1(WASM_GET_LOCAL(0)));
+ // for (int i = 0; i < 10; ++i) { f2(i); }
+ BUILD(f2, WASM_LOOP(
+ WASM_BR_IF(0, WASM_BINOP(kExprI32GeU, WASM_GET_LOCAL(0),
+ WASM_I32V_1(10))),
+ WASM_SET_LOCAL(
+ 0, WASM_BINOP(kExprI32Sub, WASM_GET_LOCAL(0), WASM_ONE)),
+ WASM_CALL_FUNCTION(runner.function_index(), WASM_GET_LOCAL(0)),
+ WASM_DROP, WASM_BR(1)));
+
+ Isolate* isolate = runner.main_isolate();
+ Handle<JSFunction> main_fun_wrapper =
+ runner.module().WrapCode(f2.function_index());
+
+ // Set first breakpoint on the GetLocal (offset 19) before the Call.
+ SetBreakpoint(runner, f2.function_index(), 19, 19);
+
+ BreakHandler count_breaks(isolate,
+ {
+ {19, BreakHandler::StepIn}, // GetLocal
+ {21, BreakHandler::StepIn}, // Call
+ {1, BreakHandler::StepOut}, // in f2
+ {23, BreakHandler::Continue} // After Call
+ });
+
+ Handle<Object> global(isolate->context()->global_object(), isolate);
+ CHECK(!Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr)
+ .is_null());
+}