Skip to content
This repository was archived by the owner on Mar 8, 2020. It is now read-only.

Commit 3b71f52

Browse files
authored
Merge pull request #75 from juanjux/fix/leak
Fix memory leaks (thanks @amlweems)
2 parents d0fba32 + 0fd9105 commit 3b71f52

File tree

3 files changed

+71
-10
lines changed

3 files changed

+71
-10
lines changed

bblfsh/pyuast.c

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@
22

33
#include <stdbool.h>
44
#include <stdint.h>
5+
#include <stdlib.h>
6+
#include <string.h>
57

68
#include "uast.h"
79

10+
// Used to store references to the Pyobjects instanced in String() and
11+
// ItemAt() methods. Those can't be DECREF'ed to 0 because libuast uses the
12+
// so we pass ownership to these lists and free them at the end of filter()
13+
static PyObject *stringAllocsList;
14+
static PyObject *itemAtAllocsList;
15+
816
static PyObject *Attribute(const void *node, const char *prop) {
917
PyObject *n = (PyObject *)node;
1018
return PyObject_GetAttrString(n, prop);
@@ -16,20 +24,38 @@ static PyObject *AttributeValue(const void *node, const char *prop) {
1624
}
1725

1826
static const char *String(const void *node, const char *prop) {
27+
const char *retval = NULL;
1928
PyObject *o = Attribute(node, prop);
20-
return o ? PyUnicode_AsUTF8(o) : NULL;
29+
if (o != NULL) {
30+
retval = PyUnicode_AsUTF8(o);
31+
PyList_Append(stringAllocsList, o);
32+
Py_DECREF(o);
33+
}
34+
return retval;
2135
}
2236

2337
static size_t Size(const void *node, const char *prop) {
38+
size_t retval = 0;
2439
PyObject *o = Attribute(node, prop);
25-
return o ? PySequence_Size(o) : 0;
40+
if (o != NULL) {
41+
retval = PySequence_Size(o);
42+
Py_DECREF(o);
43+
}
44+
45+
return retval;
2646
}
2747

2848
static PyObject *ItemAt(PyObject *object, int index) {
49+
PyObject *retval = NULL;
2950
PyObject *seq = PySequence_Fast(object, "expected a sequence");
30-
return PyList_GET_ITEM(seq, index);
31-
}
51+
if (seq != NULL) {
52+
retval = PyList_GET_ITEM(seq, index);
53+
PyList_Append(itemAtAllocsList, seq);
54+
Py_DECREF(seq);
55+
}
3256

57+
return retval;
58+
}
3359

3460
static const char *InternalType(const void *node) {
3561
return String(node, "internal_type");
@@ -68,8 +94,13 @@ static const char *PropertyKeyAt(const void *node, int index) {
6894
return NULL;
6995
}
7096

97+
const char *retval = NULL;
7198
PyObject *keys = PyMapping_Keys(properties);
72-
return keys ? PyUnicode_AsUTF8(ItemAt(keys, index)) : NULL;
99+
if (keys != NULL) {
100+
retval = PyUnicode_AsUTF8(ItemAt(keys, index));
101+
Py_DECREF(keys);
102+
}
103+
return retval;
73104
}
74105

75106
static const char *PropertyValueAt(const void *node, int index) {
@@ -78,8 +109,13 @@ static const char *PropertyValueAt(const void *node, int index) {
78109
return NULL;
79110
}
80111

112+
const char *retval = NULL;
81113
PyObject *values = PyMapping_Values(properties);
82-
return values ? PyUnicode_AsUTF8(ItemAt(values, index)) : NULL;
114+
if (values != NULL) {
115+
retval = PyUnicode_AsUTF8(ItemAt(values, index));
116+
Py_DECREF(values);
117+
}
118+
return retval;
83119
}
84120

85121
static uint32_t PositionValue(const void* node, const char *prop, const char *field) {
@@ -256,14 +292,20 @@ static PyObject *PyFilter(PyObject *self, PyObject *args)
256292
PyObject *obj = NULL;
257293
const char *query = NULL;
258294

259-
if (!PyArg_ParseTuple(args, "Os", &obj, &query))
295+
if (!PyArg_ParseTuple(args, "Os", &obj, &query)) {
260296
return NULL;
297+
}
298+
299+
itemAtAllocsList = PyList_New(0);
300+
stringAllocsList = PyList_New(0);
261301

262302
Nodes *nodes = UastFilter(ctx, obj, query);
263303
if (!nodes) {
264304
char *error = LastError();
265305
PyErr_SetString(PyExc_RuntimeError, error);
266306
free(error);
307+
Py_DECREF(stringAllocsList);
308+
Py_DECREF(itemAtAllocsList);
267309
return NULL;
268310
}
269311
size_t len = NodesSize(nodes);
@@ -275,7 +317,12 @@ static PyObject *PyFilter(PyObject *self, PyObject *args)
275317
PyList_SET_ITEM(list, i, node);
276318
}
277319
NodesFree(nodes);
278-
return PySeqIter_New(list);
320+
PyObject *iter = PySeqIter_New(list);
321+
Py_DECREF(list);
322+
323+
Py_DECREF(itemAtAllocsList);
324+
Py_DECREF(stringAllocsList);
325+
return iter;
279326
}
280327

281328
static PyMethodDef extension_methods[] = {

bblfsh/test.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,20 @@ def _validate_resp(self, resp):
188188
# Sometimes its fully qualified, sometimes is just "Node"... ditto
189189
assert(resp.uast.__class__.__name__.endswith('Node'))
190190

191+
def testManyFilters(self):
192+
root = self.client.parse(__file__).uast
193+
root.properties['k1'] = 'v2'
194+
root.properties['k2'] = 'v1'
195+
196+
import resource
197+
before = resource.getrusage(resource.RUSAGE_SELF)
198+
for _ in range(100):
199+
filter(root, "//*[@roleIdentifier]")
200+
after = resource.getrusage(resource.RUSAGE_SELF)
201+
202+
# Check that memory usage has not doubled after running the filter
203+
self.assertLess(after[2] / before[2], 2.0)
204+
191205
def _validate_filter(self, resp):
192206
results = filter(resp.uast, "//Import[@roleImport and @roleDeclaration]//alias")
193207
self.assertEqual(next(results).token, "os")

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from setuptools import setup, find_packages, Extension
66
from setuptools.command.build_ext import build_ext
77

8-
LIBUAST_VERSION = "v1.6.0"
8+
LIBUAST_VERSION = "v1.8.2"
99
SDK_VERSION = "v1.8.0"
1010
SDK_MAJOR = SDK_VERSION.split('.')[0]
1111
PYTHON = "python3"
@@ -134,7 +134,7 @@ def main():
134134
},
135135
name="bblfsh",
136136
description="Fetches Universal Abstract Syntax Trees from Babelfish.",
137-
version="2.8.1",
137+
version="2.8.2",
138138
license="Apache 2.0",
139139
author="source{d}",
140140
author_email="language-analysis@sourced.tech",

0 commit comments

Comments
 (0)