diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc index 8d100d0d..16d2eebb 100644 --- a/src/llv8-constants.cc +++ b/src/llv8-constants.cc @@ -244,8 +244,9 @@ void ScopeInfo::Load() { void Context::Load() { - kClosureIndex = - LoadConstant("class_Context__closure_index__int", "context_idx_closure"); + kClosureIndex = LoadConstant("class_Context__closure_index__int", + "context_idx_closure", -1); + kScopeInfoIndex = LoadConstant("context_idx_scope_info", -1); kPreviousIndex = LoadConstant("class_Context__previous_index__int", "context_idx_prev"); // TODO (mmarchini) change LoadConstant to accept variable arguments, a list @@ -486,6 +487,9 @@ void Types::Load() { kFirstJSObjectType = LoadConstant("type_JSGlobalObject__JS_GLOBAL_OBJECT_TYPE"); + kFirstContextType = LoadConstant("FirstContextType"); + kLastContextType = LoadConstant("LastContextType"); + kHeapNumberType = LoadConstant("type_HeapNumber__HEAP_NUMBER_TYPE"); kMapType = LoadConstant("type_Map__MAP_TYPE"); kGlobalObjectType = @@ -507,6 +511,7 @@ void Types::Load() { kSharedFunctionInfoType = LoadConstant("type_SharedFunctionInfo__SHARED_FUNCTION_INFO_TYPE"); kScriptType = LoadConstant("type_Script__SCRIPT_TYPE"); + kScopeInfoType = LoadConstant("type_ScopeInfo__SCOPE_INFO_TYPE"); if (kJSAPIObjectType == -1) { common_->Load(); diff --git a/src/llv8-constants.h b/src/llv8-constants.h index 47ed84ab..80fc6e14 100644 --- a/src/llv8-constants.h +++ b/src/llv8-constants.h @@ -199,12 +199,22 @@ class Context : public Module { CONSTANTS_DEFAULT_METHODS(Context); int64_t kClosureIndex; + int64_t kScopeInfoIndex; int64_t kGlobalObjectIndex; int64_t kPreviousIndex; int64_t kNativeIndex; int64_t kEmbedderDataIndex; int64_t kMinContextSlots; + inline bool hasClosure() { + // NOTE (mmarchini): V8 6.8 replaced the closure field (which was a + // JSFunction) with a scope_info field (which is a ScopeInfo). The change + // made it easier to get the scope info for a context, but removed our + // ability to get the outer function for a given context. We can still get + // the outer context through the previous field though. + return kClosureIndex != -1; + } + protected: void Load(); }; @@ -465,6 +475,9 @@ class Types : public Module { int64_t kFirstNonstringType; int64_t kFirstJSObjectType; + int64_t kFirstContextType; + int64_t kLastContextType; + int64_t kHeapNumberType; int64_t kMapType; int64_t kGlobalObjectType; @@ -483,6 +496,7 @@ class Types : public Module { int64_t kJSDateType; int64_t kSharedFunctionInfoType; int64_t kScriptType; + int64_t kScopeInfoType; protected: void Load(); diff --git a/src/llv8.cc b/src/llv8.cc index efec4cfa..d83bb7ad 100644 --- a/src/llv8.cc +++ b/src/llv8.cc @@ -389,10 +389,15 @@ std::string JSFunction::Inspect(InspectOptions* options, Error& err) { snprintf(tmp, sizeof(tmp), "\n context=0x%016" PRIx64, context.raw()); res += tmp; - std::string context_str = context.Inspect(err); - if (err.Fail()) return std::string(); + { + InspectOptions ctx_options; + ctx_options.detailed = true; + ctx_options.indent_depth = options->indent_depth + 1; + std::string context_str = context.Inspect(&ctx_options, err); + if (err.Fail()) return std::string(); - if (!context_str.empty()) res += "{\n" + context_str + "}"; + if (!context_str.empty()) res += ":" + context_str; + } if (options->print_source) { SharedFunctionInfo info = Info(err); @@ -821,6 +826,12 @@ std::string HeapObject::Inspect(InspectOptions* options, Error& err) { return pre + str.Inspect(options, err); } + if (type >= v8()->types()->kFirstContextType && + type <= v8()->types()->kLastContextType) { + Context ctx(this); + return pre + ctx.Inspect(options, err); + } + if (type == v8()->types()->kFixedArrayType) { FixedArray arr(this); return pre + arr.Inspect(options, err); @@ -1047,24 +1058,37 @@ 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(); -std::string Context::Inspect(Error& err) { - std::string res; + SharedFunctionInfo info = closure.Info(err); + if (err.Fail()) return HeapObject(); + + return info.GetScopeInfo(err); +} + +std::string Context::Inspect(InspectOptions* options, Error& err) { // Not enough postmortem information, return bare minimum if (v8()->shared_info()->kScopeInfoOffset == -1 && v8()->shared_info()->kNameOrScopeInfoOffset == -1) - return res; + return std::string(); - Value previous = Previous(err); - if (err.Fail()) return std::string(); + std::string res = "detailed) { + return res + ">"; + } - SharedFunctionInfo info = closure.Info(err); + res += ": {\n"; + + Value previous = Previous(err); if (err.Fail()) return std::string(); - HeapObject scope_obj = info.GetScopeInfo(err); + HeapObject scope_obj = GetScopeInfo(err); if (err.Fail()) return std::string(); ScopeInfo scope(scope_obj); @@ -1076,25 +1100,41 @@ std::string Context::Inspect(Error& err) { Smi local_count_smi = scope.ContextLocalCount(err); if (err.Fail()) return std::string(); - InspectOptions options; - HeapObject heap_previous = HeapObject(previous); if (heap_previous.Check()) { char tmp[128]; - snprintf(tmp, sizeof(tmp), " (previous)=0x%016" PRIx64, previous.raw()); - res += tmp; + snprintf(tmp, sizeof(tmp), (options->get_indent_spaces() + "(previous)=0x%016" PRIx64).c_str(), previous.raw()); + res += std::string(tmp) + ":,"; } if (!res.empty()) res += "\n"; - { + + if (v8()->context()->hasClosure()) { + JSFunction closure = Closure(err); + if (err.Fail()) return std::string(); char tmp[128]; - snprintf(tmp, sizeof(tmp), " (closure)=0x%016" PRIx64 " {", + snprintf(tmp, sizeof(tmp), (options->get_indent_spaces() + "(closure)=0x%016" PRIx64 " {").c_str(), closure.raw()); res += tmp; - InspectOptions options; - res += closure.Inspect(&options, err) + "}"; + InspectOptions closure_options; + res += closure.Inspect(&closure_options, err) + "}"; if (err.Fail()) return std::string(); + } else { + char tmp[128]; + snprintf(tmp, sizeof(tmp), (options->get_indent_spaces() + "(scope_info)=0x%016" PRIx64).c_str(), + scope.raw()); + + res += std::string(tmp) + ":"; } diff --git a/src/llv8.h b/src/llv8.h index 2d9ef37d..02ced191 100644 --- a/src/llv8.h +++ b/src/llv8.h @@ -44,14 +44,20 @@ class Value { : detailed(false), print_map(false), print_source(false), - length(kLength) {} + length(kLength), + indent_depth(1) {} static const unsigned int kLength = 16; + static const unsigned int kIndentSize = 2; + inline std::string get_indent_spaces() { + return std::string(indent_depth * kIndentSize, ' '); + } bool detailed; bool print_map; bool print_source; unsigned int length; + unsigned int indent_depth; }; Value(const Value& v) = default; @@ -382,7 +388,7 @@ class Context : public FixedArray { public: V8_VALUE_DEFAULT_METHODS(Context, FixedArray) - inline JSFunction Closure(Error& err); + inline HeapObject GetScopeInfo(Error& err); inline Value Previous(Error& err); inline Value Native(Error& err); inline bool IsNative(Error& err); @@ -390,7 +396,10 @@ class Context : public FixedArray { inline T GetEmbedderData(int64_t index, Error& err); inline Value ContextSlot(int index, Error& err); - std::string Inspect(Error& err); + std::string Inspect(InspectOptions *options, Error& err); + + private: + inline JSFunction Closure(Error& err); }; class ScopeInfo : public FixedArray { diff --git a/test/plugin/inspect-test.js b/test/plugin/inspect-test.js index ed4da485..0d4dbcdb 100644 --- a/test/plugin/inspect-test.js +++ b/test/plugin/inspect-test.js @@ -51,7 +51,7 @@ const hashMapTests = { const arrowSource = 'source:\n' + 'function c.hashmap.(anonymous function)(a,b)=>{a+b}\n' + '>'; - + t.ok(lines.includes(arrowSource), 'hashmap[25] should have the correct function source'); cb(null); @@ -305,14 +305,22 @@ const hashMapTests = { const contextTests = { 'previous': { - re: /\(previous\)/, + re: /\(previous\)=(0x[0-9a-f]+)[^\n]+/, desc: '.(previous)' }, 'closure': { - re: /\(closure\)=(0x[0-9a-f]+)[^\n]+function: closure/i, + re: /(\((?:closure|scope_info)\)=0x[0-9a-f]+)[^\n]+/i, desc: '.(closure)', validator(t, sess, addresses, name, cb) { - const address = addresses[name]; + const type = addresses[name].split("=")[0]; + let address = undefined; + if (type === "(closure)") { + address = addresses[name].split("=")[1]; + } else if (type === "(scope_info)") { + address = addresses["previous"]; + } else { + return cb(new Error("unknown field")); + } sess.send(`v8 inspect ${address}`); sess.linesUntil(/}>/, (err, lines) => { if (err) return cb(err);