Skip to content

ghidra-stubs mypy Python type stubs problems #8357

@widberg

Description

@widberg

Describe the bug
Some valid scripts do not pass type checking due to problems with the generated mypy type stubs.

To Reproduce
Run the example scripts below through mypy with --strict.

Expected behavior
I expected the example scripts to pass type checking.

Environment:

  • OS: Ubuntu 22.04.5 LTS
  • Java Version: openjdk 21.0.7 2025-04-15
  • Ghidra Version: 11.4
  • Ghidra Origin: GitHub Releases
  • Python: 3.8.20
  • mypy: 1.14.1
  • ghidra-stubs: 11.4
  • ghidra-stubs Origin: ghidra_11.4_PUBLIC/docs/ghidra_stubs/ghidra_stubs-11.4-py3-none-any.whl

Additional context
I did see the open issue #8018 but it did not seem related enough to warrant commenting this there.

Examples

1. No Typed Parameters and Missing None Return Type

type checking this script

import ghidra.features.bsim.query.protocol as ghprotocol

insertreq = ghprotocol.InsertRequest()

results in

test.py:3: error: Call to untyped function "InsertRequest" in typed context  [no-untyped-call]

even though a type stub for InsertRequest exists

class InsertRequest(BSimQuery[ResponseInsert]):
    def __init__(self):
        ...

this error is because the __init__ method has no typed parameters and no return type, so mypy counts it as untyped. Changing this to

class InsertRequest(BSimQuery[ResponseInsert]):
    def __init__(self) -> None:
        ...

fixes the problem, as described in https://stackoverflow.com/a/62615728 and https://stackoverflow.com/a/46779290. It looks like generating return types for void functions is skipped in the typestub code, which seems intentional so maybe this is due to a version difference with mypy.

2. Overloading Inherited Methods

If I'm understanding this mypy issue automatic inheritance of overloaded signatures is not supported and signatures from the superclass need to be repeated in the subclass alongside the added overload, and marked with @typing.overload. The Ghidra code for generating the type stubs only includes the added overloads in classes and does not repeat the ones from the superclass, so mypy cannot see them which causes the issue. This script

import __main__
import ghidra.app.cmd.function as ghfunction
import ghidra.program.model.symbol as ghsymbol
import ghidra.program.model.address as ghaddress

cmd = ghfunction.ApplyFunctionDataTypesCmd(
    list(), ghaddress.AddressSet(), ghsymbol.SourceType.USER_DEFINED, False, False
)
cmd.applyTo(__main__.currentProgram)

gives this error

test.py:9: error: Missing positional argument "monitor" in call to "applyTo" of "BackgroundCommand"  [call-arg]

even though these type stubs exist

class ApplyFunctionDataTypesCmd(ghidra.framework.cmd.BackgroundCommand[ghidra.program.model.listing.Program]):
        ...
class BackgroundCommand(Command[T], typing.Generic[T]):
    def applyTo(self, obj: T, monitor: ghidra.util.task.TaskMonitor) -> bool:
        ...
class Command(java.lang.Object, typing.Generic[T]):
    def applyTo(self, obj: T) -> bool:
        ...

mypy can't see the applyTo from Command that only takes one argument on the ApplyFunctionDataTypesCmd class because BackgroundCommand defining a new applyTo replaces the original one. But changing the definition of BackgroundCommand to

class BackgroundCommand(Command[T], typing.Generic[T]):
    @typing.overload
    def applyTo(self, obj: T) -> bool:
        ...

    @typing.overload
    def applyTo(self, obj: T, monitor: ghidra.util.task.TaskMonitor) -> bool:
        ...

makes mypy happy by repeating the signature of applyTo inherited from Command.

3. Special Cases

Finally, there are a few functions that are special cases and not 100% in line with what is accepted at runtime or don't play nice with Jython conversions for one reason or another.

The default <E extends Exception> void withTransaction(String description, ExceptionalCallback<E> callback) throws E method on ghidra.framework.model.DomainObject gives a mypy error when passing a Python function to the callback parameter, but doing that works at runtime. I'm not sure that mypy has a way of specifying that one type coerces to another, so maybe the way to fix this is by generating more overloads or using unions like you do for some other types of parameters. Or, maybe adding the Java type stubs would fix this case.

The defaultValue parameter of the public AddressValue defineAddress(String name, Address defaultValue, Program program) method on ghidra.features.base.values.GhidraValuesMap says "defaultValue - an option default value" in the docs but it is not marked optional in the type stubs. There are too many functions like this to list here, but I do pass None to quite a few of these non-optional optional parameters without issue at runtime. I think this one is going to be hard to fix automatically with the stub generation code since the optioanl-ness of parameters isn't encoded in the Java types, since any class type parameter could hypothetically be passed null. And I supposed the same thing could be said about the return types of functions that may return null.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions