Source code for binaryninja.lineformatter

# Copyright (c) 2025 Vector 35 Inc
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

import ctypes
import traceback
from dataclasses import dataclass
from typing import List, Optional, Union

# Binary Ninja components
import binaryninja
from . import _binaryninjacore as core
from . import function
from . import highlevelil
from . import highlight
from . import languagerepresentation
from .log import log_error
from .enums import HighlightStandardColor


[docs] @dataclass(frozen=True) class LineFormatterSettings: hlil: highlevelil.HighLevelILFunction desired_line_length: int minimum_content_length: int tab_width: int language_name: Optional[str] comment_start_string: str comment_end_string: str annotation_start_string: str annotation_end_string: str
[docs] @staticmethod def default(settings: Optional['function.DisassemblySettings'], hlil: 'highlevelil.HighLevelILFunction') -> 'LineFormatterSettings': """ Gets the default line formatter settings for High Level IL code. """ if settings is not None: settings = settings.handle api_obj = core.BNGetDefaultLineFormatterSettings(settings, hlil.handle) result = LineFormatterSettings._from_core_struct(api_obj[0]) core.BNFreeLineFormatterSettings(api_obj) return result
[docs] @staticmethod def language_representation_settings( settings: Optional['function.DisassemblySettings'], func: 'languagerepresentation.LanguageRepresentationFunction' ) -> 'LineFormatterSettings': """ Gets the default line formatter settings for a language representation function. """ if settings is not None: settings = settings.handle api_obj = core.BNGetLanguageRepresentationLineFormatterSettings(settings, func.handle) result = LineFormatterSettings._from_core_struct(api_obj[0]) core.BNFreeLineFormatterSettings(api_obj) return result
@staticmethod def _from_core_struct(settings: core.BNLineFormatterSettings) -> 'LineFormatterSettings': if len(settings.languageName) == 0: language_name = None else: language_name = settings.languageName hlil = highlevelil.HighLevelILFunction(handle=core.BNNewHighLevelILFunctionReference(settings.highLevelIL)) return LineFormatterSettings( hlil, settings.desiredLineLength, settings.minimumContentLength, settings.tabWidth, language_name, settings.commentStartString, settings.commentEndString, settings.annotationStartString, settings.annotationEndString ) def _to_core_struct(self) -> core.BNLineFormatterSettings: result = core.BNLineFormatterSettings() result.highLevelIL = self.hlil.handle result.desiredLineLength = self.desired_line_length result.minimumContentLength = self.minimum_content_length result.tabWidth = self.tab_width result.languageName = self.language_name if self.language_name is not None else "" result.commentStartString = self.comment_start_string result.commentEndString = self.comment_end_string result.annotationStartString = self.annotation_start_string result.annotationEndString = self.annotation_end_string return result
class _LineFormatterMetaClass(type): def __iter__(self): binaryninja._init_plugins() count = ctypes.c_ulonglong() types = core.BNGetLineFormatterList(count) assert types is not None, "core.BNGetLineFormatterList returned None" try: for i in range(0, count.value): yield CoreLineFormatter(handle=types[i]) finally: core.BNFreeLineFormatterList(types) def __getitem__(cls, value): binaryninja._init_plugins() lang = core.BNGetLineFormatterByName(str(value)) if lang is None: raise KeyError("'%s' is not a valid formatter" % str(value)) return CoreLineFormatter(handle=lang)
[docs] class LineFormatter(metaclass=_LineFormatterMetaClass): """ ``class LineFormatter`` represents a custom line formatter, which can reformat code in High Level IL and high level language representations. """ _registered_formatters = [] formatter_name = None def __init__(self, handle=None): if handle is not None: self.handle = core.handle_of_type(handle, core.BNLineFormatter)
[docs] def register(self): """Registers the line formatter.""" if self.__class__.formatter_name is None: raise ValueError("formatter_name is missing") self._cb = core.BNCustomLineFormatter() self._cb.context = 0 self._cb.formatLines = self._cb.formatLines.__class__(self._format_lines) self._cb.freeLines = self._cb.freeLines.__class__(self._free_lines) self.handle = core.BNRegisterLineFormatter(self.__class__.formatter_name, self._cb) self.__class__._registered_formatters.append(self)
def _format_lines( self, ctxt, in_lines, in_count: int, settings: core.BNLineFormatterSettingsHandle, out_count: ctypes.POINTER(ctypes.c_ulonglong) ): try: settings = settings[0] if len(settings.languageName) == 0: language_name = None else: language_name = settings.languageName hlil = highlevelil.HighLevelILFunction(handle=core.BNNewHighLevelILFunctionReference(settings.highLevelIL)) settings = LineFormatterSettings( hlil, settings.desiredLineLength, settings.minimumContentLength, settings.tabWidth, language_name, settings.commentStartString, settings.commentEndString, settings.annotationStartString, settings.annotationEndString ) lines = [] if in_lines is not None: for i in range(0, in_count): addr = in_lines[i].addr if in_lines[i].instrIndex != 0xffffffffffffffff: il_instr = hlil[in_lines[i].instrIndex] # type: ignore else: il_instr = None color = highlight.HighlightColor._from_core_struct(in_lines[i].highlight) tokens = function.InstructionTextToken._from_core_struct(in_lines[i].tokens, in_lines[i].count) lines.append(function.DisassemblyTextLine(tokens, addr, il_instr, color)) lines = self.format_lines(lines, settings) out_count[0] = len(lines) self.line_buf = (core.BNDisassemblyTextLine * len(lines))() for i in range(len(lines)): line = lines[i] color = line.highlight if not isinstance(color, HighlightStandardColor) and not isinstance(color, highlight.HighlightColor): raise ValueError("Specified color is not one of HighlightStandardColor, highlight.HighlightColor") if isinstance(color, HighlightStandardColor): color = highlight.HighlightColor(color) self.line_buf[i].highlight = color._to_core_struct() if line.address is None: if len(line.tokens) > 0: self.line_buf[i].addr = line.tokens[0].address else: self.line_buf[i].addr = 0 else: self.line_buf[i].addr = line.address if line.il_instruction is not None: self.line_buf[i].instrIndex = line.il_instruction.instr_index else: self.line_buf[i].instrIndex = 0xffffffffffffffff self.line_buf[i].count = len(line.tokens) self.line_buf[i].tokens = function.InstructionTextToken._get_core_struct(line.tokens) return ctypes.cast(self.line_buf, ctypes.c_void_p).value except: log_error(traceback.format_exc()) out_count[0] = 0 return None def _free_lines(self, ctxt, lines, count): self.line_buf = None
[docs] def format_lines( self, in_lines: List['function.DisassemblyTextLine'], settings: 'LineFormatterSettings' ) -> List['function.DisassemblyTextLine']: """ Reformats the given list of lines. Returns a new list of lines containing the reformatted code. """ raise NotImplementedError
@property def name(self) -> str: if hasattr(self, 'handle'): return core.BNGetLineFormatterName(self.handle) return self.__class__.formatter_name def __repr__(self): return f"<LineFormatter: {self.name}>"
_formatter_cache = {}
[docs] class CoreLineFormatter(LineFormatter): def __init__(self, handle: core.BNLineFormatter): super(CoreLineFormatter, self).__init__(handle=handle) if type(self) is CoreLineFormatter: global _formatter_cache _formatter_cache[ctypes.addressof(handle.contents)] = self
[docs] def format_lines( self, in_lines: List['function.DisassemblyTextLine'], settings: 'LineFormatterSettings' ) -> List['function.DisassemblyTextLine']: line_buf = (core.BNDisassemblyTextLine * len(in_lines))() for i in range(len(in_lines)): line = in_lines[i] color = line.highlight if not isinstance(color, HighlightStandardColor) and not isinstance(color, highlight.HighlightColor): raise ValueError("Specified color is not one of HighlightStandardColor, highlight.HighlightColor") if isinstance(color, HighlightStandardColor): color = highlight.HighlightColor(color) line_buf[i].highlight = color._to_core_struct() if line.address is None: if len(line.tokens) > 0: line_buf[i].addr = line.tokens[0].address else: line_buf[i].addr = 0 else: line_buf[i].addr = line.address if line.il_instruction is not None: line_buf[i].instrIndex = line.il_instruction.instr_index else: line_buf[i].instrIndex = 0xffffffffffffffff line_buf[i].count = len(line.tokens) line_buf[i].tokens = function.InstructionTextToken._get_core_struct(line.tokens) count = ctypes.c_ulonglong() lines = core.BNFormatLines(self.handle, line_buf, len(in_lines), settings._to_core_struct(), count) result = [] if lines is not None: result = [] for i in range(0, count.value): addr = lines[i].addr if lines[i].instrIndex != 0xffffffffffffffff: il_instr = settings.hlil[lines[i].instrIndex] # type: ignore else: il_instr = None color = highlight.HighlightColor._from_core_struct(lines[i].highlight) tokens = function.InstructionTextToken._from_core_struct(lines[i].tokens, lines[i].count) result.append(function.DisassemblyTextLine(tokens, addr, il_instr, color)) core.BNFreeDisassemblyTextLines(lines, count.value) return result
@classmethod def _from_cache(cls, handle) -> 'LineFormatter': """ Look up a representation type from a given BNLineFormatter handle :param handle: BNLineFormatter pointer :return: Formatter instance responsible for this handle """ global _formatter_cache return _formatter_cache.get(ctypes.addressof(handle.contents)) or cls(handle)