Skip to content

Commit

Permalink
cpychecker: get the checker working on C++ code
Browse files Browse the repository at this point in the history
Attempting to run the checker on C++ code would fail: it could not locate
any global declarations.

The logic to locate them in gccutils (searching for blocks in the
translation units) worked for C, but not for C++.

For C++, we add a new function:
   gcc.get_global_namespace()
to locate the gcc.NamespaceDecl for the "::" namespace, and add a lookup
method to gcc.NamespaceDecl.  We then use this for C++ to locate global
declarations, enabling the checker to run.

Also, introduce raise_cplusplus_only()
  • Loading branch information
davidmalcolm committed Mar 7, 2012
1 parent 02a0d59 commit 194c353
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 7 deletions.
5 changes: 5 additions & 0 deletions docs/basics.rst
Expand Up @@ -459,6 +459,11 @@ Global data access
The source language of this translation unit, as a string
(e.g. "GNU C")
.. py:function:: gcc.get_global_namespace(name)
C++ only: locate the :py:class:`gcc.NamespaceDecl` for the global
namespace (a.k.a. "::")
.. py:function:: gccutils.get_global_typedef(name)
Given a string `name`, look for a C/C++ `typedef` in global scope with
Expand Down
9 changes: 9 additions & 0 deletions docs/tree.rst
Expand Up @@ -320,6 +320,15 @@ Declarations
(boolean) Is this variable to be allocated with static storage?

.. py:class:: gcc.NamespaceDecl
A subclass of :py:class:`gcc.Declaration` representing a C++ namespace

.. py:method:: locate(name)
Locate the given name within the namespace, returning a
:py:class:`gcc.Tree` or `None`


.. Declaration
.. ClassMethodDecl
Expand Down
41 changes: 39 additions & 2 deletions gcc-python-tree.c
Expand Up @@ -40,6 +40,16 @@

__typeof__ (decl_as_string) decl_as_string __attribute__ ((weak));

/* Similar for namespace_binding: */
__typeof__ (namespace_binding) namespace_binding __attribute__ ((weak));

static PyObject *
raise_cplusplus_only(const char *what)
{
return PyErr_Format(PyExc_RuntimeError,
"%s is only available when compiling C++ code",
what);
}

//#include "rtl.h"
/*
Expand Down Expand Up @@ -191,8 +201,7 @@ gcc_FunctionDecl_get_fullname(struct PyGccTree *self, void *closure)
const char *str;

if (NULL == decl_as_string) {
return PyErr_Format(PyExc_RuntimeError,
"attribute 'fullname' is only available when compiling C++ code");
return raise_cplusplus_only("attribute 'fullname'");
}

str = decl_as_string(self->t,
Expand Down Expand Up @@ -568,6 +577,34 @@ gcc_TreeList_repr(struct PyGccTree * self)
return result;
}


PyObject *
gcc_NamespaceDecl_lookup(struct PyGccTree * self, PyObject *args, PyObject *kwargs)
{
tree t_result;
tree t_name;

const char *name;
char *keywords[] = {"name",
NULL};


if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"s:lookup", keywords,
&name)) {
return NULL;
}

if (NULL == namespace_binding) {
return raise_cplusplus_only("gcc.NamespaceDecl.lookup");
}

t_name = get_identifier(name);

t_result = namespace_binding(t_name, self->t);
return gcc_python_make_wrapper_tree(t_result);
}

/*
GCC's debug_tree is implemented in:
gcc/print-tree.c
Expand Down
3 changes: 3 additions & 0 deletions gcc-python-wrappers.h
Expand Up @@ -182,6 +182,9 @@ gcc_TypeDecl_get_pointer(struct PyGccTree *self, void *closure);
PyObject *
gcc_TreeList_repr(struct PyGccTree * self);

PyObject *
gcc_NamespaceDecl_lookup(struct PyGccTree * self, PyObject *args, PyObject *kwargs);

/* gcc-python-gimple.c: */
PyObject *
gcc_Gimple_repr(struct PyGccGimple * self);
Expand Down
15 changes: 15 additions & 0 deletions gcc-python.c
Expand Up @@ -27,6 +27,7 @@ int plugin_is_GPL_compatible;

#include "plugin-version.h"

#include "cp/name-lookup.h" /* for global_namespace */
#include "tree.h"
#include "function.h"
#include "diagnostic.h"
Expand Down Expand Up @@ -271,6 +272,17 @@ gcc_python_get_translation_units(PyObject *self, PyObject *args)
return VEC_tree_as_PyList(all_translation_units);
}

/* Weakly import global_namespace; it will be non-NULL for the C++ frontend: */
__typeof__ (global_namespace) global_namespace __attribute__ ((weak));

static PyObject *
gcc_python_get_global_namespace(PyObject *self, PyObject *args)
{
/* (global_namespace will be NULL outside the C++ frontend, giving a
result of None) */
return gcc_python_make_wrapper_tree(global_namespace);
}

/* Dump files */

