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