Skip to content

Commit

Permalink
loader: move ctypes hooks into a module
Browse files Browse the repository at this point in the history
Move the ctypes hooks from bootstrap script into separate module
(pymod04_ctypes). The main motivation for this is to prevent the
modifications to global namespace from affecting the ctypes hooks
code. Specifically, if user decides to use `sys` or `os` as
variables in the global namespace, that should not effect the
ctypes hook function _frozen_name(), which treats `sys` and `os`
as names of modules (which were bound to those names when the
bootstrap script was executed).

Fixes #5797.

This commit is a cleaned-up and modernized version of corresponding
commit from #3038 (daf60a6).

Co-authored-by: Hartmut Goebel <h.goebel@crazy-compilers.com>
  • Loading branch information
2 people authored and bwoodsend committed May 22, 2021
1 parent 3dd9f7f commit ed615bb
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 80 deletions.
1 change: 1 addition & 0 deletions PyInstaller/depend/analysis.py
Expand Up @@ -847,6 +847,7 @@ def get_bootstrap_modules():
('pyimod01_os_path', os.path.join(loaderpath, 'pyimod01_os_path.pyc'), 'PYMODULE'),
('pyimod02_archive', os.path.join(loaderpath, 'pyimod02_archive.pyc'), 'PYMODULE'),
('pyimod03_importers', os.path.join(loaderpath, 'pyimod03_importers.pyc'), 'PYMODULE'),
('pyimod04_ctypes', os.path.join(loaderpath, 'pyimod04_ctypes.pyc'), 'PYMODULE'), # noqa: E501
('pyiboot01_bootstrap', os.path.join(loaderpath, 'pyiboot01_bootstrap.py'), 'PYSOURCE'),
]
return loader_mods
83 changes: 3 additions & 80 deletions PyInstaller/loader/pyiboot01_bootstrap.py
Expand Up @@ -118,86 +118,9 @@ def isatty(self):
if sys.warnoptions:
import warnings

try:
import ctypes
import os
from ctypes import LibraryLoader, DEFAULT_MODE

def _frozen_name(name):
if name:
frozen_name = os.path.join(sys._MEIPASS, os.path.basename(name))
if os.path.exists(frozen_name) and not os.path.isdir(frozen_name):
name = frozen_name
return name

class PyInstallerImportError(OSError):
def __init__(self, name):
self.msg = ("Failed to load dynlib/dll %r. "
"Most probably this dynlib/dll was not found "
"when the application was frozen.") % name
self.args = (self.msg,)

class PyInstallerCDLL(ctypes.CDLL):
def __init__(self, name, *args, **kwargs):
name = _frozen_name(name)
try:
super(PyInstallerCDLL, self).__init__(name, *args, **kwargs)
except Exception as base_error:
raise PyInstallerImportError(name) from base_error

ctypes.CDLL = PyInstallerCDLL
ctypes.cdll = LibraryLoader(PyInstallerCDLL)

class PyInstallerPyDLL(ctypes.PyDLL):
def __init__(self, name, *args, **kwargs):
name = _frozen_name(name)
try:
super(PyInstallerPyDLL, self).__init__(name, *args, **kwargs)
except Exception as base_error:
raise PyInstallerImportError(name) from base_error

ctypes.PyDLL = PyInstallerPyDLL
ctypes.pydll = LibraryLoader(PyInstallerPyDLL)

if sys.platform.startswith('win'):
class PyInstallerWinDLL(ctypes.WinDLL):
def __init__(self, name,*args, **kwargs):
name = _frozen_name(name)
try:
super(PyInstallerWinDLL, self).__init__(name, *args, **kwargs)
except Exception as base_error:
raise PyInstallerImportError(name) from base_error

ctypes.WinDLL = PyInstallerWinDLL
ctypes.windll = LibraryLoader(PyInstallerWinDLL)

class PyInstallerOleDLL(ctypes.OleDLL):
def __init__(self, name,*args, **kwargs):
name = _frozen_name(name)
try:
super(PyInstallerOleDLL, self).__init__(name, *args, **kwargs)
except Exception as base_error:
raise PyInstallerImportError(name) from base_error

ctypes.OleDLL = PyInstallerOleDLL
ctypes.oledll = LibraryLoader(PyInstallerOleDLL)
except ImportError:
pass

