Library of Assembled Shared Sources
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-2025 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 // OSError_new will determine the correct subtype based on errno (or winerror)
85 // So we need to retrieve the exception type from the result.
86 TPyObjPtr value(PyObject_CallObject(PyExc_OSError, args.get()));
87 PyObject* v = value.get();
88 PyErr_SetObject((PyObject*) Py_TYPE(v), v);
89}
90
91void setOSErrorFromCode(const std::error_code& code, const TChar* path1 = nullptr, const TChar* path2 = nullptr)
92{
93 if (code.category() == std::generic_category())
94 {
95 setOSErrorFromErrno(code.value(), code.message(), path1, nullptr, path2);
96 }
97 else if (code.category() == std::system_category())
98 {
99#if LASS_PLATFORM_TYPE == LASS_PLATFORM_TYPE_WIN32
100 const int winerror = code.value(); // OSError will determine errno from winerror
101 setOSErrorFromErrno(0, code.message(), path1, &winerror, path2);
102#else
103 setOSErrorFromErrno(code.value(), code.message(), path1, nullptr, path2);
104#endif
105 }
106 else
107 {
108 LASS_ASSERT(!path1 && !path2);
109 PyErr_SetString(PyExc_Exception, code.message().c_str());
110 }
111}
112
113}
114
115namespace lass
116{
117namespace python
118{
119
120namespace impl
121{
122
123void addMessageHeader(const char* header)
124{
125 LockGIL LASS_UNUSED(lock);
126 if (!PyErr_Occurred() || !PyErr_ExceptionMatches(PyExc_TypeError))
127 {
128 return;
129 }
130 PyObject *type, *value, *traceback;
131 PyErr_Fetch(&type, &value, &traceback);
132 try
133 {
134 if (PyUnicode_Check(value))
135 {
136 std::string left = std::string(header) + ": ";
137 TPyObjPtr pyLeft(PyUnicode_DecodeUTF8(left.data(), static_cast<Py_ssize_t>(left.length()), 0));
138 PyObject* newValue = PyUnicode_Concat(pyLeft.get(), value);
139 std::swap(value, newValue);
140 Py_DECREF(newValue);
141 }
142 }
143 catch (const std::exception&)
144 {
145 }
146 PyErr_Restore(type, value, traceback);
147}
148
149
150
151void fetchAndThrowPythonException(std::string loc)
152{
153 LockGIL LASS_UNUSED(lock);
154 if (!PyErr_Occurred())
155 {
156 LASS_THROW("internal error: fetchAndThrowPythonException called while Python exception is not set");
157 }
158
159 PyObject *tempType, *tempValue, *tempTraceback;
160 PyErr_Fetch(&tempType, &tempValue, &tempTraceback);
161
162 const TPyObjPtr type(tempType);
163 const TPyObjPtr value(tempValue);
164 const TPyObjPtr traceback(tempTraceback);
165
166 throw PythonException(type, value, traceback, std::move(loc));
167}
168
169
170
171void handleException(std::exception_ptr ptr)
172{
173 LockGIL LASS_UNUSED(lock);
174 try
175 {
176 std::rethrow_exception(ptr);
177 }
178 catch (const PythonException& error)
179 {
180 PyErr_Restore(
181 fromSharedPtrToNakedCast(error.type()),
182 fromSharedPtrToNakedCast(error.value()),
183 fromSharedPtrToNakedCast(error.traceback()));
184 }
185 catch (const util::Exception& error)
186 {
187 ::std::ostringstream buffer;
188 buffer << error.message() << "\n\n(" << error.location() << ")";
189 PyErr_SetString(PyExc_Exception, buffer.str().c_str());
190 }
191 catch (const std::invalid_argument& error)
192 {
193 PyErr_SetString(PyExc_ValueError, error.what());
194 }
195 catch (const std::domain_error& error)
196 {
197 PyErr_SetString(PyExc_ValueError, error.what());
198 }
199 catch (const std::length_error& error)
200 {
201 PyErr_SetString(PyExc_ValueError, error.what());
202 }
203 catch (const std::out_of_range& error)
204 {
205 PyErr_SetString(PyExc_IndexError, error.what());
206 }
207 catch (const std::range_error& error)
208 {
209 PyErr_SetString(PyExc_ValueError, error.what());
210 }
211 catch (const std::overflow_error& error)
212 {
213 PyErr_SetString(PyExc_OverflowError, error.what());
214 }
215 catch (const std::underflow_error& error)
216 {
217 PyErr_SetString(PyExc_ValueError, error.what());
218 }
219 catch (const std::bad_cast& error)
220 {
221 PyErr_SetString(PyExc_TypeError, error.what());
222 }
223 catch (const std::bad_alloc& error)
224 {
225 PyErr_SetString(PyExc_MemoryError, error.what());
226 }
227 catch (const std::filesystem::filesystem_error& error)
228 {
229 const TChar* path1 = error.path1().empty() ? nullptr : error.path1().c_str();
230 const TChar* path2 = error.path2().empty() ? nullptr : error.path2().c_str();
231 setOSErrorFromCode(error.code(), path1, path2);
232 }
233 catch (const std::system_error& error)
234 {
235 setOSErrorFromCode(error.code());
236 }
237 catch (const std::exception& error)
238 {
239 PyErr_SetString(PyExc_Exception, error.what());
240 }
241}
242
243}
244
245PythonException::PythonException(
246 const TPyObjPtr& type, const TPyObjPtr& value, const TPyObjPtr& traceback, std::string loc):
247 util::ExceptionMixin<PythonException>(extractMessage(type.get(), value.get()), std::move(loc)),
248 type_(type),
249 value_(value),
250 traceback_(traceback)
251{
252}
253
254PythonException::PythonException(PyObject* type, const std::string& msg, std::string loc):
255 util::ExceptionMixin<PythonException>(extractMessage(type) + ": " + msg, std::move(loc)),
256 type_(fromNakedToSharedPtrCast<PyObject>(type)),
257 value_(pyBuildSimpleObject(msg)),
258 traceback_(0)
259{
260}
261
262PythonException::PythonException(PyObject* type, const std::string& msg):
263 util::ExceptionMixin<PythonException>(extractMessage(type) + ": " + msg),
264 type_(fromNakedToSharedPtrCast<PyObject>(type)),
265 value_(pyBuildSimpleObject(msg)),
266 traceback_(0)
267{
268}
269
270const std::string PythonException::extractMessage(PyObject* type, PyObject* value)
271{
272 LockGIL LASS_UNUSED(lock);
273 std::string message;
274 const TPyObjPtr typeStr(PyObject_Str(type));
275 if (!typeStr || pyGetSimpleObject(typeStr.get(), message) != 0)
276 {
277 message = "unknown python exception";
278 PyErr_Clear();
279 }
280 if (value && value != Py_None)
281 {
282 std::string valmsg;
283 const TPyObjPtr valueStr(PyObject_Str(value));
284 if (!valueStr || pyGetSimpleObject(valueStr.get(), valmsg) != 0)
285 {
286 PyErr_Clear();
287 return message;
288 }
289 message += ": " + valmsg;
290 }
291 return message;
292}
293
294
295
296PyObject* chainErrFormat(PyObject* exception, const char* format, ...)
297{
298 va_list vargs;
299 va_start(vargs, format);
300 chainErrFormatV(exception, format, vargs);
301 va_end(vargs);
302 return nullptr;
303}
304
305
306
307PyObject* chainErrFormatV(PyObject* exception, const char* format, va_list vargs)
308{
309 LockGIL LASS_UNUSED(lock);
310 if (!PyErr_Occurred())
311 {
312 return PyErr_FormatV(exception, format, vargs);
313 }
314
315 // fetch current exception
316 PyObject *exc, *fromValue, *newValue, *traceback;
317 PyErr_Fetch(&exc, &fromValue, &traceback);
318 // normalize it so `fromValue` is a valid exception instance with traceback
319 PyErr_NormalizeException(&exc, &fromValue, &traceback);
320 if (traceback)
321 {
322 PyException_SetTraceback(fromValue, traceback);
323 Py_DECREF(traceback); // no longer needed
324 }
325 Py_DECREF(exc); // no longer needed
326 assert(!PyErr_Occurred());
327
328 // create new exception with formatted message
329 PyErr_FormatV(exception, format, vargs);
330 // fetch and normalize (no need to set traceback)
331 PyErr_Fetch(&exc, &newValue, &traceback);
332 PyErr_NormalizeException(&exc, &newValue, &traceback);
333 // chain the exceptions
334 Py_INCREF(fromValue); // increase refcount as both setcause and setcontext will steal one.
335 PyException_SetCause(newValue, fromValue);
336 PyException_SetContext(newValue, fromValue);
337 // restore the exception state
338 PyErr_Restore(exc, newValue, traceback);
339
340 return nullptr;
341}
342
343
344
345}
346}
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