# pylint:disable=abstract-method,line-too-long,missing-class-docstring,wrong-import-position,too-many-positional-arguments
from __future__ import annotations

import contextlib
import copy
import enum
import re
import logging
from typing import Literal, Any, cast, overload, TYPE_CHECKING
from collections import OrderedDict, defaultdict, ChainMap
from collections.abc import Iterable
from collections.abc import MutableMapping

from archinfo import Endness, Arch
import claripy
import cxxheaderparser.simple
import cxxheaderparser.errors
import cxxheaderparser.types
import pycparser
from pycparser import c_ast

from angr.errors import AngrTypeError, AngrMissingTypeError
from angr.sim_state import SimState

StoreType = int | claripy.ast.BV

l = logging.getLogger(name=__name__)

# pycparser hack to parse type expressions
errorlog = logging.getLogger(name=__name__ + ".yacc")
errorlog.setLevel(logging.ERROR)


if TYPE_CHECKING:
    from angr.procedures.definitions import SimTypeCollection


class SimType:
    """
    SimType exists to track type information for SimProcedures.
    """

    _fields: tuple[str, ...] = ()
    _args: tuple[str, ...] = ("label", "qualifier")
    _arch: Arch | None
    _size: int | None = None
    _can_refine_int: bool = False
    _base_name: str
    _ident: str = "simtype"
    base: bool = True

    def __init__(self, label=None, qualifier: Iterable[str] | None = None):
        """
        :param label: the type label.
        """
        self.label = label
        self._arch = None
        self.qualifier = qualifier

    @staticmethod
    def _simtype_eq(self_type: SimType, other: SimType, avoid: dict[str, set[SimType]] | None) -> bool:
        if self_type is other:
            return True
        if avoid is not None and self_type in avoid["self"] and other in avoid["other"]:
            return True
        return self_type.__eq__(other, avoid=avoid)  # pylint:disable=unnecessary-dunder-call

    def __eq__(self, other, avoid=None):
        if type(self) is not type(other):
            return False

        for attr in self._fields:
            if attr == "size" and self._arch is None and other._arch is None:
                continue
            attr_self = getattr(self, attr)
            attr_other = getattr(other, attr)
            if isinstance(attr_self, SimType):
                if not SimType._simtype_eq(attr_self, attr_other, avoid):
                    return False
            elif isinstance(attr_self, (list, tuple)) and isinstance(attr_other, (list, tuple)):
                if len(attr_self) != len(attr_other):
                    return False
                for a, b in zip(attr_self, attr_other):
                    if isinstance(a, SimType) and isinstance(b, SimType):
                        if SimType._simtype_eq(a, b, avoid) is False:
                            return False
                    else:
                        if a != b:
                            return False
            else:
                if attr_self != attr_other:
                    return False

        return True

    def __ne__(self, other):
        # wow many efficient
        return not self == other

    def __hash__(self):
        # very hashing algorithm many secure wow
        out = hash(type(self))
        for attr in self._fields:
            out ^= hash(getattr(self, attr))
        return out

    def _refine_dir(self):  # pylint: disable=no-self-use
        return []

    def _refine(self, view, k):  # pylint: disable=unused-argument,no-self-use
        raise KeyError(f"{k} is not a valid refinement")

    @property
    def size(self) -> int | None:
        """
        The size of the type in bits, or None if no size is computable.
        """
        return self._size

    @property
    def alignment(self):
        """
        The alignment of the type in bytes.
        """
        if self._arch is None:
            raise ValueError("Can't tell my alignment without an arch!")
        if self.size is None:
            l.debug("The size of the type %r is unknown; assuming word size of the arch.", self)
            return self._arch.bytes
        return self.size // self._arch.byte_width

    def with_arch(self, arch: Arch | None):
        if arch is None:
            return self
        if self._arch is not None and self._arch == arch:
            return self
        return self._with_arch(arch)

    def _with_arch(self, arch):
        cp = copy.copy(self)
        cp._arch = arch
        return cp

    def _init_str(self):
        return f"NotImplemented({self.__class__.__name__})"

    def c_repr(self, name=None, full=0, memo=None, indent: int | None = 0, name_parens: bool = True):  # pylint: disable=unused-argument
        out = f"{str(self) if self.label is None else self.label} {name}"
        if self.qualifier:
            out = f"{' '.join(self.qualifier)} {out}"
        if name is None:
            return repr(self)
        return out

    def copy(self):
        raise NotImplementedError

    def extract(self, state: SimState, addr, concrete: bool = False) -> Any:
        raise NotImplementedError

    def store(self, state: SimState, addr, value: Any):
        raise NotImplementedError

    def extract_claripy(self, bits) -> Any:
        """
        Given a bitvector `bits` which was loaded from memory in a big-endian fashion, return a more appropriate or
        structured representation of the data.

        A type must have an arch associated in order to use this method.
        """
        raise NotImplementedError(f"extract_claripy is not implemented for {self}")

    def to_json(self, fields: Iterable[str] | None = None, memo: dict[str, SimTypeRef] | None = None) -> dict[str, Any]:
        """
        Serialize the type class to a JSON-compatible dictionary.
        """

        if memo is None:
            memo = {}

        nullable_fields = {"label"}
        if fields is None:
            fields = self._args

        d: dict[str, Any] = {"_t": self._ident}
        for field in fields:
            value = getattr(self, field)
            if field == "qualifier" and value is None:
                continue
            field = "q" if field == "qualifier" else field
            if isinstance(value, SimType):
                d[field] = value.to_json(memo=memo)
            elif isinstance(value, (list, tuple)):
                d[field] = [v.to_json(memo=memo) if isinstance(v, SimType) else v for v in value]
            elif isinstance(value, dict):
                d[field] = {k: v.to_json(memo=memo) if isinstance(v, SimType) else v for k, v in value.items()}
            else:
                if field in nullable_fields and value is None:
                    continue
                d[field] = value

        return d

    @staticmethod
    def from_json(d: dict[str, Any], type_collection: SimTypeCollection | None = None, memo: set[str] | None = None):
        """
        Deserialize a type class from a JSON-compatible dictionary.
        """
        if memo is None:
            memo = set()

        assert "_t" in d
        cls = IDENT_TO_CLS.get(d["_t"])  # pylint: disable=redefined-outer-name
        assert cls is not None, f"Unknown SimType class identifier {d['_t']}"
        if getattr(cls, "from_json", SimType.from_json) is not SimType.from_json:
            t = cls.from_json(d)
            if isinstance(t, SimTypeRef) and type_collection is not None and t.name is not None and t.name not in memo:
                # attempt to resolve the type ref
                with contextlib.suppress(AngrMissingTypeError):
                    return type_collection.get(t.name, memo=memo)
            return t

        kwargs = {}
        if "name" in d:
            memo.add(d["name"])
        for field in cls._args:
            field_key = "q" if field == "qualifier" else field
            field_key = "disp" if field == "disposition" else field
            if field_key not in d:
                continue
            value = d[field_key]
            if isinstance(value, dict):
                if "_t" in value:
                    value = SimType.from_json(value, type_collection=type_collection, memo=memo)
                else:
                    new_value = {}
                    for k, v in value.items():
                        if isinstance(v, dict) and "_t" in v:
                            new_value[k] = SimType.from_json(v, type_collection=type_collection, memo=memo)
                        else:
                            new_value[k] = v
                    value = new_value
            elif isinstance(value, list):
                new_value = []
                for v in value:
                    if isinstance(v, dict) and "_t" in v:
                        new_value.append(SimType.from_json(v, type_collection=type_collection, memo=memo))
                    else:
                        new_value.append(v)
                value = new_value
            kwargs[field] = value
        return cls(**kwargs)


class TypeRef(SimType):
    """
    A TypeRef is a reference to a type with a name. This allows for interactivity in type analysis, by storing a type
    and having the option to update it later and have all references to it automatically update as well.
    """

    _args = ("name", "ty", "qualifier")
    _ident = "tref"

    def __init__(self, name, ty, qualifier: Iterable | None = None):
        super().__init__()

        self.type = ty
        self._name = name
        self.qualifier = qualifier

    @property
    def type(self):
        return self._type

    @property
    def ty(self):
        return self.type

    @type.setter
    def type(self, val):
        self._type = val
        self._arch = val._arch

    @property
    def name(self):
        """
        This is a read-only property because it is desirable to store typerefs in a mapping from name to type, and we
        want the mapping to be in the loop for any updates.
        """
        return self._name

    def __eq__(self, other, avoid=None):
        return type(other) is TypeRef and self.type == other.type

    def __hash__(self):
        return hash(self.type)

    def __repr__(self):
        return self.name

    @property
    def size(self):
        return self.type.size

    @property
    def alignment(self):
        return self.type.alignment

    def with_arch(self, arch):
        self.type = self.type.with_arch(arch)
        self._arch = arch
        return self

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True):  # pylint: disable=unused-argument
        if not full:
            base_name = self.name
            if self.qualifier:
                base_name = f"{' '.join(self.qualifier)} {name}"

            if name is not None:
                return f"{base_name} {name}"
            return base_name
        result = self.type.c_repr(name=name, full=full, memo=memo, indent=indent)
        if self.qualifier:
            result = f"{' '.join(sorted(self.qualifier))} {result}"
        return result

    def copy(self):
        raise NotImplementedError("copy() for TypeRef is ill-defined. What do you want this to do?")


class NamedTypeMixin:
    """
    SimType classes with this mixin in the class hierarchy allows setting custom class names. A typical use case is
    to represent same or similar type classes with different qualified names, such as "std::basic_string" vs
    "std::__cxx11::basic_string". In such cases, .name stores the qualified name, and .unqualified_name() returns the
    unqualified name of the type.
    """

    def __init__(self, *args, name: str | None = None, **kwargs):
        super().__init__(*args, **kwargs)
        self._name = name

    @property
    def name(self) -> str:
        if self._name is None:
            self._name = repr(self)
        return self._name

    @name.setter
    def name(self, v):
        self._name = v

    def unqualified_name(self, lang: str = "c++") -> str:
        if lang == "c++":
            splitter = "::"
            n = self.name.split(splitter)
            return n[-1]
        raise NotImplementedError(f"Unsupported language {lang}.")


class SimTypeBottom(SimType):
    """
    SimTypeBottom basically represents a type error.
    """

    _base_name = "bot"
    _ident = "bot"

    def __repr__(self):
        return self.label or "BOT"

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True):  # pylint: disable=unused-argument
        if name is None:
            return "int" if self.label is None else self.label
        return f"{'int' if self.label is None else self.label} {name}"

    def _init_str(self):
        return "{}({})".format(self.__class__.__name__, (f'label="{self.label}"') if self.label else "")

    def copy(self):
        return SimTypeBottom(self.label)


class SimTypeTop(SimType):
    """
    SimTypeTop represents any type (mostly used with a pointer for void*).
    """

    _fields = ("size",)
    _args = ("size", "label", "qualifier")
    _ident = "top"

    def __init__(self, size: int | None = None, label=None, qualifier: Iterable | None = None):
        SimType.__init__(self, label)
        self._size = size
        self.qualifier = qualifier

    def __repr__(self):
        return "TOP"

    def copy(self):
        return SimTypeTop(size=self.size, label=self.label)


class SimTypeReg(SimType):
    """
    SimTypeReg is the base type for all types that are register-sized.
    """

    _fields = ("size",)
    _args = ("size", "label", "qualifier")
    _ident = "reg"

    def __init__(self, size: int | None, label=None, qualifier: Iterable | None = None):
        """
        :param label: the type label.
        :param size: the size of the type (e.g. 32bit, 8bit, etc.).
        """
        SimType.__init__(self, label=label, qualifier=qualifier)
        self._size = size

    def __repr__(self):
        return f"reg{self.size}_t"

    def store(self, state, addr, value: StoreType):
        if self.size is None:
            raise TypeError("Need a size to store")
        store_endness = state.arch.memory_endness
        with contextlib.suppress(AttributeError):
            value = value.ast  # type: ignore
        if isinstance(value, claripy.ast.Bits):  # pylint:disable=isinstance-second-argument-not-valid-type
            if value.size() != self.size:  # type: ignore
                raise ValueError("size of expression is wrong size for type")
        elif isinstance(value, int):
            value = claripy.BVV(value, self.size)
        elif isinstance(value, bytes):
            store_endness = "Iend_BE"
        else:
            raise TypeError(f"unrecognized expression type for SimType {type(self).__name__}")

        state.memory.store(addr, value, endness=store_endness)

    def copy(self):
        return self.__class__(self.size, label=self.label)


