def flush(self): # Emit warnings, sorted by source location: for loc, node in sorted(self.state_users, key=lambda pair:pair[0]): gcc.inform(loc, 'use of global state "%s %s" here' % (node.type, node))
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'
def on_pass_execution(p, fn): if p.name == '*warn_function_return': # Exercise gcc.inform with gcc.RichLocation: gcc.inform(gcc.RichLocation(fn.start), 'this is the start of the function') gcc.inform(gcc.RichLocation(fn.end), 'this is the end of the function')
def on_pass_execution(p, fn): if p.name == '*warn_function_return': caret = fn.start.offset_column(13) start = fn.start.offset_column(9) finish = fn.start.offset_column(17) compound = gcc.Location(caret, start, finish) gcc.inform(compound, "compound location") assert compound.caret == caret assert compound.start == start assert compound.finish == finish
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)
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)
def examine_one_bb_inner(self, this_fun, bb): if not bb.gimple: return False try_catch = False for stmt in bb.gimple: loc = stmt.loc if not loc: loc = this_fun.decl.location if not isinstance(stmt, gcc.GimpleCall): continue callee_name = self.handle_one_fndecl(this_fun, stmt.fn, loc) if callee_name == 'exceptions_state_mc_action_iter': try_catch = True global non_passthrough_functions if callee_name in non_passthrough_functions: continue # We have to specially handle calls where an argument to # the call is itself a function, e.g., qsort. In general # we model these as "passthrough" -- we assume that in # addition to the call the qsort there is also a call to # the argument function. for arg in stmt.args: # We are only interested in arguments which are functions. t = arg.type if isinstance(t, gcc.PointerType): t = t.dereference if not isinstance(t, gcc.FunctionType): continue if isinstance(arg, gcc.AddrExpr): arg = arg.operand global cleanup_functions if callee_name in cleanup_functions: if not isinstance(arg, gcc.FunctionDecl): gcc.inform( loc, 'cleanup argument not a DECL: %s' % repr(arg)) else: # Cleanups must be nothrow. self.output_file.write("declare_cleanup(%s)\n" % repr(str(arg.name))) else: # Assume we have a passthrough function, like # qsort or an iterator. We model this by # pretending there is an ordinary call at this # point. self.handle_one_fndecl(this_fun, arg, loc) return try_catch
def examine_one_bb_inner(self, this_fun, bb): if not bb.gimple: return False try_catch = False for stmt in bb.gimple: loc = stmt.loc if not loc: loc = this_fun.decl.location if not isinstance(stmt, gcc.GimpleCall): continue callee_name = self.handle_one_fndecl(this_fun, stmt.fn, loc) if callee_name == 'exceptions_state_mc_action_iter': try_catch = True global non_passthrough_functions if callee_name in non_passthrough_functions: continue # We have to specially handle calls where an argument to # the call is itself a function, e.g., qsort. In general # we model these as "passthrough" -- we assume that in # addition to the call the qsort there is also a call to # the argument function. for arg in stmt.args: # We are only interested in arguments which are functions. t = arg.type if isinstance(t, gcc.PointerType): t = t.dereference if not isinstance(t, gcc.FunctionType): continue if isinstance(arg, gcc.AddrExpr): arg = arg.operand global cleanup_functions if callee_name in cleanup_functions: if not isinstance(arg, gcc.FunctionDecl): gcc.inform(loc, 'cleanup argument not a DECL: %s' % repr(arg)) else: # Cleanups must be nothrow. self.output_file.write("declare_cleanup(%s)\n" % repr(str(arg.name))) else: # Assume we have a passthrough function, like # qsort or an iterator. We model this by # pretending there is an ordinary call at this # point. self.handle_one_fndecl(this_fun, arg, loc) return try_catch
def on_pass_execution(p, fn): # This pass is called fairly early on, per-function, after the # CFG has been built. Skip every other pass. if p.name != '*warn_function_return': return # I don't want notices for system headers. if in_system_header(fn.decl.location): return for parm, parmtype in zip(fn.decl.arguments, fn.decl.type.argument_types): if type(parmtype) == gcc.RecordType: gcc.inform(parm.location, 'parameter type is not trivial')
def spellcheck_node(self, node, loc): # Spellcheck any textual constants found within the node: if isinstance(node, gcc.StringCst): words = node.constant.split() for word in words: if not spellingdict.check(word): # Warn about the spelling error (controlling the warning # with the -Wall command-line option): if gcc.warning(loc, 'Possibly misspelt word in string constant: %r' % word, gcc.Option('-Wall')): # and, if the warning was not suppressed at the command line, emit # suggested respellings: suggestions = spellingdict.suggest(word) if suggestions: gcc.inform(loc, 'Suggested respellings: %r' % ', '.join(suggestions))
def on_pass_execution(p, fn): if p.name == '*warn_function_return': gcc.error(fn.start, 'this is an error (with positional args)') gcc.error(location=fn.start, message='this is an error (with keyword args)') gcc.warning(fn.end, 'this is a warning (with positional args)', gcc.Option('-Wdiv-by-zero')) gcc.warning(location=fn.end, message='this is a warning (with keyword args)', option=gcc.Option('-Wdiv-by-zero')) gcc.error( fn.start, # These should be passed through, without triggering errors: 'a warning with some embedded format strings %s and %i') # Verify that -Wno-format was honored # The behavior of these flags changed in 4.8, so skip this part # on gcc 4.8 onwards: if gcc.GCCPLUGINS_API_VERSION <= 4007: gcc.warning(fn.end, 'this warning ought not to appear', gcc.Option('-Wformat')) # Verify that we can issue an unconditional warning, with no option # (as per https://fedorahosted.org/gcc-python-plugin/ticket/8 ): gcc.warning(fn.end, 'this is an unconditional warning') gcc.warning(fn.end, 'this is another unconditional warning', None) # Verify that gcc.warning handles an object of the wrong type by # raising a TypeError try: gcc.warning( fn.end, 'this is another unconditional warning', 'this should have been a gcc.Option instance, or None') except TypeError: err = sys.exc_info()[1] sys.stderr.write('expected error was found: %s\n' % err) else: raise RuntimeError('expected exception was not raised') # Exercise gcc.inform: gcc.inform(fn.start, 'This is the start of the function') gcc.inform(fn.end, 'This is the end of the function')
def on_pass_execution(p, fn): if p.name == '*warn_function_return': gcc.error(fn.start, 'this is an error (with positional args)') gcc.error(location=fn.start, message='this is an error (with keyword args)') gcc.warning(fn.end, 'this is a warning (with positional args)', gcc.Option('-Wdiv-by-zero')) gcc.warning(location=fn.end, message='this is a warning (with keyword args)', option=gcc.Option('-Wdiv-by-zero')) gcc.error(fn.start, # These should be passed through, without triggering errors: 'a warning with some embedded format strings %s and %i') # Verify that -Wno-format was honored # The behavior of these flags changed in 4.8, so skip this part # on gcc 4.8 onwards: if gcc.GCCPLUGINS_API_VERSION <= 4007: gcc.warning(fn.end, 'this warning ought not to appear', gcc.Option('-Wformat')) # Verify that we can issue an unconditional warning, with no option # (as per https://fedorahosted.org/gcc-python-plugin/ticket/8 ): gcc.warning(fn.end, 'this is an unconditional warning') gcc.warning(fn.end, 'this is another unconditional warning', None) # Verify that gcc.warning handles an object of the wrong type by # raising a TypeError try: gcc.warning(fn.end, 'this is another unconditional warning', 'this should have been a gcc.Option instance, or None') except TypeError: err = sys.exc_info()[1] sys.stderr.write('expected error was found: %s\n' % err) else: raise RuntimeError('expected exception was not raised') # Exercise gcc.inform: gcc.inform(fn.start, 'This is the start of the function') gcc.inform(fn.end, 'This is the end of the function')
def verify_any_PyMethodDef_flags(): """ Check all initializers for PyMethodDef arrays. Verify that the flags used match the real signature of the callback function (albeit usually cast to a PyCFunction): http://docs.python.org/c-api/structures.html#PyMethodDef """ methods = get_all_PyMethodDef_initializers() #from pprint import pprint #pprint(methods) for si in methods: if 0: print(si) ml_meth = si.function_ptr_field('ml_meth') ml_flags = si.int_field('ml_flags') if 0: print(' ml_meth: %r' % ml_meth) print(' ml_flags: %r' % ml_flags) check_isinstance(ml_flags, int) if ml_meth is not None: check_isinstance(ml_meth, gcc.FunctionDecl) if ml_flags & METH_KEYWORDS: expargs = 3 exptypemsg = 'expected ml_meth callback of type "PyObject (fn)(someobject *, PyObject *args, PyObject *kwargs)" due to METH_KEYWORDS flag' else: expargs = 2 exptypemsg = 'expected ml_meth callback of type "PyObject (fn)(someobject *, PyObject *)"' actualargs = len(ml_meth.type.argument_types) if expargs != actualargs: gcc.warning( si.get_location(), 'flags do not match callback signature for %r' ' within PyMethodDef table' % ml_meth.name) gcc.inform(si.get_location(), exptypemsg + ' (%s arguments)' % expargs) gcc.inform( si.get_location(), 'actual type of underlying callback: %s' % ml_meth.type + ' (%s arguments)' % actualargs) gcc.inform( si.get_location(), 'see http://docs.python.org/c-api/structures.html#PyMethodDef' )
def verify_any_PyMethodDef_flags(): """ Check all initializers for PyMethodDef arrays. Verify that the flags used match the real signature of the callback function (albeit usually cast to a PyCFunction): http://docs.python.org/c-api/structures.html#PyMethodDef """ methods = get_all_PyMethodDef_initializers() #from pprint import pprint #pprint(methods) for si in methods: if 0: print(si) ml_meth = si.function_ptr_field('ml_meth') ml_flags = si.int_field('ml_flags') if 0: print(' ml_meth: %r' % ml_meth) print(' ml_flags: %r' % ml_flags) check_isinstance(ml_flags, int) if ml_meth is not None: check_isinstance(ml_meth, gcc.FunctionDecl) if ml_flags & METH_KEYWORDS: expargs = 3 exptypemsg = 'expected ml_meth callback of type "PyObject (fn)(someobject *, PyObject *args, PyObject *kwargs)" due to METH_KEYWORDS flag' else: expargs = 2 exptypemsg = 'expected ml_meth callback of type "PyObject (fn)(someobject *, PyObject *)"' actualargs = len(ml_meth.type.argument_types) if expargs != actualargs: gcc.warning(si.get_location(), 'flags do not match callback signature for %r' ' within PyMethodDef table' % ml_meth.name) gcc.inform(si.get_location(), exptypemsg + ' (%s arguments)' % expargs) gcc.inform(si.get_location(), 'actual type of underlying callback: %s' % ml_meth.type + ' (%s arguments)' % actualargs) gcc.inform(si.get_location(), 'see http://docs.python.org/c-api/structures.html#PyMethodDef')
def on_pass_execution(p, fn): if p.name == '*free_lang_data': for var in gcc.get_variables(): gcc.inform(var.decl.location, 'global state "%s %s" defined here' % (var.decl.type, var.decl))
def inform(self): for item in reversed(self.cleanups): gcc.inform(item.location, 'leaked cleanup')
def flush(self): # Emit warnings, sorted by source location: for loc, node in sorted(self.state_users, key=lambda pair: pair[0]): gcc.inform(loc, 'use of global state "%s %s" here' % (node.type, node))
def find_function_calls(p, fn): if p.name != "*warn_function_return": return for bb in fn.cfg.basic_blocks: if not bb.gimple: continue for stmt in bb.gimple: # Filter out anything that's not a function call. if not isinstance(stmt, gcc.GimpleCall): continue # Filter out anything that's not a function pointer reference. We're only # looking for "out->message" function calls, which go through a pointer. if not isinstance(stmt.fn, gcc.SsaName): continue if not isinstance(stmt.fn.def_stmt, gcc.GimpleAssign): continue if len(stmt.fn.def_stmt.rhs) != 1: continue # Filter out anything that's not a pcmk__output_t and that's not a reference # to the message field. if not hasattr(stmt.fn.def_stmt.rhs[0], "target") or not hasattr(stmt.fn.def_stmt.rhs[0], "field"): continue target = stmt.fn.def_stmt.rhs[0].target field = stmt.fn.def_stmt.rhs[0].field if str(target.type) != "struct pcmk__output_t" or field.name != "message": continue # The first two arguments are the pcmk__output_t and the name of the message # being called. The compiler should have caught any cases where this is # wrong, but just in case... if len(stmt.args) < 2: continue if isinstance(stmt.args[1], gcc.SsaName) and message_from_fn_call(stmt.args[1]): # This is a call to the message function that figures out the message # name by calling some function. We can't figure out exactly which # message will be called at compile time, but it's almost certainly # going to be one of these four. Iterate over each and check. They # should all have the same arguments. for messageName in ["bundle", "clone", "group", "primitive"]: if messageName not in registeredMessages: gcc.error(stmt.loc, "Message not registered: %s" % messageName) break if not check_arg_count(stmt, messageName): break if not check_arg_types(stmt, messageName): break elif not isinstance(stmt.args[1], gcc.AddrExpr): # This is a call to the message function that uses some other method # to determine the message name. We can't figure it out, so just # print a note and keep going. gcc.inform(stmt.loc, "Cannot figure out message name") continue else: # This is a call to the message function that uses a string literal # for the message name. That's easy. messageName = str(stmt.args[1]).replace('"', '') if messageName not in registeredMessages: gcc.error(stmt.loc, "Unknown format message: %s" % messageName) continue # Check that enough arguments were provided. The expected length does # not include the first two arguments, which are not for the message. if not check_arg_count(stmt, messageName): continue # Check that the types are as expected. check_arg_types(stmt, messageName)
def flush(self): gcc.inform(self.loc, self.msg)