mirror of
https://github.com/idapython/src
synced 2026-06-08 14:47:00 +00:00
123 lines
4.8 KiB
Python
123 lines
4.8 KiB
Python
|
|
import ast
|
|
|
|
import pypasses
|
|
|
|
# In the case of C++ functions/methods with default paramater values:
|
|
#
|
|
# tinfo_code_t tinfo_t::del_edm_by_value(
|
|
# uint64 value,
|
|
# uint etf_flags=0,
|
|
# bmask64_t bmask=DEFMASK64,
|
|
# uchar serial=0);
|
|
#
|
|
# SWiG will treat every single default-valued argument as an overload
|
|
# of the function, create multiple C++ implementations, and perform
|
|
# dispatch there.
|
|
# On the Python side, SWiG will create:
|
|
#
|
|
# class tinfo_t(...):
|
|
# ...
|
|
# def del_edm_by_value(self, *args):
|
|
# return _ida_typeinf.tinfo_t_del_edm_by_value(*args)
|
|
#
|
|
# While that looks good, it is a problem for us because this is not "real"
|
|
# overloading, and we would prefer the method to have the signature:
|
|
#
|
|
# def del_edm_by_value(self, value: int, etf_flags: int=0, bmask: int=DEFMASK64, serial: int=0):
|
|
#
|
|
# ...and that's exactly how we'll define it in the overrides.
|
|
# (Naturally, that means this pass needs to happen _after_ the prototype-overriding/fixing one(s))
|
|
#
|
|
# ---
|
|
#
|
|
# However, the _body_ of `del_edm_by_value` in the Python side, will
|
|
# remain:
|
|
#
|
|
# def del_edm_by_value(self, value: int, etf_flags: int=0, bmask: int=DEFMASK64, serial: int=0):
|
|
# return _ida_typeinf.tinfo_t_del_edm_by_value(*args)
|
|
#
|
|
# ...which will cause an error.
|
|
#
|
|
# We want to spot such occurrences, and prepare an `args`
|
|
# tuple with all the arguments:
|
|
#
|
|
# def del_edm_by_value(self, value: int, etf_flags: int=0, bmask: int=DEFMASK64, serial: int=0):
|
|
# args = value, etf_flags, bmask, serial
|
|
# return _ida_typeinf.tinfo_t_del_edm_by_value(*args)
|
|
#
|
|
|
|
def process(tree, opts, top_logger):
|
|
|
|
class source_transformer_t(pypasses.base_transformer_t):
|
|
|
|
def _is_string_literal(self, node):
|
|
if isinstance(node, ast.Expr):
|
|
if isinstance(node.value, ast.Constant):
|
|
if isinstance(node.value.value, str):
|
|
return True
|
|
return False
|
|
|
|
def visit_FunctionDef(self, node):
|
|
|
|
logger = top_logger.getChild(node.name)
|
|
|
|
# We want to match things such as:
|
|
#
|
|
# def del_edm_by_value(self, value: int, etf_flags: int=0, bmask: int=DEFMASK64, serial: int=0)
|
|
# return _ida_typeinf.tinfo_t_get_edm_by_value(self, *args)
|
|
#
|
|
# def my_function(something: sometype, otherthing: othertype=-2)
|
|
# return ...(*args)
|
|
#
|
|
has_self = False
|
|
real_arg = None
|
|
for arg in node.args.args:
|
|
if arg.arg == "self":
|
|
has_self = True
|
|
else:
|
|
logger.debug(f"Found 'real' argument \"{arg.arg}\". Needs investigating.")
|
|
real_arg = arg
|
|
break
|
|
|
|
if real_arg:
|
|
#
|
|
# Now we need to see if the entire function body
|
|
# consists solely of a call to a function, with
|
|
# an `*args` expression
|
|
#
|
|
retexpr_idx = 0
|
|
if self._is_string_literal(node.body[0]): # docstring
|
|
logger.debug("Ignoring docstring during body investigation")
|
|
retexpr_idx = 1
|
|
|
|
if isinstance(node.body[retexpr_idx], ast.Return):
|
|
_return = node.body[retexpr_idx]
|
|
if isinstance(_return.value, ast.Call):
|
|
args = _return.value.args
|
|
logger.debug(f"Arguments to function call: {args}")
|
|
if args:
|
|
if isinstance(args[0], ast.Name) and args[0].id == "self":
|
|
logger.debug(f"Dropping \"{args[0].id}\" from the list of arguments to the call")
|
|
args = args[1:]
|
|
if args:
|
|
if isinstance(args[0], ast.Starred) and args[0].value.id == "args":
|
|
logger.debug(f"Call consists of a single starred \"{args[0].value.id}\". Let's create it!")
|
|
targets = [ast.Name("args", ast.Store())]
|
|
elts = []
|
|
for arg in node.args.args:
|
|
if arg.arg != "self":
|
|
elts.append(ast.Name(arg.arg))
|
|
value = ast.Tuple(elts, ast.Load())
|
|
assign = ast.Assign(targets, value)
|
|
node.body.insert(retexpr_idx, assign)
|
|
# logger.debug(f">>>> {ast.dump(node, indent=4)}")
|
|
|
|
self.generic_visit(node)
|
|
return node
|
|
|
|
transformer = source_transformer_t(opts.idapython_module_name)
|
|
transformer.visit(tree)
|
|
|
|
return tree
|