diff options
Diffstat (limited to 'gdb/contrib/cleanup_check.py')
-rw-r--r-- | gdb/contrib/cleanup_check.py | 335 |
1 files changed, 0 insertions, 335 deletions
diff --git a/gdb/contrib/cleanup_check.py b/gdb/contrib/cleanup_check.py deleted file mode 100644 index be6babb..0000000 --- a/gdb/contrib/cleanup_check.py +++ /dev/null @@ -1,335 +0,0 @@ -# Copyright 2013-2019 Free Software Foundation, Inc. -# -# This is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see -# <http://www.gnu.org/licenses/>. - -import gcc -import gccutils -import sys - -want_raii_info = False - -logging = False -show_cfg = False - -def log(msg, indent=0): - global logging - if logging: - sys.stderr.write('%s%s\n' % (' ' * indent, msg)) - sys.stderr.flush() - -def is_cleanup_type(return_type): - if not isinstance(return_type, gcc.PointerType): - return False - if not isinstance(return_type.dereference, gcc.RecordType): - return False - if str(return_type.dereference.name) == 'cleanup': - return True - return False - -def is_constructor(decl): - "Return True if the function DECL is a cleanup constructor; False otherwise" - return is_cleanup_type(decl.type.type) and (not decl.name or str(decl.name) != 'make_final_cleanup') - -destructor_names = set(['do_cleanups', 'discard_cleanups']) - -def is_destructor(decl): - return decl.name in destructor_names - -# This list is just much too long... we should probably have an -# attribute instead. -special_names = set(['do_final_cleanups', 'discard_final_cleanups', - 'save_cleanups', 'save_final_cleanups', - 'restore_cleanups', 'restore_final_cleanups', - 'exceptions_state_mc_init', - 'make_my_cleanup2', 'make_final_cleanup', 'all_cleanups', - 'save_my_cleanups', 'quit_target']) - -def needs_special_treatment(decl): - return decl.name in special_names - -# Sometimes we need a new placeholder object that isn't the same as -# anything else. -class Dummy(object): - def __init__(self, location): - self.location = location - -# A wrapper for a cleanup which has been assigned to a variable. -# This holds the variable and the location. -class Cleanup(object): - def __init__(self, var, location): - self.var = var - self.location = location - -# A class representing a master cleanup. This holds a stack of -# cleanup objects and supports a merging operation. -class MasterCleanup(object): - # Create a new MasterCleanup object. OTHER, if given, is a - # MasterCleanup object to copy. - def __init__(self, other = None): - # 'cleanups' is a list of cleanups. Each element is either a - # Dummy, for an anonymous cleanup, or a Cleanup, for a cleanup - # which was assigned to a variable. - if other is None: - self.cleanups = [] - self.aliases = {} - else: - self.cleanups = other.cleanups[:] - self.aliases = dict(other.aliases) - - def compare_vars(self, definition, argument): - if definition == argument: - return True - if argument in self.aliases: - argument = self.aliases[argument] - if definition in self.aliases: - definition = self.aliases[definition] - return definition == argument - - def note_assignment(self, lhs, rhs): - log('noting assignment %s = %s' % (lhs, rhs), 4) - self.aliases[lhs] = rhs - - # Merge with another MasterCleanup. - # Returns True if this resulted in a change to our state. - def merge(self, other): - # We do explicit iteration like this so we can easily - # update the list after the loop. - counter = -1 - found_named = False - for counter in range(len(self.cleanups) - 1, -1, -1): - var = self.cleanups[counter] - log('merge checking %s' % var, 4) - # Only interested in named cleanups. - if isinstance(var, Dummy): - log('=> merge dummy', 5) - continue - # Now see if VAR is found in OTHER. - if other._find_var(var.var) >= 0: - log ('=> merge found', 5) - break - log('=>merge not found', 5) - found_named = True - if found_named and counter < len(self.cleanups) - 1: - log ('merging to %d' % counter, 4) - if counter < 0: - self.cleanups = [] - else: - self.cleanups = self.cleanups[0:counter] - return True - # If SELF is empty but OTHER has some cleanups, then consider - # that a change as well. - if len(self.cleanups) == 0 and len(other.cleanups) > 0: - log('merging non-empty other', 4) - self.cleanups = other.cleanups[:] - return True - return False - - # Push a new constructor onto our stack. LHS is the - # left-hand-side of the GimpleCall statement. It may be None, - # meaning that this constructor's value wasn't used. - def push(self, location, lhs): - if lhs is None: - obj = Dummy(location) - else: - obj = Cleanup(lhs, location) - log('pushing %s' % lhs, 4) - idx = self._find_var(lhs) - if idx >= 0: - gcc.permerror(location, 'reassigning to known cleanup') - gcc.inform(self.cleanups[idx].location, - 'previous assignment is here') - self.cleanups.append(obj) - - # A helper for merge and pop that finds BACK_TO in self.cleanups, - # and returns the index, or -1 if not found. - def _find_var(self, back_to): - for i in range(len(self.cleanups) - 1, -1, -1): - if isinstance(self.cleanups[i], Dummy): - continue - if self.compare_vars(self.cleanups[i].var, back_to): - return i - return -1 - - # Pop constructors until we find one matching BACK_TO. - # This is invoked when we see a do_cleanups call. - def pop(self, location, back_to): - log('pop:', 4) - i = self._find_var(back_to) - if i >= 0: - self.cleanups = self.cleanups[0:i] - else: - gcc.permerror(location, 'destructor call with unknown argument') - - # Check whether ARG is the current master cleanup. Return True if - # all is well. - def verify(self, location, arg): - log('verify %s' % arg, 4) - return (len(self.cleanups) > 0 - and not isinstance(self.cleanups[0], Dummy) - and self.compare_vars(self.cleanups[0].var, arg)) - - # Check whether SELF is empty. - def isempty(self): - log('isempty: len = %d' % len(self.cleanups), 4) - return len(self.cleanups) == 0 - - # Emit informational warnings about the cleanup stack. - def inform(self): - for item in reversed(self.cleanups): - gcc.inform(item.location, 'leaked cleanup') - -class CleanupChecker: - def __init__(self, fun): - self.fun = fun - self.seen_edges = set() - self.bad_returns = set() - - # This maps BB indices to a list of master cleanups for the - # BB. - self.master_cleanups = {} - - # Pick a reasonable location for the basic block BB. - def guess_bb_location(self, bb): - if isinstance(bb.gimple, list): - for stmt in bb.gimple: - if stmt.loc: - return stmt.loc - return self.fun.end - - # Compute the master cleanup list for BB. - # Modifies MASTER_CLEANUP in place. - def compute_master(self, bb, bb_from, master_cleanup): - if not isinstance(bb.gimple, list): - return - curloc = self.fun.end - for stmt in bb.gimple: - if stmt.loc: - curloc = stmt.loc - if isinstance(stmt, gcc.GimpleCall) and stmt.fndecl: - if is_constructor(stmt.fndecl): - log('saw constructor %s in bb=%d' % (str(stmt.fndecl), bb.index), 2) - self.cleanup_aware = True - master_cleanup.push(curloc, stmt.lhs) - elif is_destructor(stmt.fndecl): - if str(stmt.fndecl.name) != 'do_cleanups': - self.only_do_cleanups_seen = False - log('saw destructor %s in bb=%d, bb_from=%d, argument=%s' - % (str(stmt.fndecl.name), bb.index, bb_from, str(stmt.args[0])), - 2) - master_cleanup.pop(curloc, stmt.args[0]) - elif needs_special_treatment(stmt.fndecl): - pass - # gcc.permerror(curloc, 'function needs special treatment') - elif isinstance(stmt, gcc.GimpleAssign): - if isinstance(stmt.lhs, gcc.VarDecl) and isinstance(stmt.rhs[0], gcc.VarDecl): - master_cleanup.note_assignment(stmt.lhs, stmt.rhs[0]) - elif isinstance(stmt, gcc.GimpleReturn): - if self.is_constructor: - if not master_cleanup.verify(curloc, stmt.retval): - gcc.permerror(curloc, - 'constructor does not return master cleanup') - elif not self.is_special_constructor: - if not master_cleanup.isempty(): - if curloc not in self.bad_returns: - gcc.permerror(curloc, 'cleanup stack is not empty at return') - self.bad_returns.add(curloc) - master_cleanup.inform() - - # Traverse a basic block, updating the master cleanup information - # and propagating to other blocks. - def traverse_bbs(self, edge, bb, bb_from, entry_master): - log('traverse_bbs %d from %d' % (bb.index, bb_from), 1) - - # Propagate the entry MasterCleanup though this block. - master_cleanup = MasterCleanup(entry_master) - self.compute_master(bb, bb_from, master_cleanup) - - modified = False - if bb.index in self.master_cleanups: - # Merge the newly-computed MasterCleanup into the one we - # have already computed. If this resulted in a - # significant change, then we need to re-propagate. - modified = self.master_cleanups[bb.index].merge(master_cleanup) - else: - self.master_cleanups[bb.index] = master_cleanup - modified = True - - # EDGE is None for the entry BB. - if edge is not None: - # If merging cleanups caused a change, check to see if we - # have a bad loop. - if edge in self.seen_edges: - # This error doesn't really help. - # if modified: - # gcc.permerror(self.guess_bb_location(bb), - # 'invalid cleanup use in loop') - return - self.seen_edges.add(edge) - - if not modified: - return - - # Now propagate to successor nodes. - for edge in bb.succs: - self.traverse_bbs(edge, edge.dest, bb.index, master_cleanup) - - def check_cleanups(self): - if not self.fun.cfg or not self.fun.decl: - return 'ignored' - if is_destructor(self.fun.decl): - return 'destructor' - if needs_special_treatment(self.fun.decl): - return 'special' - - self.is_constructor = is_constructor(self.fun.decl) - self.is_special_constructor = not self.is_constructor and str(self.fun.decl.name).find('with_cleanup') > -1 - # Yuck. - if str(self.fun.decl.name) == 'gdb_xml_create_parser_and_cleanup_1': - self.is_special_constructor = True - - if self.is_special_constructor: - gcc.inform(self.fun.start, 'function %s is a special constructor' % (self.fun.decl.name)) - - # If we only see do_cleanups calls, and this function is not - # itself a constructor, then we can convert it easily to RAII. - self.only_do_cleanups_seen = not self.is_constructor - # If we ever call a constructor, then we are "cleanup-aware". - self.cleanup_aware = False - - entry_bb = self.fun.cfg.entry - master_cleanup = MasterCleanup() - self.traverse_bbs(None, entry_bb, -1, master_cleanup) - if want_raii_info and self.only_do_cleanups_seen and self.cleanup_aware: - gcc.inform(self.fun.decl.location, - 'function %s could be converted to RAII' % (self.fun.decl.name)) - if self.is_constructor: - return 'constructor' - return 'OK' - -class CheckerPass(gcc.GimplePass): - def execute(self, fun): - if fun.decl: - log("Starting " + fun.decl.name) - if show_cfg: - dot = gccutils.cfg_to_dot(fun.cfg, fun.decl.name) - gccutils.invoke_dot(dot, name=fun.decl.name) - checker = CleanupChecker(fun) - what = checker.check_cleanups() - if fun.decl: - log(fun.decl.name + ': ' + what, 2) - -ps = CheckerPass(name = 'check-cleanups') -# We need the cfg, but we want a relatively high-level Gimple. -ps.register_after('cfg') |