1// Copyright 2014 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
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
14namespace v8_inspector {
15
16namespace {
17
18const char kGlobalDebuggerScriptHandleLabel[] = "DevTools debugger";
19
20// Hash algorithm for substrings is described in "Über die Komplexität der
21// Multiplikation in
22// eingeschränkten Branchingprogrammmodellen" by Woelfe.
23// http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000
24String16 calculateHash(v8::Isolate* isolate, v8::Local<v8::String> source) {
25  static uint64_t prime[] = {0x3FB75161, 0xAB1F4E4F, 0x82675BC5, 0xCD924D35,
26                             0x81ABE279};
27  static uint64_t random[] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476,
28                              0xC3D2E1F0};
29  static uint32_t randomOdd[] = {0xB4663807, 0xCC322BF5, 0xD4F91BBD, 0xA7BEA11D,
30                                 0x8F462907};
31
32  uint64_t hashes[] = {0, 0, 0, 0, 0};
33  uint64_t zi[] = {1, 1, 1, 1, 1};
34
35  const size_t hashesSize = arraysize(hashes);
36
37  size_t current = 0;
38
39  std::unique_ptr<UChar[]> buffer(new UChar[source->Length()]);
40  int written = source->Write(
41      isolate, reinterpret_cast<uint16_t*>(buffer.get()), 0, source->Length());
42
43  const uint32_t* data = nullptr;
44  size_t sizeInBytes = sizeof(UChar) * written;
45  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    uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
55    hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
56    zi[current] = (zi[current] * random[current]) % prime[current];
57    current = current == hashesSize - 1 ? 0 : current + 1;
58  }
59  if (sizeInBytes % 4) {
60    uint32_t v = 0;
61    const uint8_t* data_8b = reinterpret_cast<const uint8_t*>(data);
62    for (size_t i = sizeInBytes - sizeInBytes % 4; i < sizeInBytes; ++i) {
63      v <<= 8;
64#if V8_TARGET_LITTLE_ENDIAN
65      v |= data_8b[i];
66#else
67      if (i % 2) {
68        v |= data_8b[i - 1];
69      } else {
70        v |= data_8b[i + 1];
71      }
72#endif
73    }
74    uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
75    hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
76    zi[current] = (zi[current] * random[current]) % prime[current];
77    current = current == hashesSize - 1 ? 0 : current + 1;
78  }
79
80  for (size_t i = 0; i < hashesSize; ++i)
81    hashes[i] = (hashes[i] + zi[i] * (prime[i] - 1)) % prime[i];
82
83  String16Builder hash;
84  for (size_t i = 0; i < hashesSize; ++i)
85    hash.appendUnsignedAsHex(static_cast<uint32_t>(hashes[i]));
86  return hash.toString();
87}
88
89class ActualScript : public V8DebuggerScript {
90  friend class V8DebuggerScript;
91
92 public:
93  ActualScript(v8::Isolate* isolate, v8::Local<v8::debug::Script> script,
94               bool isLiveEdit, V8DebuggerAgentImpl* agent,
95               V8InspectorClient* client)
96      : V8DebuggerScript(isolate, String16::fromInteger(script->Id()),
97                         GetScriptURL(isolate, script, client),
98                         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  String16 source(size_t pos, size_t len) const override {
108    v8::HandleScope scope(m_isolate);
109    v8::Local<v8::String> v8Source;
110    if (!m_scriptSource.Get(m_isolate)->JavaScriptCode().ToLocal(&v8Source)) {
111      return String16();
112    }
113    if (pos >= static_cast<size_t>(v8Source->Length())) return String16();
114    size_t substringLength =
115        std::min(len, static_cast<size_t>(v8Source->Length()) - pos);
116    std::unique_ptr<UChar[]> buffer(new UChar[substringLength]);
117    v8Source->Write(m_isolate, reinterpret_cast<uint16_t*>(buffer.get()),
118                    static_cast<int>(pos), static_cast<int>(substringLength));
119    return String16(buffer.get(), substringLength);
120  }
121  Language getLanguage() const override { return m_language; }
122
123#if V8_ENABLE_WEBASSEMBLY
124  v8::Maybe<v8::MemorySpan<const uint8_t>> wasmBytecode() const override {
125    v8::HandleScope scope(m_isolate);
126    v8::MemorySpan<const uint8_t> bytecode;
127    if (m_scriptSource.Get(m_isolate)->WasmBytecode().To(&bytecode)) {
128      return v8::Just(bytecode);
129    }
130    return v8::Nothing<v8::MemorySpan<const uint8_t>>();
131  }
132
133  v8::Maybe<v8::debug::WasmScript::DebugSymbolsType> getDebugSymbolsType()
134      const override {
135    auto script = this->script();
136    if (!script->IsWasm())
137      return v8::Nothing<v8::debug::WasmScript::DebugSymbolsType>();
138    return v8::Just(v8::debug::WasmScript::Cast(*script)->GetDebugSymbolType());
139  }
140
141  v8::Maybe<String16> getExternalDebugSymbolsURL() const override {
142    auto script = this->script();
143    if (!script->IsWasm()) return v8::Nothing<String16>();
144    v8::MemorySpan<const char> external_url =
145        v8::debug::WasmScript::Cast(*script)->ExternalSymbolsURL();
146    if (external_url.size() == 0) return v8::Nothing<String16>();
147    return v8::Just(String16(external_url.data(), external_url.size()));
148  }
149#endif  // V8_ENABLE_WEBASSEMBLY
150
151  int startLine() const override { return m_startLine; }
152  int startColumn() const override { return m_startColumn; }
153  int endLine() const override { return m_endLine; }
154  int endColumn() const override { return m_endColumn; }
155  int codeOffset() const override {
156#if V8_ENABLE_WEBASSEMBLY
157    if (script()->IsWasm()) {
158      return v8::debug::WasmScript::Cast(*script())->CodeOffset();
159    }
160#endif  // V8_ENABLE_WEBASSEMBLY
161    return 0;
162  }
163  int length() const override {
164    return static_cast<int>(m_scriptSource.Get(m_isolate)->Length());
165  }
166
167  const String16& sourceMappingURL() const override {
168    return m_sourceMappingURL;
169  }
170
171  void setSourceMappingURL(const String16& sourceMappingURL) override {
172    m_sourceMappingURL = sourceMappingURL;
173  }
174
175  void setSource(const String16& newSource, bool preview,
176                 v8::debug::LiveEditResult* result) override {
177    v8::EscapableHandleScope scope(m_isolate);
178    v8::Local<v8::String> v8Source = toV8String(m_isolate, newSource);
179    if (!m_script.Get(m_isolate)->SetScriptSource(v8Source, preview, result)) {
180      result->message = scope.Escape(result->message);
181      return;
182    }
183    // NOP if preview or unchanged source (diffs.empty() in PatchScript)
184    if (preview || result->script.IsEmpty()) return;
185
186    m_hash = String16();
187    Initialize(scope.Escape(result->script));
188  }
189
190  bool getPossibleBreakpoints(
191      const v8::debug::Location& start, const v8::debug::Location& end,
192      bool restrictToFunction,
193      std::vector<v8::debug::BreakLocation>* locations) override {
194    v8::HandleScope scope(m_isolate);
195    v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
196    std::vector<v8::debug::BreakLocation> allLocations;
197    if (!script->GetPossibleBreakpoints(start, end, restrictToFunction,
198                                        &allLocations)) {
199      return false;
200    }
201    if (!allLocations.size()) return true;
202    v8::debug::BreakLocation current = allLocations[0];
203    for (size_t i = 1; i < allLocations.size(); ++i) {
204      if (allLocations[i].GetLineNumber() == current.GetLineNumber() &&
205          allLocations[i].GetColumnNumber() == current.GetColumnNumber()) {
206        if (allLocations[i].type() != v8::debug::kCommonBreakLocation) {
207          DCHECK(allLocations[i].type() == v8::debug::kCallBreakLocation ||
208                 allLocations[i].type() == v8::debug::kReturnBreakLocation);
209          // debugger can returns more then one break location at the same
210          // source location, e.g. foo() - in this case there are two break
211          // locations before foo: for statement and for function call, we can
212          // merge them for inspector and report only one with call type.
213          current = allLocations[i];
214        }
215      } else {
216        // we assume that returned break locations are sorted.
217        DCHECK(
218            allLocations[i].GetLineNumber() > current.GetLineNumber() ||
219            (allLocations[i].GetColumnNumber() >= current.GetColumnNumber() &&
220             allLocations[i].GetLineNumber() == current.GetLineNumber()));
221        locations->push_back(current);
222        current = allLocations[i];
223      }
224    }
225    locations->push_back(current);
226    return true;
227  }
228
229  void resetBlackboxedStateCache() override {
230    v8::HandleScope scope(m_isolate);
231    v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate));
232  }
233
234  int offset(int lineNumber, int columnNumber) const override {
235    v8::HandleScope scope(m_isolate);
236    return m_script.Get(m_isolate)->GetSourceOffset(
237        v8::debug::Location(lineNumber, columnNumber));
238  }
239
240  v8::debug::Location location(int offset) const override {
241    v8::HandleScope scope(m_isolate);
242    return m_script.Get(m_isolate)->GetSourceLocation(offset);
243  }
244
245  bool setBreakpoint(const String16& condition, v8::debug::Location* location,
246                     int* id) const override {
247    v8::HandleScope scope(m_isolate);
248    return script()->SetBreakpoint(toV8String(m_isolate, condition), location,
249                                   id);
250  }
251
252  bool setInstrumentationBreakpoint(int* id) const override {
253    v8::HandleScope scope(m_isolate);
254    return script()->SetInstrumentationBreakpoint(id);
255  }
256
257  const String16& hash() const override {
258    if (!m_hash.isEmpty()) return m_hash;
259    v8::HandleScope scope(m_isolate);
260    v8::Local<v8::String> v8Source;
261    if (!m_scriptSource.Get(m_isolate)->JavaScriptCode().ToLocal(&v8Source)) {
262      v8Source = v8::String::Empty(m_isolate);
263    }
264    m_hash = calculateHash(m_isolate, v8Source);
265    DCHECK(!m_hash.isEmpty());
266    return m_hash;
267  }
268
269 private:
270  static String16 GetScriptURL(v8::Isolate* isolate,
271                               v8::Local<v8::debug::Script> script,
272                               V8InspectorClient* client) {
273    v8::Local<v8::String> sourceURL;
274    if (script->SourceURL().ToLocal(&sourceURL) && sourceURL->Length() > 0)
275      return toProtocolString(isolate, sourceURL);
276    return GetScriptName(isolate, script, client);
277  }
278
279  static String16 GetScriptName(v8::Isolate* isolate,
280                                v8::Local<v8::debug::Script> script,
281                                V8InspectorClient* client) {
282    v8::Local<v8::String> v8Name;
283    if (script->Name().ToLocal(&v8Name) && v8Name->Length() > 0) {
284      String16 name = toProtocolString(isolate, v8Name);
285      std::unique_ptr<StringBuffer> url =
286          client->resourceNameToUrl(toStringView(name));
287      return url ? toString16(url->string()) : name;
288    }
289    return String16();
290  }
291
292  v8::Local<v8::debug::Script> script() const override {
293    return m_script.Get(m_isolate);
294  }
295
296  void Initialize(v8::Local<v8::debug::Script> script) {
297    v8::Local<v8::String> tmp;
298    m_hasSourceURLComment =
299        script->SourceURL().ToLocal(&tmp) && tmp->Length() > 0;
300    if (script->SourceMappingURL().ToLocal(&tmp))
301      m_sourceMappingURL = toProtocolString(m_isolate, tmp);
302    m_startLine = script->StartLine();
303    m_startColumn = script->StartColumn();
304    m_endLine = script->EndLine();
305    m_endColumn = script->EndColumn();
306
307    USE(script->ContextId().To(&m_executionContextId));
308    m_language = V8DebuggerScript::Language::JavaScript;
309#if V8_ENABLE_WEBASSEMBLY
310    if (script->IsWasm()) {
311      m_language = V8DebuggerScript::Language::WebAssembly;
312    }
313#endif  // V8_ENABLE_WEBASSEMBLY
314
315    m_isModule = script->IsModule();
316
317    m_script.Reset(m_isolate, script);
318    m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
319    m_scriptSource.Reset(m_isolate, script->Source());
320    m_scriptSource.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
321  }
322
323  void MakeWeak() override {
324    m_script.SetWeak(
325        this,
326        [](const v8::WeakCallbackInfo<ActualScript>& data) {
327          data.GetParameter()->WeakCallback();
328        },
329        v8::WeakCallbackType::kParameter);
330  }
331
332  void WeakCallback() {
333    m_script.Reset();
334    m_agent->ScriptCollected(this);
335  }
336
337  V8DebuggerAgentImpl* m_agent;
338  String16 m_sourceMappingURL;
339  Language m_language;
340  bool m_isLiveEdit = false;
341  bool m_isModule = false;
342  mutable String16 m_hash;
343  int m_startLine = 0;
344  int m_startColumn = 0;
345  int m_endLine = 0;
346  int m_endColumn = 0;
347  v8::Global<v8::debug::Script> m_script;
348  v8::Global<v8::debug::ScriptSource> m_scriptSource;
349};
350
351}  // namespace
352
353std::unique_ptr<V8DebuggerScript> V8DebuggerScript::Create(
354    v8::Isolate* isolate, v8::Local<v8::debug::Script> scriptObj,
355    bool isLiveEdit, V8DebuggerAgentImpl* agent, V8InspectorClient* client) {
356  return std::make_unique<ActualScript>(isolate, scriptObj, isLiveEdit, agent,
357                                        client);
358}
359
360V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id,
361                                   String16 url, String16 embedderName)
362    : m_id(std::move(id)),
363      m_url(std::move(url)),
364      m_isolate(isolate),
365      m_embedderName(embedderName) {}
366
367V8DebuggerScript::~V8DebuggerScript() = default;
368
369void V8DebuggerScript::setSourceURL(const String16& sourceURL) {
370  if (sourceURL.length() > 0) {
371    m_hasSourceURLComment = true;
372    m_url = sourceURL;
373  }
374}
375
376#if V8_ENABLE_WEBASSEMBLY
377void V8DebuggerScript::removeWasmBreakpoint(int id) {
378  v8::HandleScope scope(m_isolate);
379  script()->RemoveWasmBreakpoint(id);
380}
381#endif  // V8_ENABLE_WEBASSEMBLY
382
383}  // namespace v8_inspector
384