[gcc-python-plugin] Add some simple debug logging to the plugin's startup
by David Hugh Malcolm
commit 53ac666636c28646c49c33f4c943d11d02ba9d10
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Fri Aug 5 13:22:58 2011 -0400
Add some simple debug logging to the plugin's startup
gcc-python.c | 18 ++++++++++++++++--
1 files changed, 16 insertions(+), 2 deletions(-)
---
diff --git a/gcc-python.c b/gcc-python.c
index 439e694..51c35f6 100644
--- a/gcc-python.c
+++ b/gcc-python.c
@@ -34,6 +34,14 @@ int plugin_is_GPL_compatible;
#include "cgraph.h"
#include "opts.h"
+#if 0
+#define LOG(msg) \
+ (void)fprintf(stderr, "%s:%i:%s\n", __FILE__, __LINE__, (msg))
+#else
+#define LOG(msg) ((void)0);
+#endif
+
+
#define GCC_PYTHON_TRACE_ALL_EVENTS 0
#if GCC_PYTHON_TRACE_ALL_EVENTS
static const char* event_name[] = {
@@ -1024,13 +1032,13 @@ int
plugin_init (struct plugin_name_args *plugin_info,
struct plugin_gcc_version *version)
{
+ LOG("plugin_init started");
+
if (!plugin_default_version_check (version, &gcc_version)) {
return 1;
}
actual_gcc_version = version;
- //printf("%s:%i:plugin_init\n", __FILE__, __LINE__);
-
#if PY_MAJOR_VERSION >= 3
/*
Python 3 added internal buffering to sys.stdout and sys.stderr, but this
@@ -1045,8 +1053,12 @@ plugin_init (struct plugin_name_args *plugin_info,
PyImport_AppendInittab("gcc", PyInit_gcc);
+ LOG("calling Py_Initialize...");
+
Py_Initialize();
+ LOG("Py_Initialize finished");
+
PyImport_ImportModule("gcc");
PyEval_InitThreads();
@@ -1115,6 +1127,8 @@ plugin_init (struct plugin_name_args *plugin_info,
# undef DEFEVENT
#endif /* GCC_PYTHON_TRACE_ALL_EVENTS */
+ LOG("init_plugin finished");
+
return 0;
}
12 years, 10 months
[gcc-python-plugin] Fix bogus format string within gcc_Pass_get_by_name
by David Hugh Malcolm
commit e37cbf74c3360f44d077974535de21a15453a85a
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Fri Aug 5 11:04:02 2011 -0400
Fix bogus format string within gcc_Pass_get_by_name
This code was erroneously using "|" (signifying that optional args follow),
when I meant to use ":" (signifying that the rest of the string is the name
of the callable).
Found using the plugin/cpychecker itself
gcc-python-pass.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
---
diff --git a/gcc-python-pass.c b/gcc-python-pass.c
index 01e0b25..ed3fd58 100644
--- a/gcc-python-pass.c
+++ b/gcc-python-pass.c
@@ -356,7 +356,7 @@ gcc_Pass_get_by_name(PyObject *cls, PyObject *args, PyObject *kwargs)
struct opt_pass *result;
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
- "s|get_by_name", keywords,
+ "s:get_by_name", keywords,
&name)) {
return NULL;
}
12 years, 10 months
[gcc-python-plugin] Rewrite the cpychecker as a custom pass, rather than a callback associated with every pass
by David Hugh Malcolm
commit 4e14f1fed006038549a717135fc53aa27b2061ad
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Thu Aug 4 18:06:52 2011 -0400
Rewrite the cpychecker as a custom pass, rather than a callback associated with every pass
(This should mean it gets called once per function, rather than being called
per-pass per-function and filtering on the pass)
libcpychecker/__init__.py | 32 +++++++++++++++++++-------------
1 files changed, 19 insertions(+), 13 deletions(-)
---
diff --git a/libcpychecker/__init__.py b/libcpychecker/__init__.py
index d0d46f2..91b9753 100644
--- a/libcpychecker/__init__.py
+++ b/libcpychecker/__init__.py
@@ -20,21 +20,27 @@ from libcpychecker.PyArg_ParseTuple import check_pyargs
from libcpychecker.utils import log
from libcpychecker.refcounts import check_refcounts, get_traces
-def on_pass_execution(optpass, fun,
- dump_traces=False,
- show_traces=False,
- verify_refcounting=False,
- *args, **kwargs):
- # Only run in one pass
- # FIXME: should we be adding our own pass for this?
- if optpass.name == '*warn_function_return':
+class CpyChecker(gcc.GimplePass):
+ """
+ The custom pass that implements our extra compile-time checks
+ """
+ def __init__(self,
+ dump_traces=False,
+ show_traces=False,
+ verify_refcounting=False):
+ gcc.GimplePass.__init__(self, 'cpychecker')
+ self.dump_traces = dump_traces
+ self.show_traces = show_traces
+ self.verify_refcounting = verify_refcounting
+
+ def execute(self, fun):
if fun:
log(fun)
check_pyargs(fun)
# The refcount code is too buggy for now to be on by default:
- if verify_refcounting:
- check_refcounts(fun, dump_traces, show_traces)
+ if self.verify_refcounting:
+ check_refcounts(fun, self.dump_traces, self.show_traces)
def is_a_method_callback(decl):
methods = get_all_PyMethodDef_methods()
@@ -72,6 +78,6 @@ def get_all_PyMethodDef_methods():
return result
def main(**kwargs):
- gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
- on_pass_execution,
- **kwargs)
+ # Register our GCC pass:
+ ps = CpyChecker(**kwargs)
+ ps.register_before('*warn_function_return')
12 years, 10 months
[gcc-python-plugin] Ignore Ctrl-C within gcc-with-cpychecker
by David Hugh Malcolm
commit cf2f46856166863f8dd252a4b9e19982f0a7d4e0
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Thu Aug 4 16:01:20 2011 -0400
Ignore Ctrl-C within gcc-with-cpychecker
gcc-with-cpychecker | 5 ++++-
1 files changed, 4 insertions(+), 1 deletions(-)
---
diff --git a/gcc-with-cpychecker b/gcc-with-cpychecker
index 5b557e9..8089ede 100755
--- a/gcc-with-cpychecker
+++ b/gcc-with-cpychecker
@@ -71,5 +71,8 @@ if 0:
print(' '.join(args))
p = subprocess.Popen(args)
-r = p.wait()
+try:
+ r = p.wait()
+except KeyboardInterrupt:
+ r = 1
sys.exit(r)
12 years, 10 months
[gcc-python-plugin] Add "gcc-with-cpychecker" harness
by David Hugh Malcolm
commit 96d637e49f93f6fd6f1383bb1b4fd29c0d3fa092
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Thu Aug 4 14:51:51 2011 -0400
Add "gcc-with-cpychecker" harness
Makefile | 2 +-
gcc-with-cpychecker | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 76 insertions(+), 1 deletions(-)
---
diff --git a/Makefile b/Makefile
index 8cc2f30..d629eb2 100644
--- a/Makefile
+++ b/Makefile
@@ -176,7 +176,7 @@ debug: plugin
# A simple demo, to make it easy to demonstrate the cpychecker:
demo: plugin
- gcc -fplugin=$(shell pwd)/python.so -fplugin-arg-python-script=cpychecker.py $(shell python-config --cflags) demo.c
+ ./gcc-with-cpychecker $(shell python-config --cflags) demo.c
test-suite: plugin
$(PYTHON) run-test-suite.py
diff --git a/gcc-with-cpychecker b/gcc-with-cpychecker
new file mode 100755
index 0000000..5b557e9
--- /dev/null
+++ b/gcc-with-cpychecker
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+# Copyright 2011 David Malcolm <dmalcolm(a)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/>.
+
+# Harness for invoking GCC with the cpychecker Python code within the python
+# plugin, whilst dealing with some options
+# (This code runs under the regular Python interpreter, not within gcc)
+
+import argparse
+import os
+import subprocess
+import sys
+
+abspath = os.path.abspath(os.path.dirname(sys.argv[0]))
+
+# By default, look for the plugin relative to this harness. This is intended
+# to make it easier during development, so that we can make other projects with
+# CC=../../../gcc-python-plugin/gcc-with-cpychecker
+# and similar.
+#
+# When installed, this should be fixed up.
+PLUGIN = os.path.join(abspath, 'python.so')
+
+# Create arg parser:
+parser = argparse.ArgumentParser(usage='%(prog)s [options] gcc-options')
+parser.add_argument('--check-refcounts',
+ action='store_true',
+ default=False,
+ help='foo')
+parser.add_argument('--dump-cpychecker',
+ action='store_true',
+ default=False,
+ help='foo')
+
+# Only consume args we understand, leaving the rest for gcc:
+ns, other_args = parser.parse_known_args()
+if 0:
+ print(ns)
+ print(other_args)
+
+cmd = 'from libcpychecker import main; main()'
+
+args = ['gcc',
+ ('-fplugin=%s' % PLUGIN),
+ ('-fplugin-arg-python-command=%s' % cmd)]
+args += other_args # (the args we didn't consume)
+
+# Beware of quoting: if the command is quoted within the Popen call, then
+# Python interprets it as a string literal, and does nothing.
+#
+# But if invoking from a shell, you need quotes aroung the command
+#
+# To add to the fun, "gcc -v" emits it in unquoted form,
+# which will need quotes added
+
+if 0:
+ print(' '.join(args))
+p = subprocess.Popen(args)
+
+r = p.wait()
+sys.exit(r)
12 years, 10 months
[gcc-python-plugin] Add support for specifying Python commands via -fplugin-arg-python-command
by David Hugh Malcolm
commit e6fabb3730d1d63b355058fe9361bcc7b2626351
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Thu Aug 4 14:29:38 2011 -0400
Add support for specifying Python commands via -fplugin-arg-python-command
docs/basics.rst | 14 +++++++++++++-
gcc-python.c | 26 ++++++++++++++++++++++++++
2 files changed, 39 insertions(+), 1 deletions(-)
---
diff --git a/docs/basics.rst b/docs/basics.rst
index d163877..572f482 100644
--- a/docs/basics.rst
+++ b/docs/basics.rst
@@ -66,10 +66,22 @@ Once you've built the plugin, you can invoke a Python script like this:
.. code-block:: bash
- gcc -fplugin=python.so -fplugin-arg-python-script=PATH_TO_SCRIPT.py OTHER_ARGS
+ gcc -fplugin=./python.so -fplugin-arg-python-script=PATH_TO_SCRIPT.py OTHER_ARGS
and have it run your script as the plugin starts up.
+Alternatively, you can run a one-shot Python command like this:
+
+.. code-block:: bash
+
+ gcc -fplugin=./python.so -fplugin-arg-python-command="python code" OTHER_ARGS
+
+such as:
+
+.. code-block:: bash
+
+ gcc -fplugin=./python.so -fplugin-arg-python-command="import sys; print(sys.path)" OTHER_ARGS
+
The plugin automatically adds the absolute path to its own directory to the
end of its `sys.path`, so that it can find support modules, such as gccutils.py
and `libcpychecker`.
diff --git a/gcc-python.c b/gcc-python.c
index 3369e00..439e694 100644
--- a/gcc-python.c
+++ b/gcc-python.c
@@ -875,6 +875,31 @@ gcc_python_init_gcc_module(struct plugin_name_args *plugin_info)
return 1;
}
+static void gcc_python_run_any_command(void)
+{
+ PyObject* command_obj; /* borrowed ref */
+ int result;
+ const char *command_str;
+
+ command_obj = PyDict_GetItemString(gcc_python_globals.argument_dict, "command");
+ if (!command_obj) {
+ return;
+ }
+
+ command_str = gcc_python_string_as_string(command_obj);
+
+ if (0) {
+ fprintf(stderr, "Running: %s\n", command_str);
+ }
+
+ result = PyRun_SimpleString(command_str);
+ if (-1 == result) {
+ /* Error running the python command */
+ Py_Finalize();
+ exit(1);
+ }
+}
+
static void gcc_python_run_any_script(void)
{
PyObject* script_name;
@@ -1072,6 +1097,7 @@ plugin_init (struct plugin_name_args *plugin_info,
register_callback(plugin_info->base_name, PLUGIN_FINISH,
on_plugin_finish, NULL);
+ gcc_python_run_any_command();
gcc_python_run_any_script();
//printf("%s:%i:got here\n", __FILE__, __LINE__);
12 years, 10 months
[gcc-python-plugin] Add note on stderr when refcount checker writes out a graphical error report
by David Hugh Malcolm
commit c2a8af7fde8a89cbbccf786f80df6e411f33f09c
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Aug 3 18:01:11 2011 -0400
Add note on stderr when refcount checker writes out a graphical error report
libcpychecker/refcounts.py | 9 ++++++---
.../refcounts/incorrect_py_none/stderr.txt | 1 +
.../cpychecker/refcounts/missing_decref/stderr.txt | 1 +
.../refcounts/not_setting_exception/stderr.txt | 1 +
tests/cpychecker/refcounts/object_leak/stderr.txt | 1 +
.../refcounts/returning_dead_object/stderr.txt | 1 +
.../refcounts/too_many_increfs/stderr.txt | 1 +
.../refcounts/use_after_dealloc/stderr.txt | 1 +
8 files changed, 13 insertions(+), 3 deletions(-)
---
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index e26fa19..65cacc4 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -788,9 +788,12 @@ def check_refcounts(fun, dump_traces=False, show_traces=False):
# (all traces analysed)
if rep.got_errors():
- rep.dump_html(fun,
- '%s.%s-refcount-errors.html'
- % (gcc.get_dump_base_name(), fun.decl.name))
+ filename = ('%s.%s-refcount-errors.html'
+ % (gcc.get_dump_base_name(), fun.decl.name))
+ rep.dump_html(fun, filename)
+ gcc.inform(fun.start,
+ ('graphical error report for function %r written out to %r'
+ % (fun.decl.name, filename)))
if 0:
dot = cfg_to_dot(fun.cfg)
diff --git a/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt b/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt
index 9cd0803..b7c6866 100644
--- a/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt
+++ b/tests/cpychecker/refcounts/incorrect_py_none/stderr.txt
@@ -2,3 +2,4 @@ tests/cpychecker/refcounts/incorrect_py_none/input.c: In function 'losing_refcnt
tests/cpychecker/refcounts/incorrect_py_none/input.c:26:5: error: ob_refcnt of return value is 1 too low
tests/cpychecker/refcounts/incorrect_py_none/input.c:26:5: note: when returning at: return Py_None;
tests/cpychecker/refcounts/incorrect_py_none/input.c:26:5: note: consider using "Py_RETURN_NONE;"
+tests/cpychecker/refcounts/incorrect_py_none/input.c:24:1: note: graphical error report for function 'losing_refcnt_of_none' written out to 'tests/cpychecker/refcounts/incorrect_py_none/input.c.losing_refcnt_of_none-refcount-errors.html'
diff --git a/tests/cpychecker/refcounts/missing_decref/stderr.txt b/tests/cpychecker/refcounts/missing_decref/stderr.txt
index e200d0f..5c2cf22 100644
--- a/tests/cpychecker/refcounts/missing_decref/stderr.txt
+++ b/tests/cpychecker/refcounts/missing_decref/stderr.txt
@@ -8,3 +8,4 @@ tests/cpychecker/refcounts/missing_decref/input.c:30:10: note: when PyLong_FromL
tests/cpychecker/refcounts/missing_decref/input.c:33:8: note: when taking True path at: if (!item)
tests/cpychecker/refcounts/missing_decref/input.c:34:9: note: reaching: return NULL;
tests/cpychecker/refcounts/missing_decref/input.c:38:1: note: when returning
+tests/cpychecker/refcounts/missing_decref/input.c:24:1: note: graphical error report for function 'missing_decref' written out to 'tests/cpychecker/refcounts/missing_decref/input.c.missing_decref-refcount-errors.html'
diff --git a/tests/cpychecker/refcounts/not_setting_exception/stderr.txt b/tests/cpychecker/refcounts/not_setting_exception/stderr.txt
index 3d0d16e..f6d68be 100644
--- a/tests/cpychecker/refcounts/not_setting_exception/stderr.txt
+++ b/tests/cpychecker/refcounts/not_setting_exception/stderr.txt
@@ -1,3 +1,4 @@
tests/cpychecker/refcounts/not_setting_exception/input.c: In function 'not_setting_exception':
tests/cpychecker/refcounts/not_setting_exception/input.c:32:5: error: returning (PyObject*)NULL without setting an exception
tests/cpychecker/refcounts/not_setting_exception/input.c:32:5: note: when returning at: return NULL;
+tests/cpychecker/refcounts/not_setting_exception/input.c:27:1: note: graphical error report for function 'not_setting_exception' written out to 'tests/cpychecker/refcounts/not_setting_exception/input.c.not_setting_exception-refcount-errors.html'
diff --git a/tests/cpychecker/refcounts/object_leak/stderr.txt b/tests/cpychecker/refcounts/object_leak/stderr.txt
index a376049..bb5b1b6 100644
--- a/tests/cpychecker/refcounts/object_leak/stderr.txt
+++ b/tests/cpychecker/refcounts/object_leak/stderr.txt
@@ -6,3 +6,4 @@ tests/cpychecker/refcounts/object_leak/input.c:29:5: note: when returning at:
tests/cpychecker/refcounts/object_leak/input.c:29:5: error: returning (PyObject*)NULL without setting an exception
tests/cpychecker/refcounts/object_leak/input.c:26:15: note: when PyLong_FromLong() succeeds at: PyObject *tmp = PyLong_FromLong(0x1000);
tests/cpychecker/refcounts/object_leak/input.c:29:5: note: when returning at: return NULL;
+tests/cpychecker/refcounts/object_leak/input.c:24:1: note: graphical error report for function 'object_leak' written out to 'tests/cpychecker/refcounts/object_leak/input.c.object_leak-refcount-errors.html'
diff --git a/tests/cpychecker/refcounts/returning_dead_object/stderr.txt b/tests/cpychecker/refcounts/returning_dead_object/stderr.txt
index 913b1e9..a77bf2b 100644
--- a/tests/cpychecker/refcounts/returning_dead_object/stderr.txt
+++ b/tests/cpychecker/refcounts/returning_dead_object/stderr.txt
@@ -17,3 +17,4 @@ tests/cpychecker/refcounts/returning_dead_object/input.c:39:5: note: when callin
tests/cpychecker/refcounts/returning_dead_object/input.c:42:5: note: reaching: return tmp;
tests/cpychecker/refcounts/returning_dead_object/input.c:43:1: note: when returning
tests/cpychecker/refcounts/returning_dead_object/input.c:39:5: note: memory deallocated here
+tests/cpychecker/refcounts/returning_dead_object/input.c:27:1: note: graphical error report for function 'returning_dead_object' written out to 'tests/cpychecker/refcounts/returning_dead_object/input.c.returning_dead_object-refcount-errors.html'
diff --git a/tests/cpychecker/refcounts/too_many_increfs/stderr.txt b/tests/cpychecker/refcounts/too_many_increfs/stderr.txt
index 8818217..d4dfaf1 100644
--- a/tests/cpychecker/refcounts/too_many_increfs/stderr.txt
+++ b/tests/cpychecker/refcounts/too_many_increfs/stderr.txt
@@ -5,3 +5,4 @@ tests/cpychecker/refcounts/too_many_increfs/input.c:26:9: note: when PyLong_From
tests/cpychecker/refcounts/too_many_increfs/input.c:31:5: note: when returning at: return tmp;
tests/cpychecker/refcounts/too_many_increfs/input.c:30:5: error: dereferencing NULL (tmp->ob_refcnt) at tests/cpychecker/refcounts/too_many_increfs/input.c:30
tests/cpychecker/refcounts/too_many_increfs/input.c:26:9: note: when PyLong_FromLong() fails at: tmp = PyLong_FromLong(0x1000);
+tests/cpychecker/refcounts/too_many_increfs/input.c:24:1: note: graphical error report for function 'too_many_increfs' written out to 'tests/cpychecker/refcounts/too_many_increfs/input.c.too_many_increfs-refcount-errors.html'
diff --git a/tests/cpychecker/refcounts/use_after_dealloc/stderr.txt b/tests/cpychecker/refcounts/use_after_dealloc/stderr.txt
index abd71a3..179322d 100644
--- a/tests/cpychecker/refcounts/use_after_dealloc/stderr.txt
+++ b/tests/cpychecker/refcounts/use_after_dealloc/stderr.txt
@@ -7,3 +7,4 @@ tests/cpychecker/refcounts/use_after_dealloc/input.c:41:5: note: when taking Fal
tests/cpychecker/refcounts/use_after_dealloc/input.c:41:5: note: reaching: Py_DECREF(tmp);
tests/cpychecker/refcounts/use_after_dealloc/input.c:41:5: note: when calling tp_dealloc on PyLongObject allocated at tests/cpychecker/refcounts/use_after_dealloc/input.c:30 at: Py_DECREF(tmp);
tests/cpychecker/refcounts/use_after_dealloc/input.c:42:5: note: reaching: Py_INCREF(tmp);
+tests/cpychecker/refcounts/use_after_dealloc/input.c:28:1: note: graphical error report for function 'use_after_dealloc' written out to 'tests/cpychecker/refcounts/use_after_dealloc/input.c.use_after_dealloc-refcount-errors.html'
12 years, 10 months
[gcc-python-plugin] Add some more files output by the selftests to .gitignore
by David Hugh Malcolm
commit 693f995783851b951701305ee07489ec49e11530
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Aug 3 17:28:24 2011 -0400
Add some more files output by the selftests to .gitignore
.gitignore | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 19c7a08..8c2d6be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
*.so
a.out
*.pyc
+test.png
# Generated elsewhere in build:
autogenerated*
@@ -13,3 +14,4 @@ docs/_build
# Logfiles generated during the selftest suite:
*.c.cpychecker-log.txt
*.c.*-refcount-errors.html
+tests/plugin/dumpfiles/input.c.*t.test-pass
12 years, 10 months
[gcc-python-plugin] Expose dump_base_name to Python, and use it in libcpychecker when writing logs
by David Hugh Malcolm
commit 52df972d9c21d9ceee0616af922f948bd6073b10
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Aug 3 17:16:03 2011 -0400
Expose dump_base_name to Python, and use it in libcpychecker when writing logs
Add gcc.get_dump_base_name(), exposing the base filename for dump files.
Use it when writing refcount error files, writing to
DUMP_BASE_NAME.FUNCTION_NAME-refcount-errors.html
Use it in the log mechanism within libcpychecker, writing to:
DUMP_BASE_NAME.cpychecker-log.txt
(though that feature is currently turned off in code)
Add .gitignore exclusions for those files (as generated when running the
selftest suite)
.gitignore | 4 ++++
docs/passes.rst | 22 ++++++++++++++++++++++
gcc-python.c | 22 ++++++++++++++++++++--
libcpychecker/refcounts.py | 4 +++-
libcpychecker/utils.py | 5 ++++-
tests/plugin/dumpfiles/script.py | 2 ++
tests/plugin/dumpfiles/stdout.txt | 1 +
7 files changed, 56 insertions(+), 4 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 71a7da3..19c7a08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,7 @@ autogenerated*
# Generated by Sphinx
docs/_build
+
+# Logfiles generated during the selftest suite:
+*.c.cpychecker-log.txt
+*.c.*-refcount-errors.html
diff --git a/docs/passes.rst b/docs/passes.rst
index 1937462..19bd589 100644
--- a/docs/passes.rst
+++ b/docs/passes.rst
@@ -318,3 +318,25 @@ would have a dump file like this::
;; Function foo (foo)
hello world42
+
+Alternatively, it can be simpler to create your own logging system, given that
+one can simply open a file and write to it.
+
+.. py:function:: gcc.get_dump_base_name()
+
+ Get the base file path and name prefix for GCC's dump files.
+
+ You can use this when creating non-standard logfiles and other output.
+
+ For example, the libcpychecker code can write HTML reports on
+ reference-counting errors within a function, writing the output to a file
+ named::
+
+ filename = '%s.%s-refcount-errors.html' % (gcc.get_dump_base_name(),
+ fun.decl.name)
+
+ given `fun`, a `gcc.Function`.
+
+ By default, this is the name of the input file, but within the output
+ file's directory. (It can be overridden using the `-dumpbase` command-line
+ option).
diff --git a/gcc-python.c b/gcc-python.c
index 335408e..3369e00 100644
--- a/gcc-python.c
+++ b/gcc-python.c
@@ -678,7 +678,7 @@ gcc_python_dump(PyObject *self, PyObject *arg)
}
static PyObject *
-gcc_python_get_dump_file_name(PyObject *self, PyObject *args)
+gcc_python_get_dump_file_name(PyObject *self, PyObject *noargs)
{
/* gcc/tree-pass.h declares:
extern const char *dump_file_name;
@@ -686,6 +686,21 @@ gcc_python_get_dump_file_name(PyObject *self, PyObject *args)
return gcc_python_string_or_none(dump_file_name);
}
+static PyObject *
+gcc_python_get_dump_base_name(PyObject *self, PyObject *noargs)
+{
+ /*
+ The generated gcc/options.h has:
+ #ifdef GENERATOR_FILE
+ extern const char *dump_base_name;
+ #else
+ const char *x_dump_base_name;
+ #define dump_base_name global_options.x_dump_base_name
+ #endif
+ */
+ return gcc_python_string_or_none(dump_base_name);
+}
+
static PyMethodDef GccMethods[] = {
{"register_callback",
(PyCFunction)gcc_python_register_callback,
@@ -750,9 +765,12 @@ static PyMethodDef GccMethods[] = {
{"dump", gcc_python_dump, METH_O,
"Dump str() of the argument to the current dump file (or silently discard it when no dump file is open)"},
- {"get_dump_file_name", gcc_python_get_dump_file_name, METH_VARARGS,
+ {"get_dump_file_name", gcc_python_get_dump_file_name, METH_NOARGS,
"Get the name of the current dump file (or None)"},
+ {"get_dump_base_name", gcc_python_get_dump_base_name, METH_NOARGS,
+ "Get the base name used when writing dump files"},
+
/* Sentinel: */
{NULL, NULL, 0, NULL}
};
diff --git a/libcpychecker/refcounts.py b/libcpychecker/refcounts.py
index 2e82dcf..e26fa19 100644
--- a/libcpychecker/refcounts.py
+++ b/libcpychecker/refcounts.py
@@ -788,7 +788,9 @@ def check_refcounts(fun, dump_traces=False, show_traces=False):
# (all traces analysed)
if rep.got_errors():
- rep.dump_html(fun, '%s-refcount-errors.html' % fun.decl.name)
+ rep.dump_html(fun,
+ '%s.%s-refcount-errors.html'
+ % (gcc.get_dump_base_name(), fun.decl.name))
if 0:
dot = cfg_to_dot(fun.cfg)
diff --git a/libcpychecker/utils.py b/libcpychecker/utils.py
index dae57c9..882b401 100644
--- a/libcpychecker/utils.py
+++ b/libcpychecker/utils.py
@@ -16,13 +16,16 @@
# <http://www.gnu.org/licenses/>.
# Logging
+
+import gcc
+
logfile = None
def log(msg, indent=0):
if 0:
global logfile
if not logfile:
- logfile = open('log.txt', 'w')
+ logfile = open(gcc.get_dump_base_name() + '.cpychecker-log.txt', 'w')
logfile.write('%s%s\n' % (' ' * indent, msg))
if 0:
sys.stderr.write('%s%s\n' % (' ' * indent, msg))
diff --git a/tests/plugin/dumpfiles/script.py b/tests/plugin/dumpfiles/script.py
index 1df49b1..a367f4e 100644
--- a/tests/plugin/dumpfiles/script.py
+++ b/tests/plugin/dumpfiles/script.py
@@ -20,6 +20,8 @@
import gcc
+print('gcc.get_dump_base_name(): %r' % gcc.get_dump_base_name())
+
pass_dumpfile = None
class TestPass(gcc.GimplePass):
def execute(self, fun):
diff --git a/tests/plugin/dumpfiles/stdout.txt b/tests/plugin/dumpfiles/stdout.txt
index b60c1b8..a7cd9cd 100644
--- a/tests/plugin/dumpfiles/stdout.txt
+++ b/tests/plugin/dumpfiles/stdout.txt
@@ -1,3 +1,4 @@
+gcc.get_dump_base_name(): 'tests/plugin/dumpfiles/input.c'
registering: TestPass(name='test-pass')
ps.dump_enabled: False
Manually enabling dump for our pass:
12 years, 10 months
[gcc-python-plugin] Expose gcc's debug dump facilities from the Python API
by David Hugh Malcolm
commit 4f6a522ccf59c3b5f38541cd3dac30fa75dbe0df
Author: David Malcolm <dmalcolm(a)redhat.com>
Date: Wed Aug 3 15:29:25 2011 -0400
Expose gcc's debug dump facilities from the Python API
Add methods:
gcc.dump()
gcc.get_dump_file_name()
Add properties to gcc.Pass:
static_pass_number
dump_enabled
docs/passes.rst | 96 +++++++++++++++++++++++++++++++++++++
gcc-python-pass.c | 54 +++++++++++++++++++++
gcc-python-wrappers.h | 6 ++
gcc-python.c | 52 ++++++++++++++++++++
generate-pass-c.py | 8 +++
tests/plugin/dumpfiles/input.c | 44 +++++++++++++++++
tests/plugin/dumpfiles/script.py | 64 ++++++++++++++++++++++++
tests/plugin/dumpfiles/stdout.txt | 24 +++++++++
8 files changed, 348 insertions(+), 0 deletions(-)
---
diff --git a/docs/passes.rst b/docs/passes.rst
index edb3a28..1937462 100644
--- a/docs/passes.rst
+++ b/docs/passes.rst
@@ -115,6 +115,15 @@ There are actually five "roots" to this tree:
lowered to scalar operations?
========================= ============================================ ========================= =======================
+ .. py:attribute:: static_pass_number
+
+ (int) The number of this pass, used as a fragment of the dump file name.
+ This is assigned automatically for custom passes.
+
+ .. py:attribute:: dump_enabled
+
+ (boolean) Is dumping enabled for this pass? Set this attribute to `True`
+ to enable dumping.
There are four subclasses of gcc.Pass:
@@ -222,3 +231,90 @@ to a GCC error:
As above, but replace the given pass. This method is included for
completeness; the result is unlikely to work well.
+
+Dumping per-pass information
+----------------------------
+GCC has a logging framework which supports per-pass logging ("dump files").
+
+By default, no logging is done; dumping must be explicitly enabled.
+
+Dumping of passes can be enabled from the command-line in groups:
+
+ * `-fdump-tree-all` enables dumping for all `gcc.GimplePass` (both builtin,
+ and custom ones from plugins)
+
+ * `-fdump-rtl-all` is similar, but for all `gcc.RtlPass`
+
+ * `-fdump-ipa-all` as above, but for all `gcc.IpaPass` and
+ `gcc.SimpleIpaPass`
+
+For more information, see
+http://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html
+
+It's not possible to directly enable dumping for a custom pass from the
+command-line (it would require adding new GCC command-line options). However,
+your script *can* directly enable dumping for a custom pass by writing to the
+`dump_enabled` attribute (perhaps in response to the arguments passed to
+plugin, or a driver script).
+
+If enabled for a pass, then a file is written to the same directory as the
+output file, with a name based on the input file and the pass number.
+
+For example, given a custom `gcc.Pass` with name `'test-pass'`, then when
+`input.c` is compiled to `build/output.o`::
+
+ $ gcc -fdump-tree-all -o build/output.o src/input.c
+
+then a dump file `input.c.225t.test-pass` will be written to the directory
+`build`. In this case, `225` is the `static_pass_number` field, `"t"`
+signifies a tree pass, with the pass name appearing as the suffix.
+
+.. py:function:: gcc.dump(obj)
+
+ Write str() of the argument to the current dump file. No newlines or other
+ whitespace are added.
+
+ Note that dumping is disabled by default; in this case, the call will do
+ nothing.
+
+.. py:function:: gcc.get_dump_file_name()
+
+ Get the name of the current dump file.
+
+ If called from within a pass for which dumping is enabled, it will return
+ the filename in string form.
+
+ If dumping is disabled for this pass, it will return `None`.
+
+The typical output of a dump file will contain::
+
+ ;; Function bar (bar)
+
+ (dumped information when handling function bar goes here)
+
+ ;; Function foo (foo)
+
+ (dumped information when handling function foo goes here)
+
+For example::
+
+ class TestPass(gcc.GimplePass):
+ def execute(self, fun):
+ # Dumping of strings:
+ gcc.dump('hello world')
+
+ # Dumping of other objects:
+ gcc.dump(42)
+
+ ps = TestPass(name='test-pass')
+ ps.register_after('cfg')
+ ps.dump_enabled = True
+
+would have a dump file like this::
+
+ ;; Function bar (bar)
+
+ hello world42
+ ;; Function foo (foo)
+
+ hello world42
diff --git a/gcc-python-pass.c b/gcc-python-pass.c
index cfe68b9..01e0b25 100644
--- a/gcc-python-pass.c
+++ b/gcc-python-pass.c
@@ -234,6 +234,60 @@ gcc_Pass_repr(struct PyGccPass *self)
}
PyObject *
+gcc_Pass_get_dump_enabled(struct PyGccPass *self, void *closure)
+{
+ return PyBool_FromLong(dump_enabled_p(self->pass->static_pass_number));
+}
+
+int
+gcc_Pass_set_dump_enabled(struct PyGccPass *self, PyObject *value, void *closure)
+{
+ struct dump_file_info *dfi = get_dump_file_info (self->pass->static_pass_number);
+ assert(dfi);
+
+ int newbool = PyObject_IsTrue(value);
+ if (newbool == -1) {
+ return -1;
+ }
+
+ if (dfi->state == 0) {
+ /* Dumping was disabled: */
+ if (newbool) {
+ /* Enabling: */
+ dfi->state = -1;
+ return 0;
+ } else {
+ /* No change: */
+ return 0;
+ }
+ } else {
+ if (dfi->state < 0) {
+ /* Dumping was enabled but has not yet started */
+ if (newbool) {
+ /* No change: */
+ return 0;
+ } else {
+ /* Disabling: */
+ dfi->state = 0;
+ return 0;
+ }
+ } else {
+ assert(dfi->state > 0);
+ /* Dumping was enabled and has already started */
+ if (newbool) {
+ /* No change: */
+ return 0;
+ } else {
+ /* Can't disable after it's started: */
+ PyErr_SetString(PyExc_RuntimeError,
+ "Can't disable dumping: already started");
+ return -1;
+ }
+ }
+ }
+}
+
+PyObject *
gcc_Pass_get_roots(PyObject *cls, PyObject *noargs)
{
/*
diff --git a/gcc-python-wrappers.h b/gcc-python-wrappers.h
index 8adea67..21973bf 100644
--- a/gcc-python-wrappers.h
+++ b/gcc-python-wrappers.h
@@ -170,6 +170,12 @@ PyObject *
gcc_Pass_repr(struct PyGccPass * self);
PyObject *
+gcc_Pass_get_dump_enabled(struct PyGccPass *self, void *closure);
+
+int
+gcc_Pass_set_dump_enabled(struct PyGccPass *self, PyObject *value, void *closure);
+
+PyObject *
gcc_Pass_get_roots(PyObject *cls, PyObject *noargs);
PyObject *
diff --git a/gcc-python.c b/gcc-python.c
index c38d115..335408e 100644
--- a/gcc-python.c
+++ b/gcc-python.c
@@ -641,6 +641,51 @@ gcc_python_get_callgraph_nodes(PyObject *self, PyObject *args)
return NULL;
}
+/* Dump files */
+
+static PyObject *
+gcc_python_dump(PyObject *self, PyObject *arg)
+{
+ PyObject *str_obj;
+ /*
+ gcc/output.h: declares:
+ extern FILE *dump_file;
+ This is NULL when not defined.
+ */
+ if (!dump_file) {
+ /* The most common case; make it fast */
+ Py_RETURN_NONE;
+ }
+
+ str_obj = PyObject_Str(arg);
+ if (!str_obj) {
+ return NULL;
+ }
+
+ /* FIXME: encoding issues */
+ /* FIXME: GIL */
+ if (!fwrite(gcc_python_string_as_string(str_obj),
+ strlen(gcc_python_string_as_string(str_obj)),
+ 1,
+ dump_file)) {
+ Py_DECREF(str_obj);
+ return PyErr_SetFromErrnoWithFilename(PyExc_IOError, dump_file_name);
+ }
+
+ Py_DECREF(str_obj);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+gcc_python_get_dump_file_name(PyObject *self, PyObject *args)
+{
+ /* gcc/tree-pass.h declares:
+ extern const char *dump_file_name;
+ */
+ return gcc_python_string_or_none(dump_file_name);
+}
+
static PyMethodDef GccMethods[] = {
{"register_callback",
(PyCFunction)gcc_python_register_callback,
@@ -701,6 +746,13 @@ static PyMethodDef GccMethods[] = {
{"get_callgraph_nodes", gcc_python_get_callgraph_nodes, METH_VARARGS,
"Get a list of all gcc.CallgraphNode instances"},
+ /* Dump files */
+ {"dump", gcc_python_dump, METH_O,
+ "Dump str() of the argument to the current dump file (or silently discard it when no dump file is open)"},
+
+ {"get_dump_file_name", gcc_python_get_dump_file_name, METH_VARARGS,
+ "Get the name of the current dump file (or None)"},
+
/* Sentinel: */
{NULL, NULL, 0, NULL}
};
diff --git a/generate-pass-c.py b/generate-pass-c.py
index bc9308d..bca32de 100644
--- a/generate-pass-c.py
+++ b/generate-pass-c.py
@@ -53,6 +53,14 @@ def generate_pass():
field,
'gcc_python_int_from_long(self->pass->%s)' % field,
None)
+ getsettable.add_simple_getter(cu,
+ 'static_pass_number',
+ 'gcc_python_int_from_long(self->pass->static_pass_number)',
+ 'Number of this pass, used as a fragment of the dump file name')
+ getsettable.add_gsdef('dump_enabled',
+ 'gcc_Pass_get_dump_enabled',
+ 'gcc_Pass_set_dump_enabled',
+ '(boolean) Is dumping enabled for this pass?')
cu.add_defn(getsettable.c_defn())
methods = PyMethodTable('gcc_Pass_methods', [])
diff --git a/tests/plugin/dumpfiles/input.c b/tests/plugin/dumpfiles/input.c
new file mode 100644
index 0000000..aa74a15
--- /dev/null
+++ b/tests/plugin/dumpfiles/input.c
@@ -0,0 +1,44 @@
+/*
+ Copyright 2011 David Malcolm <dmalcolm(a)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/>.
+*/
+
+int foo(const char *str)
+{
+ if (str) {
+ return 42;
+ } else {
+ return 0;
+ }
+}
+
+int bar(const char *str)
+{
+ if (str) {
+ return 0;
+ } else {
+ return 42;
+ }
+}
+
+/*
+ PEP-7
+Local variables:
+c-basic-offset: 4
+indent-tabs-mode: nil
+End:
+*/
diff --git a/tests/plugin/dumpfiles/script.py b/tests/plugin/dumpfiles/script.py
new file mode 100644
index 0000000..1df49b1
--- /dev/null
+++ b/tests/plugin/dumpfiles/script.py
@@ -0,0 +1,64 @@
+# Copyright 2011 David Malcolm <dmalcolm(a)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/>.
+
+
+# Verify working with GCC's dump files
+
+import gcc
+
+pass_dumpfile = None
+class TestPass(gcc.GimplePass):
+ def execute(self, fun):
+ global pass_dumpfile
+ print('within TestPass.execute, for %r' % fun)
+ pass_dumpfile = gcc.get_dump_file_name()
+ if 0:
+ print('pass_dumpfile: %r' % pass_dumpfile)
+
+ # Dumping of strings:
+ gcc.dump('hello world')
+
+ # Dumping of other objects:
+ gcc.dump(42)
+
+ps = TestPass(name='test-pass')
+print('registering: %r' % ps)
+ps.register_after('cfg')
+
+assert isinstance(ps.static_pass_number, int)
+print('ps.dump_enabled: %r' % ps.dump_enabled)
+print('Manually enabling dump for our pass:')
+ps.dump_enabled = True
+print('ps.dump_enabled: %r' % ps.dump_enabled)
+
+# Now register another pass afterwards, to inspect the dumpfile
+# for the previous pass:
+class VerifyPass(gcc.GimplePass):
+ def execute(self, fun):
+ global pass_dumpfile
+ print('within VerifyPass.execute, for %r' % fun)
+ if 0:
+ print('pass_dumpfile: %r' % pass_dumpfile)
+ with open(pass_dumpfile) as f:
+ content = f.read()
+ print('--CONTENT OF DUMPFILE--')
+ print(content)
+ print('--END OF DUMPFILE--')
+
+ps = VerifyPass(name='verify-pass')
+print('registering: %r' % ps)
+ps.register_after('test-pass')
diff --git a/tests/plugin/dumpfiles/stdout.txt b/tests/plugin/dumpfiles/stdout.txt
new file mode 100644
index 0000000..b60c1b8
--- /dev/null
+++ b/tests/plugin/dumpfiles/stdout.txt
@@ -0,0 +1,24 @@
+registering: TestPass(name='test-pass')
+ps.dump_enabled: False
+Manually enabling dump for our pass:
+ps.dump_enabled: True
+registering: VerifyPass(name='verify-pass')
+within TestPass.execute, for gcc.Function('bar')
+within VerifyPass.execute, for gcc.Function('bar')
+--CONTENT OF DUMPFILE--
+
+;; Function bar (bar)
+
+hello world42
+--END OF DUMPFILE--
+within TestPass.execute, for gcc.Function('foo')
+within VerifyPass.execute, for gcc.Function('foo')
+--CONTENT OF DUMPFILE--
+
+;; Function bar (bar)
+
+hello world42
+;; Function foo (foo)
+
+hello world42
+--END OF DUMPFILE--
12 years, 10 months