// Copyright 2015 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/module-decoder.h"

#include "src/logging/metrics.h"
#include "src/tracing/trace-event.h"
#include "src/wasm/constant-expression.h"
#include "src/wasm/decoder.h"
#include "src/wasm/module-decoder-impl.h"
#include "src/wasm/struct-types.h"
#include "src/wasm/wasm-constants.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-opcodes-inl.h"

namespace v8 {
namespace internal {
namespace wasm {

const char* SectionName(SectionCode code) {
  switch (code) {
    case kUnknownSectionCode:
      return "Unknown";
    case kTypeSectionCode:
      return "Type";
    case kImportSectionCode:
      return "Import";
    case kFunctionSectionCode:
      return "Function";
    case kTableSectionCode:
      return "Table";
    case kMemorySectionCode:
      return "Memory";
    case kGlobalSectionCode:
      return "Global";
    case kExportSectionCode:
      return "Export";
    case kStartSectionCode:
      return "Start";
    case kCodeSectionCode:
      return "Code";
    case kElementSectionCode:
      return "Element";
    case kDataSectionCode:
      return "Data";
    case kTagSectionCode:
      return "Tag";
    case kStringRefSectionCode:
      return "StringRef";
    case kDataCountSectionCode:
      return "DataCount";
    case kNameSectionCode:
      return kNameString;
    case kSourceMappingURLSectionCode:
      return kSourceMappingURLString;
    case kDebugInfoSectionCode:
      return kDebugInfoString;
    case kExternalDebugInfoSectionCode:
      return kExternalDebugInfoString;
    case kInstTraceSectionCode:
      return kInstTraceString;
    case kCompilationHintsSectionCode:
      return kCompilationHintsString;
    case kBranchHintsSectionCode:
      return kBranchHintsString;
    default:
      return "<unknown>";
  }
}

ModuleResult DecodeWasmModule(
    WasmFeatures enabled_features, base::Vector<const uint8_t> wire_bytes,
    bool validate_functions, ModuleOrigin origin, Counters* counters,
    std::shared_ptr<metrics::Recorder> metrics_recorder,
    v8::metrics::Recorder::ContextId context_id,
    DecodingMethod decoding_method) {
  if (counters) {
    auto size_counter =
        SELECT_WASM_COUNTER(counters, origin, wasm, module_size_bytes);
    static_assert(kV8MaxWasmModuleSize < kMaxInt);
    size_counter->AddSample(static_cast<int>(wire_bytes.size()));
  }

  v8::metrics::WasmModuleDecoded metrics_event;
  base::ElapsedTimer timer;
  timer.Start();
  ModuleResult result = DecodeWasmModule(enabled_features, wire_bytes,
                                         validate_functions, origin);
  if (counters && result.ok()) {
    auto counter =
        SELECT_WASM_COUNTER(counters, origin, wasm_functions_per, module);
    counter->AddSample(
        static_cast<int>(result.value()->num_declared_functions));
  }

  // Record event metrics.
  metrics_event.wall_clock_duration_in_us = timer.Elapsed().InMicroseconds();
  timer.Stop();
  metrics_event.success = result.ok();
  metrics_event.async = decoding_method == DecodingMethod::kAsync ||
                        decoding_method == DecodingMethod::kAsyncStream;
  metrics_event.streamed = decoding_method == DecodingMethod::kSyncStream ||
                           decoding_method == DecodingMethod::kAsyncStream;
  if (result.ok()) {
    metrics_event.function_count = result.value()->num_declared_functions;
  }
  metrics_event.module_size_in_bytes = wire_bytes.size();
  metrics_recorder->DelayMainThreadEvent(metrics_event, context_id);

  return result;
}

ModuleResult DecodeWasmModule(
    WasmFeatures enabled_features, base::Vector<const uint8_t> wire_bytes,
    bool validate_functions, ModuleOrigin origin,
    PopulateExplicitRecGroups populate_explicit_rec_groups) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
               "wasm.DecodeWasmModule");
  ModuleDecoderImpl decoder{enabled_features, wire_bytes, origin,
                            populate_explicit_rec_groups};
  return decoder.DecodeModule(validate_functions);
}

ModuleResult DecodeWasmModuleForDisassembler(
    base::Vector<const uint8_t> wire_bytes) {
  constexpr bool kNoValidateFunctions = false;
  ModuleDecoderImpl decoder{WasmFeatures::All(), wire_bytes, kWasmOrigin};
  return decoder.DecodeModule(kNoValidateFunctions);
}

ModuleDecoder::ModuleDecoder(WasmFeatures enabled_features)
    : impl_(std::make_unique<ModuleDecoderImpl>(
          enabled_features, base::Vector<const uint8_t>{}, kWasmOrigin)) {}

ModuleDecoder::~ModuleDecoder() = default;

const std::shared_ptr<WasmModule>& ModuleDecoder::shared_module() const {
  return impl_->shared_module();
}

void ModuleDecoder::DecodeModuleHeader(base::Vector<const uint8_t> bytes) {
  impl_->DecodeModuleHeader(bytes);
}

void ModuleDecoder::DecodeSection(SectionCode section_code,
                                  base::Vector<const uint8_t> bytes,
                                  uint32_t offset) {
  impl_->DecodeSection(section_code, bytes, offset);
}

void ModuleDecoder::DecodeFunctionBody(uint32_t index, uint32_t length,
                                       uint32_t offset) {
  impl_->DecodeFunctionBody(index, length, offset);
}

void ModuleDecoder::StartCodeSection(WireBytesRef section_bytes) {
  impl_->StartCodeSection(section_bytes);
}

bool ModuleDecoder::CheckFunctionsCount(uint32_t functions_count,
                                        uint32_t error_offset) {
  return impl_->CheckFunctionsCount(functions_count, error_offset);
}

ModuleResult ModuleDecoder::FinishDecoding() { return impl_->FinishDecoding(); }

size_t ModuleDecoder::IdentifyUnknownSection(ModuleDecoder* decoder,
                                             base::Vector<const uint8_t> bytes,
                                             uint32_t offset,
                                             SectionCode* result) {
  if (!decoder->ok()) return 0;
  decoder->impl_->Reset(bytes, offset);
  *result =
      IdentifyUnknownSectionInternal(decoder->impl_.get(), ITracer::NoTrace);
  return decoder->impl_->pc() - bytes.begin();
}

bool ModuleDecoder::ok() { return impl_->ok(); }

Result<const FunctionSig*> DecodeWasmSignatureForTesting(
    WasmFeatures enabled_features, Zone* zone,
    base::Vector<const uint8_t> bytes) {
  ModuleDecoderImpl decoder{enabled_features, bytes, kWasmOrigin};
  return decoder.toResult(decoder.DecodeFunctionSignature(zone, bytes.begin()));
}

ConstantExpression DecodeWasmInitExprForTesting(
    WasmFeatures enabled_features, base::Vector<const uint8_t> bytes,
    ValueType expected) {
  ModuleDecoderImpl decoder{enabled_features, bytes, kWasmOrigin};
  return decoder.DecodeInitExprForTesting(expected);
}

FunctionResult DecodeWasmFunctionForTesting(
    WasmFeatures enabled_features, Zone* zone, ModuleWireBytes wire_bytes,
    const WasmModule* module, base::Vector<const uint8_t> function_bytes) {
  if (function_bytes.size() > kV8MaxWasmFunctionSize) {
    return FunctionResult{
        WasmError{0, "size > maximum function size (%zu): %zu",
                  kV8MaxWasmFunctionSize, function_bytes.size()}};
  }
  ModuleDecoderImpl decoder{enabled_features, function_bytes, kWasmOrigin};
  return decoder.DecodeSingleFunctionForTesting(zone, wire_bytes, module);
}

AsmJsOffsetsResult DecodeAsmJsOffsets(
    base::Vector<const uint8_t> encoded_offsets) {
  std::vector<AsmJsOffsetFunctionEntries> functions;

  Decoder decoder(encoded_offsets);
  uint32_t functions_count = decoder.consume_u32v("functions count");
  // Consistency check.
  DCHECK_GE(encoded_offsets.size(), functions_count);
  functions.reserve(functions_count);

  for (uint32_t i = 0; i < functions_count; ++i) {
    uint32_t size = decoder.consume_u32v("table size");
    if (size == 0) {
      functions.emplace_back();
      continue;
    }
    DCHECK(decoder.checkAvailable(size));
    const uint8_t* table_end = decoder.pc() + size;
    uint32_t locals_size = decoder.consume_u32v("locals size");
    int function_start_position = decoder.consume_u32v("function start pos");
    int function_end_position = function_start_position;
    int last_byte_offset = locals_size;
    int last_asm_position = function_start_position;
    std::vector<AsmJsOffsetEntry> func_asm_offsets;
    func_asm_offsets.reserve(size / 4);  // conservative estimation
    // Add an entry for the stack check, associated with position 0.
    func_asm_offsets.push_back(
        {0, function_start_position, function_start_position});
    while (decoder.pc() < table_end) {
      DCHECK(decoder.ok());
      last_byte_offset += decoder.consume_u32v("byte offset delta");
      int call_position =
          last_asm_position + decoder.consume_i32v("call position delta");
      int to_number_position =
          call_position + decoder.consume_i32v("to_number position delta");
      last_asm_position = to_number_position;
      if (decoder.pc() == table_end) {
        // The last entry is the function end marker.
        DCHECK_EQ(call_position, to_number_position);
        function_end_position = call_position;
      } else {
        func_asm_offsets.push_back(
            {last_byte_offset, call_position, to_number_position});
      }
    }
    DCHECK_EQ(decoder.pc(), table_end);
    functions.emplace_back(AsmJsOffsetFunctionEntries{
        function_start_position, function_end_position,
        std::move(func_asm_offsets)});
  }
  DCHECK(decoder.ok());
  DCHECK(!decoder.more());

  return decoder.toResult(AsmJsOffsets{std::move(functions)});
}

std::vector<CustomSectionOffset> DecodeCustomSections(
    base::Vector<const uint8_t> bytes) {
  Decoder decoder(bytes);
  decoder.consume_bytes(4, "wasm magic");
  decoder.consume_bytes(4, "wasm version");

  std::vector<CustomSectionOffset> result;

  while (decoder.more()) {
    uint8_t section_code = decoder.consume_u8("section code");
    uint32_t section_length = decoder.consume_u32v("section length");
    uint32_t section_start = decoder.pc_offset();
    if (section_code != 0) {
      // Skip known sections.
      decoder.consume_bytes(section_length, "section bytes");
      continue;
    }
    uint32_t name_length = decoder.consume_u32v("name length");
    uint32_t name_offset = decoder.pc_offset();
    decoder.consume_bytes(name_length, "section name");
    uint32_t payload_offset = decoder.pc_offset();
    if (section_length < (payload_offset - section_start)) {
      decoder.error("invalid section length");
      break;
    }
    uint32_t payload_length = section_length - (payload_offset - section_start);
    decoder.consume_bytes(payload_length);
    if (decoder.failed()) break;
    result.push_back({{section_start, section_length},
                      {name_offset, name_length},
                      {payload_offset, payload_length}});
  }

  return result;
}

namespace {

bool FindNameSection(Decoder* decoder) {
  static constexpr int kModuleHeaderSize = 8;
  decoder->consume_bytes(kModuleHeaderSize, "module header");

  WasmSectionIterator section_iter(decoder, ITracer::NoTrace);

  while (decoder->ok() && section_iter.more() &&
         section_iter.section_code() != kNameSectionCode) {
    section_iter.advance(true);
  }
  if (!section_iter.more()) return false;

  // Reset the decoder to not read beyond the name section end.
  decoder->Reset(section_iter.payload(), decoder->pc_offset());
  return true;
}

enum class EmptyNames : bool { kAllow, kSkip };

void DecodeNameMapInternal(NameMap& target, Decoder& decoder,
                           EmptyNames empty_names = EmptyNames::kSkip) {
  uint32_t count = decoder.consume_u32v("names count");
  for (uint32_t i = 0; i < count; i++) {
    uint32_t index = decoder.consume_u32v("index");
    WireBytesRef name =
        consume_string(&decoder, unibrow::Utf8Variant::kLossyUtf8, "name");
    if (!decoder.ok()) break;
    if (index > NameMap::kMaxKey) continue;
    if (empty_names == EmptyNames::kSkip && name.is_empty()) continue;
    if (!validate_utf8(&decoder, name)) continue;
    target.Put(index, name);
  }
  target.FinishInitialization();
}

void DecodeNameMap(NameMap& target, Decoder& decoder,
                   uint32_t subsection_payload_length,
                   EmptyNames empty_names = EmptyNames::kSkip) {
  if (target.is_set()) {
    decoder.consume_bytes(subsection_payload_length);
    return;
  }
  DecodeNameMapInternal(target, decoder, empty_names);
}

void DecodeIndirectNameMap(IndirectNameMap& target, Decoder& decoder,
                           uint32_t subsection_payload_length) {
  if (target.is_set()) {
    decoder.consume_bytes(subsection_payload_length);
    return;
  }
  uint32_t outer_count = decoder.consume_u32v("outer count");
  for (uint32_t i = 0; i < outer_count; ++i) {
    uint32_t outer_index = decoder.consume_u32v("outer index");
    if (outer_index > IndirectNameMap::kMaxKey) continue;
    NameMap names;
    DecodeNameMapInternal(names, decoder);
    target.Put(outer_index, std::move(names));
    if (!decoder.ok()) break;
  }
  target.FinishInitialization();
}

}  // namespace

void DecodeFunctionNames(base::Vector<const uint8_t> wire_bytes,
                         NameMap& names) {
  Decoder decoder(wire_bytes);
  if (FindNameSection(&decoder)) {
    while (decoder.ok() && decoder.more()) {
      uint8_t name_type = decoder.consume_u8("name type");
      if (name_type & 0x80) break;  // no varuint7

      uint32_t name_payload_len = decoder.consume_u32v("name payload length");
      if (!decoder.checkAvailable(name_payload_len)) break;

      if (name_type != NameSectionKindCode::kFunctionCode) {
        decoder.consume_bytes(name_payload_len, "name subsection payload");
        continue;
      }
      // We need to allow empty function names for spec-conformant stack traces.
      DecodeNameMapInternal(names, decoder, EmptyNames::kAllow);
      // The spec allows only one occurrence of each subsection. We could be
      // more permissive and allow repeated subsections; in that case we'd
      // have to delay calling {target.FinishInitialization()} on the function
      // names map until we've seen them all.
      // For now, we stop decoding after finding the first function names
      // subsection.
      return;
    }
  }
}

namespace {
// A task that validates multiple functions in parallel, storing the earliest
// validation error in {this} decoder.
class ValidateFunctionsTask : public JobTask {
 public:
  explicit ValidateFunctionsTask(base::Vector<const uint8_t> wire_bytes,
                                 const WasmModule* module,
                                 WasmFeatures enabled_features,
                                 std::function<bool(int)> filter,
                                 WasmError* error_out)
      : wire_bytes_(wire_bytes),
        module_(module),
        enabled_features_(enabled_features),
        filter_(std::move(filter)),
        next_function_(module->num_imported_functions),
        after_last_function_(next_function_ + module->num_declared_functions),
        error_out_(error_out) {
    DCHECK(!error_out->has_error());
  }

