v8-debugger-script.cc 14.5 KB
Newer Older
1 2 3 4
// 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.

5
#include "src/inspector/v8-debugger-script.h"
6

7
#include "src/base/memory.h"
8
#include "src/inspector/inspected-context.h"
9
#include "src/inspector/protocol/Debugger.h"
10
#include "src/inspector/string-util.h"
11
#include "src/inspector/v8-debugger-agent-impl.h"
12
#include "src/inspector/v8-inspector-impl.h"
13 14 15

namespace v8_inspector {

16
namespace {
17

18
const char kGlobalDebuggerScriptHandleLabel[] = "DevTools debugger";
19

20 21 22 23
// 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
24
String16 calculateHash(v8::Isolate* isolate, v8::Local<v8::String> source) {
25 26 27 28 29 30 31 32 33 34
  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};

35
  const size_t hashesSize = arraysize(hashes);
36 37

  size_t current = 0;
38 39 40 41 42

  std::unique_ptr<UChar[]> buffer(new UChar[source->Length()]);
  int written = source->Write(
      isolate, reinterpret_cast<uint16_t*>(buffer.get()), 0, source->Length());

43
  const uint32_t* data = nullptr;
44 45
  size_t sizeInBytes = sizeof(UChar) * written;
  data = reinterpret_cast<const uint32_t*>(buffer.get());
46
  for (size_t i = 0; i < sizeInBytes / 4; ++i) {
47
    uint32_t d = v8::base::ReadUnalignedValue<uint32_t>(
48
        reinterpret_cast<v8::internal::Address>(data + i));
49
#if V8_TARGET_LITTLE_ENDIAN
50
    uint32_t v = d;
51
#else
52
    uint32_t v = (d << 16) | (d >> 16);
53
#endif
54 55 56 57 58 59 60
    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;
61
    const uint8_t* data_8b = reinterpret_cast<const uint8_t*>(data);
62 63
    for (size_t i = sizeInBytes - sizeInBytes % 4; i < sizeInBytes; ++i) {
      v <<= 8;
64
#if V8_TARGET_LITTLE_ENDIAN
65
      v |= data_8b[i];
66 67
#else
      if (i % 2) {
68
        v |= data_8b[i - 1];
69
      } else {
70
        v |= data_8b[i + 1];
71 72
      }
#endif
73 74 75 76 77 78 79 80 81 82 83
    }
    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;
84
  for (size_t i = 0; i < hashesSize; ++i)
85
    hash.appendUnsignedAsHex(static_cast<uint32_t>(hashes[i]));
86 87 88
  return hash.toString();
}

89 90 91 92 93
class ActualScript : public V8DebuggerScript {
  friend class V8DebuggerScript;

