Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix mypy compliance in Ghidra scripts #69

Merged
merged 2 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ max-line-length = 120

[tool.mypy]
check_untyped_defs = true
# excluded until fixed
exclude = "reccmp/ghidra_scripts/.*"

[[tool.mypy.overrides]]
module = ["ghidra", "ghidra.*"]
ignore_missing_imports = true
112 changes: 57 additions & 55 deletions reccmp/ghidra_scripts/import_functions_and_types_from_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
import ghidra
from lego_util.headers import * # pylint: disable=wildcard-import # these are just for headers
from reccmp.ghidra_scripts.lego_util.headers import * # pylint: disable=wildcard-import # these are just for headers


logger = logging.getLogger(__name__)
Expand All @@ -50,9 +49,45 @@ def reload_module(module: str):
importlib.reload(importlib.import_module(module))


reload_module("lego_util.statistics")
reload_module("lego_util.globals")
from lego_util.globals import GLOBALS
def add_python_path(path: Path):
"""
Scripts in Ghidra are executed from the tools/ghidra_scripts directory. We need to add
a few more paths to the Python path so we can import the other libraries.
"""
logger.info("Adding %s to Python Path", path)
assert path.exists()
sys.path.insert(1, str(path))


def find_and_add_venv_to_pythonpath():
path = Path(__file__).resolve()

# Add the virtual environment if we are in one, e.g. `.venv/Lib/site-packages/reccmp/ghidra_scripts/import_[...].py`
while not path.is_mount():
if path.name == "site-packages":
add_python_path(path)
return
path = path.parent

# Development setup: Running from the reccmp repository. The dependencies must be installed in a venv with name `.venv`.

# This one is needed when the reccmp project is installed in editable mode and we are running directly from the source
add_python_path(Path(__file__).parent.parent.parent)

# Now we add the virtual environment where the dependencies need to be installed
path = Path(__file__).resolve()
while not path.is_mount():
venv_candidate = path / ".venv"
if venv_candidate.exists():
site_packages = next(venv_candidate.glob("lib/**/site-packages/"), None)
if site_packages is not None:
add_python_path(site_packages)
return
path = path.parent

logger.warning(
"No virtual environment was found. This script might fail to find dependencies."
)


def setup_logging():
Expand All @@ -61,7 +96,6 @@ def setup_logging():
# formatter = logging.Formatter("%(name)s %(levelname)-8s %(message)s") # use this to identify loggers
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setFormatter(formatter)
logging.root.setLevel(GLOBALS.loglevel)
logging.root.addHandler(stdout_handler)

logger.info("Starting import...")
Expand All @@ -70,6 +104,13 @@ def setup_logging():
# This script can be run both from Ghidra and as a standalone.
# In the latter case, only the PDB parser will be used.
setup_logging()
find_and_add_venv_to_pythonpath()
reload_module("reccmp.ghidra_scripts.lego_util.statistics")
reload_module("reccmp.ghidra_scripts.lego_util.globals")
from reccmp.ghidra_scripts.lego_util.globals import GLOBALS

logging.root.setLevel(GLOBALS.loglevel)
Comment on lines +110 to +112
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This had to be reordered because we can't import GLOBALS before setting up the venv now. On the other hand, mypy doesn't allow me to import from lego_util.globals because the package can be referenced by two different paths.


try:
from ghidra.program.flatapi import FlatProgramAPI
from ghidra.util.exception import CancelledException
Expand All @@ -90,16 +131,6 @@ def get_repository_root():
return Path(__file__).absolute().parent.parent.parent


def add_python_path(path: Path):
"""
Scripts in Ghidra are executed from the tools/ghidra_scripts directory. We need to add
a few more paths to the Python path so we can import the other libraries.
"""
logger.info("Adding %s to Python Path", path)
assert path.exists()
sys.path.insert(1, str(path))


