1#   Copyright 2011, 2013 Free Software Foundation, Inc.
2#
3#   This is free software: you can redistribute it and/or modify it
4#   under the terms of the GNU General Public License as published by
5#   the Free Software Foundation, either version 3 of the License, or
6#   (at your option) any later version.
7#
8#   This program is distributed in the hope that it will be useful, but
9#   WITHOUT ANY WARRANTY; without even the implied warranty of
10#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11#   General Public License for more details.
12#
13#   You should have received a copy of the GNU General Public License
14#   along with this program.  If not, see
15#   <http://www.gnu.org/licenses/>.
16
17# This is a GCC plugin that computes some exception-handling data for
18# gdb.  This data can then be summarized and checked by the
19# exsummary.py script.
20
21# To use:
22# * First, install the GCC Python plugin.  See
23#   https://fedorahosted.org/gcc-python-plugin/
24# * export PYTHON_PLUGIN=/full/path/to/plugin/directory
25#   This should be the directory holding "python.so".
26# * cd build/gdb; make mostlyclean
27# * make CC=.../gcc-with-excheck
28#   This will write a number of .py files in the build directory.
29# * python .../exsummary.py
30#   This will show the violations.
31
32import gcc
33import gccutils
34import sys
35
36# Where our output goes.
37output_file = None
38
39# Cleanup functions require special treatment, because they take a
40# function argument, but in theory the function must be nothrow.
41cleanup_functions = {
42    'make_cleanup': 1,
43    'make_cleanup_dtor': 1,
44    'make_final_cleanup': 1,
45    'make_my_cleanup2': 1,
46    'make_my_cleanup': 1
47}
48
49# Functions which may throw but which we want to ignore.
50ignore_functions = {
51    # This one is super special.
52    'exceptions_state_mc': 1,
53    # gdb generally pretends that internal_error cannot throw, even
54    # though it can.
55    'internal_error': 1,
56    # do_cleanups and friends are supposedly nothrow but we don't want
57    # to run afoul of the indirect function call logic.
58    'do_cleanups': 1,
59    'do_final_cleanups': 1
60}
61
62# Functions which take a function argument, but which are not
63# interesting, usually because the argument is not called in the
64# current context.
65non_passthrough_functions = {
66    'signal': 1,
67    'add_internal_function': 1
68}
69
70# Return True if the type is from Python.
71def type_is_pythonic(t):
72    if isinstance(t, gcc.ArrayType):
73        t = t.type
74    if not isinstance(t, gcc.RecordType):
75        return False
76    # Hack.
77    return str(t).find('struct Py') == 0
78
79# Examine all the fields of a struct.  We don't currently need any
80# sort of recursion, so this is simple for now.
81def examine_struct_fields(initializer):
82    global output_file
83    for idx2, value2 in initializer.elements:
84        if isinstance(idx2, gcc.Declaration):
85            if isinstance(value2, gcc.AddrExpr):
86                value2 = value2.operand
87                if isinstance(value2, gcc.FunctionDecl):
88                    output_file.write("declare_nothrow(%s)\n"
89                                      % repr(str(value2.name)))
90
91# Examine all global variables looking for pointers to functions in
92# structures whose types were defined by Python.
93def examine_globals():
94    global output_file
95    vars = gcc.get_variables()
96    for var in vars:
97        if not isinstance(var.decl, gcc.VarDecl):
98            continue
99        output_file.write("################\n")
100        output_file.write("# Analysis for %s\n" % var.decl.name)
101        if not var.decl.initial:
102            continue
103        if not type_is_pythonic(var.decl.type):
104            continue
105
106        if isinstance(var.decl.type, gcc.ArrayType):
107            for idx, value in var.decl.initial.elements:
108                examine_struct_fields(value)
109        else:
110            gccutils.check_isinstance(var.decl.type, gcc.RecordType)
111            examine_struct_fields(var.decl.initial)
112
113# Called at the end of compilation to write out some data derived from
114# globals and to close the output.
115def close_output(*args):
116    global output_file
117    examine_globals()
118    output_file.close()
119
120# The pass which derives some exception-checking information.  We take
121# a two-step approach: first we get a call graph from the compiler.
122# This is emitted by the plugin as Python code.  Then, we run a second
123# program that reads all the generated Python and uses it to get a
124# global view of exception routes in gdb.
125class GdbExceptionChecker(gcc.GimplePass):
126    def __init__(self, output_file):
127        gcc.GimplePass.__init__(self, 'gdb_exception_checker')
128        self.output_file = output_file
129
130    def log(self, obj):
131        self.output_file.write("# %s\n" % str(obj))
132
133    # Return true if FN is a call to a method on a Python object.
134    # We know these cannot throw in the gdb sense.
135    def fn_is_python_ignorable(self, fn):
136        if not isinstance(fn, gcc.SsaName):
137            return False
138        stmt = fn.def_stmt
139        if not isinstance(stmt, gcc.GimpleAssign):
140            return False
141        if stmt.exprcode is not gcc.ComponentRef:
142            return False
143        rhs = stmt.rhs[0]
144        if not isinstance(rhs, gcc.ComponentRef):
145            return False
146        if not isinstance(rhs.field, gcc.FieldDecl):
147            return False
148        return rhs.field.name == 'tp_dealloc' or rhs.field.name == 'tp_free'
149
150    # Decode a function call and write something to the output.
151    # THIS_FUN is the enclosing function that we are processing.
152    # FNDECL is the call to process; it might not actually be a DECL
153    # node.
154    # LOC is the location of the call.
155    def handle_one_fndecl(self, this_fun, fndecl, loc):
156        callee_name = ''
157        if isinstance(fndecl, gcc.AddrExpr):
158            fndecl = fndecl.operand
159        if isinstance(fndecl, gcc.FunctionDecl):
160            # Ordinary call to a named function.
161            callee_name = str(fndecl.name)
162            self.output_file.write("function_call(%s, %s, %s)\n"
163                                   % (repr(callee_name),
164                                      repr(this_fun.decl.name),
165                                      repr(str(loc))))
166        elif self.fn_is_python_ignorable(fndecl):
167            # Call to tp_dealloc.
168            pass
169        elif (isinstance(fndecl, gcc.SsaName)
170              and isinstance(fndecl.var, gcc.ParmDecl)):
171            # We can ignore an indirect call via a parameter to the
172            # current function, because this is handled via the rule
173            # for passthrough functions.
174            pass
175        else:
176            # Any other indirect call.
177            self.output_file.write("has_indirect_call(%s, %s)\n"
178                                   % (repr(this_fun.decl.name),
179                                      repr(str(loc))))
180        return callee_name
181
182    # This does most of the work for examine_one_bb.
183    # THIS_FUN is the enclosing function.
184    # BB is the basic block to process.
185    # Returns True if this block is the header of a TRY_CATCH, False
186    # otherwise.
187    def examine_one_bb_inner(self, this_fun, bb):
188        if not bb.gimple:
189            return False
190        try_catch = False
191        for stmt in bb.gimple:
192            loc = stmt.loc
193            if not loc:
194                loc = this_fun.decl.location
195            if not isinstance(stmt, gcc.GimpleCall):
196                continue
197            callee_name = self.handle_one_fndecl(this_fun, stmt.fn, loc)
198
199            if callee_name == 'exceptions_state_mc_action_iter':
200                try_catch = True
201
202            global non_passthrough_functions
203            if callee_name in non_passthrough_functions:
204                continue
205
206            # We have to specially handle calls where an argument to
207            # the call is itself a function, e.g., qsort.  In general
208            # we model these as "passthrough" -- we assume that in
209            # addition to the call the qsort there is also a call to
210            # the argument function.
211            for arg in stmt.args:
212                # We are only interested in arguments which are functions.
213                t = arg.type
214                if isinstance(t, gcc.PointerType):
215                    t = t.dereference
216                if not isinstance(t, gcc.FunctionType):
217                    continue
218
219                if isinstance(arg, gcc.AddrExpr):
220                    arg = arg.operand
221
222                global cleanup_functions
223                if callee_name in cleanup_functions:
224                    if not isinstance(arg, gcc.FunctionDecl):
225                        gcc.inform(loc, 'cleanup argument not a DECL: %s' % repr(arg))
226                    else:
227                        # Cleanups must be nothrow.
228                        self.output_file.write("declare_cleanup(%s)\n"
229                                               % repr(str(arg.name)))
230                else:
231                    # Assume we have a passthrough function, like
232                    # qsort or an iterator.  We model this by
233                    # pretending there is an ordinary call at this
234                    # point.
235                    self.handle_one_fndecl(this_fun, arg, loc)
236        return try_catch
237
238    # Examine all the calls in a basic block and generate output for
239    # them.
240    # THIS_FUN is the enclosing function.
241    # BB is the basic block to examine.
242    # BB_WORKLIST is a list of basic blocks to work on; we add the
243    # appropriate successor blocks to this.
244    # SEEN_BBS is a map whose keys are basic blocks we have already
245    # processed.  We use this to ensure that we only visit a given
246    # block once.
247    def examine_one_bb(self, this_fun, bb, bb_worklist, seen_bbs):
248        try_catch = self.examine_one_bb_inner(this_fun, bb)
249        for edge in bb.succs:
250            if edge.dest in seen_bbs:
251                continue
252            seen_bbs[edge.dest] = 1
253            if try_catch:
254                # This is bogus, but we magically know the right
255                # answer.
256                if edge.false_value:
257                    bb_worklist.append(edge.dest)
258            else:
259                bb_worklist.append(edge.dest)
260
261    # Iterate over all basic blocks in THIS_FUN.
262    def iterate_bbs(self, this_fun):
263        # Iteration must be in control-flow order, because if we see a
264        # TRY_CATCH construct we need to drop all the contained blocks.
265        bb_worklist = [this_fun.cfg.entry]
266        seen_bbs = {}
267        seen_bbs[this_fun.cfg.entry] = 1
268        for bb in bb_worklist:
269            self.examine_one_bb(this_fun, bb, bb_worklist, seen_bbs)
270
271    def execute(self, fun):
272        if fun and fun.cfg and fun.decl:
273            self.output_file.write("################\n")
274            self.output_file.write("# Analysis for %s\n" % fun.decl.name)
275            self.output_file.write("define_function(%s, %s)\n"
276                                   % (repr(fun.decl.name),
277                                      repr(str(fun.decl.location))))
278
279            global ignore_functions
280            if fun.decl.name not in ignore_functions:
281                self.iterate_bbs(fun)
282
283def main(**kwargs):
284    global output_file
285    output_file = open(gcc.get_dump_base_name() + '.gdb_exc.py', 'w')
286    # We used to use attributes here, but there didn't seem to be a
287    # big benefit over hard-coding.
288    output_file.write('declare_throw("throw_exception")\n')
289    output_file.write('declare_throw("throw_verror")\n')
290    output_file.write('declare_throw("throw_vfatal")\n')
291    output_file.write('declare_throw("throw_error")\n')
292    gcc.register_callback(gcc.PLUGIN_FINISH_UNIT, close_output)
293    ps = GdbExceptionChecker(output_file)
294    ps.register_after('ssa')
295
296main()
297