import gcc import gccutils import sys def log(msg, indent=0): if False: sys.stderr.write('%s%s\n' % (' ' * indent, msg)) 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) destructor_names = set(['do_cleanups', 'discard_cleanups']) def is_destructor(decl): return decl.name in destructor_names special_names = set(['do_final_cleanups', 'discard_final_cleanups', 'save_cleanups', 'save_final_cleanups', 'restore_cleanups', 'restore_final_cleanups', 'exceptions_state_mc_init']) 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): pass def check_cleanups(fun): if not fun.cfg or not fun.decl: return 'ignored' if is_destructor(fun.decl): return 'destructor' if needs_special_treatment(fun.decl): return 'special' self_is_constructor = is_constructor(fun.decl) # If we only see do_cleanups calls, and this function is not # itself a constructor, then we can convert it easily to RAII. only_do_cleanups_seen = not self_is_constructor # If we ever call a constructor, then we are "cleanup-aware". cleanup_aware = False # This maps BB indices to the BB's master cleanup. master_cleanups = {} def compute_master(bb, master_cleanup): if not isinstance(bb.gimple, list): return None for stmt in bb.gimple: if isinstance(stmt, gcc.GimpleCall) and stmt.fndecl: log('function call', 2) if is_constructor(stmt.fndecl): log('is constructor ' + str(stmt.fndecl), 2) cleanup_aware = True if master_cleanup is None: master_cleanup = stmt.lhs if master_cleanup is None: # Maybe trigger another error later. master_cleanup = Dummy() elif is_destructor(stmt.fndecl): if stmt.fndecl.name != 'do_cleanups': only_do_cleanups_seen = False # This test is not really correct. # If we see a Phi we should peel it. # We should probably also look past other SSA # names. if stmt.args[0] == master_cleanup: master_cleanup = None elif needs_special_treatment(stmt.fndecl): pass # gcc.permerror(stmt.loc, 'function needs special treatment') elif isinstance(stmt, gcc.GimpleReturn): if self_is_constructor: # As above, the test here is incorrect. if stmt.retval is not master_cleanup: gcc.permerror(stmt.loc, 'constructor does not return master cleanup') else: log('GOT HERE: ' + str(master_cleanup), 2) if master_cleanup is not None: loc = stmt.loc if not loc: loc = fun.end gcc.permerror(loc, 'cleanup stack is not empty at return') return master_cleanup def traverse_bbs(bb, entry_master): if bb.index in master_cleanups: # FIXME do some checking return master_cleanups[bb.index] = compute_master(bb, entry_master) # Now propagate to successor nodes. for edge in bb.succs: traverse_bbs(edge.dest, master_cleanups[bb.index]) return traverse_bbs(fun.cfg.entry, None) if only_do_cleanups_seen and cleanup_aware: sys.stderr.write(str(fun.decl.location) + ':' + 'info: function ' + fun.decl.name + ' could be converted to RAII\n') if self_is_constructor: return 'constructor' return 'OK' def on_pass_execution(optpass, fun, *args, **kwargs): if optpass.name == 'release_ssa': if fun.decl: log("Starting " + fun.decl.name) what = check_cleanups(fun) if fun.decl: log(fun.decl.name + ': ' + what, 2) def main(**kwargs): gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION, on_pass_execution, **kwargs) main()