普通文本  |  445行  |  16.29 KB

// Copyright 2014 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/v8-debugger-script.h"

#include "src/inspector/inspected-context.h"
#include "src/inspector/string-util.h"
#include "src/inspector/v8-inspector-impl.h"
#include "src/inspector/wasm-translation.h"
#include "src/v8memory.h"

namespace v8_inspector {

namespace {

const char kGlobalDebuggerScriptHandleLabel[] = "DevTools debugger";

// Hash algorithm for substrings is described in "Über die Komplexität der
// Multiplikation in
// eingeschränkten Branchingprogrammmodellen" by Woelfe.
// http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000
String16 calculateHash(const String16& str) {
  static uint64_t prime[] = {0x3FB75161, 0xAB1F4E4F, 0x82675BC5, 0xCD924D35,
                             0x81ABE279};
  static uint64_t random[] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476,
                              0xC3D2E1F0};
  static uint32_t randomOdd[] = {0xB4663807, 0xCC322BF5, 0xD4F91BBD, 0xA7BEA11D,
                                 0x8F462907};

  uint64_t hashes[] = {0, 0, 0, 0, 0};
  uint64_t zi[] = {1, 1, 1, 1, 1};

  const size_t hashesSize = arraysize(hashes);

  size_t current = 0;
  const uint32_t* data = nullptr;
  size_t sizeInBytes = sizeof(UChar) * str.length();
  data = reinterpret_cast<const uint32_t*>(str.characters16());
  for (size_t i = 0; i < sizeInBytes / 4; ++i) {
    uint32_t d = v8::internal::ReadUnalignedUInt32(
        reinterpret_cast<v8::internal::Address>(data + i));
#if V8_TARGET_LITTLE_ENDIAN
    uint32_t v = d;
#else
    uint32_t v = (d << 16) | (d >> 16);
#endif
    uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
    hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
    zi[current] = (zi[current] * random[current]) % prime[current];
    current = current == hashesSize - 1 ? 0 : current + 1;
  }
  if (sizeInBytes % 4) {
    uint32_t v = 0;
    const uint8_t* data_8b = reinterpret_cast<const uint8_t*>(data);
    for (size_t i = sizeInBytes - sizeInBytes % 4; i < sizeInBytes; ++i) {
      v <<= 8;
#if V8_TARGET_LITTLE_ENDIAN
      v |= data_8b[i];
#else
      if (i % 2) {
        v |= data_8b[i - 1];
      } else {
        v |= data_8b[i + 1];
      }
#endif
    }
    uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
    hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
    zi[current] = (zi[current] * random[current]) % prime[current];
    current = current == hashesSize - 1 ? 0 : current + 1;
  }

  for (size_t i = 0; i < hashesSize; ++i)
    hashes[i] = (hashes[i] + zi[i] * (prime[i] - 1)) % prime[i];

  String16Builder hash;
  for (size_t i = 0; i < hashesSize; ++i)
    hash.appendUnsignedAsHex((uint32_t)hashes[i]);
  return hash.toString();
}

void TranslateProtocolLocationToV8Location(WasmTranslation* wasmTranslation,
                                           v8::debug::Location* loc,
                                           const String16& scriptId,
                                           const String16& expectedV8ScriptId) {
  if (loc->IsEmpty()) return;
  int lineNumber = loc->GetLineNumber();
  int columnNumber = loc->GetColumnNumber();
  String16 translatedScriptId = scriptId;
  wasmTranslation->TranslateProtocolLocationToWasmScriptLocation(
      &translatedScriptId, &lineNumber, &columnNumber);
  DCHECK_EQ(expectedV8ScriptId.utf8(), translatedScriptId.utf8());
  *loc = v8::debug::Location(lineNumber, columnNumber);
}

void TranslateV8LocationToProtocolLocation(
    WasmTranslation* wasmTranslation, v8::debug::Location* loc,
    const String16& scriptId, const String16& expectedProtocolScriptId) {
  int lineNumber = loc->GetLineNumber();
  int columnNumber = loc->GetColumnNumber();
  String16 translatedScriptId = scriptId;
  wasmTranslation->TranslateWasmScriptLocationToProtocolLocation(
      &translatedScriptId, &lineNumber, &columnNumber);
  DCHECK_EQ(expectedProtocolScriptId.utf8(), translatedScriptId.utf8());
  *loc = v8::debug::Location(lineNumber, columnNumber);
}

class ActualScript : public V8DebuggerScript {
  friend class V8DebuggerScript;

