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