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 
14 namespace v8_inspector {
15 
16 namespace {
17 
18 const 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
calculateHash(v8::Isolate* isolate, v8::Local<v8::String> source)24 String16 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 
89 class ActualScript : public V8DebuggerScript {
90   friend class V8DebuggerScript;
91 
92  public:
ActualScript(v8::Isolate* isolate, v8::Local<v8::debug::Script> script, bool isLiveEdit, V8DebuggerAgentImpl* agent, V8InspectorClient* client)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:
GetScriptURL(v8::Isolate* isolate, v8::Local<v8::debug::Script> script, V8InspectorClient* client)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 
GetScriptName(v8::Isolate* isolate, v8::Local<v8::debug::Script> script, V8InspectorClient* client)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 
Initialize(v8::Local<v8::debug::Script> script)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 
WeakCallback()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 
Create( v8::Isolate* isolate, v8::Local<v8::debug::Script> scriptObj, bool isLiveEdit, V8DebuggerAgentImpl* agent, V8InspectorClient* client)353 std::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 
V8DebuggerScript(v8::Isolate* isolate, String16 id, String16 url, String16 embedderName)360 V8DebuggerScript::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 
367 V8DebuggerScript::~V8DebuggerScript() = default;
368 
setSourceURL(const String16& sourceURL)369 void 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
removeWasmBreakpoint(int id)377 void 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