Files
revng-revng/python/revng/support/artifacts.py
Giacomo Vercesi 132d891e7c Cleanup revng.support.artifacts
Remove a few `Artifact` classes from `revng.support.artifact`. These
were needed to handle the idiosyncrasies of the legacy pipeline. Some of
the logic has been reworked into the ptml code.
2026-04-09 17:07:45 +02:00

112 lines
3.4 KiB
Python

#
# This file is distributed under the MIT License. See LICENSE.md for details.
#
from __future__ import annotations
import shutil
import subprocess
import sys
import tarfile
from io import BytesIO
from pathlib import Path
from tempfile import NamedTemporaryFile, TemporaryDirectory
from typing import IO, Dict, Type, TypeVar, Union
import pycparser
from revng.ptml.parser import PTMLDocument
from revng.ptml.parser import parse as ptml_parse
from revng.ptml.printer import ColorMode, ptml_print
from revng.support import get_llvmcpy
T = TypeVar("T")
class Artifact:
_CHILDREN: Dict[str, Type[Artifact]] = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
mimes = getattr(cls, "MIMES", None)
if mimes is not None:
Artifact._CHILDREN.update({mime: cls for mime in mimes})
@classmethod
def make(cls, data: bytes, mime: str) -> Artifact:
class_ = cls._CHILDREN.get(mime, cls)
return class_(data, mime)
# This class and all its subclasses have the same constructor
def __init__(self, data: bytes, mime: str):
# If we're in a subclass check that the mime matches with the MIMES
# filed in the class
if type(self) is not Artifact:
assert mime in self.__class__.MIMES # type: ignore[attr-defined]
self._data = data
self._mime = mime
# Common methods
@property
def raw_data(self) -> bytes:
return self._data
def write_to_disk(self, path: Union[str, Path]):
with open(path, "wb") as f:
f.write(self._data)
class PTMLArtifact(Artifact):
MIMES = ("text/plain+ptml", "text/x.c+ptml", "text/x.asm+ptml", "text/x.hexdump+ptml")
def parse(self) -> PTMLDocument:
return ptml_parse(self._data)
def print( # noqa: A003
self,
output: IO[str] = sys.stdout,
color: ColorMode = ColorMode.Autodetect,
):
ptml_print(self._data, output, color)
class LLVMArtifact(Artifact):
MIMES = ("application/x.llvm.bc",)
def module(self, name: str = "module"):
llvmcpy = get_llvmcpy()
context = llvmcpy.get_global_context()
buffer = llvmcpy.create_memory_buffer_with_memory_range_copy(
self._data, len(self._data), name
)
return context.parse_ir(buffer)
class ImageArtifact(Artifact):
MIMES = ("image/svg",)
def show(self):
with NamedTemporaryFile("wb", prefix="image-artifact-") as f:
f.write(self._data)
f.flush()
subprocess.run(["xdg-open", f.name])
class RecompilableArchiveArtifact(Artifact):
MIMES = ("application/x.recompilable-archive",)
def parse(self) -> pycparser.c_ast.FileAST:
with TemporaryDirectory() as temp_dir:
with tarfile.open(fileobj=BytesIO(self._data), mode="r:*") as tar:
tar.extractall(path=temp_dir)
c_file = Path(temp_dir) / "decompiled/functions.c"
args = ["-E", "-DDISABLE_ATTRIBUTES", "-DDISABLE_FLOAT16"]
for executable in ("cpp", "clang-cpp", "gcc", "clang"):
if shutil.which(executable) is not None:
processed_text = pycparser.preprocess_file(c_file, executable, args)
break
else:
raise ValueError("No suitable C preprocessor found")
return pycparser.CParser().parse(processed_text, str(c_file))