""" Deploy code snips into swig interface files (c) Hex-Rays """ from __future__ import print_function import sys, re, os, glob major, minor, micro, _, _ = sys.version_info from argparse import ArgumentParser parser = ArgumentParser() parser.add_argument("-t", "--template", required=True) parser.add_argument("-o", "--output", required=True) parser.add_argument("-m", "--module", required=True) parser.add_argument("-w", "--pywraps", required=True) parser.add_argument("-d", "--interface-dependencies", type=str, required=True) parser.add_argument("-l", "--lifecycle-aware", default=False, action="store_true") parser.add_argument("-v", "--verbose", default=False, action="store_true") parser.add_argument("-x", "--xml-doc-directory", required=True) args = parser.parse_args() this_dir, _ = os.path.split(__file__) sys.path.append(this_dir) import doxygen_utils typemaps = [] # generate typemaps that will have to be injected for additional checks xml_tree = doxygen_utils.load_xml_for_module(args.xml_doc_directory, args.module, or_dummy=False) if xml_tree is not None: all_functions = doxygen_utils.get_toplevel_functions(xml_tree) for fun_node in all_functions: fun_name = doxygen_utils.get_single_child_element_text_contents(fun_node, "name") params = [] def reg_param(*args): params.append(args) doxygen_utils.for_each_param(fun_node, reg_param) def relevant_and_non_null(ptyp, desc): if ptyp.strip().startswith("qstring"): return False return (desc or "").lower().find("not be null") > -1 for name, ptyp, desc in params: if relevant_and_non_null(ptyp, desc): # generate 'check' typemap signature = [] body = [] for idx, tpl in enumerate(params): name, ptyp, desc = tpl signature.append("%s %s" % (ptyp, name or "")) if relevant_and_non_null(ptyp, desc): body.append("if ( $%d == nullptr )" % (idx+1)) body.append(""" SWIG_exception_fail(SWIG_ValueError, "invalid null reference in method '$symname', argument $argnum of type '$%d_type'");""" % (idx+1)) pass typemaps.append("%%typemap(check) (%s)" % ", ".join(signature)) typemaps.append("{") typemaps.extend(body) typemaps.append("}") break else: if args.module not in ["idaapi", "idc"]: raise Exception("Missing XML file for module '%s'" % args.module) # creates a regular expression def make_re(tag, module, prefix): s = r'%(p)s<%(tag)s\(%(m)s\)>(.+?)%(p)s' % {'m': module, 'tag': tag, 'p': prefix} return (s, re.compile(s, re.DOTALL)) def convert_path(path_in): parts = path_in.split('/') return os.sep.join(parts) def apply_tags(template_str, input_str, tags, verbose, path): for desc, (expr_str, expr) in tags: # find source pattern matches = expr.findall(input_str) if not matches: if verbose: print("Failed to match <%s> source expression against '%s', skipping...!" % (desc, expr_str)) continue # find pattern in destination dest = expr.search(template_str) if not dest: raise Exception("Found <%s> for module '%s' in input (%s), but failed to match in destination" % ( desc, expr_str, path)) # accumulate all the strings to be replaced replaces = [] for src in matches: replaces.append(src) template_str = template_str[:dest.start(1)] + "\n".join(replaces) + template_str[dest.end(1):] return template_str def deploy(module, template, output, pywraps, iface_deps, lifecycle_aware, verbose): template = convert_path(template) output = convert_path(output) # read template file with open(template) as fin: template_str = fin.read() # read input file(s) all_files = glob.glob(os.path.join(pywraps, "py_%s.*" % module)) + \ glob.glob(os.path.join(pywraps, "py_%s_*.*" % module)) for path in all_files: fname = os.path.basename(path) tagname, _ = os.path.splitext(fname) if verbose: print("Considering file: '%s' (tagname: '%s')" % (path, tagname)) # create regular expressions tags = ( ('pycode', make_re('pycode', tagname, '#')), ('code', make_re('code', tagname, '//')), ('inline', make_re('inline', tagname, '//')), ('decls', make_re('decls', tagname, '//')), ('init', make_re('init', tagname, '//')), ) with open(path) as fin: input_str = fin.read() template_str = apply_tags(template_str, input_str, tags, verbose, path) # synthetic tags if typemaps: typemaps_str = "\n".join([ "//" % module, "\n".join(typemaps), "//" % module, ]) synth_tags = ( ('typemaps', make_re('typemaps', module, '//')), ) template_str = apply_tags(template_str, typemaps_str, synth_tags, verbose, "[generated]") # write output file with open(output, 'w') as f: f.write("""%module(directors="1",threads="1") {0}\n""".format("ida_%s" % module)) f.write("#ifndef IDA_MODULE_DEFINED\n") f.write(""" #define IDA_MODULE_%s\n""" % module.upper()) f.write("#define IDA_MODULE_DEFINED\n") f.write("#endif // IDA_MODULE_DEFINED\n") for dep in [module] + iface_deps.split(","): if len(dep): f.write("#ifndef HAS_DEP_ON_INTERFACE_%s\n" % dep.upper()) f.write(" #define HAS_DEP_ON_INTERFACE_%s\n" % dep.upper()) f.write("#endif\n") f.write("%include \"header.i\"\n") f.write(template_str) if lifecycle_aware: f.write(""" %%init %%{ { module_callbacks_t module_lfc; module_lfc.init = ida_%s_init; module_lfc.term = ida_%s_term; module_lfc.closebase = ida_%s_closebase; register_module_lifecycle_callbacks(module_lfc); } %%} """ % (module, module, module)) deploy( args.module, args.template, args.output, args.pywraps, args.interface_dependencies, args.lifecycle_aware, args.verbose)