Home | History | Annotate | Download | only in inspector
      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/inspector/wasm-translation.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "src/debug/debug-interface.h"
     10 #include "src/inspector/protocol/Debugger.h"
     11 #include "src/inspector/script-breakpoint.h"
     12 #include "src/inspector/string-util.h"
     13 #include "src/inspector/v8-debugger-agent-impl.h"
     14 #include "src/inspector/v8-debugger-script.h"
     15 #include "src/inspector/v8-debugger.h"
     16 #include "src/inspector/v8-inspector-impl.h"
     17 
     18 using namespace v8_inspector;
     19 using namespace v8;
     20 
     21 class WasmTranslation::TranslatorImpl {
     22  public:
     23   struct TransLocation {
     24     WasmTranslation* translation;
     25     String16 script_id;
     26     int line;
     27     int column;
     28     TransLocation(WasmTranslation* translation, String16 script_id, int line,
     29                   int column)
     30         : translation(translation),
     31           script_id(script_id),
     32           line(line),
     33           column(column) {}
     34   };
     35 
     36   virtual void Init(Isolate*, WasmTranslation*, V8DebuggerAgentImpl*) = 0;
     37   virtual void Translate(TransLocation*) = 0;
     38   virtual void TranslateBack(TransLocation*) = 0;
     39   virtual ~TranslatorImpl() {}
     40 
     41   class RawTranslator;
     42   class DisassemblingTranslator;
     43 };
     44 
     45 class WasmTranslation::TranslatorImpl::RawTranslator
     46     : public WasmTranslation::TranslatorImpl {
     47  public:
     48   void Init(Isolate*, WasmTranslation*, V8DebuggerAgentImpl*) {}
     49   void Translate(TransLocation*) {}
     50   void TranslateBack(TransLocation*) {}
     51 };
     52 
     53 class WasmTranslation::TranslatorImpl::DisassemblingTranslator
     54     : public WasmTranslation::TranslatorImpl {
     55   using OffsetTable = debug::WasmDisassembly::OffsetTable;
     56 
     57  public:
     58   DisassemblingTranslator(Isolate* isolate, Local<debug::WasmScript> script)
     59       : script_(isolate, script) {}
     60 
     61   void Init(Isolate* isolate, WasmTranslation* translation,
     62             V8DebuggerAgentImpl* agent) override {
     63     // Register fake scripts for each function in this wasm module/script.
     64     Local<debug::WasmScript> script = script_.Get(isolate);
     65     int num_functions = script->NumFunctions();
     66     int num_imported_functions = script->NumImportedFunctions();
     67     DCHECK_LE(0, num_imported_functions);
     68     DCHECK_LE(0, num_functions);
     69     DCHECK_GE(num_functions, num_imported_functions);
     70     String16 script_id = String16::fromInteger(script->Id());
     71     for (int func_idx = num_imported_functions; func_idx < num_functions;
     72          ++func_idx) {
     73       AddFakeScript(isolate, script_id, func_idx, translation, agent);
     74     }
     75   }
     76 
     77   void Translate(TransLocation* loc) override {
     78     const OffsetTable& offset_table = GetOffsetTable(loc);
     79     DCHECK(!offset_table.empty());
     80     uint32_t byte_offset = static_cast<uint32_t>(loc->column);
     81 
     82     // Binary search for the given offset.
     83     unsigned left = 0;                                            // inclusive
     84     unsigned right = static_cast<unsigned>(offset_table.size());  // exclusive
     85     while (right - left > 1) {
     86       unsigned mid = (left + right) / 2;
     87       if (offset_table[mid].byte_offset <= byte_offset) {
     88         left = mid;
     89       } else {
     90         right = mid;
     91       }
     92     }
     93 
     94     loc->script_id = GetFakeScriptId(loc);
     95     if (offset_table[left].byte_offset == byte_offset) {
     96       loc->line = offset_table[left].line;
     97       loc->column = offset_table[left].column;
     98     } else {
     99       loc->line = 0;
    100       loc->column = 0;
    101     }
    102   }
    103 
    104   void TranslateBack(TransLocation* loc) override {
    105     int func_index = GetFunctionIndexFromFakeScriptId(loc->script_id);
    106     const OffsetTable* reverse_table = GetReverseTable(func_index);
    107     if (!reverse_table) return;
    108     DCHECK(!reverse_table->empty());
    109     v8::Isolate* isolate = loc->translation->isolate_;
    110 
    111     // Binary search for the given line and column.
    112     unsigned left = 0;                                              // inclusive
    113     unsigned right = static_cast<unsigned>(reverse_table->size());  // exclusive
    114     while (right - left > 1) {
    115       unsigned mid = (left + right) / 2;
    116       auto& entry = (*reverse_table)[mid];
    117       if (entry.line < loc->line ||
    118           (entry.line == loc->line && entry.column <= loc->column)) {
    119         left = mid;
    120       } else {
    121         right = mid;
    122       }
    123     }
    124 
    125     int found_byte_offset = 0;
    126     // If we found an exact match, use it. Otherwise check whether the next
    127     // bigger entry is still in the same line. Report that one then.
    128     // Otherwise we might have hit the special case of pointing after the last
    129     // line, which is translated to the end of the function (one byte after the
    130     // last function byte).
    131     if ((*reverse_table)[left].line == loc->line &&
    132         (*reverse_table)[left].column == loc->column) {
    133       found_byte_offset = (*reverse_table)[left].byte_offset;
    134     } else if (left + 1 < reverse_table->size() &&
    135                (*reverse_table)[left + 1].line == loc->line) {
    136       found_byte_offset = (*reverse_table)[left + 1].byte_offset;
    137     } else if (left == reverse_table->size() - 1 &&
    138                (*reverse_table)[left].line == loc->line - 1 &&
    139                loc->column == 0) {
    140       std::pair<int, int> func_range =
    141           script_.Get(isolate)->GetFunctionRange(func_index);
    142       DCHECK_LE(func_range.first, func_range.second);
    143       found_byte_offset = func_range.second - func_range.first;
    144     }
    145 
    146     loc->script_id = String16::fromInteger(script_.Get(isolate)->Id());
    147     loc->line = func_index;
    148     loc->column = found_byte_offset;
    149   }
    150 
    151  private:
    152   String16 GetFakeScriptUrl(v8::Isolate* isolate, int func_index) {
    153     Local<debug::WasmScript> script = script_.Get(isolate);
    154     String16 script_name = toProtocolString(script->Name().ToLocalChecked());
    155     int numFunctions = script->NumFunctions();
    156     int numImported = script->NumImportedFunctions();
    157     String16Builder builder;
    158     builder.appendAll("wasm://wasm/", script_name, '/');
    159     if (numFunctions - numImported > 300) {
    160       size_t digits = String16::fromInteger(numFunctions - 1).length();
    161       String16 thisCategory = String16::fromInteger((func_index / 100) * 100);
    162       DCHECK_LE(thisCategory.length(), digits);
    163       for (size_t i = thisCategory.length(); i < digits; ++i)
    164         builder.append('0');
    165       builder.appendAll(thisCategory, '/');
    166     }
    167     builder.appendAll(script_name, '-');
    168     builder.appendNumber(func_index);
    169     return builder.toString();
    170   }
    171 
    172   String16 GetFakeScriptId(const String16 script_id, int func_index) {
    173     return String16::concat(script_id, '-', String16::fromInteger(func_index));
    174   }
    175   String16 GetFakeScriptId(const TransLocation* loc) {
    176     return GetFakeScriptId(loc->script_id, loc->line);
    177   }
    178 
    179   void AddFakeScript(v8::Isolate* isolate, const String16& underlyingScriptId,
    180                      int func_idx, WasmTranslation* translation,
    181                      V8DebuggerAgentImpl* agent) {
    182     String16 fake_script_id = GetFakeScriptId(underlyingScriptId, func_idx);
    183     String16 fake_script_url = GetFakeScriptUrl(isolate, func_idx);
    184 
    185     v8::Local<debug::WasmScript> script = script_.Get(isolate);
    186     // TODO(clemensh): Generate disassembly lazily when queried by the frontend.
    187     debug::WasmDisassembly disassembly = script->DisassembleFunction(func_idx);
    188 
    189     DCHECK_EQ(0, offset_tables_.count(func_idx));
    190     offset_tables_.insert(
    191         std::make_pair(func_idx, std::move(disassembly.offset_table)));
    192     String16 source(disassembly.disassembly.data(),
    193                     disassembly.disassembly.length());
    194     std::unique_ptr<V8DebuggerScript> fake_script =
    195         V8DebuggerScript::CreateWasm(isolate, translation, script,
    196                                      fake_script_id, std::move(fake_script_url),
    197                                      source);
    198 
    199     translation->AddFakeScript(fake_script->scriptId(), this);
    200     agent->didParseSource(std::move(fake_script), true);
    201   }
    202 
    203   int GetFunctionIndexFromFakeScriptId(const String16& fake_script_id) {
    204     size_t last_dash_pos = fake_script_id.reverseFind('-');
    205     DCHECK_GT(fake_script_id.length(), last_dash_pos);
    206     bool ok = true;
    207     int func_index = fake_script_id.substring(last_dash_pos + 1).toInteger(&ok);
    208     DCHECK(ok);
    209     return func_index;
    210   }
    211 
    212   const OffsetTable& GetOffsetTable(const TransLocation* loc) {
    213     int func_index = loc->line;
    214     auto it = offset_tables_.find(func_index);
    215     // TODO(clemensh): Once we load disassembly lazily, the offset table
    216     // might not be there yet. Load it lazily then.
    217     DCHECK(it != offset_tables_.end());
    218     return it->second;
    219   }
    220 
    221   const OffsetTable* GetReverseTable(int func_index) {
    222     auto it = reverse_tables_.find(func_index);
    223     if (it != reverse_tables_.end()) return &it->second;
    224 
    225     // Find offset table, copy and sort it to get reverse table.
    226     it = offset_tables_.find(func_index);
    227     if (it == offset_tables_.end()) return nullptr;
    228 
    229     OffsetTable reverse_table = it->second;
    230     // Order by line, column, then byte offset.
    231     auto cmp = [](OffsetTable::value_type el1, OffsetTable::value_type el2) {
    232       if (el1.line != el2.line) return el1.line < el2.line;
    233       if (el1.column != el2.column) return el1.column < el2.column;
    234       return el1.byte_offset < el2.byte_offset;
    235     };
    236     std::sort(reverse_table.begin(), reverse_table.end(), cmp);
    237 
    238     auto inserted = reverse_tables_.insert(
    239         std::make_pair(func_index, std::move(reverse_table)));
    240     DCHECK(inserted.second);
    241     return &inserted.first->second;
    242   }
    243 
    244   Global<debug::WasmScript> script_;
    245 
    246   // We assume to only disassemble a subset of the functions, so store them in a
    247   // map instead of an array.
    248   std::unordered_map<int, const OffsetTable> offset_tables_;
    249   std::unordered_map<int, const OffsetTable> reverse_tables_;
    250 };
    251 
    252 WasmTranslation::WasmTranslation(v8::Isolate* isolate)
    253     : isolate_(isolate), mode_(Disassemble) {}
    254 
    255 WasmTranslation::~WasmTranslation() { Clear(); }
    256 
    257 void WasmTranslation::AddScript(Local<debug::WasmScript> script,
    258                                 V8DebuggerAgentImpl* agent) {
    259   std::unique_ptr<TranslatorImpl> impl;
    260   switch (mode_) {
    261     case Raw:
    262       impl.reset(new TranslatorImpl::RawTranslator());
    263       break;
    264     case Disassemble:
    265       impl.reset(new TranslatorImpl::DisassemblingTranslator(isolate_, script));
    266       break;
    267   }
    268   DCHECK(impl);
    269   auto inserted =
    270       wasm_translators_.insert(std::make_pair(script->Id(), std::move(impl)));
    271   // Check that no mapping for this script id existed before.
    272   DCHECK(inserted.second);
    273   // impl has been moved, use the returned iterator to call Init.
    274   inserted.first->second->Init(isolate_, this, agent);
    275 }
    276 
    277 void WasmTranslation::Clear() {
    278   wasm_translators_.clear();
    279   fake_scripts_.clear();
    280 }
    281 
    282 // Translation "forward" (to artificial scripts).
    283 bool WasmTranslation::TranslateWasmScriptLocationToProtocolLocation(
    284     String16* script_id, int* line_number, int* column_number) {
    285   DCHECK(script_id && line_number && column_number);
    286   bool ok = true;
    287   int script_id_int = script_id->toInteger(&ok);
    288   if (!ok) return false;
    289 
    290   auto it = wasm_translators_.find(script_id_int);
    291   if (it == wasm_translators_.end()) return false;
    292   TranslatorImpl* translator = it->second.get();
    293 
    294   TranslatorImpl::TransLocation trans_loc(this, std::move(*script_id),
    295                                           *line_number, *column_number);
    296   translator->Translate(&trans_loc);
    297 
    298   *script_id = std::move(trans_loc.script_id);
    299   *line_number = trans_loc.line;
    300   *column_number = trans_loc.column;
    301 
    302   return true;
    303 }
    304 
    305 // Translation "backward" (from artificial to real scripts).
    306 bool WasmTranslation::TranslateProtocolLocationToWasmScriptLocation(
    307     String16* script_id, int* line_number, int* column_number) {
    308   auto it = fake_scripts_.find(*script_id);
    309   if (it == fake_scripts_.end()) return false;
    310   TranslatorImpl* translator = it->second;
    311 
    312   TranslatorImpl::TransLocation trans_loc(this, std::move(*script_id),
    313                                           *line_number, *column_number);
    314   translator->TranslateBack(&trans_loc);
    315 
    316   *script_id = std::move(trans_loc.script_id);
    317   *line_number = trans_loc.line;
    318   *column_number = trans_loc.column;
    319 
    320   return true;
    321 }
    322 
    323 void WasmTranslation::AddFakeScript(const String16& scriptId,
    324                                     TranslatorImpl* translator) {
    325   DCHECK_EQ(0, fake_scripts_.count(scriptId));
    326   fake_scripts_.insert(std::make_pair(scriptId, translator));
    327 }
    328