Skip to content

Commit 09ad48b

Browse files
committed
Handle exceptions raised in Python or Perl code correctly
We now handle user exceptions, invalid function or method names and passing exceptions from Perl through Python back to Perl.
1 parent 688a38f commit 09ad48b

File tree

3 files changed

+174
-19
lines changed

3 files changed

+174
-19
lines changed

lib/Inline/Python.pm6

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ sub py_inc_ref(OpaquePointer)
163163
sub py_getattr(OpaquePointer, Str)
164164
returns OpaquePointer { ... }
165165
native(&py_getattr);
166+
sub py_fetch_error(CArray[OpaquePointer], CArray[OpaquePointer], CArray[OpaquePointer], CArray[OpaquePointer])
167+
{ ... }
168+
native(&py_fetch_error);
166169

167170
my $objects = ObjectKeeper.new;
168171

@@ -310,39 +313,48 @@ method !setup_arguments(@args) {
310313
return $tuple;
311314
}
312315

316+
method handle_python_exception() is hidden_from_backtrace {
317+
my @exception := CArray[OpaquePointer].new();
318+
@exception[$_] = OpaquePointer for ^4;
319+
py_fetch_error(@exception);
320+
my $ex_type = @exception[0];
321+
my $ex_message = @exception[3];
322+
if $ex_type {
323+
my $message = self.py_to_p6($ex_message);
324+
@exception[$_] and py_dec_ref(@exception[$_]) for ^4;
325+
die $message;
326+
}
327+
}
328+
313329
multi method run($python, :$eval!) {
314330
my $res = py_eval($python, 0);
331+
self.handle_python_exception();
315332
self.py_to_p6($res);
316333
}
317334

318335
multi method run($python, :$file) {
319336
my $res = py_eval($python, 1);
337+
self.handle_python_exception();
320338
self.py_to_p6($res);
321-
CATCH {
322-
default {
323-
warn $_;
324-
die $_;
325-
}
326-
}
327339
}
328340

329341
method call(Str $package, Str $function, *@args) {
330342
my $py_retval = py_call_function($package, $function, self!setup_arguments(@args));
331-
return unless defined $py_retval;
343+
self.handle_python_exception();
332344
my \retval = self.py_to_p6($py_retval);
333345
return retval;
334346
}
335347

