forked from oven-sh/bun
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ErrorStackTrace.h
179 lines (148 loc) · 7.62 KB
/
ErrorStackTrace.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/**
* This source code is licensed under the terms found in the LICENSE file in
* node-jsc's root directory.
*/
#pragma once
#include <JavaScriptCore/StackVisitor.h>
#include <JavaScriptCore/CodeBlock.h>
#include <JavaScriptCore/WasmIndexOrName.h>
#include "ZigGlobalObject.h"
using namespace JSC;
using namespace WebCore;
namespace Zig {
/* JSCStackFrame is an alternative to JSC::StackFrame, which provides the following advantages\changes:
* - Also hold the call frame (ExecState). This is mainly used by CallSite to get "this value".
* - More detailed and v8 compatible "source offsets" caculations: JSC::StackFrame only provides the
* line number and column numbers. It's column calculation seems to be different than v8's column.
* According to v8's unit tests, it seems that their column number points to the beginning of
* the expression which raised the exception, while in JSC the column returned by computeLineAndColumn
* seem to point to the end of the expression. Thus, we'll do the calculations ourselves.
* Here, we'll also provide more information which is needed by jscshim (mainly by Message):
* - Full expression range in the source code
* - Line number
* - Start\end columns (before the "throw", and after the "throw <x>")
* - Line start\stop offsets in the source code
* Also, to avoid zero\one base confusions, we'll store all offsets as WTF::OrdinalNumber.
* - Function name "calculation" also checks the function's "name" property. See retrieveFunctionName's
* documentation bellow for more information.
* - String properties are exposed (and cached) as JSStrings, instead of WTF::String.
* - Helper functions like isEval and isConstructor.
*
* Note that this is not a heap allocated, garbage collected, JSCell. It must be stack allocated, as it doens't
* use any write barriers and rely on the GC to see the stored JSC object pointers on the stack.
*/
class JSCStackFrame {
public:
struct SourcePositions {
WTF::OrdinalNumber expressionStart;
WTF::OrdinalNumber expressionStop;
WTF::OrdinalNumber line;
WTF::OrdinalNumber startColumn;
WTF::OrdinalNumber endColumn;
WTF::OrdinalNumber lineStart;
WTF::OrdinalNumber lineStop;
};
private:
JSC::VM& m_vm;
JSC::JSCell* m_callee;
// May be null
JSC::CallFrame* m_callFrame;
// May be null
JSC::CodeBlock* m_codeBlock;
JSC::BytecodeIndex m_bytecodeIndex;
// Lazy-initialized
WTF::String m_sourceURL;
WTF::String m_functionName;
WTF::String m_typeName;
// m_wasmFunctionIndexOrName has meaning only when m_isWasmFrame is set
JSC::Wasm::IndexOrName m_wasmFunctionIndexOrName;
bool m_isWasmFrame;
enum class SourcePositionsState {
NotCalculated,
Failed,
Calculated
};
SourcePositions m_sourcePositions;
SourcePositionsState m_sourcePositionsState;
public:
JSCStackFrame(JSC::VM& vm, JSC::StackVisitor& visitor);
JSCStackFrame(JSC::VM& vm, const JSC::StackFrame& frame);
JSC::JSCell* callee() const { return m_callee; }
JSC::CallFrame* callFrame() const { return m_callFrame; }
JSC::CodeBlock* codeBlock() const { return m_codeBlock; }
intptr_t sourceID() const;
JSC::JSString* sourceURL();
JSC::JSString* functionName();
JSC::JSString* typeName();
bool hasBytecodeIndex() const { return (m_bytecodeIndex.offset() != UINT_MAX) && !m_isWasmFrame; }
JSC::BytecodeIndex bytecodeIndex() const
{
return m_bytecodeIndex;
}
// Returns null if can't retreive the source positions
SourcePositions* getSourcePositions();
bool isWasmFrame() const { return m_isWasmFrame; }
bool isEval() const { return m_codeBlock && (JSC::EvalCode == m_codeBlock->codeType()); }
bool isConstructor() const { return m_codeBlock && (JSC::CodeForConstruct == m_codeBlock->specializationKind()); }
private:
ALWAYS_INLINE String retrieveSourceURL();
/* Regarding real functions (not eval\module\global code), both v8 and JSC seem to follow
* the same logic, which is to first try the function's "display name", and if it's not defined,
* the function's name. In JSC, StackFrame::functionName uses JSC::getCalculatedDisplayName,
* which will internally call the JSFunction\InternalFunction's calculatedDisplayName function.
* But, those function don't check the function's "name" property if the "dispaly name" isn't defined.
* See JSFunction::name()'s and InternalFunction::name()'s implementation. According to v8's unit tests,
* v8 does check the name property in StackFrame::GetFunctionName (see the last part of the
* "CaptureStackTrace" test in test-api.cc).
* Thus, we'll reimplement the general flow of JSC::getCalculatedDisplayName and it's internal calls,
* and just try to use the "name" property when needed, so our lookup will be:
* "display name" property -> "name" property -> JSFunction\InternalFunction "name" methods.
*/
ALWAYS_INLINE String retrieveFunctionName();
ALWAYS_INLINE String retrieveTypeName();
bool calculateSourcePositions();
};
class JSCStackTrace {
private:
WTF::Vector<JSCStackFrame> m_frames;
public:
JSCStackTrace()
{
}
size_t size() const { return m_frames.size(); }
bool isEmpty() const { return m_frames.isEmpty(); }
JSCStackFrame& at(size_t i) { return m_frames.at(i); }
static JSCStackTrace fromExisting(JSC::VM& vm, const WTF::Vector<JSC::StackFrame>& existingFrames);
/* This is based on JSC::Interpreter::getStackTrace, but skips native (non js and not wasm)
* frames, which is what v8 does. Note that we could have just called JSC::Interpreter::getStackTrace
* and and filter it later (or let our callers filter it), but that would have been both inefficient, and
* problematic with the requested stack size limit (as it should only refer to the non-native frames,
* thus we would have needed to pass a large limit to JSC::Interpreter::getStackTrace, and filter out
* maxStackSize non-native frames).
*
* Return value must remain stack allocated. */
static JSCStackTrace captureCurrentJSStackTrace(Zig::GlobalObject* globalObject, JSC::CallFrame* callFrame, size_t frameLimit, JSC::JSValue caller);
/* In JSC, JSC::Exception points to the actual value that was thrown, usually
* a JSC::ErrorInstance (but could be any JSValue). In v8, on the other hand,
* TryCatch::Exception returns the thrown value, and we follow the same rule in jscshim.
* This is a problem, since JSC::Exception is the one that holds the stack trace.
* ErrorInstances might also hold the stack trace (until the error properties are
* "materialized" and it is no longer needed). So, to try to get the stack trace for a thrown JSValue,
* we'll try two things:
* - If the current JSC (vm) exception points to our value, it means our value is probably the current
* exception and we could take the stack trace from the vm's current JSC::Exception. The downside
* of doing this is that we'll get the last stack trace of the thrown value, meaning that if the value
* was thrown, stored in the api and than re-thrown, we'll get the latest stack trace and not the one
* that was available when we stored it. For now it'll do.
* - If that failed and our thrown value is a JSC::ErrorInstance, we'll try to use it's stack trace,
* if it currently has one.
*
* Return value must remain stack allocated */
static JSCStackTrace getStackTraceForThrownValue(JSC::VM& vm, JSC::JSValue thrownValue);
private:
JSCStackTrace(WTF::Vector<JSCStackFrame>& frames)
: m_frames(WTFMove(frames))
{
}
};
}