• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
isLiveEdit() const104   bool isLiveEdit() const override { return m_isLiveEdit; }
isModule() const105   bool isModule() const override { return m_isModule; }
106 
source(size_t pos,size_t len) const107   String16 source(size_t pos, size_t len) const override {
108     v8::HandleScope scope(m_isolate);
109     v8::Local<v8::String> v8Source;
110     if (!script()->Source().ToLocal(&v8Source)) return String16();
111     if (pos >= static_cast<size_t>(v8Source->Length())) return String16();
112     size_t substringLength =
113         std::min(len, static_cast<size_t>(v8Source->Length()) - pos);
114     std::unique_ptr<UChar[]> buffer(new UChar[substringLength]);
115     v8Source->Write(m_isolate, reinterpret_cast<uint16_t*>(buffer.get()),
116                     static_cast<int>(pos), static_cast<int>(substringLength));
117     return String16(buffer.get(), substringLength);
118   }
wasmBytecode() const119   v8::Maybe<v8::MemorySpan<const uint8_t>> wasmBytecode() const override {
120     v8::HandleScope scope(m_isolate);
121     auto script = this->script();
122     if (!script->IsWasm()) return v8::Nothing<v8::MemorySpan<const uint8_t>>();
123     return v8::Just(v8::debug::WasmScript::Cast(*script)->Bytecode());
124   }
getLanguage() const125   Language getLanguage() const override { return m_language; }
getDebugSymbolsType() const126   v8::Maybe<v8::debug::WasmScript::DebugSymbolsType> getDebugSymbolsType()
127       const override {
128     auto script = this->script();
129     if (!script->IsWasm())
130       return v8::Nothing<v8::debug::WasmScript::DebugSymbolsType>();
131     return v8::Just(v8::debug::WasmScript::Cast(*script)->GetDebugSymbolType());
132   }
getExternalDebugSymbolsURL() const133   v8::Maybe<String16> getExternalDebugSymbolsURL() const override {
134     auto script = this->script();
135     if (!script->IsWasm()) return v8::Nothing<String16>();
136     v8::MemorySpan<const char> external_url =
137         v8::debug::WasmScript::Cast(*script)->ExternalSymbolsURL();
138     if (external_url.size() == 0) return v8::Nothing<String16>();
139     return v8::Just(String16(external_url.data(), external_url.size()));
140   }
startLine() const141   int startLine() const override { return m_startLine; }
startColumn() const142   int startColumn() const override { return m_startColumn; }
endLine() const143   int endLine() const override { return m_endLine; }
endColumn() const144   int endColumn() const override { return m_endColumn; }
codeOffset() const145   int codeOffset() const override {
146     auto script = this->script();
147     if (!script->IsWasm()) return 0;
148     return v8::debug::WasmScript::Cast(*script)->CodeOffset();
149   }
isSourceLoadedLazily() const150   bool isSourceLoadedLazily() const override { return false; }
length() const151   int length() const override {
152     auto script = this->script();
153     if (script->IsWasm()) {
154       return static_cast<int>(
155           v8::debug::WasmScript::Cast(*script)->Bytecode().size());
156     }
157     v8::HandleScope scope(m_isolate);
158     v8::Local<v8::String> v8Source;
159     return script->Source().ToLocal(&v8Source) ? v8Source->Length() : 0;
160   }
161 
sourceMappingURL() const162   const String16& sourceMappingURL() const override {
163     return m_sourceMappingURL;
164   }
165 
setSourceMappingURL(const String16 & sourceMappingURL)166   void setSourceMappingURL(const String16& sourceMappingURL) override {
167     m_sourceMappingURL = sourceMappingURL;
168   }
169 
setSource(const String16 & newSource,bool preview,v8::debug::LiveEditResult * result)170   void setSource(const String16& newSource, bool preview,
171                  v8::debug::LiveEditResult* result) override {
172     v8::EscapableHandleScope scope(m_isolate);
173     v8::Local<v8::String> v8Source = toV8String(m_isolate, newSource);
174     if (!m_script.Get(m_isolate)->SetScriptSource(v8Source, preview, result)) {
175       result->message = scope.Escape(result->message);
176       return;
177     }
178     // NOP if preview or unchanged source (diffs.empty() in PatchScript)
179     if (preview || result->script.IsEmpty()) return;
180 
181     m_hash = String16();
182     Initialize(scope.Escape(result->script));
183   }
184 
getPossibleBreakpoints(const v8::debug::Location & start,const v8::debug::Location & end,bool restrictToFunction,std::vector<v8::debug::BreakLocation> * locations)185   bool getPossibleBreakpoints(
186       const v8::debug::Location& start, const v8::debug::Location& end,
187       bool restrictToFunction,
188       std::vector<v8::debug::BreakLocation>* locations) override {
189     v8::HandleScope scope(m_isolate);
190     v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
191     std::vector<v8::debug::BreakLocation> allLocations;
192     if (!script->GetPossibleBreakpoints(start, end, restrictToFunction,
193                                         &allLocations)) {
194       return false;
195     }
196     if (!allLocations.size()) return true;
197     v8::debug::BreakLocation current = allLocations[0];
198     for (size_t i = 1; i < allLocations.size(); ++i) {
199       if (allLocations[i].GetLineNumber() == current.GetLineNumber() &&
200           allLocations[i].GetColumnNumber() == current.GetColumnNumber()) {
201         if (allLocations[i].type() != v8::debug::kCommonBreakLocation) {
202           DCHECK(allLocations[i].type() == v8::debug::kCallBreakLocation ||
203                  allLocations[i].type() == v8::debug::kReturnBreakLocation);
204           // debugger can returns more then one break location at the same
205           // source location, e.g. foo() - in this case there are two break
206           // locations before foo: for statement and for function call, we can
207           // merge them for inspector and report only one with call type.
208           current = allLocations[i];
209         }
210       } else {
211         // we assume that returned break locations are sorted.
212         DCHECK(
213             allLocations[i].GetLineNumber() > current.GetLineNumber() ||
214             (allLocations[i].GetColumnNumber() >= current.GetColumnNumber() &&
215              allLocations[i].GetLineNumber() == current.GetLineNumber()));
216         locations->push_back(current);
217         current = allLocations[i];
218       }
219     }
220     locations->push_back(current);
221     return true;
222   }
223 
resetBlackboxedStateCache()224   void resetBlackboxedStateCache() override {
225     v8::HandleScope scope(m_isolate);
226     v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate));
227   }
228 
offset(int lineNumber,int columnNumber) const229   int offset(int lineNumber, int columnNumber) const override {
230     v8::HandleScope scope(m_isolate);
231     return m_script.Get(m_isolate)->GetSourceOffset(
232         v8::debug::Location(lineNumber, columnNumber));
233   }
234 
location(int offset) const235   v8::debug::Location location(int offset) const override {
236     v8::HandleScope scope(m_isolate);
237     return m_script.Get(m_isolate)->GetSourceLocation(offset);
238   }
239 
setBreakpoint(const String16 & condition,v8::debug::Location * location,int * id) const240   bool setBreakpoint(const String16& condition, v8::debug::Location* location,
241                      int* id) const override {
242     v8::HandleScope scope(m_isolate);
243     return script()->SetBreakpoint(toV8String(m_isolate, condition), location,
244                                    id);
245   }
246 
setBreakpointOnRun(int * id) const247   bool setBreakpointOnRun(int* id) const override {
248     v8::HandleScope scope(m_isolate);
249     return script()->SetBreakpointOnScriptEntry(id);
250   }
251 
hash() const252   const String16& hash() const override {
253     if (!m_hash.isEmpty()) return m_hash;
254     v8::HandleScope scope(m_isolate);
255     v8::Local<v8::String> v8Source;
256     if (script()->Source().ToLocal(&v8Source)) {
257       m_hash = calculateHash(m_isolate, v8Source);
258     }
259     DCHECK(!m_hash.isEmpty());
260     return m_hash;
261   }
262 
263  private:
GetScriptURL(v8::Isolate * isolate,v8::Local<v8::debug::Script> script,V8InspectorClient * client)264   static String16 GetScriptURL(v8::Isolate* isolate,
265                                v8::Local<v8::debug::Script> script,
266                                V8InspectorClient* client) {
267     v8::Local<v8::String> sourceURL;
268     if (script->SourceURL().ToLocal(&sourceURL) && sourceURL->Length() > 0)
269       return toProtocolString(isolate, sourceURL);
270     return GetScriptName(isolate, script, client);
271   }
272 
GetScriptName(v8::Isolate * isolate,v8::Local<v8::debug::Script> script,V8InspectorClient * client)273   static String16 GetScriptName(v8::Isolate* isolate,
274                                 v8::Local<v8::debug::Script> script,
275                                 V8InspectorClient* client) {
276     v8::Local<v8::String> v8Name;
277     if (script->Name().ToLocal(&v8Name) && v8Name->Length() > 0) {
278       String16 name = toProtocolString(isolate, v8Name);
279       std::unique_ptr<StringBuffer> url =
280           client->resourceNameToUrl(toStringView(name));
281       return url ? toString16(url->string()) : name;
282     }
283     return String16();
284   }
285 
script() const286   v8::Local<v8::debug::Script> script() const override {
287     return m_script.Get(m_isolate);
288   }
289 
Initialize(v8::Local<v8::debug::Script> script)290   void Initialize(v8::Local<v8::debug::Script> script) {
291     v8::Local<v8::String> tmp;
292     m_hasSourceURLComment =
293         script->SourceURL().ToLocal(&tmp) && tmp->Length() > 0;
294     if (script->SourceMappingURL().ToLocal(&tmp))
295       m_sourceMappingURL = toProtocolString(m_isolate, tmp);
296     m_startLine = script->LineOffset();
297     m_startColumn = script->ColumnOffset();
298     std::vector<int> lineEnds = script->LineEnds();
299     if (lineEnds.size()) {
300       int source_length = lineEnds[lineEnds.size() - 1];
301       m_endLine = static_cast<int>(lineEnds.size()) + m_startLine - 1;
302       if (lineEnds.size() > 1) {
303         m_endColumn = source_length - lineEnds[lineEnds.size() - 2] - 1;
304       } else {
305         m_endColumn = source_length + m_startColumn;
306       }
307     } else if (script->IsWasm()) {
308       DCHECK_EQ(0, m_startLine);
309       DCHECK_EQ(0, m_startColumn);
310       m_endLine = 0;
311       m_endColumn = static_cast<int>(
312           v8::debug::WasmScript::Cast(*script)->Bytecode().size());
313     } else {
314       m_endLine = m_startLine;
315       m_endColumn = m_startColumn;
316     }
317 
318     USE(script->ContextId().To(&m_executionContextId));
319     if (script->IsWasm()) {
320       m_language = V8DebuggerScript::Language::WebAssembly;
321     } else {
322       m_language = V8DebuggerScript::Language::JavaScript;
323     }
324 
325     m_isModule = script->IsModule();
326 
327     m_script.Reset(m_isolate, script);
328     m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
329   }
330 
MakeWeak()331   void MakeWeak() override {
332     m_script.SetWeak(
333         this,
334         [](const v8::WeakCallbackInfo<ActualScript>& data) {
335           data.GetParameter()->WeakCallback();
336         },
337         v8::WeakCallbackType::kFinalizer);
338   }
339 
WeakCallback()340   void WeakCallback() {
341     m_script.ClearWeak();
342     m_agent->ScriptCollected(this);
343   }
344 
345   V8DebuggerAgentImpl* m_agent;
346   String16 m_sourceMappingURL;
347   Language m_language;
348   bool m_isLiveEdit = false;
349   bool m_isModule = false;
350   mutable String16 m_hash;
351   int m_startLine = 0;
352   int m_startColumn = 0;
353   int m_endLine = 0;
354   int m_endColumn = 0;
355   v8::Global<v8::debug::Script> m_script;
356 };
357 
358 }  // namespace
359 
Create(v8::Isolate * isolate,v8::Local<v8::debug::Script> scriptObj,bool isLiveEdit,V8DebuggerAgentImpl * agent,V8InspectorClient * client)360 std::unique_ptr<V8DebuggerScript> V8DebuggerScript::Create(
361     v8::Isolate* isolate, v8::Local<v8::debug::Script> scriptObj,
362     bool isLiveEdit, V8DebuggerAgentImpl* agent, V8InspectorClient* client) {
363   return std::make_unique<ActualScript>(isolate, scriptObj, isLiveEdit, agent,
364                                         client);
365 }
366 
V8DebuggerScript(v8::Isolate * isolate,String16 id,String16 url,String16 embedderName)367 V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id,
368                                    String16 url, String16 embedderName)
369     : m_id(std::move(id)),
370       m_url(std::move(url)),
371       m_isolate(isolate),
372       m_embedderName(embedderName) {}
373 
374 V8DebuggerScript::~V8DebuggerScript() = default;
375 
setSourceURL(const String16 & sourceURL)376 void V8DebuggerScript::setSourceURL(const String16& sourceURL) {
377   if (sourceURL.length() > 0) {
378     m_hasSourceURLComment = true;
379     m_url = sourceURL;
380   }
381 }
382 
setBreakpoint(const String16 & condition,v8::debug::Location * loc,int * id) const383 bool V8DebuggerScript::setBreakpoint(const String16& condition,
384                                      v8::debug::Location* loc, int* id) const {
385   v8::HandleScope scope(m_isolate);
386   return script()->SetBreakpoint(toV8String(m_isolate, condition), loc, id);
387 }
388 
removeWasmBreakpoint(int id)389 void V8DebuggerScript::removeWasmBreakpoint(int id) {
390   v8::HandleScope scope(m_isolate);
391   script()->RemoveWasmBreakpoint(id);
392 }
393 
394 }  // namespace v8_inspector
395