# We need to quote the types here because they might not exist when running without Ghidra
def import_function_into_ghidra(
api: "FlatProgramAPI",
Expand Down Expand Up @@ -207,37 +238,6 @@ def log_and_track_failure(
)


def find_and_add_venv_to_pythonpath():
path = Path(__file__).resolve()

# Add the virtual environment if we are in one, e.g. `.venv/Lib/site-packages/reccmp/ghidra_scripts/import_[...].py`
while not path.is_mount():
if path.name == "site-packages":
add_python_path(path)
return
path = path.parent

# Development setup: Running from the reccmp repository. The dependencies must be installed in a venv with name `.venv`.

# This one is needed when the reccmp project is installed in editable mode and we are running directly from the source
add_python_path(Path(__file__).parent.parent.parent)

# Now we add the virtual environment where the dependencies need to be installed
path = Path(__file__).resolve()
while not path.is_mount():
venv_candidate = path / ".venv"
if venv_candidate.exists():
site_packages = next(venv_candidate.glob("lib/**/site-packages/"), None)
if site_packages is not None:
add_python_path(site_packages)
return
path = path.parent

logger.warning(
"No virtual environment was found. This script might fail to find dependencies."
)


def find_build_target() -> "RecCmpBuiltTarget":
"""
Known issue: In order to use this script, `reccmp-build.yml` must be located in the same directory as `reccmp-project.yml`.
Expand Down Expand Up @@ -338,9 +338,7 @@ def main():
# sys.path is not reset after running the script, so we should restore it
sys_path_backup = sys.path.copy()
try:
find_and_add_venv_to_pythonpath()

import setuptools # pylint: disable=unused-import # required to fix a distutils issue in Python 3.12
import setuptools # type: ignore[import-untyped] # pylint: disable=unused-import # required to fix a distutils issue in Python 3.12

# Packages are imported down here because reccmp's dependencies are only available after the venv was added to the pythonpath
reload_module("reccmp.project.detect")
Expand All @@ -359,25 +357,29 @@ def main():
reload_module("reccmp.isledecomp.compare.db")

reload_module("lego_util.exceptions")
from lego_util.exceptions import Lego1Exception
from reccmp.ghidra_scripts.lego_util.exceptions import Lego1Exception

reload_module("lego_util.pdb_extraction")
from lego_util.pdb_extraction import (
from reccmp.ghidra_scripts.lego_util.pdb_extraction import (
PdbFunctionExtractor,
PdbFunction,
)

reload_module("lego_util.vtable_importer")
from lego_util.vtable_importer import import_vftables_into_ghidra
from reccmp.ghidra_scripts.lego_util.vtable_importer import (
import_vftables_into_ghidra,
)

if GLOBALS.running_from_ghidra:
reload_module("lego_util.ghidra_helper")

reload_module("lego_util.function_importer")
from lego_util.function_importer import PdbFunctionImporter
from reccmp.ghidra_scripts.lego_util.function_importer import (
PdbFunctionImporter,
)

reload_module("lego_util.type_importer")
from lego_util.type_importer import PdbTypeImporter
from reccmp.ghidra_scripts.lego_util.type_importer import PdbTypeImporter

if __name__ == "__main__":
main()
Expand Down
8 changes: 4 additions & 4 deletions reccmp/ghidra_scripts/lego_util/function_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@
ComponentOffsetSettingsDefinition,
)

from lego_util.pdb_extraction import (
from .pdb_extraction import (
PdbFunction,
CppRegisterSymbol,
CppStackSymbol,
)
from lego_util.ghidra_helper import (
from .ghidra_helper import (
add_data_type_or_reuse_existing,
get_namespace_and_name,
get_or_add_pointer_type,
)

from lego_util.exceptions import StackOffsetMismatchError, Lego1Exception
from lego_util.type_importer import PdbTypeImporter
from .exceptions import StackOffsetMismatchError, Lego1Exception
from .type_importer import PdbTypeImporter

logger = logging.getLogger(__name__)

Expand Down
15 changes: 8 additions & 7 deletions reccmp/ghidra_scripts/lego_util/ghidra_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@
import logging
import re

from lego_util.exceptions import (
ClassOrNamespaceNotFoundInGhidraError,
TypeNotFoundInGhidraError,
MultipleTypesFoundInGhidraError,
)
from lego_util.globals import GLOBALS

# Disable spurious warnings in vscode / pylance
# pyright: reportMissingModuleSource=false

from ghidra.program.flatapi import FlatProgramAPI
from ghidra.program.model.data import DataType, DataTypeConflictHandler, PointerDataType
from ghidra.program.model.symbol import Namespace

from .exceptions import (
ClassOrNamespaceNotFoundInGhidraError,
TypeNotFoundInGhidraError,
MultipleTypesFoundInGhidraError,
)
from .globals import GLOBALS


logger = logging.getLogger(__name__)


Expand Down
2 changes: 1 addition & 1 deletion reccmp/ghidra_scripts/lego_util/globals.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from enum import Enum
from dataclasses import dataclass, field
from lego_util.statistics import Statistics
from .statistics import Statistics


class SupportedModules(Enum):
Expand Down
2 changes: 1 addition & 1 deletion reccmp/ghidra_scripts/lego_util/pdb_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def __init__(self, compare: IsleCompare):
"STD Near": "__stdcall",
}

def _get_cvdump_type(self, type_name: str | None) -> dict[str, Any | None]:
def _get_cvdump_type(self, type_name: str | None) -> dict[str, Any] | None:
return (
None
if type_name is None
Expand Down
2 changes: 1 addition & 1 deletion reccmp/ghidra_scripts/lego_util/statistics.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from dataclasses import dataclass, field
import logging

from lego_util.exceptions import (
from .exceptions import (
TypeNotFoundInGhidraError,
ClassOrNamespaceNotFoundInGhidraError,
)
Expand Down
30 changes: 15 additions & 15 deletions reccmp/ghidra_scripts/lego_util/type_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,6 @@
# pylint: disable=too-many-return-statements # a `match` would be better, but for now we are stuck with Python 3.9
# pylint: disable=no-else-return # Not sure why this rule even is a thing, this is great for checking exhaustiveness

from lego_util.exceptions import (
TypeNotFoundError,
TypeNotFoundInGhidraError,
TypeNotImplementedError,
StructModificationError,
)
from lego_util.ghidra_helper import (
add_data_type_or_reuse_existing,
get_or_add_pointer_type,
get_ghidra_type,
get_or_create_namespace,
sanitize_name,
)
from lego_util.pdb_extraction import PdbFunctionExtractor

from ghidra.program.flatapi import FlatProgramAPI
from ghidra.program.model.data import (
ArrayDataType,
Expand All @@ -39,6 +24,21 @@

from reccmp.isledecomp.cvdump.types import VirtualBasePointer

from .exceptions import (
TypeNotFoundError,
TypeNotFoundInGhidraError,
TypeNotImplementedError,
StructModificationError,
)
from .ghidra_helper import (
add_data_type_or_reuse_existing,
get_or_add_pointer_type,
get_ghidra_type,
get_or_create_namespace,
sanitize_name,
)
from .pdb_extraction import PdbFunctionExtractor

logger = logging.getLogger(__name__)


Expand Down
5 changes: 3 additions & 2 deletions reccmp/ghidra_scripts/lego_util/vtable_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
from ghidra.program.flatapi import FlatProgramAPI
from ghidra.program.model.symbol import SourceType

from lego_util.ghidra_helper import get_namespace_and_name

from reccmp.isledecomp.compare.db import ReccmpEntity

from .ghidra_helper import get_namespace_and_name


logger = logging.getLogger(__name__)


Expand Down
Loading