Files
revng-revng/share/revng/test/tests/scripting.py
Giacomo Vercesi 3e3779498e Implement python interface for revng
Add a python interface (`revng.profile`) for interacting with the rev.ng
infrastructure as a whole; either through the CLI (`CLIProject`) or the
GraphQL API (`DaemonProject`).
2025-05-07 10:48:51 +02:00

167 lines
5.7 KiB
Python
Executable File

#!/usr/bin/env python3
#
# This file is distributed under the MIT License. See LICENSE.md for details.
#
import argparse
import re
from typing import Callable, Dict, List, Mapping
from revng.model import MetaAddress, MetaAddressType # type: ignore[attr-defined]
from revng.project import CLIProject, LocalDaemonProject, Project
from revng.support.artifacts import PTMLArtifact
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--resume", help="Path to the resume directory", required=True)
parser.add_argument("--binary", help="Path to the Binary", required=True)
group = parser.add_mutually_exclusive_group()
group.add_argument("--cli", help="Run revng cli", action="store_true")
group.add_argument("--daemon", help="Run revng daemon", action="store_true")
return parser.parse_args()
def main():
args = parse_args()
if args.cli:
def project_getter():
return CLIProject(args.resume)
elif args.daemon:
def project_getter():
return LocalDaemonProject(args.resume, connection_retries=100)
else:
raise ValueError("The script expects either --cli or --daemon")
run_test(project_getter, args.binary)
def run_test(project_getter: Callable[[], Project], binary: str):
project = project_getter()
project.import_and_analyze(binary)
# Get the first function with `Name`
function_original_name = None
function_idx = 0
function_entry = None
for idx, function in enumerate(project.model.Functions):
if function.Name != "":
function_original_name = function.Name
function_idx = idx
function_entry = function.Entry
break
assert function_original_name is not None
# Run an artefact on `TypeDefinitions`
project.model.TypeDefinitions[1].get_artifact("emit-type-definitions")
all_functions_entry = [function.Entry for function in project.model.Functions]
# Assert that when we get the artifact for a single function we get
# the result of only that function
result1 = project.model.Functions[function_idx].get_artifact("disassemble")
assert isinstance(result1, PTMLArtifact)
parsed1 = result1.parse()
re_match = re.search(function_original_name, parsed1.text)
assert re_match is not None
for entry in all_functions_entry:
if entry == function_entry:
continue
re_match = re.search(str(entry), parsed1.text)
assert re_match is None
# Check that when we get the artifact of all the function we get
# the result for all the functions
result2 = project.get_artifact("disassemble")
assert isinstance(result2, Mapping)
assert set(result2.keys()) == set(map(str, all_functions_entry))
for value in result2.values():
assert isinstance(value, PTMLArtifact)
value.parse()
# Change the function name, get an artifact and make sure that the
# parsed result contains the new name
function_new_name1 = "lol"
project.model.Functions[function_idx].Name = function_new_name1
project.commit()
result3 = project.model.Functions[function_idx].get_artifact("disassemble")
assert isinstance(result3, PTMLArtifact)
parsed3 = result3.parse()
re_match = re.search(function_new_name1, parsed3.text)
assert re_match is not None
# Change function name again and commit
function_new_name2 = "wow"
project.model.Functions[function_idx].Name = function_new_name2
project.commit()
# Test revert
function_new_name3 = "heh"
project.model.Functions[function_idx].Name = function_new_name3
project.revert()
assert project.model.Functions[function_idx].Name == function_new_name2
# Stop daemon if `DaemonProject` otherwise the daemon will
# not persist the data on disk
if isinstance(project, LocalDaemonProject):
project.stop_daemon()
# Load the project again, this should also load the model and
# pipeline description
project = project_getter()
project.set_binary_path(binary)
# make sure that the second name changes was commit
assert project.model.Functions[function_idx].Name == function_new_name2
# Get the function by passing a MetaAddress
function = str(project.model.Functions[0].Entry).split(":")
address = MetaAddress(int(function[0], 16), MetaAddressType[function[1]])
# Get multiple artifacts
result = project.get_artifacts(
{
"disassemble": [project.model.Functions[address], project.model.Functions[1]],
"decompile": None,
}
)
assert isinstance(result["disassemble"], Mapping)
for value in result["disassemble"].values():
assert isinstance(value, PTMLArtifact)
value.parse()
assert isinstance(result["decompile"], Mapping)
for value in result["decompile"].values():
assert isinstance(value, PTMLArtifact)
value.parse()
# Run an analysis with targets
analysis_name = "detect-stack-size"
targets: Dict[str, List[str]] = {}
for input_, kinds in project.get_analysis_inputs(analysis_name).items():
targets[input_] = []
for kind in kinds:
if kind == "root":
targets[input_].append(f":{kind}")
else:
targets[input_].extend(
f"{_target.key()}:{kind}" for _target in project.model.Functions
)
if isinstance(project, CLIProject):
# TODO: drop this if when CLI supports passing targets for analyses
project.analyze(analysis_name, {})
else:
project.analyze(analysis_name, targets)
# Run an analysis without targets
project.analyze(analysis_name)
if __name__ == "__main__":
main()