""" Hooks for validating CPython extension source code """ class CExtensionError(Exception): # Base class for errors discovered by static analysis in C extension code def __init__(self, location): self.location = location def __str__(self): return '%s:%s: %s' % (self.location.file, self.location.line, self._get_desc()) def _get_desc(self): raise NotImplementedError class UnknownFormatChar(CExtensionError): def __init__(self, location, ch): CExtensionError.__init__(self, location) self.ch = ch def _get_desc(self): return "unknown format char: '%s'" % self.ch class UnhandledCode(UnknownFormatChar): def _get_desc(self): return "unhandled format code: '%s' (FIXME)" % self.ch def get_types(location, strfmt): """ Generate a list of C type names from a PyArg_ParseTuple format string Compare to Python/getargs.c:vgetargs1 FIXME: only implements a very small subset of the various cases; no tuples, etc """ result = [] i = 0 while i < len(strfmt): c = strfmt[i] i += 1 if i < len(strfmt): next = strfmt[i] else: next = None # FIXME: '(', ')' if c in [':', ';']: break if c =='|': continue # From convertsimple: simple = {'b':'char', 'B':'char', 'h':'short', 'H':'short', 'i':'int', 'I':'int', 'n':'Py_ssize_t', 'l':'long', 'k':'unsigned long', # L, K: FIXME 'f':'float', 'd':'double', # D: FIXME, 'c':'char', } if c in simple: result.append(simple[c] + ' *') elif c in ['s', 'z']: # string, possibly NULL/None if next == '#': result += ['const char * *', 'int *'] i += 1 elif next == '*': result.append('Py_buffer *') i += 1 else: result.append('const char * *') # FIXME: seeing lots of (const char**) versus (char**) mismatches here # do we care? elif c == 'e': if next in ['s', 't']: result += ['const char *', 'char * *'] i += 1 if i < len(strfmt): if strfmt[i] == '#': result.append('int *') elif c == 'S': result.append('PyObject * *') elif c == 'U': result.append('PyObject * *') elif c == 'O': # object if next == '!': result += ['PyTypeObject *', 'PyObject * *'] i += 1 elif next == '?': raise UnhandledCode(location, c + next) # FIXME elif next == '&': # FIXME: can't really handle this case as is, fixing for fcntmodule.c result += ['int ( PyObject * object , int * target )', # converter 'int *'] # FIXME, anything i += 1 else: result.append('PyObject * *') elif c == 'w': raise UnhandledCode(location, c) # FIXME elif c == 't': if next == '#': result += ['char * *', 'int *'] i += 1 else: raise UnknownFormatChar(location, c) return result class WrongNumberOfVars(CExtensionError): def __init__(self, location, exp_types, actual_types): CExtensionError.__init__(self, location) self.exp_types = exp_types self.actual_types = actual_types class NotEnoughVars(WrongNumberOfVars): def _get_desc(self): return 'Not enough arguments: expected %i (%s), but got %i (%s)' % ( len(self.exp_types), self.exp_types, len(self.actual_types), self.actual_types) class TooManyVars(WrongNumberOfVars): def _get_desc(self): return 'Too many arguments: expected %i (%s), but got %i (%s)' % ( len(self.exp_types), self.exp_types, len(self.actual_types), self.actual_types) class MismatchingType(CExtensionError): def __init__(self, location, arg_num, exp_type, actual_type): super(self.__class__, self).__init__(location) self.arg_num = arg_num self.exp_type = exp_type self.actual_type = actual_type def _get_desc(self): return 'Mismatching type of argument %i: expected "%s" but got "%s"' % ( self.arg_num, self.exp_type, self.actual_type) def type_equality(t1, t2): if t1 == t2: return True # do we really care about char/const char mismatches?: if t1.startswith('const char *'): if t1 == 'const '+t2: return True if t2.startswith('const char *'): if 'const '+t1 == t2: return True return False def validate_types(location, format_string, actual_types): if False: print 'validate_types(%s, %s, %s)' % ( repr(location), repr(format_string), repr(actual_types)) try: exp_types = get_types(location, format_string[1:-1]) # strip leading and trailing " chars if len(actual_types) < len(exp_types): raise NotEnoughVars(location, exp_types, actual_types) if len(actual_types) > len(exp_types): raise TooManyVars(location, exp_types, actual_types) for i, (exp, actual) in enumerate(zip(exp_types, actual_types)): if not type_equality(exp, actual): raise MismatchingType(location, i+1, exp, actual) except CExtensionError, err: print err if False: print 'validate_types(%s, %s, %s)' % ( repr(location), repr(format_string), repr(actual_types)) return 1 return 0 import unittest class TestArgParsing(unittest.TestCase): def assert_args(self, arg_str, exp_result): result = get_types(None, arg_str) self.assertEquals(result, exp_result) def test_simple_cases(self): self.assert_args('c', ['char *']) def test_socketmodule_socket_htons(self): self.assert_args('i:htons', ['int *']) def test_fcntlmodule_fcntl_flock(self): # FIXME: somewhat broken, we can't know what the converter callback is self.assert_args("O&i:flock", ['int ( PyObject * object , int * target )', 'int *', 'int *']) if __name__ == '__main__': unittest.main()