// Copyright 2019 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/wasm-module-sourcemap.h"

#include <algorithm>

#include "include/v8.h"
#include "src/api/api.h"
#include "src/base/vlq-base64.h"

namespace v8 {
namespace internal {
namespace wasm {

WasmModuleSourceMap::WasmModuleSourceMap(v8::Isolate* v8_isolate,
                                         v8::Local<v8::String> src_map_str) {
  v8::HandleScope scope(v8_isolate);
  v8::Local<v8::Context> context = v8::Context::New(v8_isolate);

  v8::Local<v8::Value> src_map_value;
  if (!v8::JSON::Parse(context, src_map_str).ToLocal(&src_map_value)) return;
  v8::Local<v8::Object> src_map_obj =
      v8::Local<v8::Object>::Cast(src_map_value);

  v8::Local<v8::Value> version_value, sources_value, mappings_value;
  bool has_valid_version =
      src_map_obj
          ->Get(context, v8::String::NewFromUtf8Literal(v8_isolate, "version"))
          .ToLocal(&version_value) &&
      version_value->IsUint32();
  uint32_t version = 0;
  if (!has_valid_version || !version_value->Uint32Value(context).To(&version) ||
      version != 3u)
    return;

  bool has_valid_sources =
      src_map_obj
          ->Get(context, v8::String::NewFromUtf8Literal(v8_isolate, "sources"))
          .ToLocal(&sources_value) &&
      sources_value->IsArray();
  if (!has_valid_sources) return;

  v8::Local<v8::Object> sources_arr =
      v8::Local<v8::Object>::Cast(sources_value);
  v8::Local<v8::Value> sources_len_value;
  if (!sources_arr
           ->Get(context, v8::String::NewFromUtf8Literal(v8_isolate, "length"))
           .ToLocal(&sources_len_value))
    return;
  uint32_t sources_len = 0;
  if (!sources_len_value->Uint32Value(context).To(&sources_len)) return;

  for (uint32_t i = 0; i < sources_len; ++i) {
    v8::Local<v8::Value> file_name_value;
    if (!sources_arr->Get(context, i).ToLocal(&file_name_value) ||
        !file_name_value->IsString())
      return;
    v8::Local<v8::String> file_name =
        v8::Local<v8::String>::Cast(file_name_value);
    auto file_name_sz = file_name->Utf8Length(v8_isolate);
    std::unique_ptr<char[]> file_name_buf(new char[file_name_sz + 1]);
    file_name->WriteUtf8(v8_isolate, file_name_buf.get());
    file_name_buf.get()[file_name_sz] = '\0';
    filenames.emplace_back(file_name_buf.get());
  }

  bool has_valid_mappings =
      src_map_obj
          ->Get(context, v8::String::NewFromUtf8Literal(v8_isolate, "mappings"))
          .ToLocal(&mappings_value) &&
      mappings_value->IsString();
  if (!has_valid_mappings) return;

  v8::Local<v8::String> mappings = v8::Local<v8::String>::Cast(mappings_value);
  int mappings_sz = mappings->Utf8Length(v8_isolate);
  std::unique_ptr<char[]> mappings_buf(new char[mappings_sz + 1]);
  mappings->WriteUtf8(v8_isolate, mappings_buf.get());
  mappings_buf.get()[mappings_sz] = '\0';

  valid_ = DecodeMapping(mappings_buf.get());
}

size_t WasmModuleSourceMap::GetSourceLine(size_t wasm_offset) const {
  std::vector<std::size_t>::const_iterator up =
      std::upper_bound(offsets.begin(), offsets.end(), wasm_offset);
  CHECK_NE(offsets.begin(), up);
  size_t source_idx = up - offsets.begin() - 1;
  return source_row[source_idx];
}

std::string WasmModuleSourceMap::GetFilename(size_t wasm_offset) const {
  std::vector<size_t>::const_iterator up =
      std::upper_bound(offsets.begin(), offsets.end(), wasm_offset);
  CHECK_NE(offsets.begin(), up);
  size_t offset_idx = up - offsets.begin() - 1;
  size_t filename_idx = file_idxs[offset_idx];
  return filenames[filename_idx];
}

bool WasmModuleSourceMap::HasSource(size_t start, size_t end) const {
  return start <= *(offsets.end() - 1) && end > *offsets.begin();
}

bool WasmModuleSourceMap::HasValidEntry(size_t start, size_t addr) const {
  std::vector<size_t>::const_iterator up =
      std::upper_bound(offsets.begin(), offsets.end(), addr);
  if (up == offsets.begin()) return false;
  size_t offset_idx = up - offsets.begin() - 1;
  size_t entry_offset = offsets[offset_idx];
  if (entry_offset < start) return false;
  return true;
}

bool WasmModuleSourceMap::DecodeMapping(const std::string& s) {
  size_t pos = 0, gen_col = 0, file_idx = 0, ori_line = 0;
  int32_t qnt = 0;

  while (pos < s.size()) {
    // Skip redundant commas.
    if (s[pos] == ',') {
      ++pos;
      continue;
    }
    if ((qnt = base::VLQBase64Decode(s.c_str(), s.size(), &pos)) ==
        std::numeric_limits<int32_t>::min())
      return false;
    gen_col += qnt;
    if ((qnt = base::VLQBase64Decode(s.c_str(), s.size(), &pos)) ==
        std::numeric_limits<int32_t>::min())
      return false;
    file_idx += qnt;
    if ((qnt = base::VLQBase64Decode(s.c_str(), s.size(), &pos)) ==
        std::numeric_limits<int32_t>::min())
      return false;
    ori_line += qnt;
    // Column number in source file is always 0 in source map generated by
    // Emscripten. We just decode this value without further usage of it.
    if ((qnt = base::VLQBase64Decode(s.c_str(), s.size(), &pos)) ==
        std::numeric_limits<int32_t>::min())
      return false;

    if (pos < s.size() && s[pos] != ',') return false;
    pos++;

    file_idxs.push_back(file_idx);
    source_row.push_back(ori_line);
    offsets.push_back(gen_col);
  }
  return true;
}

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