| // Copyright 2016 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/inspector/wasm-translation.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "src/debug/debug-interface.h" |
| #include "src/inspector/string-util.h" |
| #include "src/inspector/v8-debugger-agent-impl.h" |
| #include "src/inspector/v8-debugger-script.h" |
| #include "src/inspector/v8-debugger.h" |
| #include "src/inspector/v8-inspector-impl.h" |
| |
| namespace v8_inspector { |
| |
| using OffsetTable = v8::debug::WasmDisassembly::OffsetTable; |
| |
| struct WasmSourceInformation { |
| String16 source; |
| int end_line = 0; |
| int end_column = 0; |
| |
| OffsetTable offset_table; |
| OffsetTable reverse_offset_table; |
| |
| WasmSourceInformation(String16 source, OffsetTable offset_table) |
| : source(std::move(source)), offset_table(std::move(offset_table)) { |
| int num_lines = 0; |
| int last_newline = -1; |
| size_t next_newline = this->source.find('\n', last_newline + 1); |
| while (next_newline != String16::kNotFound) { |
| last_newline = static_cast<int>(next_newline); |
| next_newline = this->source.find('\n', last_newline + 1); |
| ++num_lines; |
| } |
| end_line = num_lines; |
| end_column = static_cast<int>(this->source.length()) - last_newline - 1; |
| |
| reverse_offset_table = this->offset_table; |
| // Order by line, column, then byte offset. |
| auto cmp = [](OffsetTable::value_type el1, OffsetTable::value_type el2) { |
| if (el1.line != el2.line) return el1.line < el2.line; |
| if (el1.column != el2.column) return el1.column < el2.column; |
| return el1.byte_offset < el2.byte_offset; |
| }; |
| std::sort(reverse_offset_table.begin(), reverse_offset_table.end(), cmp); |
| } |
| |
| WasmSourceInformation() = default; |
| }; |
| |
| class WasmTranslation::TranslatorImpl { |
| public: |
| struct TransLocation { |
| WasmTranslation* translation; |
| String16 script_id; |
| int line; |
| int column; |
| TransLocation(WasmTranslation* translation, String16 script_id, int line, |
| int column) |
| : translation(translation), |
| script_id(std::move(script_id)), |
| line(line), |
| column(column) {} |
| }; |
| |
| TranslatorImpl(v8::Isolate* isolate, v8::Local<v8::debug::WasmScript> script) |
| : script_(isolate, script) { |
| script_.AnnotateStrongRetainer(kGlobalScriptHandleLabel); |
| } |
| |
| void Init(v8::Isolate* isolate, WasmTranslation* translation, |
| V8DebuggerAgentImpl* agent) { |
| // Register fake scripts for each function in this wasm module/script. |
| v8::Local<v8::debug::WasmScript> script = script_.Get(isolate); |
| int num_functions = script->NumFunctions(); |
| int num_imported_functions = script->NumImportedFunctions(); |
| DCHECK_LE(0, num_imported_functions); |
| DCHECK_LE(0, num_functions); |
| DCHECK_GE(num_functions, num_imported_functions); |
| String16 script_id = String16::fromInteger(script->Id()); |
| for (int func_idx = num_imported_functions; func_idx < num_functions; |
| ++func_idx) { |
| AddFakeScript(isolate, script_id, func_idx, translation, agent); |
| } |
| } |
| |
| void Translate(TransLocation* loc) { |
| const OffsetTable& offset_table = GetOffsetTable(loc); |
| DCHECK(!offset_table.empty()); |
| uint32_t byte_offset = static_cast<uint32_t>(loc->column); |
| |
| // Binary search for the given offset. |
| unsigned left = 0; // inclusive |
| unsigned right = static_cast<unsigned>(offset_table.size()); // exclusive |
| while (right - left > 1) { |
| unsigned mid = (left + right) / 2; |
| if (offset_table[mid].byte_offset <= byte_offset) { |
| left = mid; |
| } else { |
| right = mid; |
| } |
| } |
| |
| loc->script_id = GetFakeScriptId(loc); |
| if (offset_table[left].byte_offset == byte_offset) { |
| loc->line = offset_table[left].line; |
| loc->column = offset_table[left].column; |
| } else { |
| loc->line = 0; |
| loc->column = 0; |
| } |
| } |
| |
| static bool LessThan(const v8::debug::WasmDisassemblyOffsetTableEntry& entry, |
| const TransLocation& loc) { |
| return entry.line < loc.line || |
| (entry.line == loc.line && entry.column < loc.column); |
| } |
| |
| void TranslateBack(TransLocation* loc) { |
| v8::Isolate* isolate = loc->translation->isolate_; |
| int func_index = GetFunctionIndexFromFakeScriptId(loc->script_id); |
| const OffsetTable& reverse_table = GetReverseTable(isolate, func_index); |
| if (reverse_table.empty()) return; |
| |
| // Binary search for the given line and column. |
| auto element = std::lower_bound(reverse_table.begin(), reverse_table.end(), |
| *loc, LessThan); |
| |
| int found_byte_offset = 0; |
| // We want an entry on the same line if possible. |
| if (element == reverse_table.end()) { |
| // We did not find an element, so this points after the function. |
| std::pair<int, int> func_range = |
| script_.Get(isolate)->GetFunctionRange(func_index); |
| DCHECK_LE(func_range.first, func_range.second); |
| found_byte_offset = func_range.second - func_range.first; |
| } else if (element->line == loc->line || element == reverse_table.begin()) { |
| found_byte_offset = element->byte_offset; |
| } else { |
| auto prev = element - 1; |
| DCHECK(prev->line == loc->line); |
| found_byte_offset = prev->byte_offset; |
| } |
| |
| loc->script_id = String16::fromInteger(script_.Get(isolate)->Id()); |
| loc->line = func_index; |
| loc->column = found_byte_offset; |
| } |
| |
| const WasmSourceInformation& GetSourceInformation(v8::Isolate* isolate, |
| int index) { |
| auto it = source_informations_.find(index); |
| if (it != source_informations_.end()) return it->second; |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::debug::WasmScript> script = script_.Get(isolate); |
| v8::debug::WasmDisassembly disassembly = script->DisassembleFunction(index); |
| |
| auto inserted = source_informations_.insert(std::make_pair( |
| index, WasmSourceInformation({disassembly.disassembly.data(), |
| disassembly.disassembly.length()}, |
| std::move(disassembly.offset_table)))); |
| DCHECK(inserted.second); |
| return inserted.first->second; |
| } |
| |
| const String16 GetHash(v8::Isolate* isolate, int index) { |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::debug::WasmScript> script = script_.Get(isolate); |
| uint32_t hash = script->GetFunctionHash(index); |
| String16Builder builder; |
| builder.appendUnsignedAsHex(hash); |
| return builder.toString(); |
| } |
| |
| int GetContextId(v8::Isolate* isolate) { |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::debug::WasmScript> script = script_.Get(isolate); |
| return script->ContextId().FromMaybe(0); |
| } |
| |
| private: |
| String16 GetFakeScriptUrl(v8::Isolate* isolate, int func_index) { |
| v8::Local<v8::debug::WasmScript> script = script_.Get(isolate); |
| String16 script_name = |
| toProtocolString(isolate, script->Name().ToLocalChecked()); |
| int numFunctions = script->NumFunctions(); |
| int numImported = script->NumImportedFunctions(); |
| String16Builder builder; |
| builder.appendAll("wasm://wasm/", script_name, '/'); |
| if (numFunctions - numImported > 300) { |
| size_t digits = String16::fromInteger(numFunctions - 1).length(); |
| String16 thisCategory = String16::fromInteger((func_index / 100) * 100); |
| DCHECK_LE(thisCategory.length(), digits); |
| for (size_t i = thisCategory.length(); i < digits; ++i) |
| builder.append('0'); |
| builder.appendAll(thisCategory, '/'); |
| } |
| builder.appendAll(script_name, '-'); |
| builder.appendNumber(func_index); |
| return builder.toString(); |
| } |
| |
| String16 GetFakeScriptId(const String16& script_id, int func_index) { |
| return String16::concat(script_id, '-', String16::fromInteger(func_index)); |
| } |
| String16 GetFakeScriptId(const TransLocation* loc) { |
| return GetFakeScriptId(loc->script_id, loc->line); |
| } |
| |
| void AddFakeScript(v8::Isolate* isolate, const String16& underlyingScriptId, |
| int func_idx, WasmTranslation* translation, |
| V8DebuggerAgentImpl* agent) { |
| String16 fake_script_id = GetFakeScriptId(underlyingScriptId, func_idx); |
| String16 fake_script_url = GetFakeScriptUrl(isolate, func_idx); |
| |
| std::unique_ptr<V8DebuggerScript> fake_script = |
| V8DebuggerScript::CreateWasm(isolate, translation, script_.Get(isolate), |
| fake_script_id, std::move(fake_script_url), |
| func_idx); |
| |
| translation->AddFakeScript(fake_script->scriptId(), this); |
| agent->didParseSource(std::move(fake_script), true); |
| } |
| |
| int GetFunctionIndexFromFakeScriptId(const String16& fake_script_id) { |
| size_t last_dash_pos = fake_script_id.reverseFind('-'); |
| DCHECK_GT(fake_script_id.length(), last_dash_pos); |
| bool ok = true; |
| int func_index = fake_script_id.substring(last_dash_pos + 1).toInteger(&ok); |
| DCHECK(ok); |
| return func_index; |
| } |
| |
| const OffsetTable& GetOffsetTable(const TransLocation* loc) { |
| int func_index = loc->line; |
| return GetSourceInformation(loc->translation->isolate_, func_index) |
| .offset_table; |
| } |
| |
| const OffsetTable& GetReverseTable(v8::Isolate* isolate, int func_index) { |
| return GetSourceInformation(isolate, func_index).reverse_offset_table; |
| } |
| |
| static constexpr char kGlobalScriptHandleLabel[] = |
| "WasmTranslation::TranslatorImpl::script_"; |
| |
| v8::Global<v8::debug::WasmScript> script_; |
| |
| // We assume to only disassemble a subset of the functions, so store them in a |
| // map instead of an array. |
| std::unordered_map<int, WasmSourceInformation> source_informations_; |
| }; |
| |
| constexpr char WasmTranslation::TranslatorImpl::kGlobalScriptHandleLabel[]; |
| |
| WasmTranslation::WasmTranslation(v8::Isolate* isolate) : isolate_(isolate) {} |
| |
| WasmTranslation::~WasmTranslation() { Clear(); } |
| |
| void WasmTranslation::AddScript(v8::Local<v8::debug::WasmScript> script, |
| V8DebuggerAgentImpl* agent) { |
| std::unique_ptr<TranslatorImpl> impl; |
| impl.reset(new TranslatorImpl(isolate_, script)); |
| DCHECK(impl); |
| auto inserted = |
| wasm_translators_.insert(std::make_pair(script->Id(), std::move(impl))); |
| // Check that no mapping for this script id existed before. |
| DCHECK(inserted.second); |
| // impl has been moved, use the returned iterator to call Init. |
| inserted.first->second->Init(isolate_, this, agent); |
| } |
| |
| void WasmTranslation::Clear() { |
| wasm_translators_.clear(); |
| fake_scripts_.clear(); |
| } |
| |
| void WasmTranslation::Clear(v8::Isolate* isolate, |
| const std::vector<int>& contextIdsToClear) { |
| for (auto iter = fake_scripts_.begin(); iter != fake_scripts_.end();) { |
| auto contextId = iter->second->GetContextId(isolate); |
| auto it = std::find(std::begin(contextIdsToClear), |
| std::end(contextIdsToClear), contextId); |
| if (it != std::end(contextIdsToClear)) { |
| iter = fake_scripts_.erase(iter); |
| } else { |
| ++iter; |
| } |
| } |
| |
| for (auto iter = wasm_translators_.begin(); |
| iter != wasm_translators_.end();) { |
| auto contextId = iter->second->GetContextId(isolate); |
| auto it = std::find(std::begin(contextIdsToClear), |
| std::end(contextIdsToClear), contextId); |
| if (it != std::end(contextIdsToClear)) { |
| iter = wasm_translators_.erase(iter); |
| } else { |
| ++iter; |
| } |
| } |
| } |
| |
| const String16& WasmTranslation::GetSource(const String16& script_id, |
| int func_index) { |
| auto it = fake_scripts_.find(script_id); |
| DCHECK_NE(it, fake_scripts_.end()); |
| return it->second->GetSourceInformation(isolate_, func_index).source; |
| } |
| |
| int WasmTranslation::GetEndLine(const String16& script_id, int func_index) { |
| auto it = fake_scripts_.find(script_id); |
| DCHECK_NE(it, fake_scripts_.end()); |
| return it->second->GetSourceInformation(isolate_, func_index).end_line; |
| } |
| |
| int WasmTranslation::GetEndColumn(const String16& script_id, int func_index) { |
| auto it = fake_scripts_.find(script_id); |
| DCHECK_NE(it, fake_scripts_.end()); |
| return it->second->GetSourceInformation(isolate_, func_index).end_column; |
| } |
| |
| String16 WasmTranslation::GetHash(const String16& script_id, int func_index) { |
| auto it = fake_scripts_.find(script_id); |
| DCHECK_NE(it, fake_scripts_.end()); |
| return it->second->GetHash(isolate_, func_index); |
| } |
| |
| // Translation "forward" (to artificial scripts). |
| bool WasmTranslation::TranslateWasmScriptLocationToProtocolLocation( |
| String16* script_id, int* line_number, int* column_number) { |
| DCHECK(script_id && line_number && column_number); |
| bool ok = true; |
| int script_id_int = script_id->toInteger(&ok); |
| if (!ok) return false; |
| |
| auto it = wasm_translators_.find(script_id_int); |
| if (it == wasm_translators_.end()) return false; |
| TranslatorImpl* translator = it->second.get(); |
| |
| TranslatorImpl::TransLocation trans_loc(this, std::move(*script_id), |
| *line_number, *column_number); |
| translator->Translate(&trans_loc); |
| |
| *script_id = std::move(trans_loc.script_id); |
| *line_number = trans_loc.line; |
| *column_number = trans_loc.column; |
| |
| return true; |
| } |
| |
| // Translation "backward" (from artificial to real scripts). |
| bool WasmTranslation::TranslateProtocolLocationToWasmScriptLocation( |
| String16* script_id, int* line_number, int* column_number) { |
| auto it = fake_scripts_.find(*script_id); |
| if (it == fake_scripts_.end()) return false; |
| TranslatorImpl* translator = it->second; |
| |
| TranslatorImpl::TransLocation trans_loc(this, std::move(*script_id), |
| *line_number, *column_number); |
| translator->TranslateBack(&trans_loc); |
| |
| *script_id = std::move(trans_loc.script_id); |
| *line_number = trans_loc.line; |
| *column_number = trans_loc.column; |
| |
| return true; |
| } |
| |
| void WasmTranslation::AddFakeScript(const String16& scriptId, |
| TranslatorImpl* translator) { |
| DCHECK_EQ(0, fake_scripts_.count(scriptId)); |
| fake_scripts_.insert(std::make_pair(scriptId, translator)); |
| } |
| } // namespace v8_inspector |