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;
TransLocationWasmTranslation::TranslatorImpl::TransLocation28 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;
~TranslatorImpl()39 virtual ~TranslatorImpl() {}
40
41 class RawTranslator;
42 class DisassemblingTranslator;
43 };
44
45 class WasmTranslation::TranslatorImpl::RawTranslator
46 : public WasmTranslation::TranslatorImpl {
47 public:
Init(Isolate *,WasmTranslation *,V8DebuggerAgentImpl *)48 void Init(Isolate*, WasmTranslation*, V8DebuggerAgentImpl*) {}
Translate(TransLocation *)49 void Translate(TransLocation*) {}
TranslateBack(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:
DisassemblingTranslator(Isolate * isolate,Local<debug::WasmScript> script)58 DisassemblingTranslator(Isolate* isolate, Local<debug::WasmScript> script)
59 : script_(isolate, script) {}
60
Init(Isolate * isolate,WasmTranslation * translation,V8DebuggerAgentImpl * agent)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
Translate(TransLocation * loc)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
TranslateBack(TransLocation * loc)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:
GetFakeScriptUrl(v8::Isolate * isolate,int func_index)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
GetFakeScriptId(const String16 script_id,int func_index)172 String16 GetFakeScriptId(const String16 script_id, int func_index) {
173 return String16::concat(script_id, '-', String16::fromInteger(func_index));
174 }
GetFakeScriptId(const TransLocation * loc)175 String16 GetFakeScriptId(const TransLocation* loc) {
176 return GetFakeScriptId(loc->script_id, loc->line);
177 }
178
AddFakeScript(v8::Isolate * isolate,const String16 & underlyingScriptId,int func_idx,WasmTranslation * translation,V8DebuggerAgentImpl * agent)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
GetFunctionIndexFromFakeScriptId(const String16 & fake_script_id)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
GetOffsetTable(const TransLocation * loc)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
GetReverseTable(int func_index)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
WasmTranslation(v8::Isolate * isolate)252 WasmTranslation::WasmTranslation(v8::Isolate* isolate)
253 : isolate_(isolate), mode_(Disassemble) {}
254
~WasmTranslation()255 WasmTranslation::~WasmTranslation() { Clear(); }
256
AddScript(Local<debug::WasmScript> script,V8DebuggerAgentImpl * agent)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
Clear()277 void WasmTranslation::Clear() {
278 wasm_translators_.clear();
279 fake_scripts_.clear();
280 }
281
282 // Translation "forward" (to artificial scripts).
TranslateWasmScriptLocationToProtocolLocation(String16 * script_id,int * line_number,int * column_number)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).
TranslateProtocolLocationToWasmScriptLocation(String16 * script_id,int * line_number,int * column_number)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
AddFakeScript(const String16 & scriptId,TranslatorImpl * translator)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