  void Run(JobDelegate* delegate) override {
    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
                 "wasm.ValidateFunctionsTask");

    Zone zone(GetWasmEngine()->allocator(), ZONE_NAME);
    do {
      // Get the index of the next function to validate.
      // {fetch_add} might overrun {after_last_function_} by a bit. Since the
      // number of functions is limited to a value much smaller than the
      // integer range, this is near impossible to happen.
      static_assert(kV8MaxWasmFunctions < kMaxInt / 2);
      int func_index;
      do {
        func_index = next_function_.fetch_add(1, std::memory_order_relaxed);
        if (V8_UNLIKELY(func_index >= after_last_function_)) return;
        DCHECK_LE(0, func_index);
      } while ((filter_ && !filter_(func_index)) ||
               module_->function_was_validated(func_index));

      zone.Reset();
      if (!ValidateFunction(func_index, &zone)) {
        // No need to validate any more functions.
        next_function_.store(after_last_function_, std::memory_order_relaxed);
        return;
      }
    } while (!delegate->ShouldYield());
  }

  size_t GetMaxConcurrency(size_t /* worker_count */) const override {
    int next_func = next_function_.load(std::memory_order_relaxed);
    return std::max(0, after_last_function_ - next_func);
  }

 private:
  bool ValidateFunction(int func_index, Zone* zone) {
    WasmFeatures unused_detected_features;
    const WasmFunction& function = module_->functions[func_index];
    DCHECK_LT(0, function.code.offset());
    FunctionBody body{function.sig, function.code.offset(),
                      wire_bytes_.begin() + function.code.offset(),
                      wire_bytes_.begin() + function.code.end_offset()};
    DecodeResult validation_result = ValidateFunctionBody(
        zone, enabled_features_, module_, &unused_detected_features, body);
    if (V8_UNLIKELY(validation_result.failed())) {
      SetError(func_index, std::move(validation_result).error());
      return false;
    }
    module_->set_function_validated(func_index);
    return true;
  }