class SimTypeNum(SimType):
    """
    SimTypeNum is a numeric type of arbitrary length
    """

    _fields = (*SimType._fields, "signed", "size")
    _args = ("size", "signed", "label", "qualifier")
    _ident = "num"

    def __init__(self, size: int, signed=True, label=None, qualifier: Iterable | None = None):
        """
        :param size:        The size of the integer, in bits
        :param signed:      Whether the integer is signed or not
        :param label:       A label for the type
        """
        super().__init__(label)
        self._size = size
        self.signed = signed
        self.qualifier = qualifier

    @property
    def size(self) -> int:
        assert self._size is not None
        return self._size

    def __repr__(self):
        return "{}int{}_t".format("" if self.signed else "u", self.size)

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> int: ...

    def extract(self, state, addr, concrete=False):
        out = state.memory.load(addr, self.size // state.arch.byte_width, endness=state.arch.memory_endness)
        if not concrete:
            return out
        n = state.solver.eval(out)
        if self.signed and n >= 1 << (self.size - 1):
            n -= 1 << (self.size)
        return n

    def store(self, state, addr, value: StoreType):
        store_endness = state.arch.memory_endness

        if isinstance(value, claripy.ast.Bits):  # pylint:disable=isinstance-second-argument-not-valid-type
            if value.size() != self.size:  # type: ignore
                raise ValueError("size of expression is wrong size for type")
        elif isinstance(value, int) and self.size is not None:
            value = claripy.BVV(value, self.size)
        elif isinstance(value, bytes):
            store_endness = "Iend_BE"
        else:
            raise TypeError(f"unrecognized expression type for SimType {type(self).__name__}")

        state.memory.store(addr, value, endness=store_endness)

    def copy(self):
        return SimTypeNum(self.size, signed=self.signed, label=self.label)


class SimTypeInt(SimTypeReg):
    """
    SimTypeInt is a type that specifies a signed or unsigned C integer.
    """

    _fields = (*tuple(x for x in SimTypeReg._fields if x != "size"), "signed")
    _args = ("signed", "label", "qualifier")
    _base_name = "int"
    _ident = "int"

    def __init__(self, signed=True, label=None, qualifier: Iterable | None = None):
        """
        :param signed:  True if signed, False if unsigned
        :param label:   The type label
        """
        super().__init__(None, label=label, qualifier=qualifier)
        self.signed = signed

    def to_json(self, fields: Iterable[str] | None = None, memo: dict[str, SimTypeRef] | None = None) -> dict[str, Any]:
        if memo is None:
            memo = {}
        d = super().to_json(fields=fields, memo=memo)
        if "signed" in d and d["signed"] is True:
            del d["signed"]
        if "q" in d and not d["q"]:
            d.pop("q")
        return d

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True):  # pylint: disable=unused-argument
        out = self._base_name
        if not self.signed:
            out = "unsigned " + out
        if self.qualifier:
            out = f"{' '.join(self.qualifier)} {out}"
        if name is None:
            return out
        return f"{out} {name}"

    def __repr__(self):
        name = self._base_name
        if not self.signed:
            name = "unsigned " + name

        try:
            return f"{name} ({self.size} bits)"
        except ValueError:
            return name

    @property
    def size(self):
        if self._arch is None:
            raise ValueError("Can't tell my size without an arch!")
        try:
            return self._arch.sizeof[self._base_name]
        except KeyError as e:
            raise ValueError(f"Arch {self._arch.name} doesn't have its {self._base_name} type defined!") from e

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> int: ...

    def extract(self, state, addr, concrete=False):
        out = state.memory.load(addr, self.size // state.arch.byte_width, endness=state.arch.memory_endness)
        if not concrete:
            return out
        n = state.solver.eval(out)
        if self.signed and n >= 1 << (self.size - 1):
            n -= 1 << self.size
        return n

    def _init_str(self):
        return "{}(signed={}{})".format(
            self.__class__.__name__,
            self.signed,
            (f', label="{self.label}"') if self.label is not None else "",
        )

    def _refine_dir(self):
        return ["signed", "unsigned"]

    def _refine(self, view, k):
        if k == "signed":
            ty = copy.copy(self)
            ty.signed = True
        elif k == "unsigned":
            ty = copy.copy(self)
            ty.signed = False
        else:
            raise KeyError(k)
        return view._deeper(ty=ty)

    def copy(self):
        return self.__class__(signed=self.signed, label=self.label)


class SimTypeShort(SimTypeInt):
    _base_name = "short"
    _ident = "short"


class SimTypeLong(SimTypeInt):
    _base_name = "long"
    _ident = "long"


class SimTypeLongLong(SimTypeInt):
    _base_name = "long long"
    _ident = "llong"


class SimTypeFixedSizeInt(SimTypeInt):
    """
    The base class for all fixed-size (i.e., the size stays the same on all platforms) integer types. Do not
    instantiate this class directly.
    """

    _args = ("signed", "label")
    _base_name: str = "int"
    _ident = "intfixedsize"
    _fixed_size: int = 32

    def c_repr(
        self,
        name=None,
        full=0,
        memo=None,
        indent: int | None = 0,
        name_parens: bool = True,  # pylint:disable=unused-argument
    ):
        out = self._base_name
        if not self.signed:
            out = "u" + out
        if name is None:
            return out
        return f"{out} {name}"

    def __repr__(self) -> str:
        name = self._base_name
        if not self.signed:
            name = "u" + name

        try:
            return f"{name} ({self.size} bits)"
        except ValueError:
            return name

    @property
    def size(self) -> int:
        return self._fixed_size


class SimTypeInt128(SimTypeFixedSizeInt):
    _base_name = "int128_t"
    _ident = "int128"
    _fixed_size = 128


class SimTypeInt256(SimTypeFixedSizeInt):
    _base_name = "int256_t"
    _ident = "int256"
    _fixed_size = 256


class SimTypeInt512(SimTypeFixedSizeInt):
    _base_name = "int512_t"
    _ident = "int512"
    _fixed_size = 512


class SimTypeChar(SimTypeReg):
    """
    SimTypeChar is a type that specifies a character;
    this could be represented by a byte, but this is meant to be interpreted as a character.
    """

    _base_name = "char"
    _args = ("signed", "label", "qualifier")
    _ident = "char"

    def __init__(self, signed=True, label=None, qualifier: Iterable | None = None):
        """
        :param label: the type label.
        """
        # FIXME: Now the size of a char is state-dependent.
        super().__init__(8, label=label, qualifier=qualifier)
        self.signed = signed

    def __repr__(self) -> str:
        return "char"

    def store(self, state, addr, value: StoreType):
        # FIXME: This is a hack.
        self._size = state.arch.byte_width
        try:
            super().store(state, addr, value)
        except TypeError:
            if isinstance(value, bytes) and len(value) == 1:
                value = claripy.BVV(value[0], state.arch.byte_width)
                super().store(state, addr, value)
            else:
                raise

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> bytes: ...

    def extract(self, state, addr, concrete: bool = False) -> claripy.ast.BV | bytes:
        # FIXME: This is a hack.
        self._size = state.arch.byte_width

        out = state.memory.load(addr, 1, endness=state.arch.memory_endness)
        if concrete:
            return bytes(cast(list[int], [state.solver.eval(out)]))
        return out

    def _init_str(self):
        return "{}({})".format(
            self.__class__.__name__,
            (f'label="{self.label}"') if self.label is not None else "",
        )

    def copy(self):
        return self.__class__(signed=self.signed, label=self.label, qualifier=self.qualifier)


class SimTypeWideChar(SimTypeReg):
    """
    SimTypeWideChar is a type that specifies a wide character (a UTF-16 character).
    """

    _args = ("signed", "label", "endness", "qualifier")
    _base_name = "char"
    _ident = "wchar"

    def __init__(self, signed=True, label=None, endness: Endness = Endness.BE, qualifier: Iterable | None = None):
        """
        :param label: the type label.
        """
        SimTypeReg.__init__(self, 16, label=label, qualifier=qualifier)
        self.signed = signed
        self.endness = endness

    def __repr__(self):
        return "wchar"

    def store(self, state, addr, value: StoreType):
        try:
            super().store(state, addr, value)
        except TypeError:
            if isinstance(value, bytes) and len(value) == 2:
                inner = (
                    ((value[0] << state.arch.byte_width) | value[1])
                    if self.endness == Endness.BE
                    else ((value[1] << state.arch.byte_width) | value[0])
                )
                value = claripy.BVV(inner, state.arch.byte_width * 2)
                super().store(state, addr, value)
            else:
                raise

    def extract(self, state, addr, concrete=False) -> Any:
        out = state.memory.load(addr, 2)
        if concrete:
            data = state.solver.eval(out, cast_to=bytes)
            fmt_str = "utf-16be" if self.endness == Endness.BE else "utf-16le"
            try:
                return data.decode(fmt_str)
            except UnicodeDecodeError:
                return data
        return out

    def _init_str(self):
        return "{}({})".format(
            self.__class__.__name__,
            (f'label="{self.label}"') if self.label is not None else "",
        )

    def copy(self):
        return self.__class__(signed=self.signed, label=self.label, endness=self.endness, qualifier=self.qualifier)


class SimTypeBool(SimTypeReg):
    _args = ("signed", "label", "qualifier")
    _base_name = "bool"
    _ident = "bool"

    def __init__(self, signed=True, label=None, qualifier: Iterable | None = None):
        """
        :param label: the type label.
        """
        # FIXME: Now the size of a char is state-dependent.
        super().__init__(8, label=label, qualifier=qualifier)
        self.signed = signed

    def __repr__(self):
        return "bool"

    def store(self, state, addr, value: StoreType | bool):
        if isinstance(value, bool):
            value = int(value)
        return super().store(state, addr, value)

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.Bool: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> bool: ...

    def extract(self, state, addr, concrete=False):
        ver = super().extract(state, addr, concrete)
        if concrete:
            return ver != b"\0"
        return ver != 0

    def _init_str(self):
        return f"{self.__class__.__name__}()"

    def copy(self):
        return self.__class__(signed=self.signed, label=self.label)


class SimTypeFd(SimTypeReg):
    """
    SimTypeFd is a type that specifies a file descriptor.
    """

    _fields = SimTypeReg._fields
    _args = ("label", "qualifier")
    _ident = "fd"

    def __init__(self, label=None, qualifier: Iterable | None = None):
        """
        :param label: the type label
        """
        # file descriptors are always 32 bits, right?
        # TODO: That's so closed-minded!
        super().__init__(32, label=label, qualifier=qualifier)

    @property
    def size(self):
        return 32

    def __repr__(self):
        return "fd_t"

    def copy(self):
        return SimTypeFd(label=self.label)

    def _init_str(self):
        return "{}({})".format(
            self.__class__.__name__,
            (f'label="{self.label}"') if self.label is not None else "",
        )

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> int: ...

    def extract(self, state, addr, concrete=False):
        # TODO: EDG says this looks dangerously closed-minded. Just in case...
        assert self.size % state.arch.byte_width == 0

        out = state.memory.load(addr, self.size // state.arch.byte_width, endness=state.arch.memory_endness)
        if not concrete:
            return out
        return state.solver.eval(out)


class PointerDisposition(enum.IntEnum):
    IN_OUT = 0
    IN = 1
    OUT = 2
    # outmaybe = conditionally overwritten
    IN_OUTMAYBE = 3
    OUTMAYBE = 4
    NONE = 5
    UNKNOWN = 6


class SimTypePointer(SimTypeReg):
    """
    SimTypePointer is a type that specifies a pointer to some other type.
    """

    _fields = (*(x for x in SimTypeReg._fields if x != "size"), "pts_to")
    _args = ("pts_to", "label", "offset", "qualifier", "disposition")
    _ident = "ptr"

    def __init__(
        self,
        pts_to: SimType,
        label=None,
        offset=0,
        qualifier: Iterable[str] | None = None,
        disposition: PointerDisposition | int = PointerDisposition.UNKNOWN,
    ):
        """
        :param label:   The type label.
        :param pts_to:  The type to which this pointer points.
        """
        super().__init__(None, label=label, qualifier=qualifier)
        self.pts_to = pts_to
        self.signed = False
        self.offset = offset
        if not isinstance(disposition, PointerDisposition):
            disposition = PointerDisposition(disposition)
        self.disposition = disposition

    def to_json(self, fields: Iterable[str] | None = None, memo: dict[str, SimTypeRef] | None = None) -> dict[str, Any]:
        if memo is None:
            memo = {}
        d = super().to_json(fields=fields, memo=memo)
        if d["offset"] == 0:
            d.pop("offset")
        if "q" in d and not d["q"]:
            d.pop("q")
        if (disp := d.pop("disposition")) != PointerDisposition.UNKNOWN:
            d["disp"] = int(disp)
        return d

    def __repr__(self):
        return f"{self.pts_to}*" if not self.label else self.label

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True):  # pylint: disable=unused-argument
        # if pts_to is SimTypeBottom, we return a void*
        if self.label is not None and name is not None:
            return super().c_repr(name=name, full=full, memo=memo, indent=indent, name_parens=name_parens)
        if isinstance(self.pts_to, SimTypeBottom):
            out = "void*"
            if self.qualifier:
                out = f"{' '.join(sorted(self.qualifier))} {out}"
            if name is None:
                return out
            return f"{out} {name}"
        # if it points to an array, we do not need to add a *

        deref_chr = "*" if not isinstance(self.pts_to, SimTypeArray) else ""
        quals = f"{' '.join(self.qualifier)}" if self.qualifier else ""
        if quals:
            name_with_deref = f"{deref_chr}{quals} {name}" if name else f"{deref_chr}{quals}"
        else:
            name_with_deref = f"{deref_chr}{name}" if name else deref_chr
        return self.pts_to.c_repr(name_with_deref, full, memo, indent)

    def make(self, pts_to):
        new = type(self)(pts_to)
        new._arch = self._arch
        return new

    @property
    def size(self):
        if self._arch is None:
            raise ValueError("Can't tell my size without an arch!")
        return self._arch.bits

    def _with_arch(self, arch):
        out = SimTypePointer(
            self.pts_to.with_arch(arch), self.label, self.offset, qualifier=self.qualifier, disposition=self.disposition
        )
        out._arch = arch
        return out

    def _init_str(self):
        label_str = f', label="{self.label}"' if self.label is not None else ""
        disposition_str = (
            f", disposition={int(self.disposition)}" if self.disposition != PointerDisposition.UNKNOWN else ""
        )
        return f"{self.__class__.__name__}({self.pts_to._init_str()}{label_str}{disposition_str}, offset={self.offset})"

    def copy(self):
        return SimTypePointer(self.pts_to, label=self.label, offset=self.offset, disposition=self.disposition)

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> int: ...

    def extract(self, state, addr, concrete=False):
        # TODO: EDG says this looks dangerously closed-minded. Just in case...
        assert self.size % state.arch.byte_width == 0

        out = state.memory.load(addr, self.size // state.arch.byte_width, endness=state.arch.memory_endness)
        if not concrete:
            return out
        return state.solver.eval(out)


class SimTypeReference(SimTypeReg):
    """
    SimTypeReference is a type that specifies a reference to some other type.
    """

    _args = ("refs", "label", "qualifier")
    _ident = "ref"

    def __init__(self, refs, label=None, qualifier: Iterable | None = None):
        super().__init__(None, label=label, qualifier=qualifier)
        self.refs: SimType = refs

    def __repr__(self):
        return f"{self.refs}&"

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True):  # pylint: disable=unused-argument
        name = "&" if name is None else f"&{name}"
        out = self.refs.c_repr(name, full, memo, indent)
        if self.qualifier:
            out = f"{' '.join(sorted(self.qualifier))} {out}"
        return out

    def make(self, refs):
        new = type(self)(refs)
        new._arch = self._arch
        return new

    @property
    def size(self):
        if self._arch is None:
            raise ValueError("Can't tell my size without an arch!")
        return self._arch.bits

    def _with_arch(self, arch):
        out = SimTypeReference(self.refs.with_arch(arch), label=self.label)
        out._arch = arch
        return out

    def _init_str(self):
        return "{}({}{})".format(
            self.__class__.__name__,
            self.refs._init_str(),
            (f', label="{self.label}"') if self.label is not None else "",
        )

    def copy(self):
        return SimTypeReference(self.refs, label=self.label)

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> int: ...

    def extract(self, state, addr, concrete=False):
        # TODO: EDG says this looks dangerously closed-minded. Just in case...
        assert self.size % state.arch.byte_width == 0

        out = state.memory.load(addr, self.size // state.arch.byte_width, endness=state.arch.memory_endness)
        if not concrete:
            return out
        return state.solver.eval(out)


class SimTypeArray(SimType):
    """
    SimTypeArray is a type that specifies a series of data laid out in sequence.
    """

    _fields = ("elem_type", "length")
    _args = ("elem_type", "length", "label", "qualifier")
    _ident = "array"

    def __init__(self, elem_type, length=None, label=None, qualifier: Iterable | None = None):
        """
        :param label:       The type label.
        :param elem_type:   The type of each element in the array.
        :param length:      An expression of the length of the array, if known.
        """
        super().__init__(label=label)
        self.elem_type: SimType = elem_type
        self.length: int | None = length
        self.qualifier = qualifier

    def __repr__(self):
        return "{}[{}]".format(self.elem_type, "" if self.length is None else self.length)

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True):  # pylint: disable=unused-argument
        if name is None:
            return repr(self)

        name = "{}[{}]".format(name, self.length if self.length is not None else "")
        out = self.elem_type.c_repr(name, full, memo, indent)
        if self.qualifier:
            out = f"{' '.join(sorted(self.qualifier))} {out}"
        return out

    @property
    def size(self):
        if self.length is None:
            return 0
        if self.elem_type.size is None:
            return None
        return self.elem_type.size * self.length

    @property
    def alignment(self):
        return self.elem_type.alignment

    def _with_arch(self, arch):
        out = SimTypeArray(self.elem_type.with_arch(arch), self.length, self.label)
        out._arch = arch
        return out

    def copy(self):
        return SimTypeArray(self.elem_type, length=self.length, label=self.label)

    _can_refine_int = True

    def _refine(self, view, k):
        return view._deeper(
            addr=view._addr + k * (self.elem_type.size // view.state.arch.byte_width), ty=self.elem_type
        )

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> list[Any]:  # associated types...
        ...

    @overload
    def extract(self, state, addr, concrete: Literal[True] = ...) -> list[Any]: ...

    def extract(self, state, addr, concrete=False):
        if self.length is None:
            return []
        if self.elem_type.size is None:
            return None
        return [
            self.elem_type.extract(state, addr + i * (self.elem_type.size // state.arch.byte_width), concrete)
            for i in range(self.length)
        ]

    def store(self, state, addr, value: list[StoreType]):
        if self.elem_type.size is None:
            raise AngrTypeError("Cannot call store on an array of unsized types")
        for i, val in enumerate(value):
            self.elem_type.store(state, addr + i * (self.elem_type.size // state.arch.byte_width), val)

    def _init_str(self):
        return "{}({}, {}{})".format(
            self.__class__.__name__,
            self.elem_type._init_str(),
            self.length,
            f", {self.label}" if self.label is not None else "",
        )


SimTypeFixedSizeArray = SimTypeArray


class SimTypeString(NamedTypeMixin, SimType):
    """
    SimTypeString is a type that represents a C-style string,
    i.e. a NUL-terminated array of bytes.
    """

    _fields = (*SimTypeArray._fields, "length")
    _args = ("length", "label", "name", "qualifier")
    _ident = "str"

    def __init__(
        self, length: int | None = None, label=None, name: str | None = None, qualifier: Iterable | None = None
    ):
        """
        :param label:   The type label.
        :param length:  An expression of the length of the string, if known.
        """
        super().__init__(label=label, name=name)
        self.elem_type = SimTypeChar()
        self.length = length
        self.qualifier = qualifier

    def __repr__(self):
        return "string_t"

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True):  # pylint: disable=unused-argument
        if name is None:
            return repr(self)

        name = "{}[{}]".format(name, self.length if self.length is not None else "")
        out = self.elem_type.c_repr(name, full, memo, indent)
        if self.qualifier:
            out = f"{' '.join(sorted(self.qualifier))} {out}"
        return out

    @overload
    def extract(self, state, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state, addr, concrete: Literal[True]) -> bytes: ...

    def extract(self, state: SimState, addr, concrete=False):
        if self.length is None:
            out = None
            last_byte = state.memory.load(addr, size=1)
            # if we try to extract a symbolic string, it's likely that we are going to be trapped in a very large loop.
            if state.solver.symbolic(last_byte):
                raise ValueError(f"Trying to extract a symbolic string at {state.solver.eval(addr):#x}")
            addr += 1
            while not (claripy.is_true(last_byte == 0) or state.solver.symbolic(last_byte)):
                out = last_byte if out is None else out.concat(last_byte)
                last_byte = state.memory.load(addr, size=1)
                addr += 1
        else:
            out = state.memory.load(addr, size=self.length)
        if not concrete:
            return out if out is not None else claripy.BVV(0, 0)
        return state.solver.eval(out, cast_to=bytes) if out is not None else b""

    _can_refine_int = True

    def _refine(self, view, k):
        return view._deeper(addr=view._addr + k, ty=SimTypeChar())

    @property
    def size(self):
        if self.length is None:
            return 4096  # :/
        return (self.length + 1) * 8

    @property
    def alignment(self):
        return 1

    def _with_arch(self, arch):
        return self

    def copy(self):
        return SimTypeString(length=self.length, label=self.label, name=self.name)

    def _init_str(self):
        return "{}({}, {}{})".format(
            self.__class__.__name__,
            self.elem_type._init_str(),
            self.length,
            f", {self.label}" if self.label is not None else "",
        )


class SimTypeWString(NamedTypeMixin, SimType):
    """
    A wide-character null-terminated string, where each character is 2 bytes.
    """

    _fields = (*SimTypeArray._fields, "length")
    _args = ("length", "label", "name", "qualifier")
    _ident = "wstr"

    def __init__(
        self, length: int | None = None, label=None, name: str | None = None, qualifier: Iterable | None = None
    ):
        super().__init__(label=label, name=name)
        self.elem_type = SimTypeNum(16, False)
        self.length = length
        self.qualifier = qualifier

    def __repr__(self):
        return "wstring_t"

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True):  # pylint: disable=unused-argument
        if name is None:
            return repr(self)

        name = "{}[{}]".format(name, self.length if self.length is not None else "")
        out = self.elem_type.c_repr(name, full, memo, indent)
        if self.qualifier:
            out = f"{' '.join(sorted(self.qualifier))} {out}"
        return out

    def extract(self, state, addr, concrete=False):
        if self.length is None:
            out = None
            last_byte = state.memory.load(addr, 2)
            # if we try to extract a symbolic string, it's likely that we are going to be trapped in a very large loop.
            if state.solver.symbolic(last_byte):
                raise ValueError(f"Trying to extract a symbolic string at {state.solver.eval(addr):#x}")
            addr += 2
            while not (claripy.is_true(last_byte == 0) or state.solver.symbolic(last_byte)):
                out = last_byte if out is None else out.concat(last_byte)
                last_byte = state.memory.load(addr, 2)
                addr += 2
        else:
            out = state.memory.load(addr, self.length * 2)
        if out is None:
            out = claripy.BVV(0, 0)
        if not concrete:
            return out
        return "".join(
            chr(state.solver.eval(x.reversed if state.arch.memory_endness == "Iend_LE" else x)) for x in out.chop(16)
        )

    def store(self, state, addr, value):
        raise NotImplementedError

    _can_refine_int = True

    def _refine(self, view, k):
        return view._deeper(addr=view._addr + k * 2, ty=SimTypeNum(16, False))

    @property
    def size(self):
        if self.length is None:
            return 4096
        return (self.length * 2 + 2) * 8

    @property
    def alignment(self):
        return 2

    def _with_arch(self, arch):
        return self

    def copy(self):
        return SimTypeWString(length=self.length, label=self.label, name=self.name)

    def _init_str(self):
        return "{}({}, {}{})".format(
            self.__class__.__name__,
            self.elem_type._init_str(),
            self.length,
            f", {self.label}" if self.label is not None else "",
        )


class SimTypeFunction(SimType):
    """
    SimTypeFunction is a type that specifies an actual function (i.e. not a pointer) with certain types of arguments and
    a certain return value.
    """

    _fields = ("args", "returnty", "variadic")
    _args = ("args", "returnty", "label", "arg_names", "variadic")
    _ident = "func"
    base = False

    def __init__(
        self,
        args: Iterable[SimType],
        returnty: SimType | None,
        label=None,
        arg_names: Iterable[str] | None = None,
        variadic=False,
    ):
        """
        :param label:    The type label
        :param args:     A tuple of types representing the arguments to the function
        :param returnty: The return type of the function, or none for void
        :param variadic: Whether the function accepts varargs
        """
        super().__init__(label=label)
        self.args: tuple[SimType, ...] = tuple(args)
        self.returnty: SimType | None = returnty
        self.arg_names: tuple[str, ...] = tuple(arg_names) if arg_names else ()
        self.variadic = variadic

    def to_json(self, fields: Iterable[str] | None = None, memo: dict[str, SimTypeRef] | None = None) -> dict[str, Any]:
        if memo is None:
            memo = {}
        d = super().to_json(fields=fields, memo=memo)
        if "variadic" in d and d["variadic"] is False:
            d.pop("variadic")
        return d

    def __hash__(self):
        return hash(type(self)) ^ hash(tuple(self.args)) ^ hash(self.returnty)

    def __repr__(self):
        argstrs = [str(a) for a in self.args]
        if self.variadic:
            argstrs.append("...")
        return "({}) -> {}".format(", ".join(argstrs), self.returnty)

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True, show_void: bool = True):  # type: ignore[override]
        formatted_args = [
            a.c_repr(n, max(full - 1, 0), memo, indent)
            for a, n in zip(self.args, self.arg_names if self.arg_names and full else (None,) * len(self.args))
        ]
        if self.variadic:
            formatted_args.append("...")
        name_str = f"({name or ''})" if name_parens else name or ""
        args_str = ", ".join(formatted_args) if formatted_args else "void" if show_void else ""
        proto = f"{name_str}({args_str})"
        return f"void {proto}" if self.returnty is None else self.returnty.c_repr(proto, full, memo, indent)

    @property
    def size(self):
        return 4096  # ???????????

    def _with_arch(self, arch):
        out = SimTypeFunction(
            [a.with_arch(arch) for a in self.args],
            self.returnty.with_arch(arch) if self.returnty is not None else None,
            label=self.label,
            arg_names=self.arg_names,
            variadic=self.variadic,
        )
        out._arch = arch
        return out

    def _arg_names_str(self, show_variadic=True):
        argnames = list(self.arg_names)
        if self.variadic and show_variadic:
            argnames.append("...")
        return ", ".join(f'"{arg_name}"' for arg_name in argnames)

    def _init_str(self):
        return "{}([{}], {}{}{}{})".format(
            self.__class__.__name__,
            ", ".join([arg._init_str() for arg in self.args]),
            self.returnty._init_str() if self.returnty else "void",
            (f', label="{self.label}"') if self.label else "",
            (f", arg_names=[{self._arg_names_str(show_variadic=False)}]") if self.arg_names else "",
            ", variadic=True" if self.variadic else "",
        )

    def copy(self):
        return SimTypeFunction(
            self.args, self.returnty, label=self.label, arg_names=self.arg_names, variadic=self.variadic
        )


class SimTypeCppFunction(SimTypeFunction):
    """
    SimTypeCppFunction is a type that specifies an actual C++-style function with information about arguments, return
    value, and more C++-specific properties.

    :ivar ctor: Whether the function is a constructor or not.
    :ivar dtor: Whether the function is a destructor or not.
    """

    _args = ("args", "returnty", "label", "arg_names", "ctor", "dtor", "convention")
    _ident = "cppfunc"

    def __init__(
        self,
        args,
        returnty,
        label=None,
        arg_names: Iterable[str] | None = None,
        ctor: bool = False,
        dtor: bool = False,
        convention: str | None = None,
    ):
        super().__init__(args, returnty, label=label, arg_names=arg_names, variadic=False)
        self.ctor = ctor
        self.dtor = dtor
        self.convention = convention

    def __repr__(self):
        argstrs = [str(a) for a in self.args]
        if self.variadic:
            argstrs.append("...")
        return str(self.label) + "({}) -> {}".format(", ".join(argstrs), self.returnty)

    def _init_str(self):
        return "{}([{}], {}{}{}{})".format(
            self.__class__.__name__,
            ", ".join([arg._init_str() for arg in self.args]),
            self.returnty,
            (f", label={self.label}") if self.label else "",
            (f", arg_names=[{self._arg_names_str(show_variadic=False)}]") if self.arg_names else "",
            ", variadic=True" if self.variadic else "",
        )

    def _with_arch(self, arch):
        out = SimTypeCppFunction(
            [a.with_arch(arch) for a in self.args],
            self.returnty.with_arch(arch) if self.returnty is not None else None,
            label=self.label,
            arg_names=self.arg_names,
            ctor=self.ctor,
            dtor=self.dtor,
            convention=self.convention,
        )
        out._arch = arch
        return out

    def copy(self):
        return SimTypeCppFunction(
            self.args,
            self.returnty,
            label=self.label,
            arg_names=self.arg_names,
            ctor=self.ctor,
            dtor=self.dtor,
            convention=self.convention,
        )


class SimTypeLength(SimTypeLong):
    """
    SimTypeLength is a type that specifies the length of some buffer in memory.

    ...I'm not really sure what the original design of this class was going for
    """

    _fields = (*(x for x in SimTypeReg._fields if x != "size"), "addr", "length")  # ?
    _args = ("signed", "addr", "length", "label")
    _ident = "len"

    def __init__(self, signed=False, addr=None, length=None, label=None):
        """
        :param signed:  Whether the value is signed or not
        :param label:   The type label.
        :param addr:    The memory address (expression).
        :param length:  The length (expression).
        """
        super().__init__(signed=signed, label=label)
        self.addr = addr
        self.length = length

    def __repr__(self):
        return "size_t"

    @property
    def size(self):
        if self._arch is None:
            raise ValueError("I can't tell my size without an arch!")
        return self._arch.bits

    def _init_str(self):
        return f"{self.__class__.__name__}(size={self.size})"

    def copy(self):
        return SimTypeLength(signed=self.signed, addr=self.addr, length=self.length, label=self.label)


class SimTypeFloat(SimTypeReg):
    """
    An IEEE754 single-precision floating point number
    """

    _base_name = "float"
    _args = ("label", "qualifier")
    _ident = "float"

    def __init__(self, size=32, label=None, qualifier: Iterable | None = None):
        super().__init__(size, label=label, qualifier=qualifier)

    sort = claripy.FSORT_FLOAT
    signed = True

    @property
    def size(self) -> int:
        return 32

    def extract(self, state, addr, concrete=False):
        itype = claripy.fpToFP(
            state.memory.load(addr, self.size // state.arch.byte_width, endness=state.arch.memory_endness), self.sort
        )
        if concrete:
            return state.solver.eval(itype)
        return itype

    def store(self, state, addr, value: StoreType | claripy.ast.FP):
        if isinstance(value, (int, float)):
            value = claripy.FPV(float(value), self.sort)
        return super().store(state, addr, value)  # type: ignore    # trust me bro

    def __repr__(self) -> str:
        return "float"

    def _init_str(self):
        return f"{self.__class__.__name__}(size={self.size})"

    def copy(self):
        return SimTypeFloat(self.size)


class SimTypeDouble(SimTypeFloat):
    """
    An IEEE754 double-precision floating point number
    """

    _base_name = "double"
    _args = ("align_double", "label", "qualifier")
    _ident = "double"

    def __init__(self, align_double=True, label=None, qualifier: Iterable | None = None):
        self.align_double = align_double
        super().__init__(64, label=label)
        self.qualifier = qualifier

    sort = claripy.FSORT_DOUBLE

    @property
    def size(self) -> int:
        return 64

    def __repr__(self):
        return "double"

    @property
    def alignment(self):
        return 8 if self.align_double else 4

    def _init_str(self):
        return f"{self.__class__.__name__}(align_double={self.align_double})"

    def copy(self):
        return SimTypeDouble(align_double=self.align_double)


class SimStruct(NamedTypeMixin, SimType):
    _fields = ("name", "fields", "anonymous")
    _args = ("fields", "name", "pack", "align", "anonymous", "qualifier")
    _ident = "struct"

    def __init__(
        self,
        fields: dict[str, SimType] | OrderedDict[str, SimType],
        name=None,
        pack=False,
        align=None,
        anonymous: bool = False,
        qualifier: Iterable | None = None,
    ):
        super().__init__(None, name="<anon>" if name is None else name)

        self._pack = pack
        self._align = align
        self.anonymous = anonymous
        self.fields: OrderedDict[str, SimType] = OrderedDict(fields)
        if qualifier:
            self.qualifier = qualifier

        # FIXME: Hack for supporting win32 struct definitions
        if self.name == "_Anonymous_e__Struct":
            self.anonymous = True

        self._arch_memo = {}

    #
    # pack and align are for supporting SimType.from_json and SimType.to_json
    #

    @property
    def pack(self):
        return self._pack

    @property
    def align(self):
        return self._align

    #
    # Other properties
    #

    @property
    def packed(self):
        return self._pack

    @property
    def offsets(self) -> dict[str, int]:
        if self._arch is None:
            raise ValueError("Need an arch to calculate offsets")

        offsets = {}
        offset_so_far = 0
        for name, ty in self.fields.items():
            if ty.size is None:
                l.debug(
                    "Found a bottom field in struct %s. Ignore and increment the offset using the default "
                    "element size.",
                    self.name,
                )
                continue
            if not self._pack:
                align = ty.alignment
                if align is NotImplemented:
                    # hack!
                    align = 1
                if offset_so_far % align != 0:
                    offset_so_far += align - offset_so_far % align
                offsets[name] = offset_so_far
                offset_so_far += ty.size // self._arch.byte_width
            else:
                offsets[name] = offset_so_far // self._arch.byte_width
                offset_so_far += ty.size

        return offsets

    def to_json(self, fields: Iterable[str] | None = None, memo: dict[str, SimTypeRef] | None = None) -> dict[str, Any]:
        if memo is None:
            memo = {}

        if self.name in memo:
            return memo[self.name].to_json(fields=fields, memo=memo)
        memo[self.name] = SimTypeRef(self.name, self.__class__)
        d = super().to_json(fields=fields, memo=memo)
        if d["pack"] is False:
            d.pop("pack")
        if d["align"] is None:
            d.pop("align")
        if d["anonymous"] is False:
            d.pop("anonymous")
        if "q" in d and not d["q"]:
            d.pop("q")
        return d

    def extract(self, state, addr, concrete=False) -> SimStructValue:
        values = {}
        for name, offset in self.offsets.items():
            ty = self.fields[name]
            v = SimMemView(ty=ty, addr=addr + offset, state=state)
            if concrete:
                values[name] = v.concrete
            else:
                values[name] = v.resolved

        return SimStructValue(self, values=values)

    def _with_arch(self, arch):
        if arch.name in self._arch_memo:
            return self._arch_memo[arch.name]

        out = SimStruct({}, name=self.name, pack=self._pack, align=self._align)
        out._arch = arch
        self._arch_memo[arch.name] = out

        out.fields = OrderedDict((k, v.with_arch(arch)) for k, v in self.fields.items())

        # Fixup the offsets to byte aligned addresses for all SimTypeNumOffset types
        offset_so_far = 0
        for _, ty in out.fields.items():
            if isinstance(ty, SimTypeNumOffset):
                out._pack = True
                ty.offset = offset_so_far % arch.byte_width
                offset_so_far += ty.size
        return out

    def __repr__(self):
        return f"struct {self.name}"

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True):  # pylint: disable=unused-argument
        if not full or (memo is not None and self in memo):
            return super().c_repr(name, full, memo, indent)

        indented = " " * indent if indent is not None else ""
        new_indent = indent + 4 if indent is not None else None
        new_indented = " " * new_indent if new_indent is not None else ""
        newline = "\n" if indent is not None else " "
        new_memo = (self,) + (memo if memo is not None else ())
        members = newline.join(
            new_indented + v.c_repr(k, max(full - 1, 0), new_memo, new_indent) + ";" for k, v in self.fields.items()
        )
        out = f"struct {self.name} {{{newline}{members}{newline}{indented}}}{'' if name is None else ' ' + name}"
        if self.qualifier:
            out = f"{' '.join(sorted(self.qualifier))} {out}"
        return out

    def __hash__(self):
        return hash((SimStruct, self._name, self._align, self._pack, tuple(self.fields.keys())))

    @property
    def size(self):
        if not self.offsets:
            return 0
        if self._arch is None:
            raise ValueError("Need an arch to compute size")

        last_name, last_off = list(self.offsets.items())[-1]
        last_type = self.fields[last_name]
        if isinstance(last_type, SimTypeNumOffset):
            return last_off * self._arch.byte_width + (last_type.size + last_type.offset)
        if last_type.size is None:
            raise AngrTypeError("Cannot compute the size of a struct with elements with no size")
        return last_off * self._arch.byte_width + last_type.size

    @property
    def alignment(self):
        if self._align is not None:
            return self._align
        if all(val.alignment is NotImplemented for val in self.fields.values()):
            return NotImplemented
        return max(val.alignment if val.alignment is not NotImplemented else 1 for val in self.fields.values())

    def _refine_dir(self):
        return list(self.fields.keys())

    def _refine(self, view, k):
        offset = self.offsets[k]
        ty = self.fields[k]
        return view._deeper(ty=ty, addr=view._addr + offset)

    def store(self, state, addr, value: StoreType):
        if type(value) is dict:
            pass
        elif type(value) is SimStructValue:
            value = value._values
        else:
            raise TypeError(f"Can't store struct of type {type(value)}")

        assert isinstance(value, dict)
        if len(value) != len(self.fields):
            raise ValueError(f"Passed bad values for {self}; expected {len(self.offsets)}, got {len(value)}")

        for field, offset in self.offsets.items():
            ty = self.fields[field]
            ty.store(state, addr + offset, value[field])

    @staticmethod
    def _field_str(field_name, field_type):
        return f'("{field_name}", {field_type._init_str()})'

    def _init_str(self):
        return '{}(OrderedDict(({},)), name="{}", pack={}, align={})'.format(
            self.__class__.__name__,
            ", ".join([self._field_str(f, ty) for f, ty in self.fields.items()]),
            self._name,
            self._pack,
            self._align,
        )

    def copy(self):
        return SimStruct(dict(self.fields), name=self.name, pack=self._pack, align=self._align)

    def __eq__(self, other, avoid: dict[str, set[SimType]] | None = None):
        if not isinstance(other, SimStruct):
            return False
        if not (
            self._pack == other._pack
            and self._align == other._align
            and self.label == other.label
            and self._name == other._name
            and self._arch == other._arch
        ):
            return False
        # fields comparison that accounts for self references
        if not self.fields and not other.fields:
            return True
        keys_self = list(self.fields)
        keys_other = list(other.fields)
        if keys_self != keys_other:
            return False
        if avoid is None:
            avoid = {"self": {self}, "other": {other}}
        for key in keys_self:
            field_self = self.fields[key]
            field_other = other.fields[key]
            if field_self in avoid["self"] and field_other in avoid["other"]:
                continue
            avoid["self"].add(field_self)
            avoid["other"].add(field_other)
            if not field_self.__eq__(field_other, avoid=avoid):
                return False
        return True


class SimStructValue:
    """
    A SimStruct type paired with some real values
    """

    def __init__(self, struct, values=None):
        """
        :param struct:      A SimStruct instance describing the type of this struct
        :param values:      A mapping from struct fields to values
        """
        self._struct = struct
        # since the keys are specified, also support specifying the values as just a list
        if values is not None and hasattr(values, "__iter__") and not hasattr(values, "items"):
            values = dict(zip(struct.fields.keys(), values))
        self._values = defaultdict(lambda: None, values or ())

    @property
    def struct(self):
        return self._struct

    def __indented_repr__(self, indent=0):
        fields = []
        for name in self._struct.fields:
            value = self._values[name]
            try:
                f = value.__indented_repr__  # type: ignore[reportAttributeAccessIssue]
                s = f(indent=indent + 2)
            except AttributeError:
                s = repr(value)
            fields.append(" " * (indent + 2) + f".{name} = {s}")

        return "{{\n{}\n{}}}".format(",\n".join(fields), " " * indent)

    def __repr__(self):
        return self.__indented_repr__()

    def __getattr__(self, k):
        return self[k]

    def __getitem__(self, k):
        if type(k) is int:
            k = self._struct.fields[k]
        if k not in self._values:
            for f in self._struct.fields:
                if isinstance(f, NamedTypeMixin) and f.name is None:
                    try:
                        return f[k]  # type: ignore # lukas WHAT
                    except KeyError:
                        continue
            raise KeyError(k)

        return self._values[k]

    def copy(self):
        return SimStructValue(self._struct, values=defaultdict(lambda: None, self._values))


class SimUnion(NamedTypeMixin, SimType):
    fields = ("members", "name")
    _args = ("members", "name", "label", "qualifier")
    _ident = "union"

    def __init__(self, members: dict[str, SimType], name=None, label=None, qualifier: Iterable | None = None):
        """
        :param members:     The members of the union, as a mapping name -> type
        :param name:        The name of the union
        """
        super().__init__(label, name=name if name is not None else "<anon>")
        self.members = members
        if qualifier:
            self.qualifier = qualifier

    @property
    def size(self):
        if self._arch is None:
            raise ValueError("Can't tell my size without an arch!")
        all_member_sizes: list[int | None] = [
            ty.size for ty in self.members.values() if not isinstance(ty, (SimTypeBottom, SimTypeRef))
        ]
        member_sizes: list[int] = [s for s in all_member_sizes if s is not None]
        # fall back to word size in case all members are SimTypeBottom
        return max(member_sizes) if member_sizes else self._arch.bytes

    @property
    def alignment(self):
        if all(val.alignment is NotImplemented for val in self.members.values()):
            return NotImplemented
        return max(val.alignment if val.alignment is not NotImplemented else 1 for val in self.members.values())

    def _refine_dir(self):
        return list(self.members.keys())

    def _refine(self, view, k):
        ty = self.members[k]
        return view._deeper(ty=ty, addr=view._addr)

    def extract(self, state, addr, concrete=False):
        values = {}
        for name, ty in self.members.items():
            v = SimMemView(ty=ty, addr=addr, state=state)
            if concrete:
                values[name] = v.concrete
            else:
                values[name] = v.resolved

        return SimUnionValue(self, values=values)

    def __repr__(self):
        # use the str instead of repr of each member to avoid exceed recursion
        # depth when representing self-referential unions
        return "union {} {{\n\t{}\n}}".format(
            self.name, "\n\t".join(f"{name} {ty!s};" for name, ty in self.members.items())
        )

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True):  # pylint: disable=unused-argument
        if not full or (memo is not None and self in memo):
            return super().c_repr(name, full, memo, indent)

        indented = " " * indent if indent is not None else ""
        new_indent = indent + 4 if indent is not None else None
        new_indented = " " * new_indent if new_indent is not None else ""
        newline = "\n" if indent is not None else " "
        new_memo = (self,) + (memo if memo is not None else ())
        members = newline.join(
            new_indented + v.c_repr(k, max(full - 1, 0), new_memo, new_indent) + ";" for k, v in self.members.items()
        )
        out = f"union {self.name} {{{newline}{members}{newline}{indented}}}{'' if name is None else ' ' + name}"
        if self.qualifier:
            out = f"{' '.join(sorted(self.qualifier))} {out}"
        return out

    def _init_str(self):
        return '{}({{{}}}, name="{}", label="{}")'.format(
            self.__class__.__name__,
            ", ".join([self._field_str(f, ty) for f, ty in self.members.items()]),
            self._name,
            self.label,
        )

    @staticmethod
    def _field_str(field_name, field_type):
        return f'"{field_name}": {field_type._init_str()}'

    def __str__(self):
        return f"union {self.name}"

    def _with_arch(self, arch):
        out = SimUnion({name: ty.with_arch(arch) for name, ty in self.members.items()}, self.label)
        out._arch = arch
        return out

    def copy(self):
        return SimUnion(dict(self.members), name=self.name, label=self.label)


class SimUnionValue:
    """
    A SimStruct type paired with some real values
    """

    def __init__(self, union, values=None):
        """
        :param union:      A SimUnion instance describing the type of this union
        :param values:      A mapping from union members to values
        """
        self._union = union
        self._values = defaultdict(lambda: None, values or ())

    def __indented_repr__(self, indent=0):
        fields = []
        for name, value in self._values.items():
            try:
                f = value.__indented_repr__  # type: ignore[reportAttributeAccessIssue]
                s = f(indent=indent + 2)
            except AttributeError:
                s = repr(value)
            fields.append(" " * (indent + 2) + f".{name} = {s}")

        return "{{\n{}\n{}}}".format(",\n".join(fields), " " * indent)

    def __repr__(self):
        return self.__indented_repr__()

    def __getattr__(self, k):
        return self[k]

    def __getitem__(self, k):
        if k not in self._values:
            raise KeyError(k)
        return self._values[k]

    def copy(self):
        return SimUnionValue(self._union, values=self._values)


class SimTypeEnum(NamedTypeMixin, SimType):
    """
    An enum type with named integer constants.

    :param members: A mapping from member names to their integer values.
    :param base_type: The underlying integer type (default: SimTypeInt).
    :param name: The name of the enum type.
    """

    _fields = ("name", "members")
    _args = ("members", "base_type", "name", "qualifier")
    _ident = "enum"

    def __init__(
        self,
        members: dict[str, int],
        base_type: SimType | None = None,
        name: str | None = None,
        qualifier: Iterable[str] | None = None,
    ):
        super().__init__(name=name if name is not None else "<anon>")
        self.members: dict[str, int] = dict(members)
        self._base_type = base_type if base_type is not None else SimTypeInt(signed=False)
        self._reverse_members: dict[int, str] = {v: k for k, v in members.items()}
        if qualifier:
            self.qualifier = qualifier

    @property
    def base_type(self) -> SimType:
        return self._base_type

    @property
    def size(self) -> int | None:
        return self._base_type.size

    @property
    def alignment(self):
        return self._base_type.alignment

    def resolve(self, value: int) -> str | None:
        """
        Resolve an integer value to its enum member name.

        :param value: The integer value to resolve.
        :return: The member name if found, None otherwise.
        """
        return self._reverse_members.get(value)

    def _with_arch(self, arch):
        out = SimTypeEnum(
            members=self.members,
            base_type=self._base_type.with_arch(arch),
            name=self._name,
            qualifier=self.qualifier,
        )
        out._arch = arch
        return out

    def __repr__(self):
        return f"enum {self._name}"

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True):  # pylint: disable=unused-argument
        if not full or (memo is not None and self in memo):
            out = f"enum {self._name}"
            if self.qualifier:
                out = f"{' '.join(sorted(self.qualifier))} {out}"
            if name is not None:
                out = f"{out} {name}"
            return out

        indented = " " * indent if indent is not None else ""
        new_indent = indent + 4 if indent is not None else None
        new_indented = " " * new_indent if new_indent is not None else ""
        newline = "\n" if indent is not None else " "
        members_str = ("," + newline).join(
            f"{new_indented}{member_name} = {member_value}" for member_name, member_value in self.members.items()
        )
        out = f"enum {self._name} {{{newline}{members_str}{newline}{indented}}}"
        if self.qualifier:
            out = f"{' '.join(sorted(self.qualifier))} {out}"
        if name is not None:
            out = f"{out} {name}"
        return out

    def _init_str(self):
        members_str = ", ".join(f'"{k}": {v}' for k, v in self.members.items())
        return f'{self.__class__.__name__}({{{members_str}}}, name="{self._name}")'

    def __hash__(self):
        return hash((SimTypeEnum, self._name, tuple(sorted(self.members.items()))))

    def copy(self):
        return SimTypeEnum(
            members=dict(self.members),
            base_type=self._base_type.copy() if hasattr(self._base_type, "copy") else self._base_type,
            name=self._name,
            qualifier=self.qualifier,
        )

    def extract(self, state, addr, concrete=False):
        return self._base_type.extract(state, addr, concrete=concrete)

    def store(self, state, addr, value):
        self._base_type.store(state, addr, value)


class SimTypeBitfield(NamedTypeMixin, SimType):
    """
    A bitfield/flags type where values can be combinations of named bit flags.

    This is useful for representing flags like PROT_READ | PROT_WRITE where
    multiple flags can be combined with bitwise OR.

    :param flags: A mapping from flag names to their bitmask values.
    :param base_type: The underlying integer type (default: SimTypeInt).
    :param name: The name of the bitfield type.
    """

    _fields = ("name", "flags")
    _args = ("flags", "base_type", "name", "qualifier")
    _ident = "bitfield"

    def __init__(
        self,
        flags: dict[str, int],
        base_type: SimType | None = None,
        name: str | None = None,
        qualifier: Iterable[str] | None = None,
    ):
        super().__init__(None, name=name if name is not None else "<anon>")
        self.flags: dict[str, int] = dict(flags)
        self._base_type = base_type if base_type is not None else SimTypeInt(signed=False)
        # Sort flags by value (descending) to prefer higher-value flags when resolving
        self._sorted_flags: list[tuple[str, int]] = sorted(flags.items(), key=lambda x: x[1], reverse=True)
        if qualifier:
            self.qualifier = qualifier

    @property
    def base_type(self) -> SimType:
        return self._base_type

    @property
    def size(self) -> int | None:
        return self._base_type.size

    @property
    def alignment(self):
        return self._base_type.alignment

    def resolve(self, value: int) -> tuple[list[str], int]:
        """
        Resolve an integer value to a combination of flag names.

        :param value: The integer value to resolve.
        :return: A tuple of (matched_flag_names, unknown_bits).
                 If unknown_bits != 0, there are bits set that don't match any known flag.
        """
        matched_flags: list[str] = []
        remaining = value

        for flag_name, flag_value in self._sorted_flags:
            if flag_value == 0:
                continue  # Skip zero-value flags unless nothing else matches
            if (remaining & flag_value) == flag_value:
                matched_flags.append(flag_name)
                remaining &= ~flag_value

        # Check for a zero flag if nothing matched and value is 0
        if value == 0 and not matched_flags:
            for flag_name, flag_value in self.flags.items():
                if flag_value == 0:
                    matched_flags.append(flag_name)
                    break

        return matched_flags, remaining

    def render(self, value: int) -> str:
        """
        Render an integer value as a combination of flag names.

        :param value: The integer value to render.
        :return: A string like "PROT_READ | PROT_WRITE" or "PROT_READ | PROT_WRITE | 0x100"
                 if there are unknown bits.
        """
        matched_flags, unknown_bits = self.resolve(value)
        parts = matched_flags.copy()
        if unknown_bits != 0:
            parts.append(hex(unknown_bits))
        if not parts:
            return "0"
        return " | ".join(parts)

    def has_unknown_bits(self, value: int) -> bool:
        """
        Check if a value contains bits that don't match any known flag.

        :param value: The integer value to check.
        :return: True if there are unknown bits, False otherwise.
        """
        _, unknown_bits = self.resolve(value)
        return unknown_bits != 0

    def validate(self, value: int) -> bool:
        """
        Check if a value contains only known flags.

        :param value: The integer value to validate.
        :return: True if all bits match known flags, False otherwise.
        """
        return not self.has_unknown_bits(value)

    def _with_arch(self, arch):
        out = SimTypeBitfield(
            flags=self.flags,
            base_type=self._base_type.with_arch(arch),
            name=self._name,
            qualifier=self.qualifier,
        )
        out._arch = arch
        return out

    def __repr__(self):
        return f"bitfield {self._name}"

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True):  # pylint: disable=unused-argument
        # Bitfields are rendered similarly to enums in C
        if not full or (memo is not None and self in memo):
            out = f"enum {self._name}"  # Use enum syntax since C doesn't have bitfield types
            if self.qualifier:
                out = f"{' '.join(sorted(self.qualifier))} {out}"
            if name is not None:
                out = f"{out} {name}"
            return out

        indented = " " * indent if indent is not None else ""
        new_indent = indent + 4 if indent is not None else None
        new_indented = " " * new_indent if new_indent is not None else ""
        newline = "\n" if indent is not None else " "
        flags_str = ("," + newline).join(
            f"{new_indented}{flag_name} = {hex(flag_value)}" for flag_name, flag_value in self.flags.items()
        )
        out = f"enum {self._name} {{{newline}{flags_str}{newline}{indented}}}"
        if self.qualifier:
            out = f"{' '.join(sorted(self.qualifier))} {out}"
        if name is not None:
            out = f"{out} {name}"
        return out

    def _init_str(self):
        flags_str = ", ".join(f'"{k}": {hex(v)}' for k, v in self.flags.items())
        return f'{self.__class__.__name__}({{{flags_str}}}, name="{self._name}")'

    def __hash__(self):
        return hash((SimTypeBitfield, self._name, tuple(sorted(self.flags.items()))))

    def copy(self):
        return SimTypeBitfield(
            flags=dict(self.flags),
            base_type=self._base_type.copy() if hasattr(self._base_type, "copy") else self._base_type,
            name=self._name,
            qualifier=self.qualifier,
        )

    def extract(self, state, addr, concrete=False):
        return self._base_type.extract(state, addr, concrete=concrete)

    def store(self, state, addr, value):
        self._base_type.store(state, addr, value)


