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 : #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 684 : 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 :
29 76 : WasmSourceInformation(String16 source, OffsetTable offset_table)
30 76 : : 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 948 : while (next_newline != String16::kNotFound) {
35 436 : last_newline = static_cast<int>(next_newline);
36 436 : next_newline = this->source.find('\n', last_newline + 1);
37 436 : ++num_lines;
38 : }
39 76 : end_line = num_lines;
40 76 : end_column = static_cast<int>(this->source.length()) - last_newline - 1;
41 :
42 76 : 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 552 : if (el1.line != el2.line) return el1.line < el2.line;
46 0 : if (el1.column != el2.column) return el1.column < el2.column;
47 0 : return el1.byte_offset < el2.byte_offset;
48 : };
49 : std::sort(reverse_offset_table.begin(), reverse_offset_table.end(), cmp);
50 76 : }
51 :
52 : WasmSourceInformation() = default;
53 : };
54 :
55 104 : class WasmTranslation::TranslatorImpl {
56 : public:
57 852 : struct TransLocation {
58 : WasmTranslation* translation;
59 : String16 script_id;
60 : int line;
61 : int column;
62 : TransLocation(WasmTranslation* translation, String16 script_id, int line,
63 : int column)
64 : : translation(translation),
65 : script_id(std::move(script_id)),
66 : line(line),
67 1704 : column(column) {}
68 : };
69 :
70 52 : TranslatorImpl(v8::Isolate* isolate, v8::Local<v8::debug::WasmScript> script)
71 : : script_(isolate, script) {
72 : script_.AnnotateStrongRetainer(kGlobalScriptHandleLabel);
73 52 : }
74 :
75 52 : void Init(v8::Isolate* isolate, WasmTranslation* translation,
76 : V8DebuggerAgentImpl* agent) {
77 : // Register fake scripts for each function in this wasm module/script.
78 : v8::Local<v8::debug::WasmScript> script = script_.Get(isolate);
79 52 : int num_functions = script->NumFunctions();
80 52 : int num_imported_functions = script->NumImportedFunctions();
81 : DCHECK_LE(0, num_imported_functions);
82 : DCHECK_LE(0, num_functions);
83 : DCHECK_GE(num_functions, num_imported_functions);
84 52 : String16 script_id = String16::fromInteger(script->Id());
85 220 : for (int func_idx = num_imported_functions; func_idx < num_functions;
86 : ++func_idx) {
87 84 : AddFakeScript(isolate, script_id, func_idx, translation, agent);
88 : }
89 52 : }
90 :
91 736 : void Translate(TransLocation* loc) {
92 : const OffsetTable& offset_table = GetOffsetTable(loc);
93 : DCHECK(!offset_table.empty());
94 736 : uint32_t byte_offset = static_cast<uint32_t>(loc->column);
95 :
96 : // Binary search for the given offset.
97 : unsigned left = 0; // inclusive
98 736 : unsigned right = static_cast<unsigned>(offset_table.size()); // exclusive
99 2984 : while (right - left > 1) {
100 2248 : unsigned mid = (left + right) / 2;
101 4496 : if (offset_table[mid].byte_offset <= byte_offset) {
102 : left = mid;
103 : } else {
104 : right = mid;
105 : }
106 : }
107 :
108 736 : loc->script_id = GetFakeScriptId(loc);
109 1472 : if (offset_table[left].byte_offset == byte_offset) {
110 736 : loc->line = offset_table[left].line;
111 736 : loc->column = offset_table[left].column;
112 : } else {
113 0 : loc->line = 0;
114 0 : loc->column = 0;
115 : }
116 736 : }
117 :
118 356 : static bool LessThan(const v8::debug::WasmDisassemblyOffsetTableEntry& entry,
119 : const TransLocation& loc) {
120 356 : return entry.line < loc.line ||
121 96 : (entry.line == loc.line && entry.column < loc.column);
122 : }
123 :
124 116 : void TranslateBack(TransLocation* loc) {
125 116 : v8::Isolate* isolate = loc->translation->isolate_;
126 116 : int func_index = GetFunctionIndexFromFakeScriptId(loc->script_id);
127 : const OffsetTable& reverse_table = GetReverseTable(isolate, func_index);
128 116 : if (reverse_table.empty()) return;
129 :
130 : // Binary search for the given line and column.
131 : auto element = std::lower_bound(reverse_table.begin(), reverse_table.end(),
132 : *loc, LessThan);
133 :
134 : int found_byte_offset = 0;
135 : // We want an entry on the same line if possible.
136 116 : if (element == reverse_table.end()) {
137 : // We did not find an element, so this points after the function.
138 : std::pair<int, int> func_range =
139 4 : script_.Get(isolate)->GetFunctionRange(func_index);
140 : DCHECK_LE(func_range.first, func_range.second);
141 4 : found_byte_offset = func_range.second - func_range.first;
142 112 : } else if (element->line == loc->line || element == reverse_table.begin()) {
143 112 : found_byte_offset = element->byte_offset;
144 : } else {
145 : auto prev = element - 1;
146 : DCHECK(prev->line == loc->line);
147 0 : found_byte_offset = prev->byte_offset;
148 : }
149 :
150 232 : loc->script_id = String16::fromInteger(script_.Get(isolate)->Id());
151 116 : loc->line = func_index;
152 116 : loc->column = found_byte_offset;
153 : }
154 :
155 980 : const WasmSourceInformation& GetSourceInformation(v8::Isolate* isolate,
156 : int index) {
157 : auto it = source_informations_.find(index);
158 980 : if (it != source_informations_.end()) return it->second;
159 152 : v8::HandleScope scope(isolate);
160 : v8::Local<v8::debug::WasmScript> script = script_.Get(isolate);
161 152 : v8::debug::WasmDisassembly disassembly = script->DisassembleFunction(index);
162 :
163 304 : auto inserted = source_informations_.insert(std::make_pair(
164 : index, WasmSourceInformation({disassembly.disassembly.data(),
165 : disassembly.disassembly.length()},
166 : std::move(disassembly.offset_table))));
167 : DCHECK(inserted.second);
168 76 : return inserted.first->second;
169 : }
170 :
171 84 : const String16 GetHash(v8::Isolate* isolate, int index) {
172 168 : v8::HandleScope scope(isolate);
173 : v8::Local<v8::debug::WasmScript> script = script_.Get(isolate);
174 84 : uint32_t hash = script->GetFunctionHash(index);
175 84 : String16Builder builder;
176 84 : builder.appendUnsignedAsHex(hash);
177 168 : return builder.toString();
178 : }
179 :
180 16 : int GetContextId(v8::Isolate* isolate) {
181 32 : v8::HandleScope scope(isolate);
182 : v8::Local<v8::debug::WasmScript> script = script_.Get(isolate);
183 48 : return script->ContextId().FromMaybe(0);
184 : }
185 :
186 : private:
187 84 : String16 GetFakeScriptUrl(v8::Isolate* isolate, int func_index) {
188 : v8::Local<v8::debug::WasmScript> script = script_.Get(isolate);
189 : String16 script_name =
190 168 : toProtocolString(isolate, script->Name().ToLocalChecked());
191 84 : int numFunctions = script->NumFunctions();
192 84 : int numImported = script->NumImportedFunctions();
193 84 : String16Builder builder;
194 168 : builder.appendAll("wasm://wasm/", script_name, '/');
195 84 : if (numFunctions - numImported > 300) {
196 0 : size_t digits = String16::fromInteger(numFunctions - 1).length();
197 0 : String16 thisCategory = String16::fromInteger((func_index / 100) * 100);
198 : DCHECK_LE(thisCategory.length(), digits);
199 0 : for (size_t i = thisCategory.length(); i < digits; ++i)
200 0 : builder.append('0');
201 0 : builder.appendAll(thisCategory, '/');
202 : }
203 84 : builder.appendAll(script_name, '-');
204 84 : builder.appendNumber(func_index);
205 168 : return builder.toString();
206 : }
207 :
208 820 : String16 GetFakeScriptId(const String16& script_id, int func_index) {
209 3280 : return String16::concat(script_id, '-', String16::fromInteger(func_index));
210 : }
211 : String16 GetFakeScriptId(const TransLocation* loc) {
212 736 : return GetFakeScriptId(loc->script_id, loc->line);
213 : }
214 :
215 84 : void AddFakeScript(v8::Isolate* isolate, const String16& underlyingScriptId,
216 : int func_idx, WasmTranslation* translation,
217 : V8DebuggerAgentImpl* agent) {
218 84 : String16 fake_script_id = GetFakeScriptId(underlyingScriptId, func_idx);
219 84 : String16 fake_script_url = GetFakeScriptUrl(isolate, func_idx);
220 :
221 : std::unique_ptr<V8DebuggerScript> fake_script =
222 : V8DebuggerScript::CreateWasm(isolate, translation, script_.Get(isolate),
223 : fake_script_id, std::move(fake_script_url),
224 252 : func_idx);
225 :
226 84 : translation->AddFakeScript(fake_script->scriptId(), this);
227 168 : agent->didParseSource(std::move(fake_script), true);
228 84 : }
229 :
230 116 : int GetFunctionIndexFromFakeScriptId(const String16& fake_script_id) {
231 : size_t last_dash_pos = fake_script_id.reverseFind('-');
232 : DCHECK_GT(fake_script_id.length(), last_dash_pos);
233 116 : bool ok = true;
234 232 : int func_index = fake_script_id.substring(last_dash_pos + 1).toInteger(&ok);
235 : DCHECK(ok);
236 116 : return func_index;
237 : }
238 :
239 : const OffsetTable& GetOffsetTable(const TransLocation* loc) {
240 : int func_index = loc->line;
241 736 : return GetSourceInformation(loc->translation->isolate_, func_index)
242 : .offset_table;
243 : }
244 :
245 : const OffsetTable& GetReverseTable(v8::Isolate* isolate, int func_index) {
246 116 : return GetSourceInformation(isolate, func_index).reverse_offset_table;
247 : }
248 :
249 : static constexpr char kGlobalScriptHandleLabel[] =
250 : "WasmTranslation::TranslatorImpl::script_";
251 :
252 : v8::Global<v8::debug::WasmScript> script_;
253 :
254 : // We assume to only disassemble a subset of the functions, so store them in a
255 : // map instead of an array.
256 : std::unordered_map<int, WasmSourceInformation> source_informations_;
257 : };
258 :
259 : constexpr char WasmTranslation::TranslatorImpl::kGlobalScriptHandleLabel[];
260 :
261 7344 : WasmTranslation::WasmTranslation(v8::Isolate* isolate) : isolate_(isolate) {}
262 :
263 7344 : WasmTranslation::~WasmTranslation() { Clear(); }
264 :
265 52 : void WasmTranslation::AddScript(v8::Local<v8::debug::WasmScript> script,
266 : V8DebuggerAgentImpl* agent) {
267 104 : std::unique_ptr<TranslatorImpl> impl;
268 52 : impl.reset(new TranslatorImpl(isolate_, script));
269 : DCHECK(impl);
270 : auto inserted =
271 104 : 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 104 : inserted.first->second->Init(isolate_, this, agent);
276 52 : }
277 :
278 3413 : void WasmTranslation::Clear() {
279 : wasm_translators_.clear();
280 : fake_scripts_.clear();
281 3413 : }
282 :
283 4 : void WasmTranslation::Clear(v8::Isolate* isolate,
284 : const std::vector<int>& contextIdsToClear) {
285 20 : for (auto iter = fake_scripts_.begin(); iter != fake_scripts_.end();) {
286 8 : auto contextId = iter->second->GetContextId(isolate);
287 : auto it = std::find(std::begin(contextIdsToClear),
288 8 : std::end(contextIdsToClear), contextId);
289 8 : if (it != std::end(contextIdsToClear)) {
290 : iter = fake_scripts_.erase(iter);
291 : } else {
292 : ++iter;
293 : }
294 : }
295 :
296 20 : for (auto iter = wasm_translators_.begin();
297 : iter != wasm_translators_.end();) {
298 8 : auto contextId = iter->second->GetContextId(isolate);
299 : auto it = std::find(std::begin(contextIdsToClear),
300 8 : std::end(contextIdsToClear), contextId);
301 8 : if (it != std::end(contextIdsToClear)) {
302 : iter = wasm_translators_.erase(iter);
303 : } else {
304 : ++iter;
305 : }
306 : }
307 4 : }
308 :
309 52 : const String16& WasmTranslation::GetSource(const String16& script_id,
310 : int func_index) {
311 : auto it = fake_scripts_.find(script_id);
312 : DCHECK_NE(it, fake_scripts_.end());
313 52 : return it->second->GetSourceInformation(isolate_, func_index).source;
314 : }
315 :
316 76 : int WasmTranslation::GetEndLine(const String16& script_id, int func_index) {
317 : auto it = fake_scripts_.find(script_id);
318 : DCHECK_NE(it, fake_scripts_.end());
319 76 : return it->second->GetSourceInformation(isolate_, func_index).end_line;
320 : }
321 :
322 0 : int WasmTranslation::GetEndColumn(const String16& script_id, int func_index) {
323 : auto it = fake_scripts_.find(script_id);
324 : DCHECK_NE(it, fake_scripts_.end());
325 0 : return it->second->GetSourceInformation(isolate_, func_index).end_column;
326 : }
327 :
328 84 : String16 WasmTranslation::GetHash(const String16& script_id, int func_index) {
329 : auto it = fake_scripts_.find(script_id);
330 : DCHECK_NE(it, fake_scripts_.end());
331 84 : return it->second->GetHash(isolate_, func_index);
332 : }
333 :
334 : // Translation "forward" (to artificial scripts).
335 147609 : bool WasmTranslation::TranslateWasmScriptLocationToProtocolLocation(
336 : String16* script_id, int* line_number, int* column_number) {
337 : DCHECK(script_id && line_number && column_number);
338 147609 : bool ok = true;
339 147609 : int script_id_int = script_id->toInteger(&ok);
340 147609 : if (!ok) return false;
341 :
342 : auto it = wasm_translators_.find(script_id_int);
343 147609 : if (it == wasm_translators_.end()) return false;
344 : TranslatorImpl* translator = it->second.get();
345 :
346 : TranslatorImpl::TransLocation trans_loc(this, std::move(*script_id),
347 1472 : *line_number, *column_number);
348 736 : translator->Translate(&trans_loc);
349 :
350 : *script_id = std::move(trans_loc.script_id);
351 736 : *line_number = trans_loc.line;
352 736 : *column_number = trans_loc.column;
353 :
354 : return true;
355 : }
356 :
357 : // Translation "backward" (from artificial to real scripts).
358 116 : bool WasmTranslation::TranslateProtocolLocationToWasmScriptLocation(
359 : String16* script_id, int* line_number, int* column_number) {
360 : auto it = fake_scripts_.find(*script_id);
361 116 : if (it == fake_scripts_.end()) return false;
362 116 : TranslatorImpl* translator = it->second;
363 :
364 : TranslatorImpl::TransLocation trans_loc(this, std::move(*script_id),
365 232 : *line_number, *column_number);
366 116 : translator->TranslateBack(&trans_loc);
367 :
368 : *script_id = std::move(trans_loc.script_id);
369 116 : *line_number = trans_loc.line;
370 116 : *column_number = trans_loc.column;
371 :
372 : return true;
373 : }
374 :
375 84 : void WasmTranslation::AddFakeScript(const String16& scriptId,
376 : TranslatorImpl* translator) {
377 : DCHECK_EQ(0, fake_scripts_.count(scriptId));
378 84 : fake_scripts_.insert(std::make_pair(scriptId, translator));
379 84 : }
380 : } // namespace v8_inspector
|