// 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