• 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/inspector/inspected-context.h"
8 #include "src/inspector/string-util.h"
9 #include "src/inspector/v8-inspector-impl.h"
10 #include "src/inspector/wasm-translation.h"
11 #include "src/v8memory.h"
12 
13 namespace v8_inspector {
14 
15 namespace {
16 
17 const char kGlobalDebuggerScriptHandleLabel[] = "DevTools debugger";
18 
19 // Hash algorithm for substrings is described in "Über die Komplexität der
20 // Multiplikation in
21 // eingeschränkten Branchingprogrammmodellen" by Woelfe.
22 // http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000
calculateHash(const String16 & str)23 String16 calculateHash(const String16& str) {
24   static uint64_t prime[] = {0x3FB75161, 0xAB1F4E4F, 0x82675BC5, 0xCD924D35,
25                              0x81ABE279};
26   static uint64_t random[] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476,
27                               0xC3D2E1F0};
28   static uint32_t randomOdd[] = {0xB4663807, 0xCC322BF5, 0xD4F91BBD, 0xA7BEA11D,
29                                  0x8F462907};
30 
31   uint64_t hashes[] = {0, 0, 0, 0, 0};
32   uint64_t zi[] = {1, 1, 1, 1, 1};
33 
34   const size_t hashesSize = arraysize(hashes);
35 
36   size_t current = 0;
37   const uint32_t* data = nullptr;
38   size_t sizeInBytes = sizeof(UChar) * str.length();
39   data = reinterpret_cast<const uint32_t*>(str.characters16());
40   for (size_t i = 0; i < sizeInBytes / 4; ++i) {
41     uint32_t d = v8::internal::ReadUnalignedUInt32(
42         reinterpret_cast<v8::internal::Address>(data + i));
43 #if V8_TARGET_LITTLE_ENDIAN
44     uint32_t v = d;
45 #else
46     uint32_t v = (d << 16) | (d >> 16);
47 #endif
48     uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
49     hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
50     zi[current] = (zi[current] * random[current]) % prime[current];
51     current = current == hashesSize - 1 ? 0 : current + 1;
52   }
53   if (sizeInBytes % 4) {
54     uint32_t v = 0;
55     const uint8_t* data_8b = reinterpret_cast<const uint8_t*>(data);
56     for (size_t i = sizeInBytes - sizeInBytes % 4; i < sizeInBytes; ++i) {
57       v <<= 8;
58 #if V8_TARGET_LITTLE_ENDIAN
59       v |= data_8b[i];
60 #else
61       if (i % 2) {
62         v |= data_8b[i - 1];
63       } else {
64         v |= data_8b[i + 1];
65       }
66 #endif
67     }
68     uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
69     hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
70     zi[current] = (zi[current] * random[current]) % prime[current];
71     current = current == hashesSize - 1 ? 0 : current + 1;
72   }
73 
74   for (size_t i = 0; i < hashesSize; ++i)
75     hashes[i] = (hashes[i] + zi[i] * (prime[i] - 1)) % prime[i];
76 
77   String16Builder hash;
78   for (size_t i = 0; i < hashesSize; ++i)
79     hash.appendUnsignedAsHex((uint32_t)hashes[i]);
80   return hash.toString();
81 }
82 
TranslateProtocolLocationToV8Location(WasmTranslation * wasmTranslation,v8::debug::Location * loc,const String16 & scriptId,const String16 & expectedV8ScriptId)83 void TranslateProtocolLocationToV8Location(WasmTranslation* wasmTranslation,
84                                            v8::debug::Location* loc,
85                                            const String16& scriptId,
86                                            const String16& expectedV8ScriptId) {
87   if (loc->IsEmpty()) return;
88   int lineNumber = loc->GetLineNumber();
89   int columnNumber = loc->GetColumnNumber();
90   String16 translatedScriptId = scriptId;
91   wasmTranslation->TranslateProtocolLocationToWasmScriptLocation(
92       &translatedScriptId, &lineNumber, &columnNumber);
93   DCHECK_EQ(expectedV8ScriptId.utf8(), translatedScriptId.utf8());
94   *loc = v8::debug::Location(lineNumber, columnNumber);
95 }
96 
TranslateV8LocationToProtocolLocation(WasmTranslation * wasmTranslation,v8::debug::Location * loc,const String16 & scriptId,const String16 & expectedProtocolScriptId)97 void TranslateV8LocationToProtocolLocation(
98     WasmTranslation* wasmTranslation, v8::debug::Location* loc,
99     const String16& scriptId, const String16& expectedProtocolScriptId) {
100   int lineNumber = loc->GetLineNumber();
101   int columnNumber = loc->GetColumnNumber();
102   String16 translatedScriptId = scriptId;
103   wasmTranslation->TranslateWasmScriptLocationToProtocolLocation(
104       &translatedScriptId, &lineNumber, &columnNumber);
105   DCHECK_EQ(expectedProtocolScriptId.utf8(), translatedScriptId.utf8());
106   *loc = v8::debug::Location(lineNumber, columnNumber);
107 }
108 
109 class ActualScript : public V8DebuggerScript {
110   friend class V8DebuggerScript;
111 
112  public:
ActualScript(v8::Isolate * isolate,v8::Local<v8::debug::Script> script,bool isLiveEdit,V8InspectorClient * client)113   ActualScript(v8::Isolate* isolate, v8::Local<v8::debug::Script> script,
114                bool isLiveEdit, V8InspectorClient* client)
115       : V8DebuggerScript(isolate, String16::fromInteger(script->Id()),
116                          GetScriptURL(isolate, script, client)),
117         m_isLiveEdit(isLiveEdit) {
118     Initialize(script);
119   }
120 
isLiveEdit() const121   bool isLiveEdit() const override { return m_isLiveEdit; }
isModule() const122   bool isModule() const override { return m_isModule; }
123 
source() const124   const String16& source() const override { return m_source; }
startLine() const125   int startLine() const override { return m_startLine; }
startColumn() const126   int startColumn() const override { return m_startColumn; }
endLine() const127   int endLine() const override { return m_endLine; }
endColumn() const128   int endColumn() const override { return m_endColumn; }
isSourceLoadedLazily() const129   bool isSourceLoadedLazily() const override { return false; }
130 
sourceMappingURL() const131   const String16& sourceMappingURL() const override {
132     return m_sourceMappingURL;
133   }
134 
setSourceMappingURL(const String16 & sourceMappingURL)135   void setSourceMappingURL(const String16& sourceMappingURL) override {
136     m_sourceMappingURL = sourceMappingURL;
137   }
138 
setSource(const String16 & newSource,bool preview,v8::debug::LiveEditResult * result)139   void setSource(const String16& newSource, bool preview,
140                  v8::debug::LiveEditResult* result) override {
141     DCHECK(!isModule());
142     v8::EscapableHandleScope scope(m_isolate);
143     v8::Local<v8::String> v8Source = toV8String(m_isolate, newSource);
144     if (!m_script.Get(m_isolate)->SetScriptSource(v8Source, preview, result)) {
145       result->message = scope.Escape(result->message);
146       return;
147     }
148     if (preview) return;
149     m_hash = String16();
150     Initialize(scope.Escape(result->script));
151   }
152 
getPossibleBreakpoints(const v8::debug::Location & start,const v8::debug::Location & end,bool restrictToFunction,std::vector<v8::debug::BreakLocation> * locations)153   bool getPossibleBreakpoints(
154       const v8::debug::Location& start, const v8::debug::Location& end,
155       bool restrictToFunction,
156       std::vector<v8::debug::BreakLocation>* locations) override {
157     v8::HandleScope scope(m_isolate);
158     v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
159     std::vector<v8::debug::BreakLocation> allLocations;
160     if (!script->GetPossibleBreakpoints(start, end, restrictToFunction,
161                                         &allLocations)) {
162       return false;
163     }
164     if (!allLocations.size()) return true;
165     v8::debug::BreakLocation current = allLocations[0];
166     for (size_t i = 1; i < allLocations.size(); ++i) {
167       if (allLocations[i].GetLineNumber() == current.GetLineNumber() &&
168           allLocations[i].GetColumnNumber() == current.GetColumnNumber()) {
169         if (allLocations[i].type() != v8::debug::kCommonBreakLocation) {
170           DCHECK(allLocations[i].type() == v8::debug::kCallBreakLocation ||
171                  allLocations[i].type() == v8::debug::kReturnBreakLocation);
172           // debugger can returns more then one break location at the same
173           // source location, e.g. foo() - in this case there are two break
174           // locations before foo: for statement and for function call, we can
175           // merge them for inspector and report only one with call type.
176           current = allLocations[i];
177         }
178       } else {
179         // we assume that returned break locations are sorted.
180         DCHECK(
181             allLocations[i].GetLineNumber() > current.GetLineNumber() ||
182             (allLocations[i].GetColumnNumber() >= current.GetColumnNumber() &&
183              allLocations[i].GetLineNumber() == current.GetLineNumber()));
184         locations->push_back(current);
185         current = allLocations[i];
186       }
187     }
188     locations->push_back(current);
189     return true;
190   }
191 
resetBlackboxedStateCache()192   void resetBlackboxedStateCache() override {
193     v8::HandleScope scope(m_isolate);
194     v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate));
195   }
196 
offset(int lineNumber,int columnNumber) const197   int offset(int lineNumber, int columnNumber) const override {
198     v8::HandleScope scope(m_isolate);
199     return m_script.Get(m_isolate)->GetSourceOffset(
200         v8::debug::Location(lineNumber, columnNumber));
201   }
202 
location(int offset) const203   v8::debug::Location location(int offset) const override {
204     v8::HandleScope scope(m_isolate);
205     return m_script.Get(m_isolate)->GetSourceLocation(offset);
206   }
207 
setBreakpoint(const String16 & condition,v8::debug::Location * location,int * id) const208   bool setBreakpoint(const String16& condition, v8::debug::Location* location,
209                      int* id) const override {
210     v8::HandleScope scope(m_isolate);
211     return script()->SetBreakpoint(toV8String(m_isolate, condition), location,
212                                    id);
213   }
214 
hash() const215   const String16& hash() const override {
216     if (m_hash.isEmpty()) m_hash = calculateHash(source());
217     DCHECK(!m_hash.isEmpty());
218     return m_hash;
219   }
220 
221  private:
GetScriptURL(v8::Isolate * isolate,v8::Local<v8::debug::Script> script,V8InspectorClient * client)222   String16 GetScriptURL(v8::Isolate* isolate,
223                         v8::Local<v8::debug::Script> script,
224                         V8InspectorClient* client) {
225     v8::Local<v8::String> sourceURL;
226     if (script->SourceURL().ToLocal(&sourceURL) && sourceURL->Length() > 0)
227       return toProtocolString(isolate, sourceURL);
228     v8::Local<v8::String> v8Name;
229     if (script->Name().ToLocal(&v8Name) && v8Name->Length() > 0) {
230       String16 name = toProtocolString(isolate, v8Name);
231       std::unique_ptr<StringBuffer> url =
232           client->resourceNameToUrl(toStringView(name));
233       return url ? toString16(url->string()) : name;
234     }
235     return String16();
236   }
237 
script() const238   v8::Local<v8::debug::Script> script() const override {
239     return m_script.Get(m_isolate);
240   }
241 
Initialize(v8::Local<v8::debug::Script> script)242   void Initialize(v8::Local<v8::debug::Script> script) {
243     v8::Local<v8::String> tmp;
244     m_hasSourceURLComment =
245         script->SourceURL().ToLocal(&tmp) && tmp->Length() > 0;
246     if (script->SourceMappingURL().ToLocal(&tmp))
247       m_sourceMappingURL = toProtocolString(m_isolate, tmp);
248     m_startLine = script->LineOffset();
249     m_startColumn = script->ColumnOffset();
250     std::vector<int> lineEnds = script->LineEnds();
251     CHECK(lineEnds.size());
252     int source_length = lineEnds[lineEnds.size() - 1];
253     if (lineEnds.size()) {
254       m_endLine = static_cast<int>(lineEnds.size()) + m_startLine - 1;
255       if (lineEnds.size() > 1) {
256         m_endColumn = source_length - lineEnds[lineEnds.size() - 2] - 1;
257       } else {
258         m_endColumn = source_length + m_startColumn;
259       }
260     } else {
261       m_endLine = m_startLine;
262       m_endColumn = m_startColumn;
263     }
264 
265     USE(script->ContextId().To(&m_executionContextId));
266 
267     if (script->Source().ToLocal(&tmp)) {
268       m_source = toProtocolString(m_isolate, tmp);
269     }
270 
271     m_isModule = script->IsModule();
272 
273     m_script.Reset(m_isolate, script);
274     m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
275   }
276 
277   String16 m_sourceMappingURL;
278   bool m_isLiveEdit = false;
279   bool m_isModule = false;
280   String16 m_source;
281   mutable String16 m_hash;
282   int m_startLine = 0;
283   int m_startColumn = 0;
284   int m_endLine = 0;
285   int m_endColumn = 0;
286   v8::Global<v8::debug::Script> m_script;
287 };
288 
289 class WasmVirtualScript : public V8DebuggerScript {
290   friend class V8DebuggerScript;
291 
292  public:
WasmVirtualScript(v8::Isolate * isolate,WasmTranslation * wasmTranslation,v8::Local<v8::debug::WasmScript> script,String16 id,String16 url,int functionIndex)293   WasmVirtualScript(v8::Isolate* isolate, WasmTranslation* wasmTranslation,
294                     v8::Local<v8::debug::WasmScript> script, String16 id,
295                     String16 url, int functionIndex)
296       : V8DebuggerScript(isolate, std::move(id), std::move(url)),
297         m_script(isolate, script),
298         m_wasmTranslation(wasmTranslation),
299         m_functionIndex(functionIndex) {
300     m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
301     m_executionContextId = script->ContextId().ToChecked();
302   }
303 
sourceMappingURL() const304   const String16& sourceMappingURL() const override { return emptyString(); }
isLiveEdit() const305   bool isLiveEdit() const override { return false; }
isModule() const306   bool isModule() const override { return false; }
setSourceMappingURL(const String16 &)307   void setSourceMappingURL(const String16&) override {}
setSource(const String16 &,bool,v8::debug::LiveEditResult *)308   void setSource(const String16&, bool, v8::debug::LiveEditResult*) override {
309     UNREACHABLE();
310   }
isSourceLoadedLazily() const311   bool isSourceLoadedLazily() const override { return true; }
source() const312   const String16& source() const override {
313     return m_wasmTranslation->GetSource(m_id, m_functionIndex);
314   }
startLine() const315   int startLine() const override {
316     return m_wasmTranslation->GetStartLine(m_id, m_functionIndex);
317   }
startColumn() const318   int startColumn() const override {
319     return m_wasmTranslation->GetStartColumn(m_id, m_functionIndex);
320   }
endLine() const321   int endLine() const override {
322     return m_wasmTranslation->GetEndLine(m_id, m_functionIndex);
323   }
endColumn() const324   int endColumn() const override {
325     return m_wasmTranslation->GetEndColumn(m_id, m_functionIndex);
326   }
327 
getPossibleBreakpoints(const v8::debug::Location & start,const v8::debug::Location & end,bool restrictToFunction,std::vector<v8::debug::BreakLocation> * locations)328   bool getPossibleBreakpoints(
329       const v8::debug::Location& start, const v8::debug::Location& end,
330       bool restrictToFunction,
331       std::vector<v8::debug::BreakLocation>* locations) override {
332     v8::HandleScope scope(m_isolate);
333     v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
334     String16 v8ScriptId = String16::fromInteger(script->Id());
335 
336     v8::debug::Location translatedStart = start;
337     TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedStart,
338                                           scriptId(), v8ScriptId);
339 
340     v8::debug::Location translatedEnd = end;
341     if (translatedEnd.IsEmpty()) {
342       // Stop before the start of the next function.
343       translatedEnd =
344           v8::debug::Location(translatedStart.GetLineNumber() + 1, 0);
345     } else {
346       TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedEnd,
347                                             scriptId(), v8ScriptId);
348     }
349 
350     bool success = script->GetPossibleBreakpoints(
351         translatedStart, translatedEnd, restrictToFunction, locations);
352     for (v8::debug::BreakLocation& loc : *locations) {
353       TranslateV8LocationToProtocolLocation(m_wasmTranslation, &loc, v8ScriptId,
354                                             scriptId());
355     }
356     return success;
357   }
358 
resetBlackboxedStateCache()359   void resetBlackboxedStateCache() override {}
360 
offset(int lineNumber,int columnNumber) const361   int offset(int lineNumber, int columnNumber) const override {
362     return kNoOffset;
363   }
364 
location(int offset) const365   v8::debug::Location location(int offset) const override {
366     return v8::debug::Location();
367   }
368 
setBreakpoint(const String16 & condition,v8::debug::Location * location,int * id) const369   bool setBreakpoint(const String16& condition, v8::debug::Location* location,
370                      int* id) const override {
371     v8::HandleScope scope(m_isolate);
372     v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
373     String16 v8ScriptId = String16::fromInteger(script->Id());
374 
375     TranslateProtocolLocationToV8Location(m_wasmTranslation, location,
376                                           scriptId(), v8ScriptId);
377     if (location->IsEmpty()) return false;
378     if (!script->SetBreakpoint(toV8String(m_isolate, condition), location, id))
379       return false;
380     TranslateV8LocationToProtocolLocation(m_wasmTranslation, location,
381                                           v8ScriptId, scriptId());
382     return true;
383   }
384 
hash() const385   const String16& hash() const override {
386     if (m_hash.isEmpty()) {
387       m_hash = m_wasmTranslation->GetHash(m_id, m_functionIndex);
388     }
389     return m_hash;
390   }
391 
392  private:
emptyString()393   static const String16& emptyString() {
394     static const String16 singleEmptyString;
395     return singleEmptyString;
396   }
397 
script() const398   v8::Local<v8::debug::Script> script() const override {
399     return m_script.Get(m_isolate);
400   }
401 
402   v8::Global<v8::debug::WasmScript> m_script;
403   WasmTranslation* m_wasmTranslation;
404   int m_functionIndex;
405   mutable String16 m_hash;
406 };
407 
408 }  // namespace
409 
Create(v8::Isolate * isolate,v8::Local<v8::debug::Script> scriptObj,bool isLiveEdit,V8InspectorClient * client)410 std::unique_ptr<V8DebuggerScript> V8DebuggerScript::Create(
411     v8::Isolate* isolate, v8::Local<v8::debug::Script> scriptObj,
412     bool isLiveEdit, V8InspectorClient* client) {
413   return std::unique_ptr<ActualScript>(
414       new ActualScript(isolate, scriptObj, isLiveEdit, client));
415 }
416 
CreateWasm(v8::Isolate * isolate,WasmTranslation * wasmTranslation,v8::Local<v8::debug::WasmScript> underlyingScript,String16 id,String16 url,int functionIndex)417 std::unique_ptr<V8DebuggerScript> V8DebuggerScript::CreateWasm(
418     v8::Isolate* isolate, WasmTranslation* wasmTranslation,
419     v8::Local<v8::debug::WasmScript> underlyingScript, String16 id,
420     String16 url, int functionIndex) {
421   return std::unique_ptr<WasmVirtualScript>(
422       new WasmVirtualScript(isolate, wasmTranslation, underlyingScript,
423                             std::move(id), std::move(url), functionIndex));
424 }
425 
V8DebuggerScript(v8::Isolate * isolate,String16 id,String16 url)426 V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id,
427                                    String16 url)
428     : m_id(std::move(id)), m_url(std::move(url)), m_isolate(isolate) {}
429 
~V8DebuggerScript()430 V8DebuggerScript::~V8DebuggerScript() {}
431 
setSourceURL(const String16 & sourceURL)432 void V8DebuggerScript::setSourceURL(const String16& sourceURL) {
433   if (sourceURL.length() > 0) {
434     m_hasSourceURLComment = true;
435     m_url = sourceURL;
436   }
437 }
438 
setBreakpoint(const String16 & condition,v8::debug::Location * loc,int * id) const439 bool V8DebuggerScript::setBreakpoint(const String16& condition,
440                                      v8::debug::Location* loc, int* id) const {
441   v8::HandleScope scope(m_isolate);
442   return script()->SetBreakpoint(toV8String(m_isolate, condition), loc, id);
443 }
444 }  // namespace v8_inspector
445