diff options
Diffstat (limited to 'class_parser.py')
-rw-r--r-- | class_parser.py | 4434 |
1 files changed, 4434 insertions, 0 deletions
diff --git a/class_parser.py b/class_parser.py new file mode 100644 index 0000000..db140f3 --- /dev/null +++ b/class_parser.py @@ -0,0 +1,4434 @@ +#!/usr/bin/python +# +# Copyright 2010, Michael Cohen <scudette@gmail.com>. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Documentation regarding the Python bounded code. + +This code originally released as part of the AFF4 project +(http://code.google.com/p/aff4/). + +Memory Management +================= + +AFF4 uses a reference count system for memory management similar in +many ways to the native Python system. The basic idea is that memory +returned by the library always carries a new reference. When the +caller is done with the memory, they must call aff4_free() on the +memory, afterwhich the memory is considered invalid. The memory may +still not be freed at this point depending on its total reference +count. + +New references may be taken to the same memory at any time using the +aff4_incref() function. This increases the reference count of the +object, and prevents it from being really freed until the correct +number of aff4_free() calls are made to it. + +This idea is important for example in the following sequence: + +FileLikeObject fd = resolver->create(resolver, "w"); +RDFURN uri = fd->urn; + +Now uri hold a reference to the urn attribute of fd, but that +attribute is actually owned by fd. If fd is freed in future, e.g. (the +close method actually frees the fd implicitely): + +fd->close(fd); + +Now the uri object is dangling. To prevent fd->urn from disappearing +when fd is freed, we need to take another reference to it: + +FileLikeObject fd = resolver->create(resolver, "w"); +RDFURN uri = fd->urn; +aff4_incref(uri); + +fd->close(fd); + +Now uri is valid (but fd is no longer valid). When we are finished +with uri we just call: + +aff4_free(uri); + + +Python Integration +------------------ + +For every AFF4 object, we create a Python wrapper object of the +corresponding type. The wrapper object contains Python wrapper methods +to allow access to the AFF4 object methods, as well as getattr methods +for attributes. It is very important to allow Python to inherit from C +classes directly - this requires every internal C method call to be +diverted to the Python object. + +The C object looks like this normally: + +struct obj { + __class__ pointer to static struct initialised with C method pointers + +... Some private members +... Attributes; + +/* Following are the methods */ + int (*method)(struct obj *self, ....); +}; + +I.e. when the method is called the struct.method member is +dereferenced to find the location of the function handling it, the +object is stuffed into the first arg, and the parameters are stuffed +into following args. + +Directing Python calls +---------------------- + +The Python object which is created is a proxy for the c object. When +Python methods are called in the Python object, they need to be +directed into the C structure and a C call must be made, then the +return value must be reconverted into Python objects and returned into +Python. This occurs automatically by the wrapper: + +struct PythonWrapper { + PyObject_HEAD + void *base; +}; + +When a Python method is called on this new Python type this is what happens: + + 1) The method name is looked up in the PyMethodDef struct as per normal. + + 2) If the method is recognised as a valid method the Python wrapper + function is called (pyCLASSNAME_method) + + 3) This method is broken into the general steps: + +PyObject *pyCLASSNAME_method(PythonWrapper self, PyObject *args, PyObject *kwds) { + set up c declerations for all args - call .definition() on all the args and return type + + parse argument using PyArg_ParseTupleAndKeywords + + Precall preparations + + Make the C call + + Post call processing of the returned value (check for errors etc) + + Convert the return value to a Python object using: + return_type.to_Python_object() + + return the Python object or raise an exception +}; + +So the aim of the wrapper function is to convert Python args to C +args, find the C method corresponding to the method name by +dereferencing the c object and then call it. + + +The problem now is what happens when a C method internally calls +another method. This is a problem because the C method has no idea its +running within Python and so will just call the regular C method that +was there already. This makes it impossible to subclass the class and +update the C method with a Python method. What we really want is when +a C method is called internally, we want to end up calling the Python +object instead to allow a purely Python implementation to override the +C method. + +This happens by way of a ProxiedMethod - A proxied method is in a +sense the reverse of the wrapper method: + +return_type ProxyCLASSNAME_method(CLASSNAME self, ....) { + Take all C args and create Python objects from them + + Dereference the object extension ((Object) self)->extension to + obtain the Python object which wraps this class. + + If an extension does not exist, just call the method as normal, + otherwise make a Python call on the wrapper object. + + Convert the returned Python object to a C type and return it. +}; + +To make all this work we have the following structures: +struct PythonWrapper { + PyObject_HEAD + struct CLASSNAME *base + + - This is a copy of the item, with all function pointer + pointing at proxy functions. We can always get the original C + function pointers through base->__class__ + + - We also set the base object extension to be the Python + object: ((Object) base)->extension = PythonWrapper. This + allows us to get back the Python object from base. +}; + + +When a Python method is invoked, we use cbase to find the C method +pointer, but we pass to it base: + +self->base->__class__->method(self->base, ....) + +base is a proper C object which had its methods dynamically replaced +with proxies. Now if an internal C method is called, the method will +dereference base and retrieve the proxied method. Calling the +proxied method will retreive the original Python object from the +object extension and make a Python call. + +In the case where a method is not overridden by Python, internal C +method calls will generate an unnecessary conversion from C to Python +and then back to C. + +Memory management in Python extension +------------------------------------- + +When calling a method which returns a new reference, we just store the +reference in the "base" member of the Python object. When Python +garbage collects our Python object, we call aff4_free() on it. + +The getattr method creates a new Python wrapper object of the correct +type, and sets its base attribute to point at the target AFF4 +object. We then aff4_incref() the target to ensure that it does not +get freed until we are finished with it. + + + Python Object + ----- + | P1 | C Object + | Base|-->+------+ + | | | C1 | + | | | | + ----- |Member|--------------+-->+----+ + +------+ | | C2 | + | | | + Getattr ------- | | | + Member | P2 | | +----+ + | Base |--+ New reference + ------- + Python Object + + Figure 1: Python object 1 owns C1's memory (when P1 is GC'ed C1 is + freed). A reference to a member of C1 is made via P1's + getattr method. The getattr method creates P2 to provide + access to C2 by setting base to C2's address. We need to + guarantee however, that C2 will not be freed suddenly + (e.g. if C1 is freed). We therefore increase C2's + reference count using aff4_incref(); +""" + +import io +import os +import pdb +import re +import sys + +import lexer + +DEBUG = 0 + +# The pytsk3 version. +VERSION = "20200117" + +# These functions are used to manage library memory. +FREE = "aff4_free" +INCREF = "aff4_incref" +CURRENT_ERROR_FUNCTION = "aff4_get_current_error" +CONSTANTS_BLACKLIST = ["TSK3_H_"] + +# Some constants. +DOCSTRING_RE = re.compile("[ ]*\n[ \t]+[*][ ]?") + + +def dispatch(name, type, *args, **kwargs): + if not type: + return PVoid(name) + + m = re.match("struct ([a-zA-Z0-9]+)_t *", type) + if m: + type = m.group(1) + + type_components = type.split() + attributes = set() + + if type_components[0] in method_attributes: + attributes.add(type_components.pop(0)) + + type = " ".join(type_components) + result = type_dispatcher[type](name, type, *args, **kwargs) + + result.attributes = attributes + + return result + + +def log(msg): + if DEBUG > 0: + sys.stderr.write("{0:s}\n".format(msg)) + + +def format_as_docstring(string): + # Remove C/C++ comment code statements. + string = DOCSTRING_RE.sub("\n", string) + byte_string = string.encode("unicode-escape") + # Escapes double quoted string. We need to run this after unicode-escape to + # prevent this operation to escape the escape character (\). In Python 3 + # the replace method requires the arguments to be byte strings. + byte_string = byte_string.replace(b"\"", b"\\\"") + # Make sure to return the string a Unicode otherwise in Python 3 the string + # is prefixed with b when written or printed. + return byte_string.decode("utf-8") + + +class Module(object): + public_api = None + public_header = None + + def __init__(self, name): + self.name = name + self.constants = set() + self.constants_blacklist = CONSTANTS_BLACKLIST + self.classes = {} + self.headers = "#include <Python.h>\n" + self.files = [] + self.active_structs = set() + self.function_definitions = set() + + init_string = "" + + def initialization(self): + result = self.init_string + ( + "\n" + "talloc_set_log_fn((void (*)(const char *)) printf);\n" + "// DEBUG: talloc_enable_leak_report();\n" + "// DEBUG: talloc_enable_leak_report_full();\n") + + for cls in self.classes.values(): + if cls.is_active(): + result += cls.initialise() + + return result + + def add_constant(self, constant, type="numeric"): + """This will be called to add #define constant macros.""" + self.constants.add((constant, type)) + + def add_class(self, cls, handler): + self.classes[cls.class_name] = cls + + # Make a wrapper in the type dispatcher so we can handle + # passing this class from/to Python + type_dispatcher[cls.class_name] = handler + + def get_string(self): + """Retrieves a string representation.""" + result = "Module {0:s}\n".format(self.name) + classes_list = list(self.classes.values()) + classes_list.sort(key=lambda cls: cls.class_name) + for cls in classes_list: + if cls.is_active(): + result += " {0:s}\n".format(cls.get_string()) + + constants_list = list(self.constants) + constants_list.sort() + result += "Constants:\n" + for name, _ in constants_list: + result += " {0:s}\n".format(name) + + return result + + def private_functions(self): + """Emits hard coded private functions for doing various things""" + values_dict = { + "classes_length": len(self.classes) + 1, + "get_current_error": CURRENT_ERROR_FUNCTION} + + return """ +/* The following is a static array mapping CLASS() pointers to their + * Python wrappers. This is used to allow the correct wrapper to be + * chosen depending on the object type found - regardless of the + * prototype. + * + * This is basically a safer way for us to cast the correct Python type + * depending on context rather than assuming a type based on the .h + * definition. For example consider the function + * + * AFFObject Resolver.open(uri, mode) + * + * The .h file implies that an AFFObject object is returned, but this is + * not true as most of the time an object of a derived class will be + * returned. In C we cast the returned value to the correct type. In the + * Python wrapper we just instantiate the correct Python object wrapper + * at runtime depending on the actual returned type. We use this lookup + * table to do so. + */ +static int TOTAL_CLASSES=0; + +/* This is a global reference to this module so classes can call each + * other. + */ +static PyObject *g_module = NULL; + +#define CONSTRUCT_INITIALIZE(class, virt_class, constructor, object, ...) \\ + (class)(((virt_class) (&__ ## class))->constructor(object, ## __VA_ARGS__)) + +#undef BUFF_SIZE +#define BUFF_SIZE 10240 + +/* Python compatibility macros + */ +#if !defined( PyMODINIT_FUNC ) +#if PY_MAJOR_VERSION >= 3 +#define PyMODINIT_FUNC PyObject * +#else +#define PyMODINIT_FUNC void +#endif +#endif /* !defined( PyMODINIT_FUNC ) */ + +#if !defined( PyVarObject_HEAD_INIT ) +#define PyVarObject_HEAD_INIT( type, size ) \\ + PyObject_HEAD_INIT( type ) \\ + size, + +#endif /* !defined( PyVarObject_HEAD_INIT ) */ + +#if PY_MAJOR_VERSION >= 3 +#define Py_TPFLAGS_HAVE_ITER 0 +#endif + +#if !defined( Py_TYPE ) +#define Py_TYPE( object ) \\ + ( ( (PyObject *) object )->ob_type ) + +#endif /* !defined( Py_TYPE ) */ + +/* Generic wrapper type + */ +typedef struct Gen_wrapper_t *Gen_wrapper; +struct Gen_wrapper_t {{ + PyObject_HEAD + + void *base; + + /* Value to indicate the base is a Python object. + */ + int base_is_python_object; + + /* Value to indicate the base is managed internal. + */ + int base_is_internal; + + PyObject *python_object1; + PyObject *python_object2; +}}; + +static struct python_wrapper_map_t {{ + Object class_ref; + PyTypeObject *python_type; + void (*initialize_proxies)(Gen_wrapper self, void *item); +}} python_wrappers[{classes_length:d}]; + +/* Create the relevant wrapper from the item based on the lookup table. + */ +Gen_wrapper new_class_wrapper(Object item, int item_is_python_object) {{ + Gen_wrapper result = NULL; + Object cls = NULL; + struct python_wrapper_map_t *python_wrapper = NULL; + int cls_index = 0; + + // Return a Py_None object for a NULL pointer + if(item == NULL) {{ + Py_IncRef((PyObject *) Py_None); + return (Gen_wrapper) Py_None; + }} + // Search for subclasses + for(cls = (Object) item->__class__; cls != cls->__super__; cls = cls->__super__) {{ + for(cls_index = 0; cls_index < TOTAL_CLASSES; cls_index++) {{ + python_wrapper = &(python_wrappers[cls_index]); + + if(python_wrapper->class_ref == cls) {{ + PyErr_Clear(); + + result = (Gen_wrapper) _PyObject_New(python_wrapper->python_type); + result->base = item; + result->base_is_python_object = item_is_python_object; + result->base_is_internal = 1; + result->python_object1 = NULL; + result->python_object2 = NULL; + + python_wrapper->initialize_proxies(result, (void *) item); + + return result; + }} + }} + }} + PyErr_Format(PyExc_RuntimeError, "Unable to find a wrapper for object %s", NAMEOF(item)); + + return NULL; +}} + +static PyObject *resolve_exception(char **error_buff) {{ + int *type = (int *){get_current_error:s}(error_buff); + + switch(*type) {{ + case EProgrammingError: + return PyExc_SystemError; + case EKeyError: + return PyExc_KeyError; + case ERuntimeError: + return PyExc_RuntimeError; + case EInvalidParameter: + return PyExc_TypeError; + case EWarning: + return PyExc_AssertionError; + case EIOError: + return PyExc_IOError; + default: + return PyExc_RuntimeError; + }} +}} + +static int type_check(PyObject *obj, PyTypeObject *type) {{ + PyTypeObject *tmp = NULL; + + // Recurse through the inheritance tree and check if the types are expected + if(obj) {{ + for(tmp = Py_TYPE(obj); + tmp && tmp != &PyBaseObject_Type; + tmp = tmp->tp_base) {{ + if(tmp == type) return 1; + }} + }} + return 0; +}} + +static int check_error() {{ + char *buffer = NULL; + int *error_type = (int *)aff4_get_current_error(&buffer); + + if(*error_type != EZero) {{ + PyObject *exception = resolve_exception(&buffer); + + if(buffer != NULL) {{ + PyErr_Format(exception, "%s", buffer); + }} else {{ + PyErr_Format(exception, "Unable to retrieve exception reason."); + }} + ClearError(); + return 1; + }} + return 0; +}} + +/* This function checks if a method was overridden in self over a + * method defined in type. This is used to determine if a Python class is + * extending this C type. If not, a proxy function is not written and C + * calls are made directly. + * + * This is an optimization to eliminate the need for a call into Python + * in the case where Python objects do not actually extend any methods. + * + * We basically just iterate over the MRO and determine if a method is + * defined in each level until we reach the base class. + */ +static int check_method_override(PyObject *self, PyTypeObject *type, char *method) {{ + struct _typeobject *ob_type = NULL; + PyObject *mro = NULL; + PyObject *py_method = NULL; + PyObject *item_object = NULL; + PyObject *dict = NULL; + Py_ssize_t item_index = 0; + Py_ssize_t number_of_items = 0; + int found = 0; + + ob_type = Py_TYPE(self); + if(ob_type == NULL ) {{ + return 0; + }} + mro = ob_type->tp_mro; + +#if PY_MAJOR_VERSION >= 3 + py_method = PyUnicode_FromString(method); +#else + py_method = PyString_FromString(method); +#endif + number_of_items = PySequence_Size(mro); + + for(item_index = 0; item_index < number_of_items; item_index++) {{ + item_object = PySequence_GetItem(mro, item_index); + + // Ok - we got to the base class - finish up + if(item_object == (PyObject *) type) {{ + Py_DecRef(item_object); + break; + }} + /* Extract the dict and check if it contains the method (the + * dict is not a real dictionary so we can not use + * PyDict_Contains). + */ + dict = PyObject_GetAttrString(item_object, "__dict__"); + if(dict != NULL && PySequence_Contains(dict, py_method)) {{ + found = 1; + }} + Py_DecRef(dict); + Py_DecRef(item_object); + + if(found != 0) {{ + break; + }} + }} + Py_DecRef(py_method); + PyErr_Clear(); + + return found; +}} + +/* Fetches the Python error (exception) + */ +void pytsk_fetch_error(void) {{ + PyObject *exception_traceback = NULL; + PyObject *exception_type = NULL; + PyObject *exception_value = NULL; + PyObject *string_object = NULL; + char *str_c = NULL; + char *error_str = NULL; + int *error_type = (int *) {get_current_error:s}(&error_str); + +#if PY_MAJOR_VERSION >= 3 + PyObject *utf8_string_object = NULL; +#endif + + // Fetch the exception state and convert it to a string: + PyErr_Fetch(&exception_type, &exception_value, &exception_traceback); + + string_object = PyObject_Repr(exception_value); + +#if PY_MAJOR_VERSION >= 3 + utf8_string_object = PyUnicode_AsUTF8String(string_object); + + if(utf8_string_object != NULL) {{ + str_c = PyBytes_AsString(utf8_string_object); + }} +#else + str_c = PyString_AsString(string_object); +#endif + + if(str_c != NULL) {{ + strncpy(error_str, str_c, BUFF_SIZE-1); + error_str[BUFF_SIZE - 1] = 0; + *error_type = ERuntimeError; + }} + PyErr_Restore(exception_type, exception_value, exception_traceback); + +#if PY_MAJOR_VERSION >= 3 + if( utf8_string_object != NULL ) {{ + Py_DecRef(utf8_string_object); + }} +#endif + Py_DecRef(string_object); + + return; +}} + +/* Copies a Python int or long object to an unsigned 64-bit value + */ +uint64_t integer_object_copy_to_uint64(PyObject *integer_object) {{ +#if defined( HAVE_LONG_LONG ) + PY_LONG_LONG long_value = 0; +#else + long long_value = 0; +#endif + int result = 0; + + if(integer_object == NULL) {{ + PyErr_Format(PyExc_ValueError, "Missing integer object"); + + return (uint64_t) -1; + }} + PyErr_Clear(); + + result = PyObject_IsInstance(integer_object, (PyObject *) &PyLong_Type); + + if(result == -1) {{ + pytsk_fetch_error(); + + return (uint64_t) -1; + + }} else if(result != 0) {{ + PyErr_Clear(); + +#if defined( HAVE_LONG_LONG ) + long_value = PyLong_AsUnsignedLongLong(integer_object); +#else + long_value = PyLong_AsUnsignedLong(integer_object); +#endif + }} +#if PY_MAJOR_VERSION < 3 + if(result == 0) {{ + PyErr_Clear(); + + result = PyObject_IsInstance(integer_object, (PyObject *) &PyInt_Type); + + if(result == -1) {{ + pytsk_fetch_error(); + + return (uint64_t) -1; + + }} else if(result != 0) {{ + PyErr_Clear(); + +#if defined( HAVE_LONG_LONG ) + long_value = PyInt_AsUnsignedLongLongMask(integer_object); +#else + long_value = PyInt_AsUnsignedLongMask(integer_object); +#endif + }} + }} +#endif /* PY_MAJOR_VERSION < 3 */ + if(result == 0) {{ + if(PyErr_Occurred()) {{ + pytsk_fetch_error(); + + return (uint64_t) -1; + }} + }} +#if defined( HAVE_LONG_LONG ) +#if ( SIZEOF_LONG_LONG > 8 ) + if((long_value < (PY_LONG_LONG) 0) || (long_value > (PY_LONG_LONG) UINT64_MAX)) {{ +#else + if(long_value < (PY_LONG_LONG) 0) {{ +#endif + PyErr_Format(PyExc_ValueError, "Integer object value out of bounds"); + + return (uint64_t) -1; + }} +#else +#if ( SIZEOF_LONG > 8 ) + if((long_value < (long) 0) || (long_value > (long) UINT64_MAX)) {{ +#else + if(long_value < (PY_LONG_LONG) 0) {{ +#endif + PyErr_Format(PyExc_ValueError, "Integer object value out of bounds"); + + return (uint64_t) -1; + }} +#endif + return (uint64_t) long_value; +}} + +""".format(**values_dict) + + def initialise_class(self, class_name, out, done=None): + if done and class_name in done: + return + + done.add(class_name) + + cls = self.classes[class_name] + """Write out class initialisation code into the main init function.""" + if cls.is_active(): + base_class = self.classes.get(cls.base_class_name) + + if base_class and base_class.is_active(): + # We have a base class - ensure it gets written out + # first: + self.initialise_class(cls.base_class_name, out, done) + + # Now assign ourselves as derived from them + out.write( + " {0:s}_Type.tp_base = &{1:s}_Type;".format( + cls.class_name, cls.base_class_name)) + + values_dict = { + "name": cls.class_name} + + out.write(( + " {name:s}_Type.tp_new = PyType_GenericNew;\n" + " if (PyType_Ready(&{name:s}_Type) < 0) {{\n" + " goto on_error;\n" + " }}\n" + " Py_IncRef((PyObject *)&{name:s}_Type);\n" + " PyModule_AddObject(module, \"{name:s}\", (PyObject *)&{name:s}_Type);\n").format( + **values_dict)) + + def write(self, out): + # Write the headers + if self.public_api: + self.public_api.write( + "#ifdef BUILDING_DLL\n" + "#include \"misc.h\"\n" + "#else\n" + "#include \"aff4_public.h\"\n" + "#endif\n") + + # Prepare all classes + for cls in self.classes.values(): + cls.prepare() + + out.write(( + "/*************************************************************\n" + " * Autogenerated module {0:s}\n" + " *\n" + " * This module was autogenerated from the following files:\n").format( + self.name)) + + for filename in self.files: + out.write(" * {0:s}\n".format(filename)) + + out.write( + " *\n" + " * This module implements the following classes:\n") + out.write(self.get_string()) + out.write( + " ************************************************************/\n") + out.write(self.headers) + out.write(self.private_functions()) + + for cls in self.classes.values(): + if cls.is_active(): + out.write( + "/******************** {0:s} ***********************/".format( + cls.class_name)) + cls.struct(out) + cls.prototypes(out) + + out.write( + "/*****************************************************\n" + " * Implementation\n" + " ****************************************************/\n" + "\n") + + for cls in self.classes.values(): + if cls.is_active(): + cls.PyMethodDef(out) + cls.PyGetSetDef(out) + cls.code(out) + cls.PyTypeObject(out) + + # Write the module initializer + values_dict = { + "module": self.name, + "version": VERSION, + "version_length": len(VERSION)} + + out.write(( + "/* Retrieves the {module:s} version\n" + " * Returns a Python object if successful or NULL on error\n" + " */\n" + "PyObject *{module:s}_get_version(PyObject *self, PyObject *arguments) {{\n" + " const char *errors = NULL;\n" + " return(PyUnicode_DecodeUTF8(\"{version:s}\", (Py_ssize_t) {version_length:d}, errors));\n" + "}}\n" + "\n" + "static PyMethodDef {module:s}_module_methods[] = {{\n" + " {{ \"get_version\",\n" + " (PyCFunction) {module:s}_get_version,\n" + " METH_NOARGS,\n" + " \"get_version() -> String\\n\"\n" + " \"\\n\"\n" + " \"Retrieves the version.\" }},\n" + "\n" + " {{NULL, NULL, 0, NULL}} /* Sentinel */\n" + "}};\n" + "\n" + "#if PY_MAJOR_VERSION >= 3\n" + "\n" + "/* The {module:s} module definition\n" + " */\n" + "PyModuleDef {module:s}_module_definition = {{\n" + " PyModuleDef_HEAD_INIT,\n" + "\n" + " /* m_name */\n" + " \"{module:s}\",\n" + " /* m_doc */\n" + " \"Python {module:s} module.\",\n" + " /* m_size */\n" + " -1,\n" + " /* m_methods */\n" + " {module:s}_module_methods,\n" + " /* m_reload */\n" + " NULL,\n" + " /* m_traverse */\n" + " NULL,\n" + " /* m_clear */\n" + " NULL,\n" + " /* m_free */\n" + " NULL,\n" + "}};\n" + "\n" + "#endif /* PY_MAJOR_VERSION >= 3 */\n" + "\n" + "/* Initializes the {module:s} module\n" + " */\n" + "#if PY_MAJOR_VERSION >= 3\n" + "PyMODINIT_FUNC PyInit_{module:s}(void) {{\n" + "#else\n" + "PyMODINIT_FUNC init{module:s}(void) {{\n" + "#endif\n" + " PyGILState_STATE gil_state;\n" + "\n" + " PyObject *module = NULL;\n" + " PyObject *d = NULL;\n" + " PyObject *tmp = NULL;\n" + "\n" + " /* Create the module\n" + " * This function must be called before grabbing the GIL\n" + " * otherwise the module will segfault on a version mismatch\n" + " */\n" + "#if PY_MAJOR_VERSION >= 3\n" + " module = PyModule_Create(\n" + " &{module:s}_module_definition );\n" + "#else\n" + " module = Py_InitModule3(\n" + " \"{module:s}\",\n" + " {module:s}_module_methods,\n" + " \"Python {module:s} module.\" );\n" + "#endif\n" + " if (module == NULL) {{\n" + "#if PY_MAJOR_VERSION >= 3\n" + " return(NULL);\n" + "#else\n" + " return;\n" + "#endif\n" + " }}\n" + " d = PyModule_GetDict(module);\n" + "\n" + " /* Make sure threads are enabled */\n" + " PyEval_InitThreads();\n" + " gil_state = PyGILState_Ensure();\n" + "\n" + " g_module = module;\n").format(**values_dict)) + + # The trick is to initialise the classes in order of their + # inheritance. The following code will order initializations + # according to their inheritance tree + done = set() + for class_name in self.classes.keys(): + self.initialise_class(class_name, out, done) + + # Add the constants in here + for constant, type in self.constants: + if type == "integer": + out.write( + " tmp = PyLong_FromUnsignedLongLong((uint64_t) {0:s});\n".format(constant)) + elif type == "string": + if constant == "TSK_VERSION_STR": + out.write(( + "#if PY_MAJOR_VERSION >= 3\n" + " tmp = PyUnicode_FromString((char *){0:s});\n" + "#else\n" + " tmp = PyString_FromString((char *){0:s});\n" + "#endif\n").format(constant)) + + else: + out.write(( + "#if PY_MAJOR_VERSION >= 3\n" + " tmp = PyBytes_FromString((char *){0:s});\n" + "#else\n" + " tmp = PyString_FromString((char *){0:s});\n" + "#endif\n").format(constant)) + else: + out.write( + " /* I dont know how to convert {0:s} type {1:s} */\n".format( + constant, type)) + continue + + out.write(( + " PyDict_SetItemString(d, \"{0:s}\", tmp);\n" + " Py_DecRef(tmp);\n").format(constant)) + + out.write(self.initialization()) + out.write( + " PyGILState_Release(gil_state);\n" + "\n" + "#if PY_MAJOR_VERSION >= 3\n" + " return module;\n" + "#else\n" + " return;\n" + "#endif\n" + "\n" + "on_error:\n" + " PyGILState_Release(gil_state);\n" + "\n" + "#if PY_MAJOR_VERSION >= 3\n" + " return NULL;\n" + "#else\n" + " return;\n" + "#endif\n" + "}\n") + + +class Type(object): + interface = None + buildstr = "O" + sense = "IN" + error_value = "return 0;" + active = True + + def __init__(self, name, type, *args, **kwargs): + super(Type, self).__init__() + self.name = name + self.type = type + self.attributes = set() + self.additional_args = kwargs + + def comment(self): + return "{0:s} {1:s} ".format(self.type, self.name) + + def get_string(self): + """Retrieves a string representation.""" + if self.name == "func_return": + return self.type + if "void" in self.type: + return "" + + return "{0:s} : {1:s}".format(self.type, self.name) + + def python_name(self): + return self.name + + def python_proxy_post_call(self): + """This is called after a proxy call""" + return "" + + def returned_python_definition(self, *arg, **kwargs): + return self.definition(*arg, **kwargs) + + def definition(self, default=None, **kwargs): + if default: + return "{0:s} {1:s}={2:s};\n".format( + self.type, self.name, default) + elif "array_size" in self.additional_args: + return ( + "int array_index = 0;\n" + "{0:s} UNUSED *{1:s};\n").format( + self.type, self.name) + else: + return "{0:s} UNUSED {1:s};\n".format( + self.type, self.name) + + def local_definition(self, default=None, **kwargs): + return "" + + def byref(self): + return "&{0:s}".format(self.name) + + def call_arg(self): + return self.name + + def passthru_call(self): + """Returns how we should call the function when simply passing args directly""" + return self.call_arg() + + def pre_call(self, method, **kwargs): + return "" + + def assign(self, call, method, target=None, **kwargs): + return ( + "Py_BEGIN_ALLOW_THREADS\n" + "{0:s} = {1:s};\n" + "Py_END_ALLOW_THREADS\n").format( + target or self.name, call) + + def post_call(self, method): + # Check for errors + result = ( + "if(check_error()) {\n" + " goto on_error;\n" + "}\n") + + if "DESTRUCTOR" in self.attributes: + result += "self->base = NULL; //DESTRUCTOR - C object no longer valid\n" + + return result + + def from_python_object(self, source, destination, method, **kwargs): + return "" + + def return_value(self, value): + return "return {0!s};".format(value) + + +class String(Type): + interface = "string" + buildstr = "s" + error_value = "return NULL;" + + def __init__(self, name, type, *args, **kwargs): + super(String, self).__init__(name, type, *args, **kwargs) + self.length = "strlen({0:s})".format(name) + + def byref(self): + return "&{0:s}".format(self.name) + + def to_python_object(self, name=None, result="Py_result", **kwargs): + values_dict = { + "length": self.length, + "name": name or self.name, + "result": result} + + result = ( + " PyErr_Clear();\n" + "\n" + " if(!{name:s}) {{\n" + " Py_IncRef(Py_None);\n" + " {result:s} = Py_None;\n" + " }} else {{\n" + "#if PY_MAJOR_VERSION >= 3\n" + " {result:s} = PyBytes_FromStringAndSize((char *){name:s}, {length:s});\n" + "#else\n" + " {result:s} = PyString_FromStringAndSize((char *){name:s}, {length:s});\n" + "#endif\n" + " if(!{result:s}) {{\n" + " goto on_error;\n" + " }}\n" + " }}\n").format(**values_dict) + + if "BORROWED" not in self.attributes and "BORROWED" not in kwargs: + result += "talloc_unlink(NULL, {0:s});\n".format(name) + + return result + + def from_python_object(self, source, destination, method, context="NULL"): + method.error_set = True + + values_dict = { + "context": context, + "destination": destination, + "source": source} + + return ( + "{{\n" + " char *buff = NULL;\n" + " Py_ssize_t length = 0;\n" + "\n" + " PyErr_Clear();\n" + "\n" + "#if PY_MAJOR_VERSION >= 3\n" + " if(PyBytes_AsStringAndSize({source:s}, &buff, &length) == -1) {{\n" + "#else\n" + " if(PyString_AsStringAndSize({source:s}, &buff, &length) == -1) {{\n" + "#endif\n" + " goto on_error;\n" + " }}\n" + " {destination:s} = talloc_size({context:s}, length + 1);\n" + " memcpy({destination:s}, buff, length);\n" + " {destination:s}[length] = 0;\n" + "}};\n").format(**values_dict) + + +class ZString(String): + interface = "null_terminated_string" + + +class BorrowedString(String): + def to_python_object(self, name=None, result="Py_result", **kwargs): + values_dict = { + "length": self.length, + "name": name or self.name, + "result": result} + + return ( + " PyErr_Clear();\n" + "#if PY_MAJOR_VERSION >= 3\n" + " {result:s} = PyBytes_FromStringAndSize((char *){name:s}, {length:s});\n" + "#else\n" + " {result:s} = PyString_FromStringAndSize((char *){name:s}, {length:s});\n" + "#endif\n").format(**values_dict) + + +class Char_and_Length(Type): + interface = "char_and_length" + buildstr = "s#" + error_value = "return NULL;" + + def __init__(self, data, data_type, length, length_type, *args, **kwargs): + super(Char_and_Length, self).__init__(data, data_type, *args, **kwargs) + + self.name = data + self.data_type = data_type + self.length = length + self.length_type = length_type + + def comment(self): + return "{0:s} {1:s}, {2:s} {3:s}".format( + self.data_type, self.name, self.length_type, self.length) + + def definition(self, default="\"\"", **kwargs): + return ( + "char *{0:s}={1:s};\n" + "Py_ssize_t {2:s}=strlen({3:s});\n").format( + self.name, default, self.length, default) + + def byref(self): + return "&{0:s}, &{1:s}".format(self.name, self.length) + + def call_arg(self): + return "({0:s}){1:s}, ({2:s}){3:s}".format( + self.data_type, self.name, self.length_type, self.length) + + def to_python_object(self, name=None, result="Py_result", **kwargs): + values_dict = { + "length": self.length, + "name": self.name, + "result": result} + + return ( + " PyErr_Clear();\n" + "#if PY_MAJOR_VERSION >= 3\n" + " {result:s} = PyBytes_FromStringAndSize((char *){name:s}, {length:s});\n" + "#else\n" + " {result:s} = PyString_FromStringAndSize((char *){name:s}, {length:s});\n" + "#endif\n" + "\n" + " if(!{result:s}) {{\n" + " goto on_error;\n" + " }}\n").format(**values_dict) + + +class Integer(Type): + interface = "integer" + buildstr = "i" + int_type = "int" + + def __init__(self, name, type, *args, **kwargs): + super(Integer, self).__init__(name, type, *args, **kwargs) + self.type = self.int_type + self.original_type = type + + def to_python_object(self, name=None, result="Py_result", **kwargs): + values_dict = { + "name": name or self.name, + "result": result} + + return ( + " PyErr_Clear();\n" + "#if PY_MAJOR_VERSION >= 3\n" + " {result:s} = PyLong_FromLong({name:s});\n" + "#else\n" + " {result:s} = PyInt_FromLong({name:s});\n" + "#endif\n").format(**values_dict) + + def from_python_object(self, source, destination, method, **kwargs): + values_dict = { + "destination": destination, + "source": source} + + return ( + " PyErr_Clear();\n" + "#if PY_MAJOR_VERSION >= 3\n" + " {destination:s} = PyLong_AsLongMask({source:s});\n" + "#else\n" + " {destination:s} = PyInt_AsLongMask({source:s});\n" + "#endif\n").format(**values_dict) + + def comment(self): + return "{0:s} {1:s} ".format(self.original_type, self.name) + + +class IntegerUnsigned(Integer): + buildstr = "I" + int_type = "unsigned int" + + def to_python_object(self, name=None, result="Py_result", **kwargs): + if "array_size" in self.additional_args: + values_dict = { + "name": name or self.name, + "result": result, + "array_size": self.additional_args["array_size"] + } + return ( + " PyErr_Clear();\n" + " {result:s} = PyList_New(0);\n" + " for(array_index = 0; array_index < {array_size:s}; array_index++) {{\n" + "#if PY_MAJOR_VERSION >= 3\n" + " PyList_Append({result:s}, PyLong_FromLong((long) {name:s}[array_index]));\n" + "#else\n" + " PyList_Append({result:s}, PyInt_FromLong((long) {name:s}[array_index]));\n" + "#endif\n" + " }}\n" + ).format(**values_dict) + else: + values_dict = { + "name": name or self.name, + "result": result} + return ( + " PyErr_Clear();\n" + "#if PY_MAJOR_VERSION >= 3\n" + " {result:s} = PyLong_FromLong((long) {name:s});\n" + "#else\n" + " {result:s} = PyInt_FromLong((long) {name:s});\n" + "#endif\n").format(**values_dict) + + def from_python_object(self, source, destination, method, **kwargs): + values_dict = { + "destination": destination, + "source": source} + + return ( + " PyErr_Clear();\n" + "#if PY_MAJOR_VERSION >= 3\n" + " {destination:s} = PyLong_AsUnsignedLongMask({source:s});\n" + "#else\n" + " {destination:s} = PyInt_AsUnsignedLongMask({source:s});\n" + "#endif\n").format(**values_dict) + + +class Integer8(Integer): + int_type = "int8_t" + + +class Integer8Unsigned(IntegerUnsigned): + int_type = "uint8_t" + + +class Integer16(Integer): + int_type = "int16_t" + + +class Integer16Unsigned(IntegerUnsigned): + int_type = "uint16_t" + + +class Integer32(Integer): + int_type = "int32_t" + + +class Integer32Unsigned(IntegerUnsigned): + int_type = "uint32_t" + + +class Integer64(Integer): + buildstr = "L" + int_type = "int64_t" + + def to_python_object(self, name=None, result="Py_result", **kwargs): + values_dict = { + "name": name or self.name, + "result": result} + + return ( + " PyErr_Clear();\n" + "#if defined( HAVE_LONG_LONG )\n" + " {result:s} = PyLong_FromLongLong({name:s});\n" + "#else\n" + " {result:s} = PyLong_FromLong({name:s});\n" + "#endif\n").format(**values_dict) + + def from_python_object(self, source, destination, method, **kwargs): + values_dict = { + "destination": destination, + "source": source} + + return ( + " PyErr_Clear();\n" + "#if PY_MAJOR_VERSION >= 3\n" + "#if defined( HAVE_LONG_LONG )\n" + " {destination:s} = PyLong_AsLongLongMask({source:s});\n" + "#else\n" + " {destination:s} = PyLong_AsLongMask({source:s});\n" + "#endif\n" + "#else\n" + "#if defined( HAVE_LONG_LONG )\n" + " {destination:s} = PyInt_AsLongLongMask({source:s});\n" + "#else\n" + " {destination:s} = PyInt_AsLongMask({source:s});\n" + "#endif\n" + "#endif /* PY_MAJOR_VERSION >= 3 */\n").format(**values_dict) + + +class Integer64Unsigned(Integer): + buildstr = "K" + int_type = "uint64_t" + + def to_python_object(self, name=None, result="Py_result", **kwargs): + values_dict = { + "name": name or self.name, + "result": result} + + return ( + " PyErr_Clear();\n" + "#if defined( HAVE_LONG_LONG )\n" + " {result:s} = PyLong_FromUnsignedLongLong({name:s});\n" + "#else\n" + " {result:s} = PyLong_FromUnsignedLong({name:s});\n" + "#endif\n").format(**values_dict) + + def from_python_object(self, source, destination, method, **kwargs): + values_dict = { + "destination": destination, + "source": source} + + # TODO: use integer_object_copy_to_uint64 instead to support both + # long and int objects. + return ( + " PyErr_Clear();\n" + "#if PY_MAJOR_VERSION >= 3\n" + "#if defined( HAVE_LONG_LONG )\n" + " {destination:s} = PyLong_AsUnsignedLongLongMask({source:s});\n" + "#else\n" + " {destination:s} = PyLong_AsUnsignedLongMask({source:s});\n" + "#endif\n" + "#else\n" + "#if defined( HAVE_LONG_LONG )\n" + " {destination:s} = PyInt_AsUnsignedLongLongMask({source:s});\n" + "#else\n" + " {destination:s} = PyInt_AsUnsignedLongMask({source:s});\n" + "#endif\n" + "#endif /* PY_MAJOR_VERSION >= 3 */\n").format(**values_dict) + + +class Long(Integer): + buildstr = "l" + int_type = "long" + + def to_python_object(self, name=None, result="Py_result", **kwargs): + values_dict = { + "name": name or self.name, + "result": result} + + return ( + "PyErr_Clear();\n" + "{result:s} = PyLong_FromLongLong({name:s});\n").format( + **values_dict) + + def from_python_object(self, source, destination, method, **kwargs): + values_dict = { + "destination": destination, + "source": source} + + return ( + "PyErr_Clear();\n" + "{destination:s} = PyLong_AsLongMask({source:s});\n").format( + **values_dict) + + +class LongUnsigned(Integer): + buildstr = "k" + int_type = "unsigned long" + + def to_python_object(self, name=None, result="Py_result", **kwargs): + values_dict = { + "name": name or self.name, + "result": result} + + return ( + "PyErr_Clear();\n" + "{result:s} = PyLong_FromUnsignedLong({name:s});\n").format( + **values_dict) + + def from_python_object(self, source, destination, method, **kwargs): + values_dict = { + "destination": destination, + "source": source} + + return ( + "PyErr_Clear();\n" + "{destination:s} = PyLong_AsUnsignedLongMask({source:s});\n").format( + **values_dict) + + +class Char(Integer): + buildstr = "s" + interface = "small_integer" + + def to_python_object(self, name=None, result="Py_result", **kwargs): + # We really want to return a string here + values_dict = { + "name": name or self.name, + "result": result} + + return ( + "{{\n" + " char *str_{name:s} = &{name:s};\n" + "\n" + " PyErr_Clear();\n" + "#if PY_MAJOR_VERSION >= 3\n" + " {result:s} = PyBytes_FromStringAndSize(str_{name:s}, 1);\n" + "#else\n" + " {result:s} = PyString_FromStringAndSize(str_{name:s}, 1);\n" + "#endif\n" + "\n" + " if(!{result:s}) {{\n" + " goto on_error;\n" + "}}\n").format(**values_dict) + + def definition(self, default="\"\\x0\"", **kwargs): + # Shut up unused warnings + return ( + "char {0:s} UNUSED=0;\n" + "char *str_{0:s} UNUSED = {1:s};\n").format( + self.name, default) + + def byref(self): + return "&str_{0:s}".format(self.name) + + def pre_call(self, method, **kwargs): + method.error_set = True + + values_dict = { + "name": self.name} + + return ( + " if(strlen(str_{name:s}) != 1) {\n" + " PyErr_Format(PyExc_RuntimeError, \"You must only provide a single character for arg {name:s}\");\n" + " goto on_error;\n" + " }\n" + "\n" + " {name:s} = str_{name:s}[0];\n").format( + **values_dict) + + +class StringOut(String): + sense = "OUT" + + +class IntegerOut(Integer): + """Handle Integers pushed out through OUT int *result.""" + sense = "OUT_DONE" + buildstr = "" + int_type = "int *" + + def definition(self, default=0, **kwargs): + # We need to make static storage for the pointers + storage = "storage_{0:s}".format(self.name) + bare_type = self.type.split()[0] + type_definition = Type.definition( + self, "&{0:s}".format(storage)) + + return ( + "{0:s} {1:s} = 0;\n" + "{2:s}\n").format( + bare_type, storage, type_definition) + + def to_python_object(self, name=None, result="Py_result", **kwargs): + values_dict = { + "name": name or self.name, + "result": result} + + return ( + "PyErr_Clear();\n" + "{result:s} = PyLong_FromLongLong(*{name:s});\n").format( + **values_dict) + + def python_name(self): + return None + + def byref(self): + return self.name + + def call_arg(self): + return "{0:s}".format(self.name) + + def passthru_call(self): + return self.name + + +class PInteger32UnsignedOut(IntegerOut): + buildstr = "" + int_type = "uint32_t *" + + +class PInteger64UnsignedOut(IntegerOut): + buildstr = "" + int_type = "uint64_t *" + + +class Char_and_Length_OUT(Char_and_Length): + sense = "OUT_DONE" + buildstr = "l" + + def definition(self, default=0, **kwargs): + values_dict = { + "default": default, + "length": self.length, + "name": self.name} + + return ( + " char *{name:s} = NULL;\n" + " Py_ssize_t {length:s} = {default:d};\n" + " PyObject *tmp_{name:s} = NULL;\n").format( + **values_dict) + + def error_cleanup(self): + values_dict = { + "name": self.name} + + return ( + " if(tmp_{name:s} != NULL) {{\n" + " Py_DecRef(tmp_{name:s});\n" + " }}\n").format(**values_dict) + + def python_name(self): + return self.length + + def byref(self): + return "&{0:s}".format(self.length) + + def pre_call(self, method, **kwargs): + values_dict = { + "length": self.length, + "name": self.name} + + return ( + " PyErr_Clear();\n" + "\n" + "#if PY_MAJOR_VERSION >= 3\n" + " tmp_{name:s} = PyBytes_FromStringAndSize(NULL, {length:s});\n" + "#else\n" + " tmp_{name:s} = PyString_FromStringAndSize(NULL, {length:s});\n" + "#endif\n" + " if(!tmp_{name:s}) {{\n" + " goto on_error;\n" + " }}\n" + "\n" + "#if PY_MAJOR_VERSION >= 3\n" + " PyBytes_AsStringAndSize(tmp_{name:s}, &{name:s}, (Py_ssize_t *)&{length:s});\n" + "#else\n" + " PyString_AsStringAndSize(tmp_{name:s}, &{name:s}, (Py_ssize_t *)&{length:s});\n" + "#endif\n").format(**values_dict) + + def to_python_object(self, name=None, result="Py_result", sense="in", **kwargs): + if "results" in kwargs: + kwargs["results"].pop(0) + + if sense == "proxied": + return "py_{0:s} = PyLong_FromLong({1:s});\n".format( + self.name, self.length) + + values_dict = { + "length": self.length, + "name": name or self.name, + "result": result} + + return ( + " /* NOTE - this should never happen\n" + " * it might indicate an overflow condition.\n" + " */\n" + " if(func_return > {length:s}) {{\n" + " printf(\"Programming Error - possible overflow!!\\n\");\n" + " abort();\n" + "\n" + " // Do we need to truncate the buffer for a short read?\n" + " }} else if(func_return < {length:s}) {{\n" + "#if PY_MAJOR_VERSION >= 3\n" + " _PyBytes_Resize(&tmp_{name:s}, (Py_ssize_t)func_return);\n" + "#else\n" + " _PyString_Resize(&tmp_{name:s}, (Py_ssize_t)func_return);\n" + "#endif\n" + " }}\n" + "\n" + " {result:s} = tmp_{name:s};\n").format(**values_dict) + + def python_proxy_post_call(self, result="Py_result"): + values_dict = { + "name": self.name, + "result": result} + + return ( + "{{\n" + " char *tmp_buff = NULL;\n" + " Py_ssize_t tmp_len = 0;\n" + "\n" + "#if PY_MAJOR_VERSION >= 3\n" + " if(PyBytes_AsStringAndSize({result:s}, &tmp_buff, &tmp_len) == -1) {{\n" + "#else\n" + " if(PyString_AsStringAndSize({result:s}, &tmp_buff, &tmp_len) == -1) {{\n" + "#endif\n" + " goto on_error;\n" + " }}\n" + " memcpy({name:s}, tmp_buff, tmp_len);\n" + " Py_DecRef({result:s});\n" + " {result:s} = PyLong_FromLong(tmp_len);\n" + "}}\n").format(**values_dict) + + +class TDB_DATA_P(Char_and_Length_OUT): + bare_type = "TDB_DATA" + + def __init__(self, name, type, *args, **kwargs): + super(TDB_DATA_P, self).__init__(name, type, *args, **kwargs) + + def definition(self, default=None, **kwargs): + return Type.definition(self) + + def byref(self): + return "{0:s}.dptr, &{0:s}.dsize".format(self.name) + + def pre_call(self, method, **kwargs): + return "" + + def call_arg(self): + return Type.call_arg(self) + + def to_python_object(self, name=None, result="Py_result", **kwargs): + values_dict = { + "name": name or self.name, + "result": result} + + return ( + " PyErr_Clear();\n" + "#if PY_MAJOR_VERSION >= 3\n" + " {result:s} = PyBytes_FromStringAndSize((char *){name:s}->dptr, {name:s}->dsize);\n" + "#else\n" + " {result:s} = PyString_FromStringAndSize((char *){name:s}->dptr, {name:s}->dsize);\n" + "#endif\n" + " talloc_free({name:s});\n").format(**values_dict) + + def from_python_object(self, source, destination, method, **kwargs): + method.error_set = True + values_dict = { + "bare_type": self.bare_type, + "destination": destination, + "source": source} + + return ( + "{destination:s} = talloc_zero(self, {bare_type:s});\n" + "{{\n" + " char *buf = NULL;\n" + " Py_ssize_t tmp = 0;\n" + "\n" + " PyErr_Clear();\n" + "\n" + "#if PY_MAJOR_VERSION >= 3\n" + " if(PyBytes_AsStringAndSize({source:s}, &buf, &tmp) == -1) {{\n" + "#else\n" + " if(PyString_AsStringAndSize({source:s}, &buf, &tmp) == -1) {{\n" + "#endif\n" + " goto on_error;\n" + " }}\n" + "\n" + " // Take a copy of the Python string\n" + " {destination:s}->dptr = talloc_memdup({destination:s}, buf, tmp);\n" + " {destination:s}->dsize = tmp;\n" + "}}\n" + "// We no longer need the Python object\n" + "Py_DecRef({source:s});\n").format(**values_dict) + + +class TDB_DATA(TDB_DATA_P): + error_value = ( + "{result:s}.dptr = NULL;\n" + "return {result:s};") + + def from_python_object(self, source, destination, method, **kwargs): + method.error_set = True + values_dict = { + "destination": destination, + "source": source} + + return ( + "{{\n" + " char *buf = NULL;\n" + " Py_ssize_t tmp = 0;\n" + "\n" + " PyErr_Clear();\n" + "\n" + "#if PY_MAJOR_VERSION >= 3\n" + " if(PyBytes_AsStringAndSize({source:s}, &buf, &tmp) == -1) {{\n" + "#else\n" + " if(PyString_AsStringAndSize({source:s}, &buf, &tmp) == -1) {{\n" + "#endif\n" + " goto on_error;\n" + " }}\n" + " // Take a copy of the Python string - This leaks - how to fix it?\n" + " {destination:s}.dptr = talloc_memdup(NULL, buf, tmp);\n" + " {destination:s}.dsize = tmp;\n" + "}}\n" + "// We no longer need the Python object\n" + "Py_DecRef({source:s});\n").format(**values_dict) + + def to_python_object(self, name=None, result="Py_result", **kwargs): + values_dict = { + "name": name or self.name, + "result": result} + + return ( + " PyErr_Clear();\n" + "#if PY_MAJOR_VERSION >= 3\n" + " {result:s} = PyBytes_FromStringAndSize((char *){name:s}.dptr, {name:s}.dsize);\n" + "#else\n" + " {result:s} = PyString_FromStringAndSize((char *){name:s}.dptr, {name:s}.dsize);\n" + "#endif\n").format(**values_dict) + + +class Void(Type): + buildstr = "" + error_value = "return;" + original_type = "" + + def __init__(self, name, type="void", *args, **kwargs): + super(Void, self).__init__(name, type, *args, **kwargs) + + def comment(self): + return "void *ctx" + + def definition(self, default=None, **kwargs): + return "" + + def to_python_object(self, name=None, result="Py_result", **kwargs): + return ( + "Py_IncRef(Py_None);\n" + "Py_result = Py_None;\n") + + def call_arg(self): + return "NULL" + + def byref(self): + return None + + def assign(self, call, method, target=None, **kwargs): + # We don't assign the result to anything. + return ( + " Py_BEGIN_ALLOW_THREADS\n" + " (void) {0:s};\n" + " Py_END_ALLOW_THREADS\n").format(call) + + def return_value(self, value): + return "return;" + + +class PVoid(Void): + def __init__(self, name, type="void *", *args, **kwargs): + super(PVoid, self).__init__(name, type, *args, **kwargs) + + +class StringArray(String): + interface = "array" + buildstr = "O" + + def definition(self, default="\"\"", **kwargs): + return ( + "char **{0:s} = NULL;\n" + "PyObject *py_{0:s} = NULL;\n").format(self.name) + + def byref(self): + return "&py_{0:s}".format(self.name) + + def from_python_object(self, source, destination, method, context="NULL"): + method.error_set = True + values_dict = { + "destination": destination, + "source": source} + + return ( + "{{\n" + " Py_ssize_t i = 0;\n" + " Py_ssize_t size = 0;\n" + "\n" + " if({source:s}) {{\n" + " if(!PySequence_Check({source:s})) {{\n" + " PyErr_Format(PyExc_ValueError, \"{destination:s} must be a sequence\");\n" + " goto on_error;\n" + " }}\n" + " size = PySequence_Size({source:s});\n" + " }}\n" + " {destination:s} = talloc_zero_array(NULL, char *, size + 1);\n" + "\n" + " for(i = 0; i < size; i++) {{\n" + " PyObject *tmp = PySequence_GetItem({source:s}, i);\n" + " if(!tmp) {{\n" + " goto on_error;\n" + " }}\n" + "#if PY_MAJOR_VERSION >= 3\n" + " {destination:s}[i] = PyBytes_AsString(tmp);\n" + "#else\n" + " {destination:s}[i] = PyString_AsString(tmp);\n" + "#endif\n" + "\n" + " if(!{destination:s}[i]) {{\n" + " Py_DecRef(tmp);\n" + " goto on_error;\n" + " }}\n" + " Py_DecRef(tmp);\n" + " }}\n" + "}}\n").format(**values_dict) + + def pre_call(self, method, **kwargs): + return self.from_python_object( + "py_{0:s}".format(self.name), self.name, method) + + def error_condition(self): + return ( + " if({0:s}) {{\n" + " talloc_free({0:s});\n" + " }}\n").format(self.name) + + +class Wrapper(Type): + """This class represents a wrapped C type """ + sense = "IN" + error_value = "return NULL;" + + def from_python_object(self, source, destination, method, **kwargs): + values_dict = { + "destination": destination, + "source": source, + "type": self.type} + + return ( + " /* First check that the returned value is in fact a Wrapper */\n" + " if(!type_check({source:s}, &{type:s}_Type)) {{\n" + " PyErr_Format(PyExc_RuntimeError, \"function must return an {type:s} instance\");\n" + " goto on_error;\n" + " }}\n" + "\n" + " {destination:s} = ((Gen_wrapper) {source:s})->base;\n" + "\n" + " if(!{destination:s}) {{\n" + " PyErr_Format(PyExc_RuntimeError, \"{type:s} instance is no longer valid (was it gc'ed?)\");\n" + " goto on_error;\n" + "}}\n" + "\n").format(**values_dict) + + def to_python_object(self, **kwargs): + return "" + + def returned_python_definition(self, default="NULL", sense="in", **kwargs): + return "{0:s} {1:s} = {2:s};\n".format( + self.type, self.name, default) + + def byref(self): + return "&wrapped_{0:s}".format(self.name) + + def definition(self, default="NULL", sense="in", **kwargs): + result = " Gen_wrapper wrapped_{0:s} UNUSED = {1:s};\n".format( + self.name, default) + + if sense == "in" and not "OUT" in self.attributes: + result += " {0:s} UNUSED {1:s};\n".format( + self.type, self.name) + + return result + + def call_arg(self): + return "{0:s}".format(self.name) + + def pre_call(self, method, python_object_index=1, **kwargs): + if "OUT" in self.attributes or self.sense == "OUT": + return "" + self.original_type = self.type.split()[0] + + values_dict = { + "name": self.name, + "original_type": self.original_type, + "python_object_index": python_object_index} + + return ( + " if(wrapped_{name:s} == NULL || (PyObject *)wrapped_{name:s} == Py_None) {{\n" + " {name:s} = NULL;\n" + " }} else if(!type_check((PyObject *)wrapped_{name:s},&{original_type:s}_Type)) {{\n" + " PyErr_Format(PyExc_RuntimeError, \"{name:s} must be derived from type {original_type:s}\");\n" + " goto on_error;\n" + " }} else if(wrapped_{name:s}->base == NULL) {{\n" + " PyErr_Format(PyExc_RuntimeError, \"{original_type:s} instance is no longer valid (was it gc'ed?)\");\n" + " goto on_error;\n" + " }} else {{\n" + " {name:s} = wrapped_{name:s}->base;\n" + " if(self->python_object{python_object_index:d} == NULL) {{\n" + " self->python_object{python_object_index:d} = (PyObject *) wrapped_{name:s};\n" + " Py_IncRef(self->python_object{python_object_index:d});\n" + " }}\n" + " }}\n").format(**values_dict) + + def assign(self, call, method, target=None, **kwargs): + method.error_set = True; + + values_dict = { + "call": call.strip(), + "incref": INCREF, + "name": target or self.name, + "type": self.type} + + result = ( + " {{\n" + " Object returned_object = NULL;\n" + "\n" + " ClearError();\n" + "\n" + " Py_BEGIN_ALLOW_THREADS\n" + " // This call will return a Python object if the base is a proxied Python object\n" + " // or a talloc managed object otherwise.\n" + " returned_object = (Object) {call:s};\n" + " Py_END_ALLOW_THREADS\n" + "\n" + " if(check_error()) {{\n" + " if(returned_object != NULL) {{\n" + " if(self->base_is_python_object != 0) {{\n" + " Py_DecRef((PyObject *) returned_object);\n" + " }} else if(self->base_is_internal != 0) {{\n" + " talloc_free(returned_object);\n" + " }}\n" + " }}\n" + " goto on_error;\n" + " }}\n").format(**values_dict) + + # Is NULL an acceptable return type? In some Python code NULL + # can be returned (e.g. in iterators) but usually it should + # be converted to Py_None. + if "NULL_OK" in self.attributes: + result += ( + " if(returned_object == NULL) {\n" + " goto on_error;\n" + " }\n") + + result += ( + " wrapped_{name:s} = new_class_wrapper(returned_object, self->base_is_python_object);\n" + "\n" + " if(wrapped_{name:s} == NULL) {{\n" + " if(returned_object != NULL) {{\n" + " if(self->base_is_python_object != 0) {{\n" + " Py_DecRef((PyObject *) returned_object);\n" + " }} else if(self->base_is_internal != 0) {{\n" + " talloc_free(returned_object);\n" + " }}\n" + " }}\n" + " goto on_error;\n" + " }}\n").format(**values_dict) + + if "BORROWED" in self.attributes: + result += ( + " #error unchecked BORROWED code segment\n" + " {incref:s}(wrapped_{name:s}->base);\n" + " if(((Object) wrapped_{name:s}->base)->extension) {{\n" + " Py_IncRef((PyObject *) ((Object) wrapped_{name:s}->base)->extension);\n" + " }}\n").format(**values_dict) + + result += ( + " }\n") + + return result + + def to_python_object( + self, name=None, result="Py_result", sense="in", **kwargs): + values_dict = { + "name": name or self.name, + "result": result} + + if sense == "proxied": + return ( + "{result:s} = (PyObject *) new_class_wrapper((Object){name:s}, 0);\n").format( + **values_dict) + + return "{result:s} = (PyObject *) wrapped_{name:s};\n".format( + **values_dict) + + +class PointerWrapper(Wrapper): + """ A pointer to a wrapped class """ + + def __init__(self, name, type, *args, **kwargs): + type = type.split()[0] + super(PointerWrapper, self).__init__(name, type, *args, **kwargs) + + def comment(self): + return "{0:s} *{1:s}".format(self.type, self.name) + + def definition(self, default="NULL", sense="in", **kwargs): + result = "Gen_wrapper wrapped_{0:s} = {1:s};".format( + self.name, default) + if sense == "in" and not "OUT" in self.attributes: + result += " {0:s} *{1:s};\n".format(self.type, self.name) + + return result + + def byref(self): + return "&wrapped_{0:s}".format(self.name) + + def pre_call(self, method, **kwargs): + if "OUT" in self.attributes or self.sense == "OUT": + return "" + self.original_type = self.type.split()[0] + values_dict = { + "name": self.name, + "original_type": self.original_type} + + return ( + "if(!wrapped_{name:s} || (PyObject *)wrapped_{name:s}==Py_None) {{\n" + " {name:s} = NULL;\n" + "}} else if(!type_check((PyObject *)wrapped_{name:s},&{original_type:s}_Type)) {{\n" + " PyErr_Format(PyExc_RuntimeError, \"{name:s} must be derived from type {original_type:s}\");\n" + " goto on_error;\n" + "}} else {{\n" + " {name:s} = ({original_type:s} *)&wrapped_{name:s}->base;\n" + "}};\n").format(**values_dict) + + +class StructWrapper(Wrapper): + """ A wrapper for struct classes """ + active = False + + def __init__(self, name, type, *args, **kwargs): + super(StructWrapper, self).__init__(name, type, *args, **kwargs) + self.original_type = type.split()[0] + + def assign(self, call, method, target=None, borrowed=True, **kwargs): + self.original_type = self.type.split()[0] + values_dict = { + "call": call.strip(), + "name": target or self.name, + "type": self.original_type} + + result = ( + "\n" + " PyErr_Clear();\n" + "\n" + " wrapped_{name:s} = (Gen_wrapper) PyObject_New(py{type:s}, &{type:s}_Type);\n" + "\n").format(**values_dict) + + if borrowed: + result += ( + " // Base is borrowed from another object.\n" + " wrapped_{name:s}->base = {call:s};\n" + " wrapped_{name:s}->base_is_python_object = 0;\n" + " wrapped_{name:s}->base_is_internal = 0;\n" + " wrapped_{name:s}->python_object1 = NULL;\n" + " wrapped_{name:s}->python_object2 = NULL;\n" + "\n").format(**values_dict) + else: + result += ( + " wrapped_{name:s}->base = {call:s};\n" + " wrapped_{name:s}->base_is_python_object = 0;\n" + " wrapped_{name:s}->base_is_internal = 1;\n" + " wrapped_{name:s}->python_object1 = NULL;\n" + " wrapped_{name:s}->python_object2 = NULL;\n" + "\n").format(**values_dict) + + if "NULL_OK" in self.attributes: + result += ( + " if(wrapped_{name:s}->base == NULL) {{\n" + " Py_DecRef((PyObject *) wrapped_{name:s});\n" + " return NULL;\n" + " }}\n").format(**values_dict) + + result += ( + " // A NULL object gets translated to a None\n" + " if(wrapped_{name:s}->base == NULL) {{\n" + " Py_DecRef((PyObject *) wrapped_{name:s});\n" + " Py_IncRef(Py_None);\n" + " wrapped_{name:s} = (Gen_wrapper) Py_None;\n" + " }}\n").format(**values_dict) + + # TODO: with the following code commented out is makes no sense to have the else clause here. + # " }} else {{\n").format(**values_dict) + + # if "FOREIGN" in self.attributes: + # result += "// Not taking references to foreign memory\n" + # elif "BORROWED" in self.attributes: + # result += "talloc_reference({name:s}->ctx, {name:s}->base);\n".format(**values_dict) + # else: + # result += "talloc_steal({name:s}->ctx, {name:s}->base);\n".format(**values_dict) + # result += "}}\n" + + return result + + def byref(self): + return "&{0:s}".format(self.name) + + def definition(self, default="NULL", sense="in", **kwargs): + result = "Gen_wrapper wrapped_{0:s} = {1:s};".format( + self.name, default) + if sense == "in" and not "OUT" in self.attributes: + result += " {0:s} *{1:s} = NULL;\n".format( + self.original_type, self.name) + + return result; + + +class PointerStructWrapper(StructWrapper): + def from_python_object(self, source, destination, method, **kwargs): + return "{0:s} = ((Gen_wrapper) {1:s})->base;\n".format( + destination, source) + + def byref(self): + return "&wrapped_{0:s}".format(self.name) + + +class Timeval(Type): + """Handle struct timeval values.""" + interface = "numeric" + buildstr = "f" + + def definition(self, default=None, **kwargs): + return ( + "struct timeval {0:s};\n".format(self.name) + + self.local_definition(default, **kwargs)) + + def local_definition(self, default=None, **kwargs): + return "float {0:s}_flt;\n".format(self.name) + + def byref(self): + return "&{0:s}_flt".format(self.name) + + def pre_call(self, method, **kwargs): + return ( + "{0:s}.tv_sec = (int){0:s}_flt;\n" + "{0:s}.tv_usec = ({0:s}_flt - {0:s}.tv_sec) * 1e6;\n").format( + self.name) + + def to_python_object(self, name=None, result="Py_result", **kwargs): + values_dict = { + "name": name or self.name, + "result": result} + + return ( + "{name:s}_flt = (double)({name:s}.tv_sec) + {name:s}.tv_usec;\n" + "{result:s} = PyFloat_FromDouble({name:s}_flt);\n").format( + **values_dict) + + +class PyObject(Type): + """Accept an opaque Python object.""" + interface = "opaque" + buildstr = "O" + + def definition(self, default="NULL", **kwargs): + self.default = default + values_dict = { + "default": self.default, + "name": self.name} + + return ( + "PyObject *{name:s} = {default:s};\n").format( + **values_dict) + + def byref(self): + return "&{0:s}".format(self.name) + + +type_dispatcher = { + "IN unsigned char *": String, + "IN char *": String, + + "unsigned char *": String, + "char *": String, + + "ZString": ZString, + + "OUT unsigned char *": StringOut, + "OUT char *": StringOut, + + "OUT uint64_t *": PInteger64UnsignedOut, + "OUT uint32_t *": PInteger32UnsignedOut, + + "void *": PVoid, + "void": Void, + + "TDB_DATA *": TDB_DATA_P, + "TDB_DATA": TDB_DATA, + "TSK_INUM_T": Integer, + + "off_t": Integer64, + "size_t": Integer64Unsigned, + "ssize_t": Integer64, + "time_t": Integer64, + + "unsigned long": LongUnsigned, + "long": Long, + "unsigned long int": LongUnsigned, + "long int": Integer, + "unsigned int": Integer, + "int": Integer, + + "uint64_t": Integer64Unsigned, + "uint32_t": Integer32Unsigned, + "uint16_t": Integer16Unsigned, + "uint8_t": Integer8Unsigned, + "int64_t": Integer64, + "int32_t": Integer32, + "int16_t": Integer16, + "int8_t": Integer8, + "char": Char, + + "struct timeval": Timeval, + "char **": StringArray, + "PyObject *": PyObject, +} + +method_attributes = ["BORROWED", "DESTRUCTOR", "IGNORE"] + + +class ResultException(object): + value = 0 + exception = "PyExc_IOError" + + def __init__(self, check, exception, message): + self.check = check + self.exception = exception + self.message = message + + def write(self, out): + out.write(( + "\n" + "/* Handle exceptions */\n" + "if({0:s}) {{\n" + " PyErr_Format(PyExc_{1:s}, {2:s});\n" + " goto on_error;\n" + "}}\n" + "\n").format(self.check, self.exception, self.message)) + + +class Method(object): + default_re = re.compile("DEFAULT\(([A-Z_a-z0-9]+)\) =(.+);") + exception_re = re.compile("RAISES\(([^,]+),\s*([^\)]+)\) =(.+);") + typedefed_re = re.compile(r"struct (.+)_t \*") + + def __init__( + self, class_name, base_class_name, name, args, return_type, + myclass=None): + if not isinstance(myclass, ClassGenerator): + raise RuntimeError("myclass must be a class generator") + + self.args = [] + self.base_class_name = base_class_name + self.class_name = class_name + self.defaults = {} + self.definition_class_name = class_name + self.docstring = "" + self.error_set = False + self.exception = None + self.name = name + self.myclass = myclass + + for type, name in args: + self.add_arg(type, name) + + try: + self.return_type = dispatch("func_return", return_type) + self.return_type.attributes.add("OUT") + self.return_type.original_type = return_type + except KeyError: + # Is it a wrapped type? + if return_type: + log("Unable to handle return type {0:s}.{1:s} {2:s}".format( + self.class_name, self.name, return_type)) + # pdb.set_trace() + self.return_type = PVoid("func_return") + + def get_string(self): + """Retrieves a string representation.""" + return "def {0:s} {1:s}({2:s}):".format( + self.return_type.get_string(), self.name, + " , ".join([a.get_string() for a in self.args])) + + def clone(self, new_class_name): + self.find_optional_vars() + + result = self.__class__( + new_class_name, self.base_class_name, self.name, [], "void *", + myclass=self.myclass) + result.args = self.args + result.return_type = self.return_type + result.definition_class_name = self.definition_class_name + result.defaults = self.defaults + result.exception = self.exception + + return result + + def find_optional_vars(self): + for line in self.docstring.splitlines(): + m = self.default_re.search(line) + if m: + name = m.group(1) + value = m.group(2) + log("Setting default value for {0:s} of {1:s}".format( + m.group(1), m.group(2))) + self.defaults[name] = value + + m = self.exception_re.search(line) + if m: + self.exception = ResultException( + m.group(1), m.group(2), m.group(3)) + + def write_local_vars(self, out): + self.find_optional_vars() + + # We do it in two passes - first mandatory then optional + kwlist = " static char *kwlist[] = {" + # Mandatory + for type in self.args: + python_name = type.python_name() + if python_name and python_name not in self.defaults: + kwlist += "\"{0:s}\",".format(python_name) + + for type in self.args: + python_name = type.python_name() + if python_name and python_name in self.defaults: + kwlist += "\"{0:s}\",".format(python_name) + + kwlist += " NULL};\n" + + for type in self.args: + out.write( + " // DEBUG: local arg type: {0:s}\n".format( + type.__class__.__name__)) + python_name = type.python_name() + try: + out.write(type.definition(default=self.defaults[python_name])) + except KeyError: + out.write(type.definition()) + + # Make up the format string for the parse args in two pases + parse_line = "" + for type in self.args: + python_name = type.python_name() + if type.buildstr and python_name not in self.defaults: + parse_line += type.buildstr + + optional_args = "" + for type in self.args: + python_name = type.python_name() + if type.buildstr and python_name in self.defaults: + optional_args += type.buildstr + + if optional_args: + parse_line += "|" + optional_args + + # Iterators have a different prototype and do not need to + # unpack any args + if not "iternext" in self.name: + # Now parse the args from Python objects + out.write("\n") + out.write(kwlist) + out.write(( + "\n" + " if(!PyArg_ParseTupleAndKeywords(args, kwds, \"{0:s}\", ").format( + parse_line)) + + tmp = ["kwlist"] + for type in self.args: + ref = type.byref() + if ref: + tmp.append(ref) + + out.write(",".join(tmp)) + self.error_set = True + out.write( + ")) {\n" + " goto on_error;\n" + " }\n") + + def error_condition(self): + result = "" + if "DESTRUCTOR" in self.return_type.attributes: + result += "self->base = NULL;\n" + + if hasattr(self, "args"): + for type in self.args: + if hasattr(type, "error_cleanup"): + result += type.error_cleanup() + + result += " return NULL;\n"; + return result + + def write_definition(self, out): + out.write( + "\n" + "/********************************************************\n" + "Autogenerated wrapper for function:\n") + out.write(self.comment()) + out.write("********************************************************/\n") + + self._prototype(out) + out.write(( + "{{\n" + " PyObject *returned_result = NULL;\n" + " PyObject *Py_result = NULL;\n" + "\n" + " // DEBUG: return type: {0:s}\n" + " ").format( + self.return_type.__class__.__name__)) + + out.write(self.return_type.definition()) + + self.write_local_vars(out) + + values_dict = { + "class_name": self.class_name, + "method": self.name} + + out.write(( + "\n" + " // Make sure that we have something valid to wrap\n" + " if(self->base == NULL) {{\n" + " return PyErr_Format(PyExc_RuntimeError, \"{class_name:s} object no longer valid\");\n" + " }}\n" + "\n").format(**values_dict)) + + # Precall preparations + out.write(" // Precall preparations\n") + out.write(self.return_type.pre_call(self)) + for type in self.args: + out.write(type.pre_call(self)) + + values_dict = { + "class_name": self.class_name, + "def_class_name": self.definition_class_name, + "method": self.name} + + out.write(( + " // Check the function is implemented\n" + " {{\n" + " void *method = (({def_class_name:s}) self->base)->{method:s};\n" + "\n" + " if(method == NULL || (void *) unimplemented == (void *) method) {{\n" + " PyErr_Format(PyExc_RuntimeError, \"{class_name:s}.{method:s} is not implemented\");\n" + " goto on_error;\n" + " }}\n" + "\n" + " // Make the call\n" + " ClearError();\n").format(**values_dict)) + + base = "(({0:s}) self->base)".format(self.definition_class_name) + call = " {0:s}->{1:s}({2:s}".format(base, self.name, base) + tmp = "" + for type in self.args: + tmp += ", " + type.call_arg() + + call += "{0:s})".format(tmp) + + # Now call the wrapped function + out.write(self.return_type.assign(call, self, borrowed=False)) + if self.exception: + self.exception.write(out) + + self.error_set = True + + out.write( + " };\n" + "\n" + " // Postcall preparations\n") + # Postcall preparations + post_calls = [] + + post_call = self.return_type.post_call(self) + post_calls.append(post_call) + out.write(" {0:s}".format(post_call)) + + for type in self.args: + post_call = type.post_call(self) + if post_call not in post_calls: + post_calls.append(post_call) + out.write(" {0:s}".format(post_call)) + + # Now assemble the results + results = [self.return_type.to_python_object()] + for type in self.args: + if type.sense == "OUT_DONE": + results.append(type.to_python_object(results=results)) + + # If all the results are returned by reference we dont need + # to prepend the void return value at all. + if isinstance(self.return_type, Void) and len(results) > 1: + results.pop(0) + + out.write( + "\n" + " // prepare results\n") + # Make a tuple of results and pass them back + if len(results) > 1: + out.write("returned_result = PyList_New(0);\n") + for result in results: + out.write(result) + out.write( + "PyList_Append(returned_result, Py_result);\n" + "Py_DecRef(Py_result);\n") + out.write("return returned_result;\n") + else: + out.write(results[0]) + # This useless code removes compiler warnings + out.write( + " returned_result = Py_result;\n" + " return returned_result;\n") + + # Write the error part of the function + if self.error_set: + out.write(( + "\n" + "on_error:\n" + "{0:s}").format(self.error_condition())) + + out.write("};\n\n") + + def add_arg(self, type, name): + try: + t = type_dispatcher[type](name, type) + except KeyError: + # Sometimes types must be typedefed in advance + try: + m = self.typedefed_re.match(type) + type = m.group(1) + log("Trying {0:s} for {1:s}".format(type, m.group(0))) + t = type_dispatcher[type](name, type) + except (KeyError, AttributeError): + log("Unable to handle type {0:s}.{1:s} {2:s}".format( + self.class_name, self.name, type)) + return + + # Here we collapse char * + int type interfaces into a + # coherent string like interface. + try: + previous = self.args[-1] + if t.interface == "integer" and previous.interface == "string": + + # We make a distinction between IN variables and OUT + # variables + if previous.sense == "OUT": + cls = Char_and_Length_OUT + else: + cls = Char_and_Length + + cls = cls( + previous.name, + previous.type, + name, type) + + self.args[-1] = cls + + return + except IndexError: + pass + + self.args.append(t) + + def comment(self): + args = [] + for type in self.args: + args.append(type.comment()) + + return "{0:s} {1:s}.{2:s}({3:s});\n".format( + self.return_type.original_type, self.class_name, self.name, + ",".join(args)) + + def prototype(self, out): + self._prototype(out) + out.write(";\n") + + def _prototype(self, out): + values_dict = { + "class_name": self.class_name, + "method": self.name} + + out.write( + "static PyObject *py{class_name:s}_{method:s}(py{class_name:s} *self, PyObject *args, PyObject *kwds)".format( + **values_dict)) + + def PyMethodDef(self, out): + docstring = self.comment() + "\n\n" + self.docstring.strip() + values_dict = { + "class_name": self.class_name, + "docstring": format_as_docstring(docstring), + "name": self.name} + + out.write(( + " {{ \"{name:s}\",\n" + " (PyCFunction) py{class_name:s}_{name:s},\n" + " METH_VARARGS|METH_KEYWORDS,\n" + " \"{docstring:s}\" }},\n" + "\n").format(**values_dict)) + + +class IteratorMethod(Method): + """A method which implements an iterator.""" + + def __init__(self, *args, **kwargs): + super(IteratorMethod, self).__init__(*args, **kwargs) + + # Tell the return type that a NULL Python return is ok + self.return_type.attributes.add("NULL_OK") + + def get_string(self): + """Retrieves a string representation.""" + return "Iterator returning {0:s}.".format(self.return_type.get_string()) + + def _prototype(self, out): + values_dict = { + "class_name": self.class_name, + "method": self.name} + + out.write( + "static PyObject *py{class_name:s}_{method:s}(py{class_name:s} *self)".format( + **values_dict)) + + def PyMethodDef(self, out): + # This method should not go in the method table as its linked + # in directly. + pass + + +class SelfIteratorMethod(IteratorMethod): + def write_definition(self, out): + out.write( + "\n" + "/********************************************************\n" + " * Autogenerated wrapper for function:\n") + out.write(self.comment()) + out.write( + "********************************************************/\n") + + self._prototype(out) + + values_dict = { + "class_name": self.class_name, + "method": self.name} + + out.write(( + "{{\n" + " (({class_name:s}) self->base)->{method:s}(({class_name:s}) self->base);\n" + " return PyObject_SelfIter((PyObject *) self);\n" + "}}\n").format(**values_dict)) + + +class ConstructorMethod(Method): + # Python constructors are a bit different than regular methods + + def _prototype(self, out): + values_dict = { + "class_name": self.class_name, + "method": self.name} + + out.write( + "static int py{class_name:s}_init(py{class_name:s} *self, PyObject *args, PyObject *kwds)\n".format( + **values_dict)) + + def prototype(self, out): + self._prototype(out) + + values_dict = { + "class_name": self.class_name} + + out.write(( + ";\n" + "static void py{class_name:s}_initialize_proxies(py{class_name:s} *self, void *item);\n").format( + **values_dict)) + + def write_destructor(self, out): + values_dict = { + "class_name": self.class_name, + "free": FREE} + + out.write(( + "static void {class_name:s}_dealloc(py{class_name:s} *self) {{\n" + " struct _typeobject *ob_type = NULL;\n" + "\n" + " if(self != NULL) {{\n" + " if(self->base != NULL) {{\n" + " if(self->base_is_python_object != 0) {{\n" + " Py_DecRef((PyObject*) self->base);\n" + " }} else if(self->base_is_internal != 0) {{\n" + " {free:s}(self->base);\n" + " }}\n" + " self->base = NULL;\n" + " }}\n" + " if(self->python_object2 != NULL) {{\n" + " Py_DecRef(self->python_object2);\n" + " self->python_object2 = NULL;\n" + " }}\n" + " if(self->python_object1 != NULL) {{\n" + " Py_DecRef(self->python_object1);\n" + " self->python_object1 = NULL;\n" + " }}\n" + " ob_type = Py_TYPE(self);\n" + " if(ob_type != NULL && ob_type->tp_free != NULL) {{\n" + " ob_type->tp_free((PyObject*) self);\n" + " }}\n" + " }}\n" + "}}\n" + "\n").format(**values_dict)) + + def error_condition(self): + return " return -1;"; + + def initialise_proxies(self, out): + self.myclass.module.function_definitions.add( + "py{0:s}_initialize_proxies".format(self.class_name)) + + values_dict = { + "class_name": self.class_name} + + out.write(( + "static void py{class_name:s}_initialize_proxies(py{class_name:s} *self, void *item) {{\n" + " {class_name:s} target = ({class_name:s}) item;\n" + "\n" + " /* Maintain a reference to the Python object\n" + " * in the C object extension\n" + " */\n" + " ((Object) item)->extension = self;\n" + "\n").format(**values_dict)) + + # Install proxies for all the method in the current class. + for method in self.myclass.module.classes[self.class_name].methods: + if method.name.startswith("_"): + continue + + # Since the SleuthKit uses close method also for freeing it needs + # to be handled separately to prevent the C/C++ code calling back + # into a garbage collected Python object. For close we keep the + # default implementation and have its destructor deal with + # correctly closing the SleuthKit object. + if method.name != "close": + values_dict = { + "class_name": method.class_name, + "definition_class_name": method.definition_class_name, + "name": method.name, + "proxied_name": method.proxied.get_name()} + + out.write(( + " if(check_method_override((PyObject *) self, &{class_name:s}_Type, \"{name:s}\")) {{\n" + " // Proxy the {name:s} method\n" + " (({definition_class_name:s}) target)->{name:s} = {proxied_name:s};\n" + " }}\n").format(**values_dict)) + + out.write("}\n\n") + + def write_definition(self, out): + self.initialise_proxies(out) + self._prototype(out) + values_dict = { + "class_name": self.class_name, + "definition_class_name": self.definition_class_name} + + out.write(( + "{{\n" + " {class_name:s} result_constructor = NULL;\n").format( + **values_dict)) + + # pdb.set_trace() + self.write_local_vars(out) + + # Assign the initialise_proxies handler + out.write(( + " self->python_object1 = NULL;\n" + " self->python_object2 = NULL;\n" + "\n" + " /* Initialise is used to keep a reference on the object?\n" + " * If not called no longer valid warnings have been seen\n" + " * on Windows.\n" + " */\n" + " self->initialise = (void *) py{class_name:s}_initialize_proxies;\n" + "\n").format(**values_dict)) + + # Precall preparations + python_object_index = 1 + for type in self.args: + out.write(type.pre_call( + self, python_object_index=python_object_index)) + python_object_index += 1 + + # Now call the wrapped function + out.write(( + " ClearError();\n" + "\n" + " /* Allocate a new instance */\n" + " self->base = ({class_name:s}) alloc_{class_name:s}();\n" + " self->base_is_python_object = 0;\n" + " self->base_is_internal = 1;\n" + " self->object_is_proxied = 0;\n" + "\n" + " /* Update the target by replacing its methods with proxies\n" + " * to call back into Python\n" + " */\n" + " py{class_name:s}_initialize_proxies(self, self->base);\n" + "\n" + " /* Now call the constructor */\n" + " Py_BEGIN_ALLOW_THREADS\n" + " result_constructor = CONSTRUCT_INITIALIZE({class_name:s}, {definition_class_name:s}, Con, self->base").format( + **values_dict)) + + tmp = "" + for type in self.args: + tmp += ", " + type.call_arg() + + self.error_set = True + out.write(tmp) + + out.write(( + ");\n" + " Py_END_ALLOW_THREADS\n" + "\n" + " if(!CheckError(EZero)) {{\n" + " char *buffer = NULL;\n" + " PyObject *exception = resolve_exception(&buffer);\n" + "\n" + " PyErr_Format(exception, \"%s\", buffer);\n" + " ClearError();\n" + " goto on_error;\n" + " }}\n" + " if(result_constructor == NULL) {{\n" + " PyErr_Format(PyExc_IOError, \"Unable to construct class {class_name:s}\");\n" + " goto on_error;\n" + " }}\n" + "\n" + " return 0;\n").format(**values_dict)) + + # Write the error part of the function. + if self.error_set: + out.write(( + "\n" + "on_error:\n" + " if(self->python_object2 != NULL) {{\n" + " Py_DecRef(self->python_object2);\n" + " self->python_object2 = NULL;\n" + " }}\n" + " if(self->python_object1 != NULL) {{\n" + " Py_DecRef(self->python_object1);\n" + " self->python_object1 = NULL;\n" + " }}\n" + " if(self->base != NULL) {{\n" + " talloc_free(self->base);\n" + " self->base = NULL;\n" + " }}\n" + "{0:s}\n").format(self.error_condition())) + + out.write("}\n\n") + + +class GetattrMethod(Method): + def __init__(self, class_name, base_class_name, myclass): + # Cannot use super here due to certain logic in Method.__init__(). + self._attributes = [] + self.base_class_name = base_class_name + self.class_name = class_name + self.error_set = True + self.myclass = myclass + self.name = "" + self.return_type = Void("") + + self.rename_class_name(class_name) + + def get_string(self): + """Retrieves a string representation.""" + result = "" + for class_name, attr in self.get_attributes(): + result += " {0:s}\n".format(attr.get_string()) + + return result + + def add_attribute(self, attr): + if attr.name: + self._attributes.append([self.class_name, attr]) + + def rename_class_name(self, new_name): + """This allows us to rename the class_name at a later stage. + Required for late initialization of Structs whose name is not + know until much later on. + """ + # TODO fix this behavior, new_name can be None but it is unclear what + # the behavious should be. Python 3 requires the values to be set to + # string types. + if not new_name: + self.class_name = "" + self.name = "" + else: + self.class_name = new_name + self.name = "py{0:s}_getattr".format(new_name) + + for attribure in self._attributes: + attribure[0] = new_name + + def get_attributes(self): + for class_name, attr in self._attributes: + try: + # If its not an active struct, skip it + if (not type_dispatcher[attr.type].active and + not attr.type in self.myclass.module.active_structs): + continue + + except KeyError: + pass + + yield class_name, attr + + def clone(self, class_name): + result = self.__class__(class_name, self.base_class_name, self.myclass) + result._attributes = self._attributes[:] + + return result + + def prototype(self, out): + if not self.name: + return + + values_dict = { + "class_name": self.class_name, + "name": self.name} + + # Define getattr. + out.write( + "static PyObject *{name:s}(py{class_name:s} *self, PyObject *name);\n".format( + **values_dict)) + + # Define getters. + for _, attr in self.get_attributes(): + values_dict = { + "class_name": self.class_name, + "name": attr.name} + + out.write( + "PyObject *py{class_name:s}_{name:s}_getter(py{class_name:s} *self, PyObject *arguments);\n".format( + **values_dict)) + + def built_ins(self, out): + """Check for some built in attributes we need to support.""" + out.write( + " if(strcmp(name, \"__members__\") == 0) {\n" + " PyMethodDef *i = NULL;\n" + " PyObject *list_object = NULL;\n" + " PyObject *string_object = NULL;\n" + "\n" + " list_object = PyList_New(0);\n" + " if(list_object == NULL) {\n" + " goto on_error;\n" + " }\n" + "\n") + + # Add attributes + for class_name, attr in self.get_attributes(): + values_dict = { + "name": attr.name} + + out.write(( + "#if PY_MAJOR_VERSION >= 3\n" + " string_object = PyUnicode_FromString(\"{name:s}\");\n" + "#else\n" + " string_object = PyString_FromString(\"{name:s}\");\n" + "#endif\n" + " PyList_Append(list_object, string_object);\n" + " Py_DecRef(string_object);\n" + "\n").format(**values_dict)) + + # Add methods + out.write(( + "\n" + " for(i = {0:s}_methods; i->ml_name; i++) {{\n" + "#if PY_MAJOR_VERSION >= 3\n" + " string_object = PyUnicode_FromString(i->ml_name);\n" + "#else\n" + " string_object = PyString_FromString(i->ml_name);\n" + "#endif\n" + " PyList_Append(list_object, string_object);\n" + " Py_DecRef(string_object);\n" + " }}\n" + "#if PY_MAJOR_VERSION >= 3\n" + " if( utf8_string_object != NULL ) {{\n" + " Py_DecRef(utf8_string_object);\n" + " }}\n" + "#endif\n" + " return list_object;\n" + " }}\n").format(self.class_name)) + + def write_definition(self, out): + if not self.name: + return + + values_dict = { + "class_name": self.class_name, + "name": self.name} + + out.write(( + "static PyObject *py{class_name:s}_getattr(py{class_name:s} *self, PyObject *pyname) {{\n" + " PyObject *result = NULL;\n" + " char *name = NULL;\n" + "\n" + "#if PY_MAJOR_VERSION >= 3\n" + " PyObject *utf8_string_object = NULL;\n" + "#endif\n" + "\n" + " // Try to hand it off to the Python native handler first\n" + " result = PyObject_GenericGetAttr((PyObject*) self, pyname);\n" + "\n" + " if(result) {{\n" + " return result;\n" + " }}\n" + "\n" + " PyErr_Clear();\n" + " // No - nothing interesting was found by python\n" + "#if PY_MAJOR_VERSION >= 3\n" + " utf8_string_object = PyUnicode_AsUTF8String(pyname);\n" + "\n" + " if(utf8_string_object != NULL) {{\n" + " name = PyBytes_AsString(utf8_string_object);\n" + " }}\n" + "#else\n" + " name = PyString_AsString(pyname);\n" + "#endif\n" + "\n" + " if(!self->base) {{\n" + "#if PY_MAJOR_VERSION >= 3\n" + " if( utf8_string_object != NULL ) {{\n" + " Py_DecRef(utf8_string_object);\n" + " }}\n" + "#endif\n" + " return PyErr_Format(PyExc_RuntimeError, \"Wrapped object ({class_name:s}.{name:s}) no longer valid\");\n" + " }}\n" + " if(!name) {{\n" + " goto on_error;\n" + " }}\n").format(**values_dict)) + + self.built_ins(out) + + out.write( + "\n" + "#if PY_MAJOR_VERSION >= 3\n" + " if( utf8_string_object != NULL ) {{\n" + " Py_DecRef(utf8_string_object);\n" + " }}\n" + "#endif\n" + " return PyObject_GenericGetAttr((PyObject *) self, pyname);\n") + + # Write the error part of the function. + if self.error_set: + out.write( + "on_error:\n" + "#if PY_MAJOR_VERSION >= 3\n" + " if( utf8_string_object != NULL ) {{\n" + " Py_DecRef(utf8_string_object);\n" + " }}\n" + "#endif\n" + self.error_condition()) + + out.write("}\n\n") + + self.write_definition_getters(out) + + def write_definition_getters(self, out): + for _, attr in self.get_attributes(): + if self.base_class_name: + call = "((({0:s}) self->base)->{1:s})".format( + self.class_name, attr.name) + else: + call = "(self->base->{0:s})".format(attr.name) + + values_dict = { + "class_name": self.class_name, + "name": attr.name, + "python_obj": attr.to_python_object(), + "python_assign": attr.assign(call, self, borrowed=True), + "python_def": attr.definition(sense="out")} + + out.write(( + "PyObject *py{class_name:s}_{name:s}_getter(py{class_name:s} *self, PyObject *arguments) {{\n" + " PyObject *Py_result = NULL;\n" + "{python_def:s}\n" + "\n" + "{python_assign:s}\n" + "{python_obj:s}\n" + "\n" + " return Py_result;\n" + "\n").format(**values_dict)) + + # Work-around for the String class that generates code that contains "goto on_error". + if isinstance(attr, String): + out.write(( + "on_error:\n" + " {0:s}\n").format(attr.error_value)) + + out.write("}\n\n") + + def PyGetSetDef(self, out): + for _, attr in self.get_attributes(): + # TODO: improve docstring. + docstring = "{0:s}.".format(attr.name) + values_dict = { + "class_name": self.class_name, + "docstring": format_as_docstring(docstring), + "name": attr.name} + + out.write(( + " {{ \"{name:s}\",\n" + " (getter) py{class_name:s}_{name:s}_getter,\n" + " (setter) 0,\n" + " \"{docstring:s}\",\n" + " NULL }},\n" + "\n").format(**values_dict)) + + +class ProxiedMethod(Method): + def __init__(self, method, myclass): + # Cannot use super here due to certain logic in Method.__init__(). + self.args = method.args + self.base_class_name = method.base_class_name + self.class_name = method.class_name + self.defaults = {} + self.definition_class_name = method.definition_class_name + self.docstring = "Proxy for {0:s}".format(method.name) + self.error_set = False + self.exception = None + self.method = method + self.myclass = myclass + self.name = method.name + self.return_type = method.return_type + + def get_name(self): + return "Proxied{0:s}_{1:s}".format( + self.myclass.class_name, self.name) + + def _prototype(self, out): + out.write("static {0:s} {1:s}({2:s} self".format( + self.return_type.type.strip(), self.get_name(), + self.definition_class_name)) + + for arg in self.args: + tmp = arg.comment().strip() + if tmp: + out.write(", {0:s}".format(tmp)) + + out.write(")") + + def prototype(self, out): + self._prototype(out) + out.write(";\n") + + def write_definition(self, out): + name = self.get_name() + if name in self.myclass.module.function_definitions: + return + else: + self.myclass.module.function_definitions.add(name) + + self._prototype(out) + self._write_definition(out) + + def _write_definition(self, out): + out.write( + " {\n" + " PyGILState_STATE gil_state;\n" + " PyObject *Py_result = NULL;\n" + " PyObject *method_name = NULL;\n") + + out.write(self.return_type.returned_python_definition()) + + for arg in self.args: + out.write(arg.local_definition()) + out.write("PyObject *py_{0:s} = NULL;\n".format(arg.name)) + + out.write(( + "\n" + " // Grab the GIL so we can do Python stuff\n" + " gil_state = PyGILState_Ensure();\n" + "\n" + "#if PY_MAJOR_VERSION >= 3\n" + " method_name = PyUnicode_FromString(\"{0:s}\");\n" + "#else\n" + " method_name = PyString_FromString(\"{0:s}\");\n" + "#endif\n").format(self.name)) + + out.write("\n// Obtain Python objects for all the args:\n") + for arg in self.args: + out.write(arg.to_python_object( + result=("py_{0:s}".format(arg.name)), sense="proxied", + BORROWED=True)) + + out.write(( + " if(((Object) self)->extension == NULL) {{\n" + " RaiseError(ERuntimeError, \"No proxied object in {0:s}\");\n" + " goto on_error;\n" + " }}\n").format(self.myclass.class_name)) + + out.write( + "\n" + " // Now call the method\n" + " PyErr_Clear();\n" + " Py_result = PyObject_CallMethodObjArgs(((Object) self)->extension, method_name, ") + + for arg in self.args: + out.write("py_{0:s},".format(arg.name)) + + # Sentinal + out.write( + "NULL);\n" + "\n") + + self.error_set = True + out.write(( + " /* Check for Python errors */\n" + " if(PyErr_Occurred()) {{\n" + " pytsk_fetch_error();\n" + "\n" + " goto on_error;\n" + " }}\n" + "\n").format(CURRENT_ERROR_FUNCTION)) + + for arg in self.args: + out.write(arg.python_proxy_post_call()) + + # Now convert the Python value back to a value + out.write(self.return_type.from_python_object( + "Py_result", self.return_type.name, self, context="self")) + + out.write( + " if(Py_result != NULL) {\n" + " Py_DecRef(Py_result);\n" + " }\n" + " Py_DecRef(method_name);\n" + "\n") + + # Decref all our Python objects: + for arg in self.args: + out.write(( + " if(py_{0:s} != NULL) {{\n" + " Py_DecRef(py_{0:s});\n" + " }}\n").format(arg.name)) + + out.write(( + " PyGILState_Release(gil_state);\n" + "\n" + " {0:s}\n").format( + self.return_type.return_value("func_return"))) + + if self.error_set: + out.write( + "\n" + "on_error:\n" + " if(Py_result != NULL) {\n" + " Py_DecRef(Py_result);\n" + " }\n" + " Py_DecRef(method_name);\n" + "\n") + + # Decref all our Python objects: + for arg in self.args: + out.write(( + " if(py_{0:s} != NULL) {{\n" + " Py_DecRef(py_{0:s});\n" + " }}\n").format(arg.name)) + + out.write(( + " PyGILState_Release(gil_state);\n" + "\n" + " {0:s}\n").format( + self.error_condition())) + + out.write( + "}\n" + "\n") + + def error_condition(self): + values_dict = { + "result": "func_return"} + return self.return_type.error_value.format(**values_dict) + + +class StructConstructor(ConstructorMethod): + """ A constructor for struct wrappers - basically just allocate + memory for the struct. + """ + + def prototype(self, out): + return Method.prototype(self, out) + + def write_destructor(self, out): + """We do not deallocate memory from structs. + + This is a real problem since struct memory is usually + allocated in some proprietary way and we cant just call free + on it when done. + """ + values_dict = { + "class_name": self.class_name} + + out.write(( + "static void {class_name:s}_dealloc(py{class_name:s} *self) {{\n" + " struct _typeobject *ob_type = NULL;\n" + "\n" + " if(self != NULL) {{\n" + " if(self->base != NULL) {{\n" + " self->base = NULL;\n" + " }}\n" + " ob_type = Py_TYPE(self);\n" + " if(ob_type != NULL && ob_type->tp_free != NULL) {{\n" + " ob_type->tp_free((PyObject*) self);\n" + " }}\n" + " }}\n" + "}}\n" + "\n").format(**values_dict)) + + def write_definition(self, out): + values_dict = { + "class_name": self.class_name} + + out.write(( + "static int py{class_name:s}_init(py{class_name:s} *self, PyObject *args, PyObject *kwds) {{\n" + " // Base is borrowed from another object.\n" + " self->base = NULL;\n" + " return 0;\n" + "}}\n" + "\n").format(**values_dict)) + + +class EmptyConstructor(ConstructorMethod): + def prototype(self, out): + return Method.prototype(self, out) + + def write_definition(self, out): + values_dict = { + "class_name": self.class_name} + + out.write( + "static int py{class_name:s}_init(py{class_name:s} *self, PyObject *args, PyObject *kwds) {{\n" + " return 0;\n" + "}}\n" + "\n".format(**values_dict)) + + +class ClassGenerator(object): + docstring = "" + + def __init__(self, class_name, base_class_name, module): + self.class_name = class_name + self.methods = [] + # self.methods = [DefinitionMethod( + # class_name, base_class_name, "_definition", [], "", + # myclass=self)] + self.module = module + self.constructor = EmptyConstructor( + class_name, base_class_name, "Con", [], "", myclass=self) + + self.base_class_name = base_class_name + self.attributes = GetattrMethod( + self.class_name, self.base_class_name, self) + self.modifier = set() + self.active = True + self.iterator = None + + def get_string(self): + """Retrieves a string representation.""" + result = ( + "#{0:s}\n" + "Class {1:s}({2:s}):\n" + " Constructor:{3:s}\n" + " Attributes:\n{4:s}\n" + " Methods:\n").format( + self.docstring, self.class_name, self.base_class_name, + self.constructor.get_string(), self.attributes.get_string()) + + for method in self.methods: + result += " {0:s}\n".format(method.get_string()) + + return result + + def prepare(self): + """ This method is called just before we need to write the + output and allows us to do any last minute fixups. + """ + pass + + def is_active(self): + """Returns true if this class is active and should be generated""" + if self.class_name in self.module.active_structs: + return True + + if (not self.active or self.modifier and + ("PRIVATE" in self.modifier or "ABSTRACT" in self.modifier)): + log("{0:s} is not active {1!s}".format( + self.class_name, self.modifier)) + return False + + return True + + def clone(self, new_class_name): + """Creates a clone of this class - usefull when implementing + class extensions. + """ + result = ClassGenerator(new_class_name, self.class_name, self.module) + result.constructor = self.constructor.clone(new_class_name) + result.methods = [ + method.clone(new_class_name) for method in self.methods] + result.attributes = self.attributes.clone(new_class_name) + + return result + + def add_attribute(self, attr_name, attr_type, modifier, *args, **kwargs): + try: + if not self.module.classes[attr_type].is_active(): + return + except KeyError: + pass + + try: + # All attribute references are always borrowed - that + # means we dont want to free them after accessing them + type_class = dispatch( + attr_name, "BORROWED {0:s}".format(attr_type), *args, **kwargs) + except KeyError: + # TODO: fix that self.class_name is None. + log("Unknown attribute type {0:s} for {1!s}.{2:s}".format( + attr_type, self.class_name, attr_name)) + return + + type_class.attributes.add(modifier) + self.attributes.add_attribute(type_class) + + def add_constructor(self, method_name, args, return_type, docstring): + if method_name.startswith("Con"): + self.constructor = ConstructorMethod( + self.class_name, self.base_class_name, method_name, args, + return_type, myclass=self) + self.constructor.docstring = docstring + + def struct(self, out): + values_dict = { + "class_name": self.class_name} + + out.write(( + "\n" + "typedef struct {{\n" + " PyObject_HEAD\n" + " {class_name:s} base;\n" + " int base_is_python_object;\n" + " int base_is_internal;\n" + " PyObject *python_object1;\n" + " PyObject *python_object2;\n" + " int object_is_proxied;\n" + "\n" + " void (*initialise)(Gen_wrapper self, void *item);\n" + "}} py{class_name:s};\n").format(**values_dict)) + + def code(self, out): + if not self.constructor: + raise RuntimeError( + "No constructor found for class {0:s}".format(self.class_name)) + + self.constructor.write_destructor(out) + self.constructor.write_definition(out) + if self.attributes: + self.attributes.write_definition(out) + + for method in self.methods: + method.write_definition(out) + + if hasattr(method, "proxied"): + method.proxied.write_definition(out) + + def initialise(self): + values_dict = { + "class_name": self.class_name} + + result = ( + "python_wrappers[TOTAL_CLASSES].class_ref = (Object)&__{class_name:s};\n" + "python_wrappers[TOTAL_CLASSES].python_type = &{class_name:s}_Type;\n").format(**values_dict) + + func_name = "py{class_name:s}_initialize_proxies".format(**values_dict) + if func_name in self.module.function_definitions: + result += ( + "python_wrappers[TOTAL_CLASSES].initialize_proxies = (void (*)(Gen_wrapper, void *)) &{0:s};\n").format( + func_name) + + result += "TOTAL_CLASSES++;\n" + return result + + def PyGetSetDef(self, out): + out.write( + "static PyGetSetDef {0:s}_get_set_definitions[] = {{\n".format( + self.class_name)) + + if self.attributes: + self.attributes.PyGetSetDef(out) + + out.write( + " {NULL, NULL, NULL, NULL, NULL} /* Sentinel */\n" + "};\n" + "\n") + + def PyMethodDef(self, out): + out.write("static PyMethodDef {0:s}_methods[] = {{\n".format( + self.class_name)) + + for method in self.methods: + method.PyMethodDef(out) + + out.write( + " {NULL, NULL, 0, NULL} /* Sentinel */\n" + "};\n" + "\n") + + def prototypes(self, out): + """Write prototype suitable for .h file""" + out.write("static PyTypeObject {0:s}_Type;\n".format(self.class_name)) + self.constructor.prototype(out) + + if self.attributes: + self.attributes.prototype(out) + for method in self.methods: + method.prototype(out) + + # Each method, except for close, needs a proxy method that + # is called when the object is sub typed. + if method.name == "close": + continue + + method.proxied = ProxiedMethod(method, method.myclass) + method.proxied.prototype(out) + + def numeric_protocol_int(self): + pass + + def numeric_protocol_nonzero(self): + values_dict = { + "class_name": self.class_name} + + return ( + "static int {class_name:s}_nonzero(py{class_name:s} *v) {{\n" + " return v->base != 0;\n" + "}}\n").format(**values_dict) + + def numeric_protocol(self, out): + args = { + "class": self.class_name} + for type, func in [ + ("nonzero", self.numeric_protocol_nonzero), + ("int", self.numeric_protocol_int)]: + + definition = func() + if definition: + out.write(definition) + args[type] = "{0:s}_{1:s}".format(self.class_name, type) + else: + args[type] = "0" + + out.write(( + "#if PY_MAJOR_VERSION >= 3\n" + "static PyNumberMethods {class:s}_as_number = {{\n" + " (binaryfunc) 0, /* nb_add */\n" + " (binaryfunc) 0, /* nb_subtract */\n" + " (binaryfunc) 0, /* nb_multiply */\n" + " (binaryfunc) 0, /* nb_remainder */\n" + " (binaryfunc) 0, /* nb_divmod */\n" + " (ternaryfunc) 0, /* nb_power */\n" + " (unaryfunc) 0, /* nb_negative */\n" + " (unaryfunc) 0, /* nb_positive */\n" + " (unaryfunc) 0, /* nb_absolute */\n" + " (inquiry) {nonzero:s}, /* nb_bool */\n" + " (unaryfunc) 0, /* nb_invert */\n" + " (binaryfunc) 0, /* nb_lshift */\n" + " (binaryfunc) 0, /* nb_rshift */\n" + " (binaryfunc) 0, /* nb_and */\n" + " (binaryfunc) 0, /* nb_xor */\n" + " (binaryfunc) 0, /* nb_or */\n" + " (unaryfunc) {int:s}, /* nb_int */\n" + " (void *) NULL, /* nb_reserved */\n" + " (unaryfunc) 0, /* nb_float */\n" + "\n" + " (binaryfunc) 0, /* nb_inplace_add */\n" + " (binaryfunc) 0, /* nb_inplace_subtract */\n" + " (binaryfunc) 0, /* nb_inplace_multiply */\n" + " (binaryfunc) 0, /* nb_inplace_remainder */\n" + " (ternaryfunc) 0, /* nb_inplace_power */\n" + " (binaryfunc) 0, /* nb_inplace_lshift */\n" + " (binaryfunc) 0, /* nb_inplace_rshift */\n" + " (binaryfunc) 0, /* nb_inplace_and */\n" + " (binaryfunc) 0, /* nb_inplace_xor */\n" + " (binaryfunc) 0, /* nb_inplace_or */\n" + "\n" + " (binaryfunc) 0, /* nb_floor_divide */\n" + " (binaryfunc) 0, /* nb_true_divide */\n" + " (binaryfunc) 0, /* nb_inplace_floor_divide */\n" + " (binaryfunc) 0, /* nb_inplace_true_divide */\n" + "\n" + " (unaryfunc) 0, /* nb_index */\n" + "}};\n" + "#else\n" + "static PyNumberMethods {class:s}_as_number = {{\n" + " (binaryfunc) 0, /* nb_add */\n" + " (binaryfunc) 0, /* nb_subtract */\n" + " (binaryfunc) 0, /* nb_multiply */\n" + " (binaryfunc) 0, /* nb_divide */\n" + " (binaryfunc) 0, /* nb_remainder */\n" + " (binaryfunc) 0, /* nb_divmod */\n" + " (ternaryfunc) 0, /* nb_power */\n" + " (unaryfunc) 0, /* nb_negative */\n" + " (unaryfunc) 0, /* nb_positive */\n" + " (unaryfunc) 0, /* nb_absolute */\n" + " (inquiry) {nonzero:s}, /* nb_nonzero */\n" + " (unaryfunc) 0, /* nb_invert */\n" + " (binaryfunc) 0, /* nb_lshift */\n" + " (binaryfunc) 0, /* nb_rshift */\n" + " (binaryfunc) 0, /* nb_and */\n" + " (binaryfunc) 0, /* nb_xor */\n" + " (binaryfunc) 0, /* nb_or */\n" + " (coercion) 0, /* nb_coerce */\n" + " (unaryfunc) {int:s}, /* nb_int */\n" + " (unaryfunc) 0, /* nb_long */\n" + " (unaryfunc) 0, /* nb_float */\n" + " (unaryfunc) 0, /* nb_oct */\n" + " (unaryfunc) 0, /* nb_hex */\n" + "\n" + " (binaryfunc) 0, /* nb_inplace_add */\n" + " (binaryfunc) 0, /* nb_inplace_subtract */\n" + " (binaryfunc) 0, /* nb_inplace_multiply */\n" + " (binaryfunc) 0, /* nb_inplace_divide */\n" + " (binaryfunc) 0, /* nb_inplace_remainder */\n" + " (ternaryfunc) 0, /* nb_inplace_power */\n" + " (binaryfunc) 0, /* nb_inplace_lshift */\n" + " (binaryfunc) 0, /* nb_inplace_rshift */\n" + " (binaryfunc) 0, /* nb_inplace_and */\n" + " (binaryfunc) 0, /* nb_inplace_xor */\n" + " (binaryfunc) 0, /* nb_inplace_or */\n" + "\n" + " (binaryfunc) 0, /* nb_floor_divide */\n" + " (binaryfunc) 0, /* nb_true_divide */\n" + " (binaryfunc) 0, /* nb_inplace_floor_divide */\n" + " (binaryfunc) 0, /* nb_inplace_true_divide */\n" + "\n" + " (unaryfunc) 0, /* nb_index */\n" + "}};\n" + "#endif /* PY_MAJOR_VERSION >= 3 */\n" + "\n").format(**args)) + + return "&{class:s}_as_number".format(**args) + + def PyTypeObject(self, out): + docstring = "{0:s}: {1:s}".format( + self.class_name, format_as_docstring(self.docstring)) + + args = { + "class": self.class_name, + "module": self.module.name, + "iterator": 0, + "iternext": 0, + "tp_str": 0, + "tp_eq": 0, + "getattr_func": 0, + "docstring": docstring} + + if self.attributes: + args["getattr_func"] = self.attributes.name + + args["numeric_protocol"] = self.numeric_protocol(out) + if "ITERATOR" in self.modifier: + args["iterator"] = "PyObject_SelfIter" + args["iternext"] = "py{0:s}_iternext".format(self.class_name) + + if "SELF_ITER" in self.modifier: + args["iterator"] = "py{0:s}___iter__".format(self.class_name) + + if "TP_STR" in self.modifier: + args["tp_str"] = "py{0:s}___str__".format(self.class_name) + + if "TP_EQUAL" in self.modifier: + args["tp_eq"] = "{0:s}_eq".format(self.class_name) + + out.write(( + "static PyTypeObject {class:s}_Type = {{\n" + " PyVarObject_HEAD_INIT(NULL, 0)\n" + " /* tp_name */\n" + " \"{module:s}.{class:s}\",\n" + " /* tp_basicsize */\n" + " sizeof(py{class:s}),\n" + " /* tp_itemsize */\n" + " 0,\n" + " /* tp_dealloc */\n" + " (destructor) {class:s}_dealloc,\n" + " /* tp_print */\n" + " 0,\n" + " /* tp_getattr */\n" + " 0,\n" + " /* tp_setattr */\n" + " 0,\n" + " /* tp_compare */\n" + " 0,\n" + " /* tp_repr */\n" + " 0,\n" + " /* tp_as_number */\n" + " {numeric_protocol:s},\n" + " /* tp_as_sequence */\n" + " 0,\n" + " /* tp_as_mapping */\n" + " 0,\n" + " /* tp_hash */\n" + " 0,\n" + " /* tp_call */\n" + " 0,\n" + " /* tp_str */\n" + " (reprfunc) {tp_str!s},\n" + " /* tp_getattro */\n" + " (getattrofunc) {getattr_func!s},\n" + " /* tp_setattro */\n" + " 0,\n" + " /* tp_as_buffer */\n" + " 0,\n" + " /* tp_flags */\n" + " Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,\n" + " /* tp_doc */\n" + " \"{docstring:s}\",\n" + " /* tp_traverse */\n" + " 0,\n" + " /* tp_clear */\n" + " 0,\n" + " /* tp_richcompare */\n" + " {tp_eq!s},\n" + " /* tp_weaklistoffset */\n" + " 0,\n" + " /* tp_iter */\n" + " (getiterfunc) {iterator!s},\n" + " /* tp_iternext */\n" + " (iternextfunc) {iternext!s},\n" + " /* tp_methods */\n" + " {class:s}_methods,\n" + " /* tp_members */\n" + " 0,\n" + " /* tp_getset */\n" + " {class:s}_get_set_definitions,\n" + " /* tp_base */\n" + " 0,\n" + " /* tp_dict */\n" + " 0,\n" + " /* tp_descr_get */\n" + " 0,\n" + " /* tp_descr_set */\n" + " 0,\n" + " /* tp_dictoffset */\n" + " 0,\n" + " /* tp_init */\n" + " (initproc) py{class:s}_init,\n" + " /* tp_alloc */\n" + " 0,\n" + " /* tp_new */\n" + " 0,\n" + "}};\n" + "\n").format(**args)) + + +class StructGenerator(ClassGenerator): + """A wrapper generator for structs.""" + + def __init__(self, class_name, module): + self.class_name = class_name + self.methods = [] + self.module = module + self.base_class_name = None + self.active = False + self.modifier = set() + self.constructor = None + self.attributes = GetattrMethod( + self.class_name, self.base_class_name, self) + + def get_string(self): + """Retrieves a string representation.""" + return ( + "# {0:s}\n" + "Struct {1:s}:\n" + "{2:s}\n").format( + self.docstring, self.class_name, self.attributes.get_string()) + + def prepare(self): + # This is needed for late stage initialization - sometimes + # our class_name is not know until now. + if not self.constructor: + self.constructor = StructConstructor( + self.class_name, self.base_class_name, "Con", [], "void", + myclass=self) + + self.attributes.rename_class_name(self.class_name) + for x in self.attributes._attributes: + x[1].attributes.add("FOREIGN") + + def struct(self, out): + values_dict = { + "class_name": self.class_name} + + out.write(( + "\n" + "typedef struct {{\n" + " PyObject_HEAD\n" + " {class_name:s} *base;\n" + " int base_is_python_object;\n" + " int base_is_internal;\n" + " PyObject *python_object1;\n" + " PyObject *python_object2;\n" + " int object_is_proxied;\n" + " {class_name:s} *cbase;\n" + "}} py{class_name:s};\n").format( + **values_dict)) + + def initialise(self): + return "" + + +class EnumConstructor(ConstructorMethod): + def prototype(self, out): + return Method.prototype(self, out) + + def write_destructor(self, out): + values_dict = { + "class_name": self.class_name} + + out.write(( + "static void {class_name:s}_dealloc(py{class_name:s} *self) {{\n" + " struct _typeobject *ob_type = NULL;\n" + "\n" + " if(self != NULL) {{\n" + " Py_DecRef(self->value);\n" + " ob_type = Py_TYPE(self);\n" + " if(ob_type != NULL && ob_type->tp_free != NULL) {{\n" + " ob_type->tp_free((PyObject*) self);\n" + " }}\n" + " }}\n" + "}}\n").format(**values_dict)) + + def write_definition(self, out): + self.myclass.modifier.add("TP_STR") + self.myclass.modifier.add("TP_EQUAL") + self._prototype(out) + + values_dict = { + "class_name": self.class_name} + + out.write(( + "{{\n" + " static char *kwlist[] = {{\"value\", NULL}};\n" + "\n" + " if(!PyArg_ParseTupleAndKeywords(args, kwds, \"O\", kwlist, &self->value)) {{\n" + " goto on_error;\n" + " }}\n" + "\n" + " Py_IncRef(self->value);\n" + "\n" + " return 0;\n" + "\n" + "on_error:\n" + " return -1;\n" + "}}\n" + "\n" + "static PyObject *py{class_name:s}___str__(py{class_name:s} *self) {{\n" + " PyObject *result = PyDict_GetItem({class_name:s}_rev_lookup, self->value);\n" + "\n" + " if(result) {{\n" + " Py_IncRef(result);\n" + " }} else {{\n" + " result = PyObject_Str(self->value);\n" + " }}\n" + "\n" + " return result;\n" + "}}\n" + "\n" + "static PyObject * {class_name:s}_eq(PyObject *me, PyObject *other, int op) {{\n" + " py{class_name:s} *self = (py{class_name:s} *)me;\n" + " int other_int = PyLong_AsLong(other);\n" + " int my_int = 0;\n" + " PyObject *result = Py_False;\n" + "\n" + " if(CheckError(EZero)) {{\n" + " my_int = PyLong_AsLong(self->value);\n" + " switch(op) {{\n" + " case Py_EQ:\n" + " result = my_int == other_int? Py_True: Py_False;\n" + " break;\n" + " case Py_NE:\n" + " result = my_int != other_int? Py_True: Py_False;\n" + " break;\n" + " default:\n" + " return Py_NotImplemented;\n" + " }}\n" + " }} else {{\n" + " return NULL;\n" + " }}\n" + "\n" + " ClearError();\n" + "\n" + " Py_IncRef(result);\n" + " return result;\n" + "}}\n" + "\n").format(**values_dict)) + + +class Enum(StructGenerator): + def __init__(self, name, module): + super(Enum, self).__init__(name, module) + self.values = [] + self.name = name + self.attributes = None + self.active = True + + def get_string(self): + """Retrieves a string representation.""" + result = "Enum {0:s}:\n".format(self.name) + for attr in self.values: + result += " {0:s}\n".format(attr) + + return result + + def prepare(self): + self.constructor = EnumConstructor( + self.class_name, self.base_class_name, "Con", [], "void", + myclass=self) + StructGenerator.prepare(self) + + def struct(self, out): + values_dict = { + "class_name": self.class_name} + + out.write(( + "\n" + "typedef struct {{\n" + " PyObject_HEAD\n" + " PyObject *value;\n" + "}} py{class_name:s};\n" + "\n" + "PyObject *{class_name:s}_Dict_lookup;\n" + "PyObject *{class_name:s}_rev_lookup;\n").format( + **values_dict)) + + def PyGetSetDef(self, out): + out.write(( + "static PyGetSetDef {0:s}_get_set_definitions[] = {{\n" + " {{NULL, NULL, NULL, NULL, NULL}} /* Sentinel */\n" + "}};\n" + "\n").format(self.class_name)) + + def PyMethodDef(self, out): + out.write(( + "static PyMethodDef {0:s}_methods[] = {{\n" + " {{NULL, NULL, 0, NULL}} /* Sentinel */\n" + "}};\n" + "\n").format(self.class_name)) + + def numeric_protocol_nonzero(self): + pass + + def numeric_protocol_int(self): + values_dict = { + "class_name": self.class_name} + + return ( + "static PyObject *{class_name:s}_int(py{class_name:s} *self) {{\n" + " Py_IncRef(self->value);\n" + " return self->value;\n" + "}}\n").format(**values_dict) + + def initialise(self): + values_dict = { + "class_name": self.class_name} + + result = ( + "{class_name:s}_Dict_lookup = PyDict_New();\n" + "{class_name:s}_rev_lookup = PyDict_New();\n").format( + **values_dict) + + if self.values: + result += ( + "{\n" + " PyObject *integer_object = NULL;\n" + " PyObject *string_object = NULL;\n") + + for attr in self.values: + values_dict = { + "class_name": self.class_name, + "value": attr} + + result += ( + " integer_object = PyLong_FromLong({value:s});\n" + "\n" + "#if PY_MAJOR_VERSION >= 3\n" + " string_object = PyUnicode_FromString(\"{value:s}\");\n" + "#else\n" + " string_object = PyString_FromString(\"{value:s}\");\n" + "#endif\n" + " PyDict_SetItem({class_name:s}_Dict_lookup, string_object, integer_object);\n" + " PyDict_SetItem({class_name:s}_rev_lookup, integer_object, string_object);\n" + " Py_DecRef(integer_object);\n" + " Py_DecRef(string_object);\n" + "\n").format(**values_dict) + + result += "}\n" + + return result + + +class EnumType(Integer): + buildstr = "i" + + def __init__(self, name, type, *args, **kwargs): + super(EnumType, self).__init__(name, type, *args, **kwargs) + self.type = type + + def definition(self, default=None, **kwargs): + # Force the enum to be an int just in case the compiler chooses + # a random size. + if default: + return " int {0:s} = {1:s};\n".format(self.name, default) + else: + return " int UNUSED {0:s} = 0;\n".format(self.name) + + def to_python_object(self, name=None, result="Py_result", **kwargs): + name = name or self.name + return ( + "PyErr_Clear();\n" + "{0:s} = PyObject_CallMethod(g_module, \"{1:s}\", \"K\", (uint64_t){2:s});\n").format( + result, self.type, name) + + def pre_call(self, method, **kwargs): + method.error_set = True + + values_dict = { + "name": self.name, + "type": self.type} + + return ( + "/* Check if the integer passed is actually a valid member\n" + " * of the enum. Enum value of 0 is always allowed.\n" + " */\n" + "if({name:s}) {{\n" + " PyObject *py_{name:s} = NULL;\n" + " PyObject *tmp = NULL;\n" + "\n" + " py_{name:s} = PyLong_FromLong({name:s});\n" + " tmp = PyDict_GetItem({type:s}_rev_lookup, py_{name:s});\n" + "\n" + " Py_DecRef(py_{name:s});\n" + " if(!tmp) {{\n" + " PyErr_Format(PyExc_RuntimeError, \"value %lu is not valid for Enum {type:s} of arg '{name:s}'\", (unsigned long){name:s});\n" + " goto on_error;\n" + " }}\n" + "}}\n").format(**values_dict) + + +class HeaderParser(lexer.SelfFeederMixIn): + tokens = [ + ["INITIAL", r"#define\s+", "PUSH_STATE", "DEFINE"], + ["DEFINE", r"([A-Za-z_0-9]+)\s+[^\n]+", "DEFINE,POP_STATE", None], + ["DEFINE", r"\n", "POP_STATE", None], + # Ignore macros with args + ["DEFINE", r"\([^\n]+", "POP_STATE", None], + + # Recognize ansi c comments + [".", r"/\*(.)", "PUSH_STATE", "COMMENT"], + ["COMMENT", r"(.+?)\*/\s+", "COMMENT_END,POP_STATE", None], + ["COMMENT", r"(.+)", "COMMENT", None], + + # And c++ comments + [".", r"//([^\n]+)", "COMMENT", None], + + # An empty line clears the current comment + [".", r"\r?\n\r?\n", "CLEAR_COMMENT", None], + + # Ignore whitespace + [".", r"\s+", "SPACE", None], + [".", r"\\\n", "SPACE", None], + + # Recognize CLASS() definitions + ["INITIAL", r"^([A-Z]+)?\s*CLASS\(([A-Z_a-z0-9]+)\s*,\s*([A-Z_a-z0-9]+)\)", + "PUSH_STATE,CLASS_START", "CLASS"], + + ["CLASS", r"^\s*(FOREIGN|ABSTRACT|PRIVATE)?([0-9A-Z_a-z ]+( |\*))METHOD\(([A-Z_a-z0-9]+),\s*([A-Z_a-z0-9]+),?", + "PUSH_STATE,METHOD_START", "METHOD"], + ["METHOD", r"\s*([0-9A-Z a-z_]+\s+\*?\*?)([0-9A-Za-z_]+),?", "METHOD_ARG", None], + ["METHOD", r"\);", "POP_STATE,METHOD_END", None], + + ["CLASS", r"^\s*(FOREIGN|ABSTRACT)?([0-9A-Z_a-z ]+\s+\*?)\s*([A-Z_a-z0-9]+)\s*;", + "CLASS_ATTRIBUTE", None], + ["CLASS", "END_CLASS", "END_CLASS,POP_STATE", None], + + # Recognize struct definitions (With name) + ["INITIAL", "([A-Z_a-z0-9 ]+)?struct\s+([A-Z_a-z0-9]+)\s+{", + "PUSH_STATE,STRUCT_START", "STRUCT"], + + # Without name (using typedef) + ["INITIAL", "typedef\s+struct\s+{", + "PUSH_STATE,TYPEDEF_STRUCT_START", "STRUCT"], + + ["STRUCT", r"^\s*([0-9A-Z_a-z ]+\s+\*?)\s*([A-Z_a-z0-9]+)(?:\[([A-Z_a-z0-9]+)\])?\s*;", + "STRUCT_ATTRIBUTE", None], + + ["STRUCT", r"^\s*([0-9A-Z_a-z ]+)\*\s+([A-Z_a-z0-9]+)\s*;", + "STRUCT_ATTRIBUTE_PTR", None], + + # Struct ended with typedef + ["STRUCT", "}\s+([0-9A-Za-z_]+);", "POP_STATE,TYPEDEF_STRUCT_END", None], + ["STRUCT", "}", "POP_STATE,STRUCT_END", None], + + # Handle recursive struct or union definition (At the moment + # we cant handle them at all) + ["(RECURSIVE_)?STRUCT", "(struct|union)\s+([_A-Za-z0-9]+)?\s*{", "PUSH_STATE", "RECURSIVE_STRUCT"], + ["RECURSIVE_STRUCT", "}\s+[0-9A-Za-z]+", "POP_STATE", None], + + # Process enums (2 forms - named and typedefed) + ["INITIAL", r"enum\s+([0-9A-Za-z_]+)\s+{", "PUSH_STATE,ENUM_START", "ENUM"], + # Unnamed + ["INITIAL", r"typedef\s+enum\s+{", "PUSH_STATE,TYPEDEF_ENUM_START", "ENUM"], + ["ENUM", r"([0-9A-Za-z_]+)\s+=[^\n]+", "ENUM_VALUE", None], + + # Typedefed ending + ["ENUM", r"}\s+([0-9A-Za-z_]+);", "POP_STATE,TYPEDEFED_ENUM_END", None], + ["ENUM", r"}", "POP_STATE,ENUM_END", None], + + ["INITIAL", r"BIND_STRUCT\(([0-9A-Za-z_ \*]+)\)", "BIND_STRUCT", None], + + # A simple typedef of one type for another type: + ["INITIAL", r"typedef ([A-Za-z_0-9]+) +([^;]+);", "SIMPLE_TYPEDEF", None], + + # Handle proxied directives + ["INITIAL", r"PXXROXY_CLASS\(([A-Za-z0-9_]+)\)", "PROXY_CLASS", None], + + ] + + def __init__(self, name, verbose=1, base=""): + self.module = Module(name) + self.base = base + super(HeaderParser, self).__init__(verbose=0) + + file_object = io.BytesIO( + b"// Base object\n" + b"CLASS(Object, Obj)\n" + b"END_CLASS\n") + self.parse_fd(file_object) + + current_comment = "" + + def COMMENT(self, t, m): + self.current_comment += m.group(1) + "\n" + + def COMMENT_END(self, t, m): + self.current_comment += m.group(1) + + def CLEAR_COMMENT(self, t, m): + self.current_comment = "" + + def DEFINE(self, t, m): + line = m.group(0) + line = line.split("/*")[0] + if "\"" in line: + type = "string" + else: + type = "integer" + + name = m.group(1).strip() + if (len(name) > 3 and name[0] != "_" and name == name.upper() and + name not in self.module.constants_blacklist): + self.module.add_constant(name, type) + + current_class = None + + def CLASS_START(self, t, m): + class_name = m.group(2).strip() + base_class_name = m.group(3).strip() + + try: + self.current_class = self.module.classes[base_class_name].clone(class_name) + except (KeyError, AttributeError): + log("Base class {0:s} is not defined !!!!".format(base_class_name)) + self.current_class = ClassGenerator(class_name, base_class_name, self.module) + + self.current_class.docstring = self.current_comment + self.current_class.modifier.add(m.group(1)) + self.module.add_class(self.current_class, Wrapper) + identifier = "{0:s} *".format(class_name) + type_dispatcher[identifier] = PointerWrapper + + current_method = None + + def METHOD_START(self, t, m): + return_type = m.group(2).strip() + method_name = m.group(5).strip() + modifier = m.group(1) or "" + + if "PRIVATE" in modifier: + return + + # Is it a regular method or a constructor? + self.current_method = Method + if (return_type == self.current_class.class_name and + method_name.startswith("Con")): + self.current_method = ConstructorMethod + elif method_name == "iternext": + self.current_method = IteratorMethod + self.current_class.modifier.add("ITERATOR") + elif method_name == "__iter__": + self.current_method = SelfIteratorMethod + self.current_class.modifier.add("SELF_ITER") + elif method_name == "__str__": + self.current_class.modifier.add("TP_STR") + + self.current_method = self.current_method( + self.current_class.class_name, + self.current_class.base_class_name, + method_name, [], return_type, + myclass=self.current_class) + self.current_method.docstring = self.current_comment + self.current_method.modifier = modifier + + def METHOD_ARG(self, t, m): + name = m.group(2).strip() + type = m.group(1).strip() + if self.current_method: + self.current_method.add_arg(type, name) + + def METHOD_END(self, t, m): + if not self.current_method: + return + + if isinstance(self.current_method, ConstructorMethod): + self.current_class.constructor = self.current_method + else: + found = False + for i in range(len(self.current_class.methods)): + # Try to replace existing methods with this new method + method = self.current_class.methods[i] + if method.name == self.current_method.name: + self.current_class.methods[i] = self.current_method + self.current_method = None + return + + # Method does not exist, just add to the end + self.current_class.methods.append(self.current_method) + + self.current_method = None + + def CLASS_ATTRIBUTE(self, t, m): + modifier = m.group(1) or "" + type = m.group(2).strip() + name = m.group(3).strip() + self.current_class.add_attribute(name, type, modifier) + + def END_CLASS(self, t, m): + self.current_class = None + + current_struct = None + + def STRUCT_START(self, t, m): + self.current_struct = StructGenerator(m.group(2).strip(), self.module) + self.current_struct.docstring = self.current_comment + self.current_struct.modifier.add(m.group(1)) + + def TYPEDEF_STRUCT_START(self, t, m): + self.current_struct = StructGenerator(None, self.module) + self.current_struct.docstring = self.current_comment + + def STRUCT_ATTRIBUTE(self, t, m): + name = m.group(2).strip() + type = m.group(1).strip() + array_size = m.group(3) + if array_size is not None: + array_size = array_size.strip() + self.current_struct.add_attribute(name, type, "", array_size=array_size) + else: + self.current_struct.add_attribute(name, type, "") + + def STRUCT_ATTRIBUTE_PTR(self, t, m): + type = "{0:s} *".format(m.group(1).strip()) + name = m.group(2).strip() + self.current_struct.add_attribute(name, type, "") + + def STRUCT_END(self, t, m): + self.module.add_class(self.current_struct, StructWrapper) + identifier = "{0:s} *".format(self.current_struct.class_name) + type_dispatcher[identifier] = PointerStructWrapper + self.current_struct = None + + def TYPEDEF_STRUCT_END(self, t, m): + self.current_struct.class_name = m.group(1).strip() + + self.STRUCT_END(t, m) + + current_enum = None + + def ENUM_START(self, t, m): + self.current_enum = Enum(m.group(1).strip(), self.module) + + def TYPEDEF_ENUM_START(self, t, m): + self.current_enum = Enum(None, self.module) + + def ENUM_VALUE(self, t, m): + self.current_enum.values.append(m.group(1).strip()) + + def ENUM_END(self, t, m): + self.module.classes[self.current_enum.name] = self.current_enum + + # For now we just treat enums as an integer, and also add + # them to the constant table. In future it would be nice to + # have them as a proper Python object so we can override + # __unicode__, __str__ and __int__. + for attr in self.current_enum.values: + self.module.add_constant(attr, "integer") + + # type_dispatcher[self.current_enum.name] = Integer + type_dispatcher[self.current_enum.name] = EnumType + self.current_enum = None + + def TYPEDEFED_ENUM_END(self, t, m): + self.current_enum.name = self.current_enum.class_name = m.group(1) + self.ENUM_END(t, m) + + def BIND_STRUCT(self, t, m): + self.module.active_structs.add(m.group(1)) + self.module.active_structs.add("{0:s} *".format(m.group(1))) + + def SIMPLE_TYPEDEF(self, t, m): + # We basically add a new type as a copy of the old + # type + old, new = m.group(1).strip(), m.group(2).strip() + if old in type_dispatcher: + type_dispatcher[new] = type_dispatcher[old] + + def PROXY_CLASS(self, t, m): + base_class_name = m.group(1).strip() + class_name = "Proxied{0:s}".format(base_class_name) + try: + proxied_class = self.module.classes[base_class_name] + except KeyError: + raise RuntimeError(( + "Need to create a proxy for {0:s} but it has not been " + "defined (yet). You must place the PROXIED_CLASS() " + "instruction after the class definition").format( + base_class_name)) + current_class = ProxyClassGenerator(class_name, + base_class_name, self.module) + # self.current_class.constructor.args += proxied_class.constructor.args + current_class.docstring = self.current_comment + + # Create proxies for all these methods + for method in proxied_class.methods: + if method.name[0] != "_": + current_class.methods.append(ProxiedMethod(method, current_class)) + + self.module.add_class(current_class, Wrapper) + + def parse_filenames(self, filenames): + for f in filenames: + self._parse(f) + + # Second pass + for f in filenames: + self._parse(f) + + def _parse(self, filename): + file_object = open(filename, "rb") + self.parse_fd(file_object) + file_object.close() + + if filename not in self.module.files: + if filename.startswith(self.base): + filename = filename[len(self.base):] + + self.module.headers += "#include \"{0:s}\"\n".format(filename) + self.module.files.append(filename) + + def write(self, out): + try: + self.module.write(out) + except: + # pdb.post_mortem() + raise + + def write_headers(self): + pass + # pdb.set_trace() + + +if __name__ == "__main__": + p = HeaderParser("pytsk3", verbose=1) + for arg in sys.argv[1:]: + p.parse_fd(open(arg, "rb")) + + log("second parse") + for arg in sys.argv[1:]: + p.parse_fd(open(arg, "rb")) + + p.write(sys.stdout) + p.write_headers() |