1 /* 2 Copyright (c) 2006 Kirk McDonald 3 4 Permission is hereby granted, free of charge, to any person obtaining a copy of 5 this software and associated documentation files (the "Software"), to deal in 6 the Software without restriction, including without limitation the rights to 7 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 of the Software, and to permit persons to whom the Software is furnished to do 9 so, subject to the following conditions: 10 11 The above copyright notice and this permission notice shall be included in all 12 copies or substantial portions of the Software. 13 14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 SOFTWARE. 21 */ 22 23 /** 24 Contains utilities for safely wrapping python exceptions in D and vice versa. 25 */ 26 module pyd.exception; 27 28 import std.conv; 29 import std.string: format; 30 import std.string; 31 import deimos.python.Python; 32 import std.traits : fullyQualifiedName; 33 34 /** 35 * This function first checks if a Python exception is set, and then (if one 36 * is) pulls it out, stuffs it in a PythonException, and throws that exception. 37 * 38 * If this exception is never caught, it will be handled by exception_catcher 39 * (below) and passed right back into Python as though nothing happened. 40 */ 41 void handle_exception(string file = __FILE__, size_t line = __LINE__) { 42 PyObject* type, value, traceback; 43 if (PyErr_Occurred() !is null) { 44 PyErr_Fetch(&type, &value, &traceback); 45 PyErr_NormalizeException(&type, &value, &traceback); 46 throw new PythonException(type, value, traceback,file,line); 47 } 48 } 49 50 // Used internally. 51 T error_code(T) () { 52 static if (is(T == PyObject*)) { 53 return null; 54 } else static if (is(T == int)) { 55 return -1; 56 } else static if (is(T == Py_ssize_t)) { 57 return -1; 58 } else static if (is(T == void)) { 59 return; 60 } else static assert(false, "exception_catcher cannot handle return type " ~ fullyQualifiedName!T); 61 } 62 63 /** 64 * It is intended that any functions that interface directly with Python which 65 * have the possibility of a D exception being raised wrap their contents in a 66 * call to this function, e.g.: 67 * 68 *$(D_CODE extern (C) 69 *PyObject* some_func(PyObject* self) { 70 * return _exception_catcher({ 71 * // ... 72 * }); 73 *}) 74 */ 75 T exception_catcher(T) (T delegate() dg) { 76 try { 77 return dg(); 78 } 79 // A Python exception was raised and duly re-thrown as a D exception. 80 // It should now be re-raised as a Python exception. 81 catch (PythonException e) { 82 PyErr_Restore(e.type(), e.value(), e.traceback()); 83 return error_code!(T)(); 84 } 85 // A D exception was raised and should be translated into a meaningful 86 // Python exception. 87 catch (Exception e) { 88 PyErr_SetString(PyExc_RuntimeError, ("D Exception:\n" ~ e.toString() ~ "\0").ptr); 89 return error_code!(T)(); 90 } 91 // Some other D object was thrown. Deal with it. 92 catch (Throwable o) { 93 PyErr_SetString(PyExc_RuntimeError, ("thrown D Object: " ~ o.classinfo.name ~ ": " ~ o.toString() ~ "\0").ptr); 94 return error_code!(T)(); 95 } 96 } 97 98 // waaa! std.string.format (and likely Object.toString) do gc allocations! 99 T exception_catcher_nogc(T) (T delegate() dg) { 100 try { 101 return dg(); 102 } 103 // A Python exception was raised and duly re-thrown as a D exception. 104 // It should now be re-raised as a Python exception. 105 catch (PythonException e) { 106 PyErr_Restore(e.type(), e.value(), e.traceback()); 107 return error_code!(T)(); 108 } 109 // A D exception was raised and should be translated into a meaningful 110 // Python exception. 111 catch (Throwable e) { 112 //auto clz1 = e.classinfo; 113 //const(char)* clz = e.classinfo.name.ptr; 114 //const(char)* msg = e.msg.ptr; 115 //const(char)* file = e.file.ptr; 116 PyObject* p = PyBytes_FromFormat("some thrown D object:\0", 117 /*clz, msg, file, e.line*/); 118 PyErr_SetObject(PyExc_RuntimeError, p); 119 Py_DECREF(p); // PyErr_SetObject has ownership of it now 120 return error_code!(T)(); 121 } 122 } 123 124 alias exception_catcher!(PyObject*) exception_catcher_PyObjectPtr; 125 alias exception_catcher!(int) exception_catcher_int; 126 alias exception_catcher!(void) exception_catcher_void; 127 128 string printSyntaxError(PyObject* type, PyObject* value, PyObject* traceback) { 129 if(value is null) return ""; 130 string text; 131 auto ptext = PyObject_GetAttrString(value, "text"); 132 if(ptext) { 133 version(Python_3_0_Or_Later) { 134 ptext = PyUnicode_AsUTF8String(ptext); 135 } 136 auto p2text = PyBytes_AsString(ptext); 137 if(p2text) text = strip(to!string(p2text)); 138 } 139 C_long offset; 140 auto poffset = PyObject_GetAttrString(value, "offset"); 141 if(poffset) { 142 offset = PyLong_AsLong(poffset); 143 } 144 auto valtype = to!string(value.ob_type.tp_name); 145 146 string message; 147 auto pmsg = PyObject_GetAttrString(value, "msg"); 148 if(pmsg) { 149 version(Python_3_0_Or_Later) { 150 pmsg = PyUnicode_AsUTF8String(pmsg); 151 } 152 auto cmsg = PyBytes_AsString(pmsg); 153 if(cmsg) message = to!string(cmsg); 154 } 155 string space = ""; 156 foreach(i; 0 .. offset-1) space ~= " "; 157 return format(q"{ 158 %s 159 %s^ 160 %s: %s}", text, space,valtype, message); 161 } 162 163 string printGenericError(PyObject* type, PyObject* value, PyObject* traceback) { 164 if(value is null) return ""; 165 auto valtype = to!string(value.ob_type.tp_name); 166 167 string message; 168 version(Python_3_0_Or_Later) { 169 PyObject* uni = PyObject_Str(value); 170 }else{ 171 PyObject* uni = PyObject_Unicode(value); 172 } 173 if(!uni) { 174 PyErr_Clear(); 175 return ""; 176 } 177 PyObject* str = PyUnicode_AsUTF8String(uni); 178 if(!str) { 179 PyErr_Clear(); 180 return ""; 181 } 182 auto cmsg = PyBytes_AsString(str); 183 if(cmsg) message = to!string(cmsg); 184 return format(q"{ 185 %s: %s}", valtype, message); 186 } 187 188 /** 189 * This simple exception class holds a Python exception. 190 */ 191 class PythonException : Exception { 192 protected: 193 PyObject* m_type, m_value, m_trace; 194 public: 195 this(PyObject* type, PyObject* value, PyObject* traceback, string file = __FILE__, size_t line = __LINE__) { 196 if(PyObject_IsInstance(value, cast(PyObject*)PyExc_SyntaxError)) { 197 super(printSyntaxError(type, value, traceback), file, line); 198 }else{ 199 super(printGenericError(type, value, traceback), file, line); 200 } 201 m_type = type; 202 m_value = value; 203 m_trace = traceback; 204 } 205 206 ~this() { 207 if (m_type) Py_DECREF(m_type); 208 if (m_value) Py_DECREF(m_value); 209 if (m_trace) Py_DECREF(m_trace); 210 } 211 212 PyObject* type() { 213 if (m_type) Py_INCREF(m_type); 214 return m_type; 215 } 216 PyObject* value() { 217 if (m_value) Py_INCREF(m_value); 218 return m_value; 219 } 220 PyObject* traceback() { 221 if (m_trace) Py_INCREF(m_trace); 222 return m_trace; 223 } 224 225 @property py_message() { 226 string message; 227 PyObject* pmsg; 228 if(m_value) { 229 if(PyObject_IsInstance(m_value, cast(PyObject*)PyExc_SyntaxError)) { 230 pmsg = PyObject_GetAttrString(m_value, "msg"); 231 }else{ 232 // todo: test this on other versions.. 233 version(Python_3_2_Or_Later) { 234 pmsg = PyObject_GetAttrString(m_value, "args"); 235 if(pmsg != null && PyTuple_Check(pmsg) && 236 PyTuple_Size(pmsg) >= 1) { 237 pmsg = cast(PyObject*) PyTuple_GetItem(pmsg, 0); 238 } 239 240 }else{ 241 pmsg = PyObject_GetAttrString(m_value, "message"); 242 } 243 } 244 if(pmsg) { 245 import pyd.make_object; 246 message = python_to_d!string(pmsg); 247 } 248 } 249 250 return message; 251 } 252 253 @property py_offset() { 254 C_long offset = -1; 255 if(m_value) { 256 auto poffset = PyObject_GetAttrString(m_value, "offset"); 257 if(poffset) { 258 offset = PyLong_AsLong(poffset); 259 } 260 } 261 return offset; 262 } 263 } 264