 public:
  ActualScript(v8::Isolate* isolate, v8::Local<v8::debug::Script> script,
               bool isLiveEdit, V8InspectorClient* client)
      : V8DebuggerScript(isolate, String16::fromInteger(script->Id()),
                         GetScriptURL(isolate, script, client)),
        m_isLiveEdit(isLiveEdit) {
    Initialize(script);
  }

  bool isLiveEdit() const override { return m_isLiveEdit; }
  bool isModule() const override { return m_isModule; }

  const String16& source() const override { return m_source; }
  int startLine() const override { return m_startLine; }
  int startColumn() const override { return m_startColumn; }
  int endLine() const override { return m_endLine; }
  int endColumn() const override { return m_endColumn; }
  bool isSourceLoadedLazily() const override { return false; }

  const String16& sourceMappingURL() const override {
    return m_sourceMappingURL;
  }

  void setSourceMappingURL(const String16& sourceMappingURL) override {
    m_sourceMappingURL = sourceMappingURL;
  }

  void setSource(const String16& newSource, bool preview,
                 v8::debug::LiveEditResult* result) override {
    DCHECK(!isModule());
    v8::EscapableHandleScope scope(m_isolate);
    v8::Local<v8::String> v8Source = toV8String(m_isolate, newSource);
    if (!m_script.Get(m_isolate)->SetScriptSource(v8Source, preview, result)) {
      result->message = scope.Escape(result->message);
      return;
    }
    if (preview) return;
    m_hash = String16();
    Initialize(scope.Escape(result->script));
  }

  bool getPossibleBreakpoints(
      const v8::debug::Location& start, const v8::debug::Location& end,
      bool restrictToFunction,
      std::vector<v8::debug::BreakLocation>* locations) override {
    v8::HandleScope scope(m_isolate);
    v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
    std::vector<v8::debug::BreakLocation> allLocations;
    if (!script->GetPossibleBreakpoints(start, end, restrictToFunction,
                                        &allLocations)) {
      return false;
    }
    if (!allLocations.size()) return true;
    v8::debug::BreakLocation current = allLocations[0];
    for (size_t i = 1; i < allLocations.size(); ++i) {
      if (allLocations[i].GetLineNumber() == current.GetLineNumber() &&
          allLocations[i].GetColumnNumber() == current.GetColumnNumber()) {
        if (allLocations[i].type() != v8::debug::kCommonBreakLocation) {
          DCHECK(allLocations[i].type() == v8::debug::kCallBreakLocation ||
                 allLocations[i].type() == v8::debug::kReturnBreakLocation);
          // debugger can returns more then one break location at the same
          // source location, e.g. foo() - in this case there are two break
          // locations before foo: for statement and for function call, we can
          // merge them for inspector and report only one with call type.
          current = allLocations[i];
        }
      } else {
        // we assume that returned break locations are sorted.
        DCHECK(
            allLocations[i].GetLineNumber() > current.GetLineNumber() ||
            (allLocations[i].GetColumnNumber() >= current.GetColumnNumber() &&
             allLocations[i].GetLineNumber() == current.GetLineNumber()));
        locations->push_back(current);
        current = allLocations[i];
      }
    }
    locations->push_back(current);
    return true;
  }