class SimCppClass(SimStruct):
    _args = (
        "unique_name",
        "name",
        "members",
        "function_members",
        "vtable_ptrs",
        "pack",
        "align",
        "size",
        "anonymous",
    )
    _ident = "cppclass"

    def __init__(
        self,
        *,
        unique_name: str | None = None,
        name: str | None = None,
        members: dict[str, SimType] | None = None,
        function_members: dict[str, SimTypeCppFunction] | None = None,
        vtable_ptrs=None,
        pack: bool = False,
        align=None,
        size: int | None = None,
        anonymous: bool = False,
    ):
        super().__init__(members or {}, name=name, pack=pack, align=align, anonymous=anonymous)
        self.unique_name = unique_name
        # these are actually addresses in the binary
        self.function_members = function_members
        # this should also be added to the fields once we know the offsets of the members of this object
        self.vtable_ptrs = [] if vtable_ptrs is None else vtable_ptrs

        # we can force the size (in bits) of a class because sometimes the class can be opaque and we don't know its
        # layout
        self._size = size

    @property
    def members(self):
        return self.fields

    @members.setter
    def members(self, value):
        self.fields = value

    @property
    def size(self):
        if self._size is not None:
            return self._size
        return super().size

    def __repr__(self):
        return f"class {self.name}" if not self.name.startswith("class") else self.name

    def to_json(self, fields: Iterable[str] | None = None, memo: dict[str, SimTypeRef] | None = None) -> dict[str, Any]:
        if memo is None:
            memo = {}

        if self.name in memo:
            return memo[self.name].to_json(fields=fields, memo=memo)
        memo[self.name] = SimTypeRef(self.name, SimCppClass)
        d = super().to_json(fields=fields, memo=memo)
        if "pack" in d and d["pack"] is False:
            d.pop("pack")
        if "align" in d and d["align"] is None:
            d.pop("align")
        if "anonymous" in d and d["anonymous"] is False:
            d.pop("anonymous")
        if "q" in d and not d["q"]:
            d.pop("q")
        return d

    def extract(self, state, addr, concrete=False) -> SimCppClassValue:
        values = {}
        for name, offset in self.offsets.items():
            ty = self.fields[name]
            v = SimMemView(ty=ty, addr=addr + offset, state=state)
            if concrete:
                values[name] = v.concrete
            else:
                values[name] = v.resolved

        return SimCppClassValue(self, values=values)

    def store(self, state, addr, value: StoreType):
        if type(value) is dict:
            pass
        elif type(value) is SimCppClassValue:
            value = value._values
        else:
            raise TypeError(f"Can't store struct of type {type(value)}")

        assert isinstance(value, dict)
        if len(value) != len(self.fields):
            raise ValueError(f"Passed bad values for {self}; expected {len(self.offsets)}, got {len(value)}")

        for field, offset in self.offsets.items():
            ty = self.fields[field]
            ty.store(state, addr + offset, value[field])

    def _with_arch(self, arch) -> SimCppClass:
        if arch.name in self._arch_memo:
            return self._arch_memo[arch.name]

        out = SimCppClass(
            unique_name=self.unique_name,
            name=self.name,
            members={},
            function_members={},
            vtable_ptrs=self.vtable_ptrs,
            pack=self._pack,
            align=self._align,
            size=self._size,
        )
        out._arch = arch
        self._arch_memo[arch.name] = out

        out.members = OrderedDict((k, v.with_arch(arch)) for k, v in self.members.items())
        out.function_members = (
            OrderedDict((k, v.with_arch(arch)) for k, v in self.function_members.items())
            if self.function_members is not None
            else None
        )

        # Fixup the offsets to byte aligned addresses for all SimTypeNumOffset types
        offset_so_far = 0
        for _, ty in out.members.items():
            if isinstance(ty, SimTypeNumOffset):
                out._pack = True
                ty.offset = offset_so_far % arch.byte_width
                offset_so_far += ty.size
        return out

    def copy(self):
        return SimCppClass(
            unique_name=self.unique_name,
            name=self.name,
            members=dict(self.fields),
            pack=self._pack,
            align=self._align,
            function_members=self.function_members,
            vtable_ptrs=self.vtable_ptrs,
            size=self._size,
        )


