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/base/memory.h"
8 #include "src/inspector/inspected-context.h"
9 #include "src/inspector/protocol/Debugger.h"
10 #include "src/inspector/string-util.h"
11 #include "src/inspector/v8-debugger-agent-impl.h"
12 #include "src/inspector/v8-inspector-impl.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
calculateHash(v8::Isolate * isolate,v8::Local<v8::String> source)24 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 uint64_t hashes[] = {0, 0, 0, 0, 0};
33 uint64_t zi[] = {1, 1, 1, 1, 1};
34
35 const size_t hashesSize = arraysize(hashes);
36
37 size_t current = 0;
38
39 std::unique_ptr<UChar[]> buffer(new UChar[source->Length()]);
40 int written = source->Write(
41 isolate, reinterpret_cast<uint16_t*>(buffer.get()), 0, source->Length());
42
43 const uint32_t* data = nullptr;
44 size_t sizeInBytes = sizeof(UChar) * written;
45 data = reinterpret_cast<const uint32_t*>(buffer.get());
46 for (size_t i = 0; i < sizeInBytes / 4; ++i) {
47 uint32_t d = v8::base::ReadUnalignedValue<uint32_t>(
48 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 uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
55 hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
56 zi[current] = (zi[current] * random[current]) % prime[current];
57 current = current == hashesSize - 1 ? 0 : current + 1;
58 }
59 if (sizeInBytes % 4) {
60 uint32_t v = 0;
61 const uint8_t* data_8b = reinterpret_cast<const uint8_t*>(data);
62 for (size_t i = sizeInBytes - sizeInBytes % 4; i < sizeInBytes; ++i) {
63 v <<= 8;
64 #if V8_TARGET_LITTLE_ENDIAN
65 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 uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
75 hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
76 zi[current] = (zi[current] * random[current]) % prime[current];
77 current = current == hashesSize - 1 ? 0 : current + 1;
78 }
79
80 for (size_t i = 0; i < hashesSize; ++i)
81 hashes[i] = (hashes[i] + zi[i] * (prime[i] - 1)) % prime[i];
82
83 String16Builder hash;
84 for (size_t i = 0; i < hashesSize; ++i)
85 hash.appendUnsignedAsHex(static_cast<uint32_t>(hashes[i]));
86 return hash.toString();
87 }
88
89 class ActualScript : public V8DebuggerScript {
90 friend class V8DebuggerScript;
91
92 public:
ActualScript(v8::Isolate * isolate,v8::Local<v8::debug::Script> script,bool isLiveEdit,V8DebuggerAgentImpl * agent,V8InspectorClient * client)93 ActualScript(v8::Isolate* isolate, v8::Local<v8::debug::Script> script,
94 bool isLiveEdit, V8DebuggerAgentImpl* agent,
95 V8InspectorClient* client)
96 : V8DebuggerScript(isolate, String16::fromInteger(script->Id()),
97 GetScriptURL(isolate, script, client),
98 GetScriptName(isolate, script, client)),
99 m_agent(agent),
100 m_isLiveEdit(isLiveEdit) {
101 Initialize(script);
102 }
103
isLiveEdit() const104 bool isLiveEdit() const override { return m_isLiveEdit; }
isModule() const105 bool isModule() const override { return m_isModule; }
106
source(size_t pos,size_t len) const107 String16 source(size_t pos, size_t len) const override {
108 v8::HandleScope scope(m_isolate);
109 v8::Local<v8::String> v8Source;
110 if (!script()->Source().ToLocal(&v8Source)) return String16();
111 if (pos >= static_cast<size_t>(v8Source->Length())) return String16();
112 size_t substringLength =
113 std::min(len, static_cast<size_t>(v8Source->Length()) - pos);
114 std::unique_ptr<UChar[]> buffer(new UChar[substringLength]);
115 v8Source->Write(m_isolate, reinterpret_cast<uint16_t*>(buffer.get()),
116 static_cast<int>(pos), static_cast<int>(substringLength));
117 return String16(buffer.get(), substringLength);
118 }
wasmBytecode() const119 v8::Maybe<v8::MemorySpan<const uint8_t>> wasmBytecode() const override {
120 v8::HandleScope scope(m_isolate);
121 auto script = this->script();
122 if (!script->IsWasm()) return v8::Nothing<v8::MemorySpan<const uint8_t>>();
123 return v8::Just(v8::debug::WasmScript::Cast(*script)->Bytecode());
124 }
getLanguage() const125 Language getLanguage() const override { return m_language; }
getDebugSymbolsType() const126 v8::Maybe<v8::debug::WasmScript::DebugSymbolsType> getDebugSymbolsType()
127 const override {
128 auto script = this->script();
129 if (!script->IsWasm())
130 return v8::Nothing<v8::debug::WasmScript::DebugSymbolsType>();
131 return v8::Just(v8::debug::WasmScript::Cast(*script)->GetDebugSymbolType());
132 }
getExternalDebugSymbolsURL() const133 v8::Maybe<String16> getExternalDebugSymbolsURL() const override {
134 auto script = this->script();
135 if (!script->IsWasm()) return v8::Nothing<String16>();
136 v8::MemorySpan<const char> external_url =
137 v8::debug::WasmScript::Cast(*script)->ExternalSymbolsURL();
138 if (external_url.size() == 0) return v8::Nothing<String16>();
139 return v8::Just(String16(external_url.data(), external_url.size()));
140 }
startLine() const141 int startLine() const override { return m_startLine; }
startColumn() const142 int startColumn() const override { return m_startColumn; }
endLine() const143 int endLine() const override { return m_endLine; }
endColumn() const144 int endColumn() const override { return m_endColumn; }
codeOffset() const145 int codeOffset() const override {
146 auto script = this->script();
147 if (!script->IsWasm()) return 0;
148 return v8::debug::WasmScript::Cast(*script)->CodeOffset();
149 }
isSourceLoadedLazily() const150 bool isSourceLoadedLazily() const override { return false; }
length() const151 int length() const override {
152 auto script = this->script();
153 if (script->IsWasm()) {
154 return static_cast<int>(
155 v8::debug::WasmScript::Cast(*script)->Bytecode().size());
156 }
157 v8::HandleScope scope(m_isolate);
158 v8::Local<v8::String> v8Source;
159 return script->Source().ToLocal(&v8Source) ? v8Source->Length() : 0;
160 }
161
sourceMappingURL() const162 const String16& sourceMappingURL() const override {
163 return m_sourceMappingURL;
164 }
165
setSourceMappingURL(const String16 & sourceMappingURL)166 void setSourceMappingURL(const String16& sourceMappingURL) override {
167 m_sourceMappingURL = sourceMappingURL;
168 }
169
setSource(const String16 & newSource,bool preview,v8::debug::LiveEditResult * result)170 void setSource(const String16& newSource, bool preview,
171 v8::debug::LiveEditResult* result) override {
172 v8::EscapableHandleScope scope(m_isolate);
173 v8::Local<v8::String> v8Source = toV8String(m_isolate, newSource);
174 if (!m_script.Get(m_isolate)->SetScriptSource(v8Source, preview, result)) {
175 result->message = scope.Escape(result->message);
176 return;
177 }
178 // NOP if preview or unchanged source (diffs.empty() in PatchScript)
179 if (preview || result->script.IsEmpty()) return;
180
181 m_hash = String16();
182 Initialize(scope.Escape(result->script));
183 }
184
getPossibleBreakpoints(const v8::debug::Location & start,const v8::debug::Location & end,bool restrictToFunction,std::vector<v8::debug::BreakLocation> * locations)185 bool getPossibleBreakpoints(
186 const v8::debug::Location& start, const v8::debug::Location& end,
187 bool restrictToFunction,
188 std::vector<v8::debug::BreakLocation>* locations) override {
189 v8::HandleScope scope(m_isolate);
190 v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
191 std::vector<v8::debug::BreakLocation> allLocations;
192 if (!script->GetPossibleBreakpoints(start, end, restrictToFunction,
193 &allLocations)) {
194 return false;
195 }
196 if (!allLocations.size()) return true;
197 v8::debug::BreakLocation current = allLocations[0];
198 for (size_t i = 1; i < allLocations.size(); ++i) {
199 if (allLocations[i].GetLineNumber() == current.GetLineNumber() &&
200 allLocations[i].GetColumnNumber() == current.GetColumnNumber()) {
201 if (allLocations[i].type() != v8::debug::kCommonBreakLocation) {
202 DCHECK(allLocations[i].type() == v8::debug::kCallBreakLocation ||
203 allLocations[i].type() == v8::debug::kReturnBreakLocation);
204 // debugger can returns more then one break location at the same
205 // source location, e.g. foo() - in this case there are two break
206 // locations before foo: for statement and for function call, we can
207 // merge them for inspector and report only one with call type.
208 current = allLocations[i];
209 }
210 } else {
211 // we assume that returned break locations are sorted.
212 DCHECK(
213 allLocations[i].GetLineNumber() > current.GetLineNumber() ||
214 (allLocations[i].GetColumnNumber() >= current.GetColumnNumber() &&
215 allLocations[i].GetLineNumber() == current.GetLineNumber()));
216 locations->push_back(current);
217 current = allLocations[i];
218 }
219 }
220 locations->push_back(current);
221 return true;
222 }
223
resetBlackboxedStateCache()224 void resetBlackboxedStateCache() override {
225 v8::HandleScope scope(m_isolate);
226 v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate));
227 }
228
offset(int lineNumber,int columnNumber) const229 int offset(int lineNumber, int columnNumber) const override {
230 v8::HandleScope scope(m_isolate);
231 return m_script.Get(m_isolate)->GetSourceOffset(
232 v8::debug::Location(lineNumber, columnNumber));
233 }
234
location(int offset) const235 v8::debug::Location location(int offset) const override {
236 v8::HandleScope scope(m_isolate);
237 return m_script.Get(m_isolate)->GetSourceLocation(offset);
238 }
239
setBreakpoint(const String16 & condition,v8::debug::Location * location,int * id) const240 bool setBreakpoint(const String16& condition, v8::debug::Location* location,
241 int* id) const override {
242 v8::HandleScope scope(m_isolate);
243 return script()->SetBreakpoint(toV8String(m_isolate, condition), location,
244 id);
245 }
246
setBreakpointOnRun(int * id) const247 bool setBreakpointOnRun(int* id) const override {
248 v8::HandleScope scope(m_isolate);
249 return script()->SetBreakpointOnScriptEntry(id);
250 }
251
hash() const252 const String16& hash() const override {
253 if (!m_hash.isEmpty()) return m_hash;
254 v8::HandleScope scope(m_isolate);
255 v8::Local<v8::String> v8Source;
256 if (script()->Source().ToLocal(&v8Source)) {
257 m_hash = calculateHash(m_isolate, v8Source);
258 }
259 DCHECK(!m_hash.isEmpty());
260 return m_hash;
261 }
262
263 private:
GetScriptURL(v8::Isolate * isolate,v8::Local<v8::debug::Script> script,V8InspectorClient * client)264 static String16 GetScriptURL(v8::Isolate* isolate,
265 v8::Local<v8::debug::Script> script,
266 V8InspectorClient* client) {
267 v8::Local<v8::String> sourceURL;
268 if (script->SourceURL().ToLocal(&sourceURL) && sourceURL->Length() > 0)
269 return toProtocolString(isolate, sourceURL);
270 return GetScriptName(isolate, script, client);
271 }
272
GetScriptName(v8::Isolate * isolate,v8::Local<v8::debug::Script> script,V8InspectorClient * client)273 static String16 GetScriptName(v8::Isolate* isolate,
274 v8::Local<v8::debug::Script> script,
275 V8InspectorClient* client) {
276 v8::Local<v8::String> v8Name;
277 if (script->Name().ToLocal(&v8Name) && v8Name->Length() > 0) {
278 String16 name = toProtocolString(isolate, v8Name);
279 std::unique_ptr<StringBuffer> url =
280 client->resourceNameToUrl(toStringView(name));
281 return url ? toString16(url->string()) : name;
282 }
283 return String16();
284 }
285
script() const286 v8::Local<v8::debug::Script> script() const override {
287 return m_script.Get(m_isolate);
288 }
289
Initialize(v8::Local<v8::debug::Script> script)290 void Initialize(v8::Local<v8::debug::Script> script) {
291 v8::Local<v8::String> tmp;
292 m_hasSourceURLComment =
293 script->SourceURL().ToLocal(&tmp) && tmp->Length() > 0;
294 if (script->SourceMappingURL().ToLocal(&tmp))
295 m_sourceMappingURL = toProtocolString(m_isolate, tmp);
296 m_startLine = script->LineOffset();
297 m_startColumn = script->ColumnOffset();
298 std::vector<int> lineEnds = script->LineEnds();
299 if (lineEnds.size()) {
300 int source_length = lineEnds[lineEnds.size() - 1];
301 m_endLine = static_cast<int>(lineEnds.size()) + m_startLine - 1;
302 if (lineEnds.size() > 1) {
303 m_endColumn = source_length - lineEnds[lineEnds.size() - 2] - 1;
304 } else {
305 m_endColumn = source_length + m_startColumn;
306 }
307 } else if (script->IsWasm()) {
308 DCHECK_EQ(0, m_startLine);
309 DCHECK_EQ(0, m_startColumn);
310 m_endLine = 0;
311 m_endColumn = static_cast<int>(
312 v8::debug::WasmScript::Cast(*script)->Bytecode().size());
313 } else {
314 m_endLine = m_startLine;
315 m_endColumn = m_startColumn;
316 }
317
318 USE(script->ContextId().To(&m_executionContextId));
319 if (script->IsWasm()) {
320 m_language = V8DebuggerScript::Language::WebAssembly;
321 } else {
322 m_language = V8DebuggerScript::Language::JavaScript;
323 }
324
325 m_isModule = script->IsModule();
326
327 m_script.Reset(m_isolate, script);
328 m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
329 }
330
MakeWeak()331 void MakeWeak() override {
332 m_script.SetWeak(
333 this,
334 [](const v8::WeakCallbackInfo<ActualScript>& data) {
335 data.GetParameter()->WeakCallback();
336 },
337 v8::WeakCallbackType::kFinalizer);
338 }
339
WeakCallback()340 void WeakCallback() {
341 m_script.ClearWeak();
342 m_agent->ScriptCollected(this);
343 }
344
345 V8DebuggerAgentImpl* m_agent;
346 String16 m_sourceMappingURL;
347 Language m_language;
348 bool m_isLiveEdit = false;
349 bool m_isModule = false;
350 mutable String16 m_hash;
351 int m_startLine = 0;
352 int m_startColumn = 0;
353 int m_endLine = 0;
354 int m_endColumn = 0;
355 v8::Global<v8::debug::Script> m_script;
356 };
357
358 } // namespace
359
Create(v8::Isolate * isolate,v8::Local<v8::debug::Script> scriptObj,bool isLiveEdit,V8DebuggerAgentImpl * agent,V8InspectorClient * client)360 std::unique_ptr<V8DebuggerScript> V8DebuggerScript::Create(
361 v8::Isolate* isolate, v8::Local<v8::debug::Script> scriptObj,
362 bool isLiveEdit, V8DebuggerAgentImpl* agent, V8InspectorClient* client) {
363 return std::make_unique<ActualScript>(isolate, scriptObj, isLiveEdit, agent,
364 client);
365 }
366
V8DebuggerScript(v8::Isolate * isolate,String16 id,String16 url,String16 embedderName)367 V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id,
368 String16 url, String16 embedderName)
369 : m_id(std::move(id)),
370 m_url(std::move(url)),
371 m_isolate(isolate),
372 m_embedderName(embedderName) {}
373
374 V8DebuggerScript::~V8DebuggerScript() = default;
375
setSourceURL(const String16 & sourceURL)376 void V8DebuggerScript::setSourceURL(const String16& sourceURL) {
377 if (sourceURL.length() > 0) {
378 m_hasSourceURLComment = true;
379 m_url = sourceURL;
380 }
381 }
382
setBreakpoint(const String16 & condition,v8::debug::Location * loc,int * id) const383 bool V8DebuggerScript::setBreakpoint(const String16& condition,
384 v8::debug::Location* loc, int* id) const {
385 v8::HandleScope scope(m_isolate);
386 return script()->SetBreakpoint(toV8String(m_isolate, condition), loc, id);
387 }
388
removeWasmBreakpoint(int id)389 void V8DebuggerScript::removeWasmBreakpoint(int id) {
390 v8::HandleScope scope(m_isolate);
391 script()->RemoveWasmBreakpoint(id);
392 }
393
394 } // namespace v8_inspector
395