Commit | Line | Data |
---|---|---|
ecd75fc8 | 1 | # Copyright 2011-2014 Free Software Foundation, Inc. |
c4a9e8b4 TT |
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 | ||
32 | import gcc | |
33 | import gccutils | |
34 | import sys | |
35 | ||
36 | # Where our output goes. | |
37 | output_file = None | |
38 | ||
39 | # Cleanup functions require special treatment, because they take a | |
40 | # function argument, but in theory the function must be nothrow. | |
41 | cleanup_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. | |
50 | ignore_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. | |
65 | non_passthrough_functions = { | |
66 | 'signal': 1, | |
67 | 'add_internal_function': 1 | |
68 | } | |
69 | ||
70 | # Return True if the type is from Python. | |
71 | def 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. | |
81 | def 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. | |
93 | def 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. | |
115 | def 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. | |
125 | class 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 | ||
283 | def 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 | ||
296 | main() |