GDB (xrefs)
cleanup_check.py
Go to the documentation of this file.
1 # Copyright 2013-2015 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 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 
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 
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':
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')
def compute_master(self, bb, bb_from, master_cleanup)
def pop(self, location, back_to)
def _find_var(self, back_to)
def needs_special_treatment(decl)
def is_destructor(decl)
def is_cleanup_type(return_type)
def __init__(self, var, location)
Definition: value.c:65
def verify(self, location, arg)
def push(self, location, lhs)
def traverse_bbs(self, edge, bb, bb_from, entry_master)
def is_constructor(decl)
def compare_vars(self, definition, argument)
def __init__(self, location)
def note_assignment(self, lhs, rhs)
const ULONGEST const LONGEST len
Definition: target.h:309