  void resetBlackboxedStateCache() override {
    v8::HandleScope scope(m_isolate);
    v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate));
  }

  int offset(int lineNumber, int columnNumber) const override {
    v8::HandleScope scope(m_isolate);
    return m_script.Get(m_isolate)->GetSourceOffset(
        v8::debug::Location(lineNumber, columnNumber));
  }

  v8::debug::Location location(int offset) const override {
    v8::HandleScope scope(m_isolate);
    return m_script.Get(m_isolate)->GetSourceLocation(offset);
  }

  bool setBreakpoint(const String16& condition, v8::debug::Location* location,
                     int* id) const override {
    v8::HandleScope scope(m_isolate);
    return script()->SetBreakpoint(toV8String(m_isolate, condition), location,
                                   id);
  }

  const String16& hash() const override {
    if (m_hash.isEmpty()) m_hash = calculateHash(source());
    DCHECK(!m_hash.isEmpty());
    return m_hash;
  }

 private:
  String16 GetScriptURL(v8::Isolate* isolate,
                        v8::Local<v8::debug::Script> script,
                        V8InspectorClient* client) {
    v8::Local<v8::String> sourceURL;
    if (script->SourceURL().ToLocal(&sourceURL) && sourceURL->Length() > 0)
      return toProtocolString(isolate, sourceURL);
    v8::Local<v8::String> v8Name;
    if (script->Name().ToLocal(&v8Name) && v8Name->Length() > 0) {
      String16 name = toProtocolString(isolate, v8Name);
      std::unique_ptr<StringBuffer> url =
          client->resourceNameToUrl(toStringView(name));
      return url ? toString16(url->string()) : name;
    }
    return String16();
  }

  v8::Local<v8::debug::Script> script() const override {
    return m_script.Get(m_isolate);
  }

  void Initialize(v8::Local<v8::debug::Script> script) {
    v8::Local<v8::String> tmp;
    m_hasSourceURLComment =
        script->SourceURL().ToLocal(&tmp) && tmp->Length() > 0;
    if (script->SourceMappingURL().ToLocal(&tmp))
      m_sourceMappingURL = toProtocolString(m_isolate, tmp);
    m_startLine = script->LineOffset();
    m_startColumn = script->ColumnOffset();
    std::vector<int> lineEnds = script->LineEnds();
    CHECK(lineEnds.size());
    int source_length = lineEnds[lineEnds.size() - 1];
    if (lineEnds.size()) {
      m_endLine = static_cast<int>(lineEnds.size()) + m_startLine - 1;
      if (lineEnds.size() > 1) {
        m_endColumn = source_length - lineEnds[lineEnds.size() - 2] - 1;
      } else {
        m_endColumn = source_length + m_startColumn;
      }
    } else {
      m_endLine = m_startLine;
      m_endColumn = m_startColumn;
    }

    USE(script->ContextId().To(&m_executionContextId));

    if (script->Source().ToLocal(&tmp)) {
      m_source = toProtocolString(m_isolate, tmp);
    }

    m_isModule = script->IsModule();

    m_script.Reset(m_isolate, script);
    m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
  }

  String16 m_sourceMappingURL;
  bool m_isLiveEdit = false;
  bool m_isModule = false;
  String16 m_source;
  mutable String16 m_hash;
  int m_startLine = 0;
  int m_startColumn = 0;
  int m_endLine = 0;
  int m_endColumn = 0;
  v8::Global<v8::debug::Script> m_script;
};

class WasmVirtualScript : public V8DebuggerScript {
  friend class V8DebuggerScript;

 public:
  WasmVirtualScript(v8::Isolate* isolate, WasmTranslation* wasmTranslation,
                    v8::Local<v8::debug::WasmScript> script, String16 id,
                    String16 url, int functionIndex)
      : V8DebuggerScript(isolate, std::move(id), std::move(url)),
        m_script(isolate, script),
        m_wasmTranslation(wasmTranslation),
        m_functionIndex(functionIndex) {
    m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
    m_executionContextId = script->ContextId().ToChecked();
  }

  const String16& sourceMappingURL() const override { return emptyString(); }
  bool isLiveEdit() const override { return false; }
  bool isModule() const override { return false; }
  void setSourceMappingURL(const String16&) override {}
  void setSource(const String16&, bool, v8::debug::LiveEditResult*) override {
    UNREACHABLE();
  }
  bool isSourceLoadedLazily() const override { return true; }
  const String16& source() const override {
    return m_wasmTranslation->GetSource(m_id, m_functionIndex);
  }
  int startLine() const override {
    return m_wasmTranslation->GetStartLine(m_id, m_functionIndex);
  }
  int startColumn() const override {
    return m_wasmTranslation->GetStartColumn(m_id, m_functionIndex);
  }
  int endLine() const override {
    return m_wasmTranslation->GetEndLine(m_id, m_functionIndex);
  }
  int endColumn() const override {
    return m_wasmTranslation->GetEndColumn(m_id, m_functionIndex);
  }