 public:
  ActualScript(v8::Isolate* isolate, v8::Local<v8::debug::Script> script,
94 95
               bool isLiveEdit, V8DebuggerAgentImpl* agent,
               V8InspectorClient* client)
96
      : V8DebuggerScript(isolate, String16::fromInteger(script->Id()),
97 98
                         GetScriptURL(isolate, script, client),
                         GetScriptName(isolate, script, client)),
99
        m_agent(agent),
100
        m_isLiveEdit(isLiveEdit) {
101
    Initialize(script);
102
  }
103

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

107 108 109 110 111 112 113 114 115 116 117 118
  String16 source(size_t pos, size_t len) const override {
    v8::HandleScope scope(m_isolate);
    v8::Local<v8::String> v8Source;
    if (!script()->Source().ToLocal(&v8Source)) return String16();
    if (pos >= static_cast<size_t>(v8Source->Length())) return String16();
    size_t substringLength =
        std::min(len, static_cast<size_t>(v8Source->Length()) - pos);
    std::unique_ptr<UChar[]> buffer(new UChar[substringLength]);
    v8Source->Write(m_isolate, reinterpret_cast<uint16_t*>(buffer.get()),
                    static_cast<int>(pos), static_cast<int>(substringLength));
    return String16(buffer.get(), substringLength);
  }
119 120 121
  Language getLanguage() const override { return m_language; }

#if V8_ENABLE_WEBASSEMBLY
122 123 124 125 126 127
  v8::Maybe<v8::MemorySpan<const uint8_t>> wasmBytecode() const override {
    v8::HandleScope scope(m_isolate);
    auto script = this->script();
    if (!script->IsWasm()) return v8::Nothing<v8::MemorySpan<const uint8_t>>();
    return v8::Just(v8::debug::WasmScript::Cast(*script)->Bytecode());
  }
128

129 130 131 132 133 134 135
  v8::Maybe<v8::debug::WasmScript::DebugSymbolsType> getDebugSymbolsType()
      const override {
    auto script = this->script();
    if (!script->IsWasm())
      return v8::Nothing<v8::debug::WasmScript::DebugSymbolsType>();
    return v8::Just(v8::debug::WasmScript::Cast(*script)->GetDebugSymbolType());
  }
136

137 138 139 140 141 142 143 144
  v8::Maybe<String16> getExternalDebugSymbolsURL() const override {
    auto script = this->script();
    if (!script->IsWasm()) return v8::Nothing<String16>();
    v8::MemorySpan<const char> external_url =
        v8::debug::WasmScript::Cast(*script)->ExternalSymbolsURL();
    if (external_url.size() == 0) return v8::Nothing<String16>();
    return v8::Just(String16(external_url.data(), external_url.size()));
  }
145 146
#endif  // V8_ENABLE_WEBASSEMBLY

147
  int startLine() const override { return m_startLine; }
148
  int startColumn() const override { return m_startColumn; }
149
  int endLine() const override { return m_endLine; }
150
  int endColumn() const override { return m_endColumn; }
151
  int codeOffset() const override {
152 153 154 155 156 157
#if V8_ENABLE_WEBASSEMBLY
    if (script()->IsWasm()) {
      return v8::debug::WasmScript::Cast(*script())->CodeOffset();
    }
#endif  // V8_ENABLE_WEBASSEMBLY
    return 0;
158
  }
159
  bool isSourceLoadedLazily() const override { return false; }
160
  int length() const override {
161
    auto script = this->script();
162
#if V8_ENABLE_WEBASSEMBLY
163 164 165 166
    if (script->IsWasm()) {
      return static_cast<int>(
          v8::debug::WasmScript::Cast(*script)->Bytecode().size());
    }
167
#endif  // V8_ENABLE_WEBASSEMBLY
168 169
    v8::HandleScope scope(m_isolate);
    v8::Local<v8::String> v8Source;
170
    return script->Source().ToLocal(&v8Source) ? v8Source->Length() : 0;
171
  }
172

173 174 175 176 177 178 179 180
  const String16& sourceMappingURL() const override {
    return m_sourceMappingURL;
  }

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

181
  void setSource(const String16& newSource, bool preview,
182 183
                 v8::debug::LiveEditResult* result) override {
    v8::EscapableHandleScope scope(m_isolate);
184
    v8::Local<v8::String> v8Source = toV8String(m_isolate, newSource);
185 186
    if (!m_script.Get(m_isolate)->SetScriptSource(v8Source, preview, result)) {
      result->message = scope.Escape(result->message);
187 188
      return;
    }
189 190 191
    // NOP if preview or unchanged source (diffs.empty() in PatchScript)
    if (preview || result->script.IsEmpty()) return;

192
    m_hash = String16();
193
    Initialize(scope.Escape(result->script));
194 195
  }

196 197
  bool getPossibleBreakpoints(
      const v8::debug::Location& start, const v8::debug::Location& end,
198
      bool restrictToFunction,
199
      std::vector<v8::debug::BreakLocation>* locations) override {
200 201
    v8::HandleScope scope(m_isolate);
    v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
    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;
233 234
  }

235 236 237 238 239
  void resetBlackboxedStateCache() override {
    v8::HandleScope scope(m_isolate);
    v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate));
  }

240 241 242 243 244 245 246 247 248 249 250
  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);
  }

251 252 253 254 255 256 257
  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);
  }

258
  bool setInstrumentationBreakpoint(int* id) const override {
259
    v8::HandleScope scope(m_isolate);
260
    return script()->SetInstrumentationBreakpoint(id);
261 262
  }

263
  const String16& hash() const override {
264 265 266
    if (!m_hash.isEmpty()) return m_hash;
    v8::HandleScope scope(m_isolate);
    v8::Local<v8::String> v8Source;
267 268
    if (!script()->Source().ToLocal(&v8Source)) {
      v8Source = v8::String::Empty(m_isolate);
269
    }
270
    m_hash = calculateHash(m_isolate, v8Source);
271 272 273 274
    DCHECK(!m_hash.isEmpty());
    return m_hash;
  }

275
 private:
