1# DExTer : Debugging Experience Tester
2# ~~~~~~   ~         ~~         ~   ~~
3#
4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5# See https://llvm.org/LICENSE.txt for license information.
6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7"""Communication via the Windows COM interface."""
8
9import inspect
10import time
11import sys
12
13# pylint: disable=import-error
14import win32com.client as com
15import win32api
16
17# pylint: enable=import-error
18
19from dex.utils.Exceptions import LoadDebuggerException
20
21_com_error = com.pywintypes.com_error  # pylint: disable=no-member
22
23
24def get_file_version(file_):
25    try:
26        info = win32api.GetFileVersionInfo(file_, "\\")
27        ms = info["FileVersionMS"]
28        ls = info["FileVersionLS"]
29        return ".".join(
30            str(s)
31            for s in [
32                win32api.HIWORD(ms),
33                win32api.LOWORD(ms),
34                win32api.HIWORD(ls),
35                win32api.LOWORD(ls),
36            ]
37        )
38    except com.pywintypes.error:  # pylint: disable=no-member
39        return "no versioninfo present"
40
41
42def _handle_com_error(e):
43    exc = sys.exc_info()
44    msg = win32api.FormatMessage(e.hresult)
45    try:
46        msg = msg.decode("CP1251")
47    except AttributeError:
48        pass
49    msg = msg.strip()
50    return msg, exc
51
52
53class ComObject(object):
54    """Wrap a raw Windows COM object in a class that implements auto-retry of
55    failed calls.
56    """
57
58    def __init__(self, raw):
59        assert not isinstance(raw, ComObject), raw
60        self.__dict__["raw"] = raw
61
62    def __str__(self):
63        return self._call(self.raw.__str__)
64
65    def __getattr__(self, key):
66        if key in self.__dict__:
67            return self.__dict__[key]
68        return self._call(self.raw.__getattr__, key)
69
70    def __setattr__(self, key, val):
71        if key in self.__dict__:
72            self.__dict__[key] = val
73        self._call(self.raw.__setattr__, key, val)
74
75    def __getitem__(self, key):
76        return self._call(self.raw.__getitem__, key)
77
78    def __setitem__(self, key, val):
79        self._call(self.raw.__setitem__, key, val)
80
81    def __call__(self, *args):
82        return self._call(self.raw, *args)
83
84    @classmethod
85    def _call(cls, fn, *args):
86        """COM calls tend to randomly fail due to thread sync issues.
87        The Microsoft recommended solution is to set up a message filter object
88        to automatically retry failed calls, but this seems prohibitively hard
89        from python, so this is a custom solution to do the same thing.
90        All COM accesses should go through this function.
91        """
92        ex = AssertionError("this should never be raised!")
93
94        assert (
95            inspect.isfunction(fn) or inspect.ismethod(fn) or inspect.isbuiltin(fn)
96        ), (fn, type(fn))
97        retries = ([0] * 50) + ([1] * 5)
98        for r in retries:
99            try:
100                try:
101                    result = fn(*args)
102                    if inspect.ismethod(result) or "win32com" in str(result.__class__):
103                        result = ComObject(result)
104                    return result
105                except _com_error as e:
106                    msg, _ = _handle_com_error(e)
107                    e = WindowsError(msg)  # pylint: disable=undefined-variable
108                    raise e
109            except (AttributeError, TypeError, OSError) as e:
110                ex = e
111                time.sleep(r)
112        raise ex
113
114
115class DTE(ComObject):
116    def __init__(self, class_string):
117        try:
118            super(DTE, self).__init__(com.DispatchEx(class_string))
119        except _com_error as e:
120            msg, exc = _handle_com_error(e)
121            raise LoadDebuggerException(
122                "{} [{}]".format(msg, class_string), orig_exception=exc
123            )
124