Commit | Line | Data |
---|---|---|
ecd75fc8 | 1 | # Copyright 2013-2014 Free Software Foundation, Inc. |
af83e3f8 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 | import gcc | |
18 | import gccutils | |
19 | import sys | |
20 | ||
21 | want_raii_info = False | |
22 | ||
23 | logging = False | |
24 | show_cfg = False | |
25 | ||
26 | def log(msg, indent=0): | |
27 | global logging | |
28 | if logging: | |
29 | sys.stderr.write('%s%s\n' % (' ' * indent, msg)) | |
30 | sys.stderr.flush() | |
31 | ||
32 | def is_cleanup_type(return_type): | |
33 | if not isinstance(return_type, gcc.PointerType): | |
34 | return False | |
35 | if not isinstance(return_type.dereference, gcc.RecordType): | |
36 | return False | |
37 | if str(return_type.dereference.name) == 'cleanup': | |
38 | return True | |
39 | return False | |
40 | ||
41 | def is_constructor(decl): | |
42 | "Return True if the function DECL is a cleanup constructor; False otherwise" | |
43 | return is_cleanup_type(decl.type.type) and (not decl.name or str(decl.name) != 'make_final_cleanup') | |
44 | ||
45 | destructor_names = set(['do_cleanups', 'discard_cleanups']) | |
46 | ||
47 | def is_destructor(decl): | |
48 | return decl.name in destructor_names | |
49 | ||
50 | # This list is just much too long... we should probably have an | |
51 | # attribute instead. | |
52 | special_names = set(['do_final_cleanups', 'discard_final_cleanups', | |
53 | 'save_cleanups', 'save_final_cleanups', | |
54 | 'restore_cleanups', 'restore_final_cleanups', | |
55 | 'exceptions_state_mc_init', | |
56 | 'make_my_cleanup2', 'make_final_cleanup', 'all_cleanups', | |
57 | 'save_my_cleanups', 'quit_target']) | |
58 | ||
59 | def needs_special_treatment(decl): | |
60 | return decl.name in special_names | |
61 | ||
62 | # Sometimes we need a new placeholder object that isn't the same as | |
63 | # anything else. | |
64 | class Dummy(object): | |
65 | def __init__(self, location): | |
66 | self.location = location | |
67 | ||
68 | # A wrapper for a cleanup which has been assigned to a variable. | |
69 | # This holds the variable and the location. | |
70 | class Cleanup(object): | |
71 | def __init__(self, var, location): | |
72 | self.var = var | |
73 | self.location = location | |
74 | ||
75 | # A class representing a master cleanup. This holds a stack of | |
76 | # cleanup objects and supports a merging operation. | |
77 | class MasterCleanup(object): | |
78 | # Create a new MasterCleanup object. OTHER, if given, is a | |
79 | # MasterCleanup object to copy. | |
80 | def __init__(self, other = None): | |
81 | # 'cleanups' is a list of cleanups. Each element is either a | |
82 | # Dummy, for an anonymous cleanup, or a Cleanup, for a cleanup | |
83 | # which was assigned to a variable. | |
84 | if other is None: | |
85 | self.cleanups = [] | |
86 | self.aliases = {} | |
87 | else: | |
88 | self.cleanups = other.cleanups[:] | |
89 | self.aliases = dict(other.aliases) | |
90 | ||
91 | def compare_vars(self, definition, argument): | |
92 | if definition == argument: | |
93 | return True | |
94 | if argument in self.aliases: | |
95 | argument = self.aliases[argument] | |
96 | if definition in self.aliases: | |
97 | definition = self.aliases[definition] | |
98 | return definition == argument | |
99 | ||
100 | def note_assignment(self, lhs, rhs): | |
101 | log('noting assignment %s = %s' % (lhs, rhs), 4) | |
102 | self.aliases[lhs] = rhs | |
103 | ||
104 | # Merge with another MasterCleanup. | |
105 | # Returns True if this resulted in a change to our state. | |
106 | def merge(self, other): | |
107 | # We do explicit iteration like this so we can easily | |
108 | # update the list after the loop. | |
109 | counter = -1 | |
110 | found_named = False | |
111 | for counter in range(len(self.cleanups) - 1, -1, -1): | |
112 | var = self.cleanups[counter] | |
113 | log('merge checking %s' % var, 4) | |
114 | # Only interested in named cleanups. | |
115 | if isinstance(var, Dummy): | |
116 | log('=> merge dummy', 5) | |
117 | continue | |
118 | # Now see if VAR is found in OTHER. | |
119 | if other._find_var(var.var) >= 0: | |
120 | log ('=> merge found', 5) | |
121 | break | |
122 | log('=>merge not found', 5) | |
123 | found_named = True | |
124 | if found_named and counter < len(self.cleanups) - 1: | |
125 | log ('merging to %d' % counter, 4) | |
126 | if counter < 0: | |
127 | self.cleanups = [] | |
128 | else: | |
129 | self.cleanups = self.cleanups[0:counter] | |
130 | return True | |
131 | # If SELF is empty but OTHER has some cleanups, then consider | |
132 | # that a change as well. | |
133 | if len(self.cleanups) == 0 and len(other.cleanups) > 0: | |
134 | log('merging non-empty other', 4) | |
135 | self.cleanups = other.cleanups[:] | |
136 | return True | |
137 | return False | |
138 | ||
139 | # Push a new constructor onto our stack. LHS is the | |
140 | # left-hand-side of the GimpleCall statement. It may be None, | |
141 | # meaning that this constructor's value wasn't used. | |
142 | def push(self, location, lhs): | |
143 | if lhs is None: | |
144 | obj = Dummy(location) | |
145 | else: | |
146 | obj = Cleanup(lhs, location) | |
147 | log('pushing %s' % lhs, 4) | |
148 | idx = self._find_var(lhs) | |
149 | if idx >= 0: | |
150 | gcc.permerror(location, 'reassigning to known cleanup') | |
151 | gcc.inform(self.cleanups[idx].location, | |
152 | 'previous assignment is here') | |
153 | self.cleanups.append(obj) | |
154 | ||
155 | # A helper for merge and pop that finds BACK_TO in self.cleanups, | |
156 | # and returns the index, or -1 if not found. | |
157 | def _find_var(self, back_to): | |
158 | for i in range(len(self.cleanups) - 1, -1, -1): | |
159 | if isinstance(self.cleanups[i], Dummy): | |
160 | continue | |
161 | if self.compare_vars(self.cleanups[i].var, back_to): | |
162 | return i | |
163 | return -1 | |
164 | ||
165 | # Pop constructors until we find one matching BACK_TO. | |
166 | # This is invoked when we see a do_cleanups call. | |
167 | def pop(self, location, back_to): | |
168 | log('pop:', 4) | |
169 | i = self._find_var(back_to) | |
170 | if i >= 0: | |
171 | self.cleanups = self.cleanups[0:i] | |
172 | else: | |
173 | gcc.permerror(location, 'destructor call with unknown argument') | |
174 | ||
175 | # Check whether ARG is the current master cleanup. Return True if | |
176 | # all is well. | |
177 | def verify(self, location, arg): | |
178 | log('verify %s' % arg, 4) | |
179 | return (len(self.cleanups) > 0 | |
180 | and not isinstance(self.cleanups[0], Dummy) | |
181 | and self.compare_vars(self.cleanups[0].var, arg)) | |
182 | ||
183 | # Check whether SELF is empty. | |
184 | def isempty(self): | |
185 | log('isempty: len = %d' % len(self.cleanups), 4) | |
186 | return len(self.cleanups) == 0 | |
187 | ||
188 | # Emit informational warnings about the cleanup stack. | |
189 | def inform(self): | |
190 | for item in reversed(self.cleanups): | |
191 | gcc.inform(item.location, 'leaked cleanup') | |
192 | ||
193 | class CleanupChecker: | |
194 | def __init__(self, fun): | |
195 | self.fun = fun | |
196 | self.seen_edges = set() | |
197 | self.bad_returns = set() | |
198 | ||
199 | # This maps BB indices to a list of master cleanups for the | |
200 | # BB. | |
201 | self.master_cleanups = {} | |
202 | ||
203 | # Pick a reasonable location for the basic block BB. | |
204 | def guess_bb_location(self, bb): | |
205 | if isinstance(bb.gimple, list): | |
206 | for stmt in bb.gimple: | |
207 | if stmt.loc: | |
208 | return stmt.loc | |
209 | return self.fun.end | |
210 | ||
211 | # Compute the master cleanup list for BB. | |
212 | # Modifies MASTER_CLEANUP in place. | |
213 | def compute_master(self, bb, bb_from, master_cleanup): | |
214 | if not isinstance(bb.gimple, list): | |
215 | return | |
216 | curloc = self.fun.end | |
217 | for stmt in bb.gimple: | |
218 | if stmt.loc: | |
219 | curloc = stmt.loc | |
220 | if isinstance(stmt, gcc.GimpleCall) and stmt.fndecl: | |
221 | if is_constructor(stmt.fndecl): | |
222 | log('saw constructor %s in bb=%d' % (str(stmt.fndecl), bb.index), 2) | |
223 | self.cleanup_aware = True | |
224 | master_cleanup.push(curloc, stmt.lhs) | |
225 | elif is_destructor(stmt.fndecl): | |
226 | if str(stmt.fndecl.name) != 'do_cleanups': | |
227 | self.only_do_cleanups_seen = False | |
228 | log('saw destructor %s in bb=%d, bb_from=%d, argument=%s' | |
229 | % (str(stmt.fndecl.name), bb.index, bb_from, str(stmt.args[0])), | |
230 | 2) | |
231 | master_cleanup.pop(curloc, stmt.args[0]) | |
232 | elif needs_special_treatment(stmt.fndecl): | |
233 | pass | |
234 | # gcc.permerror(curloc, 'function needs special treatment') | |
235 | elif isinstance(stmt, gcc.GimpleAssign): | |
236 | if isinstance(stmt.lhs, gcc.VarDecl) and isinstance(stmt.rhs[0], gcc.VarDecl): | |
237 | master_cleanup.note_assignment(stmt.lhs, stmt.rhs[0]) | |
238 | elif isinstance(stmt, gcc.GimpleReturn): | |
239 | if self.is_constructor: | |
240 | if not master_cleanup.verify(curloc, stmt.retval): | |
241 | gcc.permerror(curloc, | |
242 | 'constructor does not return master cleanup') | |
243 | elif not self.is_special_constructor: | |
244 | if not master_cleanup.isempty(): | |
245 | if curloc not in self.bad_returns: | |
246 | gcc.permerror(curloc, 'cleanup stack is not empty at return') | |
247 | self.bad_returns.add(curloc) | |
248 | master_cleanup.inform() | |
249 | ||
250 | # Traverse a basic block, updating the master cleanup information | |
251 | # and propagating to other blocks. | |
252 | def traverse_bbs(self, edge, bb, bb_from, entry_master): | |
253 | log('traverse_bbs %d from %d' % (bb.index, bb_from), 1) | |
254 | ||
255 | # Propagate the entry MasterCleanup though this block. | |
256 | master_cleanup = MasterCleanup(entry_master) | |
257 | self.compute_master(bb, bb_from, master_cleanup) | |
258 | ||
259 | modified = False | |
260 | if bb.index in self.master_cleanups: | |
261 | # Merge the newly-computed MasterCleanup into the one we | |
262 | # have already computed. If this resulted in a | |
263 | # significant change, then we need to re-propagate. | |
264 | modified = self.master_cleanups[bb.index].merge(master_cleanup) | |
265 | else: | |
266 | self.master_cleanups[bb.index] = master_cleanup | |
267 | modified = True | |
268 | ||
269 | # EDGE is None for the entry BB. | |
270 | if edge is not None: | |
271 | # If merging cleanups caused a change, check to see if we | |
272 | # have a bad loop. | |
273 | if edge in self.seen_edges: | |
274 | # This error doesn't really help. | |
275 | # if modified: | |
276 | # gcc.permerror(self.guess_bb_location(bb), | |
277 | # 'invalid cleanup use in loop') | |
278 | return | |
279 | self.seen_edges.add(edge) | |
280 | ||
281 | if not modified: | |
282 | return | |
283 | ||
284 | # Now propagate to successor nodes. | |
285 | for edge in bb.succs: | |
286 | self.traverse_bbs(edge, edge.dest, bb.index, master_cleanup) | |
287 | ||
288 | def check_cleanups(self): | |
289 | if not self.fun.cfg or not self.fun.decl: | |
290 | return 'ignored' | |
291 | if is_destructor(self.fun.decl): | |
292 | return 'destructor' | |
293 | if needs_special_treatment(self.fun.decl): | |
294 | return 'special' | |
295 | ||
296 | self.is_constructor = is_constructor(self.fun.decl) | |
297 | self.is_special_constructor = not self.is_constructor and str(self.fun.decl.name).find('with_cleanup') > -1 | |
298 | # Yuck. | |
299 | if str(self.fun.decl.name) == 'gdb_xml_create_parser_and_cleanup_1': | |
300 | self.is_special_constructor = True | |
301 | ||
302 | if self.is_special_constructor: | |
303 | gcc.inform(self.fun.start, 'function %s is a special constructor' % (self.fun.decl.name)) | |
304 | ||
305 | # If we only see do_cleanups calls, and this function is not | |
306 | # itself a constructor, then we can convert it easily to RAII. | |
307 | self.only_do_cleanups_seen = not self.is_constructor | |
308 | # If we ever call a constructor, then we are "cleanup-aware". | |
309 | self.cleanup_aware = False | |
310 | ||
311 | entry_bb = self.fun.cfg.entry | |
312 | master_cleanup = MasterCleanup() | |
313 | self.traverse_bbs(None, entry_bb, -1, master_cleanup) | |
314 | if want_raii_info and self.only_do_cleanups_seen and self.cleanup_aware: | |
315 | gcc.inform(self.fun.decl.location, | |
316 | 'function %s could be converted to RAII' % (self.fun.decl.name)) | |
317 | if self.is_constructor: | |
318 | return 'constructor' | |
319 | return 'OK' | |
320 | ||
321 | class CheckerPass(gcc.GimplePass): | |
322 | def execute(self, fun): | |
323 | if fun.decl: | |
324 | log("Starting " + fun.decl.name) | |
325 | if show_cfg: | |
326 | dot = gccutils.cfg_to_dot(fun.cfg, fun.decl.name) | |
327 | gccutils.invoke_dot(dot, name=fun.decl.name) | |
328 | checker = CleanupChecker(fun) | |
329 | what = checker.check_cleanups() | |
330 | if fun.decl: | |
331 | log(fun.decl.name + ': ' + what, 2) | |
332 | ||
333 | ps = CheckerPass(name = 'check-cleanups') | |
334 | # We need the cfg, but we want a relatively high-level Gimple. | |
335 | ps.register_after('cfg') |