276 277 278
  static String16 GetScriptURL(v8::Isolate* isolate,
                               v8::Local<v8::debug::Script> script,
                               V8InspectorClient* client) {
279 280 281
    v8::Local<v8::String> sourceURL;
    if (script->SourceURL().ToLocal(&sourceURL) && sourceURL->Length() > 0)
      return toProtocolString(isolate, sourceURL);
282 283 284 285 286 287
    return GetScriptName(isolate, script, client);
  }

  static String16 GetScriptName(v8::Isolate* isolate,
                                v8::Local<v8::debug::Script> script,
                                V8InspectorClient* client) {
288 289 290 291 292 293 294
    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;
    }
295 296
    return String16();
  }
297

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

302 303
  void Initialize(v8::Local<v8::debug::Script> script) {
    v8::Local<v8::String> tmp;
304 305
    m_hasSourceURLComment =
        script->SourceURL().ToLocal(&tmp) && tmp->Length() > 0;
306
    if (script->SourceMappingURL().ToLocal(&tmp))
307
      m_sourceMappingURL = toProtocolString(m_isolate, tmp);
308 309 310 311
    m_startLine = script->LineOffset();
    m_startColumn = script->ColumnOffset();
    std::vector<int> lineEnds = script->LineEnds();
    if (lineEnds.size()) {
312
      int source_length = lineEnds[lineEnds.size() - 1];
313 314 315 316 317 318
      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;
      }
319
#if V8_ENABLE_WEBASSEMBLY
320 321 322 323 324 325
    } else if (script->IsWasm()) {
      DCHECK_EQ(0, m_startLine);
      DCHECK_EQ(0, m_startColumn);
      m_endLine = 0;
      m_endColumn = static_cast<int>(
          v8::debug::WasmScript::Cast(*script)->Bytecode().size());
326
#endif  // V8_ENABLE_WEBASSEMBLY
327 328 329 330 331 332
    } else {
      m_endLine = m_startLine;
      m_endColumn = m_startColumn;
    }

    USE(script->ContextId().To(&m_executionContextId));
333 334
    m_language = V8DebuggerScript::Language::JavaScript;
#if V8_ENABLE_WEBASSEMBLY
335 336 337
    if (script->IsWasm()) {
      m_language = V8DebuggerScript::Language::WebAssembly;
    }
338
#endif  // V8_ENABLE_WEBASSEMBLY
339 340 341 342 343 344 345

    m_isModule = script->IsModule();

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

346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
  void MakeWeak() override {
    m_script.SetWeak(
        this,
        [](const v8::WeakCallbackInfo<ActualScript>& data) {
          data.GetParameter()->WeakCallback();
        },
        v8::WeakCallbackType::kFinalizer);
  }

  void WeakCallback() {
    m_script.ClearWeak();
    m_agent->ScriptCollected(this);
  }

  V8DebuggerAgentImpl* m_agent;
361
  String16 m_sourceMappingURL;
362
  Language m_language;
363
  bool m_isLiveEdit = false;
364
  bool m_isModule = false;
365 366 367 368 369
  mutable String16 m_hash;
  int m_startLine = 0;
  int m_startColumn = 0;
  int m_endLine = 0;
  int m_endColumn = 0;
370 371 372 373 374 375 376
  v8::Global<v8::debug::Script> m_script;
};

}  // namespace

std::unique_ptr<V8DebuggerScript> V8DebuggerScript::Create(
    v8::Isolate* isolate, v8::Local<v8::debug::Script> scriptObj,
377
    bool isLiveEdit, V8DebuggerAgentImpl* agent, V8InspectorClient* client) {
378 379
  return std::make_unique<ActualScript>(isolate, scriptObj, isLiveEdit, agent,
                                        client);
380 381 382
}

V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id,
383 384 385 386 387
                                   String16 url, String16 embedderName)
    : m_id(std::move(id)),
      m_url(std::move(url)),
      m_isolate(isolate),
      m_embedderName(embedderName) {}
388

389
V8DebuggerScript::~V8DebuggerScript() = default;
390 391

void V8DebuggerScript::setSourceURL(const String16& sourceURL) {
392 393 394 395
  if (sourceURL.length() > 0) {
    m_hasSourceURLComment = true;
    m_url = sourceURL;
  }
396 397
}

398
#if V8_ENABLE_WEBASSEMBLY
399 400 401 402
void V8DebuggerScript::removeWasmBreakpoint(int id) {
  v8::HandleScope scope(m_isolate);
  script()->RemoveWasmBreakpoint(id);
}
403
#endif  // V8_ENABLE_WEBASSEMBLY
404

405
}  // namespace v8_inspector