  // Set the error from the argument if it's earlier than the error we already
  // have (or if we have none yet). Thread-safe.
  void SetError(int func_index, WasmError error) {
    base::MutexGuard mutex_guard{&set_error_mutex_};
    if (error_out_->has_error() && error_out_->offset() <= error.offset()) {
      return;
    }
    *error_out_ = GetWasmErrorWithName(wire_bytes_, func_index, module_, error);
  }

  const base::Vector<const uint8_t> wire_bytes_;
  const WasmModule* const module_;
  const WasmFeatures enabled_features_;
  const std::function<bool(int)> filter_;
  std::atomic<int> next_function_;
  const int after_last_function_;
  base::Mutex set_error_mutex_;
  WasmError* const error_out_;
};
}  // namespace

WasmError ValidateFunctions(const WasmModule* module,
                            WasmFeatures enabled_features,
                            base::Vector<const uint8_t> wire_bytes,
                            std::function<bool(int)> filter) {
  TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
               "wasm.ValidateFunctions", "num_declared_functions",
               module->num_declared_functions, "has_filter", filter != nullptr);
  DCHECK_EQ(kWasmOrigin, module->origin);

  class NeverYieldDelegate final : public JobDelegate {
   public:
    bool ShouldYield() override { return false; }

    bool IsJoiningThread() const override { UNIMPLEMENTED(); }
    void NotifyConcurrencyIncrease() override { UNIMPLEMENTED(); }
    uint8_t GetTaskId() override { UNIMPLEMENTED(); }
  };

  // Create a {ValidateFunctionsTask} to validate all functions. The earliest
  // error found will be set on this decoder.
  WasmError validation_error;
  std::unique_ptr<JobTask> validate_job =
      std::make_unique<ValidateFunctionsTask>(
          wire_bytes, module, enabled_features, std::move(filter),
          &validation_error);

  if (v8_flags.single_threaded) {
    // In single-threaded mode, run the {ValidateFunctionsTask} synchronously.
    NeverYieldDelegate delegate;
    validate_job->Run(&delegate);
  } else {
    // Spawn the task and join it.
    std::unique_ptr<JobHandle> job_handle = V8::GetCurrentPlatform()->CreateJob(
        TaskPriority::kUserVisible, std::move(validate_job));
    job_handle->Join();
  }

  return validation_error;
}