static PyObject *
Expand Down Expand Up @@ -395,6 +407,9 @@ static PyMethodDef GccMethods[] = {
{"get_translation_units", gcc_python_get_translation_units, METH_VARARGS,
"Get a list of all gcc.TranslationUnitDecl"},

{"get_global_namespace", gcc_python_get_global_namespace, METH_VARARGS,
"C++: get the global namespace (aka '::') as a gcc.NamespaceDecl"},

/* Version handling: */
{"get_plugin_gcc_version", gcc_python_get_plugin_gcc_version, METH_VARARGS,
"Get the gcc.Version that this plugin was compiled with"},
Expand Down
15 changes: 11 additions & 4 deletions gccutils.py
Expand Up @@ -35,10 +35,14 @@ def get_global_typedef(name):
# Look up a typedef in global scope by name, returning a gcc.TypeDecl,
# or None if not found
for u in gcc.get_translation_units():
for v in u.block.vars:
if isinstance(v, gcc.TypeDecl):
if v.name == name:
return v
if u.language == 'GNU C++':
gns = gcc.get_global_namespace()
return gns.lookup(name)
if u.block:
for v in u.block.vars:
if isinstance(v, gcc.TypeDecl):
if v.name == name:
return v

def get_variables_as_dict():
result = {}
Expand All @@ -50,6 +54,9 @@ def get_global_vardecl_by_name(name):
# Look up a variable in global scope by name, returning a gcc.VarDecl,
# or None if not found
for u in gcc.get_translation_units():
if u.language == 'GNU C++':
gns = gcc.get_global_namespace()
return gns.lookup(name)
for v in u.block.vars:
if isinstance(v, gcc.VarDecl):
if v.name == name:
Expand Down
11 changes: 10 additions & 1 deletion generate-tree-c.py
Expand Up @@ -314,6 +314,8 @@ def generate_tree_code_classes():
tp_repr = None
tp_str = None

methods = PyMethodTable('gcc_%s_methods' % cc, [])

def get_getter_identifier(name):
return 'gcc_%s_get_%s' % (cc, name)

Expand Down Expand Up @@ -472,6 +474,12 @@ def add_complex_getter(name, doc):
'gcc_tree_list_from_chain(BLOCK_VARS(self->t))',
"The list of gcc.Tree for the declarations and labels in this block")

if tree_type.SYM == 'NAMESPACE_DECL':
methods.add_method('lookup',
'(PyCFunction)gcc_NamespaceDecl_lookup',
'METH_VARARGS|METH_KEYWORDS',
"Look up the given string within this namespace")

if tree_type.SYM == 'TYPE_DECL':
getsettable.add_gsdef('pointer',
'gcc_TypeDecl_get_pointer',
Expand Down Expand Up @@ -542,7 +550,7 @@ def add_complex_getter(name, doc):
"The target of the case label, as a gcc.LabelDecl")

cu.add_defn(getsettable.c_defn())

cu.add_defn(methods.c_defn())
pytype = PyGccWrapperTypeObject(identifier = 'gcc_%sType' % cc,
localname = cc,
tp_name = 'gcc.%s' % cc,
Expand All @@ -552,6 +560,7 @@ def add_complex_getter(name, doc):
tp_getset = getsettable.identifier,
tp_str = tp_str,
tp_repr = tp_repr,
tp_methods = methods.identifier,
)
if tp_as_number:
pytype.tp_as_number = '&%s' % tp_as_number
Expand Down
46 changes: 46 additions & 0 deletions tests/cpychecker/refcounts/cplusplus/input.cc
@@ -0,0 +1,46 @@
/*
Copyright 2012 David Malcolm <dmalcolm@redhat.com>
Copyright 2012 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 will run on C++ code */

PyObject *
test(int i)
{
if (i) {
/*
Verify that C++ frontend can locate globals
for types ("PyDict_Type") and exceptions (PyExc_MemoryError)
*/
return PyDict_New();
} else {
/* Verify that bugs are reported:
this code is missing a Py_INCREF on Py_None */
return Py_None;
}
}

/*
PEP-7
Local variables:
c-basic-offset: 4
indent-tabs-mode: nil
End:
*/
3 changes: 3 additions & 0 deletions tests/cpychecker/refcounts/cplusplus/metadata.ini
@@ -0,0 +1,3 @@
[ExpectedBehavior]
# We expect only compilation *warnings*, so we expect a 0 exit code
exitcode = 0
22 changes: 22 additions & 0 deletions tests/cpychecker/refcounts/cplusplus/script.py
@@ -0,0 +1,22 @@
# -*- 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/>.