336348
multi method invoke(OpaquePointer $obj, Str $method, *@args) {
337349
my $py_retval = py_call_method($obj, $method, self!setup_arguments(@args));
338-
return unless defined $py_retval;
350+
self.handle_python_exception();
339351
my \retval = self.py_to_p6($py_retval);
340352
py_dec_ref($py_retval);
341353
return retval;
342354
}
343355
multi method invoke(PythonParent $p6obj, OpaquePointer $obj, Str $method, *@args) {
344356
my $py_retval = py_call_method_inherited(self.p6_to_py($p6obj), $obj, $method, self!setup_arguments(@args));
345-
return unless defined $py_retval;
357+
self.handle_python_exception();
346358
my \retval = self.py_to_p6($py_retval);
347359
py_dec_ref($py_retval);
348360
return retval;
@@ -359,8 +371,7 @@ method BUILD {
359371
return self.p6_to_py(retvals);
360372
CATCH {
361373
default {
362-
warn $_;
363-
nativecast(CArray[OpaquePointer], $err)[0] = self.p6_to_py($_);
374+
nativecast(CArray[OpaquePointer], $err)[0] = self.p6_to_py($_.Str());
364375
return OpaquePointer;
365376
}
366377
}
@@ -371,7 +382,7 @@ method BUILD {
371382
return self.p6_to_py(retvals);
372383
CATCH {
373384
default {
374-
nativecast(CArray[OpaquePointer], $err)[0] = self.p6_to_p5($_);
385+
nativecast(CArray[OpaquePointer], $err)[0] = self.p6_to_py($_.Str());
375386
return OpaquePointer;
376387
}
377388
}

pyhelper.c

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,6 @@ PyObject *py_eval(const char* p, int type) {
5151
? Py_file_input
5252
: Py_single_input;
5353
py_result = PyRun_String(p, context, globals, locals);
54-
if (!py_result) {
55-
PyErr_Print();
56-
printf("Error -- py_eval raised an exception");
57-
return NULL;
58-
}
5954
return py_result;
6055
}
6156

@@ -203,29 +198,64 @@ PyObject *py_call_function(char *pkg, char *name, PyObject *args) {
203198
PyObject * const func = PyMapping_GetItemString(dict, name);
204199
PyObject *py_retval = NULL;
205200

201+
if (func == NULL) {
202+
PyErr_Format(PyExc_NameError, "name '%s' is not defined", name);
203+
goto cleanup;
204+
}
205+
206206
py_retval = PyObject_CallObject(func, args);
207+
207208
Py_DECREF(func);
209+
cleanup:
208210
Py_DECREF(args);
209211

210212
return py_retval;
211213
}
212214

215+
void py_fetch_error(PyObject **exception) {
216+
/* ex_type, ex_value, ex_trace, ex_message */
217+
PyErr_Fetch(&exception[0], &exception[1], &exception[2]);
218+
PyErr_NormalizeException(&exception[0], &exception[1], &exception[2]);
219+
exception[3] = PyObject_Unicode(exception[1]); /* new reference */
220+
}
221+
222+
void py_raise_missing_method(PyObject *obj, char *name) {
223+
PyObject *class = PyObject_GetAttrString(obj, "__class__");
224+
PyObject *class_name = PyObject_GetAttrString(class, "__name__");
225+
char *c_class_name = PyString_AsString(class_name);
226+
PyErr_Format(PyExc_NameError, "%s instance has no attribute '%s'", c_class_name, name);
227+
Py_DECREF(class_name);
228+
Py_DECREF(class);
229+
}
230+
213231
PyObject *py_call_method(PyObject *obj, char *name, PyObject *args) {
214232
PyObject *method = PyObject_GetAttrString(obj, name);
233+
if (method == NULL) {
234+
py_raise_missing_method(obj, name);
235+
goto cleanup;
236+
}
215237
PyObject *py_retval = PyObject_CallObject(method, args);
216238
Py_DECREF(method);
239+
240+
cleanup:
217241
Py_DECREF(args);
218242

219243
return py_retval;
220244
}
221245

222246
PyObject *py_call_method_inherited(PyObject *p6obj, PyObject *obj, char *name, PyObject *args) {
223247
PyObject *method = PyObject_GetAttrString(obj, name);
248+
if (method == NULL) {
249+
py_raise_missing_method(obj, name);
250+
goto cleanup;
251+
}
224252
PyObject *function = PyMethod_Function(method);
225253
PyObject *inherited_method = PyMethod_New(function, p6obj, perl6object);
226254
PyObject *py_retval = PyObject_CallObject(inherited_method, args);
227255
Py_DECREF(method);
228256
Py_DECREF(inherited_method);
257+
258+
cleanup:
229259
Py_DECREF(args);
230260

231261
return py_retval;
@@ -234,23 +264,33 @@ PyObject *py_call_method_inherited(PyObject *p6obj, PyObject *obj, char *name, P
234264
static PyObject *perl6_call(PyObject *self, PyObject *args) {
235265
PyObject * const index = PySequence_GetItem(args, 0);
236266
PyObject * const params = PySequence_GetItem(args, 1);
267+
PyObject * error = NULL;
237268

238-
PyObject *retval = call_p6_object(PyInt_AsLong(index), params, NULL);
269+
PyObject *retval = call_p6_object(PyInt_AsLong(index), params, &error);
270+
if (error != NULL) {
271+
PyErr_SetObject(PyExc_Exception, error);
272+
return NULL;
273+
}
239274
return retval;
240275
}
241276

242277
static PyObject *perl6_invoke(PyObject *self, PyObject *args) {
243278
PyObject * const index = PySequence_GetItem(args, 0);
244279
PyObject * const name = PySequence_GetItem(args, 1);
245280
PyObject * const params = PySequence_GetItem(args, 2);
281+
PyObject * error = NULL;
246282

247283
Py_ssize_t length;
248284
char * buf;
249285
PyString_AsStringAndSize(name, &buf, &length);
250286
char * const name_str = calloc(sizeof(char), length + 1);
251287
memcpy(name_str, buf, length);
252288

253-
PyObject *retval = call_p6_method(PyInt_AsLong(index), name_str, params, NULL);
289+
PyObject *retval = call_p6_method(PyInt_AsLong(index), name_str, params, &error);
290+
if (error != NULL) {
291+
PyErr_SetObject(PyExc_Exception, error);
292+
return NULL;
293+
}
254294
return retval;
255295
}
256296

t/exceptions.t

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env perl6
2+
3+
use v6;
4+
use Test;
5+
use Inline::Python;
6+
7+
my $py = Inline::Python.new();
8+
{
9+
try $py.run(q:heredoc/PYTHON/);
10+
raise Exception(u"foo")
11+
PYTHON
12+
ok 1, 'survived Python exception';
13+
ok $!.isa('X::AdHoc'), 'got an exception';
14+
ok $!.Str() ~~ m/foo/, 'exception message found';
15+
}
16+
{
17+
$py.run(q:heredoc/PYTHON/);
18+
def perish():
19+
raise Exception(u"foo")
20+
PYTHON
21+
try $py.call('__main__', 'perish');
22+
ok 1, 'survived Python exception in function call';
23+
ok $!.isa('X::AdHoc'), 'got an exception from function call';
24+
ok $!.Str() ~~ m/foo/, 'exception message found from function call';
25+
}
26+
{
27+
$py.run(q:heredoc/PYTHON/);
28+
class Foo:
29+
def depart(self):
30+
raise Exception(u"foo")
31+
PYTHON
32+
my $foo = $py.call('__main__', 'Foo');
33+
$foo.depart;
34+
CATCH {
35+
ok 1, 'survived Python exception in method call';
36+
when X::AdHoc {
37+
ok $_.isa('X::AdHoc'), 'got an exception from method call';
38+
ok $_.Str() ~~ m/foo/, 'exception message found from method call';
39+
}
40+
}
41+
}
42+
{
43+
$py.call('__main__', 'non_existing');
44+
CATCH {
45+
ok 1, 'survived calling missing Python function';
46+
when X::AdHoc {
47+
ok $_.isa('X::AdHoc'), 'got an exception for calling a missing function';
48+
is $_.Str(), "name 'non_existing' is not defined", 'exception message found for missing function';
49+
}
50+
}
51+
}
52+
{
53+
$py.run(q:heredoc/PYTHON/);
54+
class Foo:
55+
pass
56+
PYTHON
57+
my $foo = $py.call('__main__', 'Foo');
58+
$foo.non_existing;
59+
CATCH {
60+
ok 1, 'survived Python missing method';
61+
when X::AdHoc {
62+
ok $_.isa('X::AdHoc'), 'got an exception for calling missing method';
63+
is $_.Str(), "Foo instance has no attribute 'non_existing'", 'exception message found for calling missing method';
64+
}
65+
}
66+
}
67+
68+
69+
class Foo {
70+
method depart {
71+
die "foo";
72+
}
73+
}
74+
75+
$py.run(q:heredoc/PYTHON/);
76+
import logging
77+
def test_foo(foo):
78+
try:
79+
foo.depart()
80+
except Exception, e:
81+
logging.warn(e)
82+
return e.message
83+
PYTHON
84+
85+
is $py.call('__main__', 'test_foo', Foo.new), 'foo';
86+
87+
{
88+
$py.run(q:heredoc/PYTHON/);
89+
def pass_through(foo):
90+
foo.depart()
91+
PYTHON
92+
$py.call('__main__', 'pass_through', Foo.new);
93+
CATCH {
94+
ok 1, 'P6 exception made it through Python code';
95+
when X::AdHoc {
96+
ok $_.isa('X::AdHoc'), 'got an exception from method call';
97+
ok $_.Str() ~~ m/foo/, 'exception message found from method call';
98+
}
99+
}
100+
}
101+
102+
done;
103+
104+
# vim: ft=perl6

0 commit comments

Comments
 (0)