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-debugger-agent-impl.h"
10 : #include "src/inspector/v8-inspector-impl.h"
11 : #include "src/inspector/wasm-translation.h"
12 : #include "src/v8memory.h"
13 :
14 : namespace v8_inspector {
15 :
16 : namespace {
17 :
18 : const char kGlobalDebuggerScriptHandleLabel[] = "DevTools debugger";
19 :
20 : // Hash algorithm for substrings is described in "Über die Komplexität der
21 : // Multiplikation in
22 : // eingeschränkten Branchingprogrammmodellen" by Woelfe.
23 : // http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000
24 55887 : String16 calculateHash(v8::Isolate* isolate, v8::Local<v8::String> source) {
25 : static uint64_t prime[] = {0x3FB75161, 0xAB1F4E4F, 0x82675BC5, 0xCD924D35,
26 : 0x81ABE279};
27 : static uint64_t random[] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476,
28 : 0xC3D2E1F0};
29 : static uint32_t randomOdd[] = {0xB4663807, 0xCC322BF5, 0xD4F91BBD, 0xA7BEA11D,
30 : 0x8F462907};
31 :
32 55887 : uint64_t hashes[] = {0, 0, 0, 0, 0};
33 55887 : uint64_t zi[] = {1, 1, 1, 1, 1};
34 :
35 : const size_t hashesSize = arraysize(hashes);
36 :
37 : size_t current = 0;
38 :
39 55887 : std::unique_ptr<UChar[]> buffer(new UChar[source->Length()]);
40 55887 : int written = source->Write(
41 55887 : isolate, reinterpret_cast<uint16_t*>(buffer.get()), 0, source->Length());
42 :
43 : const uint32_t* data = nullptr;
44 55887 : size_t sizeInBytes = sizeof(UChar) * written;
45 : data = reinterpret_cast<const uint32_t*>(buffer.get());
46 296109903 : for (size_t i = 0; i < sizeInBytes / 4; ++i) {
47 : uint32_t d = v8::internal::ReadUnalignedUInt32(
48 148027008 : reinterpret_cast<v8::internal::Address>(data + i));
49 : #if V8_TARGET_LITTLE_ENDIAN
50 : uint32_t v = d;
51 : #else
52 : uint32_t v = (d << 16) | (d >> 16);
53 : #endif
54 148027008 : uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
55 148027008 : hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
56 148027008 : zi[current] = (zi[current] * random[current]) % prime[current];
57 148027008 : current = current == hashesSize - 1 ? 0 : current + 1;
58 : }
59 55887 : if (sizeInBytes % 4) {
60 : uint32_t v = 0;
61 : const uint8_t* data_8b = reinterpret_cast<const uint8_t*>(data);
62 82005 : for (size_t i = sizeInBytes - sizeInBytes % 4; i < sizeInBytes; ++i) {
63 54670 : v <<= 8;
64 : #if V8_TARGET_LITTLE_ENDIAN
65 54670 : v |= data_8b[i];
66 : #else
67 : if (i % 2) {
68 : v |= data_8b[i - 1];
69 : } else {
70 : v |= data_8b[i + 1];
71 : }
72 : #endif
73 : }
74 27335 : uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
75 27335 : hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
76 27335 : zi[current] = (zi[current] * random[current]) % prime[current];
77 : current = current == hashesSize - 1 ? 0 : current + 1;
78 : }
79 :
80 614757 : for (size_t i = 0; i < hashesSize; ++i)
81 279435 : hashes[i] = (hashes[i] + zi[i] * (prime[i] - 1)) % prime[i];
82 :
83 55887 : String16Builder hash;
84 614757 : for (size_t i = 0; i < hashesSize; ++i)
85 279435 : hash.appendUnsignedAsHex(static_cast<uint32_t>(hashes[i]));
86 111774 : return hash.toString();
87 : }
88 :
89 116 : void TranslateProtocolLocationToV8Location(WasmTranslation* wasmTranslation,
90 : v8::debug::Location* loc,
91 : const String16& scriptId,
92 : const String16& expectedV8ScriptId) {
93 116 : if (loc->IsEmpty()) return;
94 116 : int lineNumber = loc->GetLineNumber();
95 116 : int columnNumber = loc->GetColumnNumber();
96 : String16 translatedScriptId = scriptId;
97 : wasmTranslation->TranslateProtocolLocationToWasmScriptLocation(
98 116 : &translatedScriptId, &lineNumber, &columnNumber);
99 : DCHECK_EQ(expectedV8ScriptId.utf8(), translatedScriptId.utf8());
100 116 : *loc = v8::debug::Location(lineNumber, columnNumber);
101 : }
102 :
103 148 : void TranslateV8LocationToProtocolLocation(
104 : WasmTranslation* wasmTranslation, v8::debug::Location* loc,
105 : const String16& scriptId, const String16& expectedProtocolScriptId) {
106 148 : int lineNumber = loc->GetLineNumber();
107 148 : int columnNumber = loc->GetColumnNumber();
108 : String16 translatedScriptId = scriptId;
109 : wasmTranslation->TranslateWasmScriptLocationToProtocolLocation(
110 148 : &translatedScriptId, &lineNumber, &columnNumber);
111 : DCHECK_EQ(expectedProtocolScriptId.utf8(), translatedScriptId.utf8());
112 148 : *loc = v8::debug::Location(lineNumber, columnNumber);
113 148 : }
114 :
115 167661 : class ActualScript : public V8DebuggerScript {
116 : friend class V8DebuggerScript;
117 :
118 : public:
119 55887 : ActualScript(v8::Isolate* isolate, v8::Local<v8::debug::Script> script,
120 : bool isLiveEdit, V8DebuggerAgentImpl* agent,
121 : V8InspectorClient* client)
122 111774 : : V8DebuggerScript(isolate, String16::fromInteger(script->Id()),
123 111774 : GetScriptURL(isolate, script, client)),
124 : m_agent(agent),
125 223548 : m_isLiveEdit(isLiveEdit) {
126 55887 : Initialize(script);
127 55887 : }
128 :
129 55887 : bool isLiveEdit() const override { return m_isLiveEdit; }
130 55887 : bool isModule() const override { return m_isModule; }
131 :
132 1534 : String16 source(size_t pos, size_t len) const override {
133 3068 : v8::HandleScope scope(m_isolate);
134 : v8::Local<v8::String> v8Source;
135 3068 : if (!script()->Source().ToLocal(&v8Source)) return String16();
136 1534 : if (pos >= static_cast<size_t>(v8Source->Length())) return String16();
137 : size_t substringLength =
138 3040 : std::min(len, static_cast<size_t>(v8Source->Length()) - pos);
139 1520 : std::unique_ptr<UChar[]> buffer(new UChar[substringLength]);
140 1520 : v8Source->Write(m_isolate, reinterpret_cast<uint16_t*>(buffer.get()),
141 1520 : static_cast<int>(pos), static_cast<int>(substringLength));
142 1520 : return String16(buffer.get(), substringLength);
143 : }
144 58114 : int startLine() const override { return m_startLine; }
145 55887 : int startColumn() const override { return m_startColumn; }
146 58104 : int endLine() const override { return m_endLine; }
147 55887 : int endColumn() const override { return m_endColumn; }
148 55701 : bool isSourceLoadedLazily() const override { return false; }
149 76866 : int length() const override {
150 153732 : v8::HandleScope scope(m_isolate);
151 : v8::Local<v8::String> v8Source;
152 230598 : return script()->Source().ToLocal(&v8Source) ? v8Source->Length() : 0;
153 : }
154 :
155 55887 : const String16& sourceMappingURL() const override {
156 55887 : return m_sourceMappingURL;
157 : }
158 :
159 186 : void setSourceMappingURL(const String16& sourceMappingURL) override {
160 : m_sourceMappingURL = sourceMappingURL;
161 186 : }
162 :
163 50 : void setSource(const String16& newSource, bool preview,
164 : v8::debug::LiveEditResult* result) override {
165 50 : v8::EscapableHandleScope scope(m_isolate);
166 50 : v8::Local<v8::String> v8Source = toV8String(m_isolate, newSource);
167 100 : if (!m_script.Get(m_isolate)->SetScriptSource(v8Source, preview, result)) {
168 10 : result->message = scope.Escape(result->message);
169 10 : return;
170 : }
171 40 : if (preview) return;
172 80 : m_hash = String16();
173 40 : Initialize(scope.Escape(result->script));
174 : }
175 :
176 235 : bool getPossibleBreakpoints(
177 : const v8::debug::Location& start, const v8::debug::Location& end,
178 : bool restrictToFunction,
179 : std::vector<v8::debug::BreakLocation>* locations) override {
180 470 : v8::HandleScope scope(m_isolate);
181 235 : v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
182 : std::vector<v8::debug::BreakLocation> allLocations;
183 235 : if (!script->GetPossibleBreakpoints(start, end, restrictToFunction,
184 : &allLocations)) {
185 : return false;
186 : }
187 230 : if (!allLocations.size()) return true;
188 205 : v8::debug::BreakLocation current = allLocations[0];
189 9635 : for (size_t i = 1; i < allLocations.size(); ++i) {
190 7545 : if (allLocations[i].GetLineNumber() == current.GetLineNumber() &&
191 2830 : allLocations[i].GetColumnNumber() == current.GetColumnNumber()) {
192 740 : if (allLocations[i].type() != v8::debug::kCommonBreakLocation) {
193 : DCHECK(allLocations[i].type() == v8::debug::kCallBreakLocation ||
194 : allLocations[i].type() == v8::debug::kReturnBreakLocation);
195 : // debugger can returns more then one break location at the same
196 : // source location, e.g. foo() - in this case there are two break
197 : // locations before foo: for statement and for function call, we can
198 : // merge them for inspector and report only one with call type.
199 435 : current = allLocations[i];
200 : }
201 : } else {
202 : // we assume that returned break locations are sorted.
203 : DCHECK(
204 : allLocations[i].GetLineNumber() > current.GetLineNumber() ||
205 : (allLocations[i].GetColumnNumber() >= current.GetColumnNumber() &&
206 : allLocations[i].GetLineNumber() == current.GetLineNumber()));
207 3975 : locations->push_back(current);
208 3975 : current = allLocations[i];
209 : }
210 : }
211 205 : locations->push_back(current);
212 205 : return true;
213 : }
214 :
215 112020 : void resetBlackboxedStateCache() override {
216 224040 : v8::HandleScope scope(m_isolate);
217 224040 : v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate));
218 112020 : }
219 :
220 310 : int offset(int lineNumber, int columnNumber) const override {
221 620 : v8::HandleScope scope(m_isolate);
222 620 : return m_script.Get(m_isolate)->GetSourceOffset(
223 620 : v8::debug::Location(lineNumber, columnNumber));
224 : }
225 :
226 60 : v8::debug::Location location(int offset) const override {
227 120 : v8::HandleScope scope(m_isolate);
228 180 : return m_script.Get(m_isolate)->GetSourceLocation(offset);
229 : }
230 :
231 2062 : bool setBreakpoint(const String16& condition, v8::debug::Location* location,
232 : int* id) const override {
233 4124 : v8::HandleScope scope(m_isolate);
234 2062 : return script()->SetBreakpoint(toV8String(m_isolate, condition), location,
235 4124 : id);
236 : }
237 :
238 55952 : const String16& hash() const override {
239 55952 : if (!m_hash.isEmpty()) return m_hash;
240 111774 : v8::HandleScope scope(m_isolate);
241 : v8::Local<v8::String> v8Source;
242 111774 : if (script()->Source().ToLocal(&v8Source)) {
243 111774 : m_hash = calculateHash(m_isolate, v8Source);
244 : }
245 : DCHECK(!m_hash.isEmpty());
246 55887 : return m_hash;
247 : }
248 :
249 : private:
250 55887 : static String16 GetScriptURL(v8::Isolate* isolate,
251 : v8::Local<v8::debug::Script> script,
252 : V8InspectorClient* client) {
253 : v8::Local<v8::String> sourceURL;
254 111774 : if (script->SourceURL().ToLocal(&sourceURL) && sourceURL->Length() > 0)
255 2195 : return toProtocolString(isolate, sourceURL);
256 : v8::Local<v8::String> v8Name;
257 107384 : if (script->Name().ToLocal(&v8Name) && v8Name->Length() > 0) {
258 16022 : String16 name = toProtocolString(isolate, v8Name);
259 : std::unique_ptr<StringBuffer> url =
260 16022 : client->resourceNameToUrl(toStringView(name));
261 16022 : return url ? toString16(url->string()) : name;
262 : }
263 37670 : return String16();
264 : }
265 :
266 0 : v8::Local<v8::debug::Script> script() const override {
267 136349 : return m_script.Get(m_isolate);
268 : }
269 :
270 55927 : void Initialize(v8::Local<v8::debug::Script> script) {
271 : v8::Local<v8::String> tmp;
272 : m_hasSourceURLComment =
273 111854 : script->SourceURL().ToLocal(&tmp) && tmp->Length() > 0;
274 111854 : if (script->SourceMappingURL().ToLocal(&tmp))
275 1052 : m_sourceMappingURL = toProtocolString(m_isolate, tmp);
276 55927 : m_startLine = script->LineOffset();
277 55927 : m_startColumn = script->ColumnOffset();
278 55927 : std::vector<int> lineEnds = script->LineEnds();
279 55927 : CHECK(lineEnds.size());
280 111854 : int source_length = lineEnds[lineEnds.size() - 1];
281 55927 : if (lineEnds.size()) {
282 55927 : m_endLine = static_cast<int>(lineEnds.size()) + m_startLine - 1;
283 55927 : if (lineEnds.size() > 1) {
284 42898 : m_endColumn = source_length - lineEnds[lineEnds.size() - 2] - 1;
285 : } else {
286 34478 : m_endColumn = source_length + m_startColumn;
287 : }
288 : } else {
289 0 : m_endLine = m_startLine;
290 0 : m_endColumn = m_startColumn;
291 : }
292 :
293 111854 : USE(script->ContextId().To(&m_executionContextId));
294 :
295 55927 : m_isModule = script->IsModule();
296 :
297 55927 : m_script.Reset(m_isolate, script);
298 : m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
299 55927 : }
300 :
301 55887 : void MakeWeak() override {
302 : m_script.SetWeak(
303 : this,
304 20850 : [](const v8::WeakCallbackInfo<ActualScript>& data) {
305 : data.GetParameter()->WeakCallback();
306 20850 : },
307 : v8::WeakCallbackType::kFinalizer);
308 55887 : }
309 :
310 : void WeakCallback() {
311 : m_script.ClearWeak();
312 20850 : m_agent->ScriptCollected(this);
313 : }
314 :
315 : V8DebuggerAgentImpl* m_agent;
316 : String16 m_sourceMappingURL;
317 : bool m_isLiveEdit = false;
318 : bool m_isModule = false;
319 : mutable String16 m_hash;
320 : int m_startLine = 0;
321 : int m_startColumn = 0;
322 : int m_endLine = 0;
323 : int m_endColumn = 0;
324 : v8::Global<v8::debug::Script> m_script;
325 : };
326 :
327 252 : class WasmVirtualScript : public V8DebuggerScript {
328 : friend class V8DebuggerScript;
329 :
330 : public:
331 84 : WasmVirtualScript(v8::Isolate* isolate, WasmTranslation* wasmTranslation,
332 : v8::Local<v8::debug::WasmScript> script, String16 id,
333 : String16 url, int functionIndex)
334 : : V8DebuggerScript(isolate, std::move(id), std::move(url)),
335 : m_script(isolate, script),
336 : m_wasmTranslation(wasmTranslation),
337 336 : m_functionIndex(functionIndex) {
338 : m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
339 168 : m_executionContextId = script->ContextId().ToChecked();
340 84 : }
341 :
342 84 : const String16& sourceMappingURL() const override { return emptyString(); }
343 84 : bool isLiveEdit() const override { return false; }
344 84 : bool isModule() const override { return false; }
345 0 : void setSourceMappingURL(const String16&) override {}
346 0 : void setSource(const String16&, bool, v8::debug::LiveEditResult*) override {
347 0 : UNREACHABLE();
348 : }
349 84 : bool isSourceLoadedLazily() const override { return true; }
350 52 : String16 source(size_t pos, size_t len) const override {
351 52 : return m_wasmTranslation->GetSource(m_id, m_functionIndex)
352 52 : .substring(pos, len);
353 : }
354 76 : int startLine() const override {
355 76 : return m_wasmTranslation->GetStartLine(m_id, m_functionIndex);
356 : }
357 0 : int startColumn() const override {
358 0 : return m_wasmTranslation->GetStartColumn(m_id, m_functionIndex);
359 : }
360 76 : int endLine() const override {
361 76 : return m_wasmTranslation->GetEndLine(m_id, m_functionIndex);
362 : }
363 0 : int endColumn() const override {
364 0 : return m_wasmTranslation->GetEndColumn(m_id, m_functionIndex);
365 : }
366 0 : int length() const override {
367 0 : return static_cast<int>(source(0, UINT_MAX).length());
368 : }
369 :
370 24 : bool getPossibleBreakpoints(
371 : const v8::debug::Location& start, const v8::debug::Location& end,
372 : bool restrictToFunction,
373 : std::vector<v8::debug::BreakLocation>* locations) override {
374 48 : v8::HandleScope scope(m_isolate);
375 24 : v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
376 24 : String16 v8ScriptId = String16::fromInteger(script->Id());
377 :
378 24 : v8::debug::Location translatedStart = start;
379 24 : TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedStart,
380 24 : scriptId(), v8ScriptId);
381 :
382 24 : v8::debug::Location translatedEnd = end;
383 24 : if (translatedEnd.IsEmpty()) {
384 : // Stop before the start of the next function.
385 : translatedEnd =
386 8 : v8::debug::Location(translatedStart.GetLineNumber() + 1, 0);
387 : } else {
388 16 : TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedEnd,
389 16 : scriptId(), v8ScriptId);
390 : }
391 :
392 24 : bool success = script->GetPossibleBreakpoints(
393 24 : translatedStart, translatedEnd, restrictToFunction, locations);
394 96 : for (v8::debug::BreakLocation& loc : *locations) {
395 72 : TranslateV8LocationToProtocolLocation(m_wasmTranslation, &loc, v8ScriptId,
396 72 : scriptId());
397 : }
398 24 : return success;
399 : }
400 :
401 168 : void resetBlackboxedStateCache() override {}
402 :
403 0 : int offset(int lineNumber, int columnNumber) const override {
404 0 : return kNoOffset;
405 : }
406 :
407 0 : v8::debug::Location location(int offset) const override {
408 0 : return v8::debug::Location();
409 : }
410 :
411 76 : bool setBreakpoint(const String16& condition, v8::debug::Location* location,
412 : int* id) const override {
413 152 : v8::HandleScope scope(m_isolate);
414 76 : v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
415 76 : String16 v8ScriptId = String16::fromInteger(script->Id());
416 :
417 76 : TranslateProtocolLocationToV8Location(m_wasmTranslation, location,
418 76 : scriptId(), v8ScriptId);
419 76 : if (location->IsEmpty()) return false;
420 76 : if (!script->SetBreakpoint(toV8String(m_isolate, condition), location, id))
421 : return false;
422 76 : TranslateV8LocationToProtocolLocation(m_wasmTranslation, location,
423 76 : v8ScriptId, scriptId());
424 76 : return true;
425 : }
426 :
427 84 : const String16& hash() const override {
428 84 : if (m_hash.isEmpty()) {
429 168 : m_hash = m_wasmTranslation->GetHash(m_id, m_functionIndex);
430 : }
431 84 : return m_hash;
432 : }
433 :
434 84 : void MakeWeak() override {}
435 :
436 : private:
437 84 : static const String16& emptyString() {
438 : // On the heap and leaked so that no destructor needs to run at exit time.
439 124 : static const String16* singleEmptyString = new String16;
440 84 : return *singleEmptyString;
441 : }
442 :
443 0 : v8::Local<v8::debug::Script> script() const override {
444 0 : return m_script.Get(m_isolate);
445 : }
446 :
447 : v8::Global<v8::debug::WasmScript> m_script;
448 : WasmTranslation* m_wasmTranslation;
449 : int m_functionIndex;
450 : mutable String16 m_hash;
451 : };
452 :
453 : } // namespace
454 :
455 55887 : std::unique_ptr<V8DebuggerScript> V8DebuggerScript::Create(
456 : v8::Isolate* isolate, v8::Local<v8::debug::Script> scriptObj,
457 : bool isLiveEdit, V8DebuggerAgentImpl* agent, V8InspectorClient* client) {
458 111774 : return v8::base::make_unique<ActualScript>(isolate, scriptObj, isLiveEdit,
459 55887 : agent, client);
460 : }
461 :
462 84 : std::unique_ptr<V8DebuggerScript> V8DebuggerScript::CreateWasm(
463 : v8::Isolate* isolate, WasmTranslation* wasmTranslation,
464 : v8::Local<v8::debug::WasmScript> underlyingScript, String16 id,
465 : String16 url, int functionIndex) {
466 168 : return v8::base::make_unique<WasmVirtualScript>(
467 : isolate, wasmTranslation, underlyingScript, std::move(id), std::move(url),
468 84 : functionIndex);
469 : }
470 :
471 55971 : V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id,
472 : String16 url)
473 111942 : : m_id(std::move(id)), m_url(std::move(url)), m_isolate(isolate) {}
474 :
475 : V8DebuggerScript::~V8DebuggerScript() = default;
476 :
477 186 : void V8DebuggerScript::setSourceURL(const String16& sourceURL) {
478 186 : if (sourceURL.length() > 0) {
479 25 : m_hasSourceURLComment = true;
480 : m_url = sourceURL;
481 : }
482 186 : }
483 :
484 0 : bool V8DebuggerScript::setBreakpoint(const String16& condition,
485 : v8::debug::Location* loc, int* id) const {
486 0 : v8::HandleScope scope(m_isolate);
487 0 : return script()->SetBreakpoint(toV8String(m_isolate, condition), loc, id);
488 : }
489 :
490 : } // namespace v8_inspector
|