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/wasm-translation.h" 10 11 namespace v8_inspector { 12 13 namespace { 14 15 const char hexDigits[17] = "0123456789ABCDEF"; 16 17 void appendUnsignedAsHex(uint64_t number, String16Builder* destination) { 18 for (size_t i = 0; i < 8; ++i) { 19 UChar c = hexDigits[number & 0xF]; 20 destination->append(c); 21 number >>= 4; 22 } 23 } 24 25 // Hash algorithm for substrings is described in "ber die Komplexitt der 26 // Multiplikation in 27 // eingeschrnkten Branchingprogrammmodellen" by Woelfe. 28 // http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000 29 String16 calculateHash(const String16& str) { 30 static uint64_t prime[] = {0x3FB75161, 0xAB1F4E4F, 0x82675BC5, 0xCD924D35, 31 0x81ABE279}; 32 static uint64_t random[] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 33 0xC3D2E1F0}; 34 static uint32_t randomOdd[] = {0xB4663807, 0xCC322BF5, 0xD4F91BBD, 0xA7BEA11D, 35 0x8F462907}; 36 37 uint64_t hashes[] = {0, 0, 0, 0, 0}; 38 uint64_t zi[] = {1, 1, 1, 1, 1}; 39 40 const size_t hashesSize = arraysize(hashes); 41 42 size_t current = 0; 43 const uint32_t* data = nullptr; 44 size_t sizeInBytes = sizeof(UChar) * str.length(); 45 data = reinterpret_cast<const uint32_t*>(str.characters16()); 46 for (size_t i = 0; i < sizeInBytes / 4; i += 4) { 47 uint32_t v = data[i]; 48 uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF; 49 hashes[current] = (hashes[current] + zi[current] * xi) % prime[current]; 50 zi[current] = (zi[current] * random[current]) % prime[current]; 51 current = current == hashesSize - 1 ? 0 : current + 1; 52 } 53 if (sizeInBytes % 4) { 54 uint32_t v = 0; 55 for (size_t i = sizeInBytes - sizeInBytes % 4; i < sizeInBytes; ++i) { 56 v <<= 8; 57 v |= reinterpret_cast<const uint8_t*>(data)[i]; 58 } 59 uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF; 60 hashes[current] = (hashes[current] + zi[current] * xi) % prime[current]; 61 zi[current] = (zi[current] * random[current]) % prime[current]; 62 current = current == hashesSize - 1 ? 0 : current + 1; 63 } 64 65 for (size_t i = 0; i < hashesSize; ++i) 66 hashes[i] = (hashes[i] + zi[i] * (prime[i] - 1)) % prime[i]; 67 68 String16Builder hash; 69 for (size_t i = 0; i < hashesSize; ++i) appendUnsignedAsHex(hashes[i], &hash); 70 return hash.toString(); 71 } 72 73 void TranslateProtocolLocationToV8Location(WasmTranslation* wasmTranslation, 74 v8::debug::Location* loc, 75 const String16& scriptId, 76 const String16& expectedV8ScriptId) { 77 if (loc->IsEmpty()) return; 78 int lineNumber = loc->GetLineNumber(); 79 int columnNumber = loc->GetColumnNumber(); 80 String16 translatedScriptId = scriptId; 81 wasmTranslation->TranslateProtocolLocationToWasmScriptLocation( 82 &translatedScriptId, &lineNumber, &columnNumber); 83 DCHECK_EQ(expectedV8ScriptId.utf8(), translatedScriptId.utf8()); 84 *loc = v8::debug::Location(lineNumber, columnNumber); 85 } 86 87 void TranslateV8LocationToProtocolLocation( 88 WasmTranslation* wasmTranslation, v8::debug::Location* loc, 89 const String16& scriptId, const String16& expectedProtocolScriptId) { 90 int lineNumber = loc->GetLineNumber(); 91 int columnNumber = loc->GetColumnNumber(); 92 String16 translatedScriptId = scriptId; 93 wasmTranslation->TranslateWasmScriptLocationToProtocolLocation( 94 &translatedScriptId, &lineNumber, &columnNumber); 95 DCHECK_EQ(expectedProtocolScriptId.utf8(), translatedScriptId.utf8()); 96 *loc = v8::debug::Location(lineNumber, columnNumber); 97 } 98 99 class ActualScript : public V8DebuggerScript { 100 friend class V8DebuggerScript; 101 102 public: 103 ActualScript(v8::Isolate* isolate, v8::Local<v8::debug::Script> script, 104 bool isLiveEdit) 105 : V8DebuggerScript(isolate, String16::fromInteger(script->Id()), 106 GetNameOrSourceUrl(script)), 107 m_isLiveEdit(isLiveEdit) { 108 v8::Local<v8::String> tmp; 109 if (script->SourceURL().ToLocal(&tmp)) m_sourceURL = toProtocolString(tmp); 110 if (script->SourceMappingURL().ToLocal(&tmp)) 111 m_sourceMappingURL = toProtocolString(tmp); 112 m_startLine = script->LineOffset(); 113 m_startColumn = script->ColumnOffset(); 114 std::vector<int> lineEnds = script->LineEnds(); 115 CHECK(lineEnds.size()); 116 int source_length = lineEnds[lineEnds.size() - 1]; 117 if (lineEnds.size()) { 118 m_endLine = static_cast<int>(lineEnds.size()) + m_startLine - 1; 119 if (lineEnds.size() > 1) { 120 m_endColumn = source_length - lineEnds[lineEnds.size() - 2] - 1; 121 } else { 122 m_endColumn = source_length + m_startColumn; 123 } 124 } else { 125 m_endLine = m_startLine; 126 m_endColumn = m_startColumn; 127 } 128 129 v8::Local<v8::Value> contextData; 130 if (script->ContextData().ToLocal(&contextData) && contextData->IsInt32()) { 131 m_executionContextId = 132 static_cast<int>(contextData.As<v8::Int32>()->Value()); 133 } 134 135 if (script->Source().ToLocal(&tmp)) { 136 m_sourceObj.Reset(m_isolate, tmp); 137 String16 source = toProtocolString(tmp); 138 // V8 will not count last line if script source ends with \n. 139 if (source.length() > 1 && source[source.length() - 1] == '\n') { 140 m_endLine++; 141 m_endColumn = 0; 142 } 143 } 144 145 m_isModule = script->IsModule(); 146 147 m_script.Reset(m_isolate, script); 148 } 149 150 bool isLiveEdit() const override { return m_isLiveEdit; } 151 bool isModule() const override { return m_isModule; } 152 153 const String16& sourceMappingURL() const override { 154 return m_sourceMappingURL; 155 } 156 157 String16 source(v8::Isolate* isolate) const override { 158 if (!m_sourceObj.IsEmpty()) 159 return toProtocolString(m_sourceObj.Get(isolate)); 160 return V8DebuggerScript::source(isolate); 161 } 162 163 void setSourceMappingURL(const String16& sourceMappingURL) override { 164 m_sourceMappingURL = sourceMappingURL; 165 } 166 167 void setSource(v8::Local<v8::String> source) override { 168 m_source = String16(); 169 m_sourceObj.Reset(m_isolate, source); 170 m_hash = String16(); 171 } 172 173 bool getPossibleBreakpoints( 174 const v8::debug::Location& start, const v8::debug::Location& end, 175 std::vector<v8::debug::Location>* locations) override { 176 v8::HandleScope scope(m_isolate); 177 v8::Local<v8::debug::Script> script = m_script.Get(m_isolate); 178 return script->GetPossibleBreakpoints(start, end, locations); 179 } 180 181 void resetBlackboxedStateCache() override { 182 v8::HandleScope scope(m_isolate); 183 v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate)); 184 } 185 186 private: 187 String16 GetNameOrSourceUrl(v8::Local<v8::debug::Script> script) { 188 v8::Local<v8::String> name; 189 if (script->Name().ToLocal(&name) || script->SourceURL().ToLocal(&name)) 190 return toProtocolString(name); 191 return String16(); 192 } 193 194 String16 m_sourceMappingURL; 195 v8::Global<v8::String> m_sourceObj; 196 bool m_isLiveEdit = false; 197 bool m_isModule = false; 198 v8::Global<v8::debug::Script> m_script; 199 }; 200 201 class WasmVirtualScript : public V8DebuggerScript { 202 friend class V8DebuggerScript; 203 204 public: 205 WasmVirtualScript(v8::Isolate* isolate, WasmTranslation* wasmTranslation, 206 v8::Local<v8::debug::WasmScript> script, String16 id, 207 String16 url, String16 source) 208 : V8DebuggerScript(isolate, std::move(id), std::move(url)), 209 m_script(isolate, script), 210 m_wasmTranslation(wasmTranslation) { 211 int num_lines = 0; 212 int last_newline = -1; 213 size_t next_newline = source.find('\n', last_newline + 1); 214 while (next_newline != String16::kNotFound) { 215 last_newline = static_cast<int>(next_newline); 216 next_newline = source.find('\n', last_newline + 1); 217 ++num_lines; 218 } 219 m_endLine = num_lines; 220 m_endColumn = static_cast<int>(source.length()) - last_newline - 1; 221 m_source = std::move(source); 222 } 223 224 const String16& sourceMappingURL() const override { return emptyString(); } 225 bool isLiveEdit() const override { return false; } 226 bool isModule() const override { return false; } 227 void setSourceMappingURL(const String16&) override {} 228 229 bool getPossibleBreakpoints( 230 const v8::debug::Location& start, const v8::debug::Location& end, 231 std::vector<v8::debug::Location>* locations) override { 232 v8::HandleScope scope(m_isolate); 233 v8::Local<v8::debug::Script> script = m_script.Get(m_isolate); 234 String16 v8ScriptId = String16::fromInteger(script->Id()); 235 236 v8::debug::Location translatedStart = start; 237 TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedStart, 238 scriptId(), v8ScriptId); 239 240 v8::debug::Location translatedEnd = end; 241 if (translatedEnd.IsEmpty()) { 242 // Stop before the start of the next function. 243 translatedEnd = 244 v8::debug::Location(translatedStart.GetLineNumber() + 1, 0); 245 } else { 246 TranslateProtocolLocationToV8Location(m_wasmTranslation, &translatedEnd, 247 scriptId(), v8ScriptId); 248 } 249 250 bool success = script->GetPossibleBreakpoints(translatedStart, 251 translatedEnd, locations); 252 for (v8::debug::Location& loc : *locations) { 253 TranslateV8LocationToProtocolLocation(m_wasmTranslation, &loc, v8ScriptId, 254 scriptId()); 255 } 256 return success; 257 } 258 259 void resetBlackboxedStateCache() override {} 260 261 private: 262 static const String16& emptyString() { 263 static const String16 singleEmptyString; 264 return singleEmptyString; 265 } 266 267 v8::Global<v8::debug::WasmScript> m_script; 268 WasmTranslation* m_wasmTranslation; 269 }; 270 271 } // namespace 272 273 std::unique_ptr<V8DebuggerScript> V8DebuggerScript::Create( 274 v8::Isolate* isolate, v8::Local<v8::debug::Script> scriptObj, 275 bool isLiveEdit) { 276 return std::unique_ptr<ActualScript>( 277 new ActualScript(isolate, scriptObj, isLiveEdit)); 278 } 279 280 std::unique_ptr<V8DebuggerScript> V8DebuggerScript::CreateWasm( 281 v8::Isolate* isolate, WasmTranslation* wasmTranslation, 282 v8::Local<v8::debug::WasmScript> underlyingScript, String16 id, 283 String16 url, String16 source) { 284 return std::unique_ptr<WasmVirtualScript>( 285 new WasmVirtualScript(isolate, wasmTranslation, underlyingScript, 286 std::move(id), std::move(url), std::move(source))); 287 } 288 289 V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, String16 id, 290 String16 url) 291 : m_id(std::move(id)), m_url(std::move(url)), m_isolate(isolate) {} 292 293 V8DebuggerScript::~V8DebuggerScript() {} 294 295 const String16& V8DebuggerScript::sourceURL() const { 296 return m_sourceURL.isEmpty() ? m_url : m_sourceURL; 297 } 298 299 const String16& V8DebuggerScript::hash(v8::Isolate* isolate) const { 300 if (m_hash.isEmpty()) m_hash = calculateHash(source(isolate)); 301 DCHECK(!m_hash.isEmpty()); 302 return m_hash; 303 } 304 305 void V8DebuggerScript::setSourceURL(const String16& sourceURL) { 306 m_sourceURL = sourceURL; 307 } 308 309 } // namespace v8_inspector 310