From 01fd68035388eebf11c461c242d36fdd51aecd7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Thu, 17 Sep 2026 16:35:37 +0200 Subject: [PATCH] bindgen: Fix dropped constructors and out params A static function that makes the class it sits on was not read at all, so every named constructor in the API was missing. One that throws was then missing twice over: skipped for __init__ because it throws, and skipped as a factory for being the primary, leaving nothing to make the class with. An out parameter was dropped along with the value it carried, so a `gboolean` method that answers through one now binds as Optional, and any other out parameter is refused rather than bound as a result the caller cannot read. --- frida-bindgen | 2 +- frida/frida_bindgen/codegen.py | 68 +++++++++++++++++++++++++++++----- frida/frida_bindgen/model.py | 8 ++++ 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/frida-bindgen b/frida-bindgen index df17879..eace049 160000 --- a/frida-bindgen +++ b/frida-bindgen @@ -1 +1 @@ -Subproject commit df17879d92d2700a7f1228a03271ba07e2fb579b +Subproject commit eace04901ef6300429e8c343095297df042dba0e diff --git a/frida/frida_bindgen/codegen.py b/frida/frida_bindgen/codegen.py index 0effcb4..05cee3d 100644 --- a/frida/frida_bindgen/codegen.py +++ b/frida/frida_bindgen/codegen.py @@ -436,7 +436,7 @@ def generate_aio_async_method(method: Method, model: Model) -> Optional[str]: if method.return_value is not None: call = wrap_result(call, method.return_value.type, model) - ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) + ret = facade_return_type(method, model) return f""" async def {method.name}({signature}) -> {ret}: return {call}""" @@ -460,7 +460,7 @@ def generate_custom_facade_method( body = textwrap.indent(logic.strip(), " " * 8) keyword = "async def" if awaitable else "def" - ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) + ret = facade_return_type(method, model) return f""" {keyword} {method.name}({signature}) -> {ret}: {body} @@ -667,7 +667,7 @@ def generate_py_sync_method(method: Method, model: Model) -> Optional[str]: if method.return_value is not None: call = wrap_result(call, method.return_value.type, model) - ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) + ret = facade_return_type(method, model) return f""" def {method.name}({facade_signature(method, params)}) -> {ret}: return {call}""" @@ -703,7 +703,7 @@ def generate_py_async_method(method: Method, model: Model) -> Optional[str]: if method.return_value is not None: call = wrap_result(call, method.return_value.type, model) - ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) + ret = facade_return_type(method, model) return f""" def {method.name}({signature}) -> {ret}: return {call}""" @@ -793,8 +793,8 @@ def generate_pyi_class(otype: ObjectType, model: Model) -> List[str]: body = [] - primary = primary_constructor(otype) - if primary is not None and not primary.throws: + primary = inline_constructor(otype) + if primary is not None: params = pyi_parameters(primary.input_parameters, model) if params is not None: signature = ", ".join(["self"] + params) @@ -869,7 +869,13 @@ def pyi_method(method: Method, model: Model) -> Optional[str]: if params is None: return None signature = ", ".join(["self"] + params) - ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) + out = method.optional_out_parameter + if out is not None: + ret = f"Optional[{pyi_type(out.type, model)}]" + elif method.return_value is None: + ret = "None" + else: + ret = pyi_type(method.return_value.type, model) return f" def {method.name}({signature}) -> {ret}: ..." @@ -1371,6 +1377,11 @@ def generate_method(otype: ObjectType, method: Method) -> Optional[Tuple[str, st marshal = synchronous_method_return_marshal(method) if marshal is None: return None + out = method.optional_out_parameter + if out is not None: + marshal = build_return_marshal(out.type, method.object_type.model) + if marshal is None: + return None params = [] for param in method.input_parameters: sync_param = build_sync_param(param) @@ -1714,14 +1725,17 @@ def generate_synchronous_method(otype: ObjectType, method: Method, marshal: str, indent = " " * (len(otype.c_symbol_prefix) + len(method.name) + 3) handle = f"({otype.c_type} *) PY_GOBJECT_HANDLE (self)" returns_strv = method.return_value is not None and method.return_value.type.name == "utf8[]" + out = method.optional_out_parameter call_arg_list = [handle] + [p.call_arg for p in params] if returns_strv: call_arg_list.append("&retval_length") + if out is not None: + call_arg_list.append(f"&{out.name}") if method.throws: call_arg_list.append("&error") call = f"{method.c_identifier} ({', '.join(call_arg_list)})" - if not params and not method.throws and not returns_strv: + if not params and not method.throws and not returns_strv and out is None: if method.return_value is None: body = f" {call};\n\n PyFrida_RETURN_NONE;" else: @@ -1740,6 +1754,8 @@ static PyObject * decls.append(" GError * error = NULL;") if returns_strv: decls.append(" gint retval_length;") + if out is not None: + decls.append(f" {out_parameter_storage_type(out)} {out.name};") for p in params: decls += [f" {line}" for line in p.decl.splitlines()] @@ -1754,7 +1770,15 @@ static PyObject * pre = "".join(f"\n {p.pre}\n" for p in params if p.pre) - if method.return_value is None: + if out is not None: + invocation = f" gboolean found = {call};" + success = ( + " if (found)\n" + f" result = {marshal.format(value=out.name)};\n" + " else\n" + " {\n Py_IncRef (Py_None);\n result = Py_None;\n }" + ) + elif method.return_value is None: invocation = f" {call};" success = " Py_IncRef (Py_None);\n result = Py_None;" elif returns_strv: @@ -1799,6 +1823,22 @@ static PyObject * """ +def facade_return_type(method: Method, model: Model) -> str: + out = method.optional_out_parameter + if out is not None: + return f"Optional[{pyi_type(out.type, model)}]" + if method.return_value is None: + return "None" + return pyi_type(method.return_value.type, model) + + +def out_parameter_storage_type(param: Parameter) -> str: + """What the caller declares: the out parameter's own type, less the pointer.""" + c = param.type.c + assert c.endswith("*"), c + return c[:-1].strip() + + def synchronous_method_return_marshal(method: Method) -> Optional[str]: if method.is_async or method.is_property_accessor: return None @@ -2328,8 +2368,16 @@ def primary_constructor(otype: ObjectType) -> Optional[Procedure]: return next((c for c in otype.constructors if c.name == "new"), otype.constructors[0]) +def inline_constructor(otype: ObjectType) -> Optional[Procedure]: + """The constructor __init__ takes; one that throws is reached as a factory instead.""" + ctor = primary_constructor(otype) + if ctor is None or ctor.throws: + return None + return ctor + + def factory_constructors(otype: ObjectType) -> List[Procedure]: - primary = primary_constructor(otype) + primary = inline_constructor(otype) return [ ctor for ctor in otype.constructors diff --git a/frida/frida_bindgen/model.py b/frida/frida_bindgen/model.py index 77f8748..ab91e06 100644 --- a/frida/frida_bindgen/model.py +++ b/frida/frida_bindgen/model.py @@ -17,6 +17,10 @@ class Model(core.Model): class ObjectType(core.ObjectType): + @cached_property + def methods(self) -> List["Method"]: + return [m for m in super().methods if not m.out_parameters or m.optional_out_parameter is not None] + @cached_property def py_name(self) -> str: custom = self.customizations @@ -277,6 +281,7 @@ def _make_class( type_struct, parent, constructors, + functions, methods, properties, signals, @@ -291,6 +296,7 @@ def _make_class( type_struct, parent, constructors, + functions, methods, properties, signals, @@ -307,6 +313,7 @@ def _make_interface( type_struct, parent, constructors, + functions, methods, properties, signals, @@ -320,6 +327,7 @@ def _make_interface( type_struct, parent, constructors, + functions, methods, properties, signals,