-
Notifications
You must be signed in to change notification settings - Fork 535
Add type hints #611
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
Open
pmhahn
wants to merge
36
commits into
eliben:main
Choose a base branch
from
pmhahn:typing
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add type hints #611
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
c8f02b6
fixup! Modernize python code for `construct` and add types (#609)
pmhahn 7c90823
construct: Build _printable with a dict-comprehension
pmhahn 0b87088
typing: Convert to NamedTuple
pmhahn 7832ec6
typing: Add some Protocols
pmhahn d03d299
typing: Add / fix PEP-484 type hints
pmhahn 97a6b94
typing: Hint static attributes
pmhahn 16d03aa
typing: assert isinstance
pmhahn 2aadabc
typing: assert not None
pmhahn d63bb0a
typing: declare before conditional
pmhahn d6d2e33
typing: Check for None values
pmhahn 8c94d6e
typing: Invert type check for _strip_type_tag
pmhahn 1de3744
typing: Check for describe_reg_name() None
pmhahn 9104466
typing: Check for DIE._terminator None
pmhahn 0a7c264
typing: Convert ATTR_DESC into Final tuple
pmhahn 2fb0658
typing: describe_note_gnu_properties: Add hints
pmhahn f738b29
typing: elffile.section_names list
pmhahn 62ad618
typing: ENUM_D_TAG Mapping
pmhahn 0ae40e3
typing: get_symbol None
pmhahn e8ebc33
typing ignore: LocationLists.entry_translate
pmhahn 8384951
typing ignore: describe_symbol_shndx
pmhahn fbd8d4f
typing ignore: Dynamic.get_table_offset
pmhahn afa0306
typing ignore: RangeLists.dwarfinfo
pmhahn 051816e
typing ignore: Section.__eq__
pmhahn 247464d
mypy: Rename `params` for type change
pmhahn 351968e
mypy: Rename `reveal_type` for type change
pmhahn dfcd251
mypy: Rename `all_offsets` for type change
pmhahn 0f26f22
datatype: Check for parent None
pmhahn 61811e7
LocationParser.parse_from_attribute: raise ValueError
pmhahn 70d49e6
Rework get_location_list_at_offset() logic
pmhahn 56a2cb0
elf.description: Do not return None
pmhahn 9084e5a
get_relocation_tables: Store intermediate references
pmhahn b5315b6
DynamicSegment: cast to _StringTable
pmhahn cf7fa2a
Dynamic.num_tags: Return None
pmhahn 1ad58c1
GNUHashTable: Store intermediate references
pmhahn ef0c24a
typing: Re-implement Attribute instantiation
pmhahn 5c55939
typing: add mypy configuration
pmhahn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,4 +4,4 @@ | |
| # Eli Bendersky ([email protected]) | ||
| # This code is in the public domain | ||
| #------------------------------------------------------------------------------- | ||
| __version__ = '0.32' | ||
| __version__: str = '0.32' | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,11 +6,20 @@ | |
| # Eli Bendersky ([email protected]) | ||
| # This code is in the public domain | ||
| #------------------------------------------------------------------------------- | ||
| from __future__ import annotations | ||
|
|
||
| from struct import Struct | ||
| from typing import IO, TYPE_CHECKING, Any, NoReturn | ||
|
|
||
| from ..construct import ( | ||
| Subconstruct, ConstructError, ArrayError, SizeofError, Construct, StaticField, FieldError | ||
| ) | ||
|
|
||
| if TYPE_CHECKING: | ||
| from collections.abc import Callable, Iterable | ||
|
|
||
| from ..construct import Container | ||
|
|
||
|
|
||
| class RepeatUntilExcluding(Subconstruct): | ||
| """ A version of construct's RepeatUntil that doesn't include the last | ||
|
|
@@ -21,12 +30,12 @@ class RepeatUntilExcluding(Subconstruct): | |
| P.S. removed some code duplication | ||
| """ | ||
| __slots__ = ["predicate"] | ||
| def __init__(self, predicate, subcon): | ||
| def __init__(self, predicate: Callable[[Any, Container], bool], subcon: Construct) -> None: | ||
| Subconstruct.__init__(self, subcon) | ||
| self.predicate = predicate | ||
| self._clear_flag(self.FLAG_COPY_CONTEXT) | ||
| self._set_flag(self.FLAG_DYNAMIC) | ||
| def _parse(self, stream, context): | ||
| def _parse(self, stream: IO[bytes], context: Container) -> list[Any]: | ||
| obj = [] | ||
| try: | ||
| context_for_subcon = context | ||
|
|
@@ -41,15 +50,17 @@ def _parse(self, stream, context): | |
| except ConstructError as ex: | ||
| raise ArrayError("missing terminator", ex) | ||
| return obj | ||
| def _build(self, obj, stream, context): | ||
| def _build(self, obj: Iterable[Any], stream: IO[bytes], context: Container) -> NoReturn: | ||
| raise NotImplementedError('no building') | ||
| def _sizeof(self, context): | ||
| def _sizeof(self, context: Container) -> int: | ||
| raise SizeofError("can't calculate size") | ||
|
|
||
| class ULEB128(Construct): | ||
| """A construct based parser for ULEB128 encoding. | ||
| """ | ||
| def _parse(self, stream, context): | ||
| if TYPE_CHECKING: | ||
| name: str # instead of `str|None` from Construct to save us from `is None` checks everywhere | ||
| def _parse(self, stream: IO[bytes], context: Container) -> int: | ||
| value = 0 | ||
| shift = 0 | ||
| while True: | ||
|
|
@@ -65,7 +76,9 @@ def _parse(self, stream, context): | |
| class SLEB128(Construct): | ||
| """A construct based parser for SLEB128 encoding. | ||
| """ | ||
| def _parse(self, stream, context): | ||
| if TYPE_CHECKING: | ||
| name: str # instead of `str|None` from Construct to save us from `is None` checks everywhere | ||
| def _parse(self, stream: IO[bytes], context: Container) -> int: | ||
| value = 0 | ||
| shift = 0 | ||
| while True: | ||
|
|
@@ -88,40 +101,42 @@ class StreamOffset(Construct): | |
| Example: | ||
| StreamOffset("item_offset") | ||
| """ | ||
| __slots__ = [] | ||
| def __init__(self, name): | ||
| __slots__: list[str] = [] | ||
| if TYPE_CHECKING: | ||
| name: str # instead of `str|None` from Construct to save us from `is None` checks everywhere | ||
| def __init__(self, name: str) -> None: | ||
| Construct.__init__(self, name) | ||
| self._set_flag(self.FLAG_DYNAMIC) | ||
| def _parse(self, stream, context): | ||
| def _parse(self, stream: IO[bytes], context: Container) -> int: | ||
| return stream.tell() | ||
| def _build(self, obj, stream, context): | ||
| def _build(self, obj: None, stream: IO[bytes], context: Container) -> None: | ||
| context[self.name] = stream.tell() | ||
| def _sizeof(self, context): | ||
| def _sizeof(self, context: Container) -> int: | ||
| return 0 | ||
|
|
||
| _UBInt24_packer = Struct(">BH") | ||
| _ULInt24_packer = Struct("<HB") | ||
|
|
||
| class UBInt24(StaticField): | ||
| """unsigned, big endian 24-bit integer""" | ||
| def __init__(self, name): | ||
| def __init__(self, name: str) -> None: | ||
| StaticField.__init__(self, name, 3) | ||
|
|
||
| def _parse(self, stream, context): | ||
| def _parse(self, stream: IO[bytes], context: Container) -> int: | ||
| (h, l) = _UBInt24_packer.unpack(StaticField._parse(self, stream, context)) | ||
| return l | (h << 16) | ||
|
|
||
| def _build(self, obj, stream, context): | ||
| def _build(self, obj: int, stream: IO[bytes], context: Container) -> None: | ||
| StaticField._build(self, _UBInt24_packer.pack(obj >> 16, obj & 0xFFFF), stream, context) | ||
|
|
||
| class ULInt24(StaticField): | ||
| """unsigned, little endian 24-bit integer""" | ||
| def __init__(self, name): | ||
| def __init__(self, name: str) -> None: | ||
| StaticField.__init__(self, name, 3) | ||
|
|
||
| def _parse(self, stream, context): | ||
| def _parse(self, stream: IO[bytes], context: Container) -> int: | ||
| (l, h) = _ULInt24_packer.unpack(StaticField._parse(self, stream, context)) | ||
| return l | (h << 16) | ||
|
|
||
| def _build(self, obj, stream, context): | ||
| def _build(self, obj: int, stream: IO[bytes], context: Container) -> None: | ||
| StaticField._build(self, _ULInt24_packer.pack(obj & 0xFFFF, obj >> 16), stream, context) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,24 +6,43 @@ | |
| # Eli Bendersky ([email protected]) | ||
| # This code is in the public domain | ||
| #------------------------------------------------------------------------------- | ||
| from __future__ import annotations | ||
|
|
||
| from contextlib import contextmanager | ||
| from typing import IO, TYPE_CHECKING, Any, TypeVar, overload | ||
eliben marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| from .exceptions import ELFParseError, ELFError, DWARFError | ||
| from ..construct import ConstructError, ULInt8 | ||
| import os | ||
|
|
||
| if TYPE_CHECKING: | ||
| from collections.abc import Iterator, Mapping | ||
|
|
||
| from ..construct import Construct, FormatField | ||
| from ..dwarf.dwarfinfo import DebugSectionDescriptor | ||
| from .construct_utils import SLEB128, ULEB128, UBInt24, ULInt24 | ||
|
|
||
| _T = TypeVar("_T") | ||
| _K = TypeVar("_K") | ||
| _V = TypeVar("_V") | ||
|
|
||
|
|
||
| def merge_dicts(*dicts): | ||
| def merge_dicts(*dicts: Mapping[_K, _V]) -> dict[_K, _V]: | ||
pmhahn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "Given any number of dicts, merges them into a new one.""" | ||
| result = {} | ||
| result: dict[_K, _V] = {} | ||
| for d in dicts: | ||
| result.update(d) | ||
| return result | ||
|
|
||
| def bytes2str(b): | ||
| def bytes2str(b: bytes) -> str: | ||
| """Decode a bytes object into a string.""" | ||
| return b.decode('latin-1') | ||
|
|
||
| def struct_parse(struct, stream, stream_pos=None): | ||
| @overload | ||
| def struct_parse(struct: FormatField[_T] | ULEB128 | SLEB128 | UBInt24 | ULInt24, stream: IO[bytes], stream_pos: int | None = ...) -> _T: ... | ||
| @overload | ||
| def struct_parse(struct: Construct, stream: IO[bytes], stream_pos: int | None = ...) -> Any: ... | ||
| def struct_parse(struct: Construct, stream: IO[bytes], stream_pos: int | None = None) -> Any: | ||
| """ Convenience function for using the given struct to parse a stream. | ||
| If stream_pos is provided, the stream is seeked to this position before | ||
| the parsing is done. Otherwise, the current position of the stream is | ||
|
|
@@ -38,7 +57,7 @@ def struct_parse(struct, stream, stream_pos=None): | |
| raise ELFParseError(str(e)) | ||
|
|
||
|
|
||
| def parse_cstring_from_stream(stream, stream_pos=None): | ||
| def parse_cstring_from_stream(stream: IO[bytes], stream_pos: int | None = None) -> bytes | None: | ||
| """ Parse a C-string from the given stream. The string is returned without | ||
| the terminating \x00 byte. If the terminating byte wasn't found, None | ||
| is returned (the stream is exhausted). | ||
|
|
@@ -67,20 +86,20 @@ def parse_cstring_from_stream(stream, stream_pos=None): | |
| return b''.join(chunks) if found else None | ||
|
|
||
|
|
||
| def elf_assert(cond, msg=''): | ||
| def elf_assert(cond: object, msg: str = '') -> None: | ||
| """ Assert that cond is True, otherwise raise ELFError(msg) | ||
| """ | ||
| _assert_with_exception(cond, msg, ELFError) | ||
|
|
||
|
|
||
| def dwarf_assert(cond, msg=''): | ||
| def dwarf_assert(cond: object, msg: str = '') -> None: | ||
| """ Assert that cond is True, otherwise raise DWARFError(msg) | ||
| """ | ||
| _assert_with_exception(cond, msg, DWARFError) | ||
|
|
||
|
|
||
| @contextmanager | ||
| def preserve_stream_pos(stream): | ||
| def preserve_stream_pos(stream: IO[bytes]) -> Iterator[None]: | ||
| """ Usage: | ||
| # stream has some position FOO (return value of stream.tell()) | ||
| with preserve_stream_pos(stream): | ||
|
|
@@ -92,18 +111,18 @@ def preserve_stream_pos(stream): | |
| stream.seek(saved_pos) | ||
|
|
||
|
|
||
| def roundup(num, bits): | ||
| def roundup(num: int, bits: int) -> int: | ||
| """ Round up a number to nearest multiple of 2^bits. The result is a number | ||
| where the least significant bits passed in bits are 0. | ||
| """ | ||
| return (num - 1 | (1 << bits) - 1) + 1 | ||
|
|
||
| def read_blob(stream, length): | ||
| def read_blob(stream: IO[bytes], length: int) -> list[int]: | ||
| """Read length bytes from stream, return a list of ints | ||
| """ | ||
| return [struct_parse(ULInt8(''), stream) for i in range(length)] | ||
|
|
||
| def save_dwarf_section(section, filename): | ||
| def save_dwarf_section(section: DebugSectionDescriptor, filename: str) -> None: | ||
| """Debug helper: dump section contents into a file | ||
| Section is expected to be one of the debug_xxx_sec elements of DWARFInfo | ||
| """ | ||
|
|
@@ -116,21 +135,21 @@ def save_dwarf_section(section, filename): | |
| file.write(data) | ||
| stream.seek(pos, os.SEEK_SET) | ||
|
|
||
| def iterbytes(b): | ||
| def iterbytes(b: bytes) -> Iterator[bytes]: | ||
| """Return an iterator over the elements of a bytes object. | ||
|
|
||
| For example, for b'abc' yields b'a', b'b' and then b'c'. | ||
| """ | ||
| for i in range(len(b)): | ||
| yield b[i:i+1] | ||
|
|
||
| def bytes2hex(b, sep=''): | ||
| def bytes2hex(b: bytes, sep: str = '') -> str: | ||
| if not sep: | ||
| return b.hex() | ||
| return sep.join(map('{:02x}'.format, b)) | ||
|
|
||
| #------------------------- PRIVATE ------------------------- | ||
|
|
||
| def _assert_with_exception(cond, msg, exception_type): | ||
| def _assert_with_exception(cond: object, msg: str, exception_type: type[BaseException]) -> None: | ||
| if not cond: | ||
| raise exception_type(msg) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,14 +6,24 @@ | |
| # Eli Bendersky ([email protected]) | ||
| # This code is in the public domain | ||
| #------------------------------------------------------------------------------- | ||
| from __future__ import annotations | ||
|
|
||
| from typing import IO, TYPE_CHECKING, Any | ||
|
|
||
| from ..common.utils import struct_parse | ||
|
|
||
| if TYPE_CHECKING: | ||
| from collections.abc import Iterator | ||
|
|
||
| from ..construct.lib.container import Container | ||
| from .structs import DWARFStructs | ||
|
|
||
|
|
||
| class AbbrevTable: | ||
| """ Represents a DWARF abbreviation table. | ||
| """ | ||
| __slots__ = ('structs', 'stream', 'offset', '_abbrev_map') | ||
| def __init__(self, structs, stream, offset): | ||
| def __init__(self, structs: DWARFStructs, stream: IO[bytes], offset: int) -> None: | ||
| """ Create new abbreviation table. Parses the actual table from the | ||
| stream and stores it internally. | ||
|
|
||
|
|
@@ -30,19 +40,19 @@ def __init__(self, structs, stream, offset): | |
|
|
||
| self._abbrev_map = self._parse_abbrev_table() | ||
|
|
||
| def get_abbrev(self, code): | ||
| def get_abbrev(self, code: int) -> AbbrevDecl: | ||
| """ Get the AbbrevDecl for a given code. Raise KeyError if no | ||
| declaration for this code exists. | ||
| """ | ||
| return self._abbrev_map[code] | ||
|
|
||
| def _parse_abbrev_table(self): | ||
| def _parse_abbrev_table(self) -> dict[int, AbbrevDecl]: | ||
| """ Parse the abbrev table from the stream | ||
| """ | ||
| map = {} | ||
| map: dict[int, AbbrevDecl] = {} | ||
| self.stream.seek(self.offset) | ||
| while True: | ||
| decl_code = struct_parse( | ||
| decl_code: int = struct_parse( | ||
| struct=self.structs.the_Dwarf_uleb128, | ||
| stream=self.stream) | ||
| if decl_code == 0: | ||
|
|
@@ -61,20 +71,20 @@ class AbbrevDecl: | |
| The abbreviation declaration represents an "entry" that points to it. | ||
| """ | ||
| __slots__ = ('code', 'decl', '_has_children') | ||
| def __init__(self, code, decl): | ||
| def __init__(self, code: int, decl: Container) -> None: | ||
| self.code = code | ||
| self.decl = decl | ||
| self._has_children = decl['children_flag'] == 'DW_CHILDREN_yes' | ||
|
|
||
| def has_children(self): | ||
| def has_children(self) -> bool: | ||
| return self._has_children | ||
|
|
||
| def iter_attr_specs(self): | ||
| def iter_attr_specs(self) -> Iterator[tuple[str, str]]: | ||
| """ Iterate over the attribute specifications for the entry. Yield | ||
| (name, form) pairs. | ||
| """ | ||
| for attr_spec in self['attr_spec']: | ||
| yield attr_spec.name, attr_spec.form | ||
|
|
||
| def __getitem__(self, entry): | ||
| def __getitem__(self, entry: str) -> Any: | ||
| return self.decl[entry] | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.