from libcpychecker import main
main(verify_refcounting=True,
dump_traces=True,
show_traces=False)
12 changes: 12 additions & 0 deletions tests/cpychecker/refcounts/cplusplus/stderr.txt
@@ -0,0 +1,12 @@
tests/cpychecker/refcounts/cplusplus/input.cc: In function 'PyObject* test(int)':
tests/cpychecker/refcounts/cplusplus/input.cc:38:1: warning: ob_refcnt of return value is 1 too low [enabled by default]
tests/cpychecker/refcounts/cplusplus/input.cc:38:1: note: was expecting final ob_refcnt to be N + 1 (for some unknown N)
tests/cpychecker/refcounts/cplusplus/input.cc:38:1: note: due to object being referenced by: return value
tests/cpychecker/refcounts/cplusplus/input.cc:38:1: note: but final ob_refcnt is N + 0
tests/cpychecker/refcounts/cplusplus/input.cc:27:5: note: when considering value == (int)0 from tests/cpychecker/refcounts/cplusplus/input.cc:25 at: if (i) {
tests/cpychecker/refcounts/cplusplus/input.cc:27:5: note: taking False path at: if (i) {
tests/cpychecker/refcounts/cplusplus/input.cc:36:16: note: reaching: return Py_None;
tests/cpychecker/refcounts/cplusplus/input.cc:27:5: note: ob_refcnt is now refs: 0 + N where N >= 1
tests/cpychecker/refcounts/cplusplus/input.cc:38:1: note: returning
tests/cpychecker/refcounts/cplusplus/input.cc:38:1: note: consider using "Py_RETURN_NONE;"
tests/cpychecker/refcounts/cplusplus/input.cc:26:1: note: graphical error report for function 'test' written out to 'tests/cpychecker/refcounts/cplusplus/input.cc.test-refcount-errors.html'
64 changes: 64 additions & 0 deletions tests/cpychecker/refcounts/cplusplus/stdout.txt
@@ -0,0 +1,64 @@
Trace 0:
Transitions:
'when considering range: -0x80000000 <= value <= -1'
'taking True path'
'when PyDict_New() succeeds'
'returning'
Return value:
repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/cplusplus/input.cc', line=32), region=RegionOnHeap('PyDictObject', gcc.Location(file='tests/cpychecker/refcounts/cplusplus/input.cc', line=32)))
str(): (struct PyObject *)&RegionOnHeap('PyDictObject', gcc.Location(file='tests/cpychecker/refcounts/cplusplus/input.cc', line=32)) from tests/cpychecker/refcounts/cplusplus/input.cc:32
r->ob_refcnt: refs: 1 + N where N >= 0
r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/cplusplus/input.cc', line=32), region=RegionForGlobal(gcc.VarDecl('PyDict_Type')))
Exception:
(struct PyObject *)0 from tests/cpychecker/refcounts/cplusplus/input.cc:26

Trace 1:
Transitions:
'when considering range: -0x80000000 <= value <= -1'
'taking True path'
'when PyDict_New() fails'
'returning'
Return value:
repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/cplusplus/input.cc', line=32), value=0)
str(): (struct PyObject *)0 from tests/cpychecker/refcounts/cplusplus/input.cc:32
Exception:
(struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/cplusplus/input.cc:32

Trace 2:
Transitions:
'when considering value == (int)0 from tests/cpychecker/refcounts/cplusplus/input.cc:25'
'taking False path'
'returning'
Return value:
repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/cplusplus/input.cc', line=36), region=RegionForGlobal(gcc.VarDecl('_Py_NoneStruct')))
str(): (struct PyObject *)&RegionForGlobal(gcc.VarDecl('_Py_NoneStruct')) from tests/cpychecker/refcounts/cplusplus/input.cc:36
r->ob_refcnt: refs: 0 + N where N >= 1
r->ob_type: None
Exception:
(struct PyObject *)0 from tests/cpychecker/refcounts/cplusplus/input.cc:26

Trace 3:
Transitions:
'when considering range: 1 <= value <= 0x7fffffff'
'taking True path'
'when PyDict_New() succeeds'
'returning'
Return value:
repr(): PointerToRegion(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/cplusplus/input.cc', line=32), region=RegionOnHeap('PyDictObject', gcc.Location(file='tests/cpychecker/refcounts/cplusplus/input.cc', line=32)))
str(): (struct PyObject *)&RegionOnHeap('PyDictObject', gcc.Location(file='tests/cpychecker/refcounts/cplusplus/input.cc', line=32)) from tests/cpychecker/refcounts/cplusplus/input.cc:32
r->ob_refcnt: refs: 1 + N where N >= 0
r->ob_type: PointerToRegion(gcctype='struct PyTypeObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/cplusplus/input.cc', line=32), region=RegionForGlobal(gcc.VarDecl('PyDict_Type')))
Exception:
(struct PyObject *)0 from tests/cpychecker/refcounts/cplusplus/input.cc:26

Trace 4:
Transitions:
'when considering range: 1 <= value <= 0x7fffffff'
'taking True path'
'when PyDict_New() fails'
'returning'
Return value:
repr(): ConcreteValue(gcctype='struct PyObject *', loc=gcc.Location(file='tests/cpychecker/refcounts/cplusplus/input.cc', line=32), value=0)
str(): (struct PyObject *)0 from tests/cpychecker/refcounts/cplusplus/input.cc:32
Exception:
(struct PyObject *)&RegionForGlobal(gcc.VarDecl('PyExc_MemoryError')) from tests/cpychecker/refcounts/cplusplus/input.cc:32

0 comments on commit 194c353

Please sign in to comment.