angr.analyses.decompiler.edits

Knowledge-base edits for decompilation output: renaming, retyping, and commenting.

This layer is deliberately free of any UI or transport dependency so both the headless MCP server and angr-management drive the same code. It is not thread-safe: locking belongs to whoever owns the knowledge base’s lifetime.

exception angr.analyses.decompiler.edits.AmbiguousFunctionError

Bases: DecompilationEditError

More than one function matches the given name. Renames make this reachable.

exception angr.analyses.decompiler.edits.DecompilationEditError

Bases: AngrError

Base class for every failure raised by the decompilation edit layer.

class angr.analyses.decompiler.edits.EditHooks

Bases: Protocol

Notifications fired immediately before each mutation, while the old value is still readable from the knowledge base.

The method set mirrors angr-management’s plugin hooks one-for-one so its adapter is a pure forwarder. Firing before the mutation matches what the GUI’s own edit dialogs do, and is what lets a handler snapshot pre-edit state.

before_function_renamed(func, old_name, new_name)
Return type:

None

Parameters:
before_stack_var_renamed(func, offset, old_name, new_name)
Return type:

None

Parameters:
before_func_arg_renamed(func, arg_index, old_name, new_name)
Return type:

None

Parameters:
before_global_var_renamed(addr, old_name, new_name)
Return type:

None

Parameters:
before_stack_var_retyped(func, offset, old_type, new_type)
Return type:

None

Parameters:
before_func_arg_retyped(func, arg_index, old_type, new_type)
Return type:

None

Parameters:
before_global_var_retyped(addr, old_type, new_type)
Return type:

None

Parameters:
before_other_var_retyped(var, old_type, new_type)
Return type:

None

Parameters:
before_function_retyped(func, old_proto, new_proto)
Return type:

None

Parameters:
before_comment_changed(addr, old, new, created, decomp)
Return type:

None

Parameters:
__init__(*args, **kwargs)
class angr.analyses.decompiler.edits.EditResult

Bases: object

The outcome of a single edit. changed is False when the edit was a no-op.

changed: bool
kind: str
func_addr: int | None = None
old: Any = None
new: Any = None
refresh: Refresh
detail: dict[str, Any]
__init__(changed, kind, func_addr=None, old=None, new=None, refresh=<factory>, detail=<factory>)
Parameters:
Return type:

None

exception angr.analyses.decompiler.edits.FunctionNotFoundError

Bases: DecompilationEditError

No function matches the given address or name.

exception angr.analyses.decompiler.edits.InvalidNameError

Bases: DecompilationEditError

The requested name is not a usable identifier.

exception angr.analyses.decompiler.edits.NameCollisionError

Bases: DecompilationEditError

The requested name is already bound to a different function, variable, or label.

exception angr.analyses.decompiler.edits.NotDecompiledError

Bases: DecompilationEditError

The function has no cached decompilation, so there is nothing to edit.

class angr.analyses.decompiler.edits.NullEditHooks

Bases: object

A concrete no-op implementation. Subclass it so an adapter only overrides what it needs.

before_function_renamed(func, old_name, new_name)
Return type:

None

Parameters:
before_stack_var_renamed(func, offset, old_name, new_name)
Return type:

None

Parameters:
before_func_arg_renamed(func, arg_index, old_name, new_name)
Return type:

None

Parameters:
before_global_var_renamed(addr, old_name, new_name)
Return type:

None

Parameters:
before_stack_var_retyped(func, offset, old_type, new_type)
Return type:

None

Parameters:
before_func_arg_retyped(func, arg_index, old_type, new_type)
Return type:

None

Parameters:
before_global_var_retyped(addr, old_type, new_type)
Return type:

None

Parameters:
before_other_var_retyped(var, old_type, new_type)
Return type:

None

Parameters:
before_function_retyped(func, old_proto, new_proto)
Return type:

None

Parameters:
before_comment_changed(addr, old, new, created, decomp)
Return type:

None

Parameters:
class angr.analyses.decompiler.edits.Refresh

Bases: object

What a caller has to redo after an edit.

Lets a UI pick between re-rendering text, rebuilding the codegen AST, and a full re-decompilation instead of guessing. text_stale_all means every cached decompilation’s rendered text is stale (a function rename changes call sites everywhere), but the caller should re-render lazily: eagerly re-rendering the whole cache would thrash its LRU.