class SimCppClassValue(SimStructValue):
    """
    A SimCppClass type paired with some real values
    """

    def __init__(self, class_type: SimCppClass, values):
        super().__init__(class_type, values)
        self._class = class_type

    def __indented_repr__(self, indent=0):
        fields = []
        for name in self._class.fields:
            value = self._values[name]
            try:
                f = value.__indented_repr__  # type: ignore[reportAttributeAccessIssue]
                s = f(indent=indent + 2)
            except AttributeError:
                s = repr(value)
            fields.append(" " * (indent + 2) + f".{name} = {s}")

        return "{{\n{}\n{}}}".format(",\n".join(fields), " " * indent)

    def __repr__(self):
        return self.__indented_repr__()

    def __getattr__(self, k):
        return self[k]

    def __getitem__(self, k: int | str):
        if isinstance(k, int):
            k = list(self._class.fields.keys())[k]
        if k not in self._values:
            for f in self._class.fields:
                if isinstance(f, NamedTypeMixin) and f.name is None:
                    try:
                        return f[k]  # type: ignore # lukas WHAT
                    except KeyError:
                        continue
            return self._values[k]

        return self._values[k]

    def copy(self):
        return SimCppClassValue(self._class, values=defaultdict(lambda: None, self._values))


class SimTypeNumOffset(SimTypeNum):
    """
    like SimTypeNum, but supports an offset of 1 to 7 to a byte aligned address to allow structs with bitfields
    """

    _fields = (*SimTypeNum._fields, "offset")
    _args = ("size", "signed", "label", "offset", "qualifier")
    _ident = "numoff"

    def __init__(self, size, signed=True, label=None, offset=0, qualifier: Iterable | None = None):
        super().__init__(size, signed, label, qualifier=qualifier)
        self.offset = offset

    @overload
    def extract(self, state: SimState, addr, concrete: Literal[False] = ...) -> claripy.ast.BV: ...

    @overload
    def extract(self, state: SimState, addr, concrete: Literal[True]) -> int: ...

    def extract(self, state: SimState, addr, concrete=False):
        if state.arch.memory_endness != Endness.LE:
            raise NotImplementedError("This has only been implemented and tested with Little Endian arches so far")
        minimum_load_size = self.offset + self.size  # because we start from a byte aligned offset _before_ the value
        # Now round up to the next byte
        load_size = (minimum_load_size - minimum_load_size % (-state.arch.byte_width)) // state.arch.byte_width
        out = state.memory.load(addr, size=load_size, endness=state.arch.memory_endness)
        out = out[self.offset + self.size - 1 : self.offset]

        if not concrete:
            return out
        n = state.solver.eval(out)
        if self.signed and n >= 1 << (self.size - 1):
            n -= 1 << (self.size)
        return n

    def store(self, state, addr, value):
        raise NotImplementedError

    def copy(self):
        return SimTypeNumOffset(self.size, signed=self.signed, label=self.label, offset=self.offset)


class SimTypeRef(SimType):
    """
    SimTypeRef is a to-be-resolved reference to another SimType.

    SimTypeRef is not SimTypeReference.
    """

    _args = ("name", "original_type", "qualifier")
    _ident = "_ref"

    def __init__(self, name, original_type: type[SimType], qualifier: Iterable | None = None):
        super().__init__(label=name)
        self.original_type = original_type
        self.qualifier = qualifier

        if isinstance(original_type, SimTypeRef):
            raise TypeError("SimTypeRef cannot reference another SimTypeRef")

    @property
    def name(self) -> str | None:
        return self.label

    def set_size(self, v: int):
        self._size = v

    def __repr__(self):
        if self.label:
            return self.label
        prefix = "struct " if self.original_type is SimStruct else ""
        return f"{prefix}{self.name}"

    def c_repr(self, name=None, full=0, memo=None, indent=0, name_parens: bool = True) -> str:  # pylint: disable=unused-argument
        prefix = "unknown"
        if self.original_type is SimStruct:
            prefix = "struct "
        if name is None:
            name = ""
        label = self.label
        if self.qualifier:
            label = f"{' '.join(sorted(self.qualifier))} {label}"
        return f"{prefix}{label} {name}"

    def _init_str(self) -> str:
        original_type_name = self.original_type.__name__.split(".")[-1]
        return f'SimTypeRef("{self.name}", {original_type_name})'

    def to_json(self, fields: Iterable[str] | None = None, memo: dict[str, SimTypeRef] | None = None) -> dict[str, Any]:
        d = {"_t": self._ident, "name": self.name, "ot": self.original_type._ident}
        if fields is not None:
            d = {k: d[k] for k in fields}
        if "q" in d and not d["q"]:
            d.pop("q")
        return d

    @staticmethod
    def from_json(
        d: dict[str, Any], type_collection: SimTypeCollection | None = None, memo: set[str] | None = None
    ) -> SimTypeRef:
        if "ot" not in d:
            raise ValueError("Missing original type for SimTypeRef")
        original_type = IDENT_TO_CLS.get(d["ot"])
        if original_type is None:
            raise ValueError(f"Unknown original type {d['ot']} for SimTypeRef")
        qualifier = d.get("q")
        return SimTypeRef(d["name"], original_type, qualifier=qualifier)


