From aad54147563f1aa3b16c0871bd33607ef42dce8e 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 PR-URL: https://github.com/nodejs/llnode/pull/244 Reviewed-By: Matheus Marchini --- src/llnode.cc | 32 ++++++ src/llnode.h | 12 +++ src/llscan.cc | 172 ++++++++++++++++++++++-------- src/llscan.h | 58 +++++++--- src/settings.cc | 6 ++ src/settings.h | 4 + test/fixtures/inspect-scenario.js | 7 ++ test/fixtures/scan-scenario.js | 62 +++++++++++ test/plugin/scan-test.js | 58 +++++++++- 9 files changed, 350 insertions(+), 61 deletions(-) create mode 100644 test/fixtures/scan-scenario.js diff --git a/src/llnode.cc b/src/llnode.cc index 8c41b5d8..76d6d7c9 100644 --- a/src/llnode.cc +++ b/src/llnode.cc @@ -134,6 +134,34 @@ 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; + + // Extraction operator (>>) parses option and tries to interpret it + // as an `int` value. If an error occur, an internal state flag will be + // set. Not (!) operator will evaluate to true if `failbit` or `badbit` + // is set + if (!(option >> padding)) { + result.SetError("unable to convert provided value."); + return false; + }; + + // This is just an opinated range limit, to avoid negative values + // or big number, these values would produce a bad visualization experience + 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) { @@ -451,6 +479,9 @@ 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`"); @@ -485,6 +516,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..2e6deb7f 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 a6dd680f..e0c573ca 100644 --- a/src/llscan.cc +++ b/src/llscan.cc @@ -469,7 +469,6 @@ bool NodeInfoCmd::DoExecute(SBDebugger d, char** cmd, return true; } - bool FindReferencesCmd::DoExecute(SBDebugger d, char** cmd, SBCommandReturnObject& result) { if (cmd == nullptr || *cmd == nullptr) { @@ -486,10 +485,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"); @@ -499,8 +496,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; @@ -525,7 +522,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."); @@ -536,7 +533,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."); @@ -552,7 +549,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; @@ -572,8 +569,24 @@ bool FindReferencesCmd::DoExecute(SBDebugger d, char** cmd, if (!scanner->AreReferencesLoaded()) { ScanForReferences(scanner); } + + // If we're using recursive findrefs, we have to make sure the + // RecursiveScanner is initialized as well. + if (scan_options.recursive_scan) { + auto ref_scanner = new ReferenceScanner(llscan_, v8::Value()); + if (!ref_scanner->AreReferencesLoaded()) { + ScanForReferences(ref_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; @@ -587,6 +600,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); @@ -620,12 +634,49 @@ void FindReferencesCmd::ScanForReferences(ObjectScanner* scanner) { } } +void FindReferencesCmd::PrintRecursiveReferences( + lldb::SBCommandReturnObject& result, + ScanOptions* options, + ReferencesVector* visited_references, + uint64_t address, + int level +) +{ + Settings* settings = Settings::GetSettings(); + unsigned int padding = settings->GetTreePadding(); + + std::string branch = std::string(padding * level, ' ') + "+ "; + + result.Printf("%s", branch.c_str()); + + if (find(visited_references->begin(), visited_references->end(), address) != visited_references->end()) + { + std::stringstream seen_str; + seen_str << rang::fg::red << " [seen above]" << rang::fg::reset << std::endl; + result.Printf(seen_str.str().c_str()); + } else { + visited_references->push_back(address); + v8::Value value(llscan_->v8(), address); + ReferenceScanner scanner_(llscan_, value); + ReferencesVector* references_ = scanner_.GetReferences(); + PrintReferences( + result, + references_, + &scanner_, + options, + visited_references, + level + 1 + ); + } +} -void FindReferencesCmd::PrintReferences(SBCommandReturnObject& result, - ReferencesVector* references, - ObjectScanner* scanner) { +void FindReferencesCmd::PrintReferences( + SBCommandReturnObject& result, ReferencesVector* references, + 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); @@ -642,11 +693,19 @@ 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) { + PrintRecursiveReferences(result, options, already_visited_references, addr, level); + } } 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) { + PrintRecursiveReferences(result, options, already_visited_references, addr, level); + } } else if (type == v8->types()->kJSTypedArrayType) { // These should only point to off heap memory, @@ -659,14 +718,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; @@ -686,29 +747,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); @@ -722,7 +786,9 @@ 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(); @@ -743,14 +809,26 @@ 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; + 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) { + cli_cmd_->PrintRecursiveReferences(result, options, already_visited_references, c.raw(), level); + } } } } } -std::string FindReferencesCmd::ObjectScanner::GetPropertyReferenceString() { +std::string FindReferencesCmd::ObjectScanner::GetPropertyReferenceString( + int level) { std::stringstream ss; ss << rang::fg::cyan << "0x%" PRIx64 << rang::fg::reset << ": " << rang::fg::magenta << "%s" << rang::style::bold << rang::fg::yellow @@ -759,7 +837,8 @@ std::string FindReferencesCmd::ObjectScanner::GetPropertyReferenceString() { return ss.str(); } -std::string FindReferencesCmd::ObjectScanner::GetArrayReferenceString() { +std::string FindReferencesCmd::ObjectScanner::GetArrayReferenceString( + int level) { std::stringstream ss; ss << rang::fg::cyan << "0x%" PRIx64 << rang::fg::reset << ": " << rang::fg::magenta << "%s" << rang::style::bold << rang::fg::yellow @@ -770,7 +849,8 @@ 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); @@ -782,7 +862,7 @@ 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()); } @@ -800,7 +880,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()); } @@ -809,7 +890,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); @@ -822,7 +903,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()); } @@ -833,7 +914,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()); } @@ -842,7 +923,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()); } @@ -852,7 +933,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()); } @@ -957,7 +1038,8 @@ 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. @@ -1020,7 +1102,7 @@ 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); @@ -1080,10 +1162,11 @@ void FindReferencesCmd::StringScanner::PrintRefs(SBCommandReturnObject& result, std::string type_name = js_obj.GetTypeName(err); 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; + ss << rang::fg::cyan << "0x" << std::hex << js_obj.raw() << 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()); } @@ -1094,7 +1177,8 @@ 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 413692c5..b39396d3 100644 --- a/src/llscan.h +++ b/src/llscan.h @@ -76,6 +76,17 @@ 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 +95,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,22 +109,37 @@ 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) {} + Error& err, FindReferencesCmd* cli_cmd_, + ScanOptions* options, + ReferencesVector* already_visited_references, + int level = 0) {} - std::string GetPropertyReferenceString(); - std::string GetArrayReferenceString(); + 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); + void PrintRecursiveReferences( + lldb::SBCommandReturnObject& result, + ScanOptions* options, + ReferencesVector* visited_references, + uint64_t address, + int level + ); + class ReferenceScanner : public ObjectScanner { public: ReferenceScanner(LLScan* llscan, v8::Value search_value) @@ -129,12 +153,14 @@ 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; + void PrintContextRefs(lldb::SBCommandReturnObject& result, Error& err, + FindReferencesCmd* cli_cmd_, ScanOptions* options, + ReferencesVector* already_visited_references, + int level = 0) override; private: LLScan* llscan_; @@ -155,7 +181,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 +202,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..bf1bd126 100644 --- a/src/settings.cc +++ b/src/settings.cc @@ -21,6 +21,12 @@ 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..0e926aad 100644 --- a/test/fixtures/inspect-scenario.js +++ b/test/fixtures/inspect-scenario.js @@ -84,9 +84,16 @@ function closure() { this.name = "Class B"; } + function Class_C(class_b_array) { + this.arr = class_b_array; + this.my_class_c = "My Class C"; + } + 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/fixtures/scan-scenario.js b/test/fixtures/scan-scenario.js new file mode 100644 index 00000000..12f606f4 --- /dev/null +++ b/test/fixtures/scan-scenario.js @@ -0,0 +1,62 @@ +'use strict'; + +const common = require('../common'); + +const zlib = require('zlib'); + +let outerVar = 'outer variable'; + +exports.holder = {}; + +function makeThin(a, b) { + var str = a + b; + var obj = {}; + obj[str]; // Turn the cons string into a thin string. + return str; +} + +function closure() { + + function Class() { + this.x = 1; + this.y = 123.456; + + this.hashmap = {}; + } + + + Class.prototype.method = function method() { + throw new Error('Uncaught'); + }; + + const c = new Class(); + + + let scopedVar = 'scoped value'; + let scopedAPI = zlib.createDeflate()._handle; + let scopedArray = [ 0, scopedAPI ]; + + exports.holder = scopedAPI; + + c.hashmap.scoped = function name() { + return scopedVar + outerVar + scopedAPI + scopedArray; + }; + + function Class_B() { + this.my_class_b = "Class B"; + } + + function Class_C(class_b_array) { + this.arr = class_b_array; + this.my_class_c = "My Class C"; + } + + const arr = new Array(); + for(let i=0; i < 10; i++) arr.push(new Class_B()); + + let classC = new Class_C(arr); + + c.method(); +} + +closure(); diff --git a/test/plugin/scan-test.js b/test/plugin/scan-test.js index 0ec2b77a..c7f9148b 100644 --- a/test/plugin/scan-test.js +++ b/test/plugin/scan-test.js @@ -12,7 +12,7 @@ tape('v8 findrefs and friends', (t) => { test(process.env.LLNODE_NODE_EXE, process.env.LLNODE_CORE, t); } else { common.saveCore({ - scenario: 'inspect-scenario.js' + scenario: 'scan-scenario.js' }, (err) => { t.error(err); t.ok(true, 'Saved core'); @@ -32,6 +32,7 @@ function test(executable, core, t) { sess.send('version'); }); + sess.linesUntil(versionMark, (err, lines) => { t.error(err); t.ok(/\d+ Class/.test(lines.join('\n')), 'Class should be in findjsobjects'); @@ -79,6 +80,61 @@ 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 findrefs -n my_class_c'); + sess.send('version'); + }); + + // Test for findrefs -n + sess.linesUntil(versionMark, (err, lines) => { + t.error(err); + t.ok(/(0x[0-9a-f]+): Class_C\.my_class_c=(0x[0-9a-f]+)/.test(lines.join('\n')), 'Should find class C with property'); + sess.send('v8 findrefs -r -n my_class_b'); + sess.send('version'); + }); + + // Test for -r -n + sess.linesUntil(versionMark, (err, lines) => { + t.error(err); + t.ok(/Class_C\.arr/.test(lines.join('\n')), 'Should find parent reference with -r -n' ); + sess.send('v8 findrefs -s "My Class C"'); + sess.send('version'); + }); + + // Test for findrefs -s + sess.linesUntil(versionMark, (err, lines) => { + t.error(err); + t.ok(/(0x[0-9a-f]+): Class_C\.my_class_c=(0x[0-9a-f]+)/.test(lines.join('\n')), 'Should find class C with string'); + sess.send('v8 findrefs -r -s "Class B"'); + sess.send('version'); + }); + + // Test for -r -s + sess.linesUntil(versionMark, (err, lines) => { + t.error(err); + t.ok(/Class_C\.arr/.test(lines.join('\n')), 'Should find parent reference with -r -s' ); sess.send('v8 findjsinstances Zlib'); sess.send('version'); });