diff --git a/src/llscan.cc b/src/llscan.cc index 193664af..f1241ce7 100644 --- a/src/llscan.cc +++ b/src/llscan.cc @@ -612,6 +612,10 @@ void FindReferencesCmd::PrintReferences(SBCommandReturnObject& result, // "\n", type, addr); } } + + // Print references found directly inside Context objects + Error err; + scanner->PrintContextRefs(result, err); } @@ -668,6 +672,39 @@ char** FindReferencesCmd::ParseScanOptions(char** cmd, ScanType* type) { return &cmd[optind - 1]; } +// Walk all contexts previously stored and print search_value_ +// reference if it exists. Not all values are associated with +// a context object. It seems that Function-Local variables are +// stored in the stack, and when some nested closure references +// it is allocated in a Context object. +void FindReferencesCmd::ReferenceScanner::PrintContextRefs( + SBCommandReturnObject& result, Error& err) { + ContextVector* contexts = llscan_->GetContexts(); + v8::LLV8* v8 = llscan_->v8(); + + for (auto ctx : *contexts) { + Error err; + v8::HeapObject context_obj(v8, ctx); + v8::Context c(context_obj); + + v8::Context::Locals locals(&c, err); + if (err.Fail()) return; + + for (v8::Context::Locals::Iterator it = locals.begin(); it != locals.end(); + it++) { + if ((*it).raw() == search_value_.raw()) { + v8::String _name = it.LocalName(err); + if (err.Fail()) return; + + std::string name = _name.ToString(err); + if (err.Fail()) return; + + result.Printf("0x%" PRIx64 ": Context.%s=0x%" PRIx64 "\n", c.raw(), + name.c_str(), search_value_.raw()); + } + } + } +} void FindReferencesCmd::ReferenceScanner::PrintRefs( SBCommandReturnObject& result, v8::JSObject& js_obj, Error& err) { @@ -1193,6 +1230,20 @@ uint64_t FindJSObjectsVisitor::Visit(uint64_t location, uint64_t word) { v8::HeapObject heap_object(v8_value); if (!heap_object.Check()) return address_byte_size_; + bool is_context = v8::Context::IsContext(llscan_->v8(), heap_object, err); + if (err.Fail()) { + return address_byte_size_; + } + + if (is_context) { + ContextVector* contexts; + contexts = llscan_->GetContexts(); + + if (std::find(contexts->begin(), contexts->end(), word) == contexts->end()) + contexts->push_back(word); + return address_byte_size_; + } + v8::HeapObject map_object = heap_object.GetMap(err); if (err.Fail() || !map_object.Check()) return address_byte_size_; diff --git a/src/llscan.h b/src/llscan.h index 9b2ff069..419fbd37 100644 --- a/src/llscan.h +++ b/src/llscan.h @@ -13,6 +13,7 @@ namespace llnode { class LLScan; typedef std::vector ReferencesVector; +typedef std::vector ContextVector; typedef std::map ReferencesByValueMap; typedef std::map ReferencesByPropertyMap; @@ -89,6 +90,9 @@ class FindReferencesCmd : public CommandBase { virtual void PrintRefs(lldb::SBCommandReturnObject& result, v8::String& str, Error& err) {} + virtual void PrintContextRefs(lldb::SBCommandReturnObject& result, + Error& err) {} + static const char* const property_reference_template; static const char* const array_reference_template; }; @@ -115,6 +119,9 @@ class FindReferencesCmd : public CommandBase { void PrintRefs(lldb::SBCommandReturnObject& result, v8::String& str, Error& err) override; + void PrintContextRefs(lldb::SBCommandReturnObject& result, + Error& err) override; + private: LLScan* llscan_; v8::Value search_value_; @@ -315,6 +322,7 @@ class LLScan { return references_by_value_[address]; }; + // References By Property inline bool AreReferencesByPropertyLoaded() { return references_by_property_.size() > 0; }; @@ -325,6 +333,7 @@ class LLScan { return references_by_property_[property]; }; + // References By String inline bool AreReferencesByStringLoaded() { return references_by_string_.size() > 0; }; @@ -335,6 +344,10 @@ class LLScan { return references_by_string_[string_value]; }; + // Contexts + inline bool AreContextsLoaded() { return contexts_.size() > 0; }; + inline ContextVector* GetContexts() { return &contexts_; } + v8::LLV8* llv8_; private: @@ -362,6 +375,7 @@ class LLScan { ReferencesByValueMap references_by_value_; ReferencesByPropertyMap references_by_property_; ReferencesByStringMap references_by_string_; + ContextVector contexts_; }; } // namespace llnode diff --git a/src/llv8-inl.h b/src/llv8-inl.h index b4aeae69..95c5a2ee 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -167,6 +167,15 @@ inline bool Map::IsJSObjectMap(Error& err) { return InstanceType(err) >= v8()->types()->kFirstJSObjectType; } +inline bool Context::IsContext(LLV8* v8, HeapObject heap_object, Error& err) { + if (!heap_object.Check()) return false; + + int64_t type = heap_object.GetType(err); + if (err.Fail()) return false; + + return type >= v8->types()->kFirstContextType && + type <= v8->types()->kLastContextType; +} inline int64_t Map::InObjectProperties(Error& err) { if (!IsJSObjectMap(err)) { @@ -261,7 +270,6 @@ ACCESSOR(SharedFunctionInfo, scope_info, shared_info()->kScopeInfoOffset, ACCESSOR(SharedFunctionInfo, name_or_scope_info, shared_info()->kNameOrScopeInfoOffset, HeapObject) - HeapObject SharedFunctionInfo::GetScopeInfo(Error& err) { if (v8()->shared_info()->kNameOrScopeInfoOffset == -1) return scope_info(err); @@ -580,6 +588,19 @@ inline T Context::GetEmbedderData(int64_t index, Error& err) { return embedder_data.Get(index, err); } +HeapObject Context::GetScopeInfo(Error& err) { + if (v8()->context()->kScopeInfoIndex != -1) { + return FixedArray::Get(v8()->context()->kScopeInfoIndex, err); + } + JSFunction closure = Closure(err); + if (err.Fail()) return HeapObject(); + + SharedFunctionInfo info = closure.Info(err); + if (err.Fail()) return HeapObject(); + + return info.GetScopeInfo(err); +} + inline Value Context::ContextSlot(int index, Error& err) { return FixedArray::Get(v8()->context()->kMinContextSlots + index, err); } diff --git a/src/llv8.cc b/src/llv8.cc index 85ff2a3f..4973a461 100644 --- a/src/llv8.cc +++ b/src/llv8.cc @@ -876,6 +876,10 @@ std::string HeapObject::GetTypeName(Error& err) { if (type == v8()->types()->kMapType) { return "(Map)"; } + if (type >= v8()->types()->kFirstContextType && + type <= v8()->types()->kLastContextType) { + return "Context"; + } if (JSObject::IsObjectType(v8(), type)) { v8::HeapObject map_obj = GetMap(err); @@ -1058,17 +1062,43 @@ std::string FixedArray::InspectContents(int length, Error& err) { return res; } -HeapObject Context::GetScopeInfo(Error& err) { - if (v8()->context()->kScopeInfoIndex != -1) { - return FixedArray::Get(v8()->context()->kScopeInfoIndex, err); - } - JSFunction closure = Closure(err); - if (err.Fail()) return HeapObject(); +// Context locals iterator implementations +Context::Locals::Locals(Context* context, Error& err) { + context_ = context; + HeapObject scope_obj = context_->GetScopeInfo(err); + if (err.Fail()) return; + + scope_info_ = ScopeInfo(scope_obj); + Smi param_count_smi = scope_info_.ParameterCount(err); + if (err.Fail()) return; + Smi stack_count_smi = scope_info_.StackLocalCount(err); + if (err.Fail()) return; + Smi local_count_smi = scope_info_.ContextLocalCount(err); + if (err.Fail()) return; + + param_count_ = param_count_smi.GetValue(); + stack_count_ = stack_count_smi.GetValue(); + local_count_ = local_count_smi.GetValue(); +} - SharedFunctionInfo info = closure.Info(err); - if (err.Fail()) return HeapObject(); +Context::Locals::Iterator Context::Locals::begin() { return Iterator(0, this); } - return info.GetScopeInfo(err); +Context::Locals::Iterator Context::Locals::end() { + return Iterator(local_count_, this); +} + +const Context::Locals::Iterator Context::Locals::Iterator::operator++(int) { + current_++; + return Iterator(current_, this->outer_); +} + +bool Context::Locals::Iterator::operator!=(Context::Locals::Iterator that) { + return current_ != that.current_ || outer_->context_ != that.outer_->context_; +} + +v8::Value Context::Locals::Iterator::operator*() { + Error err; + return outer_->context_->ContextSlot(current_, err); } std::string Context::Inspect(InspectOptions* options, Error& err) { @@ -1093,13 +1123,6 @@ std::string Context::Inspect(InspectOptions* options, Error& err) { ScopeInfo scope(scope_obj); - Smi param_count_smi = scope.ParameterCount(err); - if (err.Fail()) return std::string(); - Smi stack_count_smi = scope.StackLocalCount(err); - if (err.Fail()) return std::string(); - Smi local_count_smi = scope.ContextLocalCount(err); - if (err.Fail()) return std::string(); - HeapObject heap_previous = HeapObject(previous); if (heap_previous.Check()) { char tmp[128]; @@ -1145,11 +1168,11 @@ std::string Context::Inspect(InspectOptions* options, Error& err) { res += ">"; } - int param_count = param_count_smi.GetValue(); - int stack_count = stack_count_smi.GetValue(); - int local_count = local_count_smi.GetValue(); - for (int i = 0; i < local_count; i++) { - String name = scope.ContextLocalName(i, param_count, stack_count, err); + Context::Locals locals(this, err); + if (err.Fail()) return std::string(); + for (v8::Context::Locals::Iterator it = locals.begin(); it != locals.end(); + it++) { + String name = it.LocalName(err); if (err.Fail()) return std::string(); if (!res.empty()) res += ",\n"; @@ -1157,7 +1180,7 @@ std::string Context::Inspect(InspectOptions* options, Error& err) { res += options->get_indent_spaces() + name.ToString(err) + "="; if (err.Fail()) return std::string(); - Value value = ContextSlot(i, err); + Value value = it.GetValue(err); if (err.Fail()) return std::string(); InspectOptions val_options; diff --git a/src/llv8.h b/src/llv8.h index 4d376a47..d2a5f142 100644 --- a/src/llv8.h +++ b/src/llv8.h @@ -384,6 +384,19 @@ class NameDictionary : public FixedArray { inline int64_t Length(Error& err); }; +class ScopeInfo : public FixedArray { + public: + V8_VALUE_DEFAULT_METHODS(ScopeInfo, FixedArray) + + inline Smi ParameterCount(Error& err); + inline Smi StackLocalCount(Error& err); + inline Smi ContextLocalCount(Error& err); + + inline String ContextLocalName(int index, int param_count, int stack_count, + Error& err); + inline HeapObject MaybeFunctionName(Error& err); +}; + class Context : public FixedArray { public: V8_VALUE_DEFAULT_METHODS(Context, FixedArray) @@ -397,23 +410,51 @@ class Context : public FixedArray { inline Value ContextSlot(int index, Error& err); std::string Inspect(InspectOptions* options, Error& err); + static inline bool IsContext(LLV8* v8, HeapObject heap_object, Error& err); + + // Iterator class to walk all local references on a context + class Locals { + public: + class Iterator { + public: + Value operator*(); + const Context::Locals::Iterator operator++(int); + bool operator!=(Context::Locals::Iterator that); + + inline Iterator(int current, Locals* outer) + : current_(current), outer_(outer){}; + + String LocalName(Error& err) { + return outer_->scope_info_.ContextLocalName( + current_, outer_->param_count_, outer_->stack_count_, err); + } + + Value GetValue(Error& err) { + return outer_->context_->ContextSlot(current_, err); + } + + private: + int current_; + Locals* outer_; + }; + + Locals(Context* context, Error& err); + + Iterator begin(); + Iterator end(); + + private: + int local_count_; + int param_count_; + int stack_count_; + Context* context_; + ScopeInfo scope_info_; + }; private: inline JSFunction Closure(Error& err); }; -class ScopeInfo : public FixedArray { - public: - V8_VALUE_DEFAULT_METHODS(ScopeInfo, FixedArray) - - inline Smi ParameterCount(Error& err); - inline Smi StackLocalCount(Error& err); - inline Smi ContextLocalCount(Error& err); - - inline String ContextLocalName(int index, int param_count, int stack_count, - Error& err); - inline HeapObject MaybeFunctionName(Error& err); -}; class Oddball : public HeapObject { public: diff --git a/test/common.js b/test/common.js index 4eb44f90..6cb7b006 100644 --- a/test/common.js +++ b/test/common.js @@ -33,7 +33,9 @@ else exports.llnodePath = path.join(exports.projectDir, pluginName); exports.saveCoreTimeout = 360 * 1000; exports.loadCoreTimeout = 60 * 1000; -exports.versionMark = /^lldb-|^lldb version/; + +let versionMark = /^lldb-|^lldb version/; +exports.versionMark = versionMark; function SessionOutput(session, stream, timeout) { EventEmitter.call(this); @@ -316,6 +318,20 @@ Session.prototype.send = function send(line, callback) { this.lldb.stdin.write(line + '\n', callback); }; +Session.prototype.hasSymbol = function hasSymbol(symbol, callback) { + this.send('target modules dump symtab'); + this.send('version'); + + let pattern = new RegExp(symbol); + this.linesUntil(versionMark, (err, lines) => { + if(pattern.test(lines.join('\n'))) { + callback(err, true); + } else { + return callback(err, false); + } + }); +} + exports.generateRanges = function generateRanges(core, dest, cb) { let script; if (process.platform === 'darwin') diff --git a/test/plugin/scan-test.js b/test/plugin/scan-test.js index 08cbef20..5ee010c7 100644 --- a/test/plugin/scan-test.js +++ b/test/plugin/scan-test.js @@ -83,7 +83,13 @@ function test(executable, core, t) { t.ok(/Object\.holder/.test(lines.join('\n')), 'Should find reference #2'); t.ok(/\(Array\)\[1\]/.test(lines.join('\n')), 'Should find reference #3'); - sess.quit(); - t.end(); + // Test if LastContextType constat exists + sess.hasSymbol('v8dbg_LastContextType', (err, hasSymbol) => { + t.error(err) + if(hasSymbol) + t.ok(/Context\.scopedAPI/.test(lines.join('\n')), 'Should find reference #4'); + sess.quit(); + t.end(); + }); }); }