From 8f53875d25445d1f1fe15c1f2291ebb1e588840b Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Thu, 3 Oct 2019 15:22:20 -0700 Subject: [PATCH 01/16] test: make SessionOutput Promise tolerant If a test case would use Promises for linesUntil, sometimes we would emit lines events while there was no listener attached to it. This commit changes SessionOutput to make it a little more resilient: now line events will only be emitted when the SessionOutput is waiting. When wait is called (via linesUntil, for example), it'll retrieve all lines (via line events) on the buffer until it reaches a line matching the regexp. If no lines match the regexp, we'll continue as before waiting for data from lldb and buffering it. --- test/common.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/common.js b/test/common.js index a8c2c125..9fd9d36b 100644 --- a/test/common.js +++ b/test/common.js @@ -45,10 +45,13 @@ function SessionOutput(session, stream, timeout) { this.timeout = timeout || 10000; this.session = session; - stream.on('data', (data) => { - buf += data; - + this.flush = function flush() { for (;;) { + // NOTE(mmarchini): don't emit line events while not waiting, otherwise + // we might miss something. + if (!this.waiting) + break + let index = buf.indexOf('\n'); if (index === -1) @@ -62,6 +65,11 @@ function SessionOutput(session, stream, timeout) { else this.emit('line', line); } + } + + stream.on('data', (data) => { + buf += data; + this.flush(); }); // Ignore errors @@ -129,6 +137,7 @@ SessionOutput.prototype.wait = function wait(regexp, callback, allLines) { }, this.timeout).unref(); this.on('line', onLine); + this.flush(); }; SessionOutput.prototype.waitBreak = function waitBreak(callback) { From e249ab5811e0f1247da15a162aaf821b41522bb5 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Thu, 3 Oct 2019 09:49:38 -0700 Subject: [PATCH 02/16] src: fix function name lookup for inferred names Apparently, sometimes the FunctionName slot on ScopeInfo is filled with the empty string instead of not existing. This commit changes our heuristic to search for the first non-empty string on the first 3 slots after the last context info slot on the ScopeInfo. This should be enough to cover most (all?) cases. Also updated frame-test to add frames to the stack which V8 will infer the name instead of storing it directly, and changed this particular test to use Promises instead of callbacks. We should be able to upgrade tests to primise-based API gradually with this approach. When all tests are promisified, we can change the api on test/common.js to be promise-based instead of callback-based. --- src/llv8-inl.h | 13 ++- test/fixtures/frame-scenario.js | 24 ++++- test/plugin/frame-test.js | 174 ++++++++++++++++++-------------- 3 files changed, 131 insertions(+), 80 deletions(-) diff --git a/src/llv8-inl.h b/src/llv8-inl.h index b792cb49..b0fde5c3 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -906,18 +906,27 @@ inline HeapObject ScopeInfo::MaybeFunctionName(Error& err) { // metadata to determine in which slot its being stored for the present // ScopeInfo, we try to find it heuristically. int tries = 3; + HeapObject likely_function_name; while (tries > 0 && proper_index < Length(err).GetValue()) { err = Error(); HeapObject maybe_function_name = FixedArray::Get(proper_index, err); - if (err.Success() && String::IsString(v8(), maybe_function_name, err)) - return maybe_function_name; + if (err.Success() && String::IsString(v8(), maybe_function_name, err)) { + likely_function_name = maybe_function_name; + if (String(likely_function_name).Length(err).GetValue() > 0) { + return likely_function_name; + } + } tries--; proper_index++; } + if (likely_function_name.Check()) { + return likely_function_name; + } + err = Error::Failure("Couldn't get FunctionName from ScopeInfo"); return HeapObject(); } diff --git a/test/fixtures/frame-scenario.js b/test/fixtures/frame-scenario.js index 259bf901..afa46e0b 100644 --- a/test/fixtures/frame-scenario.js +++ b/test/fixtures/frame-scenario.js @@ -3,13 +3,29 @@ const common = require('../common'); function crasher(unused) { 'use strict'; - process.abort(); // Creates an exit frame. - return this; // Force definition of |this|. + this.foo = arguments; // Force adaptor frame on Node.js v12+ + process.abort(); // Creates an exit frame. + return this; // Force definition of |this|. } -function eyecatcher() { +// Force V8 to use an inferred name instead of saving the variable name as +// FunctionName. +let fnInferredName; +fnInferredName = (() => function () { crasher(); // # args < # formal parameters inserts an adaptor frame. +})(); + +function Module() { + this.foo = "bar"; +} + +Module.prototype.fnInferredNamePrototype = function() { + fnInferredName(); +} + +function fnFunctionName() { + (new Module()).fnInferredNamePrototype(); return this; // Force definition of |this|. } -eyecatcher(); +fnFunctionName(); diff --git a/test/plugin/frame-test.js b/test/plugin/frame-test.js index 71007407..f4304e91 100644 --- a/test/plugin/frame-test.js +++ b/test/plugin/frame-test.js @@ -1,16 +1,31 @@ 'use strict'; +const { promisify } = require('util'); + const tape = require('tape'); const common = require('../common'); -const sourceCode = [ -"10 function eyecatcher() {", -"11 crasher(); // # args < # formal parameters inserts an adaptor frame.", -"12 return this; // Force definition of |this|.", -"13 }", -]; -const lastLine = new RegExp(sourceCode[sourceCode.length - 1]); +const sourceCodes = { + "fnFunctionName": [ + "26 function fnFunctionName() {", + "27 (new Module()).fnInferredNamePrototype();", + "28 return this; // Force definition of |this|.", + "29 }", + ], + "fnInferredNamePrototype": [ + "22 Module.prototype.fnInferredNamePrototype = function() {", + "23 fnInferredName();", + "24 }", + "25", + ], + "fnInferredName": [ + "14 fnInferredName = (() => function () {", + "15 crasher(); // # args < # formal parameters inserts an adaptor frame.", + "16 })();", + "17", + ], +}; function fatalError(t, sess, err) { t.error(err); @@ -18,94 +33,105 @@ function fatalError(t, sess, err) { return t.end(); } -function testFrameList(t, sess, frameNumber) { +async function testFrameList(t, sess, frameNumber, sourceCode, cb) { + const lastLine = new RegExp(sourceCode[sourceCode.length - 1]); + sess.send(`frame select ${frameNumber}`); - sess.linesUntil(/frame/, (err, lines) => { - if (err) { - return fatalError(t, sess, err); - } - sess.send('v8 source list'); - - sess.linesUntil(/v8 source list/, (err, lines) => { - sess.linesUntil(lastLine, (err, lines) => { - if (err) { - return fatalError(t, sess, err); - } - t.equal(lines.length, sourceCode.length, - `v8 source list correct size`); - for (let i = 0; i < lines.length; i++) { - t.equal(lines[i].trim(), sourceCode[i], `v8 source list #${i}`); - } - - sess.send('v8 source list -l 2'); - - sess.linesUntil(/v8 source list/, (err, lines) => { - sess.linesUntil(lastLine, (err, lines) => { - if (err) { - return fatalError(t, sess, err); - } - t.equal(lines.length, sourceCode.length - 1, - `v8 source list -l 2 correct size`); - for (let i = 0; i < lines.length; i++) { - t.equal(lines[i].trim(), sourceCode[i + 1], - `v8 source list -l 2 #${i}`); - } - - sess.quit(); - t.end(); - }); - }); - }); - }); - }); + await sess.linesUntil(/frame/); + sess.send('v8 source list'); + await sess.linesUntil(/v8 source list/); + + let lines = await sess.linesUntil(lastLine); + t.equal(lines.length, sourceCode.length, + `v8 source list correct size`); + for (let i = 0; i < lines.length; i++) { + t.equal(lines[i].trim(), sourceCode[i], `v8 source list #${i}`); + } + + sess.send('v8 source list -l 2'); + await sess.linesUntil(/v8 source list/); + lines = await sess.linesUntil(lastLine); + + t.equal(lines.length, sourceCode.length - 1, + `v8 source list -l 2 correct size`); + for (let i = 0; i < lines.length; i++) { + t.equal(lines[i].trim(), sourceCode[i + 1], + `v8 source list -l 2 #${i}`); + } } -tape('v8 stack', (t) => { +tape('v8 stack', async (t) => { t.timeoutAfter(15000); const sess = common.Session.create('frame-scenario.js'); - sess.waitBreak((err) => { - t.error(err); + sess.waitBreak = promisify(sess.waitBreak); + sess.linesUntil = promisify(sess.linesUntil); + + try { + await sess.waitBreak(); sess.send('v8 bt'); - }); - sess.linesUntil(/eyecatcher/, (err, lines) => { - t.error(err); + let lines = await sess.linesUntil(/\sfnFunctionName\(/); + lines.reverse(); t.ok(lines.length > 4, 'frame count'); - // FIXME(bnoordhuis) This can fail with versions of lldb that don't - // support the GetMemoryRegions() API; llnode won't be able to identify - // V8 builtins stack frames, it just prints them as anonymous frames. + lines = lines.filter((s) => !/|/.test(s)); - const eyecatcher = lines[0]; - const adapter = lines[1]; - const crasher = lines[2]; - const exit = lines[3]; - t.ok(/eyecatcher/.test(eyecatcher), 'eyecatcher frame'); - t.ok(//.test(adapter), 'arguments adapter frame'); + const exit = lines[5]; + const crasher = lines[4]; + const adapter = lines[3]; + const fnInferredName = lines[2]; + const fnInferredNamePrototype = lines[1]; + const fnFunctionName = lines[0]; + t.ok(//.test(exit), 'exit frame'); t.ok(/crasher/.test(crasher), 'crasher frame'); - { - // V8 4.5 does not use EXIT frames, only INTERNAL frames. - const isv4 = /^v4\./.test(process.version); - const re = isv4 ? // : //; - t.ok(re.test(exit), 'exit frame'); - } - // eyecatcher() is a sloppy mode function that should have an implicit + t.ok(//.test(adapter), 'arguments adapter frame'); + t.ok(/\sfnInferredName\(/.test(fnInferredName), 'fnInferredName frame'); + t.ok(/\sModule.fnInferredNamePrototype\(/.test(fnInferredNamePrototype), + 'fnInferredNamePrototype frame'); + t.ok(/\sfnFunctionName\(/.test(fnFunctionName), 'fnFunctionName frame'); + // fn() is a sloppy mode function that should have an implicit // |this| that is the global object. crasher() is a strict mode function // that should have a |this| that is the |undefined| value. // // Interestingly, V8 4.5 has a quirk where the |this| value is |undefined| // in both strict and sloppy mode unless the function actually uses |this|. // The test adds unreachable `return this` statements as a workaround. - t.ok(/this=(0x[0-9a-f]+):/.test(eyecatcher), 'global this'); + t.ok(/this=(0x[0-9a-f]+):/.test(fnFunctionName), + 'global this'); t.ok(/this=(0x[0-9a-f]+):/.test(crasher), 'undefined this'); - const eyecatcherFrame = eyecatcher.match(/frame #([0-9]+)/)[1]; - if (!eyecatcherFrame) { - fatalError(t, sess, "Couldn't determine eyecather's frame number"); + // TODO(mmarchini): also test positional info (line, column) + + const fnFunctionNameFrame = fnFunctionName.match(/frame #([0-9]+)/)[1]; + if (fnFunctionNameFrame) { + await testFrameList(t, sess, fnFunctionNameFrame, + sourceCodes['fnFunctionName']); + } else { + fatalError(t, sess, "Couldn't determine fnFunctionName's frame number"); + } + + const fnInferredNamePrototypeFrame = + fnInferredNamePrototype.match(/frame #([0-9]+)/)[1]; + if (fnInferredNamePrototypeFrame) { + await testFrameList(t, sess, fnInferredNamePrototypeFrame, + sourceCodes['fnInferredNamePrototype']); + } else { + fatalError(t, sess, + "Couldn't determine fnInferredNamePrototype's frame number"); } - testFrameList(t, sess, eyecatcherFrame); + const fnInferredNameFrame = fnInferredName.match(/frame #([0-9]+)/)[1]; + if (fnInferredNameFrame) { + await testFrameList(t, sess, + fnInferredNameFrame, sourceCodes['fnInferredName']); + } else { + fatalError(t, sess, "Couldn't determine fnInferredName's frame number"); + } - }); + sess.quit(); + return t.end(); + } catch (err) { + fatalError(t, sess, err); + } }); From fc22e5966006a579981cb87a22e37a29a3e53ca9 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Thu, 3 Oct 2019 10:02:39 -0700 Subject: [PATCH 03/16] src: use Check() instead of raw != -1 a501635516aa0c153b1e17b62e47150b6458af99 changed the behavior of Check() so that it could be properly used to determine if an object was loaded successfully from memory. In the past we used raw != -1 to perform the same check. Some places were still using the old approach, which has less guarantees than Check(). This commit changes those places to use the new approach. Also added a few RETURN_IF_THIS_INVALID guards on the functions touched by this commit. --- src/llv8.cc | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/llv8.cc b/src/llv8.cc index 155479cd..b783a2d2 100644 --- a/src/llv8.cc +++ b/src/llv8.cc @@ -304,19 +304,17 @@ bool JSFrame::MightBeV8Frame(lldb::SBFrame& frame) { } std::string JSFunction::GetDebugLine(std::string args, Error& err) { - SharedFunctionInfo info = Info(err); - if (err.Fail()) return std::string(); + RETURN_IF_THIS_INVALID(std::string()); - std::string res = info.ProperName(err); + // TODO(mmarchini) turn this into CheckedType + std::string res = Name(err); if (err.Fail()) return std::string(); if (!args.empty()) res += "(" + args + ")"; res += " at "; - std::string shared; - - res += info.GetPostfix(err); + res += Info(err).GetPostfix(err); if (err.Fail()) return std::string(); return res; @@ -385,13 +383,15 @@ std::string JSFunction::GetSource(Error& err) { std::string SharedFunctionInfo::ProperName(Error& err) { + RETURN_IF_THIS_INVALID(std::string()); + String name = Name(err); if (err.Fail()) return std::string(); std::string res = name.ToString(err); if (err.Fail() || res.empty()) { Value inferred = GetInferredName(err); - if (err.Fail() || inferred.raw() == -1) return std::string(); + if (err.Fail() || !inferred.Check()) return std::string(); // Function may not have inferred name if (!inferred.IsHoleOrUndefined(err) && !err.Fail()) @@ -406,8 +406,10 @@ std::string SharedFunctionInfo::ProperName(Error& err) { std::string SharedFunctionInfo::GetPostfix(Error& err) { + RETURN_IF_THIS_INVALID(std::string()); + Script script = GetScript(err); - if (err.Fail() || script.raw() == -1) return std::string(); + if (err.Fail() || !script.Check()) return std::string(); // There is no `Script` for functions created in C++ (and possibly others) int64_t type = script.GetType(err); @@ -1257,9 +1259,11 @@ bool JSError::HasStackTrace(Error& err) { JSArray JSError::GetFrameArray(Error& err) { + RETURN_IF_THIS_INVALID(JSArray()); + v8::Value maybe_stack = GetProperty(stack_trace_property(), err); - if (err.Fail() || maybe_stack.raw() == -1) { + if (err.Fail() || !maybe_stack.Check()) { PRINT_DEBUG("Couldn't find a symbol property in the Error object."); return JSArray(); } From d4699c05cee892116e331a651e5ca676cbc8f2c7 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Mon, 30 Sep 2019 16:55:23 -0700 Subject: [PATCH 04/16] src: introduce Constant class Same idea as CheckedType: instead of relying on the default value for constants to determine if they were loaded correctly or not (default value usually was -1), have a class which will have information about the loaded constant. This can help us: - Check if a constant was properly loaded before using it (otherwise we'd get into undefined behavior, and there are a lot of places where this happens. - Check if a constant value was loaded or if we're looking at the default value. - Explicitly define a constant as optional (constants which are not relevant except for specific V8 versions). This will help us improve reliability and debugability of llnode. --- src/constants.cc | 98 ++++++++++++++++++++++++++----------------- src/constants.h | 35 ++++++++++++++-- src/error.h | 1 + src/llv8-constants.cc | 12 +++--- src/llv8-constants.h | 4 +- src/llv8-inl.h | 17 ++++++-- src/llv8.h | 7 ++-- 7 files changed, 117 insertions(+), 57 deletions(-) diff --git a/src/constants.cc b/src/constants.cc index 09611dd3..c9cb66aa 100644 --- a/src/constants.cc +++ b/src/constants.cc @@ -1,4 +1,7 @@ #include +#include +#include +#include #include @@ -14,35 +17,28 @@ namespace llnode { template T ReadSymbolFromTarget(SBTarget& target, SBAddress& start, const char* name, - Error& err) { - SBError sberr; + SBError& sberr) { T res = 0; target.ReadMemory(start, &res, sizeof(T), sberr); - if (!sberr.Fail()) { - err = Error::Ok(); - } else { - err = Error::Failure("Failed to read symbol %s", name); - } return res; } -int64_t Constants::LookupConstant(SBTarget target, const char* name, - int64_t def, Error& err) { +std::pair Constants::LookupConstant(SBTarget target, + const char* name, + int64_t def) { int64_t res = 0; res = def; SBSymbolContextList context_list = target.FindSymbols(name); if (!context_list.IsValid() || context_list.GetSize() == 0) { - err = Error::Failure("Failed to find symbol %s", name); - return res; + return {res, false}; } SBSymbolContext context = context_list.GetContextAtIndex(0); SBSymbol symbol = context.GetSymbol(); if (!symbol.IsValid()) { - err = Error::Failure("Failed to fetch symbol %s from context", name); - return res; + return {res, false}; } SBAddress start = symbol.GetStartAddress(); @@ -50,23 +46,23 @@ int64_t Constants::LookupConstant(SBTarget target, const char* name, uint32_t size = end.GetOffset() - start.GetOffset(); // NOTE: size could be bigger for at the end symbols + SBError sberr; if (size >= 8) { - res = ReadSymbolFromTarget(target, start, name, err); + res = ReadSymbolFromTarget(target, start, name, sberr); } else if (size == 4) { - int32_t tmp = ReadSymbolFromTarget(target, start, name, err); + int32_t tmp = ReadSymbolFromTarget(target, start, name, sberr); res = static_cast(tmp); } else if (size == 2) { - int16_t tmp = ReadSymbolFromTarget(target, start, name, err); + int16_t tmp = ReadSymbolFromTarget(target, start, name, sberr); res = static_cast(tmp); } else if (size == 1) { - int8_t tmp = ReadSymbolFromTarget(target, start, name, err); + int8_t tmp = ReadSymbolFromTarget(target, start, name, sberr); res = static_cast(tmp); } else { - err = Error::Failure("Unexpected symbol size %" PRIu32 " of symbol %s", - size, name); + return {res, false}; } - return res; + return {res, !sberr.Fail()}; } void Constants::Assign(SBTarget target) { @@ -76,44 +72,68 @@ void Constants::Assign(SBTarget target) { int64_t Constants::LoadRawConstant(const char* name, int64_t def) { - Error err; - int64_t v = Constants::LookupConstant(target_, name, def, err); - if (err.Fail()) { + auto v = Constants::LookupConstant(target_, name, def); + if (!v.second) { PRINT_DEBUG("Failed to load raw constant %s, default to %" PRId64, name, def); } - return v; -} - -int64_t Constants::LoadConstant(const char* name, Error& err, int64_t def) { - int64_t v = Constants::LookupConstant( - target_, (constant_prefix() + name).c_str(), def, err); - return v; + return v.first; } int64_t Constants::LoadConstant(const char* name, int64_t def) { - Error err; - int64_t v = LoadConstant(name, err, def); - if (err.Fail()) { + auto v = Constants::LookupConstant(target_, + (constant_prefix() + name).c_str(), def); + if (!v.second) { PRINT_DEBUG("Failed to load constant %s, default to %" PRId64, name, def); } - return v; + return v.first; } int64_t Constants::LoadConstant(const char* name, const char* fallback, int64_t def) { - Error err; - int64_t v = LoadConstant(name, err, def); - if (err.Fail()) v = LoadConstant(fallback, err, def); - if (err.Fail()) { + auto v = Constants::LookupConstant(target_, + (constant_prefix() + name).c_str(), def); + if (!v.second) + v = Constants::LookupConstant(target_, + (constant_prefix() + fallback).c_str(), def); + if (!v.second) { PRINT_DEBUG("Failed to load constant %s, fallback %s, default to %" PRId64, name, fallback, def); } - return v; + return v.first; +} + +Constant Constants::LoadConstant( + std::initializer_list names) { + for (std::string name : names) { + auto v = Constants::LookupConstant(target_, + (constant_prefix() + name).c_str(), -1); + if (v.second) return Constant(v.first, name); + } + + if (Error::IsDebugMode()) { + std::string joined = ""; + for (std::string name : names) { + joined += (joined.empty() ? "'" : ", '") + name + "'"; + } + PRINT_DEBUG("Failed to load constants: %s", joined.c_str()); + } + + return Constant(); } +Constant Constants::LoadOptionalConstant( + std::initializer_list names, int def) { + for (std::string name : names) { + auto v = Constants::LookupConstant(target_, + (constant_prefix() + name).c_str(), -1); + if (v.second) return Constant(v.first, name); + } + + return Constant(def); +} } // namespace llnode diff --git a/src/constants.h b/src/constants.h index aefec402..75763b62 100644 --- a/src/constants.h +++ b/src/constants.h @@ -10,6 +10,33 @@ using lldb::SBTarget; namespace llnode { +template +class Constant { + public: + Constant() : value_(-1), valid_(false), loaded_(false) {} + explicit Constant(T value) + : value_(value), valid_(true), loaded_(false), name_("") {} + Constant(T value, std::string name) + : value_(value), valid_(true), loaded_(true), name_(name) {} + + inline bool Check() { return valid_; } + + inline bool Loaded() { return loaded_; } + + T operator*() { + // TODO(mmarchini): Check() + return value_; + } + + inline std::string name() { return name_; } + + protected: + T value_; + bool valid_; + bool loaded_; + std::string name_; +}; + #define CONSTANTS_DEFAULT_METHODS(NAME) \ inline NAME* operator()() { \ if (loaded_) return this; \ @@ -28,15 +55,17 @@ class Constants { inline virtual std::string constant_prefix() { return ""; }; - static int64_t LookupConstant(SBTarget target, const char* name, int64_t def, - Error& err); + static std::pair LookupConstant(SBTarget target, + const char* name, int64_t def); protected: int64_t LoadRawConstant(const char* name, int64_t def = -1); - int64_t LoadConstant(const char* name, Error& err, int64_t def = -1); int64_t LoadConstant(const char* name, int64_t def = -1); int64_t LoadConstant(const char* name, const char* fallback, int64_t def = -1); + Constant LoadConstant(std::initializer_list names); + Constant LoadOptionalConstant( + std::initializer_list names, int def); lldb::SBTarget target_; bool loaded_; diff --git a/src/error.h b/src/error.h index 2063e7ac..5c2db765 100644 --- a/src/error.h +++ b/src/error.h @@ -30,6 +30,7 @@ class Error { inline const char* GetMessage() { return msg_.c_str(); } static void SetDebugMode(bool mode) { is_debug_mode = mode; } + static bool IsDebugMode() { return is_debug_mode; } private: bool failed_; diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc index 124b6d50..4bddcd39 100644 --- a/src/llv8-constants.cc +++ b/src/llv8-constants.cc @@ -81,10 +81,9 @@ void HeapObject::Load() { void Map::Load() { Error err; - kInstanceAttrsOffset = - LoadConstant("class_Map__instance_attributes__int", err); - if (err.Fail()) { - kInstanceAttrsOffset = LoadConstant("class_Map__instance_type__uint16_t"); + kInstanceAttrsOffset = LoadConstant({"class_Map__instance_attributes__int", + "class_Map__instance_type__uint16_t"}); + if (kInstanceAttrsOffset.name() == "class_Map__instance_type__uint16_t") { kMapTypeMask = 0xffff; } else { kMapTypeMask = 0xff; @@ -93,8 +92,9 @@ void Map::Load() { kMaybeConstructorOffset = LoadConstant("class_Map__constructor_or_backpointer__Object", "class_Map__constructor__Object"); - kInstanceDescriptorsOffset = - LoadConstant("class_Map__instance_descriptors__DescriptorArray"); + kInstanceDescriptorsOffset = LoadConstant({ + "class_Map__instance_descriptors__DescriptorArray", + }); kBitField3Offset = LoadConstant("class_Map__bit_field3__int", "class_Map__bit_field3__SMI"); kInObjectPropertiesOffset = LoadConstant( diff --git a/src/llv8-constants.h b/src/llv8-constants.h index 52112b0b..7ebc8211 100644 --- a/src/llv8-constants.h +++ b/src/llv8-constants.h @@ -69,9 +69,9 @@ class Map : public Module { CONSTANTS_DEFAULT_METHODS(Map); int64_t kMapTypeMask; - int64_t kInstanceAttrsOffset; + Constant kInstanceAttrsOffset; int64_t kMaybeConstructorOffset; - int64_t kInstanceDescriptorsOffset; + Constant kInstanceDescriptorsOffset; int64_t kBitField3Offset; int64_t kInObjectPropertiesOffset; int64_t kInObjectPropertiesStartOffset; diff --git a/src/llv8-inl.h b/src/llv8-inl.h index b0fde5c3..0924ee97 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -165,9 +165,11 @@ inline bool HeapObject::IsJSErrorType(Error& err) { } +// TODO(mmarchini): return CheckedType inline int64_t Map::GetType(Error& err) { - int64_t type = - v8()->LoadUnsigned(LeaField(v8()->map()->kInstanceAttrsOffset), 2, err); + RETURN_IF_INVALID(v8()->map()->kInstanceAttrsOffset, -1); + int64_t type = v8()->LoadUnsigned( + LeaField(*(v8()->map()->kInstanceAttrsOffset)), 2, err); if (err.Fail()) return -1; return type & v8()->map()->kMapTypeMask; @@ -230,12 +232,19 @@ inline int64_t Map::NumberOfOwnDescriptors(Error& err) { return LoadFieldValue(v8()->OFF, err); \ } +#define SAFE_ACCESSOR(CLASS, METHOD, OFF, TYPE) \ + inline TYPE CLASS::METHOD(Error& err) { \ + if (!Check()) return TYPE(); \ + if (!v8()->OFF.Check()) return TYPE(); \ + return LoadFieldValue(*(v8()->OFF), err); \ + } + ACCESSOR(HeapObject, GetMap, heap_obj()->kMapOffset, HeapObject) ACCESSOR(Map, MaybeConstructor, map()->kMaybeConstructorOffset, HeapObject) -ACCESSOR(Map, InstanceDescriptors, map()->kInstanceDescriptorsOffset, - HeapObject) +SAFE_ACCESSOR(Map, InstanceDescriptors, map()->kInstanceDescriptorsOffset, + HeapObject) ACCESSOR(Symbol, Name, symbol()->kNameOffset, HeapObject) diff --git a/src/llv8.h b/src/llv8.h index afac08e4..5c8706c6 100644 --- a/src/llv8.h +++ b/src/llv8.h @@ -37,9 +37,10 @@ class CodeMap; NAME(Value* v) : PARENT(v->v8(), v->raw()) {} \ static inline const char* ClassName() { return #NAME; } -#define RETURN_IF_INVALID(var, ret) \ - if (!var.Check()) { \ - return ret; \ +#define RETURN_IF_INVALID(var, ret) \ + if (!var.Check()) { \ + PRINT_DEBUG("Unable to load variable %s correctly", #var); \ + return ret; \ } #define RETURN_IF_THIS_INVALID(ret) \ From a316d6d0065308004a722a697021506e2de636e4 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Thu, 3 Oct 2019 16:05:19 -0700 Subject: [PATCH 05/16] fix conflict use-check-properly and fix-inferred-name-fn-lookup --- src/llv8-inl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llv8-inl.h b/src/llv8-inl.h index 0924ee97..69e91b03 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -923,7 +923,7 @@ inline HeapObject ScopeInfo::MaybeFunctionName(Error& err) { FixedArray::Get(proper_index, err); if (err.Success() && String::IsString(v8(), maybe_function_name, err)) { likely_function_name = maybe_function_name; - if (String(likely_function_name).Length(err).GetValue() > 0) { + if (*String(likely_function_name).Length(err) > 0) { return likely_function_name; } } From cbd63be6bd11321fbcfa2d6e4917b2640b4960e7 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Thu, 3 Oct 2019 16:54:05 -0700 Subject: [PATCH 06/16] skip inferredName on 12 --- test/plugin/frame-test.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/test/plugin/frame-test.js b/test/plugin/frame-test.js index f4304e91..58908d15 100644 --- a/test/plugin/frame-test.js +++ b/test/plugin/frame-test.js @@ -86,7 +86,8 @@ tape('v8 stack', async (t) => { t.ok(//.test(exit), 'exit frame'); t.ok(/crasher/.test(crasher), 'crasher frame'); t.ok(//.test(adapter), 'arguments adapter frame'); - t.ok(/\sfnInferredName\(/.test(fnInferredName), 'fnInferredName frame'); + if (!process.version.startsWith("v12")) + t.ok(/\sfnInferredName\(/.test(fnInferredName), 'fnInferredName frame'); t.ok(/\sModule.fnInferredNamePrototype\(/.test(fnInferredNamePrototype), 'fnInferredNamePrototype frame'); t.ok(/\sfnFunctionName\(/.test(fnFunctionName), 'fnFunctionName frame'); @@ -111,14 +112,16 @@ tape('v8 stack', async (t) => { fatalError(t, sess, "Couldn't determine fnFunctionName's frame number"); } - const fnInferredNamePrototypeFrame = - fnInferredNamePrototype.match(/frame #([0-9]+)/)[1]; - if (fnInferredNamePrototypeFrame) { - await testFrameList(t, sess, fnInferredNamePrototypeFrame, - sourceCodes['fnInferredNamePrototype']); - } else { - fatalError(t, sess, - "Couldn't determine fnInferredNamePrototype's frame number"); + if (!process.version.startsWith("v12")) { + const fnInferredNamePrototypeFrame = + fnInferredNamePrototype.match(/frame #([0-9]+)/)[1]; + if (fnInferredNamePrototypeFrame) { + await testFrameList(t, sess, fnInferredNamePrototypeFrame, + sourceCodes['fnInferredNamePrototype']); + } else { + fatalError(t, sess, + "Couldn't determine fnInferredNamePrototype's frame number"); + } } const fnInferredNameFrame = fnInferredName.match(/frame #([0-9]+)/)[1]; From c177c4762ca2b4156f35da92e01ce0f568cbe1b3 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Fri, 4 Oct 2019 08:44:10 -0700 Subject: [PATCH 07/16] update UncompiledData types constants --- src/llv8-constants.cc | 12 ++++++++---- src/llv8-constants.h | 4 ++-- src/llv8-inl.h | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc index 4bddcd39..27c59cef 100644 --- a/src/llv8-constants.cc +++ b/src/llv8-constants.cc @@ -537,12 +537,16 @@ void Types::Load() { kJSDateType = LoadConstant("type_JSDate__JS_DATE_TYPE"); kSharedFunctionInfoType = LoadConstant("type_SharedFunctionInfo__SHARED_FUNCTION_INFO_TYPE"); - kUncompiledDataWithoutPreParsedScopeType = LoadConstant( + kUncompiledDataWithoutPreParsedScopeType = LoadConstant({ "type_UncompiledDataWithoutPreParsedScope__UNCOMPILED_DATA_WITHOUT_PRE_" - "PARSED_SCOPE_TYPE"); - kUncompiledDataWithPreParsedScopeType = LoadConstant( + "PARSED_SCOPE_TYPE", + "type_UncompiledDataWithoutPreparseData__UNCOMPILED_DATA_WITHOUT_" + "PREPARSE_DATA_TYPE"}); + kUncompiledDataWithPreParsedScopeType = LoadConstant({ "type_UncompiledDataWithPreParsedScope__UNCOMPILED_DATA_WITH_PRE_PARSED_" - "SCOPE_TYPE"); + "SCOPE_TYPE", + "type_UncompiledDataWithPreparseData__UNCOMPILED_DATA_WITH_" + "PREPARSE_DATA_TYPE"}); kScriptType = LoadConstant("type_Script__SCRIPT_TYPE"); kScopeInfoType = LoadConstant("type_ScopeInfo__SCOPE_INFO_TYPE"); kSymbolType = LoadConstant("type_Symbol__SYMBOL_TYPE"); diff --git a/src/llv8-constants.h b/src/llv8-constants.h index 7ebc8211..cf297419 100644 --- a/src/llv8-constants.h +++ b/src/llv8-constants.h @@ -524,8 +524,8 @@ class Types : public Module { int64_t kJSRegExpType; int64_t kJSDateType; int64_t kSharedFunctionInfoType; - int64_t kUncompiledDataWithoutPreParsedScopeType; - int64_t kUncompiledDataWithPreParsedScopeType; + Constant kUncompiledDataWithoutPreParsedScopeType; + Constant kUncompiledDataWithPreParsedScopeType; int64_t kScriptType; int64_t kScopeInfoType; int64_t kSymbolType; diff --git a/src/llv8-inl.h b/src/llv8-inl.h index 69e91b03..cd00e0d9 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -144,8 +144,8 @@ inline bool Value::IsUncompiledData(Error& err) { int64_t type = heap_object.GetType(err); if (err.Fail()) return false; - return type == v8()->types()->kUncompiledDataWithoutPreParsedScopeType || - type == v8()->types()->kUncompiledDataWithPreParsedScopeType; + return type == *v8()->types()->kUncompiledDataWithoutPreParsedScopeType || + type == *v8()->types()->kUncompiledDataWithPreParsedScopeType; } From 98058436aaf8511976475c0f53e7e330ef6233bd Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Mon, 7 Oct 2019 15:23:11 -0700 Subject: [PATCH 08/16] update DescriptorArray stuff --- src/llscan.cc | 2 +- src/llv8-constants.cc | 10 ++++-- src/llv8-constants.h | 8 +++-- src/llv8-inl.h | 73 +++++++++++++++++++++++++++++++++++-------- src/llv8.cc | 31 ++++++++++++------ src/llv8.h | 2 +- src/printer.cc | 62 +++++++++++++++++++++++++----------- 7 files changed, 139 insertions(+), 49 deletions(-) diff --git a/src/llscan.cc b/src/llscan.cc index 6755d57a..1bc1afee 100644 --- a/src/llscan.cc +++ b/src/llscan.cc @@ -1563,7 +1563,7 @@ bool FindJSObjectsVisitor::MapCacheEntry::Load(v8::Map map, if (is_histogram) type_name = heap_object.GetTypeName(err); v8::HeapObject descriptors_obj = map.InstanceDescriptors(err); - if (err.Fail()) return false; + RETURN_IF_INVALID(descriptors_obj, false); v8::DescriptorArray descriptors(descriptors_obj); own_descriptors_count_ = map.NumberOfOwnDescriptors(err); diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc index 27c59cef..2bed257c 100644 --- a/src/llv8-constants.cc +++ b/src/llv8-constants.cc @@ -94,6 +94,7 @@ void Map::Load() { "class_Map__constructor__Object"); kInstanceDescriptorsOffset = LoadConstant({ "class_Map__instance_descriptors__DescriptorArray", + "class_Map__instance_descriptors_offset", }); kBitField3Offset = LoadConstant("class_Map__bit_field3__int", "class_Map__bit_field3__SMI"); @@ -402,7 +403,7 @@ void JSArrayBufferView::Load() { void DescriptorArray::Load() { - kDetailsOffset = LoadConstant("prop_desc_details"); + kDetailsOffset = LoadConstant({"prop_desc_details"}); kKeyOffset = LoadConstant("prop_desc_key"); kValueOffset = LoadConstant("prop_desc_value"); @@ -454,8 +455,11 @@ void DescriptorArray::Load() { kRepresentationDouble = 7; } - kFirstIndex = LoadConstant("prop_idx_first"); - kSize = LoadConstant("prop_desc_size"); + // NOTE(mmarchini): removed from V8 7.2. + // https://github.com/v8/v8/commit/1ad0cd5 + kFirstIndex = LoadOptionalConstant({"prop_idx_first"}, 0); + kSize = LoadConstant({"prop_desc_size"}); + kHeaderSize = LoadOptionalConstant({"class_DescriptorArray__header_size__uintptr_t"}, 0); } diff --git a/src/llv8-constants.h b/src/llv8-constants.h index cf297419..33cc1d5c 100644 --- a/src/llv8-constants.h +++ b/src/llv8-constants.h @@ -406,7 +406,7 @@ class DescriptorArray : public Module { public: CONSTANTS_DEFAULT_METHODS(DescriptorArray); - int64_t kDetailsOffset; + Constant kDetailsOffset; int64_t kKeyOffset; int64_t kValueOffset; @@ -417,8 +417,10 @@ class DescriptorArray : public Module { int64_t kRepresentationDouble; - int64_t kFirstIndex; - int64_t kSize; + Constant kFirstIndex; + Constant kHeaderSize; + Constant kSize; + Constant kEntrySize; // node.js <= 7 int64_t kPropertyTypeMask = -1; diff --git a/src/llv8-inl.h b/src/llv8-inl.h index cd00e0d9..af4b46f7 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -271,6 +271,7 @@ inline bool Context::IsContext(LLV8* v8, HeapObject heap_object, Error& err) { } inline int64_t Map::InObjectProperties(Error& err) { + RETURN_IF_THIS_INVALID(-1); if (!IsJSObjectMap(err)) { err = Error::Failure( "Invalid call to Map::InObjectProperties with a non-JsObject type"); @@ -724,25 +725,71 @@ inline T FixedArray::Get(int index, Error& err) { return LoadFieldValue(off, err); } -inline Smi DescriptorArray::GetDetails(int index, Error& err) { - return Get(v8()->descriptor_array()->kFirstIndex + - index * v8()->descriptor_array()->kSize + - v8()->descriptor_array()->kDetailsOffset, - err); +inline Smi DescriptorArray::GetDetails(int index) { + // TODO(mmarchini): shouldn't need Error here. + Error err; + RETURN_IF_INVALID(v8()->descriptor_array()->kFirstIndex, Smi()); + RETURN_IF_INVALID(v8()->descriptor_array()->kSize, Smi()); + RETURN_IF_INVALID(v8()->descriptor_array()->kDetailsOffset, Smi()); + + index = index * *(v8()->descriptor_array()->kSize); + if (v8()->descriptor_array()->kFirstIndex.Loaded()) { + return Get(*(v8()->descriptor_array()->kFirstIndex) + + index + + *(v8()->descriptor_array()->kDetailsOffset), + err); + } else if (v8()->descriptor_array()->kHeaderSize.Loaded()) { + index *= v8()->common()->kPointerSize; + index += *(v8()->descriptor_array()->kHeaderSize); + index += (v8()->common()->kPointerSize * *(v8()->descriptor_array()->kDetailsOffset)); + return LoadFieldValue(index, err); + } else { + PRINT_DEBUG("Missing FirstIndex and HeaderSize constants, can't get key from DescriptorArray"); + return Smi(); + } } inline Value DescriptorArray::GetKey(int index, Error& err) { - return Get(v8()->descriptor_array()->kFirstIndex + - index * v8()->descriptor_array()->kSize + - v8()->descriptor_array()->kKeyOffset, - err); + RETURN_IF_INVALID(v8()->descriptor_array()->kFirstIndex, Smi()); + RETURN_IF_INVALID(v8()->descriptor_array()->kSize, Smi()); + + index = index * *(v8()->descriptor_array()->kSize); + if (v8()->descriptor_array()->kFirstIndex.Loaded()) { + // TODO(mmarchini): check on `Get` + return Get(*(v8()->descriptor_array()->kFirstIndex) + + index + + v8()->descriptor_array()->kKeyOffset, + err); + } else if (v8()->descriptor_array()->kHeaderSize.Loaded()) { + index *= v8()->common()->kPointerSize; + index += *(v8()->descriptor_array()->kHeaderSize); + index += v8()->descriptor_array()->kKeyOffset; + return LoadFieldValue(index, err); + } else { + PRINT_DEBUG("Missing FirstIndex and HeaderSize constants, can't get key from DescriptorArray"); + return Value(); + } } inline Value DescriptorArray::GetValue(int index, Error& err) { - return Get(v8()->descriptor_array()->kFirstIndex + - index * v8()->descriptor_array()->kSize + - v8()->descriptor_array()->kValueOffset, - err); + RETURN_IF_INVALID(v8()->descriptor_array()->kFirstIndex, Smi()); + RETURN_IF_INVALID(v8()->descriptor_array()->kSize, Smi()); + + index = index * *(v8()->descriptor_array()->kSize); + if (v8()->descriptor_array()->kFirstIndex.Loaded()) { + return Get(*(v8()->descriptor_array()->kFirstIndex) + + index + + v8()->descriptor_array()->kValueOffset, + err); + } else if (v8()->descriptor_array()->kHeaderSize.Loaded()) { + index *= v8()->common()->kPointerSize; + index += *(v8()->descriptor_array()->kHeaderSize); + index += (v8()->common()->kPointerSize * v8()->descriptor_array()->kValueOffset); + return LoadFieldValue(index, err); + } else { + PRINT_DEBUG("Missing FirstIndex and HeaderSize constants, can't get key from DescriptorArray"); + return Value(); + } } inline bool DescriptorArray::IsDescriptorDetails(Smi details) { diff --git a/src/llv8.cc b/src/llv8.cc index b783a2d2..937483be 100644 --- a/src/llv8.cc +++ b/src/llv8.cc @@ -571,6 +571,8 @@ std::string Value::GetTypeName(Error& err) { std::string Value::ToString(Error& err) { + RETURN_IF_THIS_INVALID(std::string()); + Smi smi(this); if (smi.Check()) return smi.ToString(err); @@ -922,7 +924,7 @@ std::vector> JSObject::DictionaryEntries(Error& err) { std::vector> JSObject::DescriptorEntries(Map map, Error& err) { HeapObject descriptors_obj = map.InstanceDescriptors(err); - if (err.Fail()) return {}; + RETURN_IF_INVALID(descriptors_obj, {}); DescriptorArray descriptors(descriptors_obj); @@ -942,8 +944,12 @@ std::vector> JSObject::DescriptorEntries(Map map, std::vector> entries; for (int64_t i = 0; i < own_descriptors_count; i++) { - Smi details = descriptors.GetDetails(i, err); - if (err.Fail()) continue; + Smi details = descriptors.GetDetails(i); + if (!details.Check()) { + PRINT_DEBUG("Failed to get details for index %ld", i); + entries.push_back(std::pair(Value(), Value())); + continue; + } Value key = descriptors.GetKey(i, err); if (err.Fail()) continue; @@ -1037,15 +1043,19 @@ void JSObject::DictionaryKeys(std::vector& keys, Error& err) { void JSObject::DescriptorKeys(std::vector& keys, Map map, Error& err) { HeapObject descriptors_obj = map.InstanceDescriptors(err); - if (err.Fail()) return; + RETURN_IF_INVALID(descriptors_obj, ); DescriptorArray descriptors(descriptors_obj); int64_t own_descriptors_count = map.NumberOfOwnDescriptors(err); if (err.Fail()) return; for (int64_t i = 0; i < own_descriptors_count; i++) { - Smi details = descriptors.GetDetails(i, err); - if (err.Fail()) return; + Smi details = descriptors.GetDetails(i); + if (!details.Check()) { + PRINT_DEBUG("Failed to get details for index %ld", i); + keys.push_back("???"); + continue; + } Value key = descriptors.GetKey(i, err); if (err.Fail()) return; @@ -1124,7 +1134,7 @@ Value JSObject::GetDictionaryProperty(std::string key_name, Error& err) { Value JSObject::GetDescriptorProperty(std::string key_name, Map map, Error& err) { HeapObject descriptors_obj = map.InstanceDescriptors(err); - if (err.Fail()) return Value(); + RETURN_IF_INVALID(descriptors_obj, Value()); DescriptorArray descriptors(descriptors_obj); int64_t own_descriptors_count = map.NumberOfOwnDescriptors(err); @@ -1142,8 +1152,11 @@ Value JSObject::GetDescriptorProperty(std::string key_name, Map map, FixedArray extra_properties(extra_properties_obj); for (int64_t i = 0; i < own_descriptors_count; i++) { - Smi details = descriptors.GetDetails(i, err); - if (err.Fail()) return Value(); + Smi details = descriptors.GetDetails(i); + if (!details.Check()) { + PRINT_DEBUG("Failed to get details for index %ld", i); + continue; + } Value key = descriptors.GetKey(i, err); if (err.Fail()) return Value(); diff --git a/src/llv8.h b/src/llv8.h index 5c8706c6..6083c1e6 100644 --- a/src/llv8.h +++ b/src/llv8.h @@ -455,7 +455,7 @@ class DescriptorArray : public FixedArray { public: V8_VALUE_DEFAULT_METHODS(DescriptorArray, FixedArray) - inline Smi GetDetails(int index, Error& err); + inline Smi GetDetails(int index); inline Value GetKey(int index, Error& err); // NOTE: Only for DATA_CONSTANT diff --git a/src/printer.cc b/src/printer.cc index 507e5c3b..086c5fb5 100644 --- a/src/printer.cc +++ b/src/printer.cc @@ -467,9 +467,7 @@ std::string Printer::Stringify(v8::JSArrayBufferView js_array_buffer_view, template <> std::string Printer::Stringify(v8::Map map, Error& err) { - v8::HeapObject descriptors_obj = map.InstanceDescriptors(err); - if (err.Fail()) return std::string(); - + // TODO(mmarchini): don't fail if can't load NumberOfOwnDescriptors int64_t own_descriptors_count = map.NumberOfOwnDescriptors(err); if (err.Fail()) return std::string(); @@ -492,25 +490,41 @@ std::string Printer::Stringify(v8::Map map, Error& err) { char tmp[256]; std::stringstream ss; ss << rang::fg::yellow - << "(own_descriptors_count), in_object_properties_or_constructor.c_str(), static_cast(in_object_properties_or_constructor_index), - static_cast(instance_size), descriptors_obj.raw()); + static_cast(instance_size)); if (!options_.detailed) { return std::string(tmp) + ">"; } - v8::DescriptorArray descriptors(descriptors_obj); - if (err.Fail()) return std::string(); + if (descriptors_obj.Check()) { + v8::DescriptorArray descriptors(descriptors_obj); + if (err.Fail()) return std::string(); - return std::string(tmp) + ":" + Stringify(descriptors, err) + - ">"; + return std::string(tmp) + ":" + Stringify(descriptors, err) + + ">"; + } else { + std::string(tmp) + ">"; + } } template <> @@ -965,7 +979,7 @@ std::string Printer::StringifyDictionary(v8::JSObject js_object, Error& err) { std::string Printer::StringifyDescriptors(v8::JSObject js_object, v8::Map map, Error& err) { v8::HeapObject descriptors_obj = map.InstanceDescriptors(err); - if (err.Fail()) return std::string(); + RETURN_IF_INVALID(descriptors_obj, std::string()); v8::DescriptorArray descriptors(descriptors_obj); int64_t own_descriptors_count = map.NumberOfOwnDescriptors(err); @@ -987,21 +1001,31 @@ std::string Printer::StringifyDescriptors(v8::JSObject js_object, v8::Map map, std::string res; std::stringstream ss; for (int64_t i = 0; i < own_descriptors_count; i++) { - v8::Smi details = descriptors.GetDetails(i, err); - if (err.Fail()) return std::string(); + if (!res.empty()) res += ",\n"; v8::Value key = descriptors.GetKey(i, err); - if (err.Fail()) return std::string(); - - if (!res.empty()) res += ",\n"; ss.str(""); ss.clear(); - ss << rang::style::bold << rang::fg::yellow << " ." + key.ToString(err) - << rang::fg::reset << rang::style::reset; + ss << rang::style::bold << rang::fg::yellow << " ."; + if (key.Check()) { + ss << key.ToString(err); + } else { + PRINT_DEBUG("Failed to get key for index %ld", i); + ss << "???"; + } + ss << rang::fg::reset << rang::style::reset; + res += ss.str() + "="; if (err.Fail()) return std::string(); + v8::Smi details = descriptors.GetDetails(i); + if (!details.Check()) { + PRINT_DEBUG("Failed to get details for index %ld", i); + res += "???"; + continue; + } + if (descriptors.IsConstFieldDetails(details) || descriptors.IsDescriptorDetails(details)) { v8::Value value; From ac7cc2bf3eb14049fd1b7a225677b7ffe747c429 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Mon, 7 Oct 2019 15:31:31 -0700 Subject: [PATCH 09/16] wip JSArrayBuffer and FixedTypedArray stuff --- src/llv8-constants.cc | 50 ++++++++++++++------ src/llv8-constants.h | 17 ++++--- src/llv8-inl.h | 105 ++++++++++++++++++++++++++++++++++++++---- src/llv8.cc | 15 ------ src/llv8.h | 22 ++++++--- src/printer.cc | 66 +++++++++++++------------- 6 files changed, 192 insertions(+), 83 deletions(-) diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc index 2bed257c..1927d792 100644 --- a/src/llv8-constants.cc +++ b/src/llv8-constants.cc @@ -350,10 +350,10 @@ void FixedTypedArrayBase::Load() { kBasePointerOffset = LoadConstant("class_FixedTypedArrayBase__base_pointer__Object"); kExternalPointerOffset = - LoadConstant("class_FixedTypedArrayBase__external_pointer__Object"); + LoadConstant({"class_FixedTypedArrayBase__external_pointer__Object", + "class_FixedTypedArrayBase__external_pointer__uintptr_t"}); } - void Oddball::Load() { kKindOffset = LoadConstant("class_Oddball__kind_offset__int"); @@ -369,19 +369,15 @@ void Oddball::Load() { void JSArrayBuffer::Load() { kBackingStoreOffset = - LoadConstant("class_JSArrayBuffer__backing_store__Object"); - kByteLengthOffset = LoadConstant("class_JSArrayBuffer__byte_length__Object"); - - // v4 compatibility fix - if (kBackingStoreOffset == -1) { - common_->Load(); + LoadConstant({"class_JSArrayBuffer__backing_store__Object", + "class_JSArrayBuffer__backing_store__uintptr_t"}); + kByteLengthOffset = + LoadConstant({"class_JSArrayBuffer__byte_length__Object", + "class_JSArrayBuffer__byte_length__size_t"}); - kBackingStoreOffset = kByteLengthOffset + common_->kPointerSize; + if (kBackingStoreOffset.Check()) { } - kBitFieldOffset = kBackingStoreOffset + common_->kPointerSize; - if (common_->kPointerSize == 8) kBitFieldOffset += 4; - kWasNeuteredMask = LoadConstant("jsarray_buffer_was_neutered_mask"); kWasNeuteredShift = LoadConstant("jsarray_buffer_was_neutered_shift"); @@ -393,14 +389,40 @@ void JSArrayBuffer::Load() { } +bool JSArrayBuffer::IsByteLengthScalar() { + return kByteLengthOffset.name() == "class_JSArrayBuffer__byte_length__size_t"; +} + +Constant JSArrayBuffer::BitFieldOffset() { + if (!kBackingStoreOffset.Check()) + return Constant(); + + common_->Load(); + int64_t kBitFieldOffset = *kBackingStoreOffset + common_->kPointerSize; + if (common_->kPointerSize == 8) kBitFieldOffset += 4; + return Constant(kBitFieldOffset); +} + + void JSArrayBufferView::Load() { kBufferOffset = LoadConstant("class_JSArrayBufferView__buffer__Object"); kByteOffsetOffset = - LoadConstant("class_JSArrayBufferView__raw_byte_offset__Object"); + LoadConstant({"class_JSArrayBufferView__raw_byte_offset__Object", + "class_JSArrayBufferView__byte_offset__size_t"}); kByteLengthOffset = - LoadConstant("class_JSArrayBufferView__raw_byte_length__Object"); + LoadConstant({"class_JSArrayBufferView__raw_byte_length__Object", + "class_JSArrayBufferView__byte_length__size_t"}); +} + +bool JSArrayBufferView::IsByteLengthScalar() { + PRINT_DEBUG("%s", kByteLengthOffset.name().c_str()); + return kByteLengthOffset.name() == "class_JSArrayBufferView__byte_length__size_t"; } +bool JSArrayBufferView::IsByteOffsetScalar() { + PRINT_DEBUG("%s", kByteOffsetOffset.name().c_str()); + return kByteOffsetOffset.name() == "class_JSArrayBufferView__byte_offset__size_t"; +} void DescriptorArray::Load() { kDetailsOffset = LoadConstant({"prop_desc_details"}); diff --git a/src/llv8-constants.h b/src/llv8-constants.h index 33cc1d5c..15d6635b 100644 --- a/src/llv8-constants.h +++ b/src/llv8-constants.h @@ -349,7 +349,7 @@ class FixedTypedArrayBase : public Module { CONSTANTS_DEFAULT_METHODS(FixedTypedArrayBase); int64_t kBasePointerOffset; - int64_t kExternalPointerOffset; + Constant kExternalPointerOffset; protected: void Load(); @@ -379,13 +379,15 @@ class JSArrayBuffer : public Module { int64_t kKindOffset; - int64_t kBackingStoreOffset; - int64_t kByteLengthOffset; - int64_t kBitFieldOffset; + Constant kBackingStoreOffset; + Constant kByteLengthOffset; int64_t kWasNeuteredMask; int64_t kWasNeuteredShift; + Constant BitFieldOffset(); + bool IsByteLengthScalar(); + protected: void Load(); }; @@ -395,8 +397,11 @@ class JSArrayBufferView : public Module { CONSTANTS_DEFAULT_METHODS(JSArrayBufferView); int64_t kBufferOffset; - int64_t kByteOffsetOffset; - int64_t kByteLengthOffset; + Constant kByteOffsetOffset; + Constant kByteLengthOffset; + + bool IsByteLengthScalar(); + bool IsByteOffsetScalar(); protected: void Load(); diff --git a/src/llv8-inl.h b/src/llv8-inl.h index af4b46f7..60f2a9dd 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -7,6 +7,33 @@ namespace llnode { namespace v8 { +using lldb::SBError; +using lldb::addr_t; + +template +inline std::string CheckedType::ToString(const char* fmt) { + if (!Check()) return "???"; + + char buf[20]; + snprintf(buf, sizeof(buf), fmt, val_); + return std::string(buf); +} + +template +inline CheckedType LLV8::LoadUnsigned(int64_t addr, uint32_t byte_size) { + SBError sberr; + int64_t value = process_.ReadUnsignedFromMemory(static_cast(addr), + byte_size, sberr); + + if (sberr.Fail()) { + PRINT_DEBUG("Failed to load unsigned from v8 memory. Reason: %s", + sberr.GetCString()); + return CheckedType(); + } + + return CheckedType(value); +} + template <> inline double LLV8::LoadValue(int64_t addr, Error& err) { return LoadDouble(addr, err); @@ -67,6 +94,14 @@ inline int64_t HeapObject::LoadField(int64_t off, Error& err) { } +template +inline CheckedType HeapObject::LoadCheckedField(Constant off) { + RETURN_IF_THIS_INVALID(CheckedType()); + RETURN_IF_INVALID(off, CheckedType()); + return v8()->LoadUnsigned(LeaField(*off), 8); +} + + template <> inline int32_t HeapObject::LoadFieldValue(int64_t off, Error& err) { return v8()->LoadValue(LeaField(off), err); @@ -499,21 +534,69 @@ inline int64_t Code::Size(Error& err) { ACCESSOR(Oddball, Kind, oddball()->kKindOffset, Smi) -inline int64_t JSArrayBuffer::BackingStore(Error& err) { - return LoadField(v8()->js_array_buffer()->kBackingStoreOffset, err); +inline CheckedType JSArrayBuffer::BackingStore() { + RETURN_IF_THIS_INVALID(CheckedType()); + + return LoadCheckedField(v8()->js_array_buffer()->kBackingStoreOffset); +} + +inline CheckedType JSArrayBuffer::ByteLength() { + RETURN_IF_THIS_INVALID(CheckedType()); + + if (!v8()->js_array_buffer()->IsByteLengthScalar()) { + Error err; + Smi len = byte_length(err); + RETURN_IF_INVALID(len, CheckedType()); + + return CheckedType(len.GetValue()); + } + + return LoadCheckedField(v8()->js_array_buffer()->kByteLengthOffset); } -inline int64_t JSArrayBuffer::BitField(Error& err) { - return LoadField(v8()->js_array_buffer()->kBitFieldOffset, err) & 0xffffffff; +inline CheckedType JSArrayBuffer::BitField() { + RETURN_IF_THIS_INVALID(CheckedType()); + CheckedType bit_fields = LoadCheckedField(v8()->js_array_buffer()->BitFieldOffset()); + RETURN_IF_INVALID(bit_fields, CheckedType()); + return CheckedType(*bit_fields & 0xffffffff); } -ACCESSOR(JSArrayBuffer, ByteLength, js_array_buffer()->kByteLengthOffset, Smi) +SAFE_ACCESSOR(JSArrayBuffer, byte_length, js_array_buffer()->kByteLengthOffset, Smi) ACCESSOR(JSArrayBufferView, Buffer, js_array_buffer_view()->kBufferOffset, JSArrayBuffer) -ACCESSOR(JSArrayBufferView, ByteOffset, + +inline CheckedType JSArrayBufferView::ByteLength() { + RETURN_IF_THIS_INVALID(CheckedType()); + + if (!v8()->js_array_buffer_view()->IsByteLengthScalar()) { + Error err; + Smi len = byte_length(err); + RETURN_IF_INVALID(len, CheckedType()); + + return CheckedType(len.GetValue()); + } + + return LoadCheckedField(v8()->js_array_buffer_view()->kByteLengthOffset); +} + +inline CheckedType JSArrayBufferView::ByteOffset() { + RETURN_IF_THIS_INVALID(CheckedType()); + + if (!v8()->js_array_buffer_view()->IsByteOffsetScalar()) { + Error err; + Smi len = byte_offset(err); + RETURN_IF_INVALID(len, CheckedType()); + + return CheckedType(len.GetValue()); + } + + return LoadCheckedField(v8()->js_array_buffer_view()->kByteOffsetOffset); +} + +SAFE_ACCESSOR(JSArrayBufferView, byte_offset, js_array_buffer_view()->kByteOffsetOffset, Smi) -ACCESSOR(JSArrayBufferView, ByteLength, +SAFE_ACCESSOR(JSArrayBufferView, byte_length, js_array_buffer_view()->kByteLengthOffset, Smi) inline ScopeInfo::PositionInfo ScopeInfo::MaybePositionInfo(Error& err) { @@ -637,7 +720,7 @@ inline int64_t FixedTypedArrayBase::GetBase(Error& err) { } inline int64_t FixedTypedArrayBase::GetExternal(Error& err) { - return LoadField(v8()->fixed_typed_array_base()->kExternalPointerOffset, err); + return LoadField(*v8()->fixed_typed_array_base()->kExternalPointerOffset, err); } inline std::string OneByteString::ToString(Error& err) { @@ -1002,10 +1085,12 @@ inline bool Oddball::IsHole(Error& err) { return kind.GetValue() == v8()->oddball()->kTheHole; } +// TODO(mmarchini): return CheckedType inline bool JSArrayBuffer::WasNeutered(Error& err) { - int64_t field = BitField(err); - if (err.Fail()) return false; + CheckedType bit_field = BitField(); + RETURN_IF_INVALID(bit_field, false); + int64_t field = *bit_field; field &= v8()->js_array_buffer()->kWasNeuteredMask; field >>= v8()->js_array_buffer()->kWasNeuteredShift; return field != 0; diff --git a/src/llv8.cc b/src/llv8.cc index 937483be..237ee9ce 100644 --- a/src/llv8.cc +++ b/src/llv8.cc @@ -80,21 +80,6 @@ int64_t LLV8::LoadPtr(int64_t addr, Error& err) { return value; } -template -CheckedType LLV8::LoadUnsigned(int64_t addr, uint32_t byte_size) { - SBError sberr; - int64_t value = process_.ReadUnsignedFromMemory(static_cast(addr), - byte_size, sberr); - - if (sberr.Fail()) { - PRINT_DEBUG("Failed to load unsigned from v8 memory. Reason: %s", - sberr.GetCString()); - return CheckedType(); - } - - return CheckedType(value); -} - int64_t LLV8::LoadUnsigned(int64_t addr, uint32_t byte_size, Error& err) { SBError sberr; int64_t value = process_.ReadUnsignedFromMemory(static_cast(addr), diff --git a/src/llv8.h b/src/llv8.h index 6083c1e6..cc31f317 100644 --- a/src/llv8.h +++ b/src/llv8.h @@ -60,6 +60,8 @@ class CheckedType { } inline bool Check() const { return valid_; } + inline std::string ToString(const char* fmt); + private: T val_; bool valid_; @@ -117,6 +119,9 @@ class HeapObject : public Value { inline int64_t LeaField(int64_t off) const; inline int64_t LoadField(int64_t off, Error& err); + template + inline CheckedType LoadCheckedField(Constant off); + template inline T LoadFieldValue(int64_t off, Error& err); @@ -562,11 +567,13 @@ class JSArrayBuffer : public JSObject { public: V8_VALUE_DEFAULT_METHODS(JSArrayBuffer, JSObject) - inline int64_t BackingStore(Error& err); - inline int64_t BitField(Error& err); - inline Smi ByteLength(Error& err); + inline CheckedType BackingStore(); + inline CheckedType BitField(); + inline CheckedType ByteLength(); inline bool WasNeutered(Error& err); + private: + inline Smi byte_length(Error& err); }; class JSArrayBufferView : public JSObject { @@ -574,8 +581,11 @@ class JSArrayBufferView : public JSObject { V8_VALUE_DEFAULT_METHODS(JSArrayBufferView, JSObject) inline JSArrayBuffer Buffer(Error& err); - inline Smi ByteOffset(Error& err); - inline Smi ByteLength(Error& err); + inline CheckedType ByteOffset(); + inline CheckedType ByteLength(); + private: + inline Smi byte_offset(Error& err); + inline Smi byte_length(Error& err); }; class JSFrame : public Value { @@ -614,7 +624,7 @@ class LLV8 { int64_t LoadConstant(const char* name); int64_t LoadPtr(int64_t addr, Error& err); template - CheckedType LoadUnsigned(int64_t addr, uint32_t byte_size); + inline CheckedType LoadUnsigned(int64_t addr, uint32_t byte_size); int64_t LoadUnsigned(int64_t addr, uint32_t byte_size, Error& err); double LoadDouble(int64_t addr, Error& err); std::string LoadBytes(int64_t addr, int64_t length, Error& err); diff --git a/src/printer.cc b/src/printer.cc index 086c5fb5..c274b93d 100644 --- a/src/printer.cc +++ b/src/printer.cc @@ -345,18 +345,13 @@ std::string Printer::Stringify(v8::JSArrayBuffer js_array_buffer, Error& err) { return ss.str(); } - int64_t data = js_array_buffer.BackingStore(err); - if (err.Fail()) return std::string(); - - v8::Smi length = js_array_buffer.ByteLength(err); - if (err.Fail()) return std::string(); - - int byte_length = static_cast(length.GetValue()); + v8::CheckedType data = js_array_buffer.BackingStore(); + v8::CheckedType byte_length = js_array_buffer.ByteLength(); char tmp[128]; - snprintf(tmp, sizeof(tmp), - "(byte_length, options_.length); - res += llv8_->LoadBytes(data, display_length, err); + int display_length = std::min(*byte_length, options_.length); + res += llv8_->LoadBytes(*data, display_length, err); - if (display_length < byte_length) { - res += " ..."; + if (display_length < *byte_length) { + res += " ..."; + } + + res += "\n]"; } - res += "\n]"; ss.str(""); ss.clear(); ss << res << rang::fg::reset; @@ -404,10 +404,12 @@ std::string Printer::Stringify(v8::JSArrayBufferView js_array_buffer_view, return ss.str().c_str(); } - int64_t data = buf.BackingStore(err); - if (err.Fail()) return std::string(); + v8::CheckedType data = buf.BackingStore(); + // TODO(mmarchini): be more lenient to failed load + RETURN_IF_INVALID(data, std::string()); - if (data == 0) { + if (*data == 0) { + PRINT_DEBUG("Is this the real life"); // The backing store has not been materialized yet. v8::HeapObject elements_obj = js_array_buffer_view.Elements(err); if (err.Fail()) return std::string(); @@ -416,22 +418,22 @@ std::string Printer::Stringify(v8::JSArrayBufferView js_array_buffer_view, if (err.Fail()) return std::string(); int64_t external = elements.GetExternal(err); if (err.Fail()) return std::string(); - data = base + external; + data = v8::CheckedType(base + external); + PRINT_DEBUG("Is this just fantasy"); } - v8::Smi off = js_array_buffer_view.ByteOffset(err); - if (err.Fail()) return std::string(); + v8::CheckedType byte_offset = js_array_buffer_view.ByteOffset(); + RETURN_IF_INVALID(byte_offset, std::string()); - v8::Smi length = js_array_buffer_view.ByteLength(err); - if (err.Fail()) return std::string(); + v8::CheckedType byte_length = js_array_buffer_view.ByteLength(); + RETURN_IF_INVALID(byte_length, std::string()); - int byte_length = static_cast(length.GetValue()); - int byte_offset = static_cast(off.GetValue()); char tmp[128]; snprintf(tmp, sizeof(tmp), - "(byte_length, options_.length); - res += llv8_->LoadBytes(data + byte_offset, display_length, err); + int display_length = std::min(*byte_length, options_.length); + res += llv8_->LoadBytes(*data + *byte_offset, display_length, err); - if (display_length < byte_length) { + if (display_length < *byte_length) { res += " ..."; } From 2429df38b271dcc2944856213d87727c666d47e5 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Mon, 7 Oct 2019 15:32:50 -0700 Subject: [PATCH 10/16] fix inspect-test function source regex --- test/plugin/inspect-test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/plugin/inspect-test.js b/test/plugin/inspect-test.js index 35792f53..bab48ab1 100644 --- a/test/plugin/inspect-test.js +++ b/test/plugin/inspect-test.js @@ -48,11 +48,9 @@ const hashMapTests = { // Include 'source:' and '>' to act as boundaries. (Avoid // passing if the whole file it displayed instead of just // the function we want.) - const arrowSource = 'source:\n' + - 'function c.hashmap.(anonymous function)(a,b)=>{a+b}\n' + - '>'; + const arrowSource = /source:\nfunction c.hashmap.(\(anonymous function\)|)\(a,b\)=>{a\+b}\n>/; - t.ok(lines.includes(arrowSource), + t.ok(lines.match(arrowSource), 'hashmap[25] should have the correct function source'); cb(null); }); From 1f53dfdc8be1fac8e85a2fe02699d447767899a2 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Tue, 8 Oct 2019 09:56:09 -0700 Subject: [PATCH 11/16] test: update Error.stack test for V8 7.5 V8 7.5 changed how it caches the stringified stack on Error objects after the first access. Instead of replacing the accessor Error.stack with the stringified stack, the stringified stack is stored in the same Symbol which was used to store the FrameArray stack. Ref: https://github.com/v8/v8/commit/c8206043e1afb6d179a68bb5a8c079de5e --- test/plugin/inspect-test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/plugin/inspect-test.js b/test/plugin/inspect-test.js index bab48ab1..0a59c9b9 100644 --- a/test/plugin/inspect-test.js +++ b/test/plugin/inspect-test.js @@ -171,7 +171,11 @@ const hashMapTests = { if (err) return cb(err); lines = lines.join('\n'); - let strStackMatch = lines.match(/stack=(0x[0-9a-f]+): Date: Tue, 8 Oct 2019 11:26:49 -0700 Subject: [PATCH 12/16] fixup! update DescriptorArray stuff --- src/llscan.cc | 4 +-- src/llv8-constants.cc | 4 +-- src/llv8-constants.h | 4 +-- src/llv8-inl.h | 66 ++++++++++++------------------------------- src/llv8.cc | 20 ++++++------- src/llv8.h | 7 +++-- src/printer.cc | 5 ++-- 7 files changed, 42 insertions(+), 68 deletions(-) diff --git a/src/llscan.cc b/src/llscan.cc index 1bc1afee..7162878c 100644 --- a/src/llscan.cc +++ b/src/llscan.cc @@ -1579,8 +1579,8 @@ bool FindJSObjectsVisitor::MapCacheEntry::Load(v8::Map map, } for (uint64_t i = 0; i < own_descriptors_count_; i++) { - v8::Value key = descriptors.GetKey(i, err); - if (err.Fail()) continue; + v8::Value key = descriptors.GetKey(i); + if (!key.Check()) continue; properties_.emplace_back(key.ToString(err)); } diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc index 1927d792..e03abaeb 100644 --- a/src/llv8-constants.cc +++ b/src/llv8-constants.cc @@ -426,8 +426,8 @@ bool JSArrayBufferView::IsByteOffsetScalar() { void DescriptorArray::Load() { kDetailsOffset = LoadConstant({"prop_desc_details"}); - kKeyOffset = LoadConstant("prop_desc_key"); - kValueOffset = LoadConstant("prop_desc_value"); + kKeyOffset = LoadConstant({"prop_desc_key"}); + kValueOffset = LoadConstant({"prop_desc_value"}); kPropertyIndexMask = LoadConstant("prop_index_mask"); kPropertyIndexShift = LoadConstant("prop_index_shift"); diff --git a/src/llv8-constants.h b/src/llv8-constants.h index 15d6635b..4f57012d 100644 --- a/src/llv8-constants.h +++ b/src/llv8-constants.h @@ -412,8 +412,8 @@ class DescriptorArray : public Module { CONSTANTS_DEFAULT_METHODS(DescriptorArray); Constant kDetailsOffset; - int64_t kKeyOffset; - int64_t kValueOffset; + Constant kKeyOffset; + Constant kValueOffset; int64_t kPropertyIndexMask; int64_t kPropertyIndexShift; diff --git a/src/llv8-inl.h b/src/llv8-inl.h index 60f2a9dd..4d9ca3be 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -808,71 +808,41 @@ inline T FixedArray::Get(int index, Error& err) { return LoadFieldValue(off, err); } -inline Smi DescriptorArray::GetDetails(int index) { +template +inline T DescriptorArray::Get(int index, int64_t offset) { // TODO(mmarchini): shouldn't need Error here. Error err; - RETURN_IF_INVALID(v8()->descriptor_array()->kFirstIndex, Smi()); - RETURN_IF_INVALID(v8()->descriptor_array()->kSize, Smi()); - RETURN_IF_INVALID(v8()->descriptor_array()->kDetailsOffset, Smi()); + RETURN_IF_INVALID(v8()->descriptor_array()->kSize, T()); index = index * *(v8()->descriptor_array()->kSize); if (v8()->descriptor_array()->kFirstIndex.Loaded()) { - return Get(*(v8()->descriptor_array()->kFirstIndex) + - index + - *(v8()->descriptor_array()->kDetailsOffset), + return FixedArray::Get(*(v8()->descriptor_array()->kFirstIndex) + + index + offset, err); } else if (v8()->descriptor_array()->kHeaderSize.Loaded()) { index *= v8()->common()->kPointerSize; index += *(v8()->descriptor_array()->kHeaderSize); - index += (v8()->common()->kPointerSize * *(v8()->descriptor_array()->kDetailsOffset)); - return LoadFieldValue(index, err); + index += (v8()->common()->kPointerSize * offset); + return LoadFieldValue(index, err); } else { PRINT_DEBUG("Missing FirstIndex and HeaderSize constants, can't get key from DescriptorArray"); - return Smi(); + return T(); } } -inline Value DescriptorArray::GetKey(int index, Error& err) { - RETURN_IF_INVALID(v8()->descriptor_array()->kFirstIndex, Smi()); - RETURN_IF_INVALID(v8()->descriptor_array()->kSize, Smi()); - - index = index * *(v8()->descriptor_array()->kSize); - if (v8()->descriptor_array()->kFirstIndex.Loaded()) { - // TODO(mmarchini): check on `Get` - return Get(*(v8()->descriptor_array()->kFirstIndex) + - index + - v8()->descriptor_array()->kKeyOffset, - err); - } else if (v8()->descriptor_array()->kHeaderSize.Loaded()) { - index *= v8()->common()->kPointerSize; - index += *(v8()->descriptor_array()->kHeaderSize); - index += v8()->descriptor_array()->kKeyOffset; - return LoadFieldValue(index, err); - } else { - PRINT_DEBUG("Missing FirstIndex and HeaderSize constants, can't get key from DescriptorArray"); - return Value(); - } +inline Smi DescriptorArray::GetDetails(int index) { + RETURN_IF_INVALID(v8()->descriptor_array()->kDetailsOffset, Smi()); + return Get(index, *v8()->descriptor_array()->kDetailsOffset); } -inline Value DescriptorArray::GetValue(int index, Error& err) { - RETURN_IF_INVALID(v8()->descriptor_array()->kFirstIndex, Smi()); - RETURN_IF_INVALID(v8()->descriptor_array()->kSize, Smi()); +inline Value DescriptorArray::GetKey(int index) { + RETURN_IF_INVALID(v8()->descriptor_array()->kKeyOffset, Value()); + return Get(index, *v8()->descriptor_array()->kKeyOffset); +} - index = index * *(v8()->descriptor_array()->kSize); - if (v8()->descriptor_array()->kFirstIndex.Loaded()) { - return Get(*(v8()->descriptor_array()->kFirstIndex) + - index + - v8()->descriptor_array()->kValueOffset, - err); - } else if (v8()->descriptor_array()->kHeaderSize.Loaded()) { - index *= v8()->common()->kPointerSize; - index += *(v8()->descriptor_array()->kHeaderSize); - index += (v8()->common()->kPointerSize * v8()->descriptor_array()->kValueOffset); - return LoadFieldValue(index, err); - } else { - PRINT_DEBUG("Missing FirstIndex and HeaderSize constants, can't get key from DescriptorArray"); - return Value(); - } +inline Value DescriptorArray::GetValue(int index) { + RETURN_IF_INVALID(v8()->descriptor_array()->kValueOffset, Value()); + return Get(index, *v8()->descriptor_array()->kValueOffset); } inline bool DescriptorArray::IsDescriptorDetails(Smi details) { diff --git a/src/llv8.cc b/src/llv8.cc index 237ee9ce..35ee6cd7 100644 --- a/src/llv8.cc +++ b/src/llv8.cc @@ -936,15 +936,15 @@ std::vector> JSObject::DescriptorEntries(Map map, continue; } - Value key = descriptors.GetKey(i, err); - if (err.Fail()) continue; + Value key = descriptors.GetKey(i); + if (!key.Check()) continue; if (descriptors.IsConstFieldDetails(details) || descriptors.IsDescriptorDetails(details)) { Value value; - value = descriptors.GetValue(i, err); - if (err.Fail()) continue; + value = descriptors.GetValue(i); + if (!value.Check()) continue; entries.push_back(std::pair(key, value)); continue; @@ -1042,8 +1042,8 @@ void JSObject::DescriptorKeys(std::vector& keys, Map map, continue; } - Value key = descriptors.GetKey(i, err); - if (err.Fail()) return; + Value key = descriptors.GetKey(i); + RETURN_IF_INVALID(key, ); // Skip non-fields for now, Object.keys(obj) does // not seem to return these (for example the "length" @@ -1143,8 +1143,8 @@ Value JSObject::GetDescriptorProperty(std::string key_name, Map map, continue; } - Value key = descriptors.GetKey(i, err); - if (err.Fail()) return Value(); + Value key = descriptors.GetKey(i); + RETURN_IF_INVALID(key, Value()); if (key.ToString(err) != key_name) { continue; @@ -1157,8 +1157,8 @@ Value JSObject::GetDescriptorProperty(std::string key_name, Map map, descriptors.IsDescriptorDetails(details)) { Value value; - value = descriptors.GetValue(i, err); - if (err.Fail()) return Value(); + value = descriptors.GetValue(i); + RETURN_IF_INVALID(value, Value()); continue; } diff --git a/src/llv8.h b/src/llv8.h index cc31f317..354f2a6c 100644 --- a/src/llv8.h +++ b/src/llv8.h @@ -460,11 +460,14 @@ class DescriptorArray : public FixedArray { public: V8_VALUE_DEFAULT_METHODS(DescriptorArray, FixedArray) + template + inline T Get(int index, int64_t offset); + inline Smi GetDetails(int index); - inline Value GetKey(int index, Error& err); + inline Value GetKey(int index); // NOTE: Only for DATA_CONSTANT - inline Value GetValue(int index, Error& err); + inline Value GetValue(int index); inline bool IsFieldDetails(Smi details); inline bool IsDescriptorDetails(Smi details); diff --git a/src/printer.cc b/src/printer.cc index c274b93d..64c38e52 100644 --- a/src/printer.cc +++ b/src/printer.cc @@ -1005,7 +1005,7 @@ std::string Printer::StringifyDescriptors(v8::JSObject js_object, v8::Map map, for (int64_t i = 0; i < own_descriptors_count; i++) { if (!res.empty()) res += ",\n"; - v8::Value key = descriptors.GetKey(i, err); + v8::Value key = descriptors.GetKey(i); ss.str(""); ss.clear(); @@ -1032,7 +1032,8 @@ std::string Printer::StringifyDescriptors(v8::JSObject js_object, v8::Map map, descriptors.IsDescriptorDetails(details)) { v8::Value value; - value = descriptors.GetValue(i, err); + value = descriptors.GetValue(i); + RETURN_IF_INVALID(value, std::string()); if (err.Fail()) return std::string(); res += printer.Stringify(value, err); From d6183b7b3106d28c8cd1da5e5b892ccdecf4509f Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Tue, 8 Oct 2019 13:24:40 -0700 Subject: [PATCH 13/16] fix jstypedarray v8 7.6 --- src/llv8-constants.cc | 19 ++++++++++-- src/llv8-constants.h | 15 ++++++++- src/llv8-inl.h | 71 ++++++++++++++++++++++++++++++++++++++++--- src/llv8.cc | 1 + src/llv8.h | 18 +++++++++-- src/printer.cc | 29 +++++------------- 6 files changed, 122 insertions(+), 31 deletions(-) diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc index e03abaeb..6a394ae9 100644 --- a/src/llv8-constants.cc +++ b/src/llv8-constants.cc @@ -348,10 +348,23 @@ void FixedArray::Load() { void FixedTypedArrayBase::Load() { kBasePointerOffset = - LoadConstant("class_FixedTypedArrayBase__base_pointer__Object"); + LoadOptionalConstant({"class_FixedTypedArrayBase__base_pointer__Object"}, 0); kExternalPointerOffset = - LoadConstant({"class_FixedTypedArrayBase__external_pointer__Object", - "class_FixedTypedArrayBase__external_pointer__uintptr_t"}); + LoadOptionalConstant({"class_FixedTypedArrayBase__external_pointer__Object", + "class_FixedTypedArrayBase__external_pointer__uintptr_t"}, 0); +} + +void JSTypedArray::Load() { + kBasePointerOffset = + LoadOptionalConstant({"class_JSTypedArray__base_pointer__Object"}, 0); + kExternalPointerOffset = + LoadOptionalConstant({"class_JSTypedArray__external_pointer__uintptr_t"}, 0); +} + +// TODO(mmarchini): proper validation so that we log an error if neither classes +// were able to load their constants. +bool JSTypedArray::IsDataPointerInJSTypedArray() { + return kBasePointerOffset.Loaded() && kExternalPointerOffset.Loaded(); } void Oddball::Load() { diff --git a/src/llv8-constants.h b/src/llv8-constants.h index 4f57012d..dbe8b44d 100644 --- a/src/llv8-constants.h +++ b/src/llv8-constants.h @@ -348,13 +348,26 @@ class FixedTypedArrayBase : public Module { public: CONSTANTS_DEFAULT_METHODS(FixedTypedArrayBase); - int64_t kBasePointerOffset; + Constant kBasePointerOffset; Constant kExternalPointerOffset; protected: void Load(); }; +class JSTypedArray : public Module { + public: + CONSTANTS_DEFAULT_METHODS(JSTypedArray); + + Constant kBasePointerOffset; + Constant kExternalPointerOffset; + + bool IsDataPointerInJSTypedArray(); + + protected: + void Load(); +}; + class Oddball : public Module { public: CONSTANTS_DEFAULT_METHODS(Oddball); diff --git a/src/llv8-inl.h b/src/llv8-inl.h index 4d9ca3be..9e31e53c 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -599,6 +599,69 @@ SAFE_ACCESSOR(JSArrayBufferView, byte_offset, SAFE_ACCESSOR(JSArrayBufferView, byte_length, js_array_buffer_view()->kByteLengthOffset, Smi) +inline CheckedType JSTypedArray::base() { + return LoadCheckedField(v8()->js_typed_array()->kBasePointerOffset); +} + +inline CheckedType JSTypedArray::external() { + return LoadCheckedField(v8()->js_typed_array()->kExternalPointerOffset); +} + +inline CheckedType JSTypedArray::GetExternal() { + if (v8()->js_typed_array()->IsDataPointerInJSTypedArray()) { + PRINT_DEBUG("OHALO"); + return external(); + } else { + PRINT_DEBUG("NAY"); + // TODO(mmarchini): don't rely on Error + Error err; + v8::HeapObject elements_obj = Elements(err); + RETURN_IF_INVALID(elements_obj, CheckedType()); + v8::FixedTypedArrayBase elements(elements_obj); + return elements.GetExternal(); + } +} + +inline CheckedType JSTypedArray::GetBase() { + if (v8()->js_typed_array()->IsDataPointerInJSTypedArray()) { + PRINT_DEBUG("ALOHA"); + return base(); + } else { + PRINT_DEBUG("NEY"); + // TODO(mmarchini): don't rely on Error + Error err; + v8::HeapObject elements_obj = Elements(err); + RETURN_IF_INVALID(elements_obj, CheckedType()); + v8::FixedTypedArrayBase elements(elements_obj); + return elements.GetBase(); + } +} + +inline CheckedType JSTypedArray::GetData() { + // TODO(mmarchini): don't rely on Error + Error err; + v8::JSArrayBuffer buf = Buffer(err); + if (err.Fail()) return CheckedType(); + + v8::CheckedType data = buf.BackingStore(); + // TODO(mmarchini): be more lenient to failed load + RETURN_IF_INVALID(data, CheckedType()); + + if (*data == 0) { + // The backing store has not been materialized yet. + + CheckedType base = GetBase(); + RETURN_IF_INVALID(base, v8::CheckedType()); + + CheckedType external = GetExternal(); + RETURN_IF_INVALID(external, v8::CheckedType()); + + data = v8::CheckedType(*base + *external); + } + return data; +} + + inline ScopeInfo::PositionInfo ScopeInfo::MaybePositionInfo(Error& err) { ScopeInfo::PositionInfo position_info = { .start_position = 0, .end_position = 0, .is_valid = false}; @@ -715,12 +778,12 @@ ACCESSOR(ThinString, Actual, thin_string()->kActualOffset, String); ACCESSOR(FixedArrayBase, Length, fixed_array_base()->kLengthOffset, Smi); -inline int64_t FixedTypedArrayBase::GetBase(Error& err) { - return LoadField(v8()->fixed_typed_array_base()->kBasePointerOffset, err); +inline CheckedType FixedTypedArrayBase::GetBase() { + return LoadCheckedField(v8()->fixed_typed_array_base()->kBasePointerOffset); } -inline int64_t FixedTypedArrayBase::GetExternal(Error& err) { - return LoadField(*v8()->fixed_typed_array_base()->kExternalPointerOffset, err); +inline CheckedType FixedTypedArrayBase::GetExternal() { + return LoadCheckedField(v8()->fixed_typed_array_base()->kExternalPointerOffset); } inline std::string OneByteString::ToString(Error& err) { diff --git a/src/llv8.cc b/src/llv8.cc index 35ee6cd7..5d316271 100644 --- a/src/llv8.cc +++ b/src/llv8.cc @@ -53,6 +53,7 @@ void LLV8::Load(SBTarget target) { fixed_array_base.Assign(target, &common); fixed_array.Assign(target, &common); fixed_typed_array_base.Assign(target, &common); + js_typed_array.Assign(target, &common); oddball.Assign(target, &common); js_array_buffer.Assign(target, &common); js_array_buffer_view.Assign(target, &common); diff --git a/src/llv8.h b/src/llv8.h index 354f2a6c..e1ba3af7 100644 --- a/src/llv8.h +++ b/src/llv8.h @@ -452,8 +452,8 @@ class FixedTypedArrayBase : public FixedArrayBase { public: V8_VALUE_DEFAULT_METHODS(FixedTypedArrayBase, FixedArrayBase) - inline int64_t GetBase(Error& err); - inline int64_t GetExternal(Error& err); + inline CheckedType GetBase(); + inline CheckedType GetExternal(); }; class DescriptorArray : public FixedArray { @@ -591,6 +591,18 @@ class JSArrayBufferView : public JSObject { inline Smi byte_length(Error& err); }; +class JSTypedArray : public JSArrayBufferView { + public: + V8_VALUE_DEFAULT_METHODS(JSTypedArray, JSArrayBufferView) + + inline CheckedType GetExternal(); + inline CheckedType GetBase(); + inline CheckedType GetData(); + private: + inline CheckedType external(); + inline CheckedType base(); +}; + class JSFrame : public Value { public: V8_VALUE_DEFAULT_METHODS(JSFrame, Value) @@ -660,6 +672,7 @@ class LLV8 { constants::ThinString thin_string; constants::FixedArrayBase fixed_array_base; constants::FixedTypedArrayBase fixed_typed_array_base; + constants::JSTypedArray js_typed_array; constants::FixedArray fixed_array; constants::Oddball oddball; constants::JSArrayBuffer js_array_buffer; @@ -695,6 +708,7 @@ class LLV8 { friend class FixedArrayBase; friend class FixedArray; friend class FixedTypedArrayBase; + friend class JSTypedArray; friend class DescriptorArray; friend class NameDictionary; friend class Context; diff --git a/src/printer.cc b/src/printer.cc index 64c38e52..e53f59c9 100644 --- a/src/printer.cc +++ b/src/printer.cc @@ -390,9 +390,10 @@ std::string Printer::Stringify(v8::JSArrayBuffer js_array_buffer, Error& err) { template <> -std::string Printer::Stringify(v8::JSArrayBufferView js_array_buffer_view, +std::string Printer::Stringify(v8::JSTypedArray js_typed_array, Error& err) { - v8::JSArrayBuffer buf = js_array_buffer_view.Buffer(err); + // TODO(mmarchini): shouldn't need to fetch buffer here + v8::JSArrayBuffer buf = js_typed_array.Buffer(err); if (err.Fail()) return std::string(); bool neutered = buf.WasNeutered(err); @@ -404,28 +405,14 @@ std::string Printer::Stringify(v8::JSArrayBufferView js_array_buffer_view, return ss.str().c_str(); } - v8::CheckedType data = buf.BackingStore(); + v8::CheckedType data = js_typed_array.GetData(); // TODO(mmarchini): be more lenient to failed load RETURN_IF_INVALID(data, std::string()); - if (*data == 0) { - PRINT_DEBUG("Is this the real life"); - // The backing store has not been materialized yet. - v8::HeapObject elements_obj = js_array_buffer_view.Elements(err); - if (err.Fail()) return std::string(); - v8::FixedTypedArrayBase elements(elements_obj); - int64_t base = elements.GetBase(err); - if (err.Fail()) return std::string(); - int64_t external = elements.GetExternal(err); - if (err.Fail()) return std::string(); - data = v8::CheckedType(base + external); - PRINT_DEBUG("Is this just fantasy"); - } - - v8::CheckedType byte_offset = js_array_buffer_view.ByteOffset(); + v8::CheckedType byte_offset = js_typed_array.ByteOffset(); RETURN_IF_INVALID(byte_offset, std::string()); - v8::CheckedType byte_length = js_array_buffer_view.ByteLength(); + v8::CheckedType byte_length = js_typed_array.ByteLength(); RETURN_IF_INVALID(byte_length, std::string()); char tmp[128]; @@ -767,8 +754,8 @@ std::string Printer::Stringify(v8::HeapObject heap_object, Error& err) { } if (type == llv8_->types()->kJSTypedArrayType) { - v8::JSArrayBufferView view(heap_object); - return pre + Stringify(view, err); + v8::JSTypedArray typed_array(heap_object); + return pre + Stringify(typed_array, err); } if (type == llv8_->types()->kJSDateType) { From aba3126daa782f40c40462abe432f6e7025bf977 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Tue, 8 Oct 2019 16:26:27 -0700 Subject: [PATCH 14/16] handle String__FIELD_offset__int --- src/llv8-constants.cc | 8 ++++---- src/llv8-constants.h | 8 ++++---- src/llv8-inl.h | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc index 6a394ae9..ff7d006d 100644 --- a/src/llv8-constants.cc +++ b/src/llv8-constants.cc @@ -322,18 +322,18 @@ void TwoByteString::Load() { void ConsString::Load() { - kFirstOffset = LoadConstant("class_ConsString__first__String"); - kSecondOffset = LoadConstant("class_ConsString__second__String"); + kFirstOffset = LoadConstant({"class_ConsString__first__String", "class_ConsString__first_offset__int"}); + kSecondOffset = LoadConstant({"class_ConsString__second__String", "class_ConsString__second_offset__int"}); } void SlicedString::Load() { kParentOffset = LoadConstant("class_SlicedString__parent__String"); - kOffsetOffset = LoadConstant("class_SlicedString__offset__SMI"); + kOffsetOffset = LoadConstant({"class_SlicedString__offset__SMI", "class_SlicedString__offset_offset__int"}); } void ThinString::Load() { - kActualOffset = LoadConstant("class_ThinString__actual__String"); + kActualOffset = LoadConstant({"class_ThinString__actual__String", "class_ThinString__actual_offset__int"}); } void FixedArrayBase::Load() { diff --git a/src/llv8-constants.h b/src/llv8-constants.h index dbe8b44d..9c31daf2 100644 --- a/src/llv8-constants.h +++ b/src/llv8-constants.h @@ -296,8 +296,8 @@ class ConsString : public Module { public: CONSTANTS_DEFAULT_METHODS(ConsString); - int64_t kFirstOffset; - int64_t kSecondOffset; + Constant kFirstOffset; + Constant kSecondOffset; protected: void Load(); @@ -308,7 +308,7 @@ class SlicedString : public Module { CONSTANTS_DEFAULT_METHODS(SlicedString); int64_t kParentOffset; - int64_t kOffsetOffset; + Constant kOffsetOffset; protected: void Load(); @@ -318,7 +318,7 @@ class ThinString : public Module { public: CONSTANTS_DEFAULT_METHODS(ThinString); - int64_t kActualOffset; + Constant kActualOffset; protected: void Load(); diff --git a/src/llv8-inl.h b/src/llv8-inl.h index 9e31e53c..d9b5aebd 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -768,13 +768,13 @@ ACCESSOR(JSFunction, Info, js_function()->kSharedInfoOffset, SharedFunctionInfo); ACCESSOR(JSFunction, GetContext, js_function()->kContextOffset, HeapObject); -ACCESSOR(ConsString, First, cons_string()->kFirstOffset, String); -ACCESSOR(ConsString, Second, cons_string()->kSecondOffset, String); +SAFE_ACCESSOR(ConsString, First, cons_string()->kFirstOffset, String); +SAFE_ACCESSOR(ConsString, Second, cons_string()->kSecondOffset, String); ACCESSOR(SlicedString, Parent, sliced_string()->kParentOffset, String); -ACCESSOR(SlicedString, Offset, sliced_string()->kOffsetOffset, Smi); +SAFE_ACCESSOR(SlicedString, Offset, sliced_string()->kOffsetOffset, Smi); -ACCESSOR(ThinString, Actual, thin_string()->kActualOffset, String); +SAFE_ACCESSOR(ThinString, Actual, thin_string()->kActualOffset, String); ACCESSOR(FixedArrayBase, Length, fixed_array_base()->kLengthOffset, Smi); From 49de4be3538703e265a052bb33a0edb0406f5eb6 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Tue, 8 Oct 2019 16:34:05 -0700 Subject: [PATCH 15/16] make symbol more resilient --- src/llv8-constants.cc | 2 +- src/llv8-constants.h | 2 +- src/llv8-inl.h | 2 +- src/llv8.cc | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc index ff7d006d..bf748f4e 100644 --- a/src/llv8-constants.cc +++ b/src/llv8-constants.cc @@ -543,7 +543,7 @@ void Frame::Load() { void Symbol::Load() { - kNameOffset = LoadConstant("class_Symbol__name__Object"); + kNameOffset = LoadConstant({"class_Symbol__name__Object"}); } diff --git a/src/llv8-constants.h b/src/llv8-constants.h index 9c31daf2..7d3775ec 100644 --- a/src/llv8-constants.h +++ b/src/llv8-constants.h @@ -509,7 +509,7 @@ class Symbol : public Module { public: CONSTANTS_DEFAULT_METHODS(Symbol); - int64_t kNameOffset; + Constant kNameOffset; protected: void Load(); diff --git a/src/llv8-inl.h b/src/llv8-inl.h index d9b5aebd..e341b09a 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -281,7 +281,7 @@ ACCESSOR(Map, MaybeConstructor, map()->kMaybeConstructorOffset, HeapObject) SAFE_ACCESSOR(Map, InstanceDescriptors, map()->kInstanceDescriptorsOffset, HeapObject) -ACCESSOR(Symbol, Name, symbol()->kNameOffset, HeapObject) +SAFE_ACCESSOR(Symbol, Name, symbol()->kNameOffset, HeapObject) inline int64_t Map::BitField3(Error& err) { return v8()->LoadUnsigned(LeaField(v8()->map()->kBitField3Offset), 4, err); diff --git a/src/llv8.cc b/src/llv8.cc index 5d316271..f70e18e5 100644 --- a/src/llv8.cc +++ b/src/llv8.cc @@ -729,6 +729,7 @@ std::string Symbol::ToString(Error& err) { return "Symbol()"; } HeapObject name = Name(err); + RETURN_IF_INVALID(name, "Symbol(???)"); return "Symbol('" + String(name).ToString(err) + "')"; } From 581dde8dbe68ddd13f69c68684e69c6aaaebba3c Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Tue, 8 Oct 2019 17:18:03 -0700 Subject: [PATCH 16/16] fixup! skip inferredName on 12 --- test/common.js | 7 +++++++ test/plugin/frame-test.js | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/test/common.js b/test/common.js index 9fd9d36b..662d193d 100644 --- a/test/common.js +++ b/test/common.js @@ -330,3 +330,10 @@ Session.prototype.hasSymbol = function hasSymbol(symbol, callback) { } }); }; + +function nodejsVersion() { + const version = process.version.substring(1, process.version.indexOf('-')); + const versionArray = version.split('.').map(s => Number(s)); + return versionArray; +} +exports.nodejsVersion = nodejsVersion; diff --git a/test/plugin/frame-test.js b/test/plugin/frame-test.js index 58908d15..7766b8a2 100644 --- a/test/plugin/frame-test.js +++ b/test/plugin/frame-test.js @@ -5,6 +5,7 @@ const { promisify } = require('util'); const tape = require('tape'); const common = require('../common'); +const { nodejsVersion } = common; const sourceCodes = { "fnFunctionName": [ @@ -86,7 +87,7 @@ tape('v8 stack', async (t) => { t.ok(//.test(exit), 'exit frame'); t.ok(/crasher/.test(crasher), 'crasher frame'); t.ok(//.test(adapter), 'arguments adapter frame'); - if (!process.version.startsWith("v12")) + if (nodejsVersion()[0] < 12) t.ok(/\sfnInferredName\(/.test(fnInferredName), 'fnInferredName frame'); t.ok(/\sModule.fnInferredNamePrototype\(/.test(fnInferredNamePrototype), 'fnInferredNamePrototype frame'); @@ -112,7 +113,7 @@ tape('v8 stack', async (t) => { fatalError(t, sess, "Couldn't determine fnFunctionName's frame number"); } - if (!process.version.startsWith("v12")) { + if (nodejsVersion()[0] < 12) { const fnInferredNamePrototypeFrame = fnInferredNamePrototype.match(/frame #([0-9]+)/)[1]; if (fnInferredNamePrototypeFrame) {