WasmError GetWasmErrorWithName(base::Vector<const uint8_t> wire_bytes,
                               int func_index, const WasmModule* module,
                               WasmError error) {
  WasmName name = ModuleWireBytes{wire_bytes}.GetNameOrNull(func_index, module);
  if (name.begin() == nullptr) {
    return WasmError(error.offset(), "Compiling function #%d failed: %s",
                     func_index, error.message().c_str());
  } else {
    TruncatedUserString<> truncated_name(name);
    return WasmError(error.offset(),
                     "Compiling function #%d:\"%.*s\" failed: %s", func_index,
                     truncated_name.length(), truncated_name.start(),
                     error.message().c_str());
  }
}

DecodedNameSection::DecodedNameSection(base::Vector<const uint8_t> wire_bytes,
                                       WireBytesRef name_section) {
  if (name_section.is_empty()) return;  // No name section.
  Decoder decoder(wire_bytes.begin() + name_section.offset(),
                  wire_bytes.begin() + name_section.end_offset(),
                  name_section.offset());
  while (decoder.ok() && decoder.more()) {
    uint8_t name_type = decoder.consume_u8("name type");
    if (name_type & 0x80) break;  // no varuint7

    uint32_t name_payload_len = decoder.consume_u32v("name payload length");
    if (!decoder.checkAvailable(name_payload_len)) break;

    switch (name_type) {
      case kModuleCode:
      case kFunctionCode:
        // Already handled elsewhere.
        decoder.consume_bytes(name_payload_len);
        break;
      case kLocalCode:
        static_assert(kV8MaxWasmFunctions <= IndirectNameMap::kMaxKey);
        static_assert(kV8MaxWasmFunctionLocals <= NameMap::kMaxKey);
        DecodeIndirectNameMap(local_names_, decoder, name_payload_len);
        break;
      case kLabelCode:
        static_assert(kV8MaxWasmFunctions <= IndirectNameMap::kMaxKey);
        static_assert(kV8MaxWasmFunctionSize <= NameMap::kMaxKey);
        DecodeIndirectNameMap(label_names_, decoder, name_payload_len);
        break;
      case kTypeCode:
        static_assert(kV8MaxWasmTypes <= NameMap::kMaxKey);
        DecodeNameMap(type_names_, decoder, name_payload_len);
        break;
      case kTableCode:
        static_assert(kV8MaxWasmTables <= NameMap::kMaxKey);
        DecodeNameMap(table_names_, decoder, name_payload_len);
        break;
      case kMemoryCode:
        static_assert(kV8MaxWasmMemories <= NameMap::kMaxKey);
        DecodeNameMap(memory_names_, decoder, name_payload_len);
        break;
      case kGlobalCode:
        static_assert(kV8MaxWasmGlobals <= NameMap::kMaxKey);
        DecodeNameMap(global_names_, decoder, name_payload_len);
        break;
      case kElementSegmentCode:
        static_assert(kV8MaxWasmTableInitEntries <= NameMap::kMaxKey);
        DecodeNameMap(element_segment_names_, decoder, name_payload_len);
        break;
      case kDataSegmentCode:
        static_assert(kV8MaxWasmDataSegments <= NameMap::kMaxKey);
        DecodeNameMap(data_segment_names_, decoder, name_payload_len);
        break;
      case kFieldCode:
        static_assert(kV8MaxWasmTypes <= IndirectNameMap::kMaxKey);
        static_assert(kV8MaxWasmStructFields <= NameMap::kMaxKey);
        DecodeIndirectNameMap(field_names_, decoder, name_payload_len);
        break;
      case kTagCode:
        static_assert(kV8MaxWasmTags <= NameMap::kMaxKey);
        DecodeNameMap(tag_names_, decoder, name_payload_len);
        break;
    }
  }
}

#undef TRACE

}  // namespace wasm
}  // namespace internal
}  // namespace v8
