// Copyright 2022 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/wasm/names-provider.h" #include "src/strings/unicode-decoder.h" #include "src/wasm/module-decoder.h" #include "src/wasm/string-builder.h" namespace v8 { namespace internal { namespace wasm { NamesProvider::NamesProvider(const WasmModule* module, base::Vector<const uint8_t> wire_bytes) : module_(module), wire_bytes_(wire_bytes) {} NamesProvider::~NamesProvider() = default; void NamesProvider::DecodeNamesIfNotYetDone() { base::MutexGuard lock(&mutex_); if (has_decoded_) return; has_decoded_ = true; name_section_names_.reset( new DecodedNameSection(wire_bytes_, module_->name_section)); ComputeNamesFromImportsExports(); } // Function names are generally handled separately from other names; in // particular we support decoding function names without decoding any other // names, in which case also computing fallback names from imports and exports // must happen separately. void NamesProvider::ComputeFunctionNamesFromImportsExports() { DCHECK(!has_computed_function_import_names_); has_computed_function_import_names_ = true; for (const WasmImport& import : module_->import_table) { if (import.kind != kExternalFunction) continue; if (module_->lazily_generated_names.Has(import.index)) continue; ComputeImportName(import, import_export_function_names_); } for (const WasmExport& ex : module_->export_table) { if (ex.kind != kExternalFunction) continue; if (module_->lazily_generated_names.Has(ex.index)) continue; ComputeExportName(ex, import_export_function_names_); } } void NamesProvider::ComputeNamesFromImportsExports() { DCHECK(!has_computed_import_names_); has_computed_import_names_ = true; DCHECK(has_decoded_); for (const WasmImport import : module_->import_table) { switch (import.kind) { case kExternalFunction: continue; // Functions are handled separately. case kExternalTable: if (name_section_names_->table_names_.Has(import.index)) continue; ComputeImportName(import, import_export_table_names_); break; case kExternalMemory: if (name_section_names_->memory_names_.Has(import.index)) continue; ComputeImportName(import, import_export_memory_names_); break; case kExternalGlobal: if (name_section_names_->global_names_.Has(import.index)) continue; ComputeImportName(import, import_export_global_names_); break; case kExternalTag: if (name_section_names_->tag_names_.Has(import.index)) continue; ComputeImportName(import, import_export_tag_names_); break; } } for (const WasmExport& ex : module_->export_table) { switch (ex.kind) { case kExternalFunction: continue; // Functions are handled separately. case kExternalTable: if (name_section_names_->table_names_.Has(ex.index)) continue; ComputeExportName(ex, import_export_table_names_); break; case kExternalMemory: if (name_section_names_->memory_names_.Has(ex.index)) continue; ComputeExportName(ex, import_export_memory_names_); break; case kExternalGlobal: if (name_section_names_->global_names_.Has(ex.index)) continue; ComputeExportName(ex, import_export_global_names_); break; case kExternalTag: if (name_section_names_->tag_names_.Has(ex.index)) continue; ComputeExportName(ex, import_export_tag_names_); break; } } } namespace { // Any disallowed characters get replaced with '_'. Reference: // https://webassembly.github.io/spec/core/text/values.html#text-id static constexpr char kIdentifierChar[] = { '_', '!', '_', '#', '$', '%', '&', '\'', // -- '_', '_', '*', '+', '_', '-', '.', '/', // -- '0', '1', '2', '3', '4', '5', '6', '7', // -- '8', '9', ':', '_', '<', '=', '>', '?', // -- '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', // -- 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', // -- 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', // -- 'X', 'Y', 'Z', '_', '\\', '_', '^', '_', // -- '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', // -- 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', // -- 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', // -- 'x', 'y', 'z', '_', '|', '_', '~', '_', // -- }; // To match legacy wasmparser behavior, we emit one '_' per invalid UTF16 // code unit. // We could decide that we don't care much how exactly non-ASCII names are // rendered and simplify this to "one '_' per invalid UTF8 byte". void SanitizeUnicodeName(StringBuilder& out, const byte* utf8_src, size_t length) { base::Vector<const uint8_t> utf8_data(utf8_src, length); Utf8Decoder decoder(utf8_data); std::vector<uint16_t> utf16(decoder.utf16_length()); decoder.Decode(utf16.data(), utf8_data); for (uint16_t c : utf16) { if (c < 32 || c >= 127) { out << '_'; } else { out << kIdentifierChar[c - 32]; } } } } // namespace void NamesProvider::ComputeImportName(const WasmImport& import, std::map<uint32_t, std::string>& target) { const byte* mod_start = wire_bytes_.begin() + import.module_name.offset(); size_t mod_length = import.module_name.length(); const byte* field_start = wire_bytes_.begin() + import.field_name.offset(); size_t field_length = import.field_name.length(); StringBuilder buffer; buffer << '$'; SanitizeUnicodeName(buffer, mod_start, mod_length); buffer << '.'; SanitizeUnicodeName(buffer, field_start, field_length); target[import.index] = std::string(buffer.start(), buffer.length()); } void NamesProvider::ComputeExportName(const WasmExport& ex, std::map<uint32_t, std::string>& target) { if (target.find(ex.index) != target.end()) return; size_t length = ex.name.length(); if (length == 0) return; StringBuilder buffer; buffer << '$'; SanitizeUnicodeName(buffer, wire_bytes_.begin() + ex.name.offset(), length); target[ex.index] = std::string(buffer.start(), buffer.length()); } namespace { V8_INLINE void MaybeAddComment(StringBuilder& out, uint32_t index, bool add_comment) { if (add_comment) out << " (;" << index << ";)"; } } // namespace void NamesProvider::WriteRef(StringBuilder& out, WireBytesRef ref) { out.write(wire_bytes_.begin() + ref.offset(), ref.length()); } void NamesProvider::PrintFunctionName(StringBuilder& out, uint32_t function_index, FunctionNamesBehavior behavior, IndexAsComment index_as_comment) { // Function names are stored elsewhere, because we need to access them // during (streaming) compilation when the NamesProvider isn't ready yet. WireBytesRef ref = module_->lazily_generated_names.LookupFunctionName( ModuleWireBytes(wire_bytes_), function_index); if (ref.is_set()) { if (behavior == kDevTools) { out << '$'; WriteRef(out, ref); MaybeAddComment(out, function_index, index_as_comment); } else { // For kWasmInternal behavior, function names don't get a `$` prefix. WriteRef(out, ref); } return; } if (behavior == kWasmInternal) return; { base::MutexGuard lock(&mutex_); if (!has_computed_function_import_names_) { ComputeFunctionNamesFromImportsExports(); } } auto it = import_export_function_names_.find(function_index); if (it != import_export_function_names_.end()) { out << it->second; MaybeAddComment(out, function_index, index_as_comment); } else { out << "$func" << function_index; } } WireBytesRef Get(const NameMap& map, uint32_t index) { const WireBytesRef* result = map.Get(index); if (!result) return {}; return *result; } WireBytesRef Get(const IndirectNameMap& map, uint32_t outer_index, uint32_t inner_index) { const NameMap* inner = map.Get(outer_index); if (!inner) return {}; return Get(*inner, inner_index); } void NamesProvider::PrintLocalName(StringBuilder& out, uint32_t function_index, uint32_t local_index, IndexAsComment index_as_comment) { DecodeNamesIfNotYetDone(); WireBytesRef ref = Get(name_section_names_->local_names_, function_index, local_index); if (ref.is_set()) { out << '$'; WriteRef(out, ref); MaybeAddComment(out, local_index, index_as_comment); } else { out << "$var" << local_index; } } void NamesProvider::PrintLabelName(StringBuilder& out, uint32_t function_index, uint32_t label_index, uint32_t fallback_index) { DecodeNamesIfNotYetDone(); WireBytesRef ref = Get(name_section_names_->label_names_, function_index, label_index); if (ref.is_set()) { out << '$'; WriteRef(out, ref); } else { out << "$label" << fallback_index; } } void NamesProvider::PrintTypeName(StringBuilder& out, uint32_t type_index, IndexAsComment index_as_comment) { DecodeNamesIfNotYetDone(); WireBytesRef ref = Get(name_section_names_->type_names_, type_index); if (ref.is_set()) { out << '$'; WriteRef(out, ref); return MaybeAddComment(out, type_index, index_as_comment); } out << "$type" << type_index; } void NamesProvider::PrintTableName(StringBuilder& out, uint32_t table_index, IndexAsComment index_as_comment) { DecodeNamesIfNotYetDone(); WireBytesRef ref = Get(name_section_names_->table_names_, table_index); if (ref.is_set()) { out << '$'; WriteRef(out, ref); return MaybeAddComment(out, table_index, index_as_comment); } auto it = import_export_table_names_.find(table_index); if (it != import_export_table_names_.end()) { out << it->second; return MaybeAddComment(out, table_index, index_as_comment); } out << "$table" << table_index; } void NamesProvider::PrintMemoryName(StringBuilder& out, uint32_t memory_index, IndexAsComment index_as_comment) { DecodeNamesIfNotYetDone(); WireBytesRef ref = Get(name_section_names_->memory_names_, memory_index); if (ref.is_set()) { out << '$'; WriteRef(out, ref); return MaybeAddComment(out, memory_index, index_as_comment); } auto it = import_export_memory_names_.find(memory_index); if (it != import_export_memory_names_.end()) { out << it->second; return MaybeAddComment(out, memory_index, index_as_comment); } out << "$memory" << memory_index; } void NamesProvider::PrintGlobalName(StringBuilder& out, uint32_t global_index, IndexAsComment index_as_comment) { DecodeNamesIfNotYetDone(); WireBytesRef ref = Get(name_section_names_->global_names_, global_index); if (ref.is_set()) { out << '$'; WriteRef(out, ref); return MaybeAddComment(out, global_index, index_as_comment); } auto it = import_export_global_names_.find(global_index); if (it != import_export_global_names_.end()) { out << it->second; return MaybeAddComment(out, global_index, index_as_comment); } out << "$global" << global_index; } void NamesProvider::PrintElementSegmentName(StringBuilder& out, uint32_t element_segment_index) { DecodeNamesIfNotYetDone(); WireBytesRef ref = Get(name_section_names_->element_segment_names_, element_segment_index); if (ref.is_set()) { out << '$'; WriteRef(out, ref); } else { out << "$elem" << element_segment_index; } } void NamesProvider::PrintDataSegmentName(StringBuilder& out, uint32_t data_segment_index) { DecodeNamesIfNotYetDone(); WireBytesRef ref = Get(name_section_names_->data_segment_names_, data_segment_index); if (ref.is_set()) { out << '$'; WriteRef(out, ref); } else { out << "$data" << data_segment_index; } } void NamesProvider::PrintFieldName(StringBuilder& out, uint32_t struct_index, uint32_t field_index, IndexAsComment index_as_comment) { DecodeNamesIfNotYetDone(); WireBytesRef ref = Get(name_section_names_->field_names_, struct_index, field_index); if (ref.is_set()) { out << '$'; WriteRef(out, ref); return MaybeAddComment(out, field_index, index_as_comment); } out << "$field" << field_index; } void NamesProvider::PrintTagName(StringBuilder& out, uint32_t tag_index, IndexAsComment index_as_comment) { DecodeNamesIfNotYetDone(); WireBytesRef ref = Get(name_section_names_->tag_names_, tag_index); if (ref.is_set()) { out << '$'; WriteRef(out, ref); return MaybeAddComment(out, tag_index, index_as_comment); } out << "$tag" << tag_index; } void NamesProvider::PrintHeapType(StringBuilder& out, HeapType type) { if (type.is_index()) { PrintTypeName(out, type.ref_index()); } else { out << type.name(); } } void NamesProvider::PrintValueType(StringBuilder& out, ValueType type) { switch (type.kind()) { case kRef: case kRefNull: if (type.encoding_needs_heap_type()) { out << (type.kind() == kRef ? "(ref " : "(ref null "); PrintHeapType(out, type.heap_type()); out << ')'; } else { out << type.heap_type().name() << "ref"; } break; case kRtt: out << "(rtt "; PrintTypeName(out, type.ref_index()); out << ')'; break; default: out << wasm::name(type.kind()); } } } // namespace wasm } // namespace internal } // namespace v8