  bool getPossibleBreakpoints(
      const v8::debug::Location& start, const v8::debug::Location& end,
      bool restrictToFunction,
      std::vector<v8::debug::BreakLocation>* locations) override {
    v8::HandleScope scope(m_isolate);
    v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
    String16 v8ScriptId = String16::fromInteger(script->Id());

    v8::debug::Location translatedStart = start;
    TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedStart,
                                          scriptId(), v8ScriptId);

    v8::debug::Location translatedEnd = end;
    if (translatedEnd.IsEmpty()) {
      // Stop before the start of the next function.
      translatedEnd =
          v8::debug::Location(translatedStart.GetLineNumber() + 1, 0);
    } else {
      TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedEnd,
                                            scriptId(), v8ScriptId);
    }

    bool success = script->GetPossibleBreakpoints(
        translatedStart, translatedEnd, restrictToFunction, locations);
    for (v8::debug::BreakLocation& loc : *locations) {
      TranslateV8LocationToProtocolLocation(m_wasmTranslation, &loc, v8ScriptId,
                                            scriptId());
    }
    return success;
  }

  void resetBlackboxedStateCache() override {}

  int offset(int lineNumber, int columnNumber) const override {
    return kNoOffset;
  }

  v8::debug::Location location(int offset) const override {
    return v8::debug::Location();
  }

  bool setBreakpoint(const String16& condition, v8::debug::Location* location,
                     int* id) const override {
    v8::HandleScope scope(m_isolate);
    v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
    String16 v8ScriptId = String16::fromInteger(script->Id());

    TranslateProtocolLocationToV8Location(m_wasmTranslation, location,
                                          scriptId(), v8ScriptId);
    if (location->IsEmpty()) return false;
    if (!script->SetBreakpoint(toV8String(m_isolate, condition), location, id))
      return false;
    TranslateV8LocationToProtocolLocation(m_wasmTranslation, location,
                                          v8ScriptId, scriptId());
    return true;
  }

  const String16& hash() const override {
    if (m_hash.isEmpty()) {
      m_hash = m_wasmTranslation->GetHash(m_id, m_functionIndex);
    }
    return m_hash;
  }

 private:
  static const String16& emptyString() {
    static const String16 singleEmptyString;
    return singleEmptyString;
  }

  v8::Local<v8::debug::Script> script() const override {
    return m_script.Get(m_isolate);
  }

  v8::Global<v8::debug::WasmScript> m_script;
  WasmTranslation* m_wasmTranslation;
  int m_functionIndex;
  mutable String16 m_hash;
};

}  // namespace

std::unique_ptr<V8DebuggerScript> V8DebuggerScript::Create(
    v8::Isolate* isolate, v8::Local<v8::debug::Script> scriptObj,
    bool isLiveEdit, V8InspectorClient* client) {
  return std::unique_ptr<ActualScript>(
      new ActualScript(isolate, scriptObj, isLiveEdit, client));
}

std::unique_ptr<V8DebuggerScript> V8DebuggerScript::CreateWasm(
    v8::Isolate* isolate, WasmTranslation* wasmTranslation,
    v8::Local<v8::debug::WasmScript> underlyingScript, String16 id,
    String16 url, int functionIndex) {
  return std::unique_ptr<WasmVirtualScript>(
      new WasmVirtualScript(isolate, wasmTranslation, underlyingScript,
                            std::move(id), std::move(url), functionIndex));
}

V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id,
                                   String16 url)
    : m_id(std::move(id)), m_url(std::move(url)), m_isolate(isolate) {}

V8DebuggerScript::~V8DebuggerScript() {}

void V8DebuggerScript::setSourceURL(const String16& sourceURL) {
  if (sourceURL.length() > 0) {
    m_hasSourceURLComment = true;
    m_url = sourceURL;
  }
}

bool V8DebuggerScript::setBreakpoint(const String16& condition,
                                     v8::debug::Location* loc, int* id) const {
  v8::HandleScope scope(m_isolate);
  return script()->SetBreakpoint(toV8String(m_isolate, condition), loc, id);
}
}  // namespace v8_inspector