Skip to content

Commit 30006fa

Browse files
committed
Merge pull request #15 from gw/feature/check-nil-on-new
Handle memory allocation failure on python.
2 parents 89fc839 + 13b57a3 commit 30006fa

File tree

4 files changed

+54
-11
lines changed

4 files changed

+54
-11
lines changed

p/go2py_converter.go

+6
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ func newPyString(s string) *C.PyObject {
6363

6464
func newPyArray(a data.Array) (*C.PyObject, error) {
6565
pylist := C.PyList_New(C.Py_ssize_t(len(a)))
66+
if pylist == nil {
67+
return nil, getPyErr()
68+
}
6669
for i, v := range a {
6770
value, err := newPyObj(v)
6871
if err != nil {
@@ -77,6 +80,9 @@ func newPyArray(a data.Array) (*C.PyObject, error) {
7780

7881
func newPyMap(m data.Map) (*C.PyObject, error) {
7982
pydict := C.PyDict_New()
83+
if pydict == nil {
84+
return nil, getPyErr()
85+
}
8086
for k, v := range m {
8187
err := func() error {
8288
key := newPyString(k)

p/pyerror.go

+37-11
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,14 @@ PyObject* idOrNone(PyObject* o)
88
return o ? o : Py_BuildValue("");
99
}
1010
11-
PyObject* fetchPythonError()
11+
void fetchPythonError(PyObject* excInfo)
1212
{
1313
PyObject *type, *value, *traceback;
14-
PyObject* ret;
1514
1615
PyErr_Fetch(&type, &value, &traceback);
17-
ret = PyTuple_New(3);
18-
PyTuple_SetItem(ret, 0, idOrNone(type));
19-
PyTuple_SetItem(ret, 1, idOrNone(value));
20-
PyTuple_SetItem(ret, 2, idOrNone(traceback));
21-
return ret;
16+
PyTuple_SetItem(excInfo, 0, idOrNone(type));
17+
PyTuple_SetItem(excInfo, 1, idOrNone(value));
18+
PyTuple_SetItem(excInfo, 2, idOrNone(traceback));
2219
}
2320
*/
2421
import "C"
@@ -97,9 +94,29 @@ func pyObjectToPyTypeObject(p *C.PyObject) *C.PyTypeObject {
9794
return (*C.PyTypeObject)(unsafe.Pointer(p))
9895
}
9996

97+
// getPyErr returns a Python's exception as an error.
98+
// getPyErr normally returns a pyErr (see its godoc for details) and clears
99+
// exception state of the python interpreter. If the exception is a MemoryError,
100+
// getPyErr returns pyNoMemoryError (without stacktrace) and does not clears
101+
// the exception state. The easiest way to extract stacktrace for MemoryError
102+
// is calling PyErr_Print(). Note that PyErr_Print() prints to stderr.
100103
func getPyErr() error {
101-
excInfo := Object{p: C.fetchPythonError()}
104+
if isPyNoMemoryError() {
105+
// Fetching stacktrace requires some memory,
106+
// so just return an error without stacktrace.
107+
return errPyNoMemory
108+
}
109+
110+
// TODO: consider to reserve excInfo
111+
excInfo := Object{p: C.PyTuple_New(3)}
112+
if excInfo.p == nil {
113+
if isPyNoMemoryError() {
114+
return errPyNoMemory
115+
}
116+
return getPyErr()
117+
}
102118
defer excInfo.decRef()
119+
C.fetchPythonError(excInfo.p)
103120

104121
formatted, err := tracebackFormatException(excInfo)
105122
if err != nil {
@@ -134,14 +151,23 @@ func extractLineFromFormattedErrorMessage(formatted Object, n C.Py_ssize_t) stri
134151
return C.GoString(C.PyString_AsString(line))
135152
}
136153

154+
// pyErr represents an exception of python.
137155
type pyErr struct {
138-
mainMsg string
139-
syntaxErrMsg string
140-
stackTrace string
156+
mainMsg string // "main error message" (one line)
157+
syntaxErrMsg string // syntax error description for SyntaxError (zero or two lines)
158+
stackTrace string // stacktrace (zero or multiple lines)
141159
}
142160

143161
// Error returns an error message string for pyErr.
144162
// This string contains multiple lines for stacktrace.
145163
func (e *pyErr) Error() string {
146164
return e.mainMsg + "\n" + e.syntaxErrMsg + e.stackTrace
147165
}
166+
167+
func isPyNoMemoryError() bool {
168+
return C.PyErr_ExceptionMatches(C.PyExc_MemoryError) != 0
169+
}
170+
171+
// errPyNoMemory is an error value representing an allocation error on Python.
172+
// This is not a pyErr because it is difficult to extract stacktrace from Python when the heap is exhaused.
173+
var errPyNoMemory = errors.New("python interpreter failed to allocate memory")

p/pyfunc.go

+7
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ func invokeDirect(pyObj *C.PyObject, name string, args ...data.Value) (Object, e
4646
defer pyFunc.decRef()
4747

4848
pyArg := C.PyTuple_New(C.Py_ssize_t(len(args)))
49+
if pyArg == nil {
50+
ch <- &Result{res, getPyErr()}
51+
return
52+
}
4953
defer C.Py_DecRef(pyArg)
5054

5155
for i, v := range args {
@@ -152,6 +156,9 @@ func (f *ObjectFunc) callObject(arg Object) (po Object, err error) {
152156

153157
func convertArgsGo2Py(args []data.Value) (Object, error) {
154158
pyArg := C.PyTuple_New(C.Py_ssize_t(len(args)))
159+
if pyArg == nil {
160+
return Object{}, getPyErr()
161+
}
155162
shouldDecRef := true
156163
defer func() {
157164
if shouldDecRef {

p/pymodule.go

+4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ func (m *ObjectModule) NewInstance(name string, args ...data.Value) (ObjectInsta
6868
defer C.Py_DecRef(pyInstance)
6969

7070
pyArg := C.PyTuple_New(C.Py_ssize_t(len(args)))
71+
if pyArg == nil {
72+
ch <- &Result{ObjectInstance{}, getPyErr()}
73+
return
74+
}
7175
defer C.Py_DecRef(pyArg)
7276

7377
for i, v := range args {

0 commit comments

Comments
 (0)