• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include <utility>
9 
10 #include "src/debug/debug-interface.h"
11 #include "src/inspector/string-util.h"
12 #include "src/inspector/v8-debugger-agent-impl.h"
13 #include "src/inspector/v8-debugger-script.h"
14 #include "src/inspector/v8-debugger.h"
15 #include "src/inspector/v8-inspector-impl.h"
16 
17 namespace v8_inspector {
18 
19 using OffsetTable = v8::debug::WasmDisassembly::OffsetTable;
20 
21 struct WasmSourceInformation {
22   String16 source;
23   int end_line = 0;
24   int end_column = 0;
25 
26   OffsetTable offset_table;
27   OffsetTable reverse_offset_table;
28 
WasmSourceInformationv8_inspector::WasmSourceInformation29   WasmSourceInformation(String16 source, OffsetTable offset_table)
30       : source(std::move(source)), offset_table(std::move(offset_table)) {
31     int num_lines = 0;
32     int last_newline = -1;
33     size_t next_newline = this->source.find('\n', last_newline + 1);
34     while (next_newline != String16::kNotFound) {
35       last_newline = static_cast<int>(next_newline);
36       next_newline = this->source.find('\n', last_newline + 1);
37       ++num_lines;
38     }
39     end_line = num_lines;
40     end_column = static_cast<int>(this->source.length()) - last_newline - 1;
41 
42     reverse_offset_table = this->offset_table;
43     // Order by line, column, then byte offset.
44     auto cmp = [](OffsetTable::value_type el1, OffsetTable::value_type el2) {
45       if (el1.line != el2.line) return el1.line < el2.line;
46       if (el1.column != el2.column) return el1.column < el2.column;
47       return el1.byte_offset < el2.byte_offset;
48     };
49     std::sort(reverse_offset_table.begin(), reverse_offset_table.end(), cmp);
50   }
51 
52   WasmSourceInformation() = default;
53 };
54 
55 class WasmTranslation::TranslatorImpl {
56  public:
57   struct TransLocation {
58     WasmTranslation* translation;
59     String16 script_id;
60     int line;
61     int column;
TransLocationv8_inspector::WasmTranslation::TranslatorImpl::TransLocation62     TransLocation(WasmTranslation* translation, String16 script_id, int line,
63                   int column)
64         : translation(translation),
65           script_id(script_id),
66           line(line),
67           column(column) {}
68   };
69 
70   virtual void Init(v8::Isolate*, WasmTranslation*, V8DebuggerAgentImpl*) = 0;
71   virtual void Translate(TransLocation*) = 0;
72   virtual void TranslateBack(TransLocation*) = 0;
73   virtual const WasmSourceInformation& GetSourceInformation(v8::Isolate*,
74                                                             int index) = 0;
75   virtual const String16 GetHash(v8::Isolate*, int index) = 0;
76 
~TranslatorImpl()77   virtual ~TranslatorImpl() {}
78 
79   class RawTranslator;
80   class DisassemblingTranslator;
81 };
82 
83 class WasmTranslation::TranslatorImpl::RawTranslator
84     : public WasmTranslation::TranslatorImpl {
85  public:
Init(v8::Isolate *,WasmTranslation *,V8DebuggerAgentImpl *)86   void Init(v8::Isolate*, WasmTranslation*, V8DebuggerAgentImpl*) override {}
Translate(TransLocation *)87   void Translate(TransLocation*) override {}
TranslateBack(TransLocation *)88   void TranslateBack(TransLocation*) override {}
GetSourceInformation(v8::Isolate *,int index)89   const WasmSourceInformation& GetSourceInformation(v8::Isolate*,
90                                                     int index) override {
91     // NOTE(mmarchini): prior to 3.9, clang won't accept const object
92     // instantiations with non-user-provided default constructors, unless an
93     // empty initializer is explicitly given. Node.js still supports older
94     // clang versions, therefore we must take care when using const objects
95     // with default constructors. For more informations, please refer to CWG
96     // 253 (http://open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#253)
97     static const WasmSourceInformation singleEmptySourceInformation = {};
98     return singleEmptySourceInformation;
99   }
GetHash(v8::Isolate *,int index)100   const String16 GetHash(v8::Isolate*, int index) override {
101     // TODO(herhut): Find useful hash default value.
102     return String16();
103   }
104 };
105 
106 class WasmTranslation::TranslatorImpl::DisassemblingTranslator
107     : public WasmTranslation::TranslatorImpl {
108 
109  public:
DisassemblingTranslator(v8::Isolate * isolate,v8::Local<v8::debug::WasmScript> script)110   DisassemblingTranslator(v8::Isolate* isolate,
111                           v8::Local<v8::debug::WasmScript> script)
112       : script_(isolate, script) {}
113 
Init(v8::Isolate * isolate,WasmTranslation * translation,V8DebuggerAgentImpl * agent)114   void Init(v8::Isolate* isolate, WasmTranslation* translation,
115             V8DebuggerAgentImpl* agent) override {
116     // Register fake scripts for each function in this wasm module/script.
117     v8::Local<v8::debug::WasmScript> script = script_.Get(isolate);
118     int num_functions = script->NumFunctions();
119     int num_imported_functions = script->NumImportedFunctions();
120     DCHECK_LE(0, num_imported_functions);
121     DCHECK_LE(0, num_functions);
122     DCHECK_GE(num_functions, num_imported_functions);
123     String16 script_id = String16::fromInteger(script->Id());
124     for (int func_idx = num_imported_functions; func_idx < num_functions;
125          ++func_idx) {
126       AddFakeScript(isolate, script_id, func_idx, translation, agent);
127     }
128   }
129 
Translate(TransLocation * loc)130   void Translate(TransLocation* loc) override {
131     const OffsetTable& offset_table = GetOffsetTable(loc);
132     DCHECK(!offset_table.empty());
133     uint32_t byte_offset = static_cast<uint32_t>(loc->column);
134 
135     // Binary search for the given offset.
136     unsigned left = 0;                                            // inclusive
137     unsigned right = static_cast<unsigned>(offset_table.size());  // exclusive
138     while (right - left > 1) {
139       unsigned mid = (left + right) / 2;
140       if (offset_table[mid].byte_offset <= byte_offset) {
141         left = mid;
142       } else {
143         right = mid;
144       }
145     }
146 
147     loc->script_id = GetFakeScriptId(loc);
148     if (offset_table[left].byte_offset == byte_offset) {
149       loc->line = offset_table[left].line;
150       loc->column = offset_table[left].column;
151     } else {
152       loc->line = 0;
153       loc->column = 0;
154     }
155   }
156 
LessThan(const v8::debug::WasmDisassemblyOffsetTableEntry & entry,const TransLocation & loc)157   static bool LessThan(const v8::debug::WasmDisassemblyOffsetTableEntry& entry,
158                        const TransLocation& loc) {
159     return entry.line < loc.line ||
160            (entry.line == loc.line && entry.column < loc.column);
161   }
162 
TranslateBack(TransLocation * loc)163   void TranslateBack(TransLocation* loc) override {
164     v8::Isolate* isolate = loc->translation->isolate_;
165     int func_index = GetFunctionIndexFromFakeScriptId(loc->script_id);
166     const OffsetTable& reverse_table = GetReverseTable(isolate, func_index);
167     if (reverse_table.empty()) return;
168 
169     // Binary search for the given line and column.
170     auto element = std::lower_bound(reverse_table.begin(), reverse_table.end(),
171                                     *loc, LessThan);
172 
173     int found_byte_offset = 0;
174     // We want an entry on the same line if possible.
175     if (element == reverse_table.end()) {
176       // We did not find an element, so this points after the function.
177       std::pair<int, int> func_range =
178           script_.Get(isolate)->GetFunctionRange(func_index);
179       DCHECK_LE(func_range.first, func_range.second);
180       found_byte_offset = func_range.second - func_range.first;
181     } else if (element->line == loc->line || element == reverse_table.begin()) {
182       found_byte_offset = element->byte_offset;
183     } else {
184       auto prev = element - 1;
185       DCHECK(prev->line == loc->line);
186       found_byte_offset = prev->byte_offset;
187     }
188 
189     loc->script_id = String16::fromInteger(script_.Get(isolate)->Id());
190     loc->line = func_index;
191     loc->column = found_byte_offset;
192   }
193 
GetSourceInformation(v8::Isolate * isolate,int index)194   const WasmSourceInformation& GetSourceInformation(v8::Isolate* isolate,
195                                                     int index) override {
196     auto it = source_informations_.find(index);
197     if (it != source_informations_.end()) return it->second;
198     v8::HandleScope scope(isolate);
199     v8::Local<v8::debug::WasmScript> script = script_.Get(isolate);
200     v8::debug::WasmDisassembly disassembly = script->DisassembleFunction(index);
201 
202     auto inserted = source_informations_.insert(std::make_pair(
203         index, WasmSourceInformation({disassembly.disassembly.data(),
204                                       disassembly.disassembly.length()},
205                                      std::move(disassembly.offset_table))));
206     DCHECK(inserted.second);
207     return inserted.first->second;
208   }
209 
GetHash(v8::Isolate * isolate,int index)210   const String16 GetHash(v8::Isolate* isolate, int index) override {
211     v8::HandleScope scope(isolate);
212     v8::Local<v8::debug::WasmScript> script = script_.Get(isolate);
213     uint32_t hash = script->GetFunctionHash(index);
214     String16Builder builder;
215     builder.appendUnsignedAsHex(hash);
216     return builder.toString();
217   }
218 
219  private:
GetFakeScriptUrl(v8::Isolate * isolate,int func_index)220   String16 GetFakeScriptUrl(v8::Isolate* isolate, int func_index) {
221     v8::Local<v8::debug::WasmScript> script = script_.Get(isolate);
222     String16 script_name =
223         toProtocolString(isolate, script->Name().ToLocalChecked());
224     int numFunctions = script->NumFunctions();
225     int numImported = script->NumImportedFunctions();
226     String16Builder builder;
227     builder.appendAll("wasm://wasm/", script_name, '/');
228     if (numFunctions - numImported > 300) {
229       size_t digits = String16::fromInteger(numFunctions - 1).length();
230       String16 thisCategory = String16::fromInteger((func_index / 100) * 100);
231       DCHECK_LE(thisCategory.length(), digits);
232       for (size_t i = thisCategory.length(); i < digits; ++i)
233         builder.append('0');
234       builder.appendAll(thisCategory, '/');
235     }
236     builder.appendAll(script_name, '-');
237     builder.appendNumber(func_index);
238     return builder.toString();
239   }
240 
GetFakeScriptId(const String16 script_id,int func_index)241   String16 GetFakeScriptId(const String16 script_id, int func_index) {
242     return String16::concat(script_id, '-', String16::fromInteger(func_index));
243   }
GetFakeScriptId(const TransLocation * loc)244   String16 GetFakeScriptId(const TransLocation* loc) {
245     return GetFakeScriptId(loc->script_id, loc->line);
246   }
247 
AddFakeScript(v8::Isolate * isolate,const String16 & underlyingScriptId,int func_idx,WasmTranslation * translation,V8DebuggerAgentImpl * agent)248   void AddFakeScript(v8::Isolate* isolate, const String16& underlyingScriptId,
249                      int func_idx, WasmTranslation* translation,
250                      V8DebuggerAgentImpl* agent) {
251     String16 fake_script_id = GetFakeScriptId(underlyingScriptId, func_idx);
252     String16 fake_script_url = GetFakeScriptUrl(isolate, func_idx);
253 
254     std::unique_ptr<V8DebuggerScript> fake_script =
255         V8DebuggerScript::CreateWasm(isolate, translation, script_.Get(isolate),
256                                      fake_script_id, std::move(fake_script_url),
257                                      func_idx);
258 
259     translation->AddFakeScript(fake_script->scriptId(), this);
260     agent->didParseSource(std::move(fake_script), true);
261   }
262 
GetFunctionIndexFromFakeScriptId(const String16 & fake_script_id)263   int GetFunctionIndexFromFakeScriptId(const String16& fake_script_id) {
264     size_t last_dash_pos = fake_script_id.reverseFind('-');
265     DCHECK_GT(fake_script_id.length(), last_dash_pos);
266     bool ok = true;
267     int func_index = fake_script_id.substring(last_dash_pos + 1).toInteger(&ok);
268     DCHECK(ok);
269     return func_index;
270   }
271 
GetOffsetTable(const TransLocation * loc)272   const OffsetTable& GetOffsetTable(const TransLocation* loc) {
273     int func_index = loc->line;
274     return GetSourceInformation(loc->translation->isolate_, func_index)
275         .offset_table;
276   }
277 
GetReverseTable(v8::Isolate * isolate,int func_index)278   const OffsetTable& GetReverseTable(v8::Isolate* isolate, int func_index) {
279     return GetSourceInformation(isolate, func_index).reverse_offset_table;
280   }
281 
282   v8::Global<v8::debug::WasmScript> script_;
283 
284   // We assume to only disassemble a subset of the functions, so store them in a
285   // map instead of an array.
286   std::unordered_map<int, WasmSourceInformation> source_informations_;
287 };
288 
WasmTranslation(v8::Isolate * isolate)289 WasmTranslation::WasmTranslation(v8::Isolate* isolate)
290     : isolate_(isolate), mode_(Disassemble) {}
291 
~WasmTranslation()292 WasmTranslation::~WasmTranslation() { Clear(); }
293 
AddScript(v8::Local<v8::debug::WasmScript> script,V8DebuggerAgentImpl * agent)294 void WasmTranslation::AddScript(v8::Local<v8::debug::WasmScript> script,
295                                 V8DebuggerAgentImpl* agent) {
296   std::unique_ptr<TranslatorImpl> impl;
297   switch (mode_) {
298     case Raw:
299       impl.reset(new TranslatorImpl::RawTranslator());
300       break;
301     case Disassemble:
302       impl.reset(new TranslatorImpl::DisassemblingTranslator(isolate_, script));
303       break;
304   }
305   DCHECK(impl);
306   auto inserted =
307       wasm_translators_.insert(std::make_pair(script->Id(), std::move(impl)));
308   // Check that no mapping for this script id existed before.
309   DCHECK(inserted.second);
310   // impl has been moved, use the returned iterator to call Init.
311   inserted.first->second->Init(isolate_, this, agent);
312 }
313 
Clear()314 void WasmTranslation::Clear() {
315   wasm_translators_.clear();
316   fake_scripts_.clear();
317 }
318 
GetSource(const String16 & script_id,int func_index)319 const String16& WasmTranslation::GetSource(const String16& script_id,
320                                            int func_index) {
321   auto it = fake_scripts_.find(script_id);
322   DCHECK_NE(it, fake_scripts_.end());
323   return it->second->GetSourceInformation(isolate_, func_index).source;
324 }
325 
GetEndLine(const String16 & script_id,int func_index)326 int WasmTranslation::GetEndLine(const String16& script_id, int func_index) {
327   auto it = fake_scripts_.find(script_id);
328   DCHECK_NE(it, fake_scripts_.end());
329   return it->second->GetSourceInformation(isolate_, func_index).end_line;
330 }
331 
GetEndColumn(const String16 & script_id,int func_index)332 int WasmTranslation::GetEndColumn(const String16& script_id, int func_index) {
333   auto it = fake_scripts_.find(script_id);
334   DCHECK_NE(it, fake_scripts_.end());
335   return it->second->GetSourceInformation(isolate_, func_index).end_column;
336 }
337 
GetHash(const String16 & script_id,int func_index)338 String16 WasmTranslation::GetHash(const String16& script_id, int func_index) {
339   auto it = fake_scripts_.find(script_id);
340   DCHECK_NE(it, fake_scripts_.end());
341   return it->second->GetHash(isolate_, func_index);
342 }
343 
344 // Translation "forward" (to artificial scripts).
TranslateWasmScriptLocationToProtocolLocation(String16 * script_id,int * line_number,int * column_number)345 bool WasmTranslation::TranslateWasmScriptLocationToProtocolLocation(
346     String16* script_id, int* line_number, int* column_number) {
347   DCHECK(script_id && line_number && column_number);
348   bool ok = true;
349   int script_id_int = script_id->toInteger(&ok);
350   if (!ok) return false;
351 
352   auto it = wasm_translators_.find(script_id_int);
353   if (it == wasm_translators_.end()) return false;
354   TranslatorImpl* translator = it->second.get();
355 
356   TranslatorImpl::TransLocation trans_loc(this, std::move(*script_id),
357                                           *line_number, *column_number);
358   translator->Translate(&trans_loc);
359 
360   *script_id = std::move(trans_loc.script_id);
361   *line_number = trans_loc.line;
362   *column_number = trans_loc.column;
363 
364   return true;
365 }
366 
367 // Translation "backward" (from artificial to real scripts).
TranslateProtocolLocationToWasmScriptLocation(String16 * script_id,int * line_number,int * column_number)368 bool WasmTranslation::TranslateProtocolLocationToWasmScriptLocation(
369     String16* script_id, int* line_number, int* column_number) {
370   auto it = fake_scripts_.find(*script_id);
371   if (it == fake_scripts_.end()) return false;
372   TranslatorImpl* translator = it->second;
373 
374   TranslatorImpl::TransLocation trans_loc(this, std::move(*script_id),
375                                           *line_number, *column_number);
376   translator->TranslateBack(&trans_loc);
377 
378   *script_id = std::move(trans_loc.script_id);
379   *line_number = trans_loc.line;
380   *column_number = trans_loc.column;
381 
382   return true;
383 }
384 
AddFakeScript(const String16 & scriptId,TranslatorImpl * translator)385 void WasmTranslation::AddFakeScript(const String16& scriptId,
386                                     TranslatorImpl* translator) {
387   DCHECK_EQ(0, fake_scripts_.count(scriptId));
388   fake_scripts_.insert(std::make_pair(scriptId, translator));
389 }
390 }  // namespace v8_inspector
391