Skip to content

Commit 011631e

Browse files
committed
Revamp of cpychecker warnings
This commit combines several changes, all of which affect the "gold" output for the selftests. * RefcountValue: * eliminate int "min_external" attribute in favor of a WithinRange "external" attribute, capturing the range of possible external references, rather than just a lower bound. * revamp __str__ to eliminate the "0 + N where N >= " since this was confusing to everyone (including me). Instead, display references as owned vs borrowed references e.g. * "refs: 1 owned" * "refs: 0 owned 1 borrowed" resorting to: * "refs: 0 owned + B borrowed where 1 <= B <= 0x80000000" and the like where necessary (though this does not yet occur in the test suite). * error messages: * emit_refcount_warning * combine the various notes into one; tweak some wording; eliminate the "for some N" gobblegook in favor of str() of the refcount value. * use "desc" rather than "v_obj.name" since is more likely to be in terms that the user can understand (e.g. "*dictA" rather than "PyDictObject"). * mention " since nothing references it" when dealing with an expected refcount of 0. * add "memory leak: " and 'future use-after-free: " prefixes to reports about refcounting errors, to better indicate the issue. Also, regenerate all the expected testcase results (using 4.8.2-7) This also strips out exact column numbering from the results, in favor of "nn", due to this in run-test-suite.py: # GCC 4.7 tracks macro expansions, and this can change the column # numbers in error reports:
1 parent fce0941 commit 011631e

File tree

250 files changed

+2180
-2213
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

250 files changed

+2180
-2213
lines changed

