1from __future__ import annotations
2
3from typing import TYPE_CHECKING, Any, BinaryIO, Generic, TypeVar
4
5from dissect.cstruct.exceptions import NullPointerDereference
6from dissect.cstruct.types.base import BaseType
7from dissect.cstruct.types.char import Char
8from dissect.cstruct.types.void import Void
9
10if TYPE_CHECKING:
11 from typing_extensions import Self
12
13T = TypeVar("T", bound=BaseType)
14
15
16class Pointer(int, BaseType, Generic[T]):
17 """Pointer to some other type."""
18
19 type: type[T]
20 _stream: BinaryIO | None
21 _context: dict[str, Any] | None
22 _value: T | None
23
24 def __new__(cls, value: int, stream: BinaryIO | None, context: dict[str, Any] | None = None) -> Self:
25 obj = super().__new__(cls, value)
26 obj._stream = stream
27 obj._context = context
28 obj._value = None
29 return obj
30
31 def __repr__(self) -> str:
32 return f"<{self.type.__name__}* @ {self:#x}>"
33
34 def __str__(self) -> str:
35 return str(self.dereference())
36
37 def __getattr__(self, attr: str) -> Any:
38 return getattr(self.dereference(), attr)
39
40 def __add__(self, other: int) -> Self:
41 return type.__call__(self.__class__, int.__add__(self, other), self._stream, self._context)
42
43 def __sub__(self, other: int) -> Self:
44 return type.__call__(self.__class__, int.__sub__(self, other), self._stream, self._context)
45
46 def __mul__(self, other: int) -> Self:
47 return type.__call__(self.__class__, int.__mul__(self, other), self._stream, self._context)
48
49 def __floordiv__(self, other: int) -> Self:
50 return type.__call__(self.__class__, int.__floordiv__(self, other), self._stream, self._context)
51
52 def __mod__(self, other: int) -> Self:
53 return type.__call__(self.__class__, int.__mod__(self, other), self._stream, self._context)
54
55 def __pow__(self, other: int) -> Self:
56 return type.__call__(self.__class__, int.__pow__(self, other), self._stream, self._context)
57
58 def __lshift__(self, other: int) -> Self:
59 return type.__call__(self.__class__, int.__lshift__(self, other), self._stream, self._context)
60
61 def __rshift__(self, other: int) -> Self:
62 return type.__call__(self.__class__, int.__rshift__(self, other), self._stream, self._context)
63
64 def __and__(self, other: int) -> Self:
65 return type.__call__(self.__class__, int.__and__(self, other), self._stream, self._context)
66
67 def __xor__(self, other: int) -> Self:
68 return type.__call__(self.__class__, int.__xor__(self, other), self._stream, self._context)
69
70 def __or__(self, other: int) -> Self:
71 return type.__call__(self.__class__, int.__or__(self, other), self._stream, self._context)
72
73 @classmethod
74 def __default__(cls) -> Self:
75 return cls.__new__(cls, cls.cs.pointer.__default__(), None, None)
76
77 @classmethod
78 def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
79 return cls.__new__(cls, cls.cs.pointer._read(stream, context), stream, context)
80
81 @classmethod
82 def _write(cls, stream: BinaryIO, data: int) -> int:
83 return cls.cs.pointer._write(stream, data)
84
85 def dereference(self) -> T:
86 if self == 0 or self._stream is None:
87 raise NullPointerDereference
88
89 if self._value is None and not issubclass(self.type, Void):
90 # Read current position of file read/write pointer
91 position = self._stream.tell()
92 # Reposition the file read/write pointer
93 self._stream.seek(self)
94
95 if issubclass(self.type, Char):
96 # this makes the assumption that a char pointer is a null-terminated string
97 value = self.type._read_0(self._stream, self._context)
98 else:
99 value = self.type._read(self._stream, self._context)
100
101 self._stream.seek(position)
102 self._value = value
103
104 return self._value