text_stale: frozenset[int] = frozenset({})
text_stale_all: bool = False
reanalyze: frozenset[int] = frozenset({})
redecompile: frozenset[int] = frozenset({})
function_list_dirty: bool = False
disassembly_dirty: bool = False
merge(other)
Return type:

Refresh

Parameters:

other (Refresh)

__init__(text_stale=frozenset({}), text_stale_all=False, reanalyze=frozenset({}), redecompile=frozenset({}), function_list_dirty=False, disassembly_dirty=False)
Parameters:
Return type:

None

class angr.analyses.decompiler.edits.ResolvedVariable

Bases: object

A pseudocode display name resolved to the objects an edit needs.

variable is the concrete/SSA variable that set_variable_type expects; unified is the unified variable that a rename mutates (None for globals, which have no unified form).

kind: Literal['argument', 'local', 'global']
variable: SimVariable
unified: SimVariable | None
cvar: CVariable | None = None
arg_index: int | None = None
stack_offset: int | None = None
global_addr: int | None = None
ambiguous: bool = False
property name: str | None
property storage: str
detail()
Return type:

dict

__init__(kind, variable, unified, cvar=None, arg_index=None, stack_offset=None, global_addr=None, ambiguous=False)
Parameters:
Return type:

None

exception angr.analyses.decompiler.edits.TypeParseError

Bases: DecompilationEditError

A C type declaration or function signature could not be parsed.

exception angr.analyses.decompiler.edits.UnsupportedEditError

Bases: DecompilationEditError

The requested edit is not supported for this kind of target.

exception angr.analyses.decompiler.edits.VariableNotFoundError

Bases: DecompilationEditError

No variable in the decompilation matches the given display name.

candidates carries the names that are available, so a caller that failed can correct itself without a second round trip.

angr.analyses.decompiler.edits.concrete_variables(varman, resolved)

Every SSA variable sharing the resolved variable’s unified form.

Retyping applies to all of them. Computed here rather than relying on set_variable_type(all_unified=True), which silently does nothing when the variable it is handed is not a key in the SSA-to-unified map.

Return type:

list[SimVariable]

Parameters:

resolved (ResolvedVariable)

angr.analyses.decompiler.edits.get_cache(kb, func_addr, flavor='pseudocode')

Return the cached decompilation for a function, or None if there is none.

Return type:

DecompilationCache | None

Parameters:
angr.analyses.decompiler.edits.global_variable_at(kb, addr)

The global SimVariable recorded at an address, if variable recovery produced one.

Parameters:
angr.analyses.decompiler.edits.invalidate(kb, func_addr, *, flavors=None, drop_variables=False)

Drop cached decompilations for a function.

Parameters:
  • flavors (Iterable[str] | None) – Flavors to drop; None drops every flavor currently cached. A prototype change must drop all of them, or a non-pseudocode flavor silently retains the old signature.

  • drop_variables (bool) – Also drop kb.dec_variables[func_addr]. Required for new argument names to take effect, but it discards every rename and manual type for the function – see snapshot_user_edits().

  • kb (KnowledgeBase)

  • func_addr (int)

Return type:

None

angr.analyses.decompiler.edits.list_variable_names(codegen, kb=None, func_addr=None)

Every display name addressable in this decompilation, for error messages.

Return type:

list[str]

Parameters:
angr.analyses.decompiler.edits.parse_address(value)

Parse an address given as an int or a string. Accepts 0x-prefixed hex and decimal.

Return type:

int

Parameters:

value (str | int)

angr.analyses.decompiler.edits.reflow_types(project, func, *, kb=None, flavor='pseudocode', rerender=True)

Re-run type inference over the cached constraints and refresh the rendered code.

Separate from set_variable_type() so a batch can retype many variables and reflow once; per-variable reflow would re-run Typehoon N times.

This is not a re-decompilation: the AST is untouched, so a retype that should change how an access renders (a struct field, an array index) needs a full re-decompilation instead.

Parameters:
angr.analyses.decompiler.edits.rename_function(project, func, new_name, *, kb=None, hooks=None, flavor='pseudocode', allow_overwrite=True, strict_names=True, rerender=True)

Rename a function.

Nothing is invalidated: other functions’ cached ASTs reference the same Function object, so only their rendered text goes stale. That is reported through Refresh.text_stale_all for the caller to act on lazily.

Return type:

EditResult

Parameters:
angr.analyses.decompiler.edits.rename_global(project, addr, new_name, *, kb=None, hooks=None, allow_overwrite=True, strict_names=True)

Rename a global by address, without needing a function whose decompilation shows it.

Return type:

EditResult

Parameters:
angr.analyses.decompiler.edits.rename_variable(project, func, variable_name, new_name, *, kb=None, hooks=None, flavor='pseudocode', allow_overwrite=True, strict_names=True, rerender=True, codegen=None)

Rename a local, an argument, or a global as it appears in a function’s decompilation.

Sets renamed on the variable, without which a later re-decompilation overwrites the name.

Return type:

EditResult

Parameters:
angr.analyses.decompiler.edits.require_cache(kb, func_addr, flavor='pseudocode')

Return the cached decompilation, raising NotDecompiledError if it is missing or empty.

Return type:

DecompilationCache

Parameters:
angr.analyses.decompiler.edits.resolve_function(kb, *, address=None, name=None, containing=True)

Find a function by address or by name.

Parameters:
  • containing (bool) – If True, an address inside a function resolves to that function rather than requiring the exact entry address.

  • kb (KnowledgeBase)

  • address (str | int | None)

  • name (str | None)

Return type:

Function

angr.analyses.decompiler.edits.resolve_variable(kb, func_addr, display_name, *, codegen=None, flavor='pseudocode')

Resolve a name as it appears in the pseudocode to the underlying variable.

Precedence is argument > local > global. A name matching more than one variable within the same bucket resolves deterministically and sets ambiguous, rather than failing – a batch edit should be able to report the collision and continue.

Return type:

ResolvedVariable

Parameters:
angr.analyses.decompiler.edits.restore_user_edits(kb, func_addr, snapshot)

Re-apply a snapshot_user_edits() result after re-decompilation.

Best-effort: idents can change across a re-decompile, so the unmatched ones are returned rather than silently dropped.

Return type:

tuple[int, list[str]]

Parameters:
angr.analyses.decompiler.edits.set_comment(project, addr, comment, *, kind=None, kb=None, hooks=None, flavor='pseudocode', mirror_to_pseudocode=True, snap=True, rerender=True)

Set the comment at an address, or clear it with an empty string or None.

The comment goes into kb.comments, which the disassembly renders and which the decompiler reads for the function header. Per-statement pseudocode comments live in codegen.stmt_comments instead, so both are written – except at the function entry, where kb.comments is already what the header renders and mirroring would show the comment twice.

kind sets the comment’s display kind; None keeps whatever kind the comment already has. Clearing a comment always clears its kind.

Return type:

EditResult

Parameters:
angr.analyses.decompiler.edits.set_function_prototype(project, func, prototype, *, kb=None, hooks=None, flavor='pseudocode', invalidate_cache=True, preserve_user_edits=True, redecompile=False)

Set a function’s prototype.

The function name inside the signature is ignored; use rename_function() to rename.

Dropping kb.dec_variables is what makes new argument names take effect, but it also discards every rename and manual type for the function. Those are snapshotted and, when this function re-decompiles, restored. Otherwise the snapshot is returned in detail["user_edits"] so a caller that decompiles asynchronously can restore it once its own job finishes.

Return type:

EditResult

Parameters:
angr.analyses.decompiler.edits.set_global_type(project, addr, c_type, *, kb=None, hooks=None)

Set the type of a global by address.

Return type:

EditResult

Parameters:
angr.analyses.decompiler.edits.set_variable_type(project, func, variable_name, c_type, *, kb=None, hooks=None, flavor='pseudocode', reflow=True, rerender=True, allow_prototype_change=True, codegen=None)

Change the type of a local, an argument, or a global.

Arguments are retyped by rewriting the function prototype, which requires a re-decompilation – the returned Refresh says so. Pass allow_prototype_change=False to refuse instead.

Return type:

EditResult

Parameters:
angr.analyses.decompiler.edits.snapshot_user_edits(kb, func_addr)

Capture user renames and manual types for a function, keyed by SimVariable.ident.

Used to survive the dec_variables drop that a prototype change requires.

Return type:

dict[str, tuple[str | None, SimType | None]]

Parameters:
angr.analyses.decompiler.edits.validate_name(name, *, strict=True)

Reject names that cannot be used as identifiers.

strict requires a C identifier. Without it only whitespace-free names are required, which still admits things like int; that render as uncompilable C – so strict is the default.

Return type:

None

Parameters:

Submodules

cache

Decompilation-cache access for the edit layer.

errors

hooks

ops

The mutating operations of the edit layer.

resolve

Resolution helpers shared by every edit operation: address-or-name to Function, and pseudocode display name to the underlying SimVariable.

results