gdb/contrib/cleanup_check.py - gdb

Global variables defined

Functions defined

Source code

  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. import gcc
  17. import gccutils
  18. import sys

  19. want_raii_info = False

  20. logging = False
  21. show_cfg = False

  22. def log(msg, indent=0):
  23.     global logging
  24.     if logging:
  25.         sys.stderr.write('%s%s\n' % ('  ' * indent, msg))
  26.         sys.stderr.flush()

  27. def is_cleanup_type(return_type):
  28.     if not isinstance(return_type, gcc.PointerType):
  29.         return False
  30.     if not isinstance(return_type.dereference, gcc.RecordType):
  31.         return False
  32.     if str(return_type.dereference.name) == 'cleanup':
  33.         return True
  34.     return False

  35. def is_constructor(decl):
  36.     "Return True if the function DECL is a cleanup constructor; False otherwise"
  37.     return is_cleanup_type(decl.type.type) and (not decl.name or str(decl.name) != 'make_final_cleanup')

  38. destructor_names = set(['do_cleanups', 'discard_cleanups'])

  39. def is_destructor(decl):
  40.     return decl.name in destructor_names

  41. # This list is just much too long... we should probably have an
  42. # attribute instead.
  43. special_names = set(['do_final_cleanups', 'discard_final_cleanups',
  44.                      'save_cleanups', 'save_final_cleanups',
  45.                      'restore_cleanups', 'restore_final_cleanups',
  46.                      'exceptions_state_mc_init',
  47.                      'make_my_cleanup2', 'make_final_cleanup', 'all_cleanups',
  48.                      'save_my_cleanups', 'quit_target'])

  49. def needs_special_treatment(decl):
  50.     return decl.name in special_names

  51. # Sometimes we need a new placeholder object that isn't the same as
  52. # anything else.
  53. class Dummy(object):
  54.     def __init__(self, location):
  55.         self.location = location

  56. # A wrapper for a cleanup which has been assigned to a variable.
  57. # This holds the variable and the location.
  58. class Cleanup(object):
  59.     def __init__(self, var, location):
  60.         self.var = var
  61.         self.location = location

  62. # A class representing a master cleanup.  This holds a stack of
  63. # cleanup objects and supports a merging operation.
  64. class MasterCleanup(object):
  65.     # Create a new MasterCleanup object.  OTHER, if given, is a
  66.     # MasterCleanup object to copy.
  67.     def __init__(self, other = None):
  68.         # 'cleanups' is a list of cleanups.  Each element is either a
  69.         # Dummy, for an anonymous cleanup, or a Cleanup, for a cleanup
  70.         # which was assigned to a variable.
  71.         if other is None:
  72.             self.cleanups = []
  73.             self.aliases = {}
  74.         else:
  75.             self.cleanups = other.cleanups[:]
  76.             self.aliases = dict(other.aliases)

  77.     def compare_vars(self, definition, argument):
  78.         if definition == argument:
  79.             return True
  80.         if argument in self.aliases:
  81.             argument = self.aliases[argument]
  82.         if definition in self.aliases:
  83.             definition = self.aliases[definition]
  84.         return definition == argument

  85.     def note_assignment(self, lhs, rhs):
  86.         log('noting assignment %s = %s' % (lhs, rhs), 4)
  87.         self.aliases[lhs] = rhs

  88.     # Merge with another MasterCleanup.
  89.     # Returns True if this resulted in a change to our state.
  90.     def merge(self, other):
  91.         # We do explicit iteration like this so we can easily
  92.         # update the list after the loop.
  93.         counter = -1
  94.         found_named = False
  95.         for counter in range(len(self.cleanups) - 1, -1, -1):
  96.             var = self.cleanups[counter]
  97.             log('merge checking %s' % var, 4)
  98.             # Only interested in named cleanups.
  99.             if isinstance(var, Dummy):
  100.                 log('=> merge dummy', 5)
  101.                 continue
  102.             # Now see if VAR is found in OTHER.
  103.             if other._find_var(var.var) >= 0:
  104.                 log ('=> merge found', 5)
  105.                 break
  106.             log('=>merge not found', 5)
  107.             found_named = True
  108.         if found_named and counter < len(self.cleanups) - 1:
  109.             log ('merging to %d' % counter, 4)
  110.             if counter < 0:
  111.                 self.cleanups = []
  112.             else:
  113.                 self.cleanups = self.cleanups[0:counter]
  114.             return True
  115.         # If SELF is empty but OTHER has some cleanups, then consider
  116.         # that a change as well.
  117.         if len(self.cleanups) == 0 and len(other.cleanups) > 0:
  118.             log('merging non-empty other', 4)
  119.             self.cleanups = other.cleanups[:]
  120.             return True
  121.         return False

  122.     # Push a new constructor onto our stack.  LHS is the
  123.     # left-hand-side of the GimpleCall statement.  It may be None,
  124.     # meaning that this constructor's value wasn't used.
  125.     def push(self, location, lhs):
  126.         if lhs is None:
  127.             obj = Dummy(location)
  128.         else:
  129.             obj = Cleanup(lhs, location)
  130.         log('pushing %s' % lhs, 4)
  131.         idx = self._find_var(lhs)
  132.         if idx >= 0:
  133.             gcc.permerror(location, 'reassigning to known cleanup')
  134.             gcc.inform(self.cleanups[idx].location,
  135.                        'previous assignment is here')
  136.         self.cleanups.append(obj)

  137.     # A helper for merge and pop that finds BACK_TO in self.cleanups,
  138.     # and returns the index, or -1 if not found.
  139.     def _find_var(self, back_to):
  140.         for i in range(len(self.cleanups) - 1, -1, -1):
  141.             if isinstance(self.cleanups[i], Dummy):
  142.                 continue
  143.             if self.compare_vars(self.cleanups[i].var, back_to):
  144.                 return i
  145.         return -1

  146.     # Pop constructors until we find one matching BACK_TO.
  147.     # This is invoked when we see a do_cleanups call.
  148.     def pop(self, location, back_to):
  149.         log('pop:', 4)
  150.         i = self._find_var(back_to)
  151.         if i >= 0:
  152.             self.cleanups = self.cleanups[0:i]
  153.         else:
  154.             gcc.permerror(location, 'destructor call with unknown argument')

  155.     # Check whether ARG is the current master cleanup.  Return True if
  156.     # all is well.
  157.     def verify(self, location, arg):
  158.         log('verify %s' % arg, 4)
  159.         return (len(self.cleanups) > 0
  160.                 and not isinstance(self.cleanups[0], Dummy)
  161.                 and self.compare_vars(self.cleanups[0].var, arg))

  162.     # Check whether SELF is empty.
  163.     def isempty(self):
  164.         log('isempty: len = %d' % len(self.cleanups), 4)
  165.         return len(self.cleanups) == 0

  166.     # Emit informational warnings about the cleanup stack.
  167.     def inform(self):
  168.         for item in reversed(self.cleanups):
  169.             gcc.inform(item.location, 'leaked cleanup')

  170. class CleanupChecker:
  171.     def __init__(self, fun):
  172.         self.fun = fun
  173.         self.seen_edges = set()
  174.         self.bad_returns = set()

  175.         # This maps BB indices to a list of master cleanups for the
  176.         # BB.
  177.         self.master_cleanups = {}

  178.     # Pick a reasonable location for the basic block BB.
  179.     def guess_bb_location(self, bb):
  180.         if isinstance(bb.gimple, list):
  181.             for stmt in bb.gimple:
  182.                 if stmt.loc:
  183.                     return stmt.loc
  184.         return self.fun.end

  185.     # Compute the master cleanup list for BB.
  186.     # Modifies MASTER_CLEANUP in place.
  187.     def compute_master(self, bb, bb_from, master_cleanup):
  188.         if not isinstance(bb.gimple, list):
  189.             return
  190.         curloc = self.fun.end
  191.         for stmt in bb.gimple:
  192.             if stmt.loc:
  193.                 curloc = stmt.loc
  194.             if isinstance(stmt, gcc.GimpleCall) and stmt.fndecl:
  195.                 if is_constructor(stmt.fndecl):
  196.                     log('saw constructor %s in bb=%d' % (str(stmt.fndecl), bb.index), 2)
  197.                     self.cleanup_aware = True
  198.                     master_cleanup.push(curloc, stmt.lhs)
  199.                 elif is_destructor(stmt.fndecl):
  200.                     if str(stmt.fndecl.name) != 'do_cleanups':
  201.                         self.only_do_cleanups_seen = False
  202.                     log('saw destructor %s in bb=%d, bb_from=%d, argument=%s'
  203.                         % (str(stmt.fndecl.name), bb.index, bb_from, str(stmt.args[0])),
  204.                         2)
  205.                     master_cleanup.pop(curloc, stmt.args[0])
  206.                 elif needs_special_treatment(stmt.fndecl):
  207.                     pass
  208.                     # gcc.permerror(curloc, 'function needs special treatment')
  209.             elif isinstance(stmt, gcc.GimpleAssign):
  210.                 if isinstance(stmt.lhs, gcc.VarDecl) and isinstance(stmt.rhs[0], gcc.VarDecl):
  211.                     master_cleanup.note_assignment(stmt.lhs, stmt.rhs[0])
  212.             elif isinstance(stmt, gcc.GimpleReturn):
  213.                 if self.is_constructor:
  214.                     if not master_cleanup.verify(curloc, stmt.retval):
  215.                         gcc.permerror(curloc,
  216.                                       'constructor does not return master cleanup')
  217.                 elif not self.is_special_constructor:
  218.                     if not master_cleanup.isempty():
  219.                         if curloc not in self.bad_returns:
  220.                             gcc.permerror(curloc, 'cleanup stack is not empty at return')
  221.                             self.bad_returns.add(curloc)
  222.                             master_cleanup.inform()

  223.     # Traverse a basic block, updating the master cleanup information
  224.     # and propagating to other blocks.
  225.     def traverse_bbs(self, edge, bb, bb_from, entry_master):
  226.         log('traverse_bbs %d from %d' % (bb.index, bb_from), 1)

  227.         # Propagate the entry MasterCleanup though this block.
  228.         master_cleanup = MasterCleanup(entry_master)
  229.         self.compute_master(bb, bb_from, master_cleanup)

  230.         modified = False
  231.         if bb.index in self.master_cleanups:
  232.             # Merge the newly-computed MasterCleanup into the one we
  233.             # have already computed.  If this resulted in a
  234.             # significant change, then we need to re-propagate.
  235.             modified = self.master_cleanups[bb.index].merge(master_cleanup)
  236.         else:
  237.             self.master_cleanups[bb.index] = master_cleanup
  238.             modified = True

  239.         # EDGE is None for the entry BB.
  240.         if edge is not None:
  241.             # If merging cleanups caused a change, check to see if we
  242.             # have a bad loop.
  243.             if edge in self.seen_edges:
  244.                 # This error doesn't really help.
  245.                 # if modified:
  246.                 #     gcc.permerror(self.guess_bb_location(bb),
  247.                 #                   'invalid cleanup use in loop')
  248.                 return
  249.             self.seen_edges.add(edge)

  250.         if not modified:
  251.             return

  252.         # Now propagate to successor nodes.
  253.         for edge in bb.succs:
  254.             self.traverse_bbs(edge, edge.dest, bb.index, master_cleanup)

  255.     def check_cleanups(self):
  256.         if not self.fun.cfg or not self.fun.decl:
  257.             return 'ignored'
  258.         if is_destructor(self.fun.decl):
  259.             return 'destructor'
  260.         if needs_special_treatment(self.fun.decl):
  261.             return 'special'

  262.         self.is_constructor = is_constructor(self.fun.decl)
  263.         self.is_special_constructor = not self.is_constructor and str(self.fun.decl.name).find('with_cleanup') > -1
  264.         # Yuck.
  265.         if str(self.fun.decl.name) == 'gdb_xml_create_parser_and_cleanup_1':
  266.             self.is_special_constructor = True

  267.         if self.is_special_constructor:
  268.             gcc.inform(self.fun.start, 'function %s is a special constructor' % (self.fun.decl.name))

  269.         # If we only see do_cleanups calls, and this function is not
  270.         # itself a constructor, then we can convert it easily to RAII.
  271.         self.only_do_cleanups_seen = not self.is_constructor
  272.         # If we ever call a constructor, then we are "cleanup-aware".
  273.         self.cleanup_aware = False

  274.         entry_bb = self.fun.cfg.entry
  275.         master_cleanup = MasterCleanup()
  276.         self.traverse_bbs(None, entry_bb, -1, master_cleanup)
  277.         if want_raii_info and self.only_do_cleanups_seen and self.cleanup_aware:
  278.             gcc.inform(self.fun.decl.location,
  279.                        'function %s could be converted to RAII' % (self.fun.decl.name))
  280.         if self.is_constructor:
  281.             return 'constructor'
  282.         return 'OK'

  283. class CheckerPass(gcc.GimplePass):
  284.     def execute(self, fun):
  285.         if fun.decl:
  286.             log("Starting " + fun.decl.name)
  287.             if show_cfg:
  288.                 dot = gccutils.cfg_to_dot(fun.cfg, fun.decl.name)
  289.                 gccutils.invoke_dot(dot, name=fun.decl.name)
  290.         checker = CleanupChecker(fun)
  291.         what = checker.check_cleanups()
  292.         if fun.decl:
  293.             log(fun.decl.name + ': ' + what, 2)

  294. ps = CheckerPass(name = 'check-cleanups')
  295. # We need the cfg, but we want a relatively high-level Gimple.
  296. ps.register_after('cfg')