Line data Source code
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
23 60326 : String16 calculateHash(v8::Isolate* isolate, v8::Local<v8::String> source) {
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 60326 : uint64_t hashes[] = {0, 0, 0, 0, 0};
32 60326 : uint64_t zi[] = {1, 1, 1, 1, 1};
33 :
34 : const size_t hashesSize = arraysize(hashes);
35 :
36 : size_t current = 0;
37 :
38 60326 : std::unique_ptr<UChar[]> buffer(new UChar[source->Length()]);
39 : int written = source->Write(
40 60326 : isolate, reinterpret_cast<uint16_t*>(buffer.get()), 0, source->Length());
41 :
42 : const uint32_t* data = nullptr;
43 60326 : size_t sizeInBytes = sizeof(UChar) * written;
44 : data = reinterpret_cast<const uint32_t*>(buffer.get());
45 138776248 : for (size_t i = 0; i < sizeInBytes / 4; ++i) {
46 : uint32_t d = v8::internal::ReadUnalignedUInt32(
47 138715922 : reinterpret_cast<v8::internal::Address>(data + i));
48 : #if V8_TARGET_LITTLE_ENDIAN
49 : uint32_t v = d;
50 : #else
51 : uint32_t v = (d << 16) | (d >> 16);
52 : #endif
53 138715922 : uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
54 138715922 : hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
55 138715922 : zi[current] = (zi[current] * random[current]) % prime[current];
56 138715922 : current = current == hashesSize - 1 ? 0 : current + 1;
57 : }
58 60326 : if (sizeInBytes % 4) {
59 : uint32_t v = 0;
60 : const uint8_t* data_8b = reinterpret_cast<const uint8_t*>(data);
61 95640 : for (size_t i = sizeInBytes - sizeInBytes % 4; i < sizeInBytes; ++i) {
62 63760 : v <<= 8;
63 : #if V8_TARGET_LITTLE_ENDIAN
64 63760 : v |= data_8b[i];
65 : #else
66 : if (i % 2) {
67 : v |= data_8b[i - 1];
68 : } else {
69 : v |= data_8b[i + 1];
70 : }
71 : #endif
72 : }
73 31880 : uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
74 31880 : hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
75 31880 : zi[current] = (zi[current] * random[current]) % prime[current];
76 : current = current == hashesSize - 1 ? 0 : current + 1;
77 : }
78 :
79 301630 : for (size_t i = 0; i < hashesSize; ++i)
80 301630 : hashes[i] = (hashes[i] + zi[i] * (prime[i] - 1)) % prime[i];
81 :
82 60326 : String16Builder hash;
83 361956 : for (size_t i = 0; i < hashesSize; ++i)
84 301630 : hash.appendUnsignedAsHex(static_cast<uint32_t>(hashes[i]));
85 120652 : return hash.toString();
86 : }
87 :
88 145 : void TranslateProtocolLocationToV8Location(WasmTranslation* wasmTranslation,
89 : v8::debug::Location* loc,
90 : const String16& scriptId,
91 : const String16& expectedV8ScriptId) {
92 145 : if (loc->IsEmpty()) return;
93 145 : int lineNumber = loc->GetLineNumber();
94 145 : int columnNumber = loc->GetColumnNumber();
95 145 : String16 translatedScriptId = scriptId;
96 : wasmTranslation->TranslateProtocolLocationToWasmScriptLocation(
97 145 : &translatedScriptId, &lineNumber, &columnNumber);
98 : DCHECK_EQ(expectedV8ScriptId.utf8(), translatedScriptId.utf8());
99 145 : *loc = v8::debug::Location(lineNumber, columnNumber);
100 : }
101 :
102 185 : void TranslateV8LocationToProtocolLocation(
103 : WasmTranslation* wasmTranslation, v8::debug::Location* loc,
104 : const String16& scriptId, const String16& expectedProtocolScriptId) {
105 185 : int lineNumber = loc->GetLineNumber();
106 185 : int columnNumber = loc->GetColumnNumber();
107 185 : String16 translatedScriptId = scriptId;
108 : wasmTranslation->TranslateWasmScriptLocationToProtocolLocation(
109 185 : &translatedScriptId, &lineNumber, &columnNumber);
110 : DCHECK_EQ(expectedProtocolScriptId.utf8(), translatedScriptId.utf8());
111 185 : *loc = v8::debug::Location(lineNumber, columnNumber);
112 185 : }
113 :
114 180978 : class ActualScript : public V8DebuggerScript {
115 : friend class V8DebuggerScript;
116 :
117 : public:
118 60326 : ActualScript(v8::Isolate* isolate, v8::Local<v8::debug::Script> script,
119 : bool isLiveEdit, V8InspectorClient* client)
120 : : V8DebuggerScript(isolate, String16::fromInteger(script->Id()),
121 : GetScriptURL(isolate, script, client)),
122 241304 : m_isLiveEdit(isLiveEdit) {
123 60326 : Initialize(script);
124 60326 : }
125 :
126 60326 : bool isLiveEdit() const override { return m_isLiveEdit; }
127 60326 : bool isModule() const override { return m_isModule; }
128 :
129 21041 : String16 source(size_t pos, size_t len) const override {
130 21041 : v8::HandleScope scope(m_isolate);
131 : v8::Local<v8::String> v8Source;
132 63123 : if (!script()->Source().ToLocal(&v8Source)) return String16();
133 21041 : if (pos >= static_cast<size_t>(v8Source->Length())) return String16();
134 : size_t substringLength =
135 42052 : std::min(len, static_cast<size_t>(v8Source->Length()) - pos);
136 21026 : std::unique_ptr<UChar[]> buffer(new UChar[substringLength]);
137 : v8Source->Write(m_isolate, reinterpret_cast<uint16_t*>(buffer.get()),
138 21026 : static_cast<int>(pos), static_cast<int>(substringLength));
139 42067 : return String16(buffer.get(), substringLength);
140 : }
141 62591 : int startLine() const override { return m_startLine; }
142 60326 : int startColumn() const override { return m_startColumn; }
143 62581 : int endLine() const override { return m_endLine; }
144 60326 : int endColumn() const override { return m_endColumn; }
145 55125 : bool isSourceLoadedLazily() const override { return false; }
146 60326 : int length() const override {
147 60326 : v8::HandleScope scope(m_isolate);
148 : v8::Local<v8::String> v8Source;
149 180978 : if (!script()->Source().ToLocal(&v8Source)) return 0;
150 60326 : return v8Source->Length();
151 : }
152 :
153 60326 : const String16& sourceMappingURL() const override {
154 60326 : return m_sourceMappingURL;
155 : }
156 :
157 5201 : void setSourceMappingURL(const String16& sourceMappingURL) override {
158 5201 : m_sourceMappingURL = sourceMappingURL;
159 5201 : }
160 :
161 50 : void setSource(const String16& newSource, bool preview,
162 : v8::debug::LiveEditResult* result) override {
163 50 : v8::EscapableHandleScope scope(m_isolate);
164 50 : v8::Local<v8::String> v8Source = toV8String(m_isolate, newSource);
165 100 : if (!m_script.Get(m_isolate)->SetScriptSource(v8Source, preview, result)) {
166 10 : result->message = scope.Escape(result->message);
167 10 : return;
168 : }
169 40 : if (preview) return;
170 80 : m_hash = String16();
171 40 : Initialize(scope.Escape(result->script));
172 : }
173 :
174 225 : bool getPossibleBreakpoints(
175 : const v8::debug::Location& start, const v8::debug::Location& end,
176 : bool restrictToFunction,
177 : std::vector<v8::debug::BreakLocation>* locations) override {
178 225 : v8::HandleScope scope(m_isolate);
179 225 : v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
180 : std::vector<v8::debug::BreakLocation> allLocations;
181 225 : if (!script->GetPossibleBreakpoints(start, end, restrictToFunction,
182 225 : &allLocations)) {
183 : return false;
184 : }
185 440 : if (!allLocations.size()) return true;
186 195 : v8::debug::BreakLocation current = allLocations[0];
187 9940 : for (size_t i = 1; i < allLocations.size(); ++i) {
188 7675 : if (allLocations[i].GetLineNumber() == current.GetLineNumber() &&
189 5800 : allLocations[i].GetColumnNumber() == current.GetColumnNumber()) {
190 1660 : if (allLocations[i].type() != v8::debug::kCommonBreakLocation) {
191 : DCHECK(allLocations[i].type() == v8::debug::kCallBreakLocation ||
192 : allLocations[i].type() == v8::debug::kReturnBreakLocation);
193 : // debugger can returns more then one break location at the same
194 : // source location, e.g. foo() - in this case there are two break
195 : // locations before foo: for statement and for function call, we can
196 : // merge them for inspector and report only one with call type.
197 530 : current = allLocations[i];
198 : }
199 : } else {
200 : // we assume that returned break locations are sorted.
201 : DCHECK(
202 : allLocations[i].GetLineNumber() > current.GetLineNumber() ||
203 : (allLocations[i].GetColumnNumber() >= current.GetColumnNumber() &&
204 : allLocations[i].GetLineNumber() == current.GetLineNumber()));
205 3945 : locations->push_back(current);
206 7890 : current = allLocations[i];
207 : }
208 : }
209 195 : locations->push_back(current);
210 420 : return true;
211 : }
212 :
213 120527 : void resetBlackboxedStateCache() override {
214 120527 : v8::HandleScope scope(m_isolate);
215 241054 : v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate));
216 120527 : }
217 :
218 310 : int offset(int lineNumber, int columnNumber) const override {
219 310 : v8::HandleScope scope(m_isolate);
220 310 : return m_script.Get(m_isolate)->GetSourceOffset(
221 620 : v8::debug::Location(lineNumber, columnNumber));
222 : }
223 :
224 60 : v8::debug::Location location(int offset) const override {
225 60 : v8::HandleScope scope(m_isolate);
226 120 : return m_script.Get(m_isolate)->GetSourceLocation(offset);
227 : }
228 :
229 2090 : bool setBreakpoint(const String16& condition, v8::debug::Location* location,
230 : int* id) const override {
231 2090 : v8::HandleScope scope(m_isolate);
232 2090 : return script()->SetBreakpoint(toV8String(m_isolate, condition), location,
233 6270 : id);
234 : }
235 :
236 60391 : const String16& hash() const override {
237 60391 : if (m_hash.isEmpty()) {
238 60326 : v8::HandleScope scope(m_isolate);
239 : v8::Local<v8::String> v8Source;
240 180978 : if (script()->Source().ToLocal(&v8Source)) {
241 120652 : m_hash = calculateHash(m_isolate, v8Source);
242 60326 : }
243 : }
244 : DCHECK(!m_hash.isEmpty());
245 60391 : return m_hash;
246 : }
247 :
248 : private:
249 60326 : String16 GetScriptURL(v8::Isolate* isolate,
250 : v8::Local<v8::debug::Script> script,
251 : V8InspectorClient* client) {
252 : v8::Local<v8::String> sourceURL;
253 120652 : if (script->SourceURL().ToLocal(&sourceURL) && sourceURL->Length() > 0)
254 2214 : return toProtocolString(isolate, sourceURL);
255 : v8::Local<v8::String> v8Name;
256 116224 : if (script->Name().ToLocal(&v8Name) && v8Name->Length() > 0) {
257 15979 : String16 name = toProtocolString(isolate, v8Name);
258 : std::unique_ptr<StringBuffer> url =
259 15979 : client->resourceNameToUrl(toStringView(name));
260 15979 : return url ? toString16(url->string()) : name;
261 : }
262 42133 : return String16();
263 : }
264 :
265 143783 : v8::Local<v8::debug::Script> script() const override {
266 287566 : return m_script.Get(m_isolate);
267 : }
268 :
269 60366 : void Initialize(v8::Local<v8::debug::Script> script) {
270 : v8::Local<v8::String> tmp;
271 : m_hasSourceURLComment =
272 120732 : script->SourceURL().ToLocal(&tmp) && tmp->Length() > 0;
273 120732 : if (script->SourceMappingURL().ToLocal(&tmp))
274 1170 : m_sourceMappingURL = toProtocolString(m_isolate, tmp);
275 60366 : m_startLine = script->LineOffset();
276 60366 : m_startColumn = script->ColumnOffset();
277 60366 : std::vector<int> lineEnds = script->LineEnds();
278 120732 : CHECK(lineEnds.size());
279 120732 : int source_length = lineEnds[lineEnds.size() - 1];
280 60366 : if (lineEnds.size()) {
281 60366 : m_endLine = static_cast<int>(lineEnds.size()) + m_startLine - 1;
282 60366 : if (lineEnds.size() > 1) {
283 42776 : m_endColumn = source_length - lineEnds[lineEnds.size() - 2] - 1;
284 : } else {
285 38978 : m_endColumn = source_length + m_startColumn;
286 : }
287 : } else {
288 0 : m_endLine = m_startLine;
289 0 : m_endColumn = m_startColumn;
290 : }
291 :
292 120732 : USE(script->ContextId().To(&m_executionContextId));
293 :
294 60366 : m_isModule = script->IsModule();
295 :
296 60366 : m_script.Reset(m_isolate, script);
297 : m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
298 60366 : }
299 :
300 : String16 m_sourceMappingURL;
301 : bool m_isLiveEdit = false;
302 : bool m_isModule = false;
303 : mutable String16 m_hash;
304 : int m_startLine = 0;
305 : int m_startColumn = 0;
306 : int m_endLine = 0;
307 : int m_endColumn = 0;
308 : v8::Global<v8::debug::Script> m_script;
309 : };
310 :
311 306 : class WasmVirtualScript : public V8DebuggerScript {
312 : friend class V8DebuggerScript;
313 :
314 : public:
315 102 : WasmVirtualScript(v8::Isolate* isolate, WasmTranslation* wasmTranslation,
316 : v8::Local<v8::debug::WasmScript> script, String16 id,
317 : String16 url, int functionIndex)
318 : : V8DebuggerScript(isolate, std::move(id), std::move(url)),
319 : m_script(isolate, script),
320 : m_wasmTranslation(wasmTranslation),
321 408 : m_functionIndex(functionIndex) {
322 : m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
323 204 : m_executionContextId = script->ContextId().ToChecked();
324 102 : }
325 :
326 102 : const String16& sourceMappingURL() const override { return emptyString(); }
327 102 : bool isLiveEdit() const override { return false; }
328 102 : bool isModule() const override { return false; }
329 0 : void setSourceMappingURL(const String16&) override {}
330 0 : void setSource(const String16&, bool, v8::debug::LiveEditResult*) override {
331 0 : UNREACHABLE();
332 : }
333 102 : bool isSourceLoadedLazily() const override { return true; }
334 64 : String16 source(size_t pos, size_t len) const override {
335 64 : return m_wasmTranslation->GetSource(m_id, m_functionIndex)
336 64 : .substring(pos, len);
337 : }
338 95 : int startLine() const override {
339 95 : return m_wasmTranslation->GetStartLine(m_id, m_functionIndex);
340 : }
341 0 : int startColumn() const override {
342 0 : return m_wasmTranslation->GetStartColumn(m_id, m_functionIndex);
343 : }
344 95 : int endLine() const override {
345 95 : return m_wasmTranslation->GetEndLine(m_id, m_functionIndex);
346 : }
347 0 : int endColumn() const override {
348 0 : return m_wasmTranslation->GetEndColumn(m_id, m_functionIndex);
349 : }
350 0 : int length() const override {
351 0 : return static_cast<int>(source(0, UINT_MAX).length());
352 : }
353 :
354 30 : bool getPossibleBreakpoints(
355 : const v8::debug::Location& start, const v8::debug::Location& end,
356 : bool restrictToFunction,
357 : std::vector<v8::debug::BreakLocation>* locations) override {
358 30 : v8::HandleScope scope(m_isolate);
359 30 : v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
360 30 : String16 v8ScriptId = String16::fromInteger(script->Id());
361 :
362 30 : v8::debug::Location translatedStart = start;
363 : TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedStart,
364 30 : scriptId(), v8ScriptId);
365 :
366 30 : v8::debug::Location translatedEnd = end;
367 30 : if (translatedEnd.IsEmpty()) {
368 : // Stop before the start of the next function.
369 : translatedEnd =
370 10 : v8::debug::Location(translatedStart.GetLineNumber() + 1, 0);
371 : } else {
372 : TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedEnd,
373 20 : scriptId(), v8ScriptId);
374 : }
375 :
376 : bool success = script->GetPossibleBreakpoints(
377 30 : translatedStart, translatedEnd, restrictToFunction, locations);
378 150 : for (v8::debug::BreakLocation& loc : *locations) {
379 : TranslateV8LocationToProtocolLocation(m_wasmTranslation, &loc, v8ScriptId,
380 90 : scriptId());
381 : }
382 30 : return success;
383 : }
384 :
385 204 : void resetBlackboxedStateCache() override {}
386 :
387 0 : int offset(int lineNumber, int columnNumber) const override {
388 0 : return kNoOffset;
389 : }
390 :
391 0 : v8::debug::Location location(int offset) const override {
392 0 : return v8::debug::Location();
393 : }
394 :
395 95 : bool setBreakpoint(const String16& condition, v8::debug::Location* location,
396 : int* id) const override {
397 95 : v8::HandleScope scope(m_isolate);
398 95 : v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
399 95 : String16 v8ScriptId = String16::fromInteger(script->Id());
400 :
401 : TranslateProtocolLocationToV8Location(m_wasmTranslation, location,
402 95 : scriptId(), v8ScriptId);
403 95 : if (location->IsEmpty()) return false;
404 95 : if (!script->SetBreakpoint(toV8String(m_isolate, condition), location, id))
405 : return false;
406 : TranslateV8LocationToProtocolLocation(m_wasmTranslation, location,
407 95 : v8ScriptId, scriptId());
408 190 : return true;
409 : }
410 :
411 102 : const String16& hash() const override {
412 102 : if (m_hash.isEmpty()) {
413 204 : m_hash = m_wasmTranslation->GetHash(m_id, m_functionIndex);
414 : }
415 102 : return m_hash;
416 : }
417 :
418 : private:
419 102 : static const String16& emptyString() {
420 : // On the heap and leaked so that no destructor needs to run at exit time.
421 102 : static const String16* singleEmptyString = new String16;
422 102 : return *singleEmptyString;
423 : }
424 :
425 0 : v8::Local<v8::debug::Script> script() const override {
426 0 : return m_script.Get(m_isolate);
427 : }
428 :
429 : v8::Global<v8::debug::WasmScript> m_script;
430 : WasmTranslation* m_wasmTranslation;
431 : int m_functionIndex;
432 : mutable String16 m_hash;
433 : };
434 :
435 : } // namespace
436 :
437 60326 : std::unique_ptr<V8DebuggerScript> V8DebuggerScript::Create(
438 : v8::Isolate* isolate, v8::Local<v8::debug::Script> scriptObj,
439 : bool isLiveEdit, V8InspectorClient* client) {
440 : return std::unique_ptr<ActualScript>(
441 120652 : new ActualScript(isolate, scriptObj, isLiveEdit, client));
442 : }
443 :
444 102 : std::unique_ptr<V8DebuggerScript> V8DebuggerScript::CreateWasm(
445 : v8::Isolate* isolate, WasmTranslation* wasmTranslation,
446 : v8::Local<v8::debug::WasmScript> underlyingScript, String16 id,
447 : String16 url, int functionIndex) {
448 : return std::unique_ptr<WasmVirtualScript>(
449 : new WasmVirtualScript(isolate, wasmTranslation, underlyingScript,
450 306 : std::move(id), std::move(url), functionIndex));
451 : }
452 :
453 0 : V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id,
454 : String16 url)
455 60428 : : m_id(std::move(id)), m_url(std::move(url)), m_isolate(isolate) {}
456 :
457 : V8DebuggerScript::~V8DebuggerScript() = default;
458 :
459 5201 : void V8DebuggerScript::setSourceURL(const String16& sourceURL) {
460 5201 : if (sourceURL.length() > 0) {
461 40 : m_hasSourceURLComment = true;
462 40 : m_url = sourceURL;
463 : }
464 5201 : }
465 :
466 0 : bool V8DebuggerScript::setBreakpoint(const String16& condition,
467 : v8::debug::Location* loc, int* id) const {
468 0 : v8::HandleScope scope(m_isolate);
469 0 : return script()->SetBreakpoint(toV8String(m_isolate, condition), loc, id);
470 : }
471 : } // namespace v8_inspector
|