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