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 14namespace v8_inspector { 15 16namespace { 17 18const 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 24String16 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 89class ActualScript : public V8DebuggerScript { 90 friend class V8DebuggerScript; 91 92 public: 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 104 bool isLiveEdit() const override { return m_isLiveEdit; } 105 bool isModule() const override { return m_isModule; } 106 107 String16 source(size_t pos, size_t len) const override { 108 v8::HandleScope scope(m_isolate); 109 v8::Local<v8::String> v8Source; 110 if (!m_scriptSource.Get(m_isolate)->JavaScriptCode().ToLocal(&v8Source)) { 111 return String16(); 112 } 113 if (pos >= static_cast<size_t>(v8Source->Length())) return String16(); 114 size_t substringLength = 115 std::min(len, static_cast<size_t>(v8Source->Length()) - pos); 116 std::unique_ptr<UChar[]> buffer(new UChar[substringLength]); 117 v8Source->Write(m_isolate, reinterpret_cast<uint16_t*>(buffer.get()), 118 static_cast<int>(pos), static_cast<int>(substringLength)); 119 return String16(buffer.get(), substringLength); 120 } 121 Language getLanguage() const override { return m_language; } 122 123#if V8_ENABLE_WEBASSEMBLY 124 v8::Maybe<v8::MemorySpan<const uint8_t>> wasmBytecode() const override { 125 v8::HandleScope scope(m_isolate); 126 v8::MemorySpan<const uint8_t> bytecode; 127 if (m_scriptSource.Get(m_isolate)->WasmBytecode().To(&bytecode)) { 128 return v8::Just(bytecode); 129 } 130 return v8::Nothing<v8::MemorySpan<const uint8_t>>(); 131 } 132 133 v8::Maybe<v8::debug::WasmScript::DebugSymbolsType> getDebugSymbolsType() 134 const override { 135 auto script = this->script(); 136 if (!script->IsWasm()) 137 return v8::Nothing<v8::debug::WasmScript::DebugSymbolsType>(); 138 return v8::Just(v8::debug::WasmScript::Cast(*script)->GetDebugSymbolType()); 139 } 140 141 v8::Maybe<String16> getExternalDebugSymbolsURL() const override { 142 auto script = this->script(); 143 if (!script->IsWasm()) return v8::Nothing<String16>(); 144 v8::MemorySpan<const char> external_url = 145 v8::debug::WasmScript::Cast(*script)->ExternalSymbolsURL(); 146 if (external_url.size() == 0) return v8::Nothing<String16>(); 147 return v8::Just(String16(external_url.data(), external_url.size())); 148 } 149#endif // V8_ENABLE_WEBASSEMBLY 150 151 int startLine() const override { return m_startLine; } 152 int startColumn() const override { return m_startColumn; } 153 int endLine() const override { return m_endLine; } 154 int endColumn() const override { return m_endColumn; } 155 int codeOffset() const override { 156#if V8_ENABLE_WEBASSEMBLY 157 if (script()->IsWasm()) { 158 return v8::debug::WasmScript::Cast(*script())->CodeOffset(); 159 } 160#endif // V8_ENABLE_WEBASSEMBLY 161 return 0; 162 } 163 int length() const override { 164 return static_cast<int>(m_scriptSource.Get(m_isolate)->Length()); 165 } 166 167 const String16& sourceMappingURL() const override { 168 return m_sourceMappingURL; 169 } 170 171 void setSourceMappingURL(const String16& sourceMappingURL) override { 172 m_sourceMappingURL = sourceMappingURL; 173 } 174 175 void setSource(const String16& newSource, bool preview, 176 v8::debug::LiveEditResult* result) override { 177 v8::EscapableHandleScope scope(m_isolate); 178 v8::Local<v8::String> v8Source = toV8String(m_isolate, newSource); 179 if (!m_script.Get(m_isolate)->SetScriptSource(v8Source, preview, result)) { 180 result->message = scope.Escape(result->message); 181 return; 182 } 183 // NOP if preview or unchanged source (diffs.empty() in PatchScript) 184 if (preview || result->script.IsEmpty()) return; 185 186 m_hash = String16(); 187 Initialize(scope.Escape(result->script)); 188 } 189 190 bool getPossibleBreakpoints( 191 const v8::debug::Location& start, const v8::debug::Location& end, 192 bool restrictToFunction, 193 std::vector<v8::debug::BreakLocation>* locations) override { 194 v8::HandleScope scope(m_isolate); 195 v8::Local<v8::debug::Script> script = m_script.Get(m_isolate); 196 std::vector<v8::debug::BreakLocation> allLocations; 197 if (!script->GetPossibleBreakpoints(start, end, restrictToFunction, 198 &allLocations)) { 199 return false; 200 } 201 if (!allLocations.size()) return true; 202 v8::debug::BreakLocation current = allLocations[0]; 203 for (size_t i = 1; i < allLocations.size(); ++i) { 204 if (allLocations[i].GetLineNumber() == current.GetLineNumber() && 205 allLocations[i].GetColumnNumber() == current.GetColumnNumber()) { 206 if (allLocations[i].type() != v8::debug::kCommonBreakLocation) { 207 DCHECK(allLocations[i].type() == v8::debug::kCallBreakLocation || 208 allLocations[i].type() == v8::debug::kReturnBreakLocation); 209 // debugger can returns more then one break location at the same 210 // source location, e.g. foo() - in this case there are two break 211 // locations before foo: for statement and for function call, we can 212 // merge them for inspector and report only one with call type. 213 current = allLocations[i]; 214 } 215 } else { 216 // we assume that returned break locations are sorted. 217 DCHECK( 218 allLocations[i].GetLineNumber() > current.GetLineNumber() || 219 (allLocations[i].GetColumnNumber() >= current.GetColumnNumber() && 220 allLocations[i].GetLineNumber() == current.GetLineNumber())); 221 locations->push_back(current); 222 current = allLocations[i]; 223 } 224 } 225 locations->push_back(current); 226 return true; 227 } 228 229 void resetBlackboxedStateCache() override { 230 v8::HandleScope scope(m_isolate); 231 v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate)); 232 } 233 234 int offset(int lineNumber, int columnNumber) const override { 235 v8::HandleScope scope(m_isolate); 236 return m_script.Get(m_isolate)->GetSourceOffset( 237 v8::debug::Location(lineNumber, columnNumber)); 238 } 239 240 v8::debug::Location location(int offset) const override { 241 v8::HandleScope scope(m_isolate); 242 return m_script.Get(m_isolate)->GetSourceLocation(offset); 243 } 244 245 bool setBreakpoint(const String16& condition, v8::debug::Location* location, 246 int* id) const override { 247 v8::HandleScope scope(m_isolate); 248 return script()->SetBreakpoint(toV8String(m_isolate, condition), location, 249 id); 250 } 251 252 bool setInstrumentationBreakpoint(int* id) const override { 253 v8::HandleScope scope(m_isolate); 254 return script()->SetInstrumentationBreakpoint(id); 255 } 256 257 const String16& hash() const override { 258 if (!m_hash.isEmpty()) return m_hash; 259 v8::HandleScope scope(m_isolate); 260 v8::Local<v8::String> v8Source; 261 if (!m_scriptSource.Get(m_isolate)->JavaScriptCode().ToLocal(&v8Source)) { 262 v8Source = v8::String::Empty(m_isolate); 263 } 264 m_hash = calculateHash(m_isolate, v8Source); 265 DCHECK(!m_hash.isEmpty()); 266 return m_hash; 267 } 268 269 private: 270 static String16 GetScriptURL(v8::Isolate* isolate, 271 v8::Local<v8::debug::Script> script, 272 V8InspectorClient* client) { 273 v8::Local<v8::String> sourceURL; 274 if (script->SourceURL().ToLocal(&sourceURL) && sourceURL->Length() > 0) 275 return toProtocolString(isolate, sourceURL); 276 return GetScriptName(isolate, script, client); 277 } 278 279 static String16 GetScriptName(v8::Isolate* isolate, 280 v8::Local<v8::debug::Script> script, 281 V8InspectorClient* client) { 282 v8::Local<v8::String> v8Name; 283 if (script->Name().ToLocal(&v8Name) && v8Name->Length() > 0) { 284 String16 name = toProtocolString(isolate, v8Name); 285 std::unique_ptr<StringBuffer> url = 286 client->resourceNameToUrl(toStringView(name)); 287 return url ? toString16(url->string()) : name; 288 } 289 return String16(); 290 } 291 292 v8::Local<v8::debug::Script> script() const override { 293 return m_script.Get(m_isolate); 294 } 295 296 void Initialize(v8::Local<v8::debug::Script> script) { 297 v8::Local<v8::String> tmp; 298 m_hasSourceURLComment = 299 script->SourceURL().ToLocal(&tmp) && tmp->Length() > 0; 300 if (script->SourceMappingURL().ToLocal(&tmp)) 301 m_sourceMappingURL = toProtocolString(m_isolate, tmp); 302 m_startLine = script->StartLine(); 303 m_startColumn = script->StartColumn(); 304 m_endLine = script->EndLine(); 305 m_endColumn = script->EndColumn(); 306 307 USE(script->ContextId().To(&m_executionContextId)); 308 m_language = V8DebuggerScript::Language::JavaScript; 309#if V8_ENABLE_WEBASSEMBLY 310 if (script->IsWasm()) { 311 m_language = V8DebuggerScript::Language::WebAssembly; 312 } 313#endif // V8_ENABLE_WEBASSEMBLY 314 315 m_isModule = script->IsModule(); 316 317 m_script.Reset(m_isolate, script); 318 m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel); 319 m_scriptSource.Reset(m_isolate, script->Source()); 320 m_scriptSource.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel); 321 } 322 323 void MakeWeak() override { 324 m_script.SetWeak( 325 this, 326 [](const v8::WeakCallbackInfo<ActualScript>& data) { 327 data.GetParameter()->WeakCallback(); 328 }, 329 v8::WeakCallbackType::kParameter); 330 } 331 332 void WeakCallback() { 333 m_script.Reset(); 334 m_agent->ScriptCollected(this); 335 } 336 337 V8DebuggerAgentImpl* m_agent; 338 String16 m_sourceMappingURL; 339 Language m_language; 340 bool m_isLiveEdit = false; 341 bool m_isModule = false; 342 mutable String16 m_hash; 343 int m_startLine = 0; 344 int m_startColumn = 0; 345 int m_endLine = 0; 346 int m_endColumn = 0; 347 v8::Global<v8::debug::Script> m_script; 348 v8::Global<v8::debug::ScriptSource> m_scriptSource; 349}; 350 351} // namespace 352 353std::unique_ptr<V8DebuggerScript> V8DebuggerScript::Create( 354 v8::Isolate* isolate, v8::Local<v8::debug::Script> scriptObj, 355 bool isLiveEdit, V8DebuggerAgentImpl* agent, V8InspectorClient* client) { 356 return std::make_unique<ActualScript>(isolate, scriptObj, isLiveEdit, agent, 357 client); 358} 359 360V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id, 361 String16 url, String16 embedderName) 362 : m_id(std::move(id)), 363 m_url(std::move(url)), 364 m_isolate(isolate), 365 m_embedderName(embedderName) {} 366 367V8DebuggerScript::~V8DebuggerScript() = default; 368 369void V8DebuggerScript::setSourceURL(const String16& sourceURL) { 370 if (sourceURL.length() > 0) { 371 m_hasSourceURLComment = true; 372 m_url = sourceURL; 373 } 374} 375 376#if V8_ENABLE_WEBASSEMBLY 377void V8DebuggerScript::removeWasmBreakpoint(int id) { 378 v8::HandleScope scope(m_isolate); 379 script()->RemoveWasmBreakpoint(id); 380} 381#endif // V8_ENABLE_WEBASSEMBLY 382 383} // namespace v8_inspector 384