-
-
Notifications
You must be signed in to change notification settings - Fork 30.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
gh-78997: AttributeError if loading fails in LibraryLoader.__getattr__
Co-authored-by: Zachary Ware <[email protected]> Co-authored-by: Filipe Laíns <[email protected]> Co-authored-by: Eric Wieser <[email protected]>
- Loading branch information
1 parent
b430399
commit 101cfe6
Showing
3 changed files
with
195 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
from ctypes import * | ||
import os | ||
import shutil | ||
import subprocess | ||
import sys | ||
import unittest | ||
import test.support | ||
from test.support import import_helper | ||
from test.support import os_helper | ||
from ctypes.util import find_library | ||
|
||
libc_name = None | ||
|
||
def setUpModule(): | ||
global libc_name | ||
if os.name == "nt": | ||
libc_name = find_library("c") | ||
elif sys.platform == "cygwin": | ||
libc_name = "cygwin1.dll" | ||
else: | ||
libc_name = find_library("c") | ||
|
||
if test.support.verbose: | ||
print("libc_name is", libc_name) | ||
|
||
class LoaderTest(unittest.TestCase): | ||
|
||
unknowndll = "xxrandomnamexx" | ||
|
||
def test_load(self): | ||
if libc_name is None: | ||
self.skipTest('could not find libc') | ||
CDLL(libc_name) | ||
CDLL(os.path.basename(libc_name)) | ||
self.assertRaises(OSError, CDLL, self.unknowndll) | ||
|
||
def test_load_version(self): | ||
if libc_name is None: | ||
self.skipTest('could not find libc') | ||
if os.path.basename(libc_name) != 'libc.so.6': | ||
self.skipTest('wrong libc path for test') | ||
cdll.LoadLibrary("libc.so.6") | ||
# linux uses version, libc 9 should not exist | ||
self.assertRaises(OSError, cdll.LoadLibrary, "libc.so.9") | ||
self.assertRaises(OSError, cdll.LoadLibrary, self.unknowndll) | ||
|
||
def test_find(self): | ||
for name in ("c", "m"): | ||
lib = find_library(name) | ||
if lib: | ||
cdll.LoadLibrary(lib) | ||
CDLL(lib) | ||
|
||
@unittest.skipUnless(os.name == "nt", | ||
'test specific to Windows') | ||
def test_load_library(self): | ||
# CRT is no longer directly loadable. See issue23606 for the | ||
# discussion about alternative approaches. | ||
#self.assertIsNotNone(libc_name) | ||
if test.support.verbose: | ||
print(find_library("kernel32")) | ||
print(find_library("user32")) | ||
|
||
if os.name == "nt": | ||
windll.kernel32.GetModuleHandleW | ||
windll["kernel32"].GetModuleHandleW | ||
windll.LoadLibrary("kernel32").GetModuleHandleW | ||
WinDLL("kernel32").GetModuleHandleW | ||
# embedded null character | ||
self.assertRaises(ValueError, windll.LoadLibrary, "kernel32\0") | ||
|
||
@unittest.skipUnless(os.name == "nt", | ||
'test specific to Windows') | ||
def test_load_ordinal_functions(self): | ||
import _ctypes_test | ||
dll = WinDLL(_ctypes_test.__file__) | ||
# We load the same function both via ordinal and name | ||
func_ord = dll[2] | ||
func_name = dll.GetString | ||
# addressof gets the address where the function pointer is stored | ||
a_ord = addressof(func_ord) | ||
a_name = addressof(func_name) | ||
f_ord_addr = c_void_p.from_address(a_ord).value | ||
f_name_addr = c_void_p.from_address(a_name).value | ||
self.assertEqual(hex(f_ord_addr), hex(f_name_addr)) | ||
|
||
self.assertRaises(AttributeError, dll.__getitem__, 1234) | ||
|
||
@unittest.skipUnless(os.name == "nt", 'Windows-specific test') | ||
def test_1703286_A(self): | ||
from _ctypes import LoadLibrary, FreeLibrary | ||
# On winXP 64-bit, advapi32 loads at an address that does | ||
# NOT fit into a 32-bit integer. FreeLibrary must be able | ||
# to accept this address. | ||
|
||
# These are tests for https://www.python.org/sf/1703286 | ||
handle = LoadLibrary("advapi32") | ||
FreeLibrary(handle) | ||
|
||
@unittest.skipUnless(os.name == "nt", 'Windows-specific test') | ||
def test_1703286_B(self): | ||
# Since on winXP 64-bit advapi32 loads like described | ||
# above, the (arbitrarily selected) CloseEventLog function | ||
# also has a high address. 'call_function' should accept | ||
# addresses so large. | ||
from _ctypes import call_function | ||
advapi32 = windll.advapi32 | ||
# Calling CloseEventLog with a NULL argument should fail, | ||
# but the call should not segfault or so. | ||
self.assertEqual(0, advapi32.CloseEventLog(None)) | ||
windll.kernel32.GetProcAddress.argtypes = c_void_p, c_char_p | ||
windll.kernel32.GetProcAddress.restype = c_void_p | ||
proc = windll.kernel32.GetProcAddress(advapi32._handle, | ||
b"CloseEventLog") | ||
self.assertTrue(proc) | ||
# This is the real test: call the function via 'call_function' | ||
self.assertEqual(0, call_function(proc, (None,))) | ||
|
||
@unittest.skipUnless(os.name == "nt", | ||
'test specific to Windows') | ||
def test_load_hasattr(self): | ||
# bpo-34816: shouldn't raise OSError | ||
self.assertFalse(hasattr(windll, 'test')) | ||
|
||
@unittest.skipUnless(os.name == "nt", | ||
'test specific to Windows') | ||
def test_load_dll_with_flags(self): | ||
_sqlite3 = import_helper.import_module("_sqlite3") | ||
src = _sqlite3.__file__ | ||
if src.lower().endswith("_d.pyd"): | ||
ext = "_d.dll" | ||
else: | ||
ext = ".dll" | ||
|
||
with os_helper.temp_dir() as tmp: | ||
# We copy two files and load _sqlite3.dll (formerly .pyd), | ||
# which has a dependency on sqlite3.dll. Then we test | ||
# loading it in subprocesses to avoid it starting in memory | ||
# for each test. | ||
target = os.path.join(tmp, "_sqlite3.dll") | ||
shutil.copy(src, target) | ||
shutil.copy(os.path.join(os.path.dirname(src), "sqlite3" + ext), | ||
os.path.join(tmp, "sqlite3" + ext)) | ||
|
||
def should_pass(command): | ||
with self.subTest(command): | ||
subprocess.check_output( | ||
[sys.executable, "-c", | ||
"from ctypes import *; import nt;" + command], | ||
cwd=tmp | ||
) | ||
|
||
def should_fail(command): | ||
with self.subTest(command): | ||
with self.assertRaises(subprocess.CalledProcessError): | ||
subprocess.check_output( | ||
[sys.executable, "-c", | ||
"from ctypes import *; import nt;" + command], | ||
cwd=tmp, stderr=subprocess.STDOUT, | ||
) | ||
|
||
# Default load should not find this in CWD | ||
should_fail("WinDLL('_sqlite3.dll')") | ||
|
||
# Relative path (but not just filename) should succeed | ||
should_pass("WinDLL('./_sqlite3.dll')") | ||
|
||
# Insecure load flags should succeed | ||
# Clear the DLL directory to avoid safe search settings propagating | ||
should_pass("windll.kernel32.SetDllDirectoryW(None); WinDLL('_sqlite3.dll', winmode=0)") | ||
|
||
# Full path load without DLL_LOAD_DIR shouldn't find dependency | ||
should_fail("WinDLL(nt._getfullpathname('_sqlite3.dll'), " + | ||
"winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32)") | ||
|
||
# Full path load with DLL_LOAD_DIR should succeed | ||
should_pass("WinDLL(nt._getfullpathname('_sqlite3.dll'), " + | ||
"winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32|" + | ||
"nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR)") | ||
|
||
# User-specified directory should succeed | ||
should_pass("import os; p = os.add_dll_directory(os.getcwd());" + | ||
"WinDLL('_sqlite3.dll'); p.close()") | ||
|
||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
3 changes: 3 additions & 0 deletions
3
Misc/NEWS.d/next/Windows/2021-04-08-00-36-37.bpo-34816.4Xe0id.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
``hasattr(ctypes.windll, 'nonexistant')`` now returns ``False`` instead of raising :exc:`OSError`. | ||
|
||
|