From b438f27a6dbc68239b3c5f491d0f9491b5cb34a0 Mon Sep 17 00:00:00 2001 From: Daniel Beckert Date: Wed, 17 Oct 2018 16:38:32 -0300 Subject: [PATCH] src: add recursive option to `findrefs` command Introduce a new command flag to `findrefs`, command --recursive allow user to transverse the whole references tree for the given object. Refs: https://github.com/nodejs/llnode/issues/115 --- src/llnode.cc | 26 +++++ src/llnode.h | 12 ++ src/llscan.cc | 179 +++++++++++++++++++++++------- src/llscan.h | 60 +++++++--- src/settings.cc | 7 ++ src/settings.h | 4 + test/fixtures/inspect-scenario.js | 6 + test/plugin/scan-test.js | 24 ++++ 8 files changed, 261 insertions(+), 57 deletions(-) diff --git a/src/llnode.cc b/src/llnode.cc index a20986c7..ea5a1c9d 100644 --- a/src/llnode.cc +++ b/src/llnode.cc @@ -136,6 +136,29 @@ bool SetPropertyColorCmd::DoExecute(SBDebugger d, char** cmd, return false; } +bool SetTreePaddingCmd::DoExecute(SBDebugger d, char** cmd, + SBCommandReturnObject& result) { + + if (cmd == nullptr || *cmd == nullptr) { + result.SetError("USAGE: v8 settings set tree-padding [1..10]"); + return false; + } + Settings* settings = Settings::GetSettings(); + std::stringstream option(cmd[0]); + int padding; + + if (!(option >> padding)) { + result.SetError("unable to convert provided value."); + return false; + }; + + if (padding < 1) padding = 1; + if (padding > 10) padding = 10; + padding = settings->SetTreePadding(padding); + result.Printf("Tree padding set to %u\n", padding); + return true; +} + bool PrintCmd::DoExecute(SBDebugger d, char** cmd, SBCommandReturnObject& result) { @@ -462,6 +485,8 @@ bool PluginInitialize(SBDebugger d) { setPropertyCmd.AddCommand("color", new llnode::SetPropertyColorCmd(), "Set color property value"); + setPropertyCmd.AddCommand("tree-padding", new llnode::SetTreePaddingCmd(&llv8), + "Set tree-padding value"); interpreter.AddCommand("findjsobjects", new llnode::FindObjectsCmd(&llscan), "Alias for `v8 findjsobjects`"); @@ -496,6 +521,7 @@ bool PluginInitialize(SBDebugger d) { " * -n, --name name - all properties with the specified name\n" " * -s, --string string - all properties that refer to the specified " "JavaScript string value\n" + " * -r, --recursive - walk through references tree recursively\n" "\n"); v8.AddCommand("getactivehandles", diff --git a/src/llnode.h b/src/llnode.h index 40d58de2..46de30cf 100644 --- a/src/llnode.h +++ b/src/llnode.h @@ -30,6 +30,18 @@ class SetPropertyColorCmd : public CommandBase { lldb::SBCommandReturnObject& result) override; }; +class SetTreePaddingCmd: public CommandBase { + public: + SetTreePaddingCmd(v8::LLV8* llv8) : llv8_(llv8) {} + ~SetTreePaddingCmd() override {} + + bool DoExecute(lldb::SBDebugger d, char** cmd, + lldb::SBCommandReturnObject& result) override; + + private: + v8::LLV8* llv8_; +}; + class PrintCmd : public CommandBase { public: PrintCmd(v8::LLV8* llv8, bool detailed) : llv8_(llv8), detailed_(detailed) {} diff --git a/src/llscan.cc b/src/llscan.cc index a6055c3c..4304f8b9 100644 --- a/src/llscan.cc +++ b/src/llscan.cc @@ -470,7 +470,6 @@ bool NodeInfoCmd::DoExecute(SBDebugger d, char** cmd, return true; } - bool FindReferencesCmd::DoExecute(SBDebugger d, char** cmd, SBCommandReturnObject& result) { if (cmd == nullptr || *cmd == nullptr) { @@ -487,10 +486,8 @@ bool FindReferencesCmd::DoExecute(SBDebugger d, char** cmd, // Load V8 constants from postmortem data llscan_->v8()->Load(target); - // Default scan type. - ScanType type = ScanType::kFieldValue; - - char** start = ParseScanOptions(cmd, &type); + ScanOptions scan_options; + char** start = ParseScanOptions(cmd, &scan_options); if (*start == nullptr) { result.SetError("Missing search parameter"); @@ -500,8 +497,8 @@ bool FindReferencesCmd::DoExecute(SBDebugger d, char** cmd, ObjectScanner* scanner; - switch (type) { - case ScanType::kFieldValue: { + switch (scan_options.scan_type) { + case ScanOptions::ScanType::kFieldValue: { std::string full_cmd; for (; start != nullptr && *start != nullptr; start++) full_cmd += *start; @@ -526,7 +523,7 @@ bool FindReferencesCmd::DoExecute(SBDebugger d, char** cmd, scanner = new ReferenceScanner(llscan_, search_value); break; } - case ScanType::kPropertyName: { + case ScanOptions::ScanType::kPropertyName: { // Check for extra parameters or parameters that needed quoting. if (start[1] != nullptr) { result.SetError("Extra search parameter or unquoted string specified."); @@ -537,7 +534,7 @@ bool FindReferencesCmd::DoExecute(SBDebugger d, char** cmd, scanner = new PropertyScanner(llscan_, property_name); break; } - case ScanType::kStringValue: { + case ScanOptions::ScanType::kStringValue: { // Check for extra parameters or parameters that needed quoting. if (start[1] != nullptr) { result.SetError("Extra search parameter or unquoted string specified."); @@ -553,7 +550,7 @@ bool FindReferencesCmd::DoExecute(SBDebugger d, char** cmd, * - Objects that refer to a particular string literal. * (lldb) findreferences -s "Hello World!" */ - case ScanType::kBadOption: { + case ScanOptions::ScanType::kBadOption: { result.SetError("Invalid search type"); result.SetStatus(eReturnStatusFailed); return false; @@ -573,8 +570,14 @@ bool FindReferencesCmd::DoExecute(SBDebugger d, char** cmd, if (!scanner->AreReferencesLoaded()) { ScanForReferences(scanner); } + + // Store already visited references to avoid and infinite recursive loop + // when `--recursive (-r)` option is set + ReferencesVector already_visited_references; + + // Get the list of references for the given search value, property or string ReferencesVector* references = scanner->GetReferences(); - PrintReferences(result, references, scanner); + PrintReferences(result, references, scanner, &scan_options, &already_visited_references); delete scanner; @@ -588,6 +591,7 @@ void FindReferencesCmd::ScanForReferences(ObjectScanner* scanner) { TypeRecordMap mapstoinstances = llscan_->GetMapsToInstances(); for (auto const entry : mapstoinstances) { TypeRecord* typerecord = entry.second; + for (uint64_t addr : typerecord->GetInstances()) { Error err; v8::Value obj_value(llscan_->v8(), addr); @@ -624,9 +628,13 @@ void FindReferencesCmd::ScanForReferences(ObjectScanner* scanner) { void FindReferencesCmd::PrintReferences(SBCommandReturnObject& result, ReferencesVector* references, - ObjectScanner* scanner) { + ObjectScanner* scanner, + ScanOptions* options, + ReferencesVector* already_visited_references, + int level) { // Walk all the object instances and handle them according to their type. TypeRecordMap mapstoinstances = llscan_->GetMapsToInstances(); + for (uint64_t addr : *references) { Error err; v8::Value obj_value(llscan_->v8(), addr); @@ -643,11 +651,47 @@ void FindReferencesCmd::PrintReferences(SBCommandReturnObject& result, // Basically we need to access objects and arrays as both objects and // arrays. v8::JSObject js_obj(heap_object); - scanner->PrintRefs(result, js_obj, err); + scanner->PrintRefs(result, js_obj, err, level); + + if (options->recursive_scan) { + if (find(already_visited_references->begin(), already_visited_references->end(), addr) != already_visited_references->end()) { + Settings* settings = Settings::GetSettings(); + unsigned int padding = settings->GetTreePadding(); + std::stringstream seen_str; + seen_str << std::string((level) * padding, ' ') << "|\n"; + seen_str << std::string((level) * padding, ' ') << "+" << std::string(padding, '-') << rang::fg::red << " [seen above]" << rang::fg::reset << std::endl; + result.Printf(seen_str.str().c_str()); + } else { + already_visited_references->push_back(addr); + v8::Value value(llscan_->v8(), addr); + ObjectScanner* scanner_ = new ReferenceScanner(llscan_, value); + ReferencesVector* references_ = scanner_->GetReferences(); + PrintReferences(result, references_, scanner_, options, already_visited_references, level+1); + delete scanner_; + } + } } else if (type < v8->types()->kFirstNonstringType) { v8::String str(heap_object); - scanner->PrintRefs(result, str, err); + scanner->PrintRefs(result, str, err, level); + + if (options->recursive_scan) { + if (find(already_visited_references->begin(), already_visited_references->end(), addr) != already_visited_references->end()) { + Settings* settings = Settings::GetSettings(); + unsigned int padding = settings->GetTreePadding(); + std::stringstream seen_str; + seen_str << std::string((level) * padding, ' ') << "|\n"; + seen_str << std::string((level) * padding, ' ') << "+" << std::string(padding, '-') << rang::fg::red << " [seen above]" << rang::fg::reset << std::endl; + result.Printf(seen_str.str().c_str()); + } else { + already_visited_references->push_back(addr); + v8::Value value(llscan_->v8(), addr); + ObjectScanner* scanner_ = new ReferenceScanner(llscan_, value); + ReferencesVector* references_ = scanner_->GetReferences(); + PrintReferences(result, references_, scanner_, options, already_visited_references, level+1); + delete scanner_; + } + } } else if (type == v8->types()->kJSTypedArrayType) { // These should only point to off heap memory, @@ -660,14 +704,16 @@ void FindReferencesCmd::PrintReferences(SBCommandReturnObject& result, // Print references found directly inside Context objects Error err; - scanner->PrintContextRefs(result, err); + scanner->PrintContextRefs(result, err, this, options, already_visited_references, level); } -char** FindReferencesCmd::ParseScanOptions(char** cmd, ScanType* type) { +char** FindReferencesCmd::ParseScanOptions(char** cmd, ScanOptions* options) { + static struct option opts[] = {{"value", no_argument, nullptr, 'v'}, {"name", no_argument, nullptr, 'n'}, {"string", no_argument, nullptr, 's'}, + {"recursive", no_argument, nullptr, 'r'}, {nullptr, 0, nullptr, 0}}; int argc = 1; @@ -687,29 +733,32 @@ char** FindReferencesCmd::ParseScanOptions(char** cmd, ScanType* type) { optind = 0; opterr = 1; do { - int arg = getopt_long(argc, args, "vns", opts, nullptr); + int arg = getopt_long(argc, args, "vnsr", opts, nullptr); if (arg == -1) break; if (found_scan_type) { - *type = ScanType::kBadOption; + options->scan_type = ScanOptions::ScanType::kBadOption; break; } switch (arg) { + case 'r': + options->recursive_scan = true; + break; case 'v': - *type = ScanType::kFieldValue; + options->scan_type = ScanOptions::ScanType::kFieldValue; found_scan_type = true; break; case 'n': - *type = ScanType::kPropertyName; + options->scan_type = ScanOptions::ScanType::kPropertyName; found_scan_type = true; break; case 's': - *type = ScanType::kStringValue; + options->scan_type = ScanOptions::ScanType::kStringValue; found_scan_type = true; break; default: - *type = ScanType::kBadOption; + options->scan_type = ScanOptions::ScanType::kBadOption; break; } } while (true); @@ -723,7 +772,7 @@ char** FindReferencesCmd::ParseScanOptions(char** cmd, ScanType* type) { // 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) { + SBCommandReturnObject& result, Error& err, FindReferencesCmd* cli_cmd_, ScanOptions* options, ReferencesVector* already_visited_references, int level) { ContextVector* contexts = llscan_->GetContexts(); v8::LLV8* v8 = llscan_->v8(); @@ -744,15 +793,52 @@ void FindReferencesCmd::ReferenceScanner::PrintContextRefs( 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()); + + std::stringstream ss; + if (level > 0) { + Settings* settings = Settings::GetSettings(); + unsigned int padding = settings->GetTreePadding(); + ss << std::string((level-1) * padding, ' ') << "|\n"; + ss << std::string((level-1) * padding, ' ') << "+" << std::string(padding-1, '-') << "+ "; + } + ss << rang::fg::cyan << "0x%" PRIx64 << rang::fg::reset << ": " + << rang::fg::magenta << "Context" << rang::style::bold << rang::fg::yellow + << ".%s" << rang::fg::reset << rang::style::reset << "=" << rang::fg::cyan + << "0x%" PRIx64 << rang::fg::reset << "\n"; + + result.Printf(ss.str().c_str(), c.raw(), name.c_str(), search_value_.raw()); + + if (options->recursive_scan) { + if (find(already_visited_references->begin(), already_visited_references->end(), c.raw()) != already_visited_references->end()) { + Settings* settings = Settings::GetSettings(); + unsigned int padding = settings->GetTreePadding(); + std::stringstream seen_str; + seen_str << std::string((level) * padding, ' ') << "|\n"; + seen_str << std::string((level) * padding, ' ') << "+" << std::string(padding, '-') << rang::fg::red << " [seen above]" << rang::fg::reset << std::endl; + result.Printf(seen_str.str().c_str()); + } else { + already_visited_references->push_back(c.raw()); + v8::Value value(llscan_->v8(), search_value_.raw()); + ObjectScanner* scanner_ = new ReferenceScanner(llscan_, value); + ReferencesVector* references_ = scanner_->GetReferences(); + cli_cmd_->PrintReferences(result, references_, scanner_, options, already_visited_references, level+1); + delete scanner_; + } + } } } } } -std::string FindReferencesCmd::ObjectScanner::GetPropertyReferenceString() { +std::string FindReferencesCmd::ObjectScanner::GetPropertyReferenceString(int level) { std::stringstream ss; + // Pretty print guide + if (level > 0) { + Settings* settings = Settings::GetSettings(); + unsigned int padding = settings->GetTreePadding(); + ss << std::string((level-1) * padding, ' ') << "|\n"; + ss << std::string((level-1) * padding, ' ') << "+" << std::string(padding-1, '-') << "+ "; + } ss << rang::fg::cyan << "0x%" PRIx64 << rang::fg::reset << ": " << rang::fg::magenta << "%s" << rang::style::bold << rang::fg::yellow << ".%s" << rang::fg::reset << rang::style::reset << "=" << rang::fg::cyan @@ -760,8 +846,15 @@ std::string FindReferencesCmd::ObjectScanner::GetPropertyReferenceString() { return ss.str(); } -std::string FindReferencesCmd::ObjectScanner::GetArrayReferenceString() { +std::string FindReferencesCmd::ObjectScanner::GetArrayReferenceString(int level) { std::stringstream ss; + // Pretty print guide + if (level > 0) { + Settings* settings = Settings::GetSettings(); + unsigned int padding = settings->GetTreePadding(); + ss << std::string((level-1) * padding, ' ') << "|\n"; + ss << std::string((level-1) * padding, ' ') << "+" << std::string(padding, '-') << "+ "; + } ss << rang::fg::cyan << "0x%" PRIx64 << rang::fg::reset << ": " << rang::fg::magenta << "%s" << rang::style::bold << rang::fg::yellow << "[%" PRId64 "]" << rang::fg::reset << rang::style::reset << "=" @@ -771,7 +864,7 @@ std::string FindReferencesCmd::ObjectScanner::GetArrayReferenceString() { void FindReferencesCmd::ReferenceScanner::PrintRefs( - SBCommandReturnObject& result, v8::JSObject& js_obj, Error& err) { + SBCommandReturnObject& result, v8::JSObject& js_obj, Error& err, int level) { int64_t length = js_obj.GetArrayLength(err); for (int64_t i = 0; i < length; ++i) { v8::Value v = js_obj.GetArrayElement(i, err); @@ -783,9 +876,11 @@ void FindReferencesCmd::ReferenceScanner::PrintRefs( std::string type_name = js_obj.GetTypeName(err); - std::string reference_template(GetArrayReferenceString()); + std::string reference_template(GetArrayReferenceString(level)); result.Printf(reference_template.c_str(), js_obj.raw(), type_name.c_str(), i, search_value_.raw()); + + } // Walk all the properties in this object. @@ -801,7 +896,8 @@ void FindReferencesCmd::ReferenceScanner::PrintRefs( std::string key = entry.first.ToString(err); std::string type_name = js_obj.GetTypeName(err); - std::string reference_template(GetPropertyReferenceString()); + std::string reference_template(GetPropertyReferenceString(level)); + result.Printf(reference_template.c_str(), js_obj.raw(), type_name.c_str(), key.c_str(), search_value_.raw()); } @@ -810,7 +906,7 @@ void FindReferencesCmd::ReferenceScanner::PrintRefs( void FindReferencesCmd::ReferenceScanner::PrintRefs( - SBCommandReturnObject& result, v8::String& str, Error& err) { + SBCommandReturnObject& result, v8::String& str, Error& err, int level) { v8::LLV8* v8 = str.v8(); int64_t repr = str.Representation(err); @@ -823,7 +919,7 @@ void FindReferencesCmd::ReferenceScanner::PrintRefs( if (err.Success() && parent.raw() == search_value_.raw()) { std::string type_name = sliced_str.GetTypeName(err); - std::string reference_template(GetPropertyReferenceString()); + std::string reference_template(GetPropertyReferenceString(level)); result.Printf(reference_template.c_str(), str.raw(), type_name.c_str(), "", search_value_.raw()); } @@ -834,7 +930,7 @@ void FindReferencesCmd::ReferenceScanner::PrintRefs( if (err.Success() && first.raw() == search_value_.raw()) { std::string type_name = cons_str.GetTypeName(err); - std::string reference_template(GetPropertyReferenceString()); + std::string reference_template(GetPropertyReferenceString(level)); result.Printf(reference_template.c_str(), str.raw(), type_name.c_str(), "", search_value_.raw()); } @@ -843,7 +939,7 @@ void FindReferencesCmd::ReferenceScanner::PrintRefs( if (err.Success() && second.raw() == search_value_.raw()) { std::string type_name = cons_str.GetTypeName(err); - std::string reference_template(GetPropertyReferenceString()); + std::string reference_template(GetPropertyReferenceString(level)); result.Printf(reference_template.c_str(), str.raw(), type_name.c_str(), "", search_value_.raw()); } @@ -853,7 +949,7 @@ void FindReferencesCmd::ReferenceScanner::PrintRefs( if (err.Success() && actual.raw() == search_value_.raw()) { std::string type_name = thin_str.GetTypeName(err); - std::string reference_template(GetPropertyReferenceString()); + std::string reference_template(GetPropertyReferenceString(level)); result.Printf(reference_template.c_str(), str.raw(), type_name.c_str(), "", search_value_.raw()); } @@ -958,7 +1054,7 @@ ReferencesVector* FindReferencesCmd::ReferenceScanner::GetReferences() { void FindReferencesCmd::PropertyScanner::PrintRefs( - SBCommandReturnObject& result, v8::JSObject& js_obj, Error& err) { + SBCommandReturnObject& result, v8::JSObject& js_obj, Error& err, int level) { // (Note: We skip array elements as they don't have names.) // Walk all the properties in this object. @@ -1021,7 +1117,8 @@ ReferencesVector* FindReferencesCmd::PropertyScanner::GetReferences() { void FindReferencesCmd::StringScanner::PrintRefs(SBCommandReturnObject& result, v8::JSObject& js_obj, - Error& err) { + Error& err, + int level) { v8::LLV8* v8 = js_obj.v8(); int64_t length = js_obj.GetArrayLength(err); @@ -1082,9 +1179,9 @@ void FindReferencesCmd::StringScanner::PrintRefs(SBCommandReturnObject& result, std::stringstream ss; ss << rang::fg::cyan << "0x" << std::hex << js_obj.raw() - << rang::fg::reset << std::dec << ": " << type_name.c_str() << "." - << key.c_str() << "=" << std::hex << entry.second.raw() << std::dec - << " '" << value.c_str() << "'" << std::endl; + << std::dec << rang::fg::reset << ": " << type_name.c_str() << "." + << key.c_str() << "=" << rang::fg::cyan << "0x" << std::hex << entry.second.raw() << std::dec + << rang::fg::reset << " '" << value.c_str() << "'" << std::endl; result.Printf("%s", ss.str().c_str()); } @@ -1095,7 +1192,7 @@ void FindReferencesCmd::StringScanner::PrintRefs(SBCommandReturnObject& result, void FindReferencesCmd::StringScanner::PrintRefs(SBCommandReturnObject& result, - v8::String& str, Error& err) { + v8::String& str, Error& err, int level) { v8::LLV8* v8 = str.v8(); // Concatenated and sliced strings refer to other strings so diff --git a/src/llscan.h b/src/llscan.h index 19e4e329..f1e581d3 100644 --- a/src/llscan.h +++ b/src/llscan.h @@ -76,6 +76,24 @@ class NodeInfoCmd : public CommandBase { LLScan* llscan_; }; +class ScanOptions { + public: + // Defines what are we looking for + enum ScanType { + kFieldValue, + kPropertyName, + kStringValue, + kBadOption + }; + + ScanOptions() + : scan_type(ScanType::kFieldValue), + recursive_scan(false) {} + + ScanType scan_type; + bool recursive_scan; +}; + class FindReferencesCmd : public CommandBase { public: FindReferencesCmd(LLScan* llscan) : llscan_(llscan) {} @@ -84,9 +102,7 @@ class FindReferencesCmd : public CommandBase { bool DoExecute(lldb::SBDebugger d, char** cmd, lldb::SBCommandReturnObject& result) override; - enum ScanType { kFieldValue, kPropertyName, kStringValue, kBadOption }; - - char** ParseScanOptions(char** cmd, ScanType* type); + char** ParseScanOptions(char** cmd, ScanOptions* options); class ObjectScanner { public: @@ -100,19 +116,27 @@ class FindReferencesCmd : public CommandBase { virtual void ScanRefs(v8::String& str, Error& err){}; virtual void PrintRefs(lldb::SBCommandReturnObject& result, - v8::JSObject& js_obj, Error& err) {} + v8::JSObject& js_obj, Error& err, int level=0) {} + virtual void PrintRefs(lldb::SBCommandReturnObject& result, v8::String& str, - Error& err) {} + Error& err, int level=0) {} virtual void PrintContextRefs(lldb::SBCommandReturnObject& result, - Error& err) {} - - std::string GetPropertyReferenceString(); - std::string GetArrayReferenceString(); + Error& err, + FindReferencesCmd* cli_cmd_, + ScanOptions* options, + ReferencesVector* already_visited_references, + int level=0) {} + + std::string GetPropertyReferenceString(int level=0); + std::string GetArrayReferenceString(int level=0); }; void PrintReferences(lldb::SBCommandReturnObject& result, - ReferencesVector* references, ObjectScanner* scanner); + ReferencesVector* references, ObjectScanner* scanner, + ScanOptions* options, + ReferencesVector* already_visited_references, + int level=0); void ScanForReferences(ObjectScanner* scanner); @@ -129,12 +153,16 @@ class FindReferencesCmd : public CommandBase { void ScanRefs(v8::String& str, Error& err) override; void PrintRefs(lldb::SBCommandReturnObject& result, v8::JSObject& js_obj, - Error& err) override; + Error& err, int level=0) override; void PrintRefs(lldb::SBCommandReturnObject& result, v8::String& str, - Error& err) override; + Error& err, int level=0) override; void PrintContextRefs(lldb::SBCommandReturnObject& result, - Error& err) override; + Error& err, + FindReferencesCmd* cli_cmd_, + ScanOptions* options, + ReferencesVector* already_visited_references, + int level=0) override; private: LLScan* llscan_; @@ -155,7 +183,7 @@ class FindReferencesCmd : public CommandBase { // We only scan properties on objects not Strings, use default no-op impl // of PrintRefs for Strings. void PrintRefs(lldb::SBCommandReturnObject& result, v8::JSObject& js_obj, - Error& err) override; + Error& err, int level=0) override; private: LLScan* llscan_; @@ -176,9 +204,9 @@ class FindReferencesCmd : public CommandBase { void ScanRefs(v8::String& str, Error& err) override; void PrintRefs(lldb::SBCommandReturnObject& result, v8::JSObject& js_obj, - Error& err) override; + Error& err, int level=0) override; void PrintRefs(lldb::SBCommandReturnObject& result, v8::String& str, - Error& err) override; + Error& err, int level=0) override; static const char* const property_reference_template; static const char* const array_reference_template; diff --git a/src/settings.cc b/src/settings.cc index b27a01df..09cf2a12 100644 --- a/src/settings.cc +++ b/src/settings.cc @@ -21,6 +21,13 @@ std::string Settings::SetColor(std::string option) { return color; } +int Settings::SetTreePadding(int option) { + if (option < 1) + option = 1; + tree_padding = option; + return tree_padding; +} + bool Settings::ShouldUseColor() { #ifdef NO_COLOR_OUTPUT return false; diff --git a/src/settings.h b/src/settings.h index 5fcc8e02..9dba2f93 100644 --- a/src/settings.h +++ b/src/settings.h @@ -20,12 +20,16 @@ class Settings { Settings& operator=(const Settings&) = delete; std::string color = "auto"; + int tree_padding = 2; + public: static Settings* GetSettings(); std::string SetColor(std::string option); std::string GetColor() { return color; }; bool ShouldUseColor(); + int GetTreePadding() { return tree_padding; }; + int SetTreePadding(int option); }; } // namespace llnode diff --git a/test/fixtures/inspect-scenario.js b/test/fixtures/inspect-scenario.js index fecd80c8..ec87c344 100644 --- a/test/fixtures/inspect-scenario.js +++ b/test/fixtures/inspect-scenario.js @@ -84,9 +84,15 @@ function closure() { this.name = "Class B"; } + function Class_C(class_b_array) { + this.arr = class_b_array; + } + const arr = new Array(); for(let i=0; i < 10; i++) arr.push(new Class_B()); + let classC = new Class_C(arr); + c.method(); } diff --git a/test/plugin/scan-test.js b/test/plugin/scan-test.js index 0ec2b77a..9b2d677e 100644 --- a/test/plugin/scan-test.js +++ b/test/plugin/scan-test.js @@ -79,6 +79,30 @@ function test(executable, core, t) { t.notOk(/\.\.\.\.\.\.\.\.\.\./.test(lines.join('\n')), 'Should not show ellipses'); t.ok(/\(Showing 6 to 10 of 10 instances\)/.test(lines.join('\n')), 'Should show 6 to 10 '); + sess.send('v8 findjsinstances Class_B'); + sess.send('version'); + }); + + // Test for recursive findrefs, a new `Class_C` was introduced in `inspect-scenario.js` + sess.linesUntil(versionMark, (err, lines) => { + t.error(err); + + for (let i=0; i < lines.length; i++) { + const match = lines[i].match(/(0x[0-9a-f]+):/i); + if (match) { + sess.send(`v8 findrefs -r ${match[1]}`); + break; + } + } + sess.send('version'); + }); + + // Test if parent `Class_C` is present on tree generated by previous command + // This means that we successfully transversed the reference tree + sess.linesUntil(versionMark, (err, lines) => { + t.error(err); + t.ok(/Class_C\.arr/.test(lines.join('\n')), 'Should find parent reference' ); + sess.send('v8 findjsinstances Zlib'); sess.send('version'); });