GDB (API)
/home/stan/gdb/src/gdb/contrib/cleanup_check.py
Go to the documentation of this file.
00001 #   Copyright 2013 Free Software Foundation, Inc.
00002 #
00003 #   This is free software: you can redistribute it and/or modify it
00004 #   under the terms of the GNU General Public License as published by
00005 #   the Free Software Foundation, either version 3 of the License, or
00006 #   (at your option) any later version.
00007 #
00008 #   This program is distributed in the hope that it will be useful, but
00009 #   WITHOUT ANY WARRANTY; without even the implied warranty of
00010 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011 #   General Public License for more details.
00012 #
00013 #   You should have received a copy of the GNU General Public License
00014 #   along with this program.  If not, see
00015 #   <http://www.gnu.org/licenses/>.
00016 
00017 import gcc
00018 import gccutils
00019 import sys
00020 
00021 want_raii_info = False
00022 
00023 logging = False
00024 show_cfg = False
00025 
00026 def log(msg, indent=0):
00027     global logging
00028     if logging:
00029         sys.stderr.write('%s%s\n' % ('  ' * indent, msg))
00030         sys.stderr.flush()
00031 
00032 def is_cleanup_type(return_type):
00033     if not isinstance(return_type, gcc.PointerType):
00034         return False
00035     if not isinstance(return_type.dereference, gcc.RecordType):
00036         return False
00037     if str(return_type.dereference.name) == 'cleanup':
00038         return True
00039     return False
00040 
00041 def is_constructor(decl):
00042     "Return True if the function DECL is a cleanup constructor; False otherwise"
00043     return is_cleanup_type(decl.type.type) and (not decl.name or str(decl.name) != 'make_final_cleanup')
00044 
00045 destructor_names = set(['do_cleanups', 'discard_cleanups'])
00046 
00047 def is_destructor(decl):
00048     return decl.name in destructor_names
00049 
00050 # This list is just much too long... we should probably have an
00051 # attribute instead.
00052 special_names = set(['do_final_cleanups', 'discard_final_cleanups',
00053                      'save_cleanups', 'save_final_cleanups',
00054                      'restore_cleanups', 'restore_final_cleanups',
00055                      'exceptions_state_mc_init',
00056                      'make_my_cleanup2', 'make_final_cleanup', 'all_cleanups',
00057                      'save_my_cleanups', 'quit_target'])
00058 
00059 def needs_special_treatment(decl):
00060     return decl.name in special_names
00061 
00062 # Sometimes we need a new placeholder object that isn't the same as
00063 # anything else.
00064 class Dummy(object):
00065     def __init__(self, location):
00066         self.location = location
00067 
00068 # A wrapper for a cleanup which has been assigned to a variable.
00069 # This holds the variable and the location.
00070 class Cleanup(object):
00071     def __init__(self, var, location):
00072         self.var = var
00073         self.location = location
00074 
00075 # A class representing a master cleanup.  This holds a stack of
00076 # cleanup objects and supports a merging operation.
00077 class MasterCleanup(object):
00078     # Create a new MasterCleanup object.  OTHER, if given, is a
00079     # MasterCleanup object to copy.
00080     def __init__(self, other = None):
00081         # 'cleanups' is a list of cleanups.  Each element is either a
00082         # Dummy, for an anonymous cleanup, or a Cleanup, for a cleanup
00083         # which was assigned to a variable.
00084         if other is None:
00085             self.cleanups = []
00086             self.aliases = {}
00087         else:
00088             self.cleanups = other.cleanups[:]
00089             self.aliases = dict(other.aliases)
00090 
00091     def compare_vars(self, definition, argument):
00092         if definition == argument:
00093             return True
00094         if argument in self.aliases:
00095             argument = self.aliases[argument]
00096         if definition in self.aliases:
00097             definition = self.aliases[definition]
00098         return definition == argument
00099 
00100     def note_assignment(self, lhs, rhs):
00101         log('noting assignment %s = %s' % (lhs, rhs), 4)
00102         self.aliases[lhs] = rhs
00103 
00104     # Merge with another MasterCleanup.
00105     # Returns True if this resulted in a change to our state.
00106     def merge(self, other):
00107         # We do explicit iteration like this so we can easily
00108         # update the list after the loop.
00109         counter = -1
00110         found_named = False
00111         for counter in range(len(self.cleanups) - 1, -1, -1):
00112             var = self.cleanups[counter]
00113             log('merge checking %s' % var, 4)
00114             # Only interested in named cleanups.
00115             if isinstance(var, Dummy):
00116                 log('=> merge dummy', 5)
00117                 continue
00118             # Now see if VAR is found in OTHER.
00119             if other._find_var(var.var) >= 0:
00120                 log ('=> merge found', 5)
00121                 break
00122             log('=>merge not found', 5)
00123             found_named = True
00124         if found_named and counter < len(self.cleanups) - 1:
00125             log ('merging to %d' % counter, 4)
00126             if counter < 0:
00127                 self.cleanups = []
00128             else:
00129                 self.cleanups = self.cleanups[0:counter]
00130             return True
00131         # If SELF is empty but OTHER has some cleanups, then consider
00132         # that a change as well.
00133         if len(self.cleanups) == 0 and len(other.cleanups) > 0:
00134             log('merging non-empty other', 4)
00135             self.cleanups = other.cleanups[:]
00136             return True
00137         return False
00138 
00139     # Push a new constructor onto our stack.  LHS is the
00140     # left-hand-side of the GimpleCall statement.  It may be None,
00141     # meaning that this constructor's value wasn't used.
00142     def push(self, location, lhs):
00143         if lhs is None:
00144             obj = Dummy(location)
00145         else:
00146             obj = Cleanup(lhs, location)
00147         log('pushing %s' % lhs, 4)
00148         idx = self._find_var(lhs)
00149         if idx >= 0:
00150             gcc.permerror(location, 'reassigning to known cleanup')
00151             gcc.inform(self.cleanups[idx].location,
00152                        'previous assignment is here')
00153         self.cleanups.append(obj)
00154 
00155     # A helper for merge and pop that finds BACK_TO in self.cleanups,
00156     # and returns the index, or -1 if not found.
00157     def _find_var(self, back_to):
00158         for i in range(len(self.cleanups) - 1, -1, -1):
00159             if isinstance(self.cleanups[i], Dummy):
00160                 continue
00161             if self.compare_vars(self.cleanups[i].var, back_to):
00162                 return i
00163         return -1
00164 
00165     # Pop constructors until we find one matching BACK_TO.
00166     # This is invoked when we see a do_cleanups call.
00167     def pop(self, location, back_to):
00168         log('pop:', 4)
00169         i = self._find_var(back_to)
00170         if i >= 0:
00171             self.cleanups = self.cleanups[0:i]
00172         else:
00173             gcc.permerror(location, 'destructor call with unknown argument')
00174 
00175     # Check whether ARG is the current master cleanup.  Return True if
00176     # all is well.
00177     def verify(self, location, arg):
00178         log('verify %s' % arg, 4)
00179         return (len(self.cleanups) > 0
00180                 and not isinstance(self.cleanups[0], Dummy)
00181                 and self.compare_vars(self.cleanups[0].var, arg))
00182 
00183     # Check whether SELF is empty.
00184     def isempty(self):
00185         log('isempty: len = %d' % len(self.cleanups), 4)
00186         return len(self.cleanups) == 0
00187 
00188     # Emit informational warnings about the cleanup stack.
00189     def inform(self):
00190         for item in reversed(self.cleanups):
00191             gcc.inform(item.location, 'leaked cleanup')
00192 
00193 class CleanupChecker:
00194     def __init__(self, fun):
00195         self.fun = fun
00196         self.seen_edges = set()
00197         self.bad_returns = set()
00198 
00199         # This maps BB indices to a list of master cleanups for the
00200         # BB.
00201         self.master_cleanups = {}
00202 
00203     # Pick a reasonable location for the basic block BB.
00204     def guess_bb_location(self, bb):
00205         if isinstance(bb.gimple, list):
00206             for stmt in bb.gimple:
00207                 if stmt.loc:
00208                     return stmt.loc
00209         return self.fun.end
00210 
00211     # Compute the master cleanup list for BB.
00212     # Modifies MASTER_CLEANUP in place.
00213     def compute_master(self, bb, bb_from, master_cleanup):
00214         if not isinstance(bb.gimple, list):
00215             return
00216         curloc = self.fun.end
00217         for stmt in bb.gimple:
00218             if stmt.loc:
00219                 curloc = stmt.loc
00220             if isinstance(stmt, gcc.GimpleCall) and stmt.fndecl:
00221                 if is_constructor(stmt.fndecl):
00222                     log('saw constructor %s in bb=%d' % (str(stmt.fndecl), bb.index), 2)
00223                     self.cleanup_aware = True
00224                     master_cleanup.push(curloc, stmt.lhs)
00225                 elif is_destructor(stmt.fndecl):
00226                     if str(stmt.fndecl.name) != 'do_cleanups':
00227                         self.only_do_cleanups_seen = False
00228                     log('saw destructor %s in bb=%d, bb_from=%d, argument=%s'
00229                         % (str(stmt.fndecl.name), bb.index, bb_from, str(stmt.args[0])),
00230                         2)
00231                     master_cleanup.pop(curloc, stmt.args[0])
00232                 elif needs_special_treatment(stmt.fndecl):
00233                     pass
00234                     # gcc.permerror(curloc, 'function needs special treatment')
00235             elif isinstance(stmt, gcc.GimpleAssign):
00236                 if isinstance(stmt.lhs, gcc.VarDecl) and isinstance(stmt.rhs[0], gcc.VarDecl):
00237                     master_cleanup.note_assignment(stmt.lhs, stmt.rhs[0])
00238             elif isinstance(stmt, gcc.GimpleReturn):
00239                 if self.is_constructor:
00240                     if not master_cleanup.verify(curloc, stmt.retval):
00241                         gcc.permerror(curloc,
00242                                       'constructor does not return master cleanup')
00243                 elif not self.is_special_constructor:
00244                     if not master_cleanup.isempty():
00245                         if curloc not in self.bad_returns:
00246                             gcc.permerror(curloc, 'cleanup stack is not empty at return')
00247                             self.bad_returns.add(curloc)
00248                             master_cleanup.inform()
00249 
00250     # Traverse a basic block, updating the master cleanup information
00251     # and propagating to other blocks.
00252     def traverse_bbs(self, edge, bb, bb_from, entry_master):
00253         log('traverse_bbs %d from %d' % (bb.index, bb_from), 1)
00254 
00255         # Propagate the entry MasterCleanup though this block.
00256         master_cleanup = MasterCleanup(entry_master)
00257         self.compute_master(bb, bb_from, master_cleanup)
00258 
00259         modified = False
00260         if bb.index in self.master_cleanups:
00261             # Merge the newly-computed MasterCleanup into the one we
00262             # have already computed.  If this resulted in a
00263             # significant change, then we need to re-propagate.
00264             modified = self.master_cleanups[bb.index].merge(master_cleanup)
00265         else:
00266             self.master_cleanups[bb.index] = master_cleanup
00267             modified = True
00268 
00269         # EDGE is None for the entry BB.
00270         if edge is not None:
00271             # If merging cleanups caused a change, check to see if we
00272             # have a bad loop.
00273             if edge in self.seen_edges:
00274                 # This error doesn't really help.
00275                 # if modified:
00276                 #     gcc.permerror(self.guess_bb_location(bb),
00277                 #                   'invalid cleanup use in loop')
00278                 return
00279             self.seen_edges.add(edge)
00280 
00281         if not modified:
00282             return
00283 
00284         # Now propagate to successor nodes.
00285         for edge in bb.succs:
00286             self.traverse_bbs(edge, edge.dest, bb.index, master_cleanup)
00287 
00288     def check_cleanups(self):
00289         if not self.fun.cfg or not self.fun.decl:
00290             return 'ignored'
00291         if is_destructor(self.fun.decl):
00292             return 'destructor'
00293         if needs_special_treatment(self.fun.decl):
00294             return 'special'
00295 
00296         self.is_constructor = is_constructor(self.fun.decl)
00297         self.is_special_constructor = not self.is_constructor and str(self.fun.decl.name).find('with_cleanup') > -1
00298         # Yuck.
00299         if str(self.fun.decl.name) == 'gdb_xml_create_parser_and_cleanup_1':
00300             self.is_special_constructor = True
00301 
00302         if self.is_special_constructor:
00303             gcc.inform(self.fun.start, 'function %s is a special constructor' % (self.fun.decl.name))
00304 
00305         # If we only see do_cleanups calls, and this function is not
00306         # itself a constructor, then we can convert it easily to RAII.
00307         self.only_do_cleanups_seen = not self.is_constructor
00308         # If we ever call a constructor, then we are "cleanup-aware".
00309         self.cleanup_aware = False
00310 
00311         entry_bb = self.fun.cfg.entry
00312         master_cleanup = MasterCleanup()
00313         self.traverse_bbs(None, entry_bb, -1, master_cleanup)
00314         if want_raii_info and self.only_do_cleanups_seen and self.cleanup_aware:
00315             gcc.inform(self.fun.decl.location,
00316                        'function %s could be converted to RAII' % (self.fun.decl.name))
00317         if self.is_constructor:
00318             return 'constructor'
00319         return 'OK'
00320 
00321 class CheckerPass(gcc.GimplePass):
00322     def execute(self, fun):
00323         if fun.decl:
00324             log("Starting " + fun.decl.name)
00325             if show_cfg:
00326                 dot = gccutils.cfg_to_dot(fun.cfg, fun.decl.name)
00327                 gccutils.invoke_dot(dot, name=fun.decl.name)
00328         checker = CleanupChecker(fun)
00329         what = checker.check_cleanups()
00330         if fun.decl:
00331             log(fun.decl.name + ': ' + what, 2)
00332 
00333 ps = CheckerPass(name = 'check-cleanups')
00334 # We need the cfg, but we want a relatively high-level Gimple.
00335 ps.register_after('cfg')
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines