Skip to content

Commit accc969

Browse files
authored
Merge branch 'main' into ctypes-empty-array-itemsize
2 parents d459459 + 101cfe6 commit accc969

14 files changed

+250
-57
lines changed

Doc/c-api/typeobj.rst

+9
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ Quick Reference
147147
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
148148
| :c:member:`~PyTypeObject.tp_vectorcall` | :c:type:`vectorcallfunc` | | | | | |
149149
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
150+
| :c:member:`~PyTypeObject.tp_watched` | char | | | | | |
151+
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
150152

151153
.. [#slots]
152154
@@ -2090,6 +2092,13 @@ and :c:type:`PyType_Type` effectively act as defaults.)
20902092
.. versionadded:: 3.9 (the field exists since 3.8 but it's only used since 3.9)
20912093

20922094

2095+
.. c:member:: char PyTypeObject.tp_watched
2096+
2097+
Internal. Do not use.
2098+
2099+
.. versionadded:: 3.12
2100+
2101+
20932102
.. _static-types:
20942103

20952104
Static Types

Doc/includes/typestruct.h

+3
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,7 @@ typedef struct _typeobject {
8080

8181
destructor tp_finalize;
8282
vectorcallfunc tp_vectorcall;
83+
84+
/* bitset of which type-watchers care about this type */
85+
char tp_watched;
8386
} PyTypeObject;

Doc/library/itertools.rst

-7
Original file line numberDiff line numberDiff line change
@@ -829,10 +829,6 @@ which incur interpreter overhead.
829829
"Count how many times the predicate is true"
830830
return sum(map(pred, iterable))
831831

832-
def pad_none(iterable):
833-
"Returns the sequence elements and then returns None indefinitely."
834-
return chain(iterable, repeat(None))
835-
836832
def ncycles(iterable, n):
837833
"Returns the sequence elements n times"
838834
return chain.from_iterable(repeat(tuple(iterable), n))
@@ -1193,9 +1189,6 @@ which incur interpreter overhead.
11931189
>>> take(5, map(int, repeatfunc(random.random)))
11941190
[0, 0, 0, 0, 0]
11951191

1196-
>>> list(islice(pad_none('abc'), 0, 6))
1197-
['a', 'b', 'c', None, None, None]
1198-
11991192
>>> list(ncycles('abc', 3))
12001193
['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']
12011194

Doc/library/random.rst

+4-1
Original file line numberDiff line numberDiff line change
@@ -320,14 +320,17 @@ be found in any statistics text.
320320
``beta > 0``. Returned values range between 0 and 1.
321321

322322

323-
.. function:: expovariate(lambd)
323+
.. function:: expovariate(lambd = 1.0)
324324

325325
Exponential distribution. *lambd* is 1.0 divided by the desired
326326
mean. It should be nonzero. (The parameter would be called
327327
"lambda", but that is a reserved word in Python.) Returned values
328328
range from 0 to positive infinity if *lambd* is positive, and from
329329
negative infinity to 0 if *lambd* is negative.
330330

331+
.. versionchanged:: 3.12
332+
Added the default value for ``lambd``.
333+
331334

332335
.. function:: gammavariate(alpha, beta)
333336

Include/internal/pycore_typeobject.h

-6
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,9 @@ struct type_cache_entry {
3636
};
3737

3838
#define MCACHE_SIZE_EXP 12
39-
#define MCACHE_STATS 0
4039

4140
struct type_cache {
4241
struct type_cache_entry hashtable[1 << MCACHE_SIZE_EXP];
43-
#if MCACHE_STATS
44-
size_t hits;
45-
size_t misses;
46-
size_t collisions;
47-
#endif
4842
};
4943

5044
/* For now we hard-code this to a value for which we are confident

Include/pystats.h

+7
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,15 @@ typedef struct _object_stats {
6565
uint64_t dict_materialized_new_key;
6666
uint64_t dict_materialized_too_big;
6767
uint64_t dict_materialized_str_subclass;
68+
uint64_t type_cache_hits;
69+
uint64_t type_cache_misses;
70+
uint64_t type_cache_dunder_hits;
71+
uint64_t type_cache_dunder_misses;
72+
uint64_t type_cache_collisions;
6873
} ObjectStats;
6974

75+
#
76+
7077
typedef struct _stats {
7178
OpcodeStats opcode_stats[256];
7279
CallStats call_stats;

Lib/ctypes/__init__.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,10 @@ def __init__(self, dlltype):
444444
def __getattr__(self, name):
445445
if name[0] == '_':
446446
raise AttributeError(name)
447-
dll = self._dlltype(name)
447+
try:
448+
dll = self._dlltype(name)
449+
except OSError:
450+
raise AttributeError(name)
448451
setattr(self, name, dll)
449452
return dll
450453

Lib/ctypes/test/test_loading.py

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
from ctypes import *
2+
import os
3+
import shutil
4+
import subprocess
5+
import sys
6+
import unittest
7+
import test.support
8+
from test.support import import_helper
9+
from test.support import os_helper
10+
from ctypes.util import find_library
11+
12+
libc_name = None
13+
14+
def setUpModule():
15+
global libc_name
16+
if os.name == "nt":
17+
libc_name = find_library("c")
18+
elif sys.platform == "cygwin":
19+
libc_name = "cygwin1.dll"
20+
else:
21+
libc_name = find_library("c")
22+
23+
if test.support.verbose:
24+
print("libc_name is", libc_name)
25+
26+
class LoaderTest(unittest.TestCase):
27+
28+
unknowndll = "xxrandomnamexx"
29+
30+
def test_load(self):
31+
if libc_name is None:
32+
self.skipTest('could not find libc')
33+
CDLL(libc_name)
34+
CDLL(os.path.basename(libc_name))
35+
self.assertRaises(OSError, CDLL, self.unknowndll)
36+
37+
def test_load_version(self):
38+
if libc_name is None:
39+
self.skipTest('could not find libc')
40+
if os.path.basename(libc_name) != 'libc.so.6':
41+
self.skipTest('wrong libc path for test')
42+
cdll.LoadLibrary("libc.so.6")
43+
# linux uses version, libc 9 should not exist
44+
self.assertRaises(OSError, cdll.LoadLibrary, "libc.so.9")
45+
self.assertRaises(OSError, cdll.LoadLibrary, self.unknowndll)
46+
47+
def test_find(self):
48+
for name in ("c", "m"):
49+
lib = find_library(name)
50+
if lib:
51+
cdll.LoadLibrary(lib)
52+
CDLL(lib)
53+
54+
@unittest.skipUnless(os.name == "nt",
55+
'test specific to Windows')
56+
def test_load_library(self):
57+
# CRT is no longer directly loadable. See issue23606 for the
58+
# discussion about alternative approaches.
59+
#self.assertIsNotNone(libc_name)
60+
if test.support.verbose:
61+
print(find_library("kernel32"))
62+
print(find_library("user32"))
63+
64+
if os.name == "nt":
65+
windll.kernel32.GetModuleHandleW
66+
windll["kernel32"].GetModuleHandleW
67+
windll.LoadLibrary("kernel32").GetModuleHandleW
68+
WinDLL("kernel32").GetModuleHandleW
69+
# embedded null character
70+
self.assertRaises(ValueError, windll.LoadLibrary, "kernel32\0")
71+
72+
@unittest.skipUnless(os.name == "nt",
73+
'test specific to Windows')
74+
def test_load_ordinal_functions(self):
75+
import _ctypes_test
76+
dll = WinDLL(_ctypes_test.__file__)
77+
# We load the same function both via ordinal and name
78+
func_ord = dll[2]
79+
func_name = dll.GetString
80+
# addressof gets the address where the function pointer is stored
81+
a_ord = addressof(func_ord)
82+
a_name = addressof(func_name)
83+
f_ord_addr = c_void_p.from_address(a_ord).value
84+
f_name_addr = c_void_p.from_address(a_name).value
85+
self.assertEqual(hex(f_ord_addr), hex(f_name_addr))
86+
87+
self.assertRaises(AttributeError, dll.__getitem__, 1234)
88+
89+
@unittest.skipUnless(os.name == "nt", 'Windows-specific test')
90+
def test_1703286_A(self):
91+
from _ctypes import LoadLibrary, FreeLibrary
92+
# On winXP 64-bit, advapi32 loads at an address that does
93+
# NOT fit into a 32-bit integer. FreeLibrary must be able
94+
# to accept this address.
95+
96+
# These are tests for https://www.python.org/sf/1703286
97+
handle = LoadLibrary("advapi32")
98+
FreeLibrary(handle)
99+
100+
@unittest.skipUnless(os.name == "nt", 'Windows-specific test')
101+
def test_1703286_B(self):
102+
# Since on winXP 64-bit advapi32 loads like described
103+
# above, the (arbitrarily selected) CloseEventLog function
104+
# also has a high address. 'call_function' should accept
105+
# addresses so large.
106+
from _ctypes import call_function
107+
advapi32 = windll.advapi32
108+
# Calling CloseEventLog with a NULL argument should fail,
109+
# but the call should not segfault or so.
110+
self.assertEqual(0, advapi32.CloseEventLog(None))
111+
windll.kernel32.GetProcAddress.argtypes = c_void_p, c_char_p
112+
windll.kernel32.GetProcAddress.restype = c_void_p
113+
proc = windll.kernel32.GetProcAddress(advapi32._handle,
114+
b"CloseEventLog")
115+
self.assertTrue(proc)
116+
# This is the real test: call the function via 'call_function'
117+
self.assertEqual(0, call_function(proc, (None,)))
118+
119+
@unittest.skipUnless(os.name == "nt",
120+
'test specific to Windows')
121+
def test_load_hasattr(self):
122+
# bpo-34816: shouldn't raise OSError
123+
self.assertFalse(hasattr(windll, 'test'))
124+
125+
@unittest.skipUnless(os.name == "nt",
126+
'test specific to Windows')
127+
def test_load_dll_with_flags(self):
128+
_sqlite3 = import_helper.import_module("_sqlite3")
129+
src = _sqlite3.__file__
130+
if src.lower().endswith("_d.pyd"):
131+
ext = "_d.dll"
132+
else:
133+
ext = ".dll"
134+
135+
with os_helper.temp_dir() as tmp:
136+
# We copy two files and load _sqlite3.dll (formerly .pyd),
137+
# which has a dependency on sqlite3.dll. Then we test
138+
# loading it in subprocesses to avoid it starting in memory
139+
# for each test.
140+
target = os.path.join(tmp, "_sqlite3.dll")
141+
shutil.copy(src, target)
142+
shutil.copy(os.path.join(os.path.dirname(src), "sqlite3" + ext),
143+
os.path.join(tmp, "sqlite3" + ext))
144+
145+
def should_pass(command):
146+
with self.subTest(command):
147+
subprocess.check_output(
148+
[sys.executable, "-c",
149+
"from ctypes import *; import nt;" + command],
150+
cwd=tmp
151+
)
152+
153+
def should_fail(command):
154+
with self.subTest(command):
155+
with self.assertRaises(subprocess.CalledProcessError):
156+
subprocess.check_output(
157+
[sys.executable, "-c",
158+
"from ctypes import *; import nt;" + command],
159+
cwd=tmp, stderr=subprocess.STDOUT,
160+
)
161+
162+
# Default load should not find this in CWD
163+
should_fail("WinDLL('_sqlite3.dll')")
164+
165+
# Relative path (but not just filename) should succeed
166+
should_pass("WinDLL('./_sqlite3.dll')")
167+
168+
# Insecure load flags should succeed
169+
# Clear the DLL directory to avoid safe search settings propagating
170+
should_pass("windll.kernel32.SetDllDirectoryW(None); WinDLL('_sqlite3.dll', winmode=0)")
171+
172+
# Full path load without DLL_LOAD_DIR shouldn't find dependency
173+
should_fail("WinDLL(nt._getfullpathname('_sqlite3.dll'), " +
174+
"winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32)")
175+
176+
# Full path load with DLL_LOAD_DIR should succeed
177+
should_pass("WinDLL(nt._getfullpathname('_sqlite3.dll'), " +
178+
"winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32|" +
179+
"nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR)")
180+
181+
# User-specified directory should succeed
182+
should_pass("import os; p = os.add_dll_directory(os.getcwd());" +
183+
"WinDLL('_sqlite3.dll'); p.close()")
184+
185+
186+
187+
if __name__ == "__main__":
188+
unittest.main()

Lib/random.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ def lognormvariate(self, mu, sigma):
577577
"""
578578
return _exp(self.normalvariate(mu, sigma))
579579

580-
def expovariate(self, lambd):
580+
def expovariate(self, lambd=1.0):
581581
"""Exponential distribution.
582582
583583
lambd is 1.0 divided by the desired mean. It should be

Lib/test/test_random.py

+1
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,7 @@ def test_zeroinputs(self):
988988
g.random = x[:].pop; g.uniform(1,10)
989989
g.random = x[:].pop; g.paretovariate(1.0)
990990
g.random = x[:].pop; g.expovariate(1.0)
991+
g.random = x[:].pop; g.expovariate()
991992
g.random = x[:].pop; g.weibullvariate(1.0, 1.0)
992993
g.random = x[:].pop; g.vonmisesvariate(1.0, 1.0)
993994
g.random = x[:].pop; g.normalvariate(0.0, 1.0)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Set a default value of 1.0 for the ``lambd`` parameter in
2+
random.expovariate().
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``hasattr(ctypes.windll, 'nonexistant')`` now returns ``False`` instead of raising :exc:`OSError`.
2+
3+

0 commit comments

Comments
 (0)