Skip to content

Commit 2e45bf5

Browse files
committed
Reject a None loop in Server's constructor instead of crashing later
uvloop.loop.Server is constructed internally with Loop(self) in a couple of places, but nothing stopped it from being called directly with None. That's not a realistic usage pattern, but Cython's typed attribute access on self._loop skips the usual None check once it's stored, so calling close() on a Server built this way segfaults deep inside _unref() instead of raising anything. Marking the loop parameter not None makes the constructor itself reject it right away with a clean TypeError, matching how the type is already declared everywhere it's used internally.
1 parent e8efea4 commit 2e45bf5

2 files changed

Lines changed: 32 additions & 1 deletion

File tree

‎tests/test_regr1.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import queue
33
import multiprocessing
44
import signal
5+
import subprocess
6+
import sys
57
import threading
68
import unittest
79

@@ -117,3 +119,32 @@ def test_issue39_regression(self):
117119
finally:
118120
self.running = False
119121
signal.signal(signal.SIGALRM, signal.SIG_IGN)
122+
123+
124+
class TestIssue760Regr(unittest.TestCase):
125+
"""See https://github.com/MagicStack/uvloop/issues/760 for details.
126+
127+
Directly constructing uvloop.loop.Server with a loop of None isn't a
128+
supported usage pattern, but it segfaulted instead of raising a
129+
Python exception, since the None was only checked much later, deep
130+
inside close(). Run the reproducer in a subprocess since a regression
131+
here crashes the whole interpreter rather than raising.
132+
"""
133+
134+
def test_server_with_none_loop_raises_instead_of_crashing(self):
135+
code = (
136+
"from uvloop.loop import Server\n"
137+
"server = Server(None)\n"
138+
"server.close()\n"
139+
)
140+
proc = subprocess.run(
141+
[sys.executable, '-c', code],
142+
stdout=subprocess.PIPE,
143+
stderr=subprocess.PIPE)
144+
145+
self.assertNotEqual(
146+
proc.returncode, -11,
147+
f'process was killed by SIGSEGV; stderr:\n'
148+
f'{proc.stderr.decode()}')
149+
self.assertEqual(proc.returncode, 1)
150+
self.assertIn(b'TypeError', proc.stderr)

‎uvloop/server.pyx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import asyncio
22

33

44
cdef class Server:
5-
def __cinit__(self, Loop loop):
5+
def __cinit__(self, Loop loop not None):
66
self._loop = loop
77
self._servers = []
88
self._waiters = []

0 commit comments

Comments
 (0)