• 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/wasm-translation.h"
10 
11 namespace v8_inspector {
12 
13 namespace {
14 
15 const char hexDigits[17] = "0123456789ABCDEF";
16 
appendUnsignedAsHex(uint64_t number,String16Builder * destination)17 void appendUnsignedAsHex(uint64_t number, String16Builder* destination) {
18   for (size_t i = 0; i < 8; ++i) {
19     UChar c = hexDigits[number & 0xF];
20     destination->append(c);
21     number >>= 4;
22   }
23 }
24 
25 // Hash algorithm for substrings is described in "Über die Komplexität der
26 // Multiplikation in
27 // eingeschränkten Branchingprogrammmodellen" by Woelfe.
28 // http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000
calculateHash(const String16 & str)29 String16 calculateHash(const String16& str) {
30   static uint64_t prime[] = {0x3FB75161, 0xAB1F4E4F, 0x82675BC5, 0xCD924D35,
31                              0x81ABE279};
32   static uint64_t random[] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476,
33                               0xC3D2E1F0};
34   static uint32_t randomOdd[] = {0xB4663807, 0xCC322BF5, 0xD4F91BBD, 0xA7BEA11D,
35                                  0x8F462907};
36 
37   uint64_t hashes[] = {0, 0, 0, 0, 0};
38   uint64_t zi[] = {1, 1, 1, 1, 1};
39 
40   const size_t hashesSize = arraysize(hashes);
41 
42   size_t current = 0;
43   const uint32_t* data = nullptr;
44   size_t sizeInBytes = sizeof(UChar) * str.length();
45   data = reinterpret_cast<const uint32_t*>(str.characters16());
46   for (size_t i = 0; i < sizeInBytes / 4; i += 4) {
47     uint32_t v = data[i];
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     for (size_t i = sizeInBytes - sizeInBytes % 4; i < sizeInBytes; ++i) {
56       v <<= 8;
57       v |= reinterpret_cast<const uint8_t*>(data)[i];
58     }
59     uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
60     hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
61     zi[current] = (zi[current] * random[current]) % prime[current];
62     current = current == hashesSize - 1 ? 0 : current + 1;
63   }
64 
65   for (size_t i = 0; i < hashesSize; ++i)
66     hashes[i] = (hashes[i] + zi[i] * (prime[i] - 1)) % prime[i];
67 
68   String16Builder hash;
69   for (size_t i = 0; i < hashesSize; ++i) appendUnsignedAsHex(hashes[i], &hash);
70   return hash.toString();
71 }
72 
TranslateProtocolLocationToV8Location(WasmTranslation * wasmTranslation,v8::debug::Location * loc,const String16 & scriptId,const String16 & expectedV8ScriptId)73 void TranslateProtocolLocationToV8Location(WasmTranslation* wasmTranslation,
74                                            v8::debug::Location* loc,
75                                            const String16& scriptId,
76                                            const String16& expectedV8ScriptId) {
77   if (loc->IsEmpty()) return;
78   int lineNumber = loc->GetLineNumber();
79   int columnNumber = loc->GetColumnNumber();
80   String16 translatedScriptId = scriptId;
81   wasmTranslation->TranslateProtocolLocationToWasmScriptLocation(
82       &translatedScriptId, &lineNumber, &columnNumber);
83   DCHECK_EQ(expectedV8ScriptId.utf8(), translatedScriptId.utf8());
84   *loc = v8::debug::Location(lineNumber, columnNumber);
85 }
86 
TranslateV8LocationToProtocolLocation(WasmTranslation * wasmTranslation,v8::debug::Location * loc,const String16 & scriptId,const String16 & expectedProtocolScriptId)87 void TranslateV8LocationToProtocolLocation(
88     WasmTranslation* wasmTranslation, v8::debug::Location* loc,
89     const String16& scriptId, const String16& expectedProtocolScriptId) {
90   int lineNumber = loc->GetLineNumber();
91   int columnNumber = loc->GetColumnNumber();
92   String16 translatedScriptId = scriptId;
93   wasmTranslation->TranslateWasmScriptLocationToProtocolLocation(
94       &translatedScriptId, &lineNumber, &columnNumber);
95   DCHECK_EQ(expectedProtocolScriptId.utf8(), translatedScriptId.utf8());
96   *loc = v8::debug::Location(lineNumber, columnNumber);
97 }
98 
99 class ActualScript : public V8DebuggerScript {
100   friend class V8DebuggerScript;
101 
102  public:
ActualScript(v8::Isolate * isolate,v8::Local<v8::debug::Script> script,bool isLiveEdit)103   ActualScript(v8::Isolate* isolate, v8::Local<v8::debug::Script> script,
104                bool isLiveEdit)
105       : V8DebuggerScript(isolate, String16::fromInteger(script->Id()),
106                          GetNameOrSourceUrl(script)),
107         m_isLiveEdit(isLiveEdit) {
108     v8::Local<v8::String> tmp;
109     if (script->SourceURL().ToLocal(&tmp)) m_sourceURL = toProtocolString(tmp);
110     if (script->SourceMappingURL().ToLocal(&tmp))
111       m_sourceMappingURL = toProtocolString(tmp);
112     m_startLine = script->LineOffset();
113     m_startColumn = script->ColumnOffset();
114     std::vector<int> lineEnds = script->LineEnds();
115     CHECK(lineEnds.size());
116     int source_length = lineEnds[lineEnds.size() - 1];
117     if (lineEnds.size()) {
118       m_endLine = static_cast<int>(lineEnds.size()) + m_startLine - 1;
119       if (lineEnds.size() > 1) {
120         m_endColumn = source_length - lineEnds[lineEnds.size() - 2] - 1;
121       } else {
122         m_endColumn = source_length + m_startColumn;
123       }
124     } else {
125       m_endLine = m_startLine;
126       m_endColumn = m_startColumn;
127     }
128 
129     v8::Local<v8::Value> contextData;
130     if (script->ContextData().ToLocal(&contextData) && contextData->IsInt32()) {
131       m_executionContextId =
132           static_cast<int>(contextData.As<v8::Int32>()->Value());
133     }
134 
135     if (script->Source().ToLocal(&tmp)) {
136       m_sourceObj.Reset(m_isolate, tmp);
137       String16 source = toProtocolString(tmp);
138       // V8 will not count last line if script source ends with \n.
139       if (source.length() > 1 && source[source.length() - 1] == '\n') {
140         m_endLine++;
141         m_endColumn = 0;
142       }
143     }
144 
145     m_isModule = script->IsModule();
146 
147     m_script.Reset(m_isolate, script);
148   }
149 
isLiveEdit() const150   bool isLiveEdit() const override { return m_isLiveEdit; }
isModule() const151   bool isModule() const override { return m_isModule; }
152 
sourceMappingURL() const153   const String16& sourceMappingURL() const override {
154     return m_sourceMappingURL;
155   }
156 
source(v8::Isolate * isolate) const157   String16 source(v8::Isolate* isolate) const override {
158     if (!m_sourceObj.IsEmpty())
159       return toProtocolString(m_sourceObj.Get(isolate));
160     return V8DebuggerScript::source(isolate);
161   }
162 
setSourceMappingURL(const String16 & sourceMappingURL)163   void setSourceMappingURL(const String16& sourceMappingURL) override {
164     m_sourceMappingURL = sourceMappingURL;
165   }
166 
setSource(v8::Local<v8::String> source)167   void setSource(v8::Local<v8::String> source) override {
168     m_source = String16();
169     m_sourceObj.Reset(m_isolate, source);
170     m_hash = String16();
171   }
172 
getPossibleBreakpoints(const v8::debug::Location & start,const v8::debug::Location & end,std::vector<v8::debug::Location> * locations)173   bool getPossibleBreakpoints(
174       const v8::debug::Location& start, const v8::debug::Location& end,
175       std::vector<v8::debug::Location>* locations) override {
176     v8::HandleScope scope(m_isolate);
177     v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
178     return script->GetPossibleBreakpoints(start, end, locations);
179   }
180 
resetBlackboxedStateCache()181   void resetBlackboxedStateCache() override {
182     v8::HandleScope scope(m_isolate);
183     v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate));
184   }
185 
186  private:
GetNameOrSourceUrl(v8::Local<v8::debug::Script> script)187   String16 GetNameOrSourceUrl(v8::Local<v8::debug::Script> script) {
188     v8::Local<v8::String> name;
189     if (script->Name().ToLocal(&name) || script->SourceURL().ToLocal(&name))
190       return toProtocolString(name);
191     return String16();
192   }
193 
194   String16 m_sourceMappingURL;
195   v8::Global<v8::String> m_sourceObj;
196   bool m_isLiveEdit = false;
197   bool m_isModule = false;
198   v8::Global<v8::debug::Script> m_script;
199 };
200 
201 class WasmVirtualScript : public V8DebuggerScript {
202   friend class V8DebuggerScript;
203 
204  public:
WasmVirtualScript(v8::Isolate * isolate,WasmTranslation * wasmTranslation,v8::Local<v8::debug::WasmScript> script,String16 id,String16 url,String16 source)205   WasmVirtualScript(v8::Isolate* isolate, WasmTranslation* wasmTranslation,
206                     v8::Local<v8::debug::WasmScript> script, String16 id,
207                     String16 url, String16 source)
208       : V8DebuggerScript(isolate, std::move(id), std::move(url)),
209         m_script(isolate, script),
210         m_wasmTranslation(wasmTranslation) {
211     int num_lines = 0;
212     int last_newline = -1;
213     size_t next_newline = source.find('\n', last_newline + 1);
214     while (next_newline != String16::kNotFound) {
215       last_newline = static_cast<int>(next_newline);
216       next_newline = source.find('\n', last_newline + 1);
217       ++num_lines;
218     }
219     m_endLine = num_lines;
220     m_endColumn = static_cast<int>(source.length()) - last_newline - 1;
221     m_source = std::move(source);
222   }
223 
sourceMappingURL() const224   const String16& sourceMappingURL() const override { return emptyString(); }
isLiveEdit() const225   bool isLiveEdit() const override { return false; }
isModule() const226   bool isModule() const override { return false; }
setSourceMappingURL(const String16 &)227   void setSourceMappingURL(const String16&) override {}
228 
getPossibleBreakpoints(const v8::debug::Location & start,const v8::debug::Location & end,std::vector<v8::debug::Location> * locations)229   bool getPossibleBreakpoints(
230       const v8::debug::Location& start, const v8::debug::Location& end,
231       std::vector<v8::debug::Location>* locations) override {
232     v8::HandleScope scope(m_isolate);
233     v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
234     String16 v8ScriptId = String16::fromInteger(script->Id());
235 
236     v8::debug::Location translatedStart = start;
237     TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedStart,
238                                           scriptId(), v8ScriptId);
239 
240     v8::debug::Location translatedEnd = end;
241     if (translatedEnd.IsEmpty()) {
242       // Stop before the start of the next function.
243       translatedEnd =
244           v8::debug::Location(translatedStart.GetLineNumber() + 1, 0);
245     } else {
246       TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedEnd,
247                                             scriptId(), v8ScriptId);
248     }
249 
250     bool success = script->GetPossibleBreakpoints(translatedStart,
251                                                   translatedEnd, locations);
252     for (v8::debug::Location& loc : *locations) {
253       TranslateV8LocationToProtocolLocation(m_wasmTranslation, &loc, v8ScriptId,
254                                             scriptId());
255     }
256     return success;
257   }
258 
resetBlackboxedStateCache()259   void resetBlackboxedStateCache() override {}
260 
261  private:
emptyString()262   static const String16& emptyString() {
263     static const String16 singleEmptyString;
264     return singleEmptyString;
265   }
266 
267   v8::Global<v8::debug::WasmScript> m_script;
268   WasmTranslation* m_wasmTranslation;
269 };
270 
271 }  // namespace
272 
Create(v8::Isolate * isolate,v8::Local<v8::debug::Script> scriptObj,bool isLiveEdit)273 std::unique_ptr<V8DebuggerScript> V8DebuggerScript::Create(
274     v8::Isolate* isolate, v8::Local<v8::debug::Script> scriptObj,
275     bool isLiveEdit) {
276   return std::unique_ptr<ActualScript>(
277       new ActualScript(isolate, scriptObj, isLiveEdit));
278 }
279 
CreateWasm(v8::Isolate * isolate,WasmTranslation * wasmTranslation,v8::Local<v8::debug::WasmScript> underlyingScript,String16 id,String16 url,String16 source)280 std::unique_ptr<V8DebuggerScript> V8DebuggerScript::CreateWasm(
281     v8::Isolate* isolate, WasmTranslation* wasmTranslation,
282     v8::Local<v8::debug::WasmScript> underlyingScript, String16 id,
283     String16 url, String16 source) {
284   return std::unique_ptr<WasmVirtualScript>(
285       new WasmVirtualScript(isolate, wasmTranslation, underlyingScript,
286                             std::move(id), std::move(url), std::move(source)));
287 }
288 
V8DebuggerScript(v8::Isolate * isolate,String16 id,String16 url)289 V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id,
290                                    String16 url)
291     : m_id(std::move(id)), m_url(std::move(url)), m_isolate(isolate) {}
292 
~V8DebuggerScript()293 V8DebuggerScript::~V8DebuggerScript() {}
294 
sourceURL() const295 const String16& V8DebuggerScript::sourceURL() const {
296   return m_sourceURL.isEmpty() ? m_url : m_sourceURL;
297 }
298 
hash(v8::Isolate * isolate) const299 const String16& V8DebuggerScript::hash(v8::Isolate* isolate) const {
300   if (m_hash.isEmpty()) m_hash = calculateHash(source(isolate));
301   DCHECK(!m_hash.isEmpty());
302   return m_hash;
303 }
304 
setSourceURL(const String16 & sourceURL)305 void V8DebuggerScript::setSourceURL(const String16& sourceURL) {
306   m_sourceURL = sourceURL;
307 }
308 
309 }  // namespace v8_inspector
310