IDENT_TO_CLS: dict[str, type[SimType]] = {}
_queue = [SimType]
while _queue:
    _cls = _queue.pop()
    assert _cls._ident not in IDENT_TO_CLS
    IDENT_TO_CLS[_cls._ident] = _cls
    _queue.extend(_cls.__subclasses__())


ALL_TYPES: dict[str, SimType] = {}
BASIC_TYPES: dict[str, SimType] = {
    "char": SimTypeChar(),
    "signed char": SimTypeChar(),
    "unsigned char": SimTypeChar(signed=False),
    "short": SimTypeShort(True),
    "signed short": SimTypeShort(True),
    "unsigned short": SimTypeShort(False),
    "short int": SimTypeShort(True),
    "signed short int": SimTypeShort(True),
    "unsigned short int": SimTypeShort(False),
    "int": SimTypeInt(True),
    "signed": SimTypeInt(True),
    "unsigned": SimTypeInt(False),
    "signed int": SimTypeInt(True),
    "unsigned int": SimTypeInt(False),
    "long": SimTypeLong(True),
    "signed long": SimTypeLong(True),
    "long signed": SimTypeLong(True),
    "unsigned long": SimTypeLong(False),
    "long int": SimTypeLong(True),
    "signed long int": SimTypeLong(True),
    "unsigned long int": SimTypeLong(False),
    "long unsigned int": SimTypeLong(False),
    "long long": SimTypeLongLong(True),
    "signed long long": SimTypeLongLong(True),
    "unsigned long long": SimTypeLongLong(False),
    "long long int": SimTypeLongLong(True),
    "signed long long int": SimTypeLongLong(True),
    "unsigned long long int": SimTypeLongLong(False),
    "__int32": SimTypeInt(True),
    "__int64": SimTypeLongLong(True),
    "__int128": SimTypeNum(128, True),
    "unsigned __int128": SimTypeNum(128, False),
    "__int256": SimTypeNum(256, True),
    "unsigned __int256": SimTypeNum(256, False),
    "bool": SimTypeBool(),
    "_Bool": SimTypeBool(),
    "float": SimTypeFloat(),
    "double": SimTypeDouble(),
    "long double": SimTypeDouble(),
    "void": SimTypeBottom(label="void"),
}
ALL_TYPES.update(BASIC_TYPES)

STDINT_TYPES = {
    "int8_t": SimTypeNum(8, True),
    "uint8_t": SimTypeNum(8, False),
    "byte": SimTypeNum(8, False),
    "int16_t": SimTypeNum(16, True),
    "uint16_t": SimTypeNum(16, False),
    "word": SimTypeNum(16, False),
    "int32_t": SimTypeNum(32, True),
    "uint32_t": SimTypeNum(32, False),
    "dword": SimTypeNum(32, False),
    "int64_t": SimTypeNum(64, True),
    "uint64_t": SimTypeNum(64, False),
    "qword": SimTypeNum(64, False),
    "ptrdiff_t": SimTypeLong(True),
    "size_t": SimTypeLength(False),
    "ssize_t": SimTypeLength(True),
    "ssize": SimTypeLength(False),
    "uintptr_t": SimTypeLong(False),
    # wide-char types
    "wchar_t": SimTypeShort(True, label="wchar_t"),
    "wint_t": SimTypeInt(True, label="wint_t"),
    "wctype_t": SimTypeInt(True, label="wctype_t"),
}
ALL_TYPES.update(STDINT_TYPES)

# Most glibc internal basic types are defined in the following two files:
# https://github.com/bminor/glibc/blob/master/bits/typesizes.h
# https://github.com/bminor/glibc/blob/master/posix/bits/types.h
# Anything that is defined in a different file should probably have a permalink

GLIBC_INTERNAL_BASIC_TYPES = {
    "__off_t": ALL_TYPES["long int"],
    "__off64_t": ALL_TYPES["long long int"],
    "__pid_t": ALL_TYPES["int"],
    "__ino_t": ALL_TYPES["unsigned long int"],
    "__ino64_t": ALL_TYPES["unsigned long long int"],
    "__mode_t": ALL_TYPES["unsigned int"],
    "__dev_t": ALL_TYPES["uint64_t"],
    "__nlink_t": ALL_TYPES["unsigned int"],
    "__uid_t": ALL_TYPES["unsigned int"],
    "__gid_t": ALL_TYPES["unsigned int"],
    "__time_t": ALL_TYPES["long int"],
    # https://github.com/bminor/glibc/blob/a01a13601c95f5d111d25557656d09fe661cfc89/sysdeps/unix/sysv/linux/x86/bits/siginfo-arch.h#L12
    "__clock_t": ALL_TYPES["uint32_t"],
    "__suseconds_t": ALL_TYPES["int64_t"],
    "socklen_t": ALL_TYPES["uint32_t"],
    "mode_t": ALL_TYPES["unsigned int"],
}
ALL_TYPES.update(GLIBC_INTERNAL_BASIC_TYPES)

GLIBC_EXTERNAL_BASIC_TYPES = {
    "off_t": ALL_TYPES["__off_t"],
    "off64_t": ALL_TYPES["__off64_t"],
    "pid_t": ALL_TYPES["__pid_t"],
    # https://www.gnu.org/software/libc/manual/html_node/Attribute-Meanings.html
    # This is "no narrower than unsigned int" but may be wider...
    # TODO: This should be defined based on the architecture
    "ino_t": ALL_TYPES["__ino_t"],
    "ino64_t": ALL_TYPES["__ino64_t"],
    # https://github.com/bminor/glibc/blob/a01a13601c95f5d111d25557656d09fe661cfc89/bits/sockaddr.h#L28
    "sa_family_t": ALL_TYPES["unsigned short int"],
    # https://github.com/bminor/glibc/blob/a01a13601c95f5d111d25557656d09fe661cfc89/inet/netinet/in.h#L123
    "in_port_t": ALL_TYPES["uint16_t"],
    # https://github.com/bminor/glibc/blob/a01a13601c95f5d111d25557656d09fe661cfc89/bits/termios.h#L102
    "tcflag_t": ALL_TYPES["unsigned long int"],
    # https://github.com/bminor/glibc/blob/a01a13601c95f5d111d25557656d09fe661cfc89/bits/termios.h#L105
    "cc_t": ALL_TYPES["unsigned char"],
    # https://github.com/bminor/glibc/blob/a01a13601c95f5d111d25557656d09fe661cfc89/bits/termios.h#L108
    "speed_t": ALL_TYPES["long int"],
    "clock_t": ALL_TYPES["__clock_t"],
    "rlim_t": ALL_TYPES["unsigned long int"],
    "rlim64_t": ALL_TYPES["uint64_t"],
    # https://github.com/bminor/glibc/blob/a01a13601c95f5d111d25557656d09fe661cfc89/bits/types/error_t.h#L22
    "error_t": ALL_TYPES["int"],
    "sigset_t": ALL_TYPES["int"],
    "sem_t": ALL_TYPES["int"],
    "sighandler_t": SimTypePointer(ALL_TYPES["void"], label="sighandler_t"),
    "comparison_fn_t": SimTypePointer(ALL_TYPES["void"], label="comparison_fn_t"),
    "DIR": SimStruct({}, name="DIR"),
    "glob_t": SimStruct({}, name="glob_t"),
    "glob64_t": SimStruct({}, name="glob64_t"),
    "__free_fn_t": SimTypePointer(ALL_TYPES["void"], label="__free_fn_t"),
    "__action_fn_t": SimTypePointer(ALL_TYPES["void"], label="__action_fn_t"),
    "__ftw_func_t": SimTypePointer(ALL_TYPES["void"], label="__ftw_func_t"),
    "mbstate_t": SimStruct({}, name="mbstate_t"),
    "fpos_t": SimStruct({}, name="fpos_t"),
    "fpos64_t": SimStruct({}, name="fpos64_t"),
    "regex_t": SimStruct({}, name="regex_t"),
    "fd_set": SimStruct({}, name="fd_set"),
    "dev_t": ALL_TYPES["int"],
}
ALL_TYPES.update(GLIBC_EXTERNAL_BASIC_TYPES)

# TODO: switch to stl types declared in types_stl
CXX_TYPES = {
    "string": SimTypeString(),
    "wstring": SimTypeWString(),
    "std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>": SimTypeString(),
    "basic_string": SimTypeString(),
    "CharT": SimTypeChar(),
}
ALL_TYPES.update(CXX_TYPES)


# Note about structs with self/next pointers -- they will be defined as memberless
# name-only structs the same way they would be in C as a forward declaration

# This dictionary is defined in two steps to allow structs that are members of other
# structs to be defined first
GLIBC_INTERNAL_TYPES = {
    "sigval": SimUnion(
        {
            "sival_int": ALL_TYPES["int"],
            "sival_ptr": SimTypePointer(ALL_TYPES["void"], label="void *"),
        },
        name="sigval",
    ),
    "__mbstate_t": SimStruct(
        {
            "__count": ALL_TYPES["int"],
            "__value": SimUnion(
                {
                    "__wch": ALL_TYPES["unsigned int"],
                    "__wchb": SimTypeArray(ALL_TYPES["char"], length=4),
                }
            ),
        },
        name="__mbstate_t",
    ),
    "_IO_codecvt": SimStruct(
        {
            "__cd_in": SimStruct({}, name="_IO_iconv_t"),
            "__cd_out": SimStruct({}, name="_IO_iconv_t"),
        },
        name="_IO_codecvt",
    ),
    "argp_option": SimStruct(
        {
            "name": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "key": ALL_TYPES["int"],
            "arg": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "flags": ALL_TYPES["int"],
            "doc": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "group": ALL_TYPES["int"],
        },
        name="argp_option",
    ),
    "argp_child": SimStruct(
        {
            "argp": SimStruct({}, name="argp"),
            "flags": ALL_TYPES["int"],
            "header": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "group": ALL_TYPES["int"],
        },
        name="argp_child",
    ),
    "argp_parser_t": SimTypeFunction(
        (
            ALL_TYPES["int"],
            SimTypePointer(ALL_TYPES["char"], label="char *"),
            SimTypePointer(SimStruct({}, name="argp_state")),
        ),
        ALL_TYPES["error_t"],
        arg_names=("__key", "__arg", "__state"),
    ),
}


GLIBC_INTERNAL_TYPES.update(
    {
        "_obstack_chunk": SimStruct(
            {
                "limit": SimTypePointer(ALL_TYPES["char"], label="char *"),
                "prev": SimTypePointer(SimStruct({}, name="_obstack_chunk", pack=False, align=None)),
                "contents": SimTypeArray(ALL_TYPES["char"], length=4, label="char"),
            },
            name="_obstack_chunk",
        ),
        # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/misc/search.h#L69
        "_ENTRY": SimStruct(
            {
                "key": SimTypePointer(ALL_TYPES["char"], label="char *"),
                "data": SimTypePointer(ALL_TYPES["void"], label="void *"),
            },
            name="_ENTRY",
        ),
        # https://man7.org/linux/man-pages/man7/sigevent.7.html
        "sigevent": SimStruct(
            {
                "sigev_notify": ALL_TYPES["int"],
                "sigev_signo": ALL_TYPES["int"],
                "sigev_value": GLIBC_INTERNAL_TYPES["sigval"],
                "sigev_notify_function": SimTypeFunction(
                    (GLIBC_INTERNAL_TYPES["sigval"],),
                    SimTypePointer(ALL_TYPES["void"], label="void *"),
                ),
                "sigev_notify_attributes": SimTypePointer(ALL_TYPES["void"], label="void *"),
                "sigev_notify_thread_id": ALL_TYPES["pid_t"],
            },
            name="sigevent",
        ),
        "in_addr": SimStruct({"s_addr": ALL_TYPES["uint32_t"]}, name="in_addr"),
        "_IO_marker": SimStruct(
            {
                "_next": SimTypePointer(SimStruct({}, name="_IO_marker"), label="struct _IO_marker *"),
                "_sbuf": SimTypePointer(SimStruct({}, name="FILE"), label="FILE *"),
                "_pos": ALL_TYPES["int"],
            },
            name="_IO_marker",
        ),
        "_IO_iconv_t": SimStruct(
            {
                # TODO: Define __gconv structs
                "step": SimTypePointer(SimStruct({}, name="__gconv_step"), label="struct __gconv_step *"),
                "step_data": SimStruct({}, name="__gconv_step_data"),
            },
            name="_IO_iconv_t",
        ),
        "_IO_codecvt": GLIBC_INTERNAL_TYPES["_IO_codecvt"],
        "_IO_lock_t": SimStruct({}, name="pthread_mutex_t"),
        "__mbstate_t": GLIBC_INTERNAL_TYPES["__mbstate_t"],
        "_IO_wide_data": SimStruct(
            {
                "_IO_read_ptr": SimTypePointer(ALL_TYPES["wchar_t"], label="wchar_t *"),
                "_IO_read_end": SimTypePointer(ALL_TYPES["wchar_t"], label="wchar_t *"),
                "_IO_read_base": SimTypePointer(ALL_TYPES["wchar_t"], label="wchar_t *"),
                "_IO_write_base": SimTypePointer(ALL_TYPES["wchar_t"], label="wchar_t *"),
                "_IO_write_ptr": SimTypePointer(ALL_TYPES["wchar_t"], label="wchar_t *"),
                "_IO_write_end": SimTypePointer(ALL_TYPES["wchar_t"], label="wchar_t *"),
                "_IO_buf_base": SimTypePointer(ALL_TYPES["wchar_t"], label="wchar_t *"),
                "_IO_buf_end": SimTypePointer(ALL_TYPES["wchar_t"], label="wchar_t *"),
                "_IO_save_base": SimTypePointer(ALL_TYPES["wchar_t"], label="wchar_t *"),
                "_IO_backup_base": SimTypePointer(ALL_TYPES["wchar_t"], label="wchar_t *"),
                "_IO_save_end": SimTypePointer(ALL_TYPES["wchar_t"], label="wchar_t *"),
                "_IO_state": GLIBC_INTERNAL_TYPES["__mbstate_t"],
                "_IO_last_state": GLIBC_INTERNAL_TYPES["__mbstate_t"],
                "_codecvt": GLIBC_INTERNAL_TYPES["_IO_codecvt"],
                "_shortbuf": SimTypeArray(ALL_TYPES["wchar_t"], length=1, label="wchar_t[1]"),
                # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/libio/libioP.h#L293
                "_wide_vtable": SimStruct({}, name="_IO_jump_t"),
            },
            name="_IO_wide_data",
        ),
        "argp": SimStruct(
            {
                "options": SimTypePointer(GLIBC_INTERNAL_TYPES["argp_option"], label="struct argp_option *"),
                "parser": GLIBC_INTERNAL_TYPES["argp_parser_t"],
                "args_doc": SimTypePointer(ALL_TYPES["char"], label="char *"),
                "doc": SimTypePointer(ALL_TYPES["char"], label="char *"),
                "children": SimTypePointer(GLIBC_INTERNAL_TYPES["argp_child"], label="struct argp_child *"),
                "help_filter": SimTypeFunction(
                    (
                        ALL_TYPES["int"],
                        SimTypePointer(ALL_TYPES["char"], label="char *"),
                        SimTypePointer(ALL_TYPES["void"], label="void *"),
                    ),
                    SimTypePointer(ALL_TYPES["char"], label="char *"),
                    arg_names=("__key", "__text", "__input"),
                ),
                "argp_domain": SimTypePointer(ALL_TYPES["char"], label="char *"),
            },
            name="argp",
        ),
        "timeval": SimStruct(
            {
                # TODO: This should be architecture dependent
                "tv_sec": ALL_TYPES["__time_t"],
                "tv_usec": ALL_TYPES["__suseconds_t"],
            },
            name="timeval",
        ),
        # https://github.com/bminor/glibc/blob/a01a13601c95f5d111d25557656d09fe661cfc89/time/bits/types/struct_timespec.h#L11
        "timespec": SimStruct(
            {
                # TODO: This should be architecture dependent
                "tv_sec": ALL_TYPES["__time_t"],
                "tv_nsec": ALL_TYPES["long int"],
                # TODO: This should be architecture dependent (byte order)
                "_pad0": ALL_TYPES["uint32_t"],
            },
            name="timeval",
        ),
        # https://github.com/bminor/glibc/blob/a01a13601c95f5d111d25557656d09fe661cfc89/bits/utmp.h#L50
        "exit_status": SimStruct(
            {
                "e_termination": ALL_TYPES["short int"],
                "e_exit": ALL_TYPES["short int"],
            },
            name="exit_status",
        ),
    }
)
ALL_TYPES.update(GLIBC_INTERNAL_TYPES)

