Home | History | Annotate | Download | only in inspector
      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