mirror of
https://github.com/frida/frida-python
synced 2026-06-08 14:16:17 +00:00
2170 lines
68 KiB
Python
2170 lines
68 KiB
Python
from __future__ import annotations
|
|
|
|
import textwrap
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional
|
|
|
|
from .model import (ClassObjectType, CustomTypeKind, Enumeration,
|
|
InterfaceObjectType, Method, Model, ObjectType, Parameter,
|
|
Procedure, Property, Signal, Tuple, to_pascal_case, to_snake_case)
|
|
|
|
ASSETS_DIR = Path(__file__).resolve().parent / "assets"
|
|
CODEGEN_HELPERS_TS = (ASSETS_DIR / "codegen_helpers.ts").read_text(encoding="utf-8")
|
|
CODEGEN_MACROS_H = (ASSETS_DIR / "codegen_macros.h").read_text(encoding="utf-8")
|
|
CODEGEN_TYPEDEFS_H = (ASSETS_DIR / "codegen_typedefs.h").read_text(encoding="utf-8")
|
|
CODEGEN_STRUCTS_H = (ASSETS_DIR / "codegen_structs.h").read_text(encoding="utf-8")
|
|
CODEGEN_PROTOTYPES_H = (ASSETS_DIR / "codegen_prototypes.h").read_text(encoding="utf-8")
|
|
CODEGEN_GOBJECT_PROTOTYPES = (ASSETS_DIR / "codegen_gobject_prototypes.h").read_text(encoding="utf-8")
|
|
CODEGEN_GOBJECT_GLOBALS = (ASSETS_DIR / "codegen_gobject_globals.c").read_text(encoding="utf-8")
|
|
CODEGEN_GOBJECT_METHODS = (ASSETS_DIR / "codegen_gobject_methods.c").read_text(encoding="utf-8")
|
|
CODEGEN_HELPERS_C = (ASSETS_DIR / "codegen_helpers.c").read_text(encoding="utf-8")
|
|
|
|
|
|
def generate_all(model: Model) -> Dict[str, str]:
|
|
return {
|
|
"py": generate_py(model),
|
|
"pyi": generate_extension_pyi(model),
|
|
"c": generate_extension_c(model),
|
|
}
|
|
|
|
|
|
def generate_py(model: Model) -> str:
|
|
type_imports = []
|
|
for t in model.public_types.values():
|
|
type_imports.append(f"{t.js_name} as _{t.js_name}")
|
|
if isinstance(t, ObjectType):
|
|
for s in t.signals:
|
|
type_imports.append(f"{s.handler_type_name} as _{s.handler_type_name}")
|
|
prefixed_name = s.prefixed_handler_type_name
|
|
if prefixed_name != s.handler_type_name:
|
|
type_imports.append(f"{prefixed_name} as _{prefixed_name}")
|
|
if isinstance(t, InterfaceObjectType) and t.has_abstract_base:
|
|
type_imports.append(f"Abstract{t.js_name} as _Abstract{t.js_name}")
|
|
for name in model.customizations.custom_types.keys():
|
|
type_imports.append(f"{name} as _{name}")
|
|
|
|
lines = [
|
|
'import bindings from "bindings";',
|
|
"import type {",
|
|
" FridaBinding,",
|
|
*[f" {i}," for i in type_imports],
|
|
" Signal,",
|
|
" SignalHandler,",
|
|
'} from "./frida_binding.d.ts";',
|
|
'import util from "util";',
|
|
*model.customizations.helper_imports,
|
|
"",
|
|
"const { inspect } = util;",
|
|
]
|
|
|
|
lines.append(
|
|
"""
|
|
const binding: FridaBinding = bindings({
|
|
bindings: "frida_binding",
|
|
try: [
|
|
["module_root", "build", "bindings"],
|
|
[process.cwd(), "bindings"],
|
|
]
|
|
});"""
|
|
)
|
|
|
|
for name, cust in model.customizations.custom_types.items():
|
|
if cust.kind == CustomTypeKind.ENUM:
|
|
lines += [
|
|
"",
|
|
f"enum {name}Impl {{",
|
|
indent_ts_code(cust.typing.strip(), 1),
|
|
"}",
|
|
f"(binding as any).{name} = {name}Impl;",
|
|
f"export const {name} = binding.{name};",
|
|
]
|
|
|
|
lines += [
|
|
"",
|
|
"{",
|
|
indent_ts_code(model.customizations.helper_code.rstrip(), 1),
|
|
indent_ts_code(CODEGEN_HELPERS_TS.rstrip(), 1),
|
|
]
|
|
|
|
ol = []
|
|
for otype in model.object_types.values():
|
|
if not otype.is_frida_options and not otype.is_frida_list:
|
|
prop_names = ", ".join([f'"{prop.js_name}"' for prop in otype.properties])
|
|
ol += [
|
|
"",
|
|
f"(binding as any).{otype.prefixed_js_name}.prototype[inspect.custom] = function (depth: number, options: util.InspectOptionsStylized): string {{",
|
|
f' return inspectWrapper(this, "{otype.js_name}", [{prop_names}], depth, options);',
|
|
"};",
|
|
]
|
|
|
|
if not otype.needs_wrapper:
|
|
continue
|
|
|
|
ol += [
|
|
"",
|
|
f"class {otype.js_name} extends binding._{otype.js_name} {{",
|
|
]
|
|
|
|
num_members = 0
|
|
|
|
custom_code = otype.customizations.custom_code
|
|
if custom_code is not None:
|
|
for declaration in custom_code.declarations:
|
|
if num_members != 0:
|
|
ol.append("")
|
|
ol.append(indent_ts_code(declaration.code.strip(), 1))
|
|
num_members += 1
|
|
|
|
for method in custom_code.methods:
|
|
if num_members != 0:
|
|
ol.append("")
|
|
ol.append(indent_ts_code(method.code.strip(), 1))
|
|
num_members += 1
|
|
|
|
ctor = otype.constructors[0] if otype.constructors else None
|
|
if ctor is not None and ctor.needs_wrapper:
|
|
ol.append(f" constructor({', '.join(ctor.param_typings)}) {{")
|
|
|
|
custom_logic = ctor.customizations.custom_logic
|
|
if custom_logic is not None:
|
|
ol += [
|
|
indent_ts_code(custom_logic.strip(), 2),
|
|
"",
|
|
]
|
|
|
|
ol += [
|
|
f" super({', '.join(param.js_name for param in ctor.parameters)});",
|
|
" }",
|
|
]
|
|
|
|
for method in otype.wrapped_methods:
|
|
custom = method.customizations
|
|
|
|
maybe_async = "async " if method.is_async else ""
|
|
maybe_await = "await " if method.is_async else ""
|
|
|
|
if num_members != 0:
|
|
ol.append("")
|
|
ol.append(
|
|
f" {maybe_async}{method.js_name}({', '.join(method.param_typings)}): {method.return_typing} {{"
|
|
)
|
|
|
|
custom_logic = custom.custom_logic
|
|
if custom_logic is not None:
|
|
ol += [
|
|
indent_ts_code(custom_logic.strip(), 2),
|
|
"",
|
|
]
|
|
|
|
return_capture = "const result = " if method.return_typing != "void" else ""
|
|
|
|
ol.append(
|
|
f" {return_capture}{maybe_await}this._{method.js_name}({', '.join(param.js_name for param in method.input_parameters)});"
|
|
)
|
|
|
|
if return_capture:
|
|
ol.append("")
|
|
return_wrapper = custom.return_wrapper
|
|
if return_wrapper is not None:
|
|
if return_wrapper.startswith("as "):
|
|
ol.append(f" return result {return_wrapper};")
|
|
else:
|
|
ol.append(f" return {return_wrapper}(result);")
|
|
else:
|
|
ol.append(" return result;")
|
|
|
|
ol.append(" }")
|
|
|
|
num_members += 1
|
|
|
|
for signal in otype.wrapped_signals:
|
|
custom = signal.customizations
|
|
|
|
option_lines = []
|
|
|
|
transform = custom.transform
|
|
if transform is not None:
|
|
param_typings = []
|
|
transformed_params = []
|
|
for i, param in enumerate(signal.parameters):
|
|
if transform is not None and i in transform:
|
|
transformed_name_and_type, transform_function = transform[i]
|
|
param_typings.append(transformed_name_and_type)
|
|
if transform_function is not None:
|
|
transformed_params.append(
|
|
f"{transform_function}({param.js_name})"
|
|
)
|
|
else:
|
|
transformed_params.append(param.js_name)
|
|
else:
|
|
param_typings.append(param.typing)
|
|
transformed_params.append(param.js_name)
|
|
|
|
transformed_params_str = ", ".join(transformed_params)
|
|
|
|
option_lines += [
|
|
f"transform({', '.join(p.js_name for p in signal.parameters)}) {{",
|
|
f" return [{transformed_params_str}];",
|
|
"},",
|
|
]
|
|
|
|
intercept = custom.intercept
|
|
if intercept is not None:
|
|
option_lines.append(f"intercept: {intercept},")
|
|
|
|
if num_members != 0:
|
|
ol.append("")
|
|
option_indent = 8 * " "
|
|
ol += [
|
|
f" {signal.js_name}: Signal<_{signal.handler_type_name}> = new SignalWrapper<__{signal.handler_type_name}, _{signal.handler_type_name}>(this._{signal.js_name}, {{",
|
|
*[option_indent + line for line in option_lines],
|
|
" });",
|
|
]
|
|
|
|
num_members += 1
|
|
|
|
ol += [
|
|
"}",
|
|
"",
|
|
f"binding.{otype.js_name} = {otype.js_name};",
|
|
]
|
|
|
|
lines += [
|
|
indent_ts_code("\n".join(ol), 1),
|
|
"}",
|
|
"",
|
|
"binding.commitConstructors();",
|
|
"",
|
|
"export const {",
|
|
]
|
|
for t in model.public_types.values():
|
|
if isinstance(t, InterfaceObjectType):
|
|
if t.has_abstract_base:
|
|
lines.append(f" Abstract{t.js_name},")
|
|
continue
|
|
lines.append(f" {t.js_name},")
|
|
lines += [
|
|
"} = binding;",
|
|
"",
|
|
"const frida = {",
|
|
]
|
|
for t in model.public_types.values():
|
|
if isinstance(t, InterfaceObjectType):
|
|
if t.has_abstract_base:
|
|
lines.append(f" Abstract{t.js_name},")
|
|
continue
|
|
lines.append(f" {t.js_name},")
|
|
for name, cust in model.customizations.custom_types.items():
|
|
if cust.kind == CustomTypeKind.ENUM:
|
|
lines.append(f" {name},")
|
|
lines += [
|
|
*[f" {e}," for e in model.customizations.facade_exports],
|
|
"} as const;",
|
|
"",
|
|
"export default frida;",
|
|
]
|
|
|
|
type_exports = []
|
|
for t in model.public_types.values():
|
|
type_exports.append(f"export type {t.js_name} = _{t.js_name};")
|
|
if isinstance(t, ObjectType):
|
|
if isinstance(t, InterfaceObjectType) and t.has_abstract_base:
|
|
type_exports.append(
|
|
f"export type Abstract{t.js_name} = _Abstract{t.js_name};"
|
|
)
|
|
type_exports += [
|
|
f"export type {s.handler_type_name} = _{s.handler_type_name};"
|
|
for s in t.signals
|
|
]
|
|
for name in model.customizations.custom_types.keys():
|
|
type_exports.append(f"export type {name} = _{name};")
|
|
|
|
lines += [
|
|
"",
|
|
"namespace frida {",
|
|
]
|
|
for e in type_exports:
|
|
lines.append(indent_ts_code(e, 1))
|
|
lines += [
|
|
"}",
|
|
"",
|
|
]
|
|
for e in type_exports:
|
|
lines.append(e)
|
|
|
|
lines.append("")
|
|
lines.append(model.customizations.facade_code)
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def generate_extension_pyi(model: Model) -> str:
|
|
lines = [
|
|
"export interface FridaBinding {",
|
|
" commitConstructors(): void;",
|
|
]
|
|
for t in model.public_types.values():
|
|
if t.is_frida_options:
|
|
lines.append(f" {t.prefixed_js_name}: {t.prefixed_js_name};")
|
|
else:
|
|
if isinstance(t, InterfaceObjectType):
|
|
if t.has_abstract_base:
|
|
lines.append(
|
|
f" Abstract{t.js_name}: typeof Abstract{t.js_name};"
|
|
)
|
|
continue
|
|
if isinstance(t, ClassObjectType):
|
|
if t.needs_wrapper:
|
|
lines.append(f" {t.js_name}: typeof {t.js_name};")
|
|
lines.append(f" {t.prefixed_js_name}: typeof {t.prefixed_js_name};")
|
|
for name, cust in model.customizations.custom_types.items():
|
|
if cust.kind == CustomTypeKind.ENUM:
|
|
lines.append(f" {name}: typeof {name};")
|
|
lines.append("}")
|
|
|
|
for otype in model.object_types.values():
|
|
if not otype.is_public:
|
|
continue
|
|
|
|
if otype.needs_wrapper:
|
|
lines += [
|
|
"",
|
|
f"export class {otype.js_name} extends {otype.prefixed_js_name} {{",
|
|
]
|
|
|
|
custom_code = otype.customizations.custom_code
|
|
if custom_code is not None:
|
|
for declaration in custom_code.declarations:
|
|
typing = declaration.typing
|
|
if typing is not None:
|
|
lines.append(f" {typing};")
|
|
|
|
for method in custom_code.methods:
|
|
typing = method.typing
|
|
if typing is not None:
|
|
lines.append(f" {typing};")
|
|
|
|
ctor = otype.constructors[0] if otype.constructors else None
|
|
if ctor is not None and ctor.needs_wrapper:
|
|
lines.append(f" constructor({', '.join(ctor.param_typings)});")
|
|
|
|
for method in otype.wrapped_methods:
|
|
params = ", ".join(method.param_typings)
|
|
lines.append(f" {method.js_name}({params}): {method.return_typing};")
|
|
|
|
for signal in otype.wrapped_signals:
|
|
lines.append(
|
|
f" readonly {signal.js_name}: Signal<{signal.handler_type_name}>;"
|
|
)
|
|
|
|
lines.append("}")
|
|
|
|
if otype.wrapped_signals:
|
|
lines.append("")
|
|
for signal in otype.wrapped_signals:
|
|
params = ", ".join(
|
|
signal.customizations.transform.get(i, (param.typing, ""))[0]
|
|
for i, param in enumerate(signal.parameters)
|
|
)
|
|
lines.append(
|
|
f"export type {signal.handler_type_name} = ({params}) => void;"
|
|
)
|
|
|
|
lines.append("")
|
|
|
|
parent = otype.parent
|
|
parent_name = parent.js_name if parent is not None else None
|
|
extends = (
|
|
""
|
|
if (parent_name is None or otype.is_frida_options)
|
|
else f" extends {parent_name}"
|
|
)
|
|
if isinstance(otype, InterfaceObjectType) or otype.is_frida_options:
|
|
lines.append(f"export interface {otype.prefixed_js_name}{extends} {{")
|
|
else:
|
|
implements = (
|
|
f" implements {', '.join([t.js_name for t in otype.implements])}"
|
|
if otype.implements
|
|
else ""
|
|
)
|
|
lines.append(
|
|
f"export class {otype.prefixed_js_name}{extends}{implements} {{"
|
|
)
|
|
|
|
if otype.constructors:
|
|
constructor = otype.constructors[0]
|
|
params = ", ".join(param.typing for param in constructor.parameters)
|
|
lines.append(f" constructor({params});")
|
|
|
|
if otype.is_frida_options:
|
|
for method in otype.methods:
|
|
if method.is_select_method:
|
|
lines.append(
|
|
f" {method.select_plural_noun}?: {model.resolve_js_type(method.select_element_type)}[];"
|
|
)
|
|
else:
|
|
for method in otype.methods:
|
|
if method.is_property_accessor:
|
|
continue
|
|
visibility = (
|
|
"protected " if method.prefixed_js_name != method.js_name else ""
|
|
)
|
|
lines.append(
|
|
f" {visibility}{method.prefixed_js_name}({', '.join(method.prefixed_param_typings)}): {method.prefixed_return_typing};"
|
|
)
|
|
|
|
for prop in otype.properties:
|
|
lines.append(f" {prop.typing};")
|
|
|
|
for signal in otype.signals:
|
|
visibility = (
|
|
"protected " if signal.prefixed_js_name != signal.js_name else ""
|
|
)
|
|
lines.append(
|
|
f" {visibility}readonly {signal.prefixed_js_name}: Signal<{signal.prefixed_handler_type_name}>;"
|
|
)
|
|
|
|
if isinstance(otype, ClassObjectType):
|
|
for itype in otype.implements:
|
|
for method in itype.methods:
|
|
lines.append(
|
|
f" {method.js_name}({', '.join(method.param_typings)}): {method.return_typing};"
|
|
)
|
|
|
|
for prop in itype.properties:
|
|
lines.append(f" {prop.typing};")
|
|
|
|
for signal in itype.signals:
|
|
lines.append(
|
|
f" readonly {signal.js_name}: Signal<{signal.handler_type_name}>;"
|
|
)
|
|
|
|
lines.append("}")
|
|
|
|
if otype.signals:
|
|
lines.append("")
|
|
for signal in otype.signals:
|
|
lines.append(
|
|
f"export type {signal.prefixed_handler_type_name} = {signal.typing};"
|
|
)
|
|
|
|
if isinstance(otype, InterfaceObjectType) and otype.has_abstract_base:
|
|
lines.append("")
|
|
|
|
object_js_name = model.resolve_object_type("Object").js_name
|
|
lines.append(
|
|
f"export abstract class Abstract{otype.js_name} extends {object_js_name} implements {otype.js_name} {{"
|
|
)
|
|
|
|
for method in otype.methods:
|
|
params = ", ".join([t.replace("?:", ":") for t in method.param_typings])
|
|
lines.append(f" {method.js_name}({params}): {method.return_typing};")
|
|
|
|
for prop in otype.properties:
|
|
lines.append(f" {prop.typing};")
|
|
|
|
for signal in otype.signals:
|
|
lines.append(
|
|
f" readonly {signal.js_name}: Signal<{signal.handler_type_name}>;"
|
|
)
|
|
|
|
lines.append("}")
|
|
|
|
for enum in model.enumerations.values():
|
|
members = ",\n ".join(
|
|
f'{member.js_name} = "{member.nick}"' for member in enum.members
|
|
)
|
|
lines += [
|
|
"",
|
|
f"export enum {enum.js_name} {{",
|
|
f" {members}",
|
|
"}",
|
|
]
|
|
|
|
for name, cust in model.customizations.custom_types.items():
|
|
code = f"\nexport {cust.kind.value} {name}"
|
|
if cust.kind == CustomTypeKind.TYPE:
|
|
code += " = "
|
|
code += cust.typing.strip()
|
|
code += ";"
|
|
else:
|
|
code += " {\n"
|
|
code += indent_ts_code(cust.typing.strip(), 1)
|
|
code += "\n}"
|
|
lines.append(code)
|
|
|
|
lines.append(
|
|
"""
|
|
export class Signal<H extends SignalHandler> {
|
|
connect(handler: H): void;
|
|
disconnect(handler: H): void;
|
|
}
|
|
|
|
export type SignalHandler = (...args: any[]) => void;
|
|
"""
|
|
)
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def generate_extension_c(model: Model) -> str:
|
|
object_types = model.object_types.values()
|
|
enumerations = model.enumerations.values()
|
|
|
|
code = generate_includes()
|
|
code += CODEGEN_MACROS_H
|
|
code += "\n" + CODEGEN_TYPEDEFS_H
|
|
code += generate_object_type_typedefs(model)
|
|
code += "\n\n" + CODEGEN_STRUCTS_H
|
|
code += "\n" + generate_object_type_structs(model)
|
|
#code += generate_abstract_base_type_declarations(model)
|
|
#code += generate_operation_structs(object_types)
|
|
code += "\n" + generate_prototypes(model)
|
|
#code += generate_abstract_base_define_type_invocations(model)
|
|
code += "\n" + generate_shared_globals()
|
|
code += "\n" + CODEGEN_GOBJECT_GLOBALS
|
|
code += "\n" + generate_object_type_method_definitions(model)
|
|
code += "\n" + generate_object_type_toplevel_definitions(model)
|
|
#code += generate_type_tags(object_types)
|
|
#code += generate_constructor_declarations(object_types)
|
|
#code += generate_tsfn_declarations(object_types)
|
|
code += "\n" + generate_init_function(object_types, enumerations)
|
|
#code += generate_commit_constructors_function(object_types)
|
|
|
|
for otype in object_types:
|
|
if otype.name == "Object":
|
|
code += "\n" + CODEGEN_GOBJECT_METHODS
|
|
continue
|
|
if otype.is_frida_options:
|
|
#code += generate_options_conversion_functions(otype)
|
|
continue
|
|
if otype.is_frida_list:
|
|
#code += generate_list_conversion_functions(otype)
|
|
continue
|
|
|
|
#code += generate_object_type_registration_code(otype, model)
|
|
#code += generate_object_type_conversion_functions(otype)
|
|
code += generate_object_type_constructor(otype)
|
|
#code += generate_object_type_finalizer(otype)
|
|
#code += generate_object_type_cleanup_code(otype)
|
|
|
|
#for method in otype.methods:
|
|
# code += generate_method_code(method)
|
|
|
|
#for signal in otype.signals:
|
|
# code += generate_signal_getter_code(otype, signal)
|
|
|
|
#if isinstance(otype, InterfaceObjectType) and otype.has_abstract_base:
|
|
# code += generate_abstract_base_registration_code(otype)
|
|
# code += generate_abstract_base_constructor(otype)
|
|
# code += generate_abstract_base_gobject_glue(otype)
|
|
# for method in otype.methods:
|
|
# code += generate_abstract_base_method_code(method)
|
|
|
|
|
|
#for enum in enumerations:
|
|
# code += generate_enum_registration_code(enum)
|
|
# code += generate_enum_conversion_functions(enum)
|
|
|
|
#code += CODEGEN_HELPERS_C
|
|
|
|
return code
|
|
|
|
|
|
def generate_includes() -> str:
|
|
return """\
|
|
#include <frida-core.h>
|
|
|
|
#define PY_SSIZE_T_CLEAN
|
|
|
|
/*
|
|
* Don't propagate _DEBUG state to pyconfig as it incorrectly attempts to load
|
|
* debug libraries that don't normally ship with Python (e.g. 2.x). Debuggers
|
|
* wishing to spelunk the Python core can override this workaround by defining
|
|
* _FRIDA_ENABLE_PYDEBUG.
|
|
*/
|
|
#if defined (_DEBUG) && !defined (_FRIDA_ENABLE_PYDEBUG)
|
|
# undef _DEBUG
|
|
# include <pyconfig.h>
|
|
# define _DEBUG
|
|
#else
|
|
# include <pyconfig.h>
|
|
#endif
|
|
|
|
#include <Python.h>
|
|
|
|
"""
|
|
|
|
|
|
def generate_operation_structs(object_types: List[ObjectType]) -> str:
|
|
structs = []
|
|
|
|
for otype in object_types:
|
|
is_iface_with_abstract_base = (
|
|
isinstance(otype, InterfaceObjectType) and otype.has_abstract_base
|
|
)
|
|
|
|
for method in otype.methods:
|
|
if method.is_async:
|
|
param_declarations = generate_parameter_variable_declarations(method)
|
|
return_declaration = generate_return_variable_declaration(method)
|
|
decls = "".join(
|
|
[
|
|
indent_c_code(param_declarations, 1, prologue="\n"),
|
|
indent_c_code(return_declaration, 1, prologue="\n"),
|
|
]
|
|
)
|
|
structs.append(
|
|
f"""\
|
|
typedef struct {{
|
|
napi_deferred deferred;
|
|
{otype.c_type} * handle;{decls}
|
|
}} {method.operation_type_name};
|
|
"""
|
|
)
|
|
|
|
if is_iface_with_abstract_base:
|
|
param_declarations = generate_parameter_variable_declarations(method)
|
|
return_declaration = generate_return_variable_declaration(method)
|
|
decls = "".join(
|
|
[
|
|
indent_c_code(param_declarations, 1, prologue="\n"),
|
|
indent_c_code(return_declaration, 1, prologue="\n"),
|
|
]
|
|
)
|
|
structs.append(
|
|
f"""\
|
|
typedef struct {{
|
|
{otype.abstract_base_c_type} * self;{decls}
|
|
}} {method.abstract_base_operation_type_name};
|
|
"""
|
|
)
|
|
|
|
return "\n".join(structs)
|
|
|
|
|
|
def generate_prototypes(model: Model) -> str:
|
|
prototypes = []
|
|
|
|
for otype in model.regular_object_types:
|
|
otype_cprefix = otype.c_symbol_prefix
|
|
|
|
if otype.name == "Object":
|
|
prototypes += [
|
|
"",
|
|
CODEGEN_GOBJECT_PROTOTYPES.rstrip(),
|
|
]
|
|
else:
|
|
prototypes += [
|
|
"",
|
|
f"static int {otype_cprefix}_init ({otype_cprefix} * self, PyObject * args, PyObject * kw);",
|
|
f"static void {otype_cprefix}_dealloc ({otype_cprefix} * self);",
|
|
]
|
|
|
|
return "\n".join(prototypes)
|
|
|
|
|
|
def generate_shared_globals() -> str:
|
|
return "\n".join(
|
|
[
|
|
"",
|
|
'static struct PyModuleDef PyFrida_moduledef = { PyModuleDef_HEAD_INIT, "_frida", "Frida", -1, NULL, };',
|
|
"",
|
|
"static initproc PyGObject_tp_init;",
|
|
"static destructor PyGObject_tp_dealloc;",
|
|
]
|
|
)
|
|
|
|
|
|
def generate_object_type_toplevel_definitions(model: Model) -> str:
|
|
defs = []
|
|
|
|
for otype in model.regular_object_types:
|
|
if otype.name == "Object":
|
|
kind = "BASETYPE"
|
|
extra_args = ""
|
|
else:
|
|
kind = "TYPE"
|
|
parent = otype.parent
|
|
parent_name = parent.py_name if parent is not None else "GObject"
|
|
extra_args = f", {parent_name}"
|
|
|
|
cprefix = otype.c_symbol_prefix
|
|
|
|
defs.append(f"""PYFRIDA_DEFINE_{kind} ("_frida.{otype.py_name}", {otype.py_name}{extra_args},
|
|
{{ Py_tp_doc, "{otype.name}" }},
|
|
{{ Py_tp_init, {cprefix}_init }},
|
|
{{ Py_tp_dealloc, {cprefix}_dealloc }},
|
|
{{ Py_tp_methods, {cprefix}_methods }},
|
|
);""")
|
|
|
|
return "\n\n".join(defs)
|
|
|
|
|
|
def generate_object_type_method_definitions(model: Model) -> str:
|
|
defs = []
|
|
|
|
for otype in model.regular_object_types:
|
|
cprefix = otype.c_symbol_prefix
|
|
defs.append(f"""static PyMethodDef {cprefix}_methods[] =
|
|
{{
|
|
{{ NULL }}
|
|
}};""")
|
|
|
|
return "\n\n".join(defs)
|
|
|
|
|
|
def generate_type_tags(object_types: List[ObjectType]) -> str:
|
|
type_tags = [
|
|
"static napi_type_tag fdn_handle_wrapper_type_tag = { 0xdd596d4f2dad45f9, 0x844585a48e8d05ba };",
|
|
"static napi_type_tag fdn_object_type_tag = { 0x4eeacfcdc22c425a, 0x91346eafdc89fedc };",
|
|
]
|
|
return "\n".join(type_tags) + "\n"
|
|
|
|
|
|
def generate_constructor_declarations(object_types: List[ObjectType]) -> str:
|
|
declarations = []
|
|
|
|
for otype in object_types:
|
|
if otype.is_frida_options or otype.is_frida_list:
|
|
continue
|
|
declarations.append(f"static napi_ref {otype.c_symbol_prefix}_constructor;")
|
|
|
|
declarations += [
|
|
"",
|
|
"static napi_ref fdn_signal_constructor;",
|
|
]
|
|
|
|
return "\n" + "\n".join(declarations) + "\n"
|
|
|
|
|
|
def generate_tsfn_declarations(object_types: List[ObjectType]) -> str:
|
|
declarations = []
|
|
|
|
for otype in object_types:
|
|
async_methods = [method for method in otype.methods if method.is_async]
|
|
if async_methods:
|
|
declarations.append("")
|
|
for method in async_methods:
|
|
declarations.append(
|
|
f"static napi_threadsafe_function {otype.c_symbol_prefix}_{method.name}_tsfn;"
|
|
)
|
|
|
|
if isinstance(otype, InterfaceObjectType) and otype.has_abstract_base:
|
|
declarations.append(
|
|
f"static napi_threadsafe_function {otype.abstract_base_c_symbol_prefix}_release_js_resources_tsfn;"
|
|
)
|
|
for method in otype.methods:
|
|
declarations.append(
|
|
f"static napi_threadsafe_function {otype.abstract_base_c_symbol_prefix}_{method.name}_tsfn;"
|
|
)
|
|
|
|
declarations += [
|
|
"",
|
|
"static napi_threadsafe_function fdn_keep_alive_tsfn;",
|
|
]
|
|
|
|
return "\n".join(declarations) + "\n"
|
|
|
|
|
|
def generate_init_function(
|
|
object_types: List[ObjectType], enumerations: List[Enumeration]
|
|
) -> str:
|
|
object_type_entries = []
|
|
for otype in object_types:
|
|
if otype.is_frida_options or otype.is_frida_list:
|
|
continue
|
|
object_type_entries.append((otype.py_name, otype.get_type))
|
|
#if isinstance(otype, InterfaceObjectType) and otype.has_abstract_base:
|
|
# object_type_entries.append((f"Abstract{otype.name}", f"PyAbstract{otype.name}_get_type"))
|
|
object_type_registration_calls = "\n ".join(
|
|
[f'PYFRIDA_REGISTER_TYPE ({name}, {get_type} ());' for name, get_type in object_type_entries]
|
|
)
|
|
|
|
return f"""
|
|
PyMODINIT_FUNC
|
|
PyInit__frida (void)
|
|
{{
|
|
PyObject * module;
|
|
|
|
frida_init ();
|
|
|
|
PyGObject_class_init ();
|
|
|
|
module = PyModule_Create (&PyFrida_moduledef);
|
|
|
|
PyModule_AddStringConstant (module, "__version__", frida_version_string ());
|
|
|
|
PyGObject_tp_init = PyType_GetSlot ((PyTypeObject *) PYFRIDA_TYPE_OBJECT (GObject), Py_tp_init);
|
|
PyGObject_tp_dealloc = PyType_GetSlot ((PyTypeObject *) PYFRIDA_TYPE_OBJECT (GObject), Py_tp_dealloc);
|
|
|
|
{object_type_registration_calls}
|
|
|
|
return module;
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_commit_constructors_function(object_types: List[ObjectType]) -> str:
|
|
commits = ""
|
|
for otype in object_types:
|
|
if otype.is_frida_options or otype.is_frida_list:
|
|
continue
|
|
|
|
otype_cprefix = otype.c_symbol_prefix
|
|
|
|
if otype.needs_wrapper:
|
|
commits += f""" if ({otype_cprefix}_constructor == NULL)
|
|
{{
|
|
napi_get_named_property (env, exports, "{otype.js_name}", &ctor);
|
|
napi_create_reference (env, ctor, 1, &{otype_cprefix}_constructor);
|
|
}}
|
|
"""
|
|
|
|
commits += f" g_hash_table_insert (fdn_constructors, GSIZE_TO_POINTER ({otype.get_type} ()), {otype_cprefix}_constructor);\n\n"
|
|
|
|
inherits = ""
|
|
for otype in object_types:
|
|
if otype.is_frida_options or otype.is_frida_list:
|
|
continue
|
|
|
|
parent = otype.parent
|
|
if parent is None:
|
|
continue
|
|
|
|
if otype.needs_wrapper:
|
|
inherits += f"""
|
|
napi_get_named_property (env, exports, "{otype.prefixed_js_name}", &ctor);
|
|
fdn_inherit_ref_val (env, {otype.c_symbol_prefix}_constructor, ctor, object_ctor, set_proto);
|
|
"""
|
|
if parent.name == "Object":
|
|
inherits += " fdn_inherit_val_val (env, ctor, fdn_object_ctor, object_ctor, set_proto);"
|
|
else:
|
|
inherits += " fdn_inherit_val_ref (env, ctor, {parent.c_symbol_prefix}_constructor, object_ctor, set_proto);"
|
|
else:
|
|
if parent.name == "Object":
|
|
inherits += f"""
|
|
fdn_inherit_ref_val (env, {otype.c_symbol_prefix}_constructor, fdn_object_ctor, object_ctor, set_proto);
|
|
"""
|
|
else:
|
|
inherits += f"""
|
|
fdn_inherit_ref_ref (env, {otype.c_symbol_prefix}_constructor, {parent.c_symbol_prefix}_constructor, object_ctor, set_proto);
|
|
"""
|
|
|
|
return f"""
|
|
static napi_value
|
|
fdn_commit_constructors (napi_env env,
|
|
napi_callback_info info)
|
|
{{
|
|
napi_value result, exports, ctor, global, object_ctor, set_proto, fdn_object_ctor;
|
|
|
|
napi_get_reference_value (env, fdn_exports, &exports);
|
|
{commits} napi_get_global (env, &global);
|
|
napi_get_named_property (env, global, "Object", &object_ctor);
|
|
napi_get_named_property (env, object_ctor, "setPrototypeOf", &set_proto);
|
|
|
|
napi_get_reference_value (env, fdn_object_constructor, &fdn_object_ctor);
|
|
{inherits}
|
|
napi_get_undefined (env, &result);
|
|
return result;
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_object_type_registration_code(otype: ObjectType, model: Model) -> str:
|
|
otype_cprefix = otype.c_symbol_prefix
|
|
|
|
ctor_ref_creation = (
|
|
""
|
|
if otype.needs_wrapper
|
|
else f"\n napi_create_reference (env, constructor, 1, &{otype_cprefix}_constructor);"
|
|
)
|
|
jsprop_registrations = []
|
|
tsfn_initializations = []
|
|
|
|
for method in otype.methods:
|
|
if method.is_property_accessor:
|
|
continue
|
|
jsprop_registrations.append(generate_method_registration_entry(method))
|
|
if method.is_async:
|
|
tsfn_initializations.append(
|
|
f"""\
|
|
napi_create_threadsafe_function (env, NULL, NULL, fdn_utf8_to_value (env, "{method.prefixed_js_name}"), 0, 1, NULL, NULL, NULL, {otype_cprefix}_{method.name}_deliver, &{otype_cprefix}_{method.name}_tsfn);
|
|
napi_unref_threadsafe_function (env, {otype_cprefix}_{method.name}_tsfn);"""
|
|
)
|
|
|
|
for prop in otype.properties:
|
|
jsprop_registrations.append(generate_property_registration_entry(prop))
|
|
|
|
for signal in otype.signals:
|
|
jsprop_registrations.append(generate_signal_registration_entry(signal))
|
|
|
|
if isinstance(otype, ClassObjectType):
|
|
for itype in otype.implements:
|
|
for method in itype.methods:
|
|
if method.is_property_accessor:
|
|
continue
|
|
jsprop_registrations.append(generate_method_registration_entry(method))
|
|
|
|
for prop in itype.properties:
|
|
jsprop_registrations.append(generate_property_registration_entry(prop))
|
|
|
|
for signal in itype.signals:
|
|
jsprop_registrations.append(generate_signal_registration_entry(signal))
|
|
|
|
jsprop_registrations_str = "\n ".join(jsprop_registrations)
|
|
tsfn_initializations_code = "\n\n".join(tsfn_initializations)
|
|
|
|
def calculate_indent(suffix: str) -> str:
|
|
return " " * (len(otype_cprefix) + len(suffix) + 2)
|
|
|
|
two_newlines = "\n\n"
|
|
|
|
return f"""
|
|
static void
|
|
{otype_cprefix}_register (napi_env env,
|
|
{calculate_indent("_register")}napi_value exports)
|
|
{{
|
|
napi_property_descriptor properties[] =
|
|
{{
|
|
{jsprop_registrations_str}
|
|
}};
|
|
napi_value constructor;
|
|
|
|
napi_define_class (env, "{otype.prefixed_js_name}", NAPI_AUTO_LENGTH, {otype_cprefix}_construct, NULL, G_N_ELEMENTS (properties), properties, &constructor);{ctor_ref_creation}
|
|
|
|
napi_set_named_property (env, exports, "{otype.prefixed_js_name}", constructor);{indent_c_code(tsfn_initializations_code, 1, prologue=two_newlines)}
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_method_registration_entry(method: Method) -> str:
|
|
return f'{{ "{method.prefixed_js_name}", NULL, {method.object_type.c_symbol_prefix}_{method.name}, NULL, NULL, NULL, napi_default, NULL }},'
|
|
|
|
|
|
def generate_property_registration_entry(prop: Property) -> str:
|
|
otype_cprefix = prop.object_type.c_symbol_prefix
|
|
|
|
setter_str = f"{otype_cprefix}_{prop.setter}" if prop.setter is not None else "NULL"
|
|
|
|
attrs = ["enumerable", "configurable"]
|
|
if prop.setter is not None:
|
|
attrs.insert(0, "writable")
|
|
attrs_str = " | ".join([f"napi_{attr}" for attr in attrs])
|
|
|
|
return f'{{ "{prop.js_name}", NULL, NULL, {otype_cprefix}_{prop.getter}, {setter_str}, NULL, {attrs_str}, NULL }},'
|
|
|
|
|
|
def generate_signal_registration_entry(signal: Signal) -> str:
|
|
return f'{{ "{signal.prefixed_js_name}", NULL, NULL, {signal.object_type.c_symbol_prefix}_get_{signal.c_name}_signal, NULL, NULL, napi_default, NULL }},'
|
|
|
|
|
|
def generate_object_type_conversion_functions(otype: ObjectType) -> str:
|
|
otype_cprefix = otype.c_symbol_prefix
|
|
|
|
def calculate_indent(suffix: str) -> str:
|
|
return " " * (len(otype_cprefix) + len(suffix) + 2)
|
|
|
|
from_value_function = f"""
|
|
static gboolean
|
|
{otype_cprefix}_from_value (napi_env env,
|
|
{calculate_indent("_from_value")}napi_value value,
|
|
{calculate_indent("_from_value")}{otype.c_type} ** handle)
|
|
{{
|
|
return fdn_object_unwrap (env, value, {otype.get_type} (), (GObject **) handle);
|
|
}}
|
|
"""
|
|
|
|
to_value_function = f"""
|
|
static napi_value
|
|
{otype_cprefix}_to_value (napi_env env,
|
|
{calculate_indent("_to_value")}{otype.c_type} * handle)
|
|
{{
|
|
return fdn_object_new (env, G_OBJECT (handle), {otype_cprefix}_constructor);
|
|
}}
|
|
"""
|
|
|
|
return from_value_function + to_value_function
|
|
|
|
|
|
def generate_object_type_constructor(otype: ObjectType) -> str:
|
|
otype_cprefix = otype.c_symbol_prefix
|
|
|
|
ctor = next(iter(otype.constructors), None)
|
|
|
|
storage_prefix = ""
|
|
invalid_arg_label = "propagate_error"
|
|
error_check = ""
|
|
construction_failed_logic = ""
|
|
|
|
if ctor is not None:
|
|
param_declarations = generate_parameter_variable_declarations(
|
|
ctor, initialize=True
|
|
)
|
|
param_conversions = generate_input_parameter_conversions_code(
|
|
ctor, storage_prefix, invalid_arg_label
|
|
)
|
|
param_destructions = generate_parameter_destructions_code(ctor, storage_prefix)
|
|
|
|
call_args = generate_call_arguments_code(ctor, storage_prefix)
|
|
constructor_call = (
|
|
f"handle = {otype.c_cast_macro} ({ctor.c_identifier} ({call_args}));"
|
|
)
|
|
unconstructable_logic = ""
|
|
|
|
if ctor.throws:
|
|
error_check = """if (error != NULL)
|
|
goto construction_failed;"""
|
|
construction_failed_logic = """construction_failed:
|
|
{
|
|
napi_throw (env, fdn_error_to_value (env, error));
|
|
g_error_free (error);
|
|
goto propagate_error;
|
|
}
|
|
"""
|
|
else:
|
|
param_declarations = ""
|
|
param_conversions = """if (argc == 0)
|
|
goto unconstructable;
|
|
|
|
goto invalid_handle;"""
|
|
param_destructions = ""
|
|
constructor_call = ""
|
|
unconstructable_logic = f"""unconstructable:
|
|
{{
|
|
napi_throw_error (env, NULL, "type {otype.js_name} cannot be constructed");
|
|
return NULL;
|
|
}}
|
|
"""
|
|
|
|
def calculate_indent(suffix: str) -> str:
|
|
return " " * (len(otype_cprefix) + len(suffix) + 2)
|
|
|
|
one_newline = "\n"
|
|
two_newlines = "\n\n"
|
|
|
|
return f"""
|
|
static int
|
|
{otype_cprefix}_init ({otype_cprefix} * self,
|
|
{calculate_indent("_init")}PyObject * args,
|
|
{calculate_indent("_init")}PyObject * kw)
|
|
{{
|
|
int result = -1;{indent_c_code(param_declarations, 1, prologue=one_newline)}
|
|
{otype.c_type} * handle = NULL;
|
|
|
|
if ({otype.parent_c_symbol_prefix}_tp_init ((PyObject *) self, args, kw) < 0)
|
|
goto propagate_error;
|
|
|
|
{indent_c_code(param_conversions, 1)}{indent_c_code(constructor_call, 1, prologue=two_newlines)}{indent_c_code(error_check, 1, prologue=one_newline)}
|
|
|
|
PyGObject_take_handle ((PyGObject *) self, g_steal_pointer (&handle), PYFRIDA_TYPE ({otype.py_name}));
|
|
|
|
result = 0;
|
|
goto beach;
|
|
|
|
{unconstructable_logic}{construction_failed_logic}propagate_error:
|
|
{{
|
|
g_clear_object (&handle);
|
|
goto beach;
|
|
}}
|
|
beach:
|
|
{{{indent_c_code(param_destructions, 2, prologue=one_newline)}
|
|
return result;
|
|
}}
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_object_type_finalizer(otype: ObjectType) -> str:
|
|
custom = otype.customizations
|
|
if custom is None or custom.cleanup is None:
|
|
return ""
|
|
|
|
otype_cprefix = otype.c_symbol_prefix
|
|
|
|
indent = " " * (len(otype_cprefix) + len("_finalize") + 2)
|
|
|
|
return f"""
|
|
static void
|
|
{otype_cprefix}_finalize (napi_env env,
|
|
{indent}void * finalize_data,
|
|
{indent}void * finalize_hint)
|
|
{{
|
|
{otype.c_type} * self = finalize_data;
|
|
|
|
if (g_object_steal_data (G_OBJECT (self), "fdn-cleanup-hook") != NULL)
|
|
napi_remove_env_cleanup_hook (env, {otype_cprefix}_handle_cleanup, self);
|
|
|
|
fdn_object_finalize (env, finalize_data, finalize_hint);
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_object_type_cleanup_code(otype: ObjectType) -> str:
|
|
custom = otype.customizations
|
|
if custom is None or custom.cleanup is None:
|
|
return ""
|
|
|
|
cleanup_method = next(
|
|
(method for method in otype.methods if method.name == custom.cleanup)
|
|
)
|
|
|
|
return f"""
|
|
static void
|
|
{otype.c_symbol_prefix}_handle_cleanup (void * data)
|
|
{{
|
|
{otype.c_type} * self = data;
|
|
|
|
g_object_steal_data (G_OBJECT (self), "fdn-cleanup-hook");
|
|
|
|
{cleanup_method.c_identifier}_sync (self, NULL, NULL);
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_method_code(method: Method) -> str:
|
|
otype = method.object_type
|
|
operation_type_name = method.operation_type_name
|
|
otype_cprefix = otype.c_symbol_prefix
|
|
|
|
storage_prefix = "operation->" if method.is_async else ""
|
|
invalid_arg_label = "invalid_argument" if method.is_async else "beach"
|
|
|
|
if method.input_parameters:
|
|
args_declarations = f"""\
|
|
size_t argc = {len(method.input_parameters)};
|
|
napi_value args[{len(method.input_parameters)}];"""
|
|
get_cb_info_argc_args = "&argc, args"
|
|
else:
|
|
args_declarations = ""
|
|
get_cb_info_argc_args = "NULL, NULL"
|
|
|
|
param_conversions = generate_input_parameter_conversions_code(
|
|
method, storage_prefix, invalid_arg_label
|
|
)
|
|
param_destructions = generate_parameter_destructions_code(method, storage_prefix)
|
|
|
|
return_assignment = generate_return_assignment_code(method, storage_prefix)
|
|
return_conversion = generate_return_conversion_code(method, storage_prefix)
|
|
return_destruction = generate_return_destruction_code(method, storage_prefix)
|
|
|
|
keep_alive_code = ""
|
|
custom = method.customizations
|
|
if custom is not None:
|
|
if custom.ref_keep_alive:
|
|
keep_alive_code = (
|
|
"\n\nnapi_ref_threadsafe_function (env, fdn_keep_alive_tsfn);"
|
|
)
|
|
elif custom.unref_keep_alive:
|
|
keep_alive_code = (
|
|
"\n\nnapi_unref_threadsafe_function (env, fdn_keep_alive_tsfn);"
|
|
)
|
|
|
|
def calculate_indent(suffix: str) -> str:
|
|
return " " * (len(otype_cprefix) + 1 + len(method.name) + len(suffix) + 2)
|
|
|
|
one_newline = "\n"
|
|
two_newlines = "\n\n"
|
|
|
|
if method.is_async:
|
|
operation_free_function = f"""\
|
|
static void
|
|
{otype_cprefix}_{method.name}_operation_free ({operation_type_name} * operation)
|
|
{{{indent_c_code(param_destructions, 1, prologue=one_newline)}{indent_c_code(return_destruction, 1, prologue=one_newline)}
|
|
g_slice_free ({operation_type_name}, operation);
|
|
}}"""
|
|
|
|
code = f"""
|
|
static napi_value
|
|
{otype_cprefix}_{method.name} (napi_env env,
|
|
{calculate_indent('')}napi_callback_info info)
|
|
{{{indent_c_code(args_declarations, 1, prologue=one_newline)}
|
|
napi_value jsthis;
|
|
{otype.c_type} * handle;
|
|
napi_deferred deferred;
|
|
napi_value promise;
|
|
{operation_type_name} * operation;
|
|
GSource * source;
|
|
|
|
if (napi_get_cb_info (env, info, {get_cb_info_argc_args}, &jsthis, NULL) != napi_ok)
|
|
return NULL;
|
|
|
|
if (napi_unwrap (env, jsthis, (void **) &handle) != napi_ok)
|
|
return NULL;
|
|
|
|
napi_create_promise (env, &deferred, &promise);
|
|
|
|
operation = g_slice_new0 ({operation_type_name});
|
|
operation->deferred = deferred;
|
|
operation->handle = handle;
|
|
operation->error = NULL;{indent_c_code(param_conversions, 1, prologue=two_newlines)}
|
|
|
|
source = g_idle_source_new ();
|
|
g_source_set_callback (source, {otype_cprefix}_{method.name}_begin,
|
|
operation, NULL);
|
|
g_source_attach (source, frida_get_main_context ());
|
|
g_source_unref (source);
|
|
|
|
napi_ref_threadsafe_function (env, {otype_cprefix}_{method.name}_tsfn);
|
|
|
|
return promise;
|
|
|
|
invalid_argument:
|
|
{{
|
|
napi_reject_deferred (env, deferred, NULL);
|
|
{otype_cprefix}_{method.name}_operation_free (operation);
|
|
return NULL;
|
|
}}
|
|
}}
|
|
|
|
static gboolean
|
|
{otype_cprefix}_{method.name}_begin (gpointer user_data)
|
|
{{
|
|
{operation_type_name} * operation = user_data;
|
|
|
|
{method.c_identifier} (operation->handle,
|
|
{", ".join([f"operation->{param.name}" for param in method.parameters])},
|
|
{otype_cprefix}_{method.name}_end, operation);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}}
|
|
|
|
static void
|
|
{otype_cprefix}_{method.name}_end (GObject * source_object,
|
|
{calculate_indent("_end")}GAsyncResult * res,
|
|
{calculate_indent("_end")}gpointer user_data)
|
|
{{
|
|
{operation_type_name} * operation = user_data;
|
|
|
|
{return_assignment}{method.finish_c_identifier} (operation->handle, res, &operation->error);
|
|
|
|
napi_call_threadsafe_function ({otype_cprefix}_{method.name}_tsfn, operation, napi_tsfn_blocking);
|
|
}}
|
|
|
|
static void
|
|
{otype_cprefix}_{method.name}_deliver (napi_env env,
|
|
{calculate_indent("_deliver")}napi_value js_cb,
|
|
{calculate_indent("_deliver")}void * context,
|
|
{calculate_indent("_deliver")}void * data)
|
|
{{
|
|
{operation_type_name} * operation = data;
|
|
|
|
if (operation->error != NULL)
|
|
{{
|
|
napi_value error_obj = fdn_error_to_value (env, operation->error);
|
|
napi_reject_deferred (env, operation->deferred, error_obj);
|
|
g_error_free (operation->error);
|
|
}}
|
|
else
|
|
{{
|
|
napi_value js_retval;
|
|
{indent_c_code(return_conversion, 2)}
|
|
napi_resolve_deferred (env, operation->deferred, js_retval);{indent_c_code(keep_alive_code, 2)}
|
|
}}
|
|
|
|
{otype_cprefix}_{method.name}_operation_free (operation);
|
|
|
|
napi_unref_threadsafe_function (env, {otype_cprefix}_{method.name}_tsfn);
|
|
}}
|
|
|
|
{operation_free_function}
|
|
"""
|
|
else:
|
|
param_declarations = generate_parameter_variable_declarations(
|
|
method, initialize=True
|
|
)
|
|
return_declaration = generate_return_variable_declaration(method)
|
|
call_args = generate_call_arguments_code(
|
|
method, storage_prefix, instance_arg="handle"
|
|
)
|
|
|
|
if method.throws:
|
|
error_check = """if (error != NULL)
|
|
goto call_failed;"""
|
|
call_failed_logic = """call_failed:
|
|
{
|
|
napi_throw (env, fdn_error_to_value (env, error));
|
|
g_error_free (error);
|
|
goto beach;
|
|
}
|
|
"""
|
|
else:
|
|
error_check = ""
|
|
call_failed_logic = ""
|
|
|
|
post_call_logic = "".join(
|
|
[
|
|
indent_c_code(error_check, 1, prologue=one_newline),
|
|
indent_c_code(keep_alive_code, 1, prologue=two_newlines),
|
|
indent_c_code(return_conversion, 1, prologue=two_newlines),
|
|
indent_c_code(return_destruction, 1, prologue=one_newline),
|
|
]
|
|
)
|
|
|
|
code = f"""
|
|
static napi_value
|
|
{otype_cprefix}_{method.name} (napi_env env,
|
|
{calculate_indent('')}napi_callback_info info)
|
|
{{
|
|
napi_value js_retval = NULL;{indent_c_code(args_declarations, 1, prologue=one_newline)}
|
|
napi_value jsthis;
|
|
{otype.c_type} * handle;{indent_c_code(param_declarations, 1, prologue=one_newline)}{indent_c_code(return_declaration, 1, prologue=one_newline)}
|
|
|
|
if (napi_get_cb_info (env, info, {get_cb_info_argc_args}, &jsthis, NULL) != napi_ok)
|
|
goto beach;
|
|
|
|
if (napi_unwrap (env, jsthis, (void **) &handle) != napi_ok)
|
|
goto beach;{indent_c_code(param_conversions, 1, prologue=two_newlines)}
|
|
|
|
{return_assignment}{method.c_identifier} ({call_args});{post_call_logic}
|
|
goto beach;
|
|
|
|
{call_failed_logic}beach:
|
|
{{{indent_c_code(param_destructions, 2, prologue=one_newline)}
|
|
return js_retval;
|
|
}}
|
|
}}
|
|
"""
|
|
return code
|
|
|
|
|
|
def generate_parameter_variable_declarations(
|
|
proc: Procedure, initialize: bool = False
|
|
) -> str:
|
|
decls = []
|
|
|
|
for param in proc.parameters:
|
|
line = f"{param.type.c.replace('const ', '')} {param.name}"
|
|
if initialize:
|
|
default_val = param.type.default_value
|
|
if default_val is not None:
|
|
line += f" = {default_val}"
|
|
line += ";"
|
|
decls.append(line)
|
|
|
|
if proc.throws:
|
|
line = "GError * error"
|
|
if initialize:
|
|
line += " = NULL"
|
|
line += ";"
|
|
decls.append(line)
|
|
|
|
return "\n".join(decls)
|
|
|
|
|
|
def generate_input_parameter_conversions_code(
|
|
proc: Procedure, storage_prefix: str, invalid_arg_label: str
|
|
) -> str:
|
|
conversions = [
|
|
generate_parameter_conversion_code(param, i, storage_prefix, invalid_arg_label)
|
|
for i, param in enumerate(proc.input_parameters)
|
|
]
|
|
return "\n\n".join(conversions)
|
|
|
|
|
|
def generate_parameter_destructions_code(proc: Procedure, storage_prefix: str) -> str:
|
|
destructions = [
|
|
generate_parameter_destruction_code(param, storage_prefix)
|
|
for param in proc.parameters
|
|
]
|
|
return "\n".join([d for d in destructions if d is not None])
|
|
|
|
|
|
def generate_parameter_conversion_code(
|
|
param: Parameter, index: int, storage_prefix: str, invalid_arg_label: str
|
|
) -> str:
|
|
code = f"""\
|
|
if (argc > {index} && !fdn_is_undefined_or_null (env, args[{index}]))
|
|
{{
|
|
if (!fdn_{param.type.nick}_from_value (env, args[{index}], &{storage_prefix}{param.name}))
|
|
goto {invalid_arg_label};
|
|
}}
|
|
else
|
|
{{
|
|
"""
|
|
|
|
if param.nullable:
|
|
code += f" {storage_prefix}{param.name} = NULL;"
|
|
else:
|
|
code += f""" napi_throw_type_error (env, NULL, "missing argument: {param.js_name}");
|
|
goto {invalid_arg_label};"""
|
|
|
|
code += "\n}"
|
|
|
|
return code
|
|
|
|
|
|
def generate_parameter_destruction_code(
|
|
param: Parameter, storage_prefix: str
|
|
) -> Optional[str]:
|
|
func = param.destroy_func
|
|
if func is None:
|
|
return None
|
|
return generate_destruction_code(f"{storage_prefix}{param.name}", func)
|
|
|
|
|
|
def generate_call_arguments_code(
|
|
proc: Procedure, storage_prefix: str, instance_arg: Optional[str] = None
|
|
) -> str:
|
|
names = []
|
|
if instance_arg is not None:
|
|
names.append(instance_arg)
|
|
names += [f"{storage_prefix}{param.name}" for param in proc.parameters]
|
|
if proc.throws:
|
|
names.append(f"&{storage_prefix}error")
|
|
return ", ".join(names)
|
|
|
|
|
|
def generate_return_variable_declaration(method: Method) -> str:
|
|
return (
|
|
f"{method.return_value.type.c} retval;"
|
|
if method.return_value is not None
|
|
else ""
|
|
)
|
|
|
|
|
|
def generate_return_assignment_code(method: Method, storage_prefix: str) -> str:
|
|
return f"{storage_prefix}retval = " if method.return_value is not None else ""
|
|
|
|
|
|
def generate_return_conversion_code(method: Method, storage_prefix: str) -> str:
|
|
if method.return_value is not None:
|
|
custom = method.customizations
|
|
if custom is not None and custom.return_cconversion is not None:
|
|
code = f"js_retval = {custom.return_cconversion};"
|
|
else:
|
|
code = f"js_retval = fdn_{method.return_value.type.nick}_to_value (env, {storage_prefix}retval);"
|
|
if method.return_value.nullable:
|
|
code = f"if ({storage_prefix}retval != NULL)\n {code}\nelse\n napi_get_null (env, &js_retval);"
|
|
else:
|
|
code = "napi_get_undefined (env, &js_retval);"
|
|
return code
|
|
|
|
|
|
def generate_return_destruction_code(method: Method, storage_prefix: str) -> str:
|
|
retval = method.return_value
|
|
if retval is None:
|
|
return ""
|
|
func = retval.destroy_func
|
|
if func is None:
|
|
return ""
|
|
return generate_destruction_code(f"{storage_prefix}retval", func)
|
|
|
|
|
|
def generate_destruction_code(variable: str, destroy_func: str):
|
|
if destroy_func == "g_free":
|
|
return f"g_free ({variable});"
|
|
return f"g_clear_pointer (&{variable}, {destroy_func});"
|
|
|
|
|
|
def generate_signal_getter_code(otype: ObjectType, signal: Signal) -> str:
|
|
cprefix = otype.c_symbol_prefix
|
|
|
|
custom = signal.customizations
|
|
behavior = custom.behavior if custom is not None else "FDN_SIGNAL_ALLOW_EXIT"
|
|
|
|
indent = " " * (len(cprefix) + 5 + len(signal.c_name) + 9)
|
|
|
|
return f"""
|
|
static napi_value
|
|
{cprefix}_get_{signal.c_name}_signal (napi_env env,
|
|
{indent}napi_callback_info info)
|
|
{{
|
|
return fdn_object_get_signal (env, info, "{signal.name}", "_{signal.prefixed_js_name}", {behavior});
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_object_type_typedefs(model: Model) -> str:
|
|
return "\n".join([f"typedef struct _{t.c_symbol_prefix} {t.c_symbol_prefix};" for t in model.regular_object_types if t.name != "Object"])
|
|
|
|
|
|
def generate_object_type_structs(model: Model) -> str:
|
|
structs = []
|
|
|
|
for otype in model.regular_object_types:
|
|
if otype.name == "Object":
|
|
continue
|
|
structs.append(f"""struct _{otype.c_symbol_prefix}
|
|
{{
|
|
{otype.parent_c_symbol_prefix} parent;
|
|
}};""")
|
|
|
|
return "\n\n".join(structs)
|
|
|
|
|
|
def generate_abstract_base_type_declarations(model: Model) -> str:
|
|
decls = []
|
|
for itype in model.interface_types_with_abstract_base:
|
|
ctype = itype.abstract_base_c_type
|
|
cprefix = itype.abstract_base_c_symbol_prefix
|
|
module_upper = "PY"
|
|
obj_name_upper = "ABSTRACT_" + to_snake_case(itype.name).upper()
|
|
decls.append(
|
|
f"""G_DECLARE_FINAL_TYPE ({ctype}, {cprefix}, {module_upper}, {obj_name_upper}, GObject)
|
|
|
|
struct _{ctype}
|
|
{{
|
|
GObject parent;
|
|
|
|
napi_ref wrapper;
|
|
gint disposed;
|
|
}};
|
|
"""
|
|
)
|
|
return "\n".join(decls) + "\n"
|
|
|
|
|
|
def generate_abstract_base_define_type_invocations(model: Model) -> str:
|
|
invocations = []
|
|
for itype in model.interface_types_with_abstract_base:
|
|
cprefix = itype.abstract_base_c_symbol_prefix
|
|
invocations.append(
|
|
f"""G_DEFINE_TYPE_EXTENDED ({itype.abstract_base_c_type},
|
|
{cprefix},
|
|
G_TYPE_OBJECT,
|
|
0,
|
|
G_IMPLEMENT_INTERFACE ({itype.get_type} (),
|
|
{cprefix}_iface_init))"""
|
|
)
|
|
return "\n\n".join(invocations) + "\n\n"
|
|
|
|
|
|
def generate_abstract_base_registration_code(otype: ObjectType) -> str:
|
|
otype_cprefix = otype.abstract_base_c_symbol_prefix
|
|
|
|
tsfn_initializations = [
|
|
f"""\
|
|
napi_create_threadsafe_function (env, NULL, NULL, fdn_utf8_to_value (env, "cleanup"), 0, 1, NULL, NULL, NULL, {otype_cprefix}_release_js_resources, &{otype_cprefix}_release_js_resources_tsfn);
|
|
napi_unref_threadsafe_function (env, {otype_cprefix}_release_js_resources_tsfn);"""
|
|
]
|
|
|
|
for method in otype.methods:
|
|
if method.is_property_accessor:
|
|
continue
|
|
tsfn_initializations.append(
|
|
f"""\
|
|
napi_create_threadsafe_function (env, NULL, NULL, fdn_utf8_to_value (env, "{method.prefixed_js_name}"), 0, 1, NULL, NULL, NULL, {otype_cprefix}_{method.name}_begin, &{otype_cprefix}_{method.name}_tsfn);
|
|
napi_unref_threadsafe_function (env, {otype_cprefix}_{method.name}_tsfn);"""
|
|
)
|
|
|
|
tsfn_initializations_code = "\n\n".join(tsfn_initializations)
|
|
|
|
def calculate_indent(suffix: str) -> str:
|
|
return " " * (len(otype_cprefix) + len(suffix) + 2)
|
|
|
|
two_newlines = "\n\n"
|
|
|
|
return f"""
|
|
static void
|
|
{otype_cprefix}_register (napi_env env,
|
|
{calculate_indent("_register")}napi_value exports)
|
|
{{
|
|
napi_value constructor;
|
|
napi_define_class (env, "Abstract{otype.js_name}", NAPI_AUTO_LENGTH, {otype_cprefix}_construct, NULL, 0, NULL, &constructor);
|
|
|
|
napi_set_named_property (env, exports, "Abstract{otype.js_name}", constructor);{indent_c_code(tsfn_initializations_code, 1, prologue=two_newlines)}
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_abstract_base_constructor(otype: ObjectType) -> str:
|
|
otype_cprefix = otype.abstract_base_c_symbol_prefix
|
|
|
|
def calculate_indent(suffix: str) -> str:
|
|
return " " * (len(otype_cprefix) + len(suffix) + 2)
|
|
|
|
return f"""
|
|
static napi_value
|
|
{otype_cprefix}_construct (napi_env env,
|
|
{calculate_indent("_construct")}napi_callback_info info)
|
|
{{
|
|
napi_value jsthis;
|
|
{otype.abstract_base_c_type} * handle = NULL;
|
|
|
|
if (napi_get_cb_info (env, info, NULL, NULL, &jsthis, NULL) != napi_ok)
|
|
goto propagate_error;
|
|
|
|
handle = g_object_new ({otype_cprefix}_get_type (), NULL);
|
|
|
|
if (!fdn_object_wrap (env, jsthis, G_OBJECT (handle), fdn_object_finalize))
|
|
goto propagate_error;
|
|
|
|
napi_create_reference (env, jsthis, 1, &handle->wrapper);
|
|
|
|
return jsthis;
|
|
|
|
propagate_error:
|
|
{{
|
|
g_clear_object (&handle);
|
|
return NULL;
|
|
}}
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_abstract_base_gobject_glue(otype: ObjectType) -> str:
|
|
otype_cprefix = otype.abstract_base_c_symbol_prefix
|
|
ctype = otype.abstract_base_c_type
|
|
|
|
vmethod_lines = []
|
|
for m in otype.methods:
|
|
vmethod_lines += [
|
|
f"iface->{m.name} = {otype_cprefix}_{m.name};",
|
|
f"iface->{m.name}_finish = {otype_cprefix}_{m.name}_finish;",
|
|
]
|
|
vmethod_assignments = indent_c_code("\n".join(vmethod_lines), 1)
|
|
|
|
def calculate_indent(suffix: str) -> str:
|
|
return " " * (len(otype_cprefix) + len(suffix) + 2)
|
|
|
|
return f"""
|
|
static void
|
|
{otype_cprefix}_class_init ({ctype}Class * klass)
|
|
{{
|
|
GObjectClass * object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->dispose = {otype_cprefix}_dispose;
|
|
}}
|
|
|
|
static void
|
|
{otype_cprefix}_iface_init (gpointer g_iface,
|
|
{calculate_indent("_iface_init")}gpointer iface_data)
|
|
{{
|
|
{otype.type_struct} * iface = g_iface;
|
|
|
|
{vmethod_assignments}
|
|
}}
|
|
|
|
static void
|
|
{otype_cprefix}_init ({ctype} * self)
|
|
{{
|
|
self->disposed = FALSE;
|
|
}}
|
|
|
|
static void
|
|
{otype_cprefix}_dispose (GObject * object)
|
|
{{
|
|
{otype.abstract_base_c_type} * self = {otype.abstract_base_c_cast_macro} (object);
|
|
|
|
if (g_atomic_int_compare_and_exchange (&self->disposed, FALSE, TRUE) && !fdn_in_cleanup)
|
|
{{
|
|
napi_call_threadsafe_function ({otype_cprefix}_release_js_resources_tsfn, g_object_ref (self), napi_tsfn_blocking);
|
|
}}
|
|
|
|
G_OBJECT_CLASS ({otype_cprefix}_parent_class)->dispose (object);
|
|
}}
|
|
|
|
static void
|
|
{otype_cprefix}_release_js_resources (napi_env env,
|
|
{calculate_indent("_release_js_resources")}napi_value js_cb,
|
|
{calculate_indent("_release_js_resources")}void * context,
|
|
{calculate_indent("_release_js_resources")}void * data)
|
|
{{
|
|
{otype.abstract_base_c_type} * self = data;
|
|
|
|
napi_delete_reference (env, self->wrapper);
|
|
self->wrapper = NULL;
|
|
|
|
g_object_unref (self);
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_abstract_base_method_code(method: Method) -> str:
|
|
otype = method.object_type
|
|
operation_type_name = method.abstract_base_operation_type_name
|
|
otype_cprefix = otype.abstract_base_c_symbol_prefix
|
|
|
|
method_name_pascal = to_pascal_case(method.name)
|
|
method_cprefix = f"{otype_cprefix}_{method.name}"
|
|
|
|
def calculate_indent(suffix: str) -> str:
|
|
return " " * (len(otype_cprefix) + len(method.name) + len(suffix) + 3)
|
|
|
|
params = f",\n{calculate_indent('')}".join(method.param_ctypings)
|
|
finish_params = f",\n{calculate_indent('_finish')}".join(
|
|
method.finish_param_ctypings
|
|
)
|
|
|
|
storage_prefix = "operation->"
|
|
|
|
param_assignments = generate_abstract_base_input_parameter_assignment_code(
|
|
method, storage_prefix
|
|
)
|
|
param_conversions = generate_abstract_base_input_parameter_conversions_code(
|
|
method, storage_prefix
|
|
)
|
|
param_destructions = generate_parameter_destructions_code(method, storage_prefix)
|
|
cancellable_name = next(
|
|
(p.name for p in method.parameters if p.type.name == "Gio.Cancellable"), "NULL"
|
|
)
|
|
|
|
result_declaration = generate_return_variable_declaration(method)
|
|
result_conversion, result_destroy = generate_abstract_base_return_conversion_code(
|
|
method, "propagate_error"
|
|
)
|
|
|
|
finish_error = "error" if method.throws else "NULL"
|
|
finish_statement = f"g_task_propagate_pointer (G_TASK (result), {finish_error})"
|
|
retval = method.return_value
|
|
if retval is not None:
|
|
from_pointer_func = retval.type.from_pointer_func
|
|
if from_pointer_func is not None:
|
|
finish_statement = f"{from_pointer_func} ({finish_statement})"
|
|
finish_statement = f"return {finish_statement}"
|
|
finish_code = f"{finish_statement};"
|
|
|
|
one_newline = "\n"
|
|
two_newlines = "\n\n"
|
|
|
|
operation_free_function = f"""\
|
|
static void
|
|
{method_cprefix}_operation_free ({operation_type_name} * operation)
|
|
{{{indent_c_code(param_destructions, 1, prologue=one_newline)}
|
|
g_slice_free ({operation_type_name}, operation);
|
|
}}"""
|
|
|
|
return f"""
|
|
static void
|
|
{method_cprefix} ({params})
|
|
{{
|
|
{otype.abstract_base_c_type} * self;
|
|
{operation_type_name} * operation;
|
|
GTask * task;
|
|
|
|
self = {otype.abstract_base_c_cast_macro} ({method.cself_name});
|
|
|
|
operation = g_slice_new0 ({operation_type_name});
|
|
operation->self = self;{indent_c_code(param_assignments, 1, prologue=one_newline)}
|
|
|
|
task = g_task_new (self, {cancellable_name}, callback, user_data);
|
|
g_task_set_task_data (task, operation, (GDestroyNotify) {otype_cprefix}_{method.name}_operation_free);
|
|
|
|
napi_call_threadsafe_function ({otype_cprefix}_{method.name}_tsfn, task, napi_tsfn_blocking);
|
|
}}
|
|
|
|
{operation_free_function}
|
|
|
|
static void
|
|
{method_cprefix}_begin (napi_env env,
|
|
{calculate_indent("_begin")}napi_value js_cb,
|
|
{calculate_indent("_begin")}void * context,
|
|
{calculate_indent("_begin")}void * data)
|
|
{{
|
|
GTask * task = data;
|
|
{operation_type_name} * operation;
|
|
{otype.abstract_base_c_type} * self;
|
|
napi_value wrapper, method, args[{len(method.input_parameters)}], js_retval, then, then_args[2], then_retval;
|
|
|
|
operation = g_task_get_task_data (task);
|
|
self = operation->self;
|
|
|
|
if (napi_get_reference_value (env, self->wrapper, &wrapper) != napi_ok)
|
|
goto propagate_error;
|
|
|
|
if (napi_get_named_property (env, wrapper, "{method.prefixed_js_name}", &method) != napi_ok)
|
|
goto propagate_error;{indent_c_code(param_conversions, 1, prologue=two_newlines)}
|
|
|
|
if (napi_call_function (env, wrapper, method, G_N_ELEMENTS (args), args, &js_retval) != napi_ok)
|
|
goto propagate_error;
|
|
|
|
if (napi_get_named_property (env, js_retval, "then", &then) != napi_ok)
|
|
goto propagate_error;
|
|
|
|
napi_create_function (env, "on{method_name_pascal}Success", NAPI_AUTO_LENGTH, {otype_cprefix}_{method.name}_on_success, task, &then_args[0]);
|
|
napi_create_function (env, "on{method_name_pascal}Failure", NAPI_AUTO_LENGTH, {otype_cprefix}_{method.name}_on_failure, task, &then_args[1]);
|
|
|
|
if (napi_call_function (env, js_retval, then, G_N_ELEMENTS (then_args), then_args, &then_retval) != napi_ok)
|
|
goto propagate_error;
|
|
|
|
return;
|
|
|
|
propagate_error:
|
|
{{
|
|
napi_value js_error;
|
|
GError * error;
|
|
|
|
napi_get_and_clear_last_exception (env, &js_error);
|
|
fdn_error_from_value (env, js_error, &error);
|
|
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
|
|
return;
|
|
}}
|
|
}}
|
|
|
|
static napi_value
|
|
{method_cprefix}_on_success (napi_env env,
|
|
{calculate_indent("_on_success")}napi_callback_info info)
|
|
{{
|
|
size_t argc = 1;
|
|
napi_value js_retval;
|
|
GTask * task;{indent_c_code(result_declaration, 1, prologue=one_newline)}
|
|
gpointer raw_result;
|
|
|
|
if (napi_get_cb_info (env, info, &argc, &js_retval, NULL, (void **) &task) != napi_ok)
|
|
goto propagate_error;
|
|
if (argc != 1)
|
|
goto internal_error;
|
|
|
|
{indent_c_code(result_conversion, 1)}
|
|
|
|
g_task_return_pointer (task, raw_result, {result_destroy});
|
|
goto beach;
|
|
|
|
propagate_error:
|
|
{{
|
|
napi_value js_error;
|
|
GError * error;
|
|
|
|
napi_get_and_clear_last_exception (env, &js_error);
|
|
fdn_error_from_value (env, js_error, &error);
|
|
|
|
g_task_return_error (task, error);
|
|
|
|
goto beach;
|
|
}}
|
|
internal_error:
|
|
{{
|
|
g_task_return_new_error (task, FRIDA_ERROR, FRIDA_ERROR_INVALID_OPERATION,
|
|
"Internal error");
|
|
|
|
goto beach;
|
|
}}
|
|
beach:
|
|
{{
|
|
napi_value val;
|
|
|
|
g_object_unref (task);
|
|
|
|
napi_get_undefined (env, &val);
|
|
return val;
|
|
}}
|
|
}}
|
|
|
|
static napi_value
|
|
{method_cprefix}_on_failure (napi_env env,
|
|
{calculate_indent("_on_failure")}napi_callback_info info)
|
|
{{
|
|
size_t argc = 1;
|
|
napi_value js_error;
|
|
GTask * task;
|
|
GError * error;
|
|
|
|
if (napi_get_cb_info (env, info, &argc, &js_error, NULL, (void **) &task) != napi_ok)
|
|
goto propagate_error;
|
|
if (argc != 1)
|
|
goto internal_error;
|
|
|
|
if (!fdn_error_from_value (env, js_error, &error))
|
|
goto propagate_error;
|
|
|
|
g_task_return_error (task, error);
|
|
goto beach;
|
|
|
|
propagate_error:
|
|
{{
|
|
napi_value js_error;
|
|
GError * error;
|
|
|
|
napi_get_and_clear_last_exception (env, &js_error);
|
|
fdn_error_from_value (env, js_error, &error);
|
|
|
|
g_task_return_error (task, error);
|
|
|
|
goto beach;
|
|
}}
|
|
internal_error:
|
|
{{
|
|
g_task_return_new_error (task, FRIDA_ERROR, FRIDA_ERROR_INVALID_OPERATION,
|
|
"Internal error");
|
|
|
|
goto beach;
|
|
}}
|
|
beach:
|
|
{{
|
|
napi_value val;
|
|
|
|
g_object_unref (task);
|
|
|
|
napi_get_undefined (env, &val);
|
|
return val;
|
|
}}
|
|
}}
|
|
|
|
static {method.return_ctyping}
|
|
{method_cprefix}_finish ({finish_params})
|
|
{{
|
|
{finish_code}
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_abstract_base_input_parameter_assignment_code(
|
|
proc: Procedure, storage_prefix: str
|
|
) -> str:
|
|
assigments = [
|
|
generate_abstract_base_parameter_assignment_code(param, i, storage_prefix)
|
|
for i, param in enumerate(proc.input_parameters)
|
|
]
|
|
return "\n".join(assigments)
|
|
|
|
|
|
def generate_abstract_base_input_parameter_conversions_code(
|
|
proc: Procedure, storage_prefix: str
|
|
) -> str:
|
|
conversions = [
|
|
generate_abstract_base_parameter_conversion_code(param, i, storage_prefix)
|
|
for i, param in enumerate(proc.input_parameters)
|
|
]
|
|
return "\n\n".join(conversions)
|
|
|
|
|
|
def generate_abstract_base_parameter_conversion_code(
|
|
param: Parameter, index: int, storage_prefix: str
|
|
) -> str:
|
|
lval = f"{storage_prefix}{param.name}"
|
|
|
|
code = f"args[{index}] = fdn_{param.type.nick}_to_value (env, {lval});"
|
|
if param.nullable:
|
|
code = f"if ({lval} != NULL)\n {code}\nelse\n napi_get_null (env, &args[{index}]);"
|
|
|
|
return code
|
|
|
|
|
|
def generate_abstract_base_parameter_assignment_code(
|
|
param: Parameter, index: int, storage_prefix: str
|
|
) -> str:
|
|
lval = f"{storage_prefix}{param.name}"
|
|
|
|
copy_func = param.copy_func
|
|
if copy_func is not None:
|
|
if param.nullable and copy_func not in {"g_strdup", "g_strdupv"}:
|
|
return (
|
|
f"{lval} = ({param.name} != NULL) ? {copy_func} ({param.name}) : NULL;"
|
|
)
|
|
return f"{lval} = {copy_func} ({param.name});"
|
|
|
|
return f"{lval} = {param.name};"
|
|
|
|
|
|
def generate_abstract_base_return_conversion_code(
|
|
method: Method, invalid_label: str
|
|
) -> Tuple[str, str]:
|
|
retval = method.return_value
|
|
if retval is not None:
|
|
destroy_func = retval.destroy_func
|
|
if destroy_func is None:
|
|
destroy_func = "NULL"
|
|
|
|
result_conversion = (
|
|
f"{retval.type.to_pointer_func} (retval)"
|
|
if retval.type.to_pointer_func is not None
|
|
else "retval"
|
|
)
|
|
|
|
code = f"""\
|
|
if (!fdn_{retval.type.nick}_from_value (env, js_retval, &retval))
|
|
goto {invalid_label};
|
|
|
|
raw_result = {result_conversion};"""
|
|
|
|
if retval.nullable:
|
|
code = f"if (!fdn_is_null (js_result))\n{{ {indent_c_code(code, 1)}\n}} else {{\n raw_result = NULL;\n }}"
|
|
|
|
else:
|
|
code = "raw_result = NULL;"
|
|
destroy_func = "NULL"
|
|
|
|
return (code, destroy_func)
|
|
|
|
|
|
def generate_enum_registration_code(enum: Enumeration) -> str:
|
|
cprefix = enum.c_symbol_prefix
|
|
|
|
properties = []
|
|
for member in enum.members:
|
|
properties.append(
|
|
f'{{ "{member.js_name}", NULL, NULL, NULL, NULL, fdn_utf8_to_value (env, "{member.nick}"), napi_enumerable, NULL }}'
|
|
)
|
|
|
|
properties_str = ",\n ".join(properties)
|
|
|
|
def calculate_indent(suffix: str) -> str:
|
|
return " " * (len(cprefix) + len(suffix) + 2)
|
|
|
|
return f"""
|
|
static void
|
|
{cprefix}_register (napi_env env,
|
|
{calculate_indent("_register")}napi_value exports)
|
|
{{
|
|
napi_value enum_object;
|
|
napi_property_descriptor properties[] = {{
|
|
{properties_str}
|
|
}};
|
|
|
|
napi_create_object (env, &enum_object);
|
|
napi_define_properties (env, enum_object, G_N_ELEMENTS (properties), properties);
|
|
|
|
napi_set_named_property (env, exports, "{enum.js_name}", enum_object);
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_enum_conversion_functions(enum: Enumeration) -> str:
|
|
cprefix = enum.c_symbol_prefix
|
|
|
|
def calculate_indent(suffix: str) -> str:
|
|
return " " * (len(cprefix) + len(suffix) + 2)
|
|
|
|
return f"""
|
|
static gboolean
|
|
{cprefix}_from_value (napi_env env,
|
|
{calculate_indent("_from_value")}napi_value value,
|
|
{calculate_indent("_from_value")}{enum.c_type} * e)
|
|
{{
|
|
return fdn_enum_from_value (env, {enum.get_type} (), value, (gint *) e);
|
|
}}
|
|
|
|
static napi_value
|
|
{cprefix}_to_value (napi_env env,
|
|
{calculate_indent("_to_value")}{enum.c_type} e)
|
|
{{
|
|
return fdn_enum_to_value (env, {enum.get_type} (), e);
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_options_conversion_functions(otype: ObjectType) -> str:
|
|
cprefix = otype.c_symbol_prefix
|
|
|
|
def calculate_indent(suffix: str) -> str:
|
|
return " " * (len(cprefix) + len(suffix) + 2)
|
|
|
|
selection_code = ""
|
|
for method in otype.methods:
|
|
if not method.is_select_method:
|
|
continue
|
|
|
|
plural_noun = method.select_plural_noun
|
|
param_type = method.select_element_type
|
|
param_from_value = f"fdn_{param_type.nick}_from_value"
|
|
|
|
element_destroy_code = ""
|
|
destroy_func = param_type.destroy_func
|
|
if destroy_func is not None:
|
|
element_destroy_code = f"\n\n {destroy_func} (element);"
|
|
|
|
selection_code += f"""
|
|
|
|
{{
|
|
napi_value js_{plural_noun};
|
|
napi_valuetype value_type;
|
|
|
|
if (napi_get_named_property (env, value, "{plural_noun}", &js_{plural_noun}) != napi_ok)
|
|
goto propagate_error;
|
|
|
|
if (napi_typeof (env, js_{plural_noun}, &value_type) != napi_ok)
|
|
goto propagate_error;
|
|
|
|
if (value_type != napi_undefined)
|
|
{{
|
|
uint32_t length, i;
|
|
|
|
if (napi_get_array_length (env, js_{plural_noun}, &length) != napi_ok)
|
|
goto propagate_error;
|
|
|
|
for (i = 0; i != length; i++)
|
|
{{
|
|
napi_value js_element;
|
|
{param_type.c.replace('const ', '')} element;
|
|
|
|
if (napi_get_element (env, js_{plural_noun}, i, &js_element) != napi_ok)
|
|
goto propagate_error;
|
|
|
|
if (!{param_from_value} (env, js_element, &element))
|
|
goto propagate_error;
|
|
|
|
{method.c_identifier} (opts, element);{element_destroy_code}
|
|
}}
|
|
}}
|
|
}}"""
|
|
|
|
cleanup_code = ""
|
|
if selection_code:
|
|
cleanup_code += """
|
|
|
|
propagate_error:
|
|
{
|
|
g_object_unref (opts);
|
|
return FALSE;
|
|
}"""
|
|
|
|
return f"""
|
|
static gboolean
|
|
{cprefix}_from_value (napi_env env,
|
|
{calculate_indent("_from_value")}napi_value value,
|
|
{calculate_indent("_from_value")}{otype.c_type} ** options)
|
|
{{
|
|
{otype.c_type} * opts;
|
|
|
|
if (!fdn_options_from_value (env, {otype.get_type} (), value, (gpointer *) &opts))
|
|
return FALSE;{selection_code}
|
|
|
|
*options = opts;
|
|
return TRUE;{cleanup_code}
|
|
}}
|
|
"""
|
|
|
|
|
|
def generate_list_conversion_functions(otype: ObjectType) -> str:
|
|
cprefix = otype.c_symbol_prefix
|
|
|
|
size_method = next((method for method in otype.methods if method.name == "size"))
|
|
get_method = next((method for method in otype.methods if method.name == "get"))
|
|
|
|
element_type = get_method.return_value.type
|
|
|
|
def calculate_indent(suffix: str) -> str:
|
|
return " " * (len(cprefix) + len(suffix) + 2)
|
|
|
|
return f"""
|
|
static napi_value
|
|
{cprefix}_to_value (napi_env env,
|
|
{calculate_indent("_to_value")}{otype.c_type} * list)
|
|
{{
|
|
napi_value result;
|
|
gint size, i;
|
|
|
|
size = {size_method.c_identifier} (list);
|
|
napi_create_array_with_length (env, size, &result);
|
|
|
|
for (i = 0; i != size; i++)
|
|
{{
|
|
{element_type.c} handle = {get_method.c_identifier} (list, i);
|
|
napi_set_element (env, result, i, fdn_{element_type.nick}_to_value (env, handle));
|
|
g_object_unref (handle);
|
|
}}
|
|
|
|
return result;
|
|
}}
|
|
"""
|
|
|
|
|
|
def indent_ts_code(code: str, level: int, prologue: str = "") -> str:
|
|
prefix = (level * 4) * " "
|
|
return indent_code(code, prefix, prologue)
|
|
|
|
|
|
def indent_c_code(code: str, level: int, prologue: str = "") -> str:
|
|
prefix = (level * 2) * " "
|
|
return indent_code(code, prefix, prologue)
|
|
|
|
|
|
def indent_code(code: str, prefix: str, prologue: str = "") -> str:
|
|
if not code:
|
|
return ""
|
|
return prologue + textwrap.indent(code, prefix, lambda line: line.strip() != "")
|