1+ /* *
2+ * @file ExceptionType.cc
3+ * @author Tom Tang ([email protected] ) and Philippe Laporte ([email protected] ) 4+ * @brief Struct for representing Python Exception objects from a corresponding JS Error object
5+ * @date 2023-04-11
6+ *
7+ * @copyright Copyright (c) 2023-2024 Distributive Corp.
8+ *
9+ */
10+
111#include " include/modules/pythonmonkey/pythonmonkey.hh"
212#include " include/setSpiderMonkeyException.hh"
313
717#include < js/Exception.h>
818
919#include < Python.h>
20+ #include < frameobject.h>
21+
1022
11- ExceptionType::ExceptionType (PyObject *object) : PyType( object) {}
23+ // TODO (Tom Tang): preserve the original Python exception object somewhere in the JS obj for lossless two-way conversion
1224
1325ExceptionType::ExceptionType (JSContext *cx, JS::HandleObject error) {
1426 // Convert the JS Error object to a Python string
@@ -26,13 +38,223 @@ ExceptionType::ExceptionType(JSContext *cx, JS::HandleObject error) {
2638 Py_XDECREF (errStr);
2739}
2840
29- // TODO (Tom Tang): preserve the original Python exception object somewhere in the JS obj for lossless two-way conversion
30- JSObject *ExceptionType::toJsError (JSContext *cx) {
31- PyObject *pyErrType = PyObject_Type (pyObject);
41+
42+ // Generating trace information
43+
44+ #define PyTraceBack_LIMIT 1000
45+
46+ static const int TB_RECURSIVE_CUTOFF = 3 ;
47+
48+ #if PY_VERSION_HEX >= 0x03090000
49+
50+ static inline int
51+ tb_get_lineno (PyTracebackObject *tb) {
52+ PyFrameObject *frame = tb->tb_frame ;
53+ PyCodeObject *code = PyFrame_GetCode (frame);
54+ int lineno = PyCode_Addr2Line (code, tb->tb_lasti );
55+ Py_DECREF (code);
56+ return lineno;
57+ }
58+
59+ #endif
60+
61+ static int
62+ tb_print_line_repeated (_PyUnicodeWriter *writer, long cnt)
63+ {
64+ cnt -= TB_RECURSIVE_CUTOFF;
65+ PyObject *line = PyUnicode_FromFormat (
66+ (cnt > 1 )
67+ ? " [Previous line repeated %ld more times]\n "
68+ : " [Previous line repeated %ld more time]\n " ,
69+ cnt);
70+ if (line == NULL ) {
71+ return -1 ;
72+ }
73+ int err = _PyUnicodeWriter_WriteStr (writer, line);
74+ Py_DECREF (line);
75+ return err;
76+ }
77+
78+ JSObject *ExceptionType::toJsError (JSContext *cx, PyObject *exceptionValue, PyObject *traceBack) {
79+ assert (exceptionValue != NULL );
80+
81+ PyObject *pyErrType = PyObject_Type (exceptionValue);
3282 const char *pyErrTypeName = _PyType_Name ((PyTypeObject *)pyErrType);
33- PyObject *pyErrMsg = PyObject_Str (pyObject);
34- // TODO (Tom Tang): Convert Python traceback and set it as the `stack` property on JS Error object
35- // PyObject *traceback = PyException_GetTraceback(pyObject);
83+
84+ PyObject *pyErrMsg = PyObject_Str (exceptionValue);
85+
86+ if (traceBack) {
87+ _PyUnicodeWriter writer;
88+ _PyUnicodeWriter_Init (&writer);
89+
90+ PyObject *fileName = NULL ;
91+ int lineno = -1 ;
92+
93+ PyTracebackObject *tb = (PyTracebackObject *)traceBack;
94+
95+ long limit = PyTraceBack_LIMIT;
96+
97+ PyObject *limitv = PySys_GetObject (" tracebacklimit" );
98+ if (limitv && PyLong_Check (limitv)) {
99+ int overflow;
100+ limit = PyLong_AsLongAndOverflow (limitv, &overflow);
101+ if (overflow > 0 ) {
102+ limit = LONG_MAX;
103+ }
104+ else if (limit <= 0 ) {
105+ return NULL ;
106+ }
107+ }
108+
109+ PyCodeObject *code = NULL ;
110+ Py_ssize_t depth = 0 ;
111+ PyObject *last_file = NULL ;
112+ int last_line = -1 ;
113+ PyObject *last_name = NULL ;
114+ long cnt = 0 ;
115+ PyTracebackObject *tb1 = tb;
116+ int err = 0 ;
117+
118+ int res;
119+ PyObject *line = PyUnicode_FromString (" Traceback (most recent call last):\n " );
120+ if (line == NULL ) {
121+ goto error;
122+ }
123+ res = _PyUnicodeWriter_WriteStr (&writer, line);
124+ Py_DECREF (line);
125+ if (res < 0 ) {
126+ goto error;
127+ }
128+
129+ // TODO should we reverse the stack and put it in the more common, non-python, top-most to bottom-most order? Wait for user feedback on experience
130+ while (tb1 != NULL ) {
131+ depth++;
132+ tb1 = tb1->tb_next ;
133+ }
134+ while (tb != NULL && depth > limit) {
135+ depth--;
136+ tb = tb->tb_next ;
137+ }
138+
139+ #if PY_VERSION_HEX >= 0x03090000
140+
141+ while (tb != NULL ) {
142+ code = PyFrame_GetCode (tb->tb_frame );
143+
144+ int tb_lineno = tb->tb_lineno ;
145+ if (tb_lineno == -1 ) {
146+ tb_lineno = tb_get_lineno (tb);
147+ }
148+
149+ if (last_file == NULL ||
150+ code->co_filename != last_file ||
151+ last_line == -1 || tb_lineno != last_line ||
152+ last_name == NULL || code->co_name != last_name) {
153+
154+ if (cnt > TB_RECURSIVE_CUTOFF) {
155+ if (tb_print_line_repeated (&writer, cnt) < 0 ) {
156+ goto error;
157+ }
158+ }
159+ last_file = code->co_filename ;
160+ last_line = tb_lineno;
161+ last_name = code->co_name ;
162+ cnt = 0 ;
163+ }
164+
165+ cnt++;
166+
167+ if (cnt <= TB_RECURSIVE_CUTOFF) {
168+ fileName = code->co_filename ;
169+ lineno = tb_lineno;
170+
171+ line = PyUnicode_FromFormat (" File \" %U\" , line %d, in %U\n " , fileName, lineno, code->co_name );
172+ if (line == NULL ) {
173+ goto error;
174+ }
175+
176+ int res = _PyUnicodeWriter_WriteStr (&writer, line);
177+ Py_DECREF (line);
178+ if (res < 0 ) {
179+ goto error;
180+ }
181+ }
182+
183+ Py_CLEAR (code);
184+ tb = tb->tb_next ;
185+ }
186+ if (cnt > TB_RECURSIVE_CUTOFF) {
187+ if (tb_print_line_repeated (&writer, cnt) < 0 ) {
188+ goto error;
189+ }
190+ }
191+
192+ #else
193+
194+ while (tb != NULL && err == 0 ) {
195+ if (last_file == NULL ||
196+ tb->tb_frame ->f_code ->co_filename != last_file ||
197+ last_line == -1 || tb->tb_lineno != last_line ||
198+ last_name == NULL || tb->tb_frame ->f_code ->co_name != last_name) {
199+ if (cnt > TB_RECURSIVE_CUTOFF) {
200+ err = tb_print_line_repeated (&writer, cnt);
201+ }
202+ last_file = tb->tb_frame ->f_code ->co_filename ;
203+ last_line = tb->tb_lineno ;
204+ last_name = tb->tb_frame ->f_code ->co_name ;
205+ cnt = 0 ;
206+ }
207+ cnt++;
208+ if (err == 0 && cnt <= TB_RECURSIVE_CUTOFF) {
209+ fileName = tb->tb_frame ->f_code ->co_filename ;
210+ lineno = tb->tb_lineno ;
211+
212+ line = PyUnicode_FromFormat (" File \" %U\" , line %d, in %U\n " , fileName, lineno, tb->tb_frame ->f_code ->co_name );
213+ if (line == NULL ) {
214+ goto error;
215+ }
216+
217+ int res = _PyUnicodeWriter_WriteStr (&writer, line);
218+ Py_DECREF (line);
219+ if (res < 0 ) {
220+ goto error;
221+ }
222+ }
223+ tb = tb->tb_next ;
224+ }
225+ if (err == 0 && cnt > TB_RECURSIVE_CUTOFF) {
226+ err = tb_print_line_repeated (&writer, cnt);
227+ }
228+
229+ if (err) {
230+ goto error;
231+ }
232+
233+ #endif
234+
235+ {
236+ std::stringstream msgStream;
237+ msgStream << " Python " << pyErrTypeName << " : " << PyUnicode_AsUTF8 (pyErrMsg) << " \n " << PyUnicode_AsUTF8 (_PyUnicodeWriter_Finish (&writer));
238+ std::string msg = msgStream.str ();
239+
240+ JS::RootedValue rval (cx);
241+ JS::RootedString filename (cx, JS_NewStringCopyZ (cx, PyUnicode_AsUTF8 (fileName)));
242+ JS::RootedString message (cx, JS_NewStringCopyZ (cx, msg.c_str ()));
243+ // TODO stack argument cannot be passed in as a string anymore (deprecated), and could not find a proper example using the new argument type
244+ if (!JS::CreateError (cx, JSExnType::JSEXN_ERR, nullptr , filename, lineno, 0 , nullptr , message, JS::NothingHandleValue, &rval)) {
245+ return NULL ;
246+ }
247+
248+ Py_DECREF (pyErrType);
249+ Py_DECREF (pyErrMsg);
250+
251+ return rval.toObjectOrNull ();
252+ }
253+
254+ error:
255+ _PyUnicodeWriter_Dealloc (&writer);
256+ Py_XDECREF (code);
257+ }
36258
37259 std::stringstream msgStream;
38260 msgStream << " Python " << pyErrTypeName << " : " << PyUnicode_AsUTF8 (pyErrMsg);
@@ -42,10 +264,12 @@ JSObject *ExceptionType::toJsError(JSContext *cx) {
42264 JS::RootedObject stack (cx);
43265 JS::RootedString filename (cx, JS_NewStringCopyZ (cx, " [python code]" ));
44266 JS::RootedString message (cx, JS_NewStringCopyZ (cx, msg.c_str ()));
45- JS::CreateError (cx, JSExnType::JSEXN_ERR, stack, filename, 0 , 0 , nullptr , message, JS::NothingHandleValue, &rval);
267+ if (!JS::CreateError (cx, JSExnType::JSEXN_ERR, nullptr , filename, 0 , 0 , nullptr , message, JS::NothingHandleValue, &rval)) {
268+ return NULL ;
269+ }
46270
47271 Py_DECREF (pyErrType);
48272 Py_DECREF (pyErrMsg);
49273
50274 return rval.toObjectOrNull ();
51- }
275+ }
0 commit comments