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