# On Mac OS X insert sys._MEIPASS in the first position of the list of paths
# that ctypes uses to search for libraries.
#
# Note: 'ctypes' module will NOT be bundled with every app because code in this
# module is not scanned for module dependencies. It is safe to wrap
# 'ctypes' module into 'try/except ImportError' block.
if sys.platform.startswith('darwin'):
try:
from ctypes.macholib import dyld
dyld.DEFAULT_LIBRARY_FALLBACK.insert(0, sys._MEIPASS)
except ImportError:
# Do nothing when module 'ctypes' is not available.
pass

# Install the hooks for ctypes
import pyimod04_ctypes # noqa: E402
pyimod04_ctypes.install()

# Make .eggs and zipfiles available at runtime
d = "eggs"
Expand Down
105 changes: 105 additions & 0 deletions PyInstaller/loader/pyimod04_ctypes.py
@@ -0,0 +1,105 @@
#-----------------------------------------------------------------------------
# Copyright (c) 2005-2021, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License with exception
# for distributing bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------

"""
Hooks to make ctypes.CDLL, .PyDLL, etc. look in sys._MEIPASS first.
"""

import sys


def install():
"""Install the hooks.
This must be done from a function as opposed to at module-level,
because when the module is imported/executed, the import machinery
is not completely set up yet.
"""

import os

try:
import ctypes
except ImportError:
# ctypes is not included in the frozen application
return

def _frozen_name(name):
if name:
frozen_name = os.path.join(sys._MEIPASS, os.path.basename(name))
if os.path.exists(frozen_name) and not os.path.isdir(frozen_name):
name = frozen_name
return name

class PyInstallerImportError(OSError):
def __init__(self, name):
self.msg = ("Failed to load dynlib/dll %r. "
"Most probably this dynlib/dll was not found "
"when the application was frozen.") % name
self.args = (self.msg,)

class PyInstallerCDLL(ctypes.CDLL):
def __init__(self, name, *args, **kwargs):
name = _frozen_name(name)
try:
super().__init__(name, *args, **kwargs)
except Exception as base_error:
raise PyInstallerImportError(name) from base_error

ctypes.CDLL = PyInstallerCDLL
ctypes.cdll = ctypes.LibraryLoader(PyInstallerCDLL)

class PyInstallerPyDLL(ctypes.PyDLL):
def __init__(self, name, *args, **kwargs):
name = _frozen_name(name)
try:
super().__init__(name, *args, **kwargs)
except Exception as base_error:
raise PyInstallerImportError(name) from base_error

ctypes.PyDLL = PyInstallerPyDLL
ctypes.pydll = ctypes.LibraryLoader(PyInstallerPyDLL)

if sys.platform.startswith('win'):
class PyInstallerWinDLL(ctypes.WinDLL):
def __init__(self, name, *args, **kwargs):
name = _frozen_name(name)
try:
super().__init__(name, *args, **kwargs)
except Exception as base_error:
raise PyInstallerImportError(name) from base_error

ctypes.WinDLL = PyInstallerWinDLL
ctypes.windll = ctypes.LibraryLoader(PyInstallerWinDLL)

class PyInstallerOleDLL(ctypes.OleDLL):
def __init__(self, name, *args, **kwargs):
name = _frozen_name(name)
try:
super().__init__(name, *args, **kwargs)
except Exception as base_error:
raise PyInstallerImportError(name) from base_error

ctypes.OleDLL = PyInstallerOleDLL
ctypes.oledll = ctypes.LibraryLoader(PyInstallerOleDLL)


# On Mac OS X insert sys._MEIPASS in the first position of the list of paths
# that ctypes uses to search for libraries.
#
# Note: 'ctypes' module will NOT be bundled with every app because code in this
# module is not scanned for module dependencies. It is safe to wrap
# 'ctypes' module into 'try/except ImportError' block.
if sys.platform.startswith('darwin'):
try:
from ctypes.macholib import dyld
dyld.DEFAULT_LIBRARY_FALLBACK.insert(0, sys._MEIPASS)
except ImportError:
# Do nothing when module 'ctypes' is not available.
pass
3 changes: 3 additions & 0 deletions news/5797.bugfix.rst
@@ -0,0 +1,3 @@
Prevent the use of ``sys`` or ``os`` as variables in the global namespace
in frozen script from affecting the ``ctypes`` hooks thar are installed
during bootstrap.

0 comments on commit ed615bb

Please sign in to comment.