1 /*
2     tests/test_custom-exceptions.cpp -- exception translation
3 
4     Copyright (c) 2016 Pim Schellart <P.Schellart@princeton.edu>
5 
6     All rights reserved. Use of this source code is governed by a
7     BSD-style license that can be found in the LICENSE file.
8 */
9 
10 #include "pybind11_tests.h"
11 
12 // A type that should be raised as an exception in Python
13 class MyException : public std::exception {
14 public:
MyException(const char * m)15     explicit MyException(const char * m) : message{m} {}
what() const16     const char * what() const noexcept override {return message.c_str();}
17 private:
18     std::string message = "";
19 };
20 
21 // A type that should be translated to a standard Python exception
22 class MyException2 : public std::exception {
23 public:
MyException2(const char * m)24     explicit MyException2(const char * m) : message{m} {}
what() const25     const char * what() const noexcept override {return message.c_str();}
26 private:
27     std::string message = "";
28 };
29 
30 // A type that is not derived from std::exception (and is thus unknown)
31 class MyException3 {
32 public:
MyException3(const char * m)33     explicit MyException3(const char * m) : message{m} {}
what() const34     virtual const char * what() const noexcept {return message.c_str();}
35     // Rule of 5 BEGIN: to preempt compiler warnings.
36     MyException3(const MyException3&) = default;
37     MyException3(MyException3&&) = default;
38     MyException3& operator=(const MyException3&) = default;
39     MyException3& operator=(MyException3&&) = default;
40     virtual ~MyException3() = default;
41     // Rule of 5 END.
42 private:
43     std::string message = "";
44 };
45 
46 // A type that should be translated to MyException
47 // and delegated to its exception translator
48 class MyException4 : public std::exception {
49 public:
MyException4(const char * m)50     explicit MyException4(const char * m) : message{m} {}
what() const51     const char * what() const noexcept override {return message.c_str();}
52 private:
53     std::string message = "";
54 };
55 
56 
57 // Like the above, but declared via the helper function
58 class MyException5 : public std::logic_error {
59 public:
MyException5(const std::string & what)60     explicit MyException5(const std::string &what) : std::logic_error(what) {}
61 };
62 
63 // Inherits from MyException5
64 class MyException5_1 : public MyException5 {
65     using MyException5::MyException5;
66 };
67 
68 struct PythonCallInDestructor {
PythonCallInDestructorPythonCallInDestructor69     PythonCallInDestructor(const py::dict &d) : d(d) {}
~PythonCallInDestructorPythonCallInDestructor70     ~PythonCallInDestructor() { d["good"] = true; }
71 
72     py::dict d;
73 };
74 
75 
76 
77 struct PythonAlreadySetInDestructor {
PythonAlreadySetInDestructorPythonAlreadySetInDestructor78     PythonAlreadySetInDestructor(const py::str &s) : s(s) {}
~PythonAlreadySetInDestructorPythonAlreadySetInDestructor79     ~PythonAlreadySetInDestructor() {
80         py::dict foo;
81         try {
82             // Assign to a py::object to force read access of nonexistent dict entry
83             py::object o = foo["bar"];
84         }
85         catch (py::error_already_set& ex) {
86             ex.discard_as_unraisable(s);
87         }
88     }
89 
90     py::str s;
91 };
92 
93 
TEST_SUBMODULE(exceptions,m)94 TEST_SUBMODULE(exceptions, m) {
95     m.def("throw_std_exception", []() {
96         throw std::runtime_error("This exception was intentionally thrown.");
97     });
98 
99     // make a new custom exception and use it as a translation target
100     static py::exception<MyException> ex(m, "MyException");
101     py::register_exception_translator([](std::exception_ptr p) {
102         try {
103             if (p) std::rethrow_exception(p);
104         } catch (const MyException &e) {
105             // Set MyException as the active python error
106             ex(e.what());
107         }
108     });
109 
110     // register new translator for MyException2
111     // no need to store anything here because this type will
112     // never by visible from Python
113     py::register_exception_translator([](std::exception_ptr p) {
114         try {
115             if (p) std::rethrow_exception(p);
116         } catch (const MyException2 &e) {
117             // Translate this exception to a standard RuntimeError
118             PyErr_SetString(PyExc_RuntimeError, e.what());
119         }
120     });
121 
122     // register new translator for MyException4
123     // which will catch it and delegate to the previously registered
124     // translator for MyException by throwing a new exception
125     py::register_exception_translator([](std::exception_ptr p) {
126         try {
127             if (p) std::rethrow_exception(p);
128         } catch (const MyException4 &e) {
129             throw MyException(e.what());
130         }
131     });
132 
133     // A simple exception translation:
134     auto ex5 = py::register_exception<MyException5>(m, "MyException5");
135     // A slightly more complicated one that declares MyException5_1 as a subclass of MyException5
136     py::register_exception<MyException5_1>(m, "MyException5_1", ex5.ptr());
137 
138     m.def("throws1", []() { throw MyException("this error should go to a custom type"); });
139     m.def("throws2", []() { throw MyException2("this error should go to a standard Python exception"); });
140     m.def("throws3", []() { throw MyException3("this error cannot be translated"); });
141     m.def("throws4", []() { throw MyException4("this error is rethrown"); });
142     m.def("throws5", []() { throw MyException5("this is a helper-defined translated exception"); });
143     m.def("throws5_1", []() { throw MyException5_1("MyException5 subclass"); });
144     m.def("throws_logic_error", []() { throw std::logic_error("this error should fall through to the standard handler"); });
145     m.def("throws_overflow_error", []() {throw std::overflow_error(""); });
146     m.def("exception_matches", []() {
147         py::dict foo;
148         try {
149             // Assign to a py::object to force read access of nonexistent dict entry
150             py::object o = foo["bar"];
151         }
152         catch (py::error_already_set& ex) {
153             if (!ex.matches(PyExc_KeyError)) throw;
154             return true;
155         }
156         return false;
157     });
158     m.def("exception_matches_base", []() {
159         py::dict foo;
160         try {
161             // Assign to a py::object to force read access of nonexistent dict entry
162             py::object o = foo["bar"];
163         }
164         catch (py::error_already_set &ex) {
165             if (!ex.matches(PyExc_Exception)) throw;
166             return true;
167         }
168         return false;
169     });
170     m.def("modulenotfound_exception_matches_base", []() {
171         try {
172             // On Python >= 3.6, this raises a ModuleNotFoundError, a subclass of ImportError
173             py::module_::import("nonexistent");
174         }
175         catch (py::error_already_set &ex) {
176             if (!ex.matches(PyExc_ImportError)) throw;
177             return true;
178         }
179         return false;
180     });
181 
182     m.def("throw_already_set", [](bool err) {
183         if (err)
184             PyErr_SetString(PyExc_ValueError, "foo");
185         try {
186             throw py::error_already_set();
187         } catch (const std::runtime_error& e) {
188             if ((err && e.what() != std::string("ValueError: foo")) ||
189                 (!err && e.what() != std::string("Unknown internal error occurred")))
190             {
191                 PyErr_Clear();
192                 throw std::runtime_error("error message mismatch");
193             }
194         }
195         PyErr_Clear();
196         if (err)
197             PyErr_SetString(PyExc_ValueError, "foo");
198         throw py::error_already_set();
199     });
200 
201     m.def("python_call_in_destructor", [](py::dict d) {
202         try {
203             PythonCallInDestructor set_dict_in_destructor(d);
204             PyErr_SetString(PyExc_ValueError, "foo");
205             throw py::error_already_set();
206         } catch (const py::error_already_set&) {
207             return true;
208         }
209         return false;
210     });
211 
212     m.def("python_alreadyset_in_destructor", [](py::str s) {
213         PythonAlreadySetInDestructor alreadyset_in_destructor(s);
214         return true;
215     });
216 
217     // test_nested_throws
218     m.def("try_catch", [m](py::object exc_type, py::function f, py::args args) {
219         try { f(*args); }
220         catch (py::error_already_set &ex) {
221             if (ex.matches(exc_type))
222                 py::print(ex.what());
223             else
224                 throw;
225         }
226     });
227 
228     // Test repr that cannot be displayed
229     m.def("simple_bool_passthrough", [](bool x) {return x;});
230 
231 }
232