Library of Assembled Shared Sources
 
Loading...
Searching...
No Matches
python/exception.cpp
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2004-2026 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43#include "python_common.h"
44#include "exception.h"
45#include "pyobject_plus.h"
46#include "py_tuple.h"
47#include <system_error>
48#include <filesystem>
49
50namespace
51{
52
53using namespace lass::python;
54namespace fs = std::filesystem;
55
56using TChar = fs::path::value_type;
57
58void setOSErrorFromErrno(int errno_, const std::string& message, const TChar* path1 = nullptr, const int* winerr = nullptr, const TChar* path2 = nullptr)
59{
60 TPyObjPtr args;
61 if (path2)
62 {
63 if (winerr)
64 {
65 args = makeTuple(errno_, message, path1, *winerr, path2);
66 }
67 else
68 {
69 args = makeTuple(errno_, message, path1, nullptr, path2);
70 }
71 }
72 else if (winerr)
73 {
74 args = makeTuple(errno_, message, path1, *winerr);
75 }
76 else if (path1)
77 {
78 args = makeTuple(errno_, message, path1);
79 }
80 else
81 {
82 args = makeTuple(errno_, message);
83 }
84 if (!args)
85 {
86 return; // makeTuple failed, error set
87 }
88 // OSError_new will determine the correct subtype based on errno (or winerror)
89 // So we need to retrieve the exception type from the result.
90 TPyObjPtr value(PyObject_CallObject(PyExc_OSError, args.get()));
91 if (!value)
92 {
93 return; // constructor failed, error set
94 }
95 PyObject* v = value.get();
96 PyErr_SetObject((PyObject*) Py_TYPE(v), v);
97}
98
99void setOSErrorFromCode(const std::error_code& code, const TChar* path1 = nullptr, const TChar* path2 = nullptr)
100{
101 if (code.category() == std::generic_category())
102 {
103 setOSErrorFromErrno(code.value(), code.message(), path1, nullptr, path2);
104 }
105 else if (code.category() == std::system_category())
106 {
107#if LASS_PLATFORM_TYPE == LASS_PLATFORM_TYPE_WIN32
108 const int winerror = code.value(); // OSError will determine errno from winerror
109 setOSErrorFromErrno(0, code.message(), path1, &winerror, path2);
110#else
111 setOSErrorFromErrno(code.value(), code.message(), path1, nullptr, path2);
112#endif
113 }
114 else
115 {
116 LASS_ASSERT(!path1 && !path2);
117 PyErr_SetString(PyExc_Exception, code.message().c_str());
118 }
119}
120
121}
122
123namespace lass
124{
125namespace python
126{
127
128namespace impl
129{
130
131void addMessageHeader(const char* header)
132{
133 LockGIL lock;
134 if (!PyErr_Occurred() || !PyErr_ExceptionMatches(PyExc_TypeError))
135 {
136 return;
137 }
138 PyObject *type, *value, *traceback;
139 PyErr_Fetch(&type, &value, &traceback);
140 try
141 {
142 if (PyUnicode_Check(value))
143 {
144 std::string left = std::string(header) + ": ";
145 TPyObjPtr pyLeft(PyUnicode_DecodeUTF8(left.data(), static_cast<Py_ssize_t>(left.length()), 0));
146 PyObject* newValue = PyUnicode_Concat(pyLeft.get(), value);
147 std::swap(value, newValue);
148 Py_DECREF(newValue);
149 }
150 }
151 catch (const std::exception&)
152 {
153 }
154 PyErr_Restore(type, value, traceback);
155}
156
157
158
159void fetchAndThrowPythonException(std::string loc)
160{
161 LockGIL lock;
162 if (!PyErr_Occurred())
163 {
164 LASS_THROW("internal error: fetchAndThrowPythonException called while Python exception is not set");
165 }
166
167 PyObject *tempType, *tempValue, *tempTraceback;
168 PyErr_Fetch(&tempType, &tempValue, &tempTraceback);
169
170 const TPyObjPtr type(tempType);
171 const TPyObjPtr value(tempValue);
172 const TPyObjPtr traceback(tempTraceback);
173
174 throw PythonException(type, value, traceback, std::move(loc));
175}
176
177
178
179void handleException(std::exception_ptr ptr)
180{
181 LockGIL lock;
182 try
183 {
184 std::rethrow_exception(ptr);
185 }
186 catch (const PythonException& error)
187 {
188 PyErr_Restore(
189 fromSharedPtrToNakedCast(error.type()),
190 fromSharedPtrToNakedCast(error.value()),
191 fromSharedPtrToNakedCast(error.traceback()));
192 }
193 catch (const util::Exception& error)
194 {
195 ::std::ostringstream buffer;
196 buffer << error.message() << "\n\n(" << error.location() << ")";
197 PyErr_SetString(PyExc_Exception, buffer.str().c_str());
198 }
199 catch (const std::invalid_argument& error)
200 {
201 PyErr_SetString(PyExc_ValueError, error.what());
202 }
203 catch (const std::domain_error& error)
204 {
205 PyErr_SetString(PyExc_ValueError, error.what());
206 }
207 catch (const std::length_error& error)
208 {
209 PyErr_SetString(PyExc_ValueError, error.what());
210 }
211 catch (const std::out_of_range& error)
212 {
213 PyErr_SetString(PyExc_IndexError, error.what());
214 }
215 catch (const std::range_error& error)
216 {
217 PyErr_SetString(PyExc_ValueError, error.what());
218 }
219 catch (const std::overflow_error& error)
220 {
221 PyErr_SetString(PyExc_OverflowError, error.what());
222 }
223 catch (const std::underflow_error& error)
224 {
225 PyErr_SetString(PyExc_ValueError, error.what());
226 }
227 catch (const std::bad_cast& error)
228 {
229 PyErr_SetString(PyExc_TypeError, error.what());
230 }
231 catch (const std::bad_alloc& error)
232 {
233 PyErr_SetString(PyExc_MemoryError, error.what());
234 }
235 catch (const std::filesystem::filesystem_error& error)
236 {
237 const TChar* path1 = error.path1().empty() ? nullptr : error.path1().c_str();
238 const TChar* path2 = error.path2().empty() ? nullptr : error.path2().c_str();
239 setOSErrorFromCode(error.code(), path1, path2);
240 }
241 catch (const std::system_error& error)
242 {
243 setOSErrorFromCode(error.code());
244 }
245 catch (const std::exception& error)
246 {
247 PyErr_SetString(PyExc_Exception, error.what());
248 }
249}
250
251}
252
253PythonException::PythonException(
254 const TPyObjPtr& type, const TPyObjPtr& value, const TPyObjPtr& traceback, std::string loc):
255 util::ExceptionMixin<PythonException>(extractMessage(type.get(), value.get()), std::move(loc)),
256 type_(type),
257 value_(value),
258 traceback_(traceback)
259{
260}
261
262PythonException::PythonException(PyObject* type, const std::string& msg, std::string loc):
263 util::ExceptionMixin<PythonException>(extractMessage(type) + ": " + msg, std::move(loc)),
264 type_(fromNakedToSharedPtrCast<PyObject>(type)),
265 value_(pyBuildSimpleObject(msg)),
266 traceback_(0)
267{
268}
269
270PythonException::PythonException(PyObject* type, const std::string& msg):
271 util::ExceptionMixin<PythonException>(extractMessage(type) + ": " + msg),
272 type_(fromNakedToSharedPtrCast<PyObject>(type)),
273 value_(pyBuildSimpleObject(msg)),
274 traceback_(0)
275{
276}
277
278const std::string PythonException::extractMessage(PyObject* type, PyObject* value)
279{
280 LockGIL lock;
281 std::string message;
282 const TPyObjPtr typeStr(PyObject_Str(type));
283 if (!typeStr || pyGetSimpleObject(typeStr.get(), message) != 0)
284 {
285 message = "unknown python exception";
286 PyErr_Clear();
287 }
288 if (value && value != Py_None)
289 {
290 std::string valmsg;
291 const TPyObjPtr valueStr(PyObject_Str(value));
292 if (!valueStr || pyGetSimpleObject(valueStr.get(), valmsg) != 0)
293 {
294 PyErr_Clear();
295 return message;
296 }
297 message += ": " + valmsg;
298 }
299 return message;
300}
301
302
303
304PyObject* chainErrFormat(PyObject* exception, const char* format, ...)
305{
306 va_list vargs;
307 va_start(vargs, format);
308 chainErrFormatV(exception, format, vargs);
309 va_end(vargs);
310 return nullptr;
311}
312
313
314
315PyObject* chainErrFormatV(PyObject* exception, const char* format, va_list vargs)
316{
317 LockGIL lock;
318 if (!PyErr_Occurred())
319 {
320 return PyErr_FormatV(exception, format, vargs);
321 }
322
323 // fetch current exception
324 PyObject *exc, *fromValue, *newValue, *traceback;
325 PyErr_Fetch(&exc, &fromValue, &traceback);
326 // normalize it so `fromValue` is a valid exception instance with traceback
327 PyErr_NormalizeException(&exc, &fromValue, &traceback);
328 if (traceback)
329 {
330 PyException_SetTraceback(fromValue, traceback);
331 Py_DECREF(traceback); // no longer needed
332 }
333 Py_DECREF(exc); // no longer needed
334 assert(!PyErr_Occurred());
335
336 // create new exception with formatted message
337 PyErr_FormatV(exception, format, vargs);
338 // fetch and normalize (no need to set traceback)
339 PyErr_Fetch(&exc, &newValue, &traceback);
340 PyErr_NormalizeException(&exc, &newValue, &traceback);
341 // chain the exceptions
342 Py_INCREF(fromValue); // increase refcount as both setcause and setcontext will steal one.
343 PyException_SetCause(newValue, fromValue);
344 PyException_SetContext(newValue, fromValue);
345 // restore the exception state
346 PyErr_Restore(exc, newValue, traceback);
347
348 return nullptr;
349}
350
351
352
353}
354}
acquire the GIL for the current scope.
Definition gil.h:56
C++ exception type that holds a Python exception.
type of all exceptions in lass
void addMessageHeader(const char *header)
Prepend a message to the current Python exception value.
void handleException(std::exception_ptr ptr)
Handle a C++ exception by raising an Python exception.
void fetchAndThrowPythonException(std::string loc)
Fetch the current Python exception and throw it as a C++ PythonException.
PyObject * chainErrFormat(PyObject *exception, const char *format,...)
Raise an explicitly chained Python exception.
PyObject * chainErrFormatV(PyObject *exception, const char *format, va_list vargs)
Raise an explicitly chained Python exception.
PyObjectPtr< PyObject >::Type TPyObjPtr
PyObjectPtr to a PyObject.
PyObject * fromSharedPtrToNakedCast(const util::SharedPtr< T, PyObjectStorage, PyObjectCounter > &object)
fromSharedPtrToNakedCast.
lass::util::SharedPtr< T, PyObjectStorage, PyObjectCounter > fromNakedToSharedPtrCast(PyObject *object)
fromNakedToSharedPtrCast.
Comprehensive C++ to Python binding library.
general utility, debug facilities, ...
Library for Assembled Shared Sources.
Definition config.h:53