GDB (API)
|
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')