GLIBC_TYPES = {
    # DO NOT use the glibc manual to define these structs! It is not accurate and does
    # not contain all fields or even the fields in the correct order!. Instead, you
    # need to use the glibc source and actually find the struct. In most cases,
    # a link to the struct is provided.
    # ABI-defined, for x86_64 it can be found here in sec 3.34:
    # https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf
    # TODO: This should be architecture dependent
    "va_list": SimTypeArray(
        SimStruct(
            {
                "gp_offset": ALL_TYPES["unsigned int"],
                "fp_offset": ALL_TYPES["unsigned int"],
                "overflow_arg_area": SimTypePointer(ALL_TYPES["void"], label="void *"),
                "reg_save_area": SimTypePointer(ALL_TYPES["void"], label="void *"),
            },
            name="va_list",
        ),
        length=1,
        label="va_list[1]",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/malloc/malloc.h#L82
    "mallinfo": SimStruct(
        {
            "arena": ALL_TYPES["int"],
            "ordblks": ALL_TYPES["int"],
            "smblks": ALL_TYPES["int"],
            "hblks": ALL_TYPES["int"],
            "hblkhd": ALL_TYPES["int"],
            "usmblks": ALL_TYPES["int"],
            "fsmblks": ALL_TYPES["int"],
            "uordblks": ALL_TYPES["int"],
            "fordblks": ALL_TYPES["int"],
            "keepcost": ALL_TYPES["int"],
        },
        name="mallinfo",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/malloc/malloc.h#L99
    "mallinfo2": SimStruct(
        {
            "arena": ALL_TYPES["size_t"],
            "ordblks": ALL_TYPES["size_t"],
            "smblks": ALL_TYPES["size_t"],
            "hblks": ALL_TYPES["size_t"],
            "hblkhd": ALL_TYPES["size_t"],
            "usmblks": ALL_TYPES["size_t"],
            "fsmblks": ALL_TYPES["size_t"],
            "uordblks": ALL_TYPES["size_t"],
            "fordblks": ALL_TYPES["size_t"],
            "keepcost": ALL_TYPES["size_t"],
        },
        name="mallinfo2",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/malloc/obstack.h#L153
    "obstack": SimStruct(
        {
            "chunk_size": SimTypeLong(signed=True, label="long"),
            "chunk": GLIBC_INTERNAL_TYPES["_obstack_chunk"],
            "object_base": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "next_free": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "chunk_limit": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "temp": SimUnion(
                {
                    "tempint": ALL_TYPES["ptrdiff_t"],
                    "tempptr": SimTypePointer(ALL_TYPES["void"], label="void *"),
                }
            ),
            "alignment_mask": ALL_TYPES["int"],
            "chunkfun": SimTypeFunction(
                (SimTypePointer(ALL_TYPES["void"], label="void *"), ALL_TYPES["long"]),
                SimTypePointer(ALL_TYPES["_obstack_chunk"], label="struct _obstack_chunk *"),
            ),
            "freefun": SimTypeFunction(
                (
                    SimTypePointer(ALL_TYPES["void"], label="void *"),
                    SimTypePointer(ALL_TYPES["_obstack_chunk"], label="_obstack_chunk *"),
                ),
                ALL_TYPES["void"],
            ),
            "extra_arg": SimTypePointer(ALL_TYPES["void"], label="void *"),
            "use_extra_arg": SimTypeNumOffset(1, signed=False, label="unsigned"),
            "maybe_extra_object": SimTypeNumOffset(1, signed=False, label="unsigned"),
            "alloc_failed": SimTypeNumOffset(1, signed=False, label="unsigned"),
        },
        name="obstack",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/locale/locale.h#L51
    "lconv": SimStruct(
        {
            "decimal_point": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "thousands_sep": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "grouping": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "int_curr_symbol": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "currency_symbol": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "mon_decimal_point": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "mon_thousands_sep": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "mon_grouping": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "positive_sign": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "negative_sign": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "int_frac_digits": ALL_TYPES["char"],
            "frac_digits": ALL_TYPES["char"],
            "p_cs_precedes": ALL_TYPES["char"],
            "p_sep_by_space": ALL_TYPES["char"],
            "n_cs_precedes": ALL_TYPES["char"],
            "n_sep_by_space": ALL_TYPES["char"],
            "p_sign_posn": ALL_TYPES["char"],
            "n_sign_posn": ALL_TYPES["char"],
            "int_p_cs_precedes": ALL_TYPES["char"],
            "int_p_sep_by_space": ALL_TYPES["char"],
            "int_n_cs_precedes": ALL_TYPES["char"],
            "int_n_sep_by_space": ALL_TYPES["char"],
            "int_p_sign_posn": ALL_TYPES["char"],
            "int_n_sign_posn": ALL_TYPES["char"],
        },
        name="lconv",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/misc/search.h#L97
    "hsearch_data": SimStruct(
        {
            "table": SimTypePointer(ALL_TYPES["_ENTRY"], label="struct _ENTRY *"),
            "size": ALL_TYPES["unsigned int"],
            "filled": ALL_TYPES["unsigned int"],
        },
        name="hsearch_data",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/libio/bits/types/struct_FILE.h#L49
    "FILE_t": SimStruct(
        {
            "_flags": ALL_TYPES["int"],
            "_IO_read_ptr": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "_IO_read_end": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "_IO_read_base": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "_IO_write_base": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "_IO_write_ptr": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "_IO_write_end": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "_IO_buf_base": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "_IO_buf_end": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "_IO_save_base": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "_IO_backup_base": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "_IO_save_end": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "_markers": SimTypePointer(ALL_TYPES["_IO_marker"]),
            "_chain": SimTypePointer(SimStruct({}, name="_IO_FILE"), label="struct _IO_FILE *"),
            "_fileno": ALL_TYPES["int"],
            "_flags2": ALL_TYPES["int"],
            "_old_offset": ALL_TYPES["__off_t"],
            "_cur_column": ALL_TYPES["unsigned short"],
            "_vtable_offset": ALL_TYPES["signed char"],
            "_shortbuf": SimTypeArray(ALL_TYPES["char"], length=1, label="char[1]"),
            "_lock": SimTypePointer(ALL_TYPES["_IO_lock_t"]),
            "_offset": ALL_TYPES["__off64_t"],
            "_codecvt": SimTypePointer(ALL_TYPES["_IO_codecvt"], label="struct _IO_codecvt *"),
            "_wide_data": SimTypePointer(ALL_TYPES["_IO_wide_data"], label="struct _IO_wide_data *"),
            "_freeres_list": SimTypePointer(SimStruct({}, name="_IO_FILE"), label="struct _IO_FILE *"),
            "__pad5": ALL_TYPES["size_t"],
            "_mode": ALL_TYPES["int"],
            "_unused2": SimTypeArray(
                ALL_TYPES["char"],
                length=20,
                label="char[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)]",
            ),
        },
        name="FILE_t",
    ),
    "FILE": SimStruct({}, name="FILE"),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/stdio-common/printf.h#L34
    "printf_info": SimStruct(
        {
            "prec": ALL_TYPES["int"],
            "width": ALL_TYPES["int"],
            "spec": ALL_TYPES["wchar_t"],
            "is_long_double": SimTypeNumOffset(1, signed=False, label="unsigned int"),
            "is_short": SimTypeNumOffset(1, signed=False, label="unsigned int"),
            "is_long": SimTypeNumOffset(1, signed=False, label="unsigned int"),
            "alt": SimTypeNumOffset(1, signed=False, label="unsigned int"),
            "space": SimTypeNumOffset(1, signed=False, label="unsigned int"),
            "left": SimTypeNumOffset(1, signed=False, label="unsigned int"),
            "showsign": SimTypeNumOffset(1, signed=False, label="unsigned int"),
            "group": SimTypeNumOffset(1, signed=False, label="unsigned int"),
            "extra": SimTypeNumOffset(1, signed=False, label="unsigned int"),
            "is_char": SimTypeNumOffset(1, signed=False, label="unsigned int"),
            "wide": SimTypeNumOffset(1, signed=False, label="unsigned int"),
            "i18n": SimTypeNumOffset(1, signed=False, label="unsigned int"),
            "is_binary128": SimTypeNumOffset(1, signed=False, label="unsigned int"),
            "__pad": SimTypeNumOffset(3, signed=False, label="unsigned int"),
            "user": ALL_TYPES["unsigned short int"],
            "pad": ALL_TYPES["wchar_t"],
        },
        name="printf_info",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/rt/aio.h#L34
    "aiocb": SimStruct(
        {
            "aio_filedes": ALL_TYPES["int"],
            "aio_lio_opcode": ALL_TYPES["int"],
            "aio_reqprio": ALL_TYPES["int"],
            "aio_buf": SimTypePointer(ALL_TYPES["void"], label="void *"),
            "aio_nbytes": ALL_TYPES["size_t"],
            "aio_sigevent": ALL_TYPES["sigevent"],
            "__next_prio": SimTypePointer(SimStruct({}, name="aiocb"), label="struct aiocb *"),
            "__abs_prio": ALL_TYPES["int"],
            "__policy": ALL_TYPES["int"],
            "__error_code": ALL_TYPES["int"],
            "__return_value": ALL_TYPES["ssize_t"],
            # TODO: This should be architecture dependent
            "aio_offset": ALL_TYPES["off_t"],
            "__glibc_reserved": SimTypeArray(ALL_TYPES["char"], length=32, label="char[32]"),
        },
        name="aiocb",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/rt/aio.h#L62
    "aiocb64": SimStruct(
        {
            "aio_filedes": ALL_TYPES["int"],
            "aio_lio_opcode": ALL_TYPES["int"],
            "aio_reqprio": ALL_TYPES["int"],
            "aio_buf": SimTypePointer(ALL_TYPES["void"], label="void *"),
            "aio_nbytes": ALL_TYPES["size_t"],
            "aio_sigevent": ALL_TYPES["sigevent"],
            "__next_prio": SimTypePointer(SimStruct({}, name="aiocb"), label="struct aiocb *"),
            "__abs_prio": ALL_TYPES["int"],
            "__policy": ALL_TYPES["int"],
            "__error_code": ALL_TYPES["int"],
            "__return_value": ALL_TYPES["ssize_t"],
            "aio_offset": ALL_TYPES["off64_t"],
            "__glibc_reserved": SimTypeArray(ALL_TYPES["char"], length=32, label="char[32]"),
        },
        name="aiocb64",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/rt/aio.h#L86
    "aioinit": SimStruct(
        {
            "aio_threads": ALL_TYPES["int"],
            "aio_num": ALL_TYPES["int"],
            "aio_locks": ALL_TYPES["int"],
            "aio_debug": ALL_TYPES["int"],
            "aio_numusers": ALL_TYPES["int"],
            "aio_idle_time": ALL_TYPES["int"],
            "aio_reserved": ALL_TYPES["int"],
        },
        name="aioinit",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/bits/dirent.h#L23
    "dirent": SimStruct(
        {
            "d_ino": ALL_TYPES["ino_t"],
            "d_reclen": ALL_TYPES["unsigned short int"],
            "d_type": ALL_TYPES["unsigned char"],
            "d_namelen": ALL_TYPES["unsigned char"],
            "d_name": SimTypeArray(ALL_TYPES["char"], length=1, label="char[1]"),
        },
        name="dirent",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/bits/dirent.h#L39
    "dirent64": SimStruct(
        {
            "d_ino": ALL_TYPES["ino64_t"],
            "d_reclen": ALL_TYPES["unsigned short int"],
            "d_type": ALL_TYPES["unsigned char"],
            "d_namelen": ALL_TYPES["unsigned char"],
            "d_name": SimTypeArray(ALL_TYPES["char"], length=1, label="char[1]"),
        },
        name="dirent64",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/bits/stat.h#L31
    "struct stat": SimStruct(
        {
            "st_mode": ALL_TYPES["__mode_t"],
            # TODO: This should be architecture dependent
            "st_ino": ALL_TYPES["__ino_t"],
            "st_dev": ALL_TYPES["__dev_t"],
            "st_nlink": ALL_TYPES["__nlink_t"],
            "st_uid": ALL_TYPES["__uid_t"],
            "st_gid": ALL_TYPES["__gid_t"],
            # TODO: This should be architecture dependent
            "st_size": ALL_TYPES["__off_t"],
            "st_atime": ALL_TYPES["__time_t"],
            "st_mtime": ALL_TYPES["__time_t"],
            "st_ctime": ALL_TYPES["__time_t"],
        },
        name="stat",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/bits/stat.h#L86
    "struct stat64": SimStruct(
        {
            "st_mode": ALL_TYPES["__mode_t"],
            # TODO: This should be architecture dependent
            "st_ino": ALL_TYPES["__ino64_t"],
            "st_dev": ALL_TYPES["__dev_t"],
            "st_nlink": ALL_TYPES["__nlink_t"],
            "st_uid": ALL_TYPES["__uid_t"],
            "st_gid": ALL_TYPES["__gid_t"],
            # TODO: This should be architecture dependent
            "st_size": ALL_TYPES["__off64_t"],
            "st_atime": ALL_TYPES["__time_t"],
            "st_mtime": ALL_TYPES["__time_t"],
            "st_ctime": ALL_TYPES["__time_t"],
        },
        name="stat64",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/io/utime.h#L36
    "utimbuf": SimStruct(
        {
            # TODO: This should be architecture dependent
            "actime": ALL_TYPES["__time_t"],
            "modtime": ALL_TYPES["__time_t"],
        },
        name="utimbuf",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/bits/socket.h#L152
    "sockaddr": SimStruct(
        {
            "sin_family": ALL_TYPES["sa_family_t"],
            "sa_data": SimTypeArray(ALL_TYPES["char"], length=14, label="char[14]"),
        },
        name="sockaddr",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/inet/netinet/in.h#L245
    "sockaddr_in": SimStruct(
        {
            "sin_family": ALL_TYPES["sa_family_t"],
            "sin_port": ALL_TYPES["in_port_t"],
            "sin_addr": ALL_TYPES["in_addr"],
            "sin_zero": SimTypeArray(
                ALL_TYPES["unsigned char"],
                length=8,
                label=(
                    "unsigned char[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - "
                    "sizeof (in_port_t) - sizeof (struct in_addr)]"
                ),
            ),
        },
        name="sockaddr_in",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/sysdeps/gnu/net/if.h#L33
    "if_nameindex": SimStruct(
        {
            "if_index": ALL_TYPES["unsigned int"],
            "if_name": SimTypePointer(ALL_TYPES["char"], label="char *"),
        },
        name="if_nameindex",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/resolv/netdb.h#L98
    "hostent": SimStruct(
        {
            "h_name": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "h_aliases": SimTypePointer(SimTypePointer(ALL_TYPES["char"], label="char *"), label="char **"),
            "h_addrtype": ALL_TYPES["int"],
            "h_length": ALL_TYPES["int"],
            "h_addr_list": SimTypePointer(SimTypePointer(ALL_TYPES["char"], label="char *"), label="char **"),
        },
        name="hostent",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/resolv/netdb.h#L255
    "servent": SimStruct(
        {
            "s_name": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "s_aliases": SimTypePointer(SimTypePointer(ALL_TYPES["char"], label="char *"), label="char **"),
            "s_port": ALL_TYPES["int"],
            "s_proto": SimTypePointer(ALL_TYPES["char"], label="char *"),
        },
        name="servent",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/resolv/netdb.h#L324
    "protoent": SimStruct(
        {
            "p_name": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "p_aliases": SimTypePointer(SimTypePointer(ALL_TYPES["char"], label="char *"), label="char **"),
            "p_proto": ALL_TYPES["int"],
        },
        name="protoent",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/bits/netdb.h#L26
    "netent": SimStruct(
        {
            "n_name": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "n_aliases": SimTypePointer(SimTypePointer(ALL_TYPES["char"], label="char *"), label="char **"),
            "n_addrtype": ALL_TYPES["int"],
            "n_net": ALL_TYPES["uint32_t"],
        },
        name="netent",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/bits/termios.h#L111
    "termios": SimStruct(
        {
            "c_iflag": ALL_TYPES["tcflag_t"],
            "c_oflag": ALL_TYPES["tcflag_t"],
            "c_cflag": ALL_TYPES["tcflag_t"],
            "c_lflag": ALL_TYPES["tcflag_t"],
            "c_cc": SimTypeArray(ALL_TYPES["cc_t"], length=20, label="cc_t[20]"),
            "__ispeed": ALL_TYPES["speed_t"],
            "__ospeed": ALL_TYPES["speed_t"],
        },
        name="termios",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/bits/ioctl-types.h#L56
    "sgttyb": SimStruct(
        {
            "sg_ispeed": ALL_TYPES["char"],
            "sg_ospeed": ALL_TYPES["char"],
            "sg_erase": ALL_TYPES["char"],
            "sg_kill": ALL_TYPES["char"],
            "sg_flags": ALL_TYPES["short int"],
        },
        name="sgttyb",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/bits/ioctl-types.h#L70
    "winsize": SimStruct(
        {
            "ws_row": ALL_TYPES["unsigned short int"],
            "ws_col": ALL_TYPES["unsigned short int"],
            "ws_xpixel": ALL_TYPES["unsigned short int"],
            "ws_ypixel": ALL_TYPES["unsigned short int"],
        },
        name="winsize",
    ),
    # This type is legitimately opaque
    "random_data": SimStruct({}),
    # This type is also legitimately opaque
    "drand48_data": SimStruct({}),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/posix/sys/times.h#L32
    "tms": SimStruct(
        {
            "tms_utime": ALL_TYPES["clock_t"],
            "tms_stime": ALL_TYPES["clock_t"],
            "tms_cutime": ALL_TYPES["clock_t"],
            "tms_cstime": ALL_TYPES["clock_t"],
        },
        name="tms",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/time/sys/time.h#L52
    "timezone": SimStruct(
        {
            "tz_minuteswest": ALL_TYPES["int"],
            "tz_dsttime": ALL_TYPES["int"],
        },
        name="timezone",
    ),
    "timeval": ALL_TYPES["timeval"],
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/sysdeps/unix/sysv/linux/bits/timex.h#L26
    "timex": SimStruct(
        # TODO: This should be architecture dependent
        {
            "modes": ALL_TYPES["unsigned int"],
            "_pad0": ALL_TYPES["uint32_t"],
            "offset": ALL_TYPES["long long"],
            "freq": ALL_TYPES["long long"],
            "maxerror": ALL_TYPES["long long"],
            "esterror": ALL_TYPES["long long"],
            "status": ALL_TYPES["int"],
            "_pad1": ALL_TYPES["uint32_t"],
            "constant": ALL_TYPES["long long"],
            "precision": ALL_TYPES["long long"],
            "tolerance": ALL_TYPES["long long"],
            "time": ALL_TYPES["timeval"],
            "tick": ALL_TYPES["long long"],
            "ppsfreq": ALL_TYPES["long long"],
            "jitter": ALL_TYPES["long long"],
            "shift": ALL_TYPES["int"],
            "_pad2": ALL_TYPES["uint32_t"],
            "stabil": ALL_TYPES["long long"],
            "jitcnt": ALL_TYPES["long long"],
            "calcnt": ALL_TYPES["long long"],
            "errcnt": ALL_TYPES["long long"],
            "stbcnt": ALL_TYPES["long long"],
            "tai": ALL_TYPES["int"],
            "_pad3": SimTypeArray(ALL_TYPES["uint32_t"], length=11, label="int :32[11]"),
        },
        name="timex",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/time/bits/types/struct_tm.h#L7
    "tm": SimStruct(
        {
            "tm_sec": ALL_TYPES["int"],
            "tm_min": ALL_TYPES["int"],
            "tm_hour": ALL_TYPES["int"],
            "tm_mday": ALL_TYPES["int"],
            "tm_mon": ALL_TYPES["int"],
            "tm_year": ALL_TYPES["int"],
            "tm_wday": ALL_TYPES["int"],
            "tm_yday": ALL_TYPES["int"],
            "tm_isdst": ALL_TYPES["int"],
            "tm_gmtoff": ALL_TYPES["long int"],
            "tm_zone": SimTypePointer(ALL_TYPES["char"], label="char *"),
        },
        name="tm",
    ),
    # https://github.com/bminor/glibc/blob/a01a13601c95f5d111d25557656d09fe661cfc89/sysdeps/unix/sysv/linux/sys/timex.h#L30
    "ntptimeval": SimStruct(
        {
            "time": ALL_TYPES["timeval"],
            "maxerror": ALL_TYPES["long int"],
            "esterror": ALL_TYPES["long int"],
            "tai": ALL_TYPES["long int"],
            "__glibc_reserved1": ALL_TYPES["long int"],
            "__glibc_reserved2": ALL_TYPES["long int"],
            "__glibc_reserved3": ALL_TYPES["long int"],
            "__glibc_reserved4": ALL_TYPES["long int"],
        },
        name="ntptimeval",
    ),
    # https://github.com/bminor/glibc/blob/a01a13601c95f5d111d25557656d09fe661cfc89/misc/bits/types/struct_iovec.h#L26
    "iovec": SimStruct(
        {
            "iov_base": SimTypePointer(ALL_TYPES["void"], label="void *"),
            "iov_len": ALL_TYPES["size_t"],
        }
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/time/sys/time.h#L130
    "itimerval": SimStruct(
        {
            "it_interval": ALL_TYPES["timeval"],
            "it_value": ALL_TYPES["timeval"],
        },
        name="itimerval",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/resource/bits/types/struct_rusage.h#L33
    "rusage": SimStruct(
        {
            "ru_utime": ALL_TYPES["timeval"],
            "ru_stime": ALL_TYPES["timeval"],
            "ru_maxrss": ALL_TYPES["long int"],
            "ru_ixrss": ALL_TYPES["long int"],
            "ru_idrss": ALL_TYPES["long int"],
            "ru_isrss": ALL_TYPES["long int"],
            "ru_minflt": ALL_TYPES["long int"],
            "ru_majflt": ALL_TYPES["long int"],
            "ru_nswap": ALL_TYPES["long int"],
            "ru_inblock": ALL_TYPES["long int"],
            "ru_oublock": ALL_TYPES["long int"],
            "ru_msgsnd": ALL_TYPES["long int"],
            "ru_msgrcv": ALL_TYPES["long int"],
            "ru_nsignals": ALL_TYPES["long int"],
            "ru_nvcsw": ALL_TYPES["long int"],
            "ru_nivcsw": ALL_TYPES["long int"],
        },
        name="rusage",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/resource/vtimes.c#L28
    "vtimes": SimStruct(
        {
            "vm_utime": ALL_TYPES["int"],
            "vm_stime": ALL_TYPES["int"],
            "vm_idsrss": ALL_TYPES["unsigned int"],
            "vm_ixrss": ALL_TYPES["unsigned int"],
            "vm_maxrss": ALL_TYPES["int"],
            "vm_maxflt": ALL_TYPES["int"],
            "vm_minflt": ALL_TYPES["int"],
            "vm_nswap": ALL_TYPES["int"],
            "vm_inblk": ALL_TYPES["int"],
            "vm_outblk": ALL_TYPES["int"],
        },
        name="vtimes",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/sysdeps/unix/sysv/linux/bits/resource.h#L139
    "rlimit": SimStruct(
        {
            "rlim_cur": ALL_TYPES["rlim_t"],
            "rlim_max": ALL_TYPES["rlim_t"],
        },
        name="rlimit",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/sysdeps/unix/sysv/linux/bits/resource.h#L148
    "rlimit64": SimStruct(
        {
            "rlim_cur": ALL_TYPES["rlim64_t"],
            "rlim_max": ALL_TYPES["rlim64_t"],
        },
        name="rlimit64",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/bits/types/struct_sched_param.h#L23
    "sched_param": SimStruct(
        {"sched_priority": ALL_TYPES["int"]},
        name="sched_param",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/signal/bits/types/struct_sigstack.h#L23
    "sigstack": SimStruct(
        {
            "ss_sp": SimTypePointer(ALL_TYPES["void"], label="void *"),
            "ss_onstack": ALL_TYPES["int"],
        },
        name="sigstack",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/posix/bits/getopt_ext.h#L50
    "option": SimStruct(
        {
            "name": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "has_arg": ALL_TYPES["int"],
            "flag": SimTypePointer(ALL_TYPES["int"], label="int *"),
            "val": ALL_TYPES["int"],
        },
        name="option",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/argp/argp.h#L273
    "argp_state": SimStruct(
        {
            "root_argp": ALL_TYPES["argp"],
            "argc": ALL_TYPES["int"],
            "argv": SimTypePointer(SimTypePointer(ALL_TYPES["char"], label="char *"), label="char **"),
            "next": ALL_TYPES["int"],
            "flags": ALL_TYPES["unsigned"],
            "arg_num": ALL_TYPES["unsigned"],
            "quoted": ALL_TYPES["int"],
            "input": SimTypePointer(ALL_TYPES["void"], label="void *"),
            "child_inputs": SimTypePointer(SimTypePointer(ALL_TYPES["void"], label="void *"), label="void **"),
            "hook": SimTypePointer(ALL_TYPES["void"], label="void *"),
            "name": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "err_stream": SimStruct({}, name="FILE"),
            "pstate": SimTypePointer(ALL_TYPES["void"], label="void *"),
        },
        name="argp_state",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/sysvipc/sys/sem.h#L40
    "sembuf": SimStruct(
        {
            "sem_num": ALL_TYPES["unsigned short int"],
            "sem_op": ALL_TYPES["short int"],
            "sem_flg": ALL_TYPES["short int"],
        },
        name="sembuf",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/bits/utmp.h#L58
    "utmp": SimStruct(
        {
            "ut_type": ALL_TYPES["short int"],
            "ut_pid": ALL_TYPES["pid_t"],
            "ut_line": SimTypeArray(ALL_TYPES["char"], length=32, label="char[32]"),
            "ut_id": SimTypeArray(ALL_TYPES["char"], length=4, label="char[32]"),
            "ut_user": SimTypeArray(ALL_TYPES["char"], length=32, label="char[32]"),
            "ut_host": SimTypeArray(ALL_TYPES["char"], length=256, label="char[32]"),
            "ut_exit": ALL_TYPES["exit_status"],
            "ut_session": ALL_TYPES["long int"],
            "ut_tv": ALL_TYPES["timeval"],
            "ut_addr_v6": SimTypeArray(ALL_TYPES["int32_t"], length=4, label="int32_t[4]"),
            "__glibc_reserved": SimTypeArray(ALL_TYPES["char"], length=20, label="char[20]"),
        },
        name="utmp",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/sysdeps/gnu/bits/utmpx.h#L55
    "utmpx": SimStruct(
        {
            "ut_type": ALL_TYPES["short int"],
            "ut_pid": ALL_TYPES["pid_t"],
            "ut_line": SimTypeArray(ALL_TYPES["char"], length=32, label="char[32]"),
            "ut_id": SimTypeArray(ALL_TYPES["char"], length=4, label="char[32]"),
            "ut_user": SimTypeArray(ALL_TYPES["char"], length=32, label="char[32]"),
            "ut_host": SimTypeArray(ALL_TYPES["char"], length=256, label="char[32]"),
            "ut_exit": ALL_TYPES["exit_status"],
            "ut_session": ALL_TYPES["long int"],
            "ut_tv": ALL_TYPES["timeval"],
            "ut_addr_v6": SimTypeArray(ALL_TYPES["int32_t"], length=4, label="int32_t[4]"),
            "__glibc_reserved": SimTypeArray(ALL_TYPES["char"], length=20, label="char[20]"),
        },
        name="utmx",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/pwd/pwd.h#L49
    "passwd": SimStruct(
        {
            "pw_name": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "pw_passwd": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "pw_uid": ALL_TYPES["__uid_t"],
            "pw_gid": ALL_TYPES["__gid_t"],
            "pw_gecos": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "pw_dir": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "pw_shell": SimTypePointer(ALL_TYPES["char"], label="char *"),
        },
        name="passwd",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/grp/grp.h#L42
    "group": SimStruct(
        {
            "gr_name": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "gr_passwd": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "gr_gid": ALL_TYPES["__gid_t"],
            "gr_mem": SimTypePointer(SimTypePointer(ALL_TYPES["char"], label="char *"), label="char **"),
        },
        name="group",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/posix/sys/utsname.h#L48
    "utsname": SimStruct(
        {
            "sysname": SimTypeArray(ALL_TYPES["char"], length=1024, label="char[1024]"),
            "nodename": SimTypeArray(ALL_TYPES["char"], length=1024, label="char[1024]"),
            "release": SimTypeArray(ALL_TYPES["char"], length=1024, label="char[1024]"),
            "version": SimTypeArray(ALL_TYPES["char"], length=1024, label="char[1024]"),
            "machine": SimTypeArray(ALL_TYPES["char"], length=1024, label="char[1024]"),
            "domain": SimTypeArray(ALL_TYPES["char"], length=1024, label="char[1024]"),
        },
        name="utsname",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/misc/fstab.h#L57
    "fstab": SimStruct(
        {
            "fs_spec": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "fs_file": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "fs_vfstype": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "fs_mntops": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "fs_type": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "fs_freq": ALL_TYPES["int"],
            "fs_passno": ALL_TYPES["int"],
        },
        name="fstab",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/misc/mntent.h#L51
    "mntent": SimStruct(
        {
            "mnt_fsname": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "mnt_dir": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "mnt_type": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "mnt_opts": SimTypePointer(ALL_TYPES["char"], label="char *"),
            "mnt_freq": ALL_TYPES["int"],
            "mnt_passno": ALL_TYPES["int"],
        },
        name="mntent",
    ),
    # https://github.com/bminor/glibc/blob/2d5ec6692f5746ccb11db60976a6481ef8e9d74f/crypt/crypt.h#L43
    "crypt_data": SimStruct(
        {
            "keysched": SimTypeArray(ALL_TYPES["char"], length=16 * 8, label="char[16 * 8]"),
            "sb0": SimTypeArray(ALL_TYPES["char"], length=32768, label="char[32768]"),
            "sb1": SimTypeArray(ALL_TYPES["char"], length=32768, label="char[32768]"),
            "sb2": SimTypeArray(ALL_TYPES["char"], length=32768, label="char[32768]"),
            "sb3": SimTypeArray(ALL_TYPES["char"], length=32768, label="char[32768]"),
            "crypt_3_buf": SimTypeArray(ALL_TYPES["char"], length=14, label="char[14]"),
            "current_salt": SimTypeArray(ALL_TYPES["char"], length=2, label="char[2]"),
            "current_saltbits": ALL_TYPES["long int"],
            "direction": ALL_TYPES["int"],
            "initialized": ALL_TYPES["int"],
        },
        name="crypt_data",
    ),
}
ALL_TYPES.update(GLIBC_TYPES)


def _make_scope(predefined_types=None):
    """
    Generate CParser scope_stack argument to parse method
    """
    all_types = ChainMap(predefined_types or {}, ALL_TYPES)
    scope = {}
    for ty in all_types:
        if ty in BASIC_TYPES:
            continue
        if " " in ty:
            continue

        typ = all_types[ty]
        if type(typ) is TypeRef:
            typ = typ.type
        if isinstance(typ, (SimTypeFunction, SimTypeString, SimTypeWString)):
            continue

        scope[ty] = True
    return [scope]


def register_types(types):
    """
    Pass in some types and they will be registered to the global type store.

    The argument may be either a mapping from name to SimType, or a plain SimType.
    The plain SimType must be either a struct or union type with a name present.

    >>> register_types(parse_types("typedef int x; typedef float y;"))
    >>> register_types(parse_type("struct abcd { int ab; float cd; }"))
    """
    if type(types) is SimStruct:
        if types.name == "<anon>":
            raise ValueError("Cannot register anonymous struct")
        ALL_TYPES["struct " + types.name] = types
    elif type(types) is SimUnion:
        if types.name == "<anon>":
            raise ValueError("Cannot register anonymous union")
        ALL_TYPES["union " + types.name] = types
    else:
        ALL_TYPES.update(types)


def parse_signature(defn, predefined_types=None, arch=None) -> SimTypeFunction:
    """
    Parse a single function prototype and return its type
    """
    try:
        parsed = parse_file(defn.strip(" \n\t;") + ";", predefined_types=predefined_types, arch=arch)
        result = next(iter(parsed[0].values()))
        assert isinstance(result, SimTypeFunction)
        return result
    except StopIteration as e:
        raise ValueError("No declarations found") from e


def parse_defns(defn, predefined_types=None, arch=None) -> dict[str, SimType]:
    """
    Parse a series of C definitions, returns a mapping from variable name to variable type object
    """
    return parse_file(defn, predefined_types=predefined_types, arch=arch)[0]


def parse_types(defn, predefined_types=None, arch=None) -> dict[str, SimType]:
    """
    Parse a series of C definitions, returns a mapping from type name to type object
    """
    return parse_file(defn, predefined_types=predefined_types, arch=arch)[1]


_include_re = re.compile(r"^\s*#include")


def parse_file(
    defn,
    predefined_types: dict[Any, SimType] | None = None,
    arch=None,
    side_effect_types: dict[Any, SimType] | None = None,
) -> tuple[dict[str, SimType], dict[str, SimType]]:
    """
    Parse a series of C definitions, returns a tuple of two type mappings, one for variable
    definitions and one for type definitions.
    """

    if pycparser is None:
        raise ImportError("Please install pycparser in order to parse C definitions")

    defn = "\n".join(x for x in defn.split("\n") if _include_re.match(x) is None)
    # remove comments
    defn = re.sub(r"/\*.*?\*/", r"", defn, flags=re.DOTALL)
    defn = re.sub(r"//.*?$", r"", defn, flags=re.MULTILINE)

    # pylint: disable=unexpected-keyword-arg
    node = pycparser.c_parser.CParser().parse(defn, scope_stack=_make_scope(predefined_types))
    if not isinstance(node, c_ast.FileAST):
        raise ValueError("Something went horribly wrong using pycparser")
    out = {}
    out_types = {}
    extra_types = ChainMap(side_effect_types if side_effect_types is not None else out_types, predefined_types or {})

    for piece in node.ext:
        if isinstance(piece, c_ast.FuncDef):
            out[piece.decl.name] = _decl_to_type(piece.decl.type, extra_types, arch=arch)
        elif isinstance(piece, c_ast.Decl):
            ty = _decl_to_type(piece.type, extra_types, arch=arch)
            if piece.name is not None:
                out[piece.name] = ty

            # Don't forget to update typedef types
            ty_real = ty.type if isinstance(ty, TypeRef) else ty
            if isinstance(ty_real, (SimStruct, SimUnion)) and ty_real.name != "<anon>":
                if piece.name is None:
                    out_types[("struct " if isinstance(ty, SimStruct) else "union ") + ty_real.name] = ty_real
                for _, i in out_types.items():
                    if isinstance(i, type(ty_real)) and i.name == ty_real.name:
                        if isinstance(ty_real, SimStruct):
                            assert isinstance(i, SimStruct)
                            i.fields = ty_real.fields
                        else:
                            assert isinstance(i, SimUnion)
                            i.members = ty_real.members

        elif isinstance(piece, c_ast.Typedef):
            out_types[piece.name] = copy.copy(_decl_to_type(piece.type, extra_types, arch=arch))
            out_types[piece.name].label = piece.name

    return out, out_types


_type_parser_singleton = None


def type_parser_singleton() -> pycparser.CParser:
    global _type_parser_singleton  # pylint:disable=global-statement
    if pycparser is not None and _type_parser_singleton is None:
        _type_parser_singleton = pycparser.CParser()
    assert _type_parser_singleton is not None
    return _type_parser_singleton


def parse_type(defn, predefined_types=None, arch=None):  # pylint:disable=unused-argument
    """
    Parse a simple type expression into a SimType

    >>> parse_type('int *')
    """
    return parse_type_with_name(defn, predefined_types=predefined_types, arch=arch)[0]


def parse_type_with_name(
    defn,
    preprocess=True,
    predefined_types: dict[Any, SimType] | None = None,
    arch=None,
    side_effect_types: dict[Any, SimType] | None = None,
):  # pylint:disable=unused-argument
    """
    Parse a simple type expression into a SimType, returning a tuple of the type object and any associated name
    that might be found in the place a name would go in a type declaration.

    >>> parse_type_with_name('int *foo')
    """
    if pycparser is None:
        raise ImportError("Please install pycparser in order to parse C definitions")

    if preprocess:
        defn = re.sub(r"/\*.*?\*/", r"", defn)

    # pylint: disable=unexpected-keyword-arg
    node = type_parser_singleton().parse_type_with_name(defn, scope_stack=_make_scope(predefined_types))
    if not isinstance(node, c_ast.Typename) and not isinstance(node, c_ast.Decl):
        raise pycparser.c_parser.ParseError("Got an unexpected type out of pycparser")

    decl = node.type
    extra_types = ChainMap(side_effect_types if side_effect_types is not None else {}, predefined_types or {})
    return _decl_to_type(decl, extra_types=extra_types, arch=arch), node.name


def _accepts_scope_stack():
    """
    pycparser hack to include scope_stack as parameter in CParser parse method
    """

    def parse(self, text, filename="", debug=False, scope_stack=None):  # pylint:disable=unused-argument
        # debug is not used and only kept for backward compatibility as in pycparser >= 3.0

        self.clex._filename = filename
        self.clex._lineno = 1
        self._scope_stack = [{}] if scope_stack is None else scope_stack

        self.clex.input(text, filename)
        self._tokens = pycparser.c_parser._TokenStream(self.clex)

        ast = self._parse_translation_unit_or_empty()
        tok = self._peek()
        if tok is not None:
            self._parse_error(f"before: {tok.value}", self._tok_coord(tok))
        return ast

    def parse_type_with_name(self, text, filename="", scope_stack=None) -> c_ast.Typename:
        self.clex._filename = filename
        self.clex._lineno = 1
        self._scope_stack = [{}] if scope_stack is None else scope_stack

        self.clex.input(text, filename)
        self._tokens = pycparser.c_parser._TokenStream(self.clex)

        return self._parse_type_name()  # ignore all tokens that follow the type name

    pycparser.CParser.parse = parse
    pycparser.CParser.parse_type_with_name = parse_type_with_name


def _decl_to_type(
    decl, extra_types: MutableMapping[str, SimType] | None = None, bitsize=None, arch: Arch | None = None
) -> SimType:
    if extra_types is None:
        extra_types = {}

    if isinstance(decl, c_ast.FuncDecl):
        argtyps = (
            ()
            if decl.args is None
            else [
                (
                    ...
                    if type(x) is c_ast.EllipsisParam
                    else (
                        SimTypeBottom().with_arch(arch)
                        if type(x) is c_ast.ID
                        else _decl_to_type(x.type, extra_types, arch=arch)
                    )
                )
                for x in decl.args.params
            ]
        )
        arg_names = (
            [arg.name for arg in decl.args.params if type(arg) is not c_ast.EllipsisParam] if decl.args else None
        )
        # special handling: func(void) is func()
        if (
            len(argtyps) == 1
            and isinstance(argtyps[0], SimTypeBottom)
            and arg_names is not None
            and arg_names[0] is None
        ):
            argtyps = ()
            arg_names = None
        if argtyps and argtyps[-1] is ...:
            argtyps.pop()
            variadic = True
        else:
            variadic = False
        r = SimTypeFunction(
            cast(list[SimType], argtyps),
            _decl_to_type(decl.type, extra_types, arch=arch),
            arg_names=arg_names,
            variadic=variadic,
        )
        r._arch = arch
        return r

    if isinstance(decl, c_ast.TypeDecl):
        quals = list(decl.quals) if hasattr(decl, "quals") and decl.quals else None
        if decl.declname == "TOP":
            r = SimTypeTop(qualifier=quals)
            r._arch = arch
            return r
        r = _decl_to_type(decl.type, extra_types, bitsize=bitsize, arch=arch)
        if quals:
            r = r.copy()
            r.qualifier = list(quals)
        return r

    if isinstance(decl, c_ast.PtrDecl):
        quals = list(decl.quals) if hasattr(decl, "quals") and decl.quals else None

        pts_to = _decl_to_type(decl.type, extra_types, arch=arch)
        r = SimTypePointer(pts_to, qualifier=quals)
        r._arch = arch
        return r

    if isinstance(decl, c_ast.ArrayDecl):
        elem_type = _decl_to_type(decl.type, extra_types, arch=arch)
        quals = list(decl.quals) if hasattr(decl, "quals") and decl.quals else None  # type: ignore

        if decl.dim is None:
            r = SimTypeArray(elem_type)
            r._arch = arch
            return r
        try:
            size = _parse_const(decl.dim, extra_types=extra_types, arch=arch)
        except ValueError as e:
            l.warning("Got error parsing array dimension, defaulting to zero: %s", e)
            size = 0
        r = SimTypeFixedSizeArray(elem_type, size, qualifier=quals)
        r._arch = arch
        return r

    if isinstance(decl, c_ast.Struct):
        quals = list(decl.quals) if hasattr(decl, "quals") and decl.quals else None  # type: ignore
        if decl.decls is not None:
            fields = OrderedDict(
                (field.name, _decl_to_type(field.type, extra_types, bitsize=field.bitsize, arch=arch))
                for field in decl.decls
            )
        else:
            fields = OrderedDict()
        if decl.name is not None:
            key = "struct " + decl.name
            struct = extra_types.get(key)
            from_global = False
            if struct is None:
                struct = ALL_TYPES.get(key)
                if struct is not None:
                    from_global = True
                    struct = struct.with_arch(arch)
            if struct is None:
                # fallback to using decl.name as key directly
                struct = ALL_TYPES.get(decl.name)
                if struct is not None and (
                    isinstance(struct, SimStruct)
                    or (isinstance(struct, TypeRef) and isinstance(struct.type, SimStruct))
                ):
                    from_global = True
                    struct = struct.with_arch(arch)
                else:
                    # give up
                    struct = None
            struct_ref = struct
            if isinstance(struct_ref, TypeRef):
                struct = struct_ref.type
            if struct is not None and not isinstance(struct, SimStruct):
                raise AngrTypeError("Provided a non-SimStruct value for a type that must be a struct")

            if struct is None:
                struct = SimStruct(fields, decl.name, qualifier=quals)
                struct._arch = arch
                struct_ref = struct
            elif not struct.fields:
                struct.fields = fields
            elif fields and struct.fields != fields:
                if from_global:
                    struct = SimStruct(fields, decl.name, qualifier=quals)
                    struct._arch = arch
                    struct_ref = struct
                else:
                    raise ValueError("Redefining body of " + key)
            assert struct_ref is not None
            extra_types[key] = struct_ref
        else:
            struct = SimStruct(fields, qualifier=quals)
            struct._arch = arch
        return struct

    if isinstance(decl, c_ast.Union):
        if decl.decls is not None:
            fields = {field.name: _decl_to_type(field.type, extra_types, arch=arch) for field in decl.decls}
        else:
            fields = {}
        quals = list(decl.quals) if hasattr(decl, "quals") and decl.quals else None  # type: ignore

        if decl.name is not None:
            key = "union " + decl.name
            union = extra_types.get(key)
            from_global = False
            if union is None and key in ALL_TYPES:
                union = ALL_TYPES[key]
                from_global = True
            union_ref = union
            if isinstance(union_ref, TypeRef):
                union = union_ref.type
            if union is not None and not isinstance(union, SimUnion):
                raise AngrTypeError("Provided a non-SimUnion value for a type that must be a union")

            if union is None:
                union = SimUnion(fields, decl.name, qualifier=quals)
                union._arch = arch
                union_ref = union
            elif not union.members:
                union.members = fields
            elif fields and union.members != fields:
                if from_global:
                    union = SimStruct(fields, decl.name, qualifier=quals)
                    union._arch = arch
                    union_ref = union
                else:
                    raise ValueError("Redefining body of " + key)

            assert union_ref is not None
            extra_types[key] = union_ref
        else:
            union = SimUnion(fields, qualifier=quals)
            union._arch = arch
        return union

    if isinstance(decl, c_ast.IdentifierType):
        key = " ".join(decl.names)
        if bitsize is not None:
            return SimTypeNumOffset(int(bitsize.value), signed=False).with_arch(arch)
        if key in extra_types:
            return extra_types[key].with_arch(arch)
        if key in ALL_TYPES:
            return ALL_TYPES[key].with_arch(arch)
        raise TypeError(f"Unknown type '{key}'")

    if isinstance(decl, c_ast.Enum):
        # See C99 at 6.7.2.2
        # Parse enum members if present
        members: dict[str, int] = {}
        if decl.values is not None:
            next_value = 0
            for enumerator in decl.values.enumerators:
                if enumerator.value is not None:
                    with contextlib.suppress(ValueError):
                        # Keep the auto-incremented value
                        next_value = _parse_const(enumerator.value, arch=arch, extra_types=extra_types)
                assert isinstance(next_value, int)
                members[enumerator.name] = next_value
                next_value += 1

        enum_name = decl.name
        if enum_name is not None:
            key = "enum " + enum_name
            existing = extra_types.get(key) if extra_types else None
            if existing is None:
                existing = ALL_TYPES.get(key)
            if existing is not None and isinstance(existing, SimTypeEnum):
                # Update existing enum with members if it was forward-declared
                if not existing.members and members:
                    existing.members = members
                return existing.with_arch(arch)

            enum_type = SimTypeEnum(members, name=enum_name)
            enum_type._arch = arch
            if extra_types is not None:
                extra_types[key] = enum_type
            return enum_type

        # Anonymous enum
        enum_type = SimTypeEnum(members)
        enum_type._arch = arch
        return enum_type

    raise ValueError("Unknown type!")


def _parse_const(c, arch=None, extra_types=None):
    if type(c) is c_ast.Constant:
        return int(c.value, base=0)
    if type(c) is c_ast.BinaryOp:
        if c.op == "+":
            return _parse_const(c.children()[0][1], arch, extra_types) + _parse_const(
                c.children()[1][1], arch, extra_types
            )
        if c.op == "-":
            return _parse_const(c.children()[0][1], arch, extra_types) - _parse_const(
                c.children()[1][1], arch, extra_types
            )
        if c.op == "*":
            return _parse_const(c.children()[0][1], arch, extra_types) * _parse_const(
                c.children()[1][1], arch, extra_types
            )
        if c.op == "/":
            return _parse_const(c.children()[0][1], arch, extra_types) // _parse_const(
                c.children()[1][1], arch, extra_types
            )
        if c.op == "<<":
            return _parse_const(c.children()[0][1], arch, extra_types) << _parse_const(
                c.children()[1][1], arch, extra_types
            )
        if c.op == ">>":
            return _parse_const(c.children()[0][1], arch, extra_types) >> _parse_const(
                c.children()[1][1], arch, extra_types
            )
        raise ValueError(f"Binary op {c.op}")
    if type(c) is c_ast.UnaryOp:
        if c.op == "sizeof":
            return _decl_to_type(c.expr.type, extra_types=extra_types, arch=arch).size
        if c.op == "-":
            return -_parse_const(c.expr, arch, extra_types)
        if c.op == "+":
            return _parse_const(c.expr, arch, extra_types)
        if c.op == "~":
            return ~_parse_const(c.expr, arch, extra_types)
        raise ValueError(f"Unary op {c.op}")
    if type(c) is c_ast.Cast:
        return _parse_const(c.expr, arch, extra_types)
    raise ValueError(c)


CPP_DECL_TYPES = (
    cxxheaderparser.types.Method
    | cxxheaderparser.types.Array
    | cxxheaderparser.types.Pointer
    | cxxheaderparser.types.MoveReference
    | cxxheaderparser.types.Reference
    | cxxheaderparser.types.FunctionType
    | cxxheaderparser.types.Function
    | cxxheaderparser.types.Type
)


@overload
def _cpp_value_to_value(val: cxxheaderparser.types.Value, to_type: type[int]) -> int | None: ...


@overload
def _cpp_value_to_value(val: cxxheaderparser.types.Value, to_type: type[str]) -> str | None: ...


def _cpp_value_to_value(val: cxxheaderparser.types.Value, to_type: type) -> int | str | None:
    v = val.format()
    if to_type is int:
        try:
            return int(v)
        except ValueError:
            return None
    if to_type is str:
        return v
    raise TypeError(f"Unsupported to_type {to_type}")


def _cpp_decl_to_type(
    decl: CPP_DECL_TYPES, extra_types: MutableMapping[str, SimType], opaque_classes: bool = True
) -> (
    SimTypeCppFunction
    | SimTypeFunction
    | SimCppClass
    | SimTypeReference
    | SimTypePointer
    | SimTypeArray
    | SimTypeBottom
):
    if cxxheaderparser is None:
        raise ImportError("Please install cxxheaderparser to parse C++ definitions")
    if isinstance(decl, cxxheaderparser.types.Method):
        the_func = decl
        func_name = the_func.name.format()
        # translate parameters
        args = []
        arg_names: list[str] = []
        for idx, param in enumerate(the_func.parameters):
            arg_type = param.type
            args.append(_cpp_decl_to_type(arg_type, extra_types, opaque_classes=opaque_classes))
            arg_name = param.name if param.name is not None else f"arg_{idx}"
            arg_names.append(arg_name)

        args = tuple(args)
        arg_names_tuple: tuple[str, ...] = tuple(arg_names)

        # note that the constructor and destructor handling in cxxheaderparser is a bit weird and I could not get it to
        # work, hence the following hack
        ctor = dtor = False
        convention = the_func.msvc_convention
        if len(the_func.name.segments) >= 2:
            seg1, seg0 = the_func.name.segments[-2:]
            seg1 = seg1.format()
            seg0 = seg0.format()
            if seg0 == seg1:
                ctor = True
                if the_func.return_type is not None:
                    convention = the_func.return_type.format()  # it's usually just "__thiscall"
            elif seg0 == "~" + seg1:
                dtor = True
                if the_func.return_type is not None:
                    convention = the_func.return_type.format()  # it's usually just "__thiscall"
        # returns
        if the_func.return_type is None or ctor or dtor:
            returnty = SimTypeBottom()
        else:
            returnty = _cpp_decl_to_type(the_func.return_type, extra_types, opaque_classes=opaque_classes)
        return SimTypeCppFunction(
            args,
            returnty,
            label=func_name,
            arg_names=arg_names_tuple,
            ctor=ctor,
            dtor=dtor,
            convention=convention,
        )

    if isinstance(decl, cxxheaderparser.types.Function):
        # a function declaration
        the_func = decl
        func_name = the_func.name.format()
        # translate parameters
        args = []
        arg_names: list[str] = []
        for idx, param in enumerate(the_func.parameters):
            arg_type = param.type
            args.append(_cpp_decl_to_type(arg_type, extra_types, opaque_classes=opaque_classes))
            arg_name = param.name if param.name is not None else f"arg_{idx}"
            arg_names.append(arg_name)

        args = tuple(args)
        arg_names_tuple: tuple[str, ...] = tuple(arg_names)
        # returns
        if the_func.return_type is None:
            returnty = SimTypeBottom()
        else:
            returnty = _cpp_decl_to_type(the_func.return_type, extra_types, opaque_classes=opaque_classes)

        return SimTypeFunction(args, returnty, label=func_name, arg_names=arg_names_tuple)

    if isinstance(decl, cxxheaderparser.types.Type):
        # attempt to parse it as one of the existing types
        lbl = decl.format()
        lbl = lbl.removeprefix("const ")
        if lbl in extra_types:
            t = extra_types[lbl]
        elif lbl in ALL_TYPES:
            t = ALL_TYPES[lbl]
        elif opaque_classes is True:
            # create a struct or a class without knowing the internal members
            if decl.typename.classkey == "struct":
                t = SimTypeRef(lbl.removeprefix("struct "), SimStruct)
            else:
                t = SimCppClass(unique_name=lbl, name=lbl, members={}, size=32)
        else:
            raise TypeError(f'Unknown type "{lbl}"')

        if isinstance(t, NamedTypeMixin):
            t = t.copy()
            t.name = lbl  # pylint:disable=attribute-defined-outside-init
        return t  # type: ignore

    if isinstance(decl, cxxheaderparser.types.Array):
        subt = _cpp_decl_to_type(decl.array_of, extra_types, opaque_classes=opaque_classes)
        size: int | None = (
            _cpp_value_to_value(decl.size, int) if isinstance(decl.size, cxxheaderparser.types.Value) else decl.size
        )
        return SimTypeArray(subt, length=size)

    if isinstance(decl, cxxheaderparser.types.MoveReference):
        subt = _cpp_decl_to_type(decl.moveref_to, extra_types, opaque_classes=opaque_classes)
        return SimTypeReference(subt)  # FIXME: Move reference vs reference

    if isinstance(decl, cxxheaderparser.types.Reference):
        subt = _cpp_decl_to_type(decl.ref_to, extra_types, opaque_classes=opaque_classes)
        return SimTypeReference(subt)

    if isinstance(decl, cxxheaderparser.types.Pointer):
        subt = _cpp_decl_to_type(decl.ptr_to, extra_types, opaque_classes=opaque_classes)
        return SimTypePointer(subt)

    if isinstance(decl, cxxheaderparser.types.FunctionType):
        params = tuple(
            _cpp_decl_to_type(param.type, extra_types, opaque_classes=opaque_classes) for param in decl.parameters
        )
        param_names = (
            tuple(param.name.format() for param in decl.parameters)  # type: ignore
            if all(param.name is not None for param in decl.parameters)
            else None
        )
        returnty = _cpp_decl_to_type(decl.return_type, extra_types, opaque_classes=opaque_classes)
        return SimTypeCppFunction(params, returnty, arg_names=param_names, convention=decl.msvc_convention)

    raise NotImplementedError


def normalize_cpp_function_name(name: str) -> str:
    stripped_any = True
    while stripped_any:
        stripped_any = False
        # strip virtual/static/inline/friend keywords
        prefixes = ["virtual", "static", "inline", "friend"]
        for pre in prefixes:
            new_name = name.removeprefix(pre + " ")
            if new_name != name:
                name = new_name
                stripped_any = True

        # strip access specifiers
        prefixes = ["public:", "protected:", "private:", "[thunk]:"]
        for pre in prefixes:
            new_name = name.removeprefix(pre)
            if new_name != name:
                name = new_name
                stripped_any = True

        new_name = name.strip()
        if new_name != name:
            name = new_name
            stripped_any = True

    if "void (__cdecl *)" in name:
        name = name.replace("void (__cdecl *)", "void ")

    if name.startswith("operator"):
        # the return type is missing; give it a default type
        name = "int " + name

    if " __int" in name:
        name = name.replace(" __int64 ", " long long ")
        name = name.replace(" __int32 ", " int ")
        name = name.replace(" __int16 ", " short ")

    return name.removesuffix(";")


def parse_cpp_file(cpp_decl, with_param_names: bool = False):  # pylint: disable=unused-argument
    #
    # A series of hacks to make cxxheaderparser happy with whatever C++ function prototypes we feed in
    #

    if cxxheaderparser is None:
        raise ImportError("Please install cxxheaderparser to parse C++ definitions")

    # CppHeaderParser does not support specialization
    s = normalize_cpp_function_name(cpp_decl)

    # CppHeaderParser does not like missing function body
    s += "\n\n{}"

    try:
        h = cxxheaderparser.simple.parse_string(s)
    except cxxheaderparser.errors.CxxParseError:
        # GCC-mangled (and thus, demangled) function names do not have return types encoded; let's try to prefix s with
        # "void" and try again
        s = "void " + s
        try:
            h = cxxheaderparser.simple.parse_string(s)
        except cxxheaderparser.errors.CxxParseError:
            # if it still fails, we give up
            return None, None

    if not h.namespace:
        return None, None

    func_decls: dict[str, SimTypeCppFunction | SimTypeFunction] = {}
    for the_func in h.namespace.functions + h.namespace.method_impls:
        # FIXME: We always assume that there is a "this" pointer but it is not the case for static methods.
        proto = cast(SimTypeCppFunction | SimTypeFunction | None, _cpp_decl_to_type(the_func, {}, opaque_classes=True))
        if proto is not None:
            func_name = the_func.name.format()
            if isinstance(proto, SimTypeCppFunction):
                proto.args = (
                    SimTypePointer(pts_to=SimTypeBottom(label="void")),
                    *proto.args,
                )  # pylint:disable=attribute-defined-outside-init
                proto.arg_names = ("this", *proto.arg_names)  # pylint:disable=attribute-defined-outside-init
            func_decls[func_name] = proto

    return func_decls, {}


if pycparser is not None:
    _accepts_scope_stack()

with contextlib.suppress(ImportError):
    register_types(
        parse_types("""
typedef long time_t;

struct timespec {
    time_t tv_sec;
    long tv_nsec;
};

struct timeval {
    time_t tv_sec;
    long tv_usec;
};
""")
    )

from .state_plugins.view import SimMemView
