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 60443 : 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 60443 : uint64_t hashes[] = {0, 0, 0, 0, 0};
32 60443 : uint64_t zi[] = {1, 1, 1, 1, 1};
33 :
34 : const size_t hashesSize = arraysize(hashes);
35 :
36 : size_t current = 0;
37 :
38 60443 : std::unique_ptr<UChar[]> buffer(new UChar[source->Length()]);
39 : int written = source->Write(
40 60443 : isolate, reinterpret_cast<uint16_t*>(buffer.get()), 0, source->Length());
41 :
42 : const uint32_t* data = nullptr;
43 60443 : size_t sizeInBytes = sizeof(UChar) * written;
44 : data = reinterpret_cast<const uint32_t*>(buffer.get());
45 144246180 : for (size_t i = 0; i < sizeInBytes / 4; ++i) {
46 : uint32_t d = v8::internal::ReadUnalignedUInt32(
47 144185737 : 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 144185737 : uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
54 144185737 : hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
55 144185737 : zi[current] = (zi[current] * random[current]) % prime[current];
56 144185737 : current = current == hashesSize - 1 ? 0 : current + 1;
57 : }
58 60443 : if (sizeInBytes % 4) {
59 : uint32_t v = 0;
60 : const uint8_t* data_8b = reinterpret_cast<const uint8_t*>(data);
61 80598 : for (size_t i = sizeInBytes - sizeInBytes % 4; i < sizeInBytes; ++i) {
62 53732 : v <<= 8;
63 : #if V8_TARGET_LITTLE_ENDIAN
64 53732 : 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 26866 : uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
74 26866 : hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
75 26866 : zi[current] = (zi[current] * random[current]) % prime[current];
76 : current = current == hashesSize - 1 ? 0 : current + 1;
77 : }
78 :
79 302215 : for (size_t i = 0; i < hashesSize; ++i)
80 302215 : hashes[i] = (hashes[i] + zi[i] * (prime[i] - 1)) % prime[i];
81 :
82 60443 : String16Builder hash;
83 362658 : for (size_t i = 0; i < hashesSize; ++i)
84 302215 : hash.appendUnsignedAsHex(static_cast<uint32_t>(hashes[i]));
85 120886 : return hash.toString();
86 : }
87 :
88 116 : void TranslateProtocolLocationToV8Location(WasmTranslation* wasmTranslation,
89 : v8::debug::Location* loc,
90 : const String16& scriptId,
91 : const String16& expectedV8ScriptId) {
92 116 : if (loc->IsEmpty()) return;
93 116 : int lineNumber = loc->GetLineNumber();
94 116 : int columnNumber = loc->GetColumnNumber();
95 : String16 translatedScriptId = scriptId;
96 : wasmTranslation->TranslateProtocolLocationToWasmScriptLocation(
97 116 : &translatedScriptId, &lineNumber, &columnNumber);
98 : DCHECK_EQ(expectedV8ScriptId.utf8(), translatedScriptId.utf8());
99 116 : *loc = v8::debug::Location(lineNumber, columnNumber);
100 : }
101 :
102 148 : void TranslateV8LocationToProtocolLocation(
103 : WasmTranslation* wasmTranslation, v8::debug::Location* loc,
104 : const String16& scriptId, const String16& expectedProtocolScriptId) {
105 148 : int lineNumber = loc->GetLineNumber();
106 148 : int columnNumber = loc->GetColumnNumber();
107 : String16 translatedScriptId = scriptId;
108 : wasmTranslation->TranslateWasmScriptLocationToProtocolLocation(
109 148 : &translatedScriptId, &lineNumber, &columnNumber);
110 : DCHECK_EQ(expectedProtocolScriptId.utf8(), translatedScriptId.utf8());
111 148 : *loc = v8::debug::Location(lineNumber, columnNumber);
112 148 : }
113 :
114 181329 : class ActualScript : public V8DebuggerScript {
115 : friend class V8DebuggerScript;
116 :
117 : public:
118 60443 : 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 362658 : m_isLiveEdit(isLiveEdit) {
123 60443 : Initialize(script);
124 60443 : }
125 :
126 60443 : bool isLiveEdit() const override { return m_isLiveEdit; }
127 60443 : bool isModule() const override { return m_isModule; }
128 :
129 21037 : String16 source(size_t pos, size_t len) const override {
130 21037 : v8::HandleScope scope(m_isolate);
131 : v8::Local<v8::String> v8Source;
132 63111 : if (!script()->Source().ToLocal(&v8Source)) return String16();
133 21037 : if (pos >= static_cast<size_t>(v8Source->Length())) return String16();
134 : size_t substringLength =
135 42046 : std::min(len, static_cast<size_t>(v8Source->Length()) - pos);
136 21023 : std::unique_ptr<UChar[]> buffer(new UChar[substringLength]);
137 : v8Source->Write(m_isolate, reinterpret_cast<uint16_t*>(buffer.get()),
138 21023 : static_cast<int>(pos), static_cast<int>(substringLength));
139 42060 : return String16(buffer.get(), substringLength);
140 : }
141 62665 : int startLine() const override { return m_startLine; }
142 60443 : int startColumn() const override { return m_startColumn; }
143 62655 : int endLine() const override { return m_endLine; }
144 60443 : int endColumn() const override { return m_endColumn; }
145 55242 : bool isSourceLoadedLazily() const override { return false; }
146 60443 : int length() const override {
147 60443 : v8::HandleScope scope(m_isolate);
148 : v8::Local<v8::String> v8Source;
149 181329 : if (!script()->Source().ToLocal(&v8Source)) return 0;
150 60443 : return v8Source->Length();
151 : }
152 :
153 60443 : const String16& sourceMappingURL() const override {
154 60443 : return m_sourceMappingURL;
155 : }
156 :
157 5201 : void setSourceMappingURL(const String16& sourceMappingURL) override {
158 : 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 : m_hash = String16();
171 40 : Initialize(scope.Escape(result->script));
172 : }
173 :
174 235 : 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 235 : v8::HandleScope scope(m_isolate);
179 235 : v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
180 : std::vector<v8::debug::BreakLocation> allLocations;
181 235 : if (!script->GetPossibleBreakpoints(start, end, restrictToFunction,
182 235 : &allLocations)) {
183 : return false;
184 : }
185 460 : if (!allLocations.size()) return true;
186 205 : v8::debug::BreakLocation current = allLocations[0];
187 10020 : for (size_t i = 1; i < allLocations.size(); ++i) {
188 7725 : if (allLocations[i].GetLineNumber() == current.GetLineNumber() &&
189 5840 : 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 3975 : locations->push_back(current);
206 7950 : current = allLocations[i];
207 : }
208 : }
209 205 : locations->push_back(current);
210 440 : return true;
211 : }
212 :
213 120761 : void resetBlackboxedStateCache() override {
214 120761 : v8::HandleScope scope(m_isolate);
215 241522 : v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate));
216 120761 : }
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 2047 : bool setBreakpoint(const String16& condition, v8::debug::Location* location,
230 : int* id) const override {
231 2047 : v8::HandleScope scope(m_isolate);
232 2047 : return script()->SetBreakpoint(toV8String(m_isolate, condition), location,
233 6141 : id);
234 : }
235 :
236 60508 : const String16& hash() const override {
237 60508 : if (m_hash.isEmpty()) {
238 60443 : v8::HandleScope scope(m_isolate);
239 : v8::Local<v8::String> v8Source;
240 181329 : if (script()->Source().ToLocal(&v8Source)) {
241 120886 : m_hash = calculateHash(m_isolate, v8Source);
242 60443 : }
243 : }
244 : DCHECK(!m_hash.isEmpty());
245 60508 : return m_hash;
246 : }
247 :
248 : private:
249 60443 : String16 GetScriptURL(v8::Isolate* isolate,
250 : v8::Local<v8::debug::Script> script,
251 : V8InspectorClient* client) {
252 : v8::Local<v8::String> sourceURL;
253 120886 : if (script->SourceURL().ToLocal(&sourceURL) && sourceURL->Length() > 0)
254 2205 : return toProtocolString(isolate, sourceURL);
255 : v8::Local<v8::String> v8Name;
256 116476 : if (script->Name().ToLocal(&v8Name) && v8Name->Length() > 0) {
257 16017 : String16 name = toProtocolString(isolate, v8Name);
258 : std::unique_ptr<StringBuffer> url =
259 16017 : client->resourceNameToUrl(toStringView(name));
260 16017 : return url ? toString16(url->string()) : name;
261 : }
262 42221 : return String16();
263 : }
264 :
265 143970 : v8::Local<v8::debug::Script> script() const override {
266 287940 : return m_script.Get(m_isolate);
267 : }
268 :
269 60483 : void Initialize(v8::Local<v8::debug::Script> script) {
270 : v8::Local<v8::String> tmp;
271 : m_hasSourceURLComment =
272 120966 : script->SourceURL().ToLocal(&tmp) && tmp->Length() > 0;
273 120966 : if (script->SourceMappingURL().ToLocal(&tmp))
274 992 : m_sourceMappingURL = toProtocolString(m_isolate, tmp);
275 60483 : m_startLine = script->LineOffset();
276 60483 : m_startColumn = script->ColumnOffset();
277 60483 : std::vector<int> lineEnds = script->LineEnds();
278 120966 : CHECK(lineEnds.size());
279 120966 : int source_length = lineEnds[lineEnds.size() - 1];
280 60483 : if (lineEnds.size()) {
281 60483 : m_endLine = static_cast<int>(lineEnds.size()) + m_startLine - 1;
282 60483 : if (lineEnds.size() > 1) {
283 42868 : m_endColumn = source_length - lineEnds[lineEnds.size() - 2] - 1;
284 : } else {
285 39049 : m_endColumn = source_length + m_startColumn;
286 : }
287 : } else {
288 0 : m_endLine = m_startLine;
289 0 : m_endColumn = m_startColumn;
290 : }
291 :
292 120966 : USE(script->ContextId().To(&m_executionContextId));
293 :
294 60483 : m_isModule = script->IsModule();
295 :
296 60483 : m_script.Reset(m_isolate, script);
297 : m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
298 60483 : }
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 252 : class WasmVirtualScript : public V8DebuggerScript {
312 : friend class V8DebuggerScript;
313 :
314 : public:
315 84 : 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 168 : m_functionIndex(functionIndex) {
322 : m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
323 168 : m_executionContextId = script->ContextId().ToChecked();
324 84 : }
325 :
326 84 : const String16& sourceMappingURL() const override { return emptyString(); }
327 84 : bool isLiveEdit() const override { return false; }
328 84 : 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 84 : bool isSourceLoadedLazily() const override { return true; }
334 52 : String16 source(size_t pos, size_t len) const override {
335 52 : return m_wasmTranslation->GetSource(m_id, m_functionIndex)
336 52 : .substring(pos, len);
337 : }
338 76 : int startLine() const override {
339 76 : 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 76 : int endLine() const override {
345 76 : 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 24 : 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 24 : v8::HandleScope scope(m_isolate);
359 24 : v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
360 24 : String16 v8ScriptId = String16::fromInteger(script->Id());
361 :
362 24 : v8::debug::Location translatedStart = start;
363 : TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedStart,
364 24 : scriptId(), v8ScriptId);
365 :
366 24 : v8::debug::Location translatedEnd = end;
367 24 : if (translatedEnd.IsEmpty()) {
368 : // Stop before the start of the next function.
369 : translatedEnd =
370 8 : v8::debug::Location(translatedStart.GetLineNumber() + 1, 0);
371 : } else {
372 : TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedEnd,
373 16 : scriptId(), v8ScriptId);
374 : }
375 :
376 : bool success = script->GetPossibleBreakpoints(
377 24 : translatedStart, translatedEnd, restrictToFunction, locations);
378 120 : for (v8::debug::BreakLocation& loc : *locations) {
379 : TranslateV8LocationToProtocolLocation(m_wasmTranslation, &loc, v8ScriptId,
380 72 : scriptId());
381 : }
382 24 : return success;
383 : }
384 :
385 168 : 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 76 : bool setBreakpoint(const String16& condition, v8::debug::Location* location,
396 : int* id) const override {
397 76 : v8::HandleScope scope(m_isolate);
398 76 : v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
399 76 : String16 v8ScriptId = String16::fromInteger(script->Id());
400 :
401 : TranslateProtocolLocationToV8Location(m_wasmTranslation, location,
402 76 : scriptId(), v8ScriptId);
403 76 : if (location->IsEmpty()) return false;
404 76 : if (!script->SetBreakpoint(toV8String(m_isolate, condition), location, id))
405 : return false;
406 : TranslateV8LocationToProtocolLocation(m_wasmTranslation, location,
407 76 : v8ScriptId, scriptId());
408 152 : return true;
409 : }
410 :
411 84 : const String16& hash() const override {
412 84 : if (m_hash.isEmpty()) {
413 168 : m_hash = m_wasmTranslation->GetHash(m_id, m_functionIndex);
414 : }
415 84 : return m_hash;
416 : }
417 :
418 : private:
419 84 : static const String16& emptyString() {
420 : // On the heap and leaked so that no destructor needs to run at exit time.
421 124 : static const String16* singleEmptyString = new String16;
422 84 : 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 60443 : 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 120886 : new ActualScript(isolate, scriptObj, isLiveEdit, client));
442 : }
443 :
444 84 : 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 336 : std::move(id), std::move(url), functionIndex));
451 : }
452 :
453 0 : V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id,
454 : String16 url)
455 121054 : : 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 : 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
|