commit a680a5763be563c266fef9c11830d6750534b34e Author: David Malcolm dmalcolm@redhat.com Date: Mon Sep 26 17:35:25 2011 -0400
cpychecker: improve detection of uninitialized data
Generalize test for deference of an uninitialized pointer: also warn about comparisons that involve uninitialized data, and function calls in which uninitialized data is passed as an argument to a function.
libcpychecker/absinterp.py | 39 ++++++++++--- .../read-through-uninitialized-ptr/stderr.txt | 2 +- .../read-through-uninitialized-ptr/stdout.txt | 4 +- .../uninitialized_data/comparison/input.c | 61 ++++++++++++++++++++ .../uninitialized_data/comparison/script.py | 24 ++++++++ .../uninitialized_data/comparison/stderr.txt | 8 +++ .../uninitialized_data/comparison/stdout.txt | 40 +++++++++++++ .../uninitialized_data/function_arg/input.c | 57 ++++++++++++++++++ .../uninitialized_data/function_arg/script.py | 24 ++++++++ .../uninitialized_data/function_arg/stderr.txt | 8 +++ .../uninitialized_data/function_arg/stdout.txt | 40 +++++++++++++ 11 files changed, 296 insertions(+), 11 deletions(-) --- diff --git a/libcpychecker/absinterp.py b/libcpychecker/absinterp.py index 26899be..af32f4b 100644 --- a/libcpychecker/absinterp.py +++ b/libcpychecker/absinterp.py @@ -79,7 +79,7 @@ class AbstractValue: """ Return a boolean, or None (meaning we don't know) """ - raise NotImplementedError + raise NotImplementedError("is_equal for %s" % self)
class UnknownValue(AbstractValue): """ @@ -203,6 +203,10 @@ class UninitializedData(AbstractValue): else: return 'uninitialized data'
+ def is_equal(self, rhs): + # We don't know: + return None + class PredictedError(Exception): pass
@@ -229,16 +233,18 @@ class PredictedValueError(PredictedError): self.value = value self.isdefinite = isdefinite
-class UninitializedPtrDereference(PredictedValueError): - def __init__(self, state, expr, ptr): +class UsageOfUninitializedData(PredictedValueError): + def __init__(self, state, expr, value, desc): check_isinstance(state, State) check_isinstance(expr, gcc.Tree) - check_isinstance(ptr, AbstractValue) - PredictedValueError.__init__(self, state, expr, ptr, True) + check_isinstance(value, AbstractValue) + PredictedValueError.__init__(self, state, expr, value, True) + check_isinstance(desc, str) + self.desc = desc
def __str__(self): - return ('dereferencing uninitialized pointer (%s) at %s' - % (self.expr, self.state.loc.get_stmt().loc)) + return ('%s: %s at %s' + % (self.desc, self.expr, self.state.loc.get_stmt().loc))
class NullPtrDereference(PredictedValueError): def __init__(self, state, expr, ptr, isdefinite): @@ -964,7 +970,8 @@ class State: check_isinstance(ptr, AbstractValue)
if isinstance(ptr, UninitializedData): - raise UninitializedPtrDereference(self, expr, ptr) + raise UsageOfUninitializedData(self, expr, ptr, + 'dereferencing uninitialized pointer')
if isinstance(ptr, ConcreteValue): if ptr.is_null_ptr(): @@ -1146,6 +1153,14 @@ class State: meth = getattr(facet, 'impl_%s' % fnname) # Evaluate the arguments: args = self.eval_stmt_args(stmt) + + # Check for uninitialized data: + for i, arg in enumerate(args): + if isinstance(arg, UninitializedData): + raise UsageOfUninitializedData(self, stmt.args[i], + arg, + 'passing uninitialized data as argument %i to function' % (i + 1)) + # Call the facet's method: return meth(stmt, *args)
@@ -1298,6 +1313,14 @@ class State: if result is not None: return not result
+ # Detect usage of uninitialized data: + if isinstance(lhs, UninitializedData): + raise UsageOfUninitializedData(self, expr_lhs, lhs, + 'comparison against uninitialized data') + if isinstance(rhs, UninitializedData): + raise UsageOfUninitializedData(self, expr_rhs, rhs, + 'comparison against uninitialized data') + # Specialcasing: comparison of unknown ptr with NULL: if (isinstance(expr_lhs, gcc.VarDecl) and isinstance(expr_rhs, gcc.IntegerCst) diff --git a/tests/cpychecker/absinterp/read-through-uninitialized-ptr/stderr.txt b/tests/cpychecker/absinterp/read-through-uninitialized-ptr/stderr.txt index 72a0877..3b54349 100644 --- a/tests/cpychecker/absinterp/read-through-uninitialized-ptr/stderr.txt +++ b/tests/cpychecker/absinterp/read-through-uninitialized-ptr/stderr.txt @@ -1,3 +1,3 @@ tests/cpychecker/absinterp/read-through-uninitialized-ptr/input.c: In function 'test_read_through_uninitialized_pointer': -tests/cpychecker/absinterp/read-through-uninitialized-ptr/input.c:35:5: error: dereferencing uninitialized pointer (coord_ptr->x) at tests/cpychecker/absinterp/read-through-uninitialized-ptr/input.c:35 +tests/cpychecker/absinterp/read-through-uninitialized-ptr/input.c:35:5: error: dereferencing uninitialized pointer: coord_ptr->x at tests/cpychecker/absinterp/read-through-uninitialized-ptr/input.c:35 tests/cpychecker/absinterp/read-through-uninitialized-ptr/input.c:32:1: note: graphical error report for function 'test_read_through_uninitialized_pointer' written out to 'tests/cpychecker/absinterp/read-through-uninitialized-ptr/input.c.test_read_through_uninitialized_pointer-refcount-errors.html' diff --git a/tests/cpychecker/absinterp/read-through-uninitialized-ptr/stdout.txt b/tests/cpychecker/absinterp/read-through-uninitialized-ptr/stdout.txt index b9da615..4ec1576 100644 --- a/tests/cpychecker/absinterp/read-through-uninitialized-ptr/stdout.txt +++ b/tests/cpychecker/absinterp/read-through-uninitialized-ptr/stdout.txt @@ -1,6 +1,6 @@ Trace 0: Transitions: - error: UninitializedPtrDereference() - error: dereferencing uninitialized pointer (coord_ptr->x) at tests/cpychecker/absinterp/read-through-uninitialized-ptr/input.c:35 + error: UsageOfUninitializedData() + error: dereferencing uninitialized pointer: coord_ptr->x at tests/cpychecker/absinterp/read-through-uninitialized-ptr/input.c:35 Exception: (struct PyObject *)0 from tests/cpychecker/absinterp/read-through-uninitialized-ptr/input.c:32 diff --git a/tests/cpychecker/refcounts/uninitialized_data/comparison/input.c b/tests/cpychecker/refcounts/uninitialized_data/comparison/input.c new file mode 100644 index 0000000..2a98d1a --- /dev/null +++ b/tests/cpychecker/refcounts/uninitialized_data/comparison/input.c @@ -0,0 +1,61 @@ +/* + Copyright 2011 David Malcolm dmalcolm@redhat.com + Copyright 2011 Red Hat, Inc. + + This is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see + http://www.gnu.org/licenses/. +*/ + +#include <Python.h> + +/* + Verify that the checker can cope with various incorrect actions on an + uninitialized pointer +*/ + +PyObject * +test(int len) +{ + PyObject *result = NULL; + PyObject *item; /* never initialized */ + int i; + + result = PyList_New(len); + if (!result) { + goto error; + } + + for (i = 0; i < len; i++) { + /* Erroneous comparison against uninitialized data: */ + if (!item) { + goto error; + } + /* Erroneous call using uninitialized data: */ + PyList_SetItem(result, i, item); + } + + return result; + + error: + Py_XDECREF(result); + return NULL; +} + +/* + PEP-7 +Local variables: +c-basic-offset: 4 +indent-tabs-mode: nil +End: +*/ diff --git a/tests/cpychecker/refcounts/uninitialized_data/comparison/script.py b/tests/cpychecker/refcounts/uninitialized_data/comparison/script.py new file mode 100644 index 0000000..e469f23 --- /dev/null +++ b/tests/cpychecker/refcounts/uninitialized_data/comparison/script.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright 2011 David Malcolm dmalcolm@redhat.com +# Copyright 2011 Red Hat, Inc. +# +# This is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# http://www.gnu.org/licenses/. + +import gcc +from libcpychecker import main + +main(verify_refcounting=True, + show_traces=False, + dump_traces=True) diff --git a/tests/cpychecker/refcounts/uninitialized_data/comparison/stderr.txt b/tests/cpychecker/refcounts/uninitialized_data/comparison/stderr.txt new file mode 100644 index 0000000..4ae11f9 --- /dev/null +++ b/tests/cpychecker/refcounts/uninitialized_data/comparison/stderr.txt @@ -0,0 +1,8 @@ +tests/cpychecker/refcounts/uninitialized_data/comparison/input.c: In function 'test': +tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:41:5: error: comparison against uninitialized data: gcc.VarDecl('item') at tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:41 +tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:34:12: note: when PyList_New() succeeds at: result = PyList_New(len); +tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:35:8: note: when taking False path at: if (!result) { +tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:39:12: note: reaching: for (i = 0; i < len; i++) { +tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:39:5: note: when taking True path at: for (i = 0; i < len; i++) { +tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:41:5: note: reaching: if (!item) { +tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:29:1: note: graphical error report for function 'test' written out to 'tests/cpychecker/refcounts/uninitialized_data/comparison/input.c.test-refcount-errors.html' diff --git a/tests/cpychecker/refcounts/uninitialized_data/comparison/stdout.txt b/tests/cpychecker/refcounts/uninitialized_data/comparison/stdout.txt new file mode 100644 index 0000000..bacb664 --- /dev/null +++ b/tests/cpychecker/refcounts/uninitialized_data/comparison/stdout.txt @@ -0,0 +1,40 @@ +Trace 0: + Transitions: + 'PyList_New() succeeds' + 'taking False path' + 'taking True path' + error: UsageOfUninitializedData() + error: comparison against uninitialized data: gcc.VarDecl('item') at tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:41 + PyListObject allocated at tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:34: + repr(): RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/comparison/input.c', line=34)) + str(): PyListObject allocated at tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:34 + r->ob_refcnt: refs: 1 + N where N >= 0 + r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/comparison/input.c', line=34), region=RegionForGlobal(gcc.VarDecl('PyList_Type'))) + Exception: + (struct PyObject *)0 from tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:29 + +Trace 1: + Transitions: + 'PyList_New() succeeds' + 'taking False path' + 'taking False path' + 'returning' + Return value: + repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/comparison/input.c', line=34), region=RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/comparison/input.c', line=34))) + str(): (struct PyObject *)&RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/comparison/input.c', line=34)) from tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:34 + r->ob_refcnt: refs: 1 + N where N >= 0 + r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/comparison/input.c', line=34), region=RegionForGlobal(gcc.VarDecl('PyList_Type'))) + Exception: + (struct PyObject *)0 from tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:29 + +Trace 2: + Transitions: + 'PyList_New() fails' + 'taking True path' + 'taking True path' + 'returning' + Return value: + repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/comparison/input.c', line=52), value=0) + str(): (struct PyObject *)0 from tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:52 + Exception: + (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/uninitialized_data/comparison/input.c:34 diff --git a/tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c b/tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c new file mode 100644 index 0000000..a81ed21 --- /dev/null +++ b/tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c @@ -0,0 +1,57 @@ +/* + Copyright 2011 David Malcolm dmalcolm@redhat.com + Copyright 2011 Red Hat, Inc. + + This is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see + http://www.gnu.org/licenses/. +*/ + +#include <Python.h> + +/* + Verify that the checker can cope with various incorrect actions on an + uninitialized pointer +*/ + +PyObject * +test(int len) +{ + PyObject *result = NULL; + PyObject *item; /* never initialized */ + int i; + + result = PyList_New(len); + if (!result) { + goto error; + } + + for (i = 0; i < len; i++) { + /* Erroneous call using uninitialized data: */ + PyList_SetItem(result, i, item); + } + + return result; + + error: + Py_XDECREF(result); + return NULL; +} + +/* + PEP-7 +Local variables: +c-basic-offset: 4 +indent-tabs-mode: nil +End: +*/ diff --git a/tests/cpychecker/refcounts/uninitialized_data/function_arg/script.py b/tests/cpychecker/refcounts/uninitialized_data/function_arg/script.py new file mode 100644 index 0000000..e469f23 --- /dev/null +++ b/tests/cpychecker/refcounts/uninitialized_data/function_arg/script.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright 2011 David Malcolm dmalcolm@redhat.com +# Copyright 2011 Red Hat, Inc. +# +# This is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# http://www.gnu.org/licenses/. + +import gcc +from libcpychecker import main + +main(verify_refcounting=True, + show_traces=False, + dump_traces=True) diff --git a/tests/cpychecker/refcounts/uninitialized_data/function_arg/stderr.txt b/tests/cpychecker/refcounts/uninitialized_data/function_arg/stderr.txt new file mode 100644 index 0000000..c8222fb --- /dev/null +++ b/tests/cpychecker/refcounts/uninitialized_data/function_arg/stderr.txt @@ -0,0 +1,8 @@ +tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c: In function 'test': +tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:41:16: error: passing uninitialized data as argument 3 to function: gcc.VarDecl('item') at tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:41 +tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:34:12: note: when PyList_New() succeeds at: result = PyList_New(len); +tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:35:8: note: when taking False path at: if (!result) { +tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:39:12: note: reaching: for (i = 0; i < len; i++) { +tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:39:5: note: when taking True path at: for (i = 0; i < len; i++) { +tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:41:16: note: reaching: PyList_SetItem(result, i, item); +tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:29:1: note: graphical error report for function 'test' written out to 'tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c.test-refcount-errors.html' diff --git a/tests/cpychecker/refcounts/uninitialized_data/function_arg/stdout.txt b/tests/cpychecker/refcounts/uninitialized_data/function_arg/stdout.txt new file mode 100644 index 0000000..7e57b5e --- /dev/null +++ b/tests/cpychecker/refcounts/uninitialized_data/function_arg/stdout.txt @@ -0,0 +1,40 @@ +Trace 0: + Transitions: + 'PyList_New() succeeds' + 'taking False path' + 'taking True path' + error: UsageOfUninitializedData() + error: passing uninitialized data as argument 3 to function: gcc.VarDecl('item') at tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:41 + PyListObject allocated at tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:34: + repr(): RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c', line=34)) + str(): PyListObject allocated at tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:34 + r->ob_refcnt: refs: 1 + N where N >= 0 + r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c', line=34), region=RegionForGlobal(gcc.VarDecl('PyList_Type'))) + Exception: + (struct PyObject *)0 from tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:29 + +Trace 1: + Transitions: + 'PyList_New() succeeds' + 'taking False path' + 'taking False path' + 'returning' + Return value: + repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c', line=34), region=RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c', line=34))) + str(): (struct PyObject *)&RegionOnHeap('PyListObject', gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c', line=34)) from tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:34 + r->ob_refcnt: refs: 1 + N where N >= 0 + r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c', line=34), region=RegionForGlobal(gcc.VarDecl('PyList_Type'))) + Exception: + (struct PyObject *)0 from tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:29 + +Trace 2: + Transitions: + 'PyList_New() fails' + 'taking True path' + 'taking True path' + 'returning' + Return value: + repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c', line=48), value=0) + str(): (struct PyObject *)0 from tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:48 + Exception: + (struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/uninitialized_data/function_arg/input.c:34
gcc-python-plugin-commits@lists.stg.fedorahosted.org