Skip to content

Commit e88054f

Browse files
authored
Start to check type hints gradually (#2714)
* Start to gradually add type hints Use mypy-baseline to ignore all existing errors for now while avoiding adding new ones in new code. * Remove Python 2 fallback imports * Remove py2compat layer * Avoid redefining a tube helper function * Remove explicit new-style class (object) syntax old-style classes were removed in Python 3 so there is no distinction anymore. * Ignore type error in shellcraft helper script * Fix repeatedly changing `linux_dirent.d_name` * Avoid typechecking windows_termcap on Linux We run mypy on Linux in CI, ignore Windows related import errors * Run mypy-baseline in CI * Add type hint plan to CONTRIBUTING * Sort the mypy baseline Run with `--sort-baseline` for better diffs. `mypy | mypy-baseline sync --sort-baseline` * Switch dev dependencies to dependency-groups The dependency groups aren't baked into build metadata and won't appear on Pypi.
1 parent f81578f commit e88054f

42 files changed

Lines changed: 260 additions & 194 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ You should see `[DEBUG]` statements that show what's happening behind the scenes
3939

4040
## Verify on Ubuntu
4141

42-
If possible, please verify that your issue occurs on 64-bit Ubuntu 22.04. We provide a Dockerfile based on Ubuntu 22.04 via `docker.io` to make this super simple, no VM required!
42+
If possible, please verify that your issue occurs on 64-bit Ubuntu 24.04. We provide a Dockerfile based on Ubuntu 24.04 via `docker.io` to make this super simple, no VM required!
4343

4444
```sh
4545
# Download the Docker image

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ You can always [change the branch][change] after you create the PR if it's again
2424

2525
[contributing]: https://github.com/Gallopsled/pwntools/blob/dev/CONTRIBUTING.md
2626
[testing]: https://github.com/Gallopsled/pwntools/blob/dev/TESTING.md
27-
[change]: https://github.com/blog/2224-change-the-base-branch-of-a-pull-request
27+
[change]: https://github.blog/news-insights/product-news/change-the-base-branch-of-a-pull-request/
2828

2929
## Changelog
3030

.github/workflows/lint.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,8 @@ jobs:
3636
run: |
3737
pip install vermin
3838
vermin -vvv --no-tips -t=3.10- --violations ./pwnlib ./pwn
39+
40+
- name: MyPy type hint baseline
41+
run: |
42+
pip install -U . --group dev
43+
mypy | mypy-baseline filter

CONTRIBUTING.md

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,57 @@
22

33
Github has a great guide for contributing to open source projects:
44

5-
- [Contributing to a project](https://guides.github.com/activities/forking/)
6-
- [Fork the repository](https://guides.github.com/activities/forking/#fork)
7-
- [Clone your fork](https://guides.github.com/activities/forking/#clone)
8-
- [Making and pushing changes](https://guides.github.com/activities/forking/#making-changes)
9-
- [Making a Pull Request](https://guides.github.com/activities/forking/#making-a-pull-request)
10-
- [Huzzah!](https://guides.github.com/activities/forking/#huzzah)
5+
- [Contributing to a project](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project)
6+
- [Fork the repository](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project#creating-your-own-copy-of-a-project)
7+
- [Clone your fork](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project#cloning-a-fork-to-your-computer)
8+
- [Creating a branch to work on](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project#creating-a-branch-to-work-on)
9+
- [Making and pushing changes](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project#making-and-pushing-changes)
10+
- [Making a Pull Request](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project#making-a-pull-request)
1111

1212
## pwntools Specifics
1313

1414
In general, we like to keep things documented. You should add documentation to any new functionality, and update it for any changed functionality. Our docstrings use the [Google Style Python Docstrings](https://sphinxcontrib-napoleon.readthedocs.org/en/latest/example_google.html#example-google).
1515

16-
After you have documentation, you should add a [doctest](https://docs.python.org/2/library/doctest.html).
16+
After you have documentation, you should add a [doctest](https://docs.python.org/3/library/doctest.html).
1717

1818
Finally, it is probably a good idea to run the test suite locally before doing
1919
the pull-request to make sure everything works, however this is not a
20-
requirement.
20+
requirement. See the [TESTING](TESTING.md) documentation on how to do that.
2121

2222
Once you are ready to do a pull-request, you should figure out if your changes
2323
constitutes a new feature or a bugfix in stable or beta. If it is a bugfix in
2424
stable or beta, you should do the pull-request against the branch in question,
2525
and otherwise your pull-request should be against the dev branch.
2626

27-
Once you do the pull-request Travis CI will run the test-suite on it. Once it
27+
Once you do the pull-request Github Actions will run the test-suite on it. Once it
2828
passes one of the core developers will look at your pull request, possibly
2929
comment on it and then hopefully merge it into the branch in question.
3030

31+
## Python Type Hints
32+
33+
Since Pwntools 5 dropped support for Python 2, type hints are added gradually to the existing code base.
34+
[Type hints in Python](https://typing.python.org/en/latest/spec/index.html) allow static analysis tools and IDEs to help catch errors and provide a better developer
35+
experience without running the code.
36+
37+
New code should include type hints where applicable and shouldn't add new type warnings.
38+
You can use [this cheat sheet](https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html) as a reference for the type hint syntax.
39+
40+
Pwntools uses [mypy](https://mypy.readthedocs.io/en/stable/index.html) as a static type checker. Instead of trying to
41+
introduce type hints everywhere at once, Pwntools uses [mypy-baseline](https://mypy-baseline.orsinium.dev/usage) to gradually reduce the number of problems over time.
42+
43+
To run the type checker locally, you can install Pwntools with the `dev` extra and check for new errors (`--group dev` requires pip 25.1+):
44+
45+
```shell
46+
$ pip install -U . --group dev
47+
$ mypy | mypy-baseline filter
48+
```
49+
50+
If you fixed some problems, you can update the list of known errors in `mypy-baseline.txt` using
51+
52+
```shell
53+
$ mypy | mypy-baseline sync --sort-baseline
54+
```
55+
3156
## Automated Testing
3257

3358
Pull requests against Pwntools require at a minimum that no tests have been broken, and ideally each pull request will include new tests to ensure that all of the functionality works as intended.

TESTING.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,19 @@ To run the test suite, it is best to use Ubuntu 22.04 or 24.04, and run the foll
1010
bash travis/install.sh
1111
bash travis/ssh_setup.sh
1212
pip install --upgrade --editable .
13+
pip install --upgrade -r docs/requirements.txt
1314
PWNLIB_NOTERM=1 make -C docs doctest
1415
```
1516

17+
You can run doctests for a single file for faster iteration:
18+
19+
```sh
20+
PWNLIB_NOTERM=1 python -m sphinx -b doctest docs/source docs/build/doctest docs/source/elf/elf.rst
21+
```
22+
1623
## Testing in Docker
1724

18-
A `Dockerfile` has been provided which has a clean testing environment with Ubuntu Jammy. It is very similar to the online Github Actions CI testing environment, but uses a more modern version of Ubuntu.
25+
A `Dockerfile` has been provided which has a clean testing environment with Ubuntu Noble. It is very similar to the online Github Actions CI testing environment.
1926

2027
See `travis/docker/README.md` for more information.
2128

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def filter(self, record):
8585
# Sphinx modifies sys.stdout, and context.log_terminal has
8686
# a reference to the original instance. We need to update
8787
# it for logging to be captured.
88-
class stdout(object):
88+
class stdout:
8989
def __getattr__(self, name):
9090
return getattr(sys.stdout, name)
9191
def __setattr__(self, name, value):

mypy-baseline.txt

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
pwn/__init__.py:0: error: Incompatible types in assignment (expression has type "PwnlibArgs", variable has type Module) [assignment]
2+
pwn/__init__.py:0: error: Module has no attribute "update" [attr-defined]
3+
pwn/toplevel.py:0: error: Incompatible import of "size" (imported name has type "Callable[[Any, Any, Any], Any]", local name has type "str") [assignment]
4+
pwn/toplevel.py:0: error: Incompatible import of "size" (imported name has type "Callable[[Any, Any, Any], Any]", local name has type "str") [assignment]
5+
pwn/toplevel.py:0: error: Incompatible import of "size" (imported name has type "int", local name has type "str") [assignment]
6+
pwnlib/abi.py:0: error: Need type annotation for "register_arguments" (hint: "register_arguments: list[<type>] = ...") [var-annotated]
7+
pwnlib/asm.py:0: error: Module "pwnlib.util.hashes" has no attribute "sha1sumhex" [attr-defined]
8+
pwnlib/asm.py:0: error: Need type annotation for "util_versions" [var-annotated]
9+
pwnlib/atexception.py:0: error: Function "_run_handlers" could always be true in boolean context [truthy-function]
10+
pwnlib/commandline/cyclic.py:0: error: "Callable[[Any, Any, Any, Any], Any]" has no attribute "add_argument" [attr-defined]
11+
pwnlib/commandline/cyclic.py:0: error: "Callable[[Any, Any, Any, Any], Any]" has no attribute "add_argument" [attr-defined]
12+
pwnlib/commandline/cyclic.py:0: error: Incompatible types in assignment (expression has type "_MutuallyExclusiveGroup", variable has type "Callable[[Any, Any, Any, Any], Any]") [assignment]
13+
pwnlib/commandline/template.py:0: error: Module has no attribute "data" [attr-defined]
14+
pwnlib/context/__init__.py:0: error: Argument 1 to "int" has incompatible type "str | None"; expected "str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc" [arg-type]
15+
pwnlib/context/__init__.py:0: error: Need type annotation for "defaults" [var-annotated]
16+
pwnlib/elf/__init__.py:0: error: Name "datatypes" is not defined [name-defined]
17+
pwnlib/elf/datatypes.py:0: error: "type[constants]" has no attribute "DT_GNU_HASH" [attr-defined]
18+
pwnlib/elf/datatypes.py:0: error: "type[constants]" has no attribute "STN_UNDEF" [attr-defined]
19+
pwnlib/elf/datatypes.py:0: error: Unsupported dynamic base class "generate_siginfo" [misc]
20+
pwnlib/elf/datatypes.py:0: error: Unsupported dynamic base class "generate_siginfo" [misc]
21+
pwnlib/elf/elf.py:0: error: Name "address" already defined on line 0 [no-redef]
22+
pwnlib/elf/elf.py:0: error: Need type annotation for "functions" (hint: "functions: dict[<type>, <type>] = ...") [var-annotated]
23+
pwnlib/elf/elf.py:0: error: Need type annotation for "got" (hint: "got: dict[<type>, <type>] = ...") [var-annotated]
24+
pwnlib/elf/elf.py:0: error: Need type annotation for "plt" (hint: "plt: dict[<type>, <type>] = ...") [var-annotated]
25+
pwnlib/elf/elf.py:0: error: Need type annotation for "symbols" (hint: "symbols: dict[<type>, <type>] = ...") [var-annotated]
26+
pwnlib/encoders/arm/alphanumeric/__init__.py:0: error: Incompatible types in assignment (expression has type "str", base class "Encoder" defined the type as "None") [assignment]
27+
pwnlib/encoders/arm/xor.py:0: error: Incompatible types in assignment (expression has type "str", base class "Encoder" defined the type as "None") [assignment]
28+
pwnlib/encoders/encoder.py:0: error: Need type annotation for "_encoders" [var-annotated]
29+
pwnlib/encoders/encoder.py:0: error: Need type annotation for "blacklist" (hint: "blacklist: set[<type>] = ...") [var-annotated]
30+
pwnlib/encoders/i386/delta.py:0: error: Incompatible types in assignment (expression has type "str", base class "Encoder" defined the type as "None") [assignment]
31+
pwnlib/encoders/i386/xor.py:0: error: Incompatible types in assignment (expression has type "str", base class "Encoder" defined the type as "None") [assignment]
32+
pwnlib/encoders/mips/xor.py:0: error: Incompatible types in assignment (expression has type "str", base class "Encoder" defined the type as "None") [assignment]
33+
pwnlib/filepointer.py:0: error: Need type annotation for "length" (hint: "length: dict[<type>, <type>] = ...") [var-annotated]
34+
pwnlib/filepointer.py:0: error: Need type annotation for "vars_" (hint: "vars_: list[<type>] = ...") [var-annotated]
35+
pwnlib/filesystem/path.py:0: error: "classmethod" used with a non-method [misc]
36+
pwnlib/filesystem/path.py:0: error: "classmethod" used with a non-method [misc]
37+
pwnlib/filesystem/path.py:0: error: "type[Path]" has no attribute "mkdtemp" [attr-defined]
38+
pwnlib/filesystem/path.py:0: error: "type[Path]" has no attribute "mktemp" [attr-defined]
39+
pwnlib/gdb_api_bridge.py:0: error: Name "socket_path" is not defined [name-defined]
40+
pwnlib/libcdb.py:0: error: Module "pwnlib.util.hashes" has no attribute "md5filehex" [attr-defined]
41+
pwnlib/libcdb.py:0: error: Module "pwnlib.util.hashes" has no attribute "sha1filehex" [attr-defined]
42+
pwnlib/libcdb.py:0: error: Module "pwnlib.util.hashes" has no attribute "sha256filehex" [attr-defined]
43+
pwnlib/log.py:0: error: Module has no attribute "bold_blue" [attr-defined]
44+
pwnlib/log.py:0: error: Module has no attribute "bold_blue" [attr-defined]
45+
pwnlib/log.py:0: error: Module has no attribute "bold_blue" [attr-defined]
46+
pwnlib/log.py:0: error: Module has no attribute "bold_green" [attr-defined]
47+
pwnlib/log.py:0: error: Module has no attribute "bold_red" [attr-defined]
48+
pwnlib/log.py:0: error: Module has no attribute "bold_red" [attr-defined]
49+
pwnlib/log.py:0: error: Module has no attribute "bold_yellow" [attr-defined]
50+
pwnlib/log.py:0: error: Module has no attribute "bold_yellow" [attr-defined]
51+
pwnlib/log.py:0: error: Module has no attribute "magenta" [attr-defined]
52+
pwnlib/log.py:0: error: Module has no attribute "on_red" [attr-defined]
53+
pwnlib/log.py:0: error: Module has no attribute "on_red" [attr-defined]
54+
pwnlib/log.py:0: error: Module has no attribute "on_red" [attr-defined]
55+
pwnlib/log.py:0: error: Need type annotation for "_one_time_infos" (hint: "_one_time_infos: set[<type>] = ...") [var-annotated]
56+
pwnlib/log.py:0: error: Need type annotation for "_one_time_warnings" (hint: "_one_time_warnings: set[<type>] = ...") [var-annotated]
57+
pwnlib/memleak.py:0: error: Module "pwnlib.util.packing" has no attribute "_p8lu" [attr-defined]
58+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_autoclose" has incompatible type "Callable[[AdbClient, Any], Any]"; expected "AdbClient" [arg-type]
59+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_autoclose" has incompatible type "Callable[[AdbClient, Any], Any]"; expected "AdbClient" [arg-type]
60+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_autoclose" has incompatible type "Callable[[AdbClient], Any]"; expected "AdbClient" [arg-type]
61+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_autoclose" has incompatible type "Callable[[AdbClient], Any]"; expected "AdbClient" [arg-type]
62+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_autoclose" has incompatible type "Callable[[AdbClient], Any]"; expected "AdbClient" [arg-type]
63+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_autoclose" has incompatible type "def __exit__(AdbClient, /, *Any, **Any) -> Any"; expected "AdbClient" [arg-type]
64+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_sync" has incompatible type "Callable[[AdbClient, Any, Any, Any, Any, Any], Any]"; expected "AdbClient" [arg-type]
65+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_sync" has incompatible type "Callable[[AdbClient, Any, Any, Any], Any]"; expected "AdbClient" [arg-type]
66+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_sync" has incompatible type "Callable[[AdbClient, Any], Any]"; expected "AdbClient" [arg-type]
67+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_sync" has incompatible type "Callable[[AdbClient, Any], Any]"; expected "AdbClient" [arg-type]
68+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_with_transport" has incompatible type "Callable[[AdbClient, Any, Any, Any, Any, Any], Any]"; expected "AdbClient" [arg-type]
69+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_with_transport" has incompatible type "Callable[[AdbClient, Any], Any]"; expected "AdbClient" [arg-type]
70+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_with_transport" has incompatible type "Callable[[AdbClient], Any]"; expected "AdbClient" [arg-type]
71+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_with_transport" has incompatible type "Callable[[AdbClient], Any]"; expected "AdbClient" [arg-type]
72+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_with_transport" has incompatible type "Callable[[AdbClient], Any]"; expected "AdbClient" [arg-type]
73+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_with_transport" has incompatible type "Callable[[AdbClient], Any]"; expected "AdbClient" [arg-type]
74+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_with_transport" has incompatible type "Callable[[AdbClient], Any]"; expected "AdbClient" [arg-type]
75+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_with_transport" has incompatible type "Callable[[AdbClient], Any]"; expected "AdbClient" [arg-type]
76+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_with_transport" has incompatible type "Callable[[AdbClient], Any]"; expected "AdbClient" [arg-type]
77+
pwnlib/protocols/adb/__init__.py:0: error: Argument 1 to "_with_transport" has incompatible type "Callable[[AdbClient], Any]"; expected "AdbClient" [arg-type]
78+
pwnlib/rop/call.py:0: error: Name "address" already defined on line 0 [no-redef]
79+
pwnlib/rop/call.py:0: error: Need type annotation for "args" (hint: "args: list[<type>] = ...") [var-annotated]
80+
pwnlib/rop/call.py:0: error: Need type annotation for "values" (hint: "values: list[<type>] = ...") [var-annotated]
81+
pwnlib/rop/gadgets.py:0: error: Need type annotation for "insns" (hint: "insns: list[<type>] = ...") [var-annotated]
82+
pwnlib/rop/gadgets.py:0: error: Need type annotation for "regs" (hint: "regs: dict[<type>, <type>] = ...") [var-annotated]
83+
pwnlib/rop/rop.py:0: error: Need type annotation for "descriptions" (hint: "descriptions: dict[<type>, <type>] = ...") [var-annotated]
84+
pwnlib/rop/srop.py:0: error: Need type annotation for "_regs" (hint: "_regs: list[<type>] = ...") [var-annotated]
85+
pwnlib/shellcraft/__init__.py:0: error: Argument 1 to "append" of "list" has incompatible type "LazyImporter"; expected "MetaPathFinderProtocol" [arg-type]
86+
pwnlib/shellcraft/__init__.py:0: error: Need type annotation for "_templates" (hint: "_templates: list[<type>] = ...") [var-annotated]
87+
pwnlib/term/__init__.py:0: error: Module has no attribute "height" [attr-defined]
88+
pwnlib/term/__init__.py:0: error: Module has no attribute "width" [attr-defined]
89+
pwnlib/term/key.py:0: error: Need type annotation for "_kbuf" (hint: "_kbuf: list[<type>] = ...") [var-annotated]
90+
pwnlib/term/readline.py:0: error: Module has no attribute "reverse" [attr-defined]
91+
pwnlib/term/readline.py:0: error: Need type annotation for "history" (hint: "history: list[<type>] = ...") [var-annotated]
92+
pwnlib/term/readline.py:0: error: Need type annotation for "search_results" (hint: "search_results: list[<type>] = ...") [var-annotated]
93+
pwnlib/term/term.py:0: error: Need type annotation for "on_winch" (hint: "on_winch: list[<type>] = ...") [var-annotated]
94+
pwnlib/tubes/process.py:0: error: Cannot assign to a type [misc]
95+
pwnlib/tubes/process.py:0: error: Incompatible types in assignment (expression has type "PTY", variable has type "type[PTY]") [assignment]
96+
pwnlib/tubes/process.py:0: error: Module "pwnlib.util.hashes" has no attribute "sha256file" [attr-defined]
97+
pwnlib/tubes/tube.py:0: error: Argument 1 to "make_wrapper" has incompatible type "function"; expected "tube" [arg-type]
98+
pwnlib/util/fiddling.py:0: error: Module has no attribute "blue" [attr-defined]
99+
pwnlib/util/fiddling.py:0: error: Module has no attribute "blue" [attr-defined]
100+
pwnlib/util/fiddling.py:0: error: Module has no attribute "gray" [attr-defined]
101+
pwnlib/util/fiddling.py:0: error: Module has no attribute "gray" [attr-defined]
102+
pwnlib/util/fiddling.py:0: error: Module has no attribute "green" [attr-defined]
103+
pwnlib/util/fiddling.py:0: error: Module has no attribute "has_gray" [attr-defined]
104+
pwnlib/util/fiddling.py:0: error: Module has no attribute "has_gray" [attr-defined]
105+
pwnlib/util/fiddling.py:0: error: Module has no attribute "red" [attr-defined]
106+
pwnlib/util/fiddling.py:0: error: Module has no attribute "red" [attr-defined]
107+
pwnlib/util/iters.py:0: error: Name "pairwise" already defined (possibly by an import) [no-redef]

pwnlib/abi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from pwnlib.context import context
33

44

5-
class ABI(object):
5+
class ABI:
66
"""
77
Encapsulates information about a calling convention.
88
"""

0 commit comments

Comments
 (0)