LCOV - code coverage report
Current view: top level - test/cctest/wasm - test-wasm-breakpoints.cc (source / functions) Hit Total Coverage
Test: app.info Lines: 144 145 99.3 %
Date: 2017-10-20 Functions: 25 27 92.6 %

          Line data    Source code
       1             : // Copyright 2016 the V8 project authors. All rights reserved.
       2             : // Use of this source code is governed by a BSD-style license that can be
       3             : // found in the LICENSE file.
       4             : 
       5             : #include "src/assembler-inl.h"
       6             : #include "src/debug/debug-interface.h"
       7             : #include "src/frames-inl.h"
       8             : #include "src/property-descriptor.h"
       9             : #include "src/utils.h"
      10             : #include "src/wasm/wasm-objects-inl.h"
      11             : 
      12             : #include "test/cctest/cctest.h"
      13             : #include "test/cctest/compiler/value-helper.h"
      14             : #include "test/cctest/wasm/wasm-run-utils.h"
      15             : #include "test/common/wasm/test-signatures.h"
      16             : #include "test/common/wasm/wasm-macro-gen.h"
      17             : 
      18             : namespace v8 {
      19             : namespace internal {
      20             : namespace wasm {
      21             : 
      22             : namespace {
      23             : 
      24          36 : void CheckLocations(
      25             :     WasmCompiledModule *compiled_module, debug::Location start,
      26             :     debug::Location end,
      27             :     std::initializer_list<debug::Location> expected_locations_init) {
      28             :   std::vector<debug::BreakLocation> locations;
      29             :   bool success =
      30          36 :       compiled_module->GetPossibleBreakpoints(start, end, &locations);
      31          36 :   CHECK(success);
      32             : 
      33          72 :   printf("got %d locations: ", static_cast<int>(locations.size()));
      34         132 :   for (size_t i = 0, e = locations.size(); i != e; ++i) {
      35          60 :     printf("%s<%d,%d>", i == 0 ? "" : ", ", locations[i].GetLineNumber(),
      36         180 :            locations[i].GetColumnNumber());
      37             :   }
      38             :   printf("\n");
      39             : 
      40             :   std::vector<debug::Location> expected_locations(expected_locations_init);
      41         108 :   CHECK_EQ(expected_locations.size(), locations.size());
      42          96 :   for (size_t i = 0, e = locations.size(); i != e; ++i) {
      43         180 :     CHECK_EQ(expected_locations[i].GetLineNumber(),
      44             :              locations[i].GetLineNumber());
      45         180 :     CHECK_EQ(expected_locations[i].GetColumnNumber(),
      46             :              locations[i].GetColumnNumber());
      47             :   }
      48          36 : }
      49           6 : void CheckLocationsFail(WasmCompiledModule *compiled_module,
      50             :                         debug::Location start, debug::Location end) {
      51             :   std::vector<debug::BreakLocation> locations;
      52             :   bool success =
      53           6 :       compiled_module->GetPossibleBreakpoints(start, end, &locations);
      54           6 :   CHECK(!success);
      55           6 : }
      56             : 
      57             : class BreakHandler : public debug::DebugDelegate {
      58             :  public:
      59             :   enum Action {
      60             :     Continue = StepAction::LastStepAction + 1,
      61             :     StepNext = StepAction::StepNext,
      62             :     StepIn = StepAction::StepIn,
      63             :     StepOut = StepAction::StepOut
      64             :   };
      65             :   struct BreakPoint {
      66             :     int position;
      67             :     Action action;
      68             :     BreakPoint(int position, Action action)
      69          18 :         : position(position), action(action) {}
      70             :   };
      71             : 
      72          18 :   explicit BreakHandler(Isolate* isolate,
      73             :                         std::initializer_list<BreakPoint> expected_breaks)
      74          18 :       : isolate_(isolate), expected_breaks_(expected_breaks) {
      75          18 :     v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this);
      76          18 :   }
      77          18 :   ~BreakHandler() {
      78             :     // Check that all expected breakpoints have been hit.
      79          54 :     CHECK_EQ(count_, expected_breaks_.size());
      80             :     v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_),
      81          18 :                                 nullptr);
      82          18 :   }
      83             : 
      84             :   int count() const { return count_; }
      85             : 
      86             :  private:
      87             :   Isolate* isolate_;
      88             :   int count_ = 0;
      89             :   std::vector<BreakPoint> expected_breaks_;
      90             : 
      91          48 :   void BreakProgramRequested(v8::Local<v8::Context> paused_context,
      92             :                              v8::Local<v8::Object> exec_state,
      93             :                              v8::Local<v8::Value> break_points_hit,
      94             :                              const std::vector<int>&) override {
      95          48 :     printf("Break #%d\n", count_);
      96         144 :     CHECK_GT(expected_breaks_.size(), count_);
      97             : 
      98             :     // Check the current position.
      99          78 :     StackTraceFrameIterator frame_it(isolate_);
     100          48 :     auto summ = FrameSummary::GetTop(frame_it.frame()).AsWasmInterpreted();
     101          96 :     CHECK_EQ(expected_breaks_[count_].position, summ.byte_offset());
     102             : 
     103          48 :     Action next_action = expected_breaks_[count_].action;
     104          48 :     switch (next_action) {
     105             :       case Continue:
     106             :         break;
     107             :       case StepNext:
     108             :       case StepIn:
     109             :       case StepOut:
     110          60 :         isolate_->debug()->PrepareStep(static_cast<StepAction>(next_action));
     111          30 :         break;
     112             :       default:
     113           0 :         UNREACHABLE();
     114             :     }
     115          48 :     ++count_;
     116          48 :   }
     117             : };
     118             : 
     119          48 : Handle<JSObject> MakeFakeBreakpoint(Isolate* isolate, int position) {
     120             :   Handle<JSObject> obj =
     121          24 :       isolate->factory()->NewJSObject(isolate->object_function());
     122             :   // Generate an "isTriggered" method that always returns true.
     123             :   // This can/must be refactored once we remove remaining JS parts from the
     124             :   // debugger (bug 5530).
     125          24 :   Handle<String> source = isolate->factory()->NewStringFromStaticChars("true");
     126             :   Handle<Context> context(isolate->context(), isolate);
     127             :   Handle<JSFunction> triggered_fun =
     128             :       Compiler::GetFunctionFromString(context, source, NO_PARSE_RESTRICTION,
     129             :                                       kNoSourcePosition)
     130          48 :           .ToHandleChecked();
     131             :   PropertyDescriptor desc;
     132             :   desc.set_value(triggered_fun);
     133             :   Handle<String> name =
     134          24 :       isolate->factory()->InternalizeUtf8String(CStrVector("isTriggered"));
     135          48 :   CHECK(
     136             :       JSObject::DefineOwnProperty(isolate, obj, name, &desc, Object::DONT_THROW)
     137             :           .FromMaybe(false));
     138          24 :   return obj;
     139             : }
     140             : 
     141          24 : void SetBreakpoint(WasmRunnerBase& runner, int function_index, int byte_offset,
     142             :                    int expected_set_byte_offset = -1) {
     143             :   int func_offset =
     144          24 :       runner.builder().GetFunctionAt(function_index)->code.offset();
     145          24 :   int code_offset = func_offset + byte_offset;
     146          24 :   if (expected_set_byte_offset == -1) expected_set_byte_offset = byte_offset;
     147             :   Handle<WasmInstanceObject> instance = runner.builder().instance_object();
     148             :   Handle<WasmCompiledModule> compiled_module(instance->compiled_module());
     149             :   Handle<JSObject> fake_breakpoint_object =
     150          24 :       MakeFakeBreakpoint(runner.main_isolate(), code_offset);
     151          24 :   CHECK(WasmCompiledModule::SetBreakPoint(compiled_module, &code_offset,
     152             :                                           fake_breakpoint_object));
     153          24 :   int set_byte_offset = code_offset - func_offset;
     154          24 :   CHECK_EQ(expected_set_byte_offset, set_byte_offset);
     155             :   // Also set breakpoint on the debug info of the instance directly, since the
     156             :   // instance chain is not setup properly in tests.
     157             :   Handle<WasmDebugInfo> debug_info =
     158          24 :       WasmInstanceObject::GetOrCreateDebugInfo(instance);
     159          24 :   WasmDebugInfo::SetBreakpoint(debug_info, function_index, set_byte_offset);
     160          24 : }
     161             : 
     162             : // Wrapper with operator<<.
     163             : struct WasmValWrapper {
     164             :   WasmValue val;
     165             : 
     166         312 :   bool operator==(const WasmValWrapper& other) const {
     167         312 :     return val == other.val;
     168             :   }
     169             : };
     170             : 
     171             : // Only needed in debug builds. Avoid unused warning otherwise.
     172             : #ifdef DEBUG
     173             : std::ostream& operator<<(std::ostream& out, const WasmValWrapper& wrapper) {
     174             :   switch (wrapper.val.type()) {
     175             :     case kWasmI32:
     176             :       out << "i32: " << wrapper.val.to<int32_t>();
     177             :       break;
     178             :     case kWasmI64:
     179             :       out << "i64: " << wrapper.val.to<int64_t>();
     180             :       break;
     181             :     case kWasmF32:
     182             :       out << "f32: " << wrapper.val.to<float>();
     183             :       break;
     184             :     case kWasmF64:
     185             :       out << "f64: " << wrapper.val.to<double>();
     186             :       break;
     187             :     default:
     188             :       UNIMPLEMENTED();
     189             :   }
     190             :   return out;
     191             : }
     192             : #endif
     193             : 
     194             : class CollectValuesBreakHandler : public debug::DebugDelegate {
     195             :  public:
     196         330 :   struct BreakpointValues {
     197             :     std::vector<WasmValue> locals;
     198             :     std::vector<WasmValue> stack;
     199             :   };
     200             : 
     201           6 :   explicit CollectValuesBreakHandler(
     202             :       Isolate* isolate, std::initializer_list<BreakpointValues> expected_values)
     203           6 :       : isolate_(isolate), expected_values_(expected_values) {
     204           6 :     v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this);
     205           6 :   }
     206          12 :   ~CollectValuesBreakHandler() {
     207             :     v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_),
     208           6 :                                 nullptr);
     209           6 :   }
     210             : 
     211             :  private:
     212             :   Isolate* isolate_;
     213             :   int count_ = 0;
     214             :   std::vector<BreakpointValues> expected_values_;
     215             : 
     216          66 :   void BreakProgramRequested(v8::Local<v8::Context> paused_context,
     217             :                              v8::Local<v8::Object> exec_state,
     218             :                              v8::Local<v8::Value> break_points_hit,
     219             :                              const std::vector<int>&) override {
     220          66 :     printf("Break #%d\n", count_);
     221         132 :     CHECK_GT(expected_values_.size(), count_);
     222          66 :     auto& expected = expected_values_[count_];
     223          66 :     ++count_;
     224             : 
     225         132 :     HandleScope handles(isolate_);
     226             : 
     227          66 :     StackTraceFrameIterator frame_it(isolate_);
     228          66 :     auto summ = FrameSummary::GetTop(frame_it.frame()).AsWasmInterpreted();
     229             :     Handle<WasmInstanceObject> instance = summ.wasm_instance();
     230             : 
     231             :     auto frame =
     232         132 :         instance->debug_info()->GetInterpretedFrame(frame_it.frame()->fp(), 0);
     233         396 :     CHECK_EQ(expected.locals.size(), frame->GetLocalCount());
     234         264 :     for (int i = 0; i < frame->GetLocalCount(); ++i) {
     235         792 :       CHECK_EQ(WasmValWrapper{expected.locals[i]},
     236             :                WasmValWrapper{frame->GetLocalValue(i)});
     237             :     }
     238             : 
     239         180 :     CHECK_EQ(expected.stack.size(), frame->GetStackHeight());
     240         162 :     for (int i = 0; i < frame->GetStackHeight(); ++i) {
     241         144 :       CHECK_EQ(WasmValWrapper{expected.stack[i]},
     242             :                WasmValWrapper{frame->GetStackValue(i)});
     243             :     }
     244             : 
     245         132 :     isolate_->debug()->PrepareStep(StepAction::StepIn);
     246          66 :   }
     247             : };
     248             : 
     249             : // Special template to explicitly cast to WasmValue.
     250             : template <typename Arg>
     251             : WasmValue MakeWasmVal(Arg arg) {
     252             :   return WasmValue(arg);
     253             : }
     254             : // Translate long to i64 (ambiguous otherwise).
     255             : template <>
     256             : WasmValue MakeWasmVal(long arg) {  // NOLINT: allow long parameter
     257             :   return WasmValue(static_cast<int64_t>(arg));
     258             : }
     259             : 
     260             : template <typename... Args>
     261         108 : std::vector<WasmValue> wasmVec(Args... args) {
     262         108 :   std::array<WasmValue, sizeof...(args)> arr{{MakeWasmVal(args)...}};
     263         108 :   return std::vector<WasmValue>{arr.begin(), arr.end()};
     264             : }
     265             : 
     266             : }  // namespace
     267             : 
     268       23724 : TEST(WasmCollectPossibleBreakpoints) {
     269           6 :   WasmRunner<int> runner(kExecuteCompiled);
     270             : 
     271           6 :   BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_ZERO, WASM_ONE));
     272             : 
     273             :   Handle<WasmInstanceObject> instance = runner.builder().instance_object();
     274             :   std::vector<debug::Location> locations;
     275             :   // Check all locations for function 0.
     276             :   CheckLocations(instance->compiled_module(), {0, 0}, {1, 0},
     277          12 :                  {{0, 1}, {0, 2}, {0, 4}, {0, 6}, {0, 7}});
     278             :   // Check a range ending at an instruction.
     279          12 :   CheckLocations(instance->compiled_module(), {0, 2}, {0, 4}, {{0, 2}});
     280             :   // Check a range ending one behind an instruction.
     281          12 :   CheckLocations(instance->compiled_module(), {0, 2}, {0, 5}, {{0, 2}, {0, 4}});
     282             :   // Check a range starting at an instruction.
     283          12 :   CheckLocations(instance->compiled_module(), {0, 7}, {0, 8}, {{0, 7}});
     284             :   // Check from an instruction to beginning of next function.
     285          12 :   CheckLocations(instance->compiled_module(), {0, 7}, {1, 0}, {{0, 7}});
     286             :   // Check from end of one function (no valid instruction position) to beginning
     287             :   // of next function. Must be empty, but not fail.
     288          12 :   CheckLocations(instance->compiled_module(), {0, 8}, {1, 0}, {});
     289             :   // Check from one after the end of the function. Must fail.
     290          12 :   CheckLocationsFail(instance->compiled_module(), {0, 9}, {1, 0});
     291           6 : }
     292             : 
     293       23724 : TEST(WasmSimpleBreak) {
     294           6 :   WasmRunner<int> runner(kExecuteCompiled);
     295          12 :   Isolate* isolate = runner.main_isolate();
     296             : 
     297           6 :   BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3)));
     298             : 
     299             :   Handle<JSFunction> main_fun_wrapper =
     300           6 :       runner.builder().WrapCode(runner.function_index());
     301             :   SetBreakpoint(runner, runner.function_index(), 4, 4);
     302             : 
     303          12 :   BreakHandler count_breaks(isolate, {{4, BreakHandler::Continue}});
     304             : 
     305           6 :   Handle<Object> global(isolate->context()->global_object(), isolate);
     306             :   MaybeHandle<Object> retval =
     307           6 :       Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr);
     308           6 :   CHECK(!retval.is_null());
     309             :   int result;
     310           6 :   CHECK(retval.ToHandleChecked()->ToInt32(&result));
     311           6 :   CHECK_EQ(14, result);
     312           6 : }
     313             : 
     314       23724 : TEST(WasmSimpleStepping) {
     315           6 :   WasmRunner<int> runner(kExecuteCompiled);
     316           6 :   BUILD(runner, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3)));
     317             : 
     318          12 :   Isolate* isolate = runner.main_isolate();
     319             :   Handle<JSFunction> main_fun_wrapper =
     320           6 :       runner.builder().WrapCode(runner.function_index());
     321             : 
     322             :   // Set breakpoint at the first I32Const.
     323           6 :   SetBreakpoint(runner, runner.function_index(), 1, 1);
     324             : 
     325             :   BreakHandler count_breaks(isolate,
     326             :                             {
     327             :                                 {1, BreakHandler::StepNext},  // I32Const
     328             :                                 {3, BreakHandler::StepNext},  // I32Const
     329             :                                 {5, BreakHandler::Continue}   // I32Add
     330          12 :                             });
     331             : 
     332           6 :   Handle<Object> global(isolate->context()->global_object(), isolate);
     333             :   MaybeHandle<Object> retval =
     334           6 :       Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr);
     335           6 :   CHECK(!retval.is_null());
     336             :   int result;
     337           6 :   CHECK(retval.ToHandleChecked()->ToInt32(&result));
     338           6 :   CHECK_EQ(14, result);
     339           6 : }
     340             : 
     341       23724 : TEST(WasmStepInAndOut) {
     342           6 :   WasmRunner<int, int> runner(kExecuteCompiled);
     343          18 :   WasmFunctionCompiler& f2 = runner.NewFunction<void>();
     344             :   f2.AllocateLocal(ValueType::kWord32);
     345             : 
     346             :   // Call f2 via indirect call, because a direct call requires f2 to exist when
     347             :   // we compile main, but we need to compile main first so that the order of
     348             :   // functions in the code section matches the function indexes.
     349             : 
     350             :   // return arg0
     351           6 :   BUILD(runner, WASM_RETURN1(WASM_GET_LOCAL(0)));
     352             :   // for (int i = 0; i < 10; ++i) { f2(i); }
     353          12 :   BUILD(f2, WASM_LOOP(
     354             :                 WASM_BR_IF(0, WASM_BINOP(kExprI32GeU, WASM_GET_LOCAL(0),
     355             :                                          WASM_I32V_1(10))),
     356             :                 WASM_SET_LOCAL(
     357             :                     0, WASM_BINOP(kExprI32Sub, WASM_GET_LOCAL(0), WASM_ONE)),
     358             :                 WASM_CALL_FUNCTION(runner.function_index(), WASM_GET_LOCAL(0)),
     359             :                 WASM_DROP, WASM_BR(1)));
     360             : 
     361          12 :   Isolate* isolate = runner.main_isolate();
     362             :   Handle<JSFunction> main_fun_wrapper =
     363           6 :       runner.builder().WrapCode(f2.function_index());
     364             : 
     365             :   // Set first breakpoint on the GetLocal (offset 19) before the Call.
     366           6 :   SetBreakpoint(runner, f2.function_index(), 19, 19);
     367             : 
     368             :   BreakHandler count_breaks(isolate,
     369             :                             {
     370             :                                 {19, BreakHandler::StepIn},   // GetLocal
     371             :                                 {21, BreakHandler::StepIn},   // Call
     372             :                                 {1, BreakHandler::StepOut},   // in f2
     373             :                                 {23, BreakHandler::Continue}  // After Call
     374          12 :                             });
     375             : 
     376           6 :   Handle<Object> global(isolate->context()->global_object(), isolate);
     377          12 :   CHECK(!Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr)
     378             :              .is_null());
     379           6 : }
     380             : 
     381       23724 : TEST(WasmGetLocalsAndStack) {
     382           6 :   WasmRunner<void, int> runner(kExecuteCompiled);
     383             :   runner.AllocateLocal(ValueType::kWord64);
     384             :   runner.AllocateLocal(ValueType::kFloat32);
     385             :   runner.AllocateLocal(ValueType::kFloat64);
     386             : 
     387           6 :   BUILD(runner,
     388             :         // set [1] to 17
     389             :         WASM_SET_LOCAL(1, WASM_I64V_1(17)),
     390             :         // set [2] to <arg0> = 7
     391             :         WASM_SET_LOCAL(2, WASM_F32_SCONVERT_I32(WASM_GET_LOCAL(0))),
     392             :         // set [3] to <arg1>/2 = 8.5
     393             :         WASM_SET_LOCAL(3, WASM_F64_DIV(WASM_F64_SCONVERT_I64(WASM_GET_LOCAL(1)),
     394             :                                        WASM_F64(2))));
     395             : 
     396          12 :   Isolate* isolate = runner.main_isolate();
     397             :   Handle<JSFunction> main_fun_wrapper =
     398           6 :       runner.builder().WrapCode(runner.function_index());
     399             : 
     400             :   // Set breakpoint at the first instruction (7 bytes for local decls: num
     401             :   // entries + 3x<count, type>).
     402           6 :   SetBreakpoint(runner, runner.function_index(), 7, 7);
     403             : 
     404             :   CollectValuesBreakHandler break_handler(
     405             :       isolate,
     406             :       {
     407             :           // params + locals          stack
     408             :           {wasmVec(7, 0L, 0.f, 0.), wasmVec()},          // 0: i64.const[17]
     409             :           {wasmVec(7, 0L, 0.f, 0.), wasmVec(17L)},       // 1: set_local[1]
     410             :           {wasmVec(7, 17L, 0.f, 0.), wasmVec()},         // 2: get_local[0]
     411             :           {wasmVec(7, 17L, 0.f, 0.), wasmVec(7)},        // 3: f32.convert_s
     412             :           {wasmVec(7, 17L, 0.f, 0.), wasmVec(7.f)},      // 4: set_local[2]
     413             :           {wasmVec(7, 17L, 7.f, 0.), wasmVec()},         // 5: get_local[1]
     414             :           {wasmVec(7, 17L, 7.f, 0.), wasmVec(17L)},      // 6: f64.convert_s
     415             :           {wasmVec(7, 17L, 7.f, 0.), wasmVec(17.)},      // 7: f64.const[2]
     416             :           {wasmVec(7, 17L, 7.f, 0.), wasmVec(17., 2.)},  // 8: f64.div
     417             :           {wasmVec(7, 17L, 7.f, 0.), wasmVec(8.5)},      // 9: set_local[3]
     418             :           {wasmVec(7, 17L, 7.f, 8.5), wasmVec()},        // 10: end
     419          36 :       });
     420             : 
     421           6 :   Handle<Object> global(isolate->context()->global_object(), isolate);
     422             :   Handle<Object> args[]{handle(Smi::FromInt(7), isolate)};
     423          12 :   CHECK(!Execution::Call(isolate, main_fun_wrapper, global, 1, args).is_null());
     424           6 : }
     425             : 
     426             : }  // namespace wasm
     427             : }  // namespace internal
     428       71154 : }  // namespace v8

Generated by: LCOV version 1.10