libcpychecker/refcounts.py

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -276,52 +276,72 @@ class RefcountValue(AbstractValue):
276276
277277
'relvalue' is all of the references owned within this function.
278278
279-
'min_external' is a lower bound on all references owned outside the
279+
'external' is a WithinRange, a bound on all references owned outside the
280280
scope of this function.
281281
282-
The actual value of ob_refcnt >= (relvalue + min_external)
282+
The actual value of ob_refcnt = (relvalue + external)
283+
hence:
284+
285+
(relvalue + external.minvalue) <= ob_refcnt <= (relvalue + external.minvalue)
283286
284287
Examples:
285288
286-
- an argument passed in a a borrowed ref starts with (0, 1), in that
289+
- an argument passed in a borrowed ref starts with (0, [1, 1]), in that
287290
the function doesn't own any refs on it, but it has a refcount of at
288291
least 1, due to refs we know nothing about.
289292
290-
- a newly constructed object gets (1, 0): we own a reference on it,
293+
- a newly constructed object gets (1, [0, 0]): we own a reference on it,
291294
and we don't know if there are any external refs on it.
292295
"""
293-
__slots__ = ('r_obj', 'relvalue', 'min_external')
296+
__slots__ = ('r_obj', 'relvalue', 'external')
294297

295-
def __init__(self, loc, r_obj, relvalue, min_external):
298+
def __init__(self, loc, r_obj, relvalue, external):
296299
if loc:
297300
check_isinstance(loc, gcc.Location)
298301
if r_obj:
299302
check_isinstance(r_obj, Region)
303+
check_isinstance(external, WithinRange)
300304
AbstractValue.__init__(self, get_Py_ssize_t().type, loc)
301305
self.r_obj = r_obj
302306
self.relvalue = relvalue
303-
self.min_external = min_external
307+
self.external = external
304308

305309
@classmethod
306310
def new_ref(cls, loc, r_obj):
307311
return RefcountValue(loc, r_obj,
308312
relvalue=1,
309-
min_external=0)
313+
external=WithinRange(get_Py_ssize_t().type, loc,
314+
0, 0))
310315

311316
@classmethod
312317
def borrowed_ref(cls, loc, r_obj):
313318
return RefcountValue(loc, r_obj,
314319
relvalue=0,
315-
min_external=1)
320+
external=WithinRange(get_Py_ssize_t().type, loc,
321+
1, 1))
316322

317323
def get_min_value(self):
318-
return self.relvalue + self.min_external
324+
return self.relvalue + self.external.minvalue
319325

320326
def __str__(self):
321-
return 'refs: %i + N where N >= %i' % (self.relvalue, self.min_external)
327+
# Try to present the simplest possible string representation of
328+
# a refcount:
329+
if self.external.minvalue == self.external.maxvalue:
330+
# Known value for external refcount
331+
if self.external.minvalue == 0:
332+
# We know there are no external refs; don't display it
333+
# at all, rather than "0":
334+
return 'refs: %i owned' % self.relvalue
335+
else:
336+
# We know of an exact number of external refs; display
337+
# it as borrowed:
338+
return ('refs: %i owned, %i borrowed'
339+
% (self.relvalue, self.external.minvalue))
340+
return ('refs: %i owned + B borrowed where %s'
341+
% (self.relvalue, str(self.external).replace('val', 'B')))
322342

323343
def __repr__(self):
324-
return 'RefcountValue(%i, %i)' % (self.relvalue, self.min_external)
344+
return 'RefcountValue(%i, %r)' % (self.relvalue, self.external)
325345

326346
def get_referrers_as_json(self, state):
327347
# FIXME:
@@ -346,7 +366,7 @@ def get_referrers_as_json(self, state):
346366

347367
def json_fields(self, state):
348368
actual = OrderedDict(refs_we_own=self.relvalue,
349-
lower_bound_of_other_refs=self.min_external)
369+
lower_bound_of_other_refs=self.external.minvalue)
350370
exp_refs = self.get_referrers_as_json(state)
351371
expected = dict(pointers_to_this=exp_refs)
352372
return dict(actual_ob_refcnt=actual,
@@ -356,10 +376,10 @@ def eval_binop(self, exprcode, rhs, rhsdesc, gcctype, loc):
356376
if isinstance(rhs, ConcreteValue):
357377
if exprcode == gcc.PlusExpr:
358378
return RefcountValue(loc, self.r_obj,
359-
self.relvalue + rhs.value, self.min_external)
379+
self.relvalue + rhs.value, self.external)
360380
elif exprcode == gcc.MinusExpr:
361381
return RefcountValue(loc, self.r_obj,
362-
self.relvalue - rhs.value, self.min_external)
382+
self.relvalue - rhs.value, self.external)
363383
return UnknownValue.make(gcctype, loc)
364384

365385
def eval_comparison(self, opname, rhs, rhsdesc):
@@ -566,7 +586,7 @@ def _incref_internal(oldvalue):
566586
return RefcountValue(loc,
567587
pyobjectptr.region,
568588
oldvalue.relvalue + 1,
569-
oldvalue.min_external)
589+
oldvalue.external)
570590
self.change_refcount(pyobjectptr,
571591
loc,
572592
_incref_internal)
@@ -580,7 +600,10 @@ def _incref_external(oldvalue):
580600
return RefcountValue(loc,
581601
pyobjectptr.region,
582602
oldvalue.relvalue,
583-
oldvalue.min_external + 1)
603+
WithinRange(oldvalue.external.gcctype,
604+
loc,
605+
oldvalue.external.minvalue + 1,
606+
oldvalue.external.maxvalue + 1))
584607
self.change_refcount(pyobjectptr,
585608
loc,
586609
_incref_external)
@@ -594,7 +617,7 @@ def _decref_internal(oldvalue):
594617
return RefcountValue(loc,
595618
pyobjectptr.region,
596619
oldvalue.relvalue - 1,
597-
oldvalue.min_external)
620+
oldvalue.external)
598621
check_isinstance(pyobjectptr, PointerToRegion)
599622
v_ob_refcnt = self.change_refcount(pyobjectptr,
600623
loc,
@@ -737,7 +760,10 @@ def _steal_ref(v_old):
737760
return RefcountValue(loc,
738761
pyobjectptr.region,
739762
v_old.relvalue - 1,
740-
v_old.min_external + 1)
763+
WithinRange(v_old.external.gcctype,
764+
loc,
765+
v_old.external.minvalue + 1,
766+
v_old.external.maxvalue + 1))
741767
check_isinstance(pyobjectptr, PointerToRegion)
742768
self.change_refcount(pyobjectptr,
743769
loc,
@@ -4019,26 +4045,28 @@ def emit_refcount_warning(msg,
40194045
exp_refcnt, exp_refs, v_ob_refcnt, r_obj, desc,
40204046
trace, endstate, fun, rep):
40214047
w = rep.make_warning(fun, endstate.get_gcc_loc(fun), msg)
4022-
w.add_note(endstate.get_gcc_loc(fun),
4023-
('was expecting final ob_refcnt to be N + %i (for some unknown N)'
4024-
% exp_refcnt))
4025-
if exp_refcnt > 0:
4026-
w.add_note(endstate.get_gcc_loc(fun),
4027-
('due to object being referenced by: %s'
4028-
% ', '.join(exp_refs)))
4029-
w.add_note(endstate.get_gcc_loc(fun),
4030-
('but final ob_refcnt is N + %i'
4031-
% v_ob_refcnt.relvalue))
4048+
40324049
# For dynamically-allocated objects, indicate where they
40334050
# were allocated:
40344051
if isinstance(r_obj, RegionOnHeap):
40354052
alloc_loc = r_obj.alloc_stmt.loc
40364053
if alloc_loc:
40374054
w.add_note(r_obj.alloc_stmt.loc,
4038-
('%s allocated at: %s'
4039-
% (r_obj.name,
4055+
('%s was allocated at: %s'
4056+
% (desc,
40404057
get_src_for_loc(alloc_loc))))
40414058

4059+
details = ('was expecting final owned ob_refcnt of %s to be %i'
4060+
% (desc, exp_refcnt))
4061+
if exp_refcnt > 0:
4062+
details += (' due to object being referenced by: %s'
4063+
% ', '.join(exp_refs))
4064+
else:
4065+
details += (' since nothing references it')
4066+
details += ' but final ob_refcnt is %s' % v_ob_refcnt
4067+
w.add_note(endstate.get_gcc_loc(fun),
4068+
details)
4069+
40424070
# Summarize the control flow we followed through the function:
40434071
if 1:
40444072
annotator = RefcountAnnotator(r_obj, desc)
@@ -4102,14 +4130,14 @@ def check_refcount_for_one_object(r_obj, v_ob_refcnt, v_return,
41024130
if isinstance(v_ob_refcnt, RefcountValue):
41034131
if v_ob_refcnt.relvalue > exp_refcnt:
41044132
# Refcount is too high:
4105-
w = emit_refcount_warning('ob_refcnt of %s is %i too high'
4133+
w = emit_refcount_warning('memory leak: ob_refcnt of %s is %i too high'
41064134
% (desc,
41074135
v_ob_refcnt.relvalue - exp_refcnt),
41084136
exp_refcnt, exp_refs, v_ob_refcnt, r_obj, desc,
41094137
trace, endstate, fun, rep)
41104138
elif v_ob_refcnt.relvalue < exp_refcnt:
41114139
# Refcount is too low:
4112-
w = emit_refcount_warning('ob_refcnt of %s is %i too low'
4140+
w = emit_refcount_warning('future use-after-free: ob_refcnt of %s is %i too low'
41134141
% (desc,
41144142
exp_refcnt - v_ob_refcnt.relvalue),
41154143
exp_refcnt, exp_refs, v_ob_refcnt, r_obj, desc,

tests/cpychecker/PyArg_Parse/incorrect_codes_S_and_U/stderr.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
tests/cpychecker/PyArg_Parse/incorrect_codes_S_and_U/input.c: In function 'incorrect_usage_of_S_and_U':
2-
tests/cpychecker/PyArg_Parse/incorrect_codes_S_and_U/input.c:33:21: warning: Mismatching type in call to PyArg_Parse with format code "SU" [enabled by default]
1+
In function 'incorrect_usage_of_S_and_U':
2+
tests/cpychecker/PyArg_Parse/incorrect_codes_S_and_U/input.c:33:nn: warning: Mismatching type in call to PyArg_Parse with format code "SU" [enabled by default]
33
argument 3 ("&val1") had type
44
"int *" (pointing to 32 bits)
55
but was expecting
66
one of "struct PyStringObject * *" or "struct PyObject * *"
77
for format code "S"
8-
tests/cpychecker/PyArg_Parse/incorrect_codes_S_and_U/input.c:33:21: warning: Mismatching type in call to PyArg_Parse with format code "SU" [enabled by default]
8+
tests/cpychecker/PyArg_Parse/incorrect_codes_S_and_U/input.c:33:nn: warning: Mismatching type in call to PyArg_Parse with format code "SU" [enabled by default]
99
argument 4 ("&val2") had type
1010
"int *" (pointing to 32 bits)
1111
but was expecting

tests/cpychecker/PyArg_ParseTuple/code_O_bang/stderr.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
tests/cpychecker/PyArg_ParseTuple/code_O_bang/input.c: In function 'handle_subclasses':
2-
tests/cpychecker/PyArg_ParseTuple/code_O_bang/input.c:85:19: warning: Mismatching type in call to PyArg_ParseTuple with format code "O!O!O!O!O!O!O!" [enabled by default]
1+
In function 'handle_subclasses':
2+
tests/cpychecker/PyArg_ParseTuple/code_O_bang/input.c:85:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "O!O!O!O!O!O!O!" [enabled by default]
33
argument 10 ("long_obj") had type
44
"struct PyLongObject *"
55
but was expecting
66
"struct PyUnicodeObject * *" (based on PyTypeObject: 'PyUnicode_Type') or "struct PyObject * *"
77
for format code "O!"
8-
tests/cpychecker/PyArg_ParseTuple/code_O_bang/input.c:90:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "O!O!O!O!O!O!O!" [enabled by default]
8+
tests/cpychecker/PyArg_ParseTuple/code_O_bang/input.c:90:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "O!O!O!O!O!O!O!" [enabled by default]
99
argument 12 ("&unknown_obj") had type
1010
"struct UnknownObject * *"
1111
but was expecting
1212
""struct PyObject * *"" (unable to determine relevant PyTypeObject)
1313
for format code "O!"
14-
tests/cpychecker/PyArg_ParseTuple/code_O_bang/input.c:90:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "O!O!O!O!O!O!O!" [enabled by default]
14+
tests/cpychecker/PyArg_ParseTuple/code_O_bang/input.c:90:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "O!O!O!O!O!O!O!" [enabled by default]
1515
argument 14 ("&extension_obj") had type
1616
"struct ExtensionObject * *"
1717
but was expecting

tests/cpychecker/PyArg_ParseTuple/code_s/incorrect-constness/stderr.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
tests/cpychecker/PyArg_ParseTuple/code_s/incorrect-constness/input.c: In function 'test':
2-
tests/cpychecker/PyArg_ParseTuple/code_s/incorrect-constness/input.c:34:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "s" [enabled by default]
1+
In function 'test':
2+
tests/cpychecker/PyArg_ParseTuple/code_s/incorrect-constness/input.c:34:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "s" [enabled by default]
33
argument 3 ("&str") had type
44
"char * *"
55
but was expecting

tests/cpychecker/PyArg_ParseTuple/incorrect_code_z_hash/stderr.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
tests/cpychecker/PyArg_ParseTuple/incorrect_code_z_hash/input.c: In function 'incorrect_code_z_hash':
2-
tests/cpychecker/PyArg_ParseTuple/incorrect_code_z_hash/input.c:34:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "z#" [enabled by default]
1+
In function 'incorrect_code_z_hash':
2+
tests/cpychecker/PyArg_ParseTuple/incorrect_code_z_hash/input.c:34:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "z#" [enabled by default]
33
argument 4 ("&len") had type
44
"float *" (pointing to 32 bits)
55
but was expecting

tests/cpychecker/PyArg_ParseTuple/incorrect_codes_S_and_U/stderr.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
tests/cpychecker/PyArg_ParseTuple/incorrect_codes_S_and_U/input.c: In function 'incorrect_usage_of_S_and_U':
2-
tests/cpychecker/PyArg_ParseTuple/incorrect_codes_S_and_U/input.c:29:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "SU" [enabled by default]
1+
In function 'incorrect_usage_of_S_and_U':
2+
tests/cpychecker/PyArg_ParseTuple/incorrect_codes_S_and_U/input.c:29:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "SU" [enabled by default]
33
argument 3 ("&val1") had type
44
"int *" (pointing to 32 bits)
55
but was expecting
66
one of "struct PyStringObject * *" or "struct PyObject * *"
77
for format code "S"
8-
tests/cpychecker/PyArg_ParseTuple/incorrect_codes_S_and_U/input.c:29:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "SU" [enabled by default]
8+
tests/cpychecker/PyArg_ParseTuple/incorrect_codes_S_and_U/input.c:29:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "SU" [enabled by default]
99
argument 4 ("&val2") had type
1010
"int *" (pointing to 32 bits)
1111
but was expecting

tests/cpychecker/PyArg_ParseTuple/incorrect_converters/stderr.txt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,60 @@
1-
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c: In function 'incorrect_usages_of_converter':
2-
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:36:26: warning: Not enough arguments in call to PyArg_ParseTuple with format string "O&"
1+
In function 'incorrect_usages_of_converter':
2+
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:36:nn: warning: Not enough arguments in call to PyArg_ParseTuple with format string "O&"
33
expected 2 extra arguments:
44
"int (converter)(PyObject *, T*)" for some type T
55
"T*" for some type T
66
but got none
77
[enabled by default]
8-
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:39:26: warning: Not enough arguments in call to PyArg_ParseTuple with format string "O&"
8+
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:39:nn: warning: Not enough arguments in call to PyArg_ParseTuple with format string "O&"
99
expected 2 extra arguments:
1010
"int (converter)(PyObject *, T*)" for some type T
1111
"T*" for some type T
1212
but got 1:
1313
"int"
1414
[enabled by default]
15-
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:44:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
15+
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:44:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
1616
argument 3 ("42") had type
1717
"int"
1818
but was expecting
1919
"int (converter)(PyObject *, T*)" for some type T
2020
for format code "O&"
21-
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:49:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
21+
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:49:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
2222
argument 3 ("&i0") had type
2323
"int *" (pointing to 32 bits)
2424
but was expecting
2525
"int (converter)(PyObject *, T*)" for some type T
2626
for format code "O&"
27-
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:54:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
27+
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:54:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
2828
argument 3 ("not_returning_int") had type
2929
"void (*fn) ()"
3030
but was expecting
3131
"int (converter)(PyObject *, T*)" for some type T
3232
for format code "O&"
33-
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:57:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
33+
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:57:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
3434
argument 3 ("not_enough_args") had type
3535
"int (*fn) (struct PyObject *)"
3636
but was expecting
3737
"int (converter)(PyObject *, T*)" for some type T
3838
for format code "O&"
39-
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:60:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
39+
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:60:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
4040
argument 3 ("too_many_args") had type
4141
"int (*fn) (struct PyObject *, int *, int)"
4242
but was expecting
4343
"int (converter)(PyObject *, T*)" for some type T
4444
for format code "O&"
45-
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:63:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
45+
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:63:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
4646
argument 3 ("not_taking_PyObjectPtr") had type
4747
"int (*fn) (int, int)"
4848
but was expecting
4949
"int (converter)(PyObject *, T*)" for some type T
5050
for format code "O&"
51-
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:68:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
51+
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:68:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
5252
argument 4 ("42") had type
5353
"int"
5454
but was expecting
5555
"Py_ssize_t *" (pointing to 64 bits) (from second argument of "int (*fn) (struct PyObject *, Py_ssize_t *)")
5656
for format code "O&"
57-
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:73:26: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
57+
tests/cpychecker/PyArg_ParseTuple/incorrect_converters/input.c:73:nn: warning: Mismatching type in call to PyArg_ParseTuple with format code "O&" [enabled by default]
5858
argument 4 ("&i0") had type
5959
"int *" (pointing to 32 bits)
6060
but was expecting
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
tests/cpychecker/PyArg_ParseTuple/keywords/input.c: In function 'parse_to_a_typedef':
2-
tests/cpychecker/PyArg_ParseTuple/keywords/input.c:32:37: warning: keywords to PyArg_ParseTupleAndKeywords are not NULL-terminated [enabled by default]
1+
In function 'parse_to_a_typedef':
2+
tests/cpychecker/PyArg_ParseTuple/keywords/input.c:32:nn: warning: keywords to PyArg_ParseTupleAndKeywords are not NULL-terminated [enabled by default]

0 commit comments

Comments
 (0)