Skip to content

Commit 0ba17a5

Browse files
committed
Safer handling of string arrays
We need to keep hold of the strings which we create. We must also hold on to the array of strings which we assing to our git_strarray. We were not doing the latter, which meant that our strings may have been freed too early, leaving us with with memory access errors (though often not leading to a crash due to the custom allocator in python). As we need to keep hold of two/three pieces of information, this looks like a good place to introduce a context manager. This allows us to keep these pointers alive without burdening the call sites with a return of multiple objects they have no use for.
1 parent 7f21f6e commit 0ba17a5

File tree

3 files changed

+37
-37
lines changed

3 files changed

+37
-37
lines changed

pygit2/index.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from _pygit2 import Oid, Tree, Diff
3333
from .errors import check_error
3434
from .ffi import ffi, C
35-
from .utils import is_string, strings_to_strarray, to_bytes, to_str
35+
from .utils import is_string, to_bytes, to_str, StrArray
3636

3737

3838
class Index(object):
@@ -175,9 +175,9 @@ def add_all(self, pathspecs=[]):
175175
If pathspecs are specified, only files matching those pathspecs will
176176
be added.
177177
"""
178-
arr, refs = strings_to_strarray(pathspecs)
179-
err = C.git_index_add_all(self._index, arr, 0, ffi.NULL, ffi.NULL)
180-
check_error(err, True)
178+
with StrArray(pathspecs) as arr:
179+
err = C.git_index_add_all(self._index, arr, 0, ffi.NULL, ffi.NULL)
180+
check_error(err, True)
181181

182182
def add(self, path_or_entry):
183183
"""add([path|entry])

pygit2/remote.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from .ffi import ffi, C
3535
from .credentials import KeypairFromAgent
3636
from .refspec import Refspec
37-
from .utils import to_bytes, strarray_to_strings, strings_to_strarray
37+
from .utils import to_bytes, strarray_to_strings, StrArray
3838

3939

4040
def maybe_string(ptr):
@@ -253,9 +253,9 @@ def fetch_refspecs(self):
253253

254254
@fetch_refspecs.setter
255255
def fetch_refspecs(self, l):
256-
arr, refs = strings_to_strarray(l)
257-
err = C.git_remote_set_fetch_refspecs(self._remote, arr)
258-
check_error(err)
256+
with StrArray(l) as arr:
257+
err = C.git_remote_set_fetch_refspecs(self._remote, arr)
258+
check_error(err)
259259

260260
@property
261261
def push_refspecs(self):
@@ -269,9 +269,9 @@ def push_refspecs(self):
269269

270270
@push_refspecs.setter
271271
def push_refspecs(self, l):
272-
arr, refs = strings_to_strarray(l)
273-
err = C.git_remote_set_push_refspecs(self._remote, arr)
274-
check_error(err)
272+
with StrArray(l) as arr:
273+
err = C.git_remote_set_push_refspecs(self._remote, arr)
274+
check_error(err)
275275

276276
def add_fetch(self, spec):
277277
"""add_fetch(refspec)
@@ -305,7 +305,6 @@ def push(self, specs, signature=None, message=None):
305305
err = C.git_remote_init_callbacks(defaultcallbacks, 1)
306306
check_error(err)
307307

308-
refspecs, refspecs_refs = strings_to_strarray(specs)
309308
if signature:
310309
sig_cptr = ffi.new('git_signature **')
311310
ffi.buffer(sig_cptr)[:] = signature._pointer[:]
@@ -333,8 +332,9 @@ def push(self, specs, signature=None, message=None):
333332
raise
334333

335334
try:
336-
err = C.git_remote_push(self._remote, refspecs, ffi.NULL, sig_ptr, to_bytes(message))
337-
check_error(err)
335+
with StrArray(specs) as refspecs:
336+
err = C.git_remote_push(self._remote, refspecs, ffi.NULL, sig_ptr, to_bytes(message))
337+
check_error(err)
338338
finally:
339339
self._self_handle = None
340340

pygit2/utils.py

+23-23
Original file line numberDiff line numberDiff line change
@@ -50,34 +50,34 @@ def strarray_to_strings(arr):
5050
return l
5151

5252

53-
def strings_to_strarray(l):
54-
"""Convert a list of strings to a git_strarray
53+
class StrArray(object):
54+
"""A git_strarray wrapper
5555
56-
We return first the git_strarray* you can pass to libgit2 and a
57-
list of references to the memory, which we must keep around for as
58-
long as the git_strarray must live.
59-
"""
56+
Use this in order to get a git_strarray* to pass to libgit2 out of a
57+
list of strings. This has a context manager, which you should use, e.g.
6058
61-
if not isinstance(l, list):
62-
raise TypeError("Value must be a list")
59+
with StrArray(list_of_strings) as arr:
60+
C.git_function_that_takes_strarray(arr)
61+
"""
6362

64-
arr = ffi.new('git_strarray *')
65-
strings = ffi.new('char *[]', len(l))
63+
def __init__(self, l):
64+
if not isinstance(l, list):
65+
raise TypeError("Value must be a list")
6666

67-
# We need refs in order to keep a reference to the value returned
68-
# by the ffi.new(). Otherwise, they will be freed and the memory
69-
# re-used, with less than great consequences.
70-
refs = [None] * len(l)
67+
arr = ffi.new('git_strarray *')
68+
strings = [None] * len(l)
69+
for i in range(len(l)):
70+
if not is_string(l[i]):
71+
raise TypeError("Value must be a string")
7172

72-
for i in range(len(l)):
73-
if not is_string(l[i]):
74-
raise TypeError("Value must be a string")
73+
strings[i] = ffi.new('char []', to_bytes(l[i]))
7574

76-
s = ffi.new('char []', to_bytes(l[i]))
77-
refs[i] = s
78-
strings[i] = s
75+
self._arr = ffi.new('char *[]', strings)
76+
self._strings = strings
77+
self.array = ffi.new('git_strarray *', [self._arr, len(strings)])
7978

80-
arr.strings = strings
81-
arr.count = len(l)
79+
def __enter__(self):
80+
return self.array
8281

83-
return arr, refs
82+
def __exit__(self, type, value, traceback):
83+
pass

0 commit comments

Comments
 (0)