Skip to content

Commit bb0d400

Browse files
Matheus Marchinimmarchini
authored andcommitted
src: print stack for error objects
Error objects have a .stack property, which is actually an accessor. The stack is stored in a unnamed symbol property and converted to a string when .stack is accessed in JavaScript. This patch implements the accessor behavior to print the error stack when inspecting an Error object. Fixes: #218 PR-URL: #233 Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Colin Ihrig <[email protected]>
1 parent 2fc1571 commit bb0d400

File tree

4 files changed

+143
-9
lines changed

4 files changed

+143
-9
lines changed

src/llv8-inl.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,22 @@ inline int64_t HeapObject::GetType(Error& err) {
8484
}
8585

8686

87+
inline bool HeapObject::IsJSErrorType(Error& err) {
88+
int64_t type = GetType(err);
89+
if (err.Fail()) return false;
90+
if (type == v8()->types()->kJSErrorType) return true;
91+
92+
// NOTE (mmarchini): We don't have a JSErrorType constant on Node.js v6.x,
93+
// thus we try to guess if the object is an Error object by checking if its
94+
// name is Error. Should work most of the time.
95+
if (!JSObject::IsObjectType(v8(), type)) return false;
96+
97+
JSObject obj(this);
98+
std::string type_name = obj.GetTypeName(err);
99+
return err.Success() && type_name == "Error";
100+
}
101+
102+
87103
inline int64_t Map::GetType(Error& err) {
88104
int64_t type =
89105
v8()->LoadUnsigned(LeaField(v8()->map()->kInstanceAttrsOffset), 2, err);

src/llv8.cc

Lines changed: 109 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,11 @@ std::string HeapObject::Inspect(InspectOptions* options, Error& err) {
827827
return pre + m.Inspect(options, err);
828828
}
829829

830+
if (IsJSErrorType(err)) {
831+
JSError error(this);
832+
return pre + error.Inspect(options, err);
833+
}
834+
830835
if (JSObject::IsObjectType(v8(), type)) {
831836
JSObject o(this);
832837
return pre + o.Inspect(options, err);
@@ -1495,22 +1500,31 @@ std::string JSObject::Inspect(InspectOptions* options, Error& err) {
14951500
int64_t constructor_type = constructor_obj.GetType(err);
14961501
if (err.Fail()) return std::string();
14971502

1503+
std::stringstream output;
14981504
if (constructor_type != v8()->types()->kJSFunctionType) {
1499-
std::stringstream ss;
1500-
ss << rang::fg::yellow << "<Object: no constructor>" << rang::fg::reset;
1501-
return ss.str().c_str();
1505+
output << rang::fg::yellow << "<Object: " << rang::fg::gray
1506+
<< "no constructor" << rang::fg::yellow << ">" << rang::fg::reset;
1507+
return output.str();
15021508
}
15031509

15041510
JSFunction constructor(constructor_obj);
15051511

1506-
std::string res = "<Object: " + constructor.Name(err);
1512+
output << rang::fg::yellow << "<Object: " << constructor.Name(err);
15071513
if (err.Fail()) return std::string();
15081514

1515+
output << InspectAllProperties(options, err) << rang::fg::yellow << ">"
1516+
<< rang::fg::reset;
1517+
return output.str();
1518+
}
1519+
1520+
std::string JSObject::InspectAllProperties(InspectOptions* options,
1521+
Error& err) {
1522+
std::string res = std::string();
15091523
// Print properties in detailed mode
15101524
if (options->detailed) {
15111525
std::stringstream ss;
15121526
ss << rang::fg::magenta << res << rang::fg::reset;
1513-
res = ss.str().c_str();
1527+
res = ss.str();
15141528

15151529
res += " " + InspectProperties(err);
15161530
if (err.Fail()) return std::string();
@@ -1523,15 +1537,101 @@ std::string JSObject::Inspect(InspectOptions* options, Error& err) {
15231537
ss << rang::fg::magenta << "\n internal fields" << rang::fg::reset
15241538
<< " {" << std::endl
15251539
<< fields << "}";
1526-
res += ss.str().c_str();
1540+
res += ss.str();
15271541
}
1528-
return res + ">";
1542+
return res;
15291543
} else {
15301544
std::stringstream ss;
1531-
ss << rang::fg::yellow << res << ">" << rang::fg::reset;
1532-
res = ss.str().c_str();
1545+
ss << rang::fg::yellow << res << rang::fg::reset;
1546+
res = ss.str();
15331547
return res;
15341548
}
1549+
1550+
return res;
1551+
}
1552+
1553+
std::string JSError::InspectAllProperties(InspectOptions* options, Error& err) {
1554+
std::string res = JSObject::InspectAllProperties(options, err);
1555+
if (options->detailed) {
1556+
InspectOptions simple;
1557+
1558+
// TODO (mmarchini): once we have Symbol support we'll need to search for
1559+
// <unnamed symbol>, since the stack symbol doesn't have an external name.
1560+
// In the future we can add postmortem metadata on V8 regarding existing
1561+
// symbols, but for now we'll use an heuristic to find the stack in the
1562+
// error object.
1563+
Value maybe_stack = GetProperty("<non-string>", err);
1564+
1565+
if (err.Fail()) {
1566+
Error::PrintInDebugMode(
1567+
"Couldn't find a symbol property in the Error object.");
1568+
return res;
1569+
}
1570+
1571+
int64_t type = HeapObject(maybe_stack).GetType(err);
1572+
1573+
if (err.Fail()) {
1574+
Error::PrintInDebugMode("Symbol property references an invalid object.");
1575+
return res;
1576+
}
1577+
1578+
// NOTE (mmarchini): The stack is stored as a JSArray
1579+
if (type != v8()->types()->kJSArrayType) {
1580+
Error::PrintInDebugMode("Symbol property doesn't have the right type.");
1581+
return res;
1582+
}
1583+
1584+
JSArray arr(maybe_stack);
1585+
1586+
Value maybe_stack_len = arr.GetArrayElement(0, err);
1587+
1588+
if (err.Fail()) {
1589+
Error::PrintInDebugMode(
1590+
"Couldn't get the first element from the stack array");
1591+
return res;
1592+
}
1593+
1594+
int64_t stack_len = Smi(maybe_stack_len).GetValue();
1595+
1596+
int multiplier = 5;
1597+
// On Node.js v8.x, the first array element is the stack size, and each
1598+
// stack frame use 5 elements.
1599+
if ((stack_len * multiplier + 1) != arr.GetArrayLength(err)) {
1600+
// On Node.js v6.x, the first array element is zero, and each stack frame
1601+
// use 4 element.
1602+
multiplier = 4;
1603+
if ((stack_len != 0) ||
1604+
((arr.GetArrayLength(err) - 1) % multiplier != 0)) {
1605+
Error::PrintInDebugMode(
1606+
"JSArray doesn't look like a Stack Frames array. stack_len: %lld "
1607+
"array_len: %lld",
1608+
stack_len, arr.GetArrayLength(err));
1609+
return res;
1610+
}
1611+
stack_len = (arr.GetArrayLength(err) - 1) / multiplier;
1612+
}
1613+
1614+
std::stringstream error_stack;
1615+
error_stack << std::endl
1616+
<< rang::fg::red << " error stack" << rang::fg::reset << " {"
1617+
<< std::endl;
1618+
1619+
// TODO (mmarchini): Refactor: create an StackIterator which returns
1620+
// StackFrame objects
1621+
for (int64_t i = 0; i < stack_len; i++) {
1622+
Value maybe_fn = arr.GetArrayElement(2 + (i * multiplier), err);
1623+
if (err.Fail()) {
1624+
error_stack << rang::fg::gray << " <unknown>" << std::endl;
1625+
continue;
1626+
}
1627+
1628+
error_stack << " " << HeapObject(maybe_fn).Inspect(&simple, err)
1629+
<< std::endl;
1630+
}
1631+
error_stack << " }";
1632+
res += error_stack.str();
1633+
}
1634+
return res;
15351635
}
15361636

15371637

src/llv8.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ class HeapObject : public Value {
114114
std::string ToString(Error& err);
115115
std::string Inspect(InspectOptions* options, Error& err);
116116
std::string GetTypeName(Error& err);
117+
118+
inline bool IsJSErrorType(Error& err);
117119
};
118120

119121
class Map : public HeapObject {
@@ -257,6 +259,7 @@ class JSObject : public HeapObject {
257259
inline HeapObject Elements(Error& err);
258260

259261
std::string Inspect(InspectOptions* options, Error& err);
262+
virtual std::string InspectAllProperties(InspectOptions* options, Error& err);
260263
std::string InspectInternalFields(Error& err);
261264
std::string InspectProperties(Error& err);
262265

@@ -290,6 +293,14 @@ class JSObject : public HeapObject {
290293
Value GetDescriptorProperty(std::string key_name, Map map, Error& err);
291294
};
292295

296+
class JSError : public JSObject {
297+
public:
298+
V8_VALUE_DEFAULT_METHODS(JSError, JSObject);
299+
300+
std::string InspectAllProperties(InspectOptions* options,
301+
Error& err) override;
302+
};
303+
293304
class JSArray : public JSObject {
294305
public:
295306
V8_VALUE_DEFAULT_METHODS(JSArray, JSObject);
@@ -583,6 +594,7 @@ class LLV8 {
583594
friend class ThinString;
584595
friend class HeapNumber;
585596
friend class JSObject;
597+
friend class JSError;
586598
friend class JSArray;
587599
friend class FixedArrayBase;
588600
friend class FixedArray;

test/plugin/inspect-test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ const hashMapTests = {
151151
let errnoMatch = lines.match(/errno=<Smi: 1>/i);
152152
t.ok(errnoMatch, 'hashmap.error.errno should be 1');
153153

154+
let stackMatch = lines.match(/error stack {/i);
155+
t.ok(stackMatch, 'Error object should have an error stack');
156+
157+
let closureMatch = lines.match(/0x[0-9a-f]+:<function: closure/i);
158+
t.ok(closureMatch, 'closure frame should be in the error stack');
159+
154160
cb(null);
155161
});
156162
}

0 commit comments

Comments
 (0)