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 m_agent(agent),
99 m_isLiveEdit(isLiveEdit) {
100 Initialize(script);
101 }
102
isLiveEdit() const103 bool isLiveEdit() const override { return m_isLiveEdit; }
isModule() const104 bool isModule() const override { return m_isModule; }
105
source(size_t pos,size_t len) const106 String16 source(size_t pos, size_t len) const override {
107 v8::HandleScope scope(m_isolate);
108 v8::Local<v8::String> v8Source;
109 if (!script()->Source().ToLocal(&v8Source)) return String16();
110 if (pos >= static_cast<size_t>(v8Source->Length())) return String16();
111 size_t substringLength =
112 std::min(len, static_cast<size_t>(v8Source->Length()) - pos);
113 std::unique_ptr<UChar[]> buffer(new UChar[substringLength]);
114 v8Source->Write(m_isolate, reinterpret_cast<uint16_t*>(buffer.get()),
115 static_cast<int>(pos), static_cast<int>(substringLength));
116 return String16(buffer.get(), substringLength);
117 }
wasmBytecode() const118 v8::Maybe<v8::MemorySpan<const uint8_t>> wasmBytecode() const override {
119 v8::HandleScope scope(m_isolate);
120 auto script = this->script();
121 if (!script->IsWasm()) return v8::Nothing<v8::MemorySpan<const uint8_t>>();
122 return v8::Just(v8::debug::WasmScript::Cast(*script)->Bytecode());
123 }
getLanguage() const124 Language getLanguage() const override { return m_language; }
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; }
codeOffset() const129 int codeOffset() const override {
130 auto script = this->script();
131 if (!script->IsWasm()) return 0;
132 return v8::debug::WasmScript::Cast(*script)->CodeOffset();
133 }
isSourceLoadedLazily() const134 bool isSourceLoadedLazily() const override { return false; }
length() const135 int length() const override {
136 v8::HandleScope scope(m_isolate);
137 v8::Local<v8::String> v8Source;
138 return script()->Source().ToLocal(&v8Source) ? v8Source->Length() : 0;
139 }
140
sourceMappingURL() const141 const String16& sourceMappingURL() const override {
142 return m_sourceMappingURL;
143 }
144
setSourceMappingURL(const String16 & sourceMappingURL)145 void setSourceMappingURL(const String16& sourceMappingURL) override {
146 m_sourceMappingURL = sourceMappingURL;
147 }
148
setSource(const String16 & newSource,bool preview,v8::debug::LiveEditResult * result)149 void setSource(const String16& newSource, bool preview,
150 v8::debug::LiveEditResult* result) override {
151 v8::EscapableHandleScope scope(m_isolate);
152 v8::Local<v8::String> v8Source = toV8String(m_isolate, newSource);
153 if (!m_script.Get(m_isolate)->SetScriptSource(v8Source, preview, result)) {
154 result->message = scope.Escape(result->message);
155 return;
156 }
157 if (preview) return;
158 m_hash = String16();
159 Initialize(scope.Escape(result->script));
160 }
161
getPossibleBreakpoints(const v8::debug::Location & start,const v8::debug::Location & end,bool restrictToFunction,std::vector<v8::debug::BreakLocation> * locations)162 bool getPossibleBreakpoints(
163 const v8::debug::Location& start, const v8::debug::Location& end,
164 bool restrictToFunction,
165 std::vector<v8::debug::BreakLocation>* locations) override {
166 v8::HandleScope scope(m_isolate);
167 v8::Local<v8::debug::Script> script = m_script.Get(m_isolate);
168 std::vector<v8::debug::BreakLocation> allLocations;
169 if (!script->GetPossibleBreakpoints(start, end, restrictToFunction,
170 &allLocations)) {
171 return false;
172 }
173 if (!allLocations.size()) return true;
174 v8::debug::BreakLocation current = allLocations[0];
175 for (size_t i = 1; i < allLocations.size(); ++i) {
176 if (allLocations[i].GetLineNumber() == current.GetLineNumber() &&
177 allLocations[i].GetColumnNumber() == current.GetColumnNumber()) {
178 if (allLocations[i].type() != v8::debug::kCommonBreakLocation) {
179 DCHECK(allLocations[i].type() == v8::debug::kCallBreakLocation ||
180 allLocations[i].type() == v8::debug::kReturnBreakLocation);
181 // debugger can returns more then one break location at the same
182 // source location, e.g. foo() - in this case there are two break
183 // locations before foo: for statement and for function call, we can
184 // merge them for inspector and report only one with call type.
185 current = allLocations[i];
186 }
187 } else {
188 // we assume that returned break locations are sorted.
189 DCHECK(
190 allLocations[i].GetLineNumber() > current.GetLineNumber() ||
191 (allLocations[i].GetColumnNumber() >= current.GetColumnNumber() &&
192 allLocations[i].GetLineNumber() == current.GetLineNumber()));
193 locations->push_back(current);
194 current = allLocations[i];
195 }
196 }
197 locations->push_back(current);
198 return true;
199 }
200
resetBlackboxedStateCache()201 void resetBlackboxedStateCache() override {
202 v8::HandleScope scope(m_isolate);
203 v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate));
204 }
205
offset(int lineNumber,int columnNumber) const206 int offset(int lineNumber, int columnNumber) const override {
207 v8::HandleScope scope(m_isolate);
208 return m_script.Get(m_isolate)->GetSourceOffset(
209 v8::debug::Location(lineNumber, columnNumber));
210 }
211
location(int offset) const212 v8::debug::Location location(int offset) const override {
213 v8::HandleScope scope(m_isolate);
214 return m_script.Get(m_isolate)->GetSourceLocation(offset);
215 }
216
setBreakpoint(const String16 & condition,v8::debug::Location * location,int * id) const217 bool setBreakpoint(const String16& condition, v8::debug::Location* location,
218 int* id) const override {
219 v8::HandleScope scope(m_isolate);
220 return script()->SetBreakpoint(toV8String(m_isolate, condition), location,
221 id);
222 }
223
setBreakpointOnRun(int * id) const224 bool setBreakpointOnRun(int* id) const override {
225 v8::HandleScope scope(m_isolate);
226 return script()->SetBreakpointOnScriptEntry(id);
227 }
228
hash() const229 const String16& hash() const override {
230 if (!m_hash.isEmpty()) return m_hash;
231 v8::HandleScope scope(m_isolate);
232 v8::Local<v8::String> v8Source;
233 if (script()->Source().ToLocal(&v8Source)) {
234 m_hash = calculateHash(m_isolate, v8Source);
235 }
236 DCHECK(!m_hash.isEmpty());
237 return m_hash;
238 }
239
240 private:
GetScriptURL(v8::Isolate * isolate,v8::Local<v8::debug::Script> script,V8InspectorClient * client)241 static String16 GetScriptURL(v8::Isolate* isolate,
242 v8::Local<v8::debug::Script> script,
243 V8InspectorClient* client) {
244 v8::Local<v8::String> sourceURL;
245 if (script->SourceURL().ToLocal(&sourceURL) && sourceURL->Length() > 0)
246 return toProtocolString(isolate, sourceURL);
247 v8::Local<v8::String> v8Name;
248 if (script->Name().ToLocal(&v8Name) && v8Name->Length() > 0) {
249 String16 name = toProtocolString(isolate, v8Name);
250 std::unique_ptr<StringBuffer> url =
251 client->resourceNameToUrl(toStringView(name));
252 return url ? toString16(url->string()) : name;
253 }
254 return String16();
255 }
256
script() const257 v8::Local<v8::debug::Script> script() const override {
258 return m_script.Get(m_isolate);
259 }
260
Initialize(v8::Local<v8::debug::Script> script)261 void Initialize(v8::Local<v8::debug::Script> script) {
262 v8::Local<v8::String> tmp;
263 m_hasSourceURLComment =
264 script->SourceURL().ToLocal(&tmp) && tmp->Length() > 0;
265 if (script->SourceMappingURL().ToLocal(&tmp))
266 m_sourceMappingURL = toProtocolString(m_isolate, tmp);
267 m_startLine = script->LineOffset();
268 m_startColumn = script->ColumnOffset();
269 std::vector<int> lineEnds = script->LineEnds();
270 if (lineEnds.size()) {
271 int source_length = lineEnds[lineEnds.size() - 1];
272 m_endLine = static_cast<int>(lineEnds.size()) + m_startLine - 1;
273 if (lineEnds.size() > 1) {
274 m_endColumn = source_length - lineEnds[lineEnds.size() - 2] - 1;
275 } else {
276 m_endColumn = source_length + m_startColumn;
277 }
278 } else {
279 m_endLine = m_startLine;
280 m_endColumn = m_startColumn;
281 }
282
283 USE(script->ContextId().To(&m_executionContextId));
284 if (script->IsWasm()) {
285 m_language = V8DebuggerScript::Language::WebAssembly;
286 } else {
287 m_language = V8DebuggerScript::Language::JavaScript;
288 }
289
290 m_isModule = script->IsModule();
291
292 m_script.Reset(m_isolate, script);
293 m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
294 }
295
MakeWeak()296 void MakeWeak() override {
297 m_script.SetWeak(
298 this,
299 [](const v8::WeakCallbackInfo<ActualScript>& data) {
300 data.GetParameter()->WeakCallback();
301 },
302 v8::WeakCallbackType::kFinalizer);
303 }
304
WeakCallback()305 void WeakCallback() {
306 m_script.ClearWeak();
307 m_agent->ScriptCollected(this);
308 }
309
310 V8DebuggerAgentImpl* m_agent;
311 String16 m_sourceMappingURL;
312 Language m_language;
313 bool m_isLiveEdit = false;
314 bool m_isModule = false;
315 mutable String16 m_hash;
316 int m_startLine = 0;
317 int m_startColumn = 0;
318 int m_endLine = 0;
319 int m_endColumn = 0;
320 v8::Global<v8::debug::Script> m_script;
321 };
322
323 } // namespace
324
Create(v8::Isolate * isolate,v8::Local<v8::debug::Script> scriptObj,bool isLiveEdit,V8DebuggerAgentImpl * agent,V8InspectorClient * client)325 std::unique_ptr<V8DebuggerScript> V8DebuggerScript::Create(
326 v8::Isolate* isolate, v8::Local<v8::debug::Script> scriptObj,
327 bool isLiveEdit, V8DebuggerAgentImpl* agent, V8InspectorClient* client) {
328 return std::make_unique<ActualScript>(isolate, scriptObj, isLiveEdit, agent,
329 client);
330 }
331
V8DebuggerScript(v8::Isolate * isolate,String16 id,String16 url)332 V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id,
333 String16 url)
334 : m_id(std::move(id)), m_url(std::move(url)), m_isolate(isolate) {}
335
336 V8DebuggerScript::~V8DebuggerScript() = default;
337
setSourceURL(const String16 & sourceURL)338 void V8DebuggerScript::setSourceURL(const String16& sourceURL) {
339 if (sourceURL.length() > 0) {
340 m_hasSourceURLComment = true;
341 m_url = sourceURL;
342 }
343 }
344
setBreakpoint(const String16 & condition,v8::debug::Location * loc,int * id) const345 bool V8DebuggerScript::setBreakpoint(const String16& condition,
346 v8::debug::Location* loc, int* id) const {
347 v8::HandleScope scope(m_isolate);
348 return script()->SetBreakpoint(toV8String(m_isolate, condition), loc, id);
349 }
350
removeWasmBreakpoint(int id)351 void V8DebuggerScript::removeWasmBreakpoint(int id) {
352 v8::HandleScope scope(m_isolate);
353 script()->RemoveWasmBreakpoint(id);
354 }
355
356 } // namespace v8_inspector
357