Skip to content

Commit ccb0442

Browse files
authored
bpo-32043: New "developer mode": "-X dev" option (#4413)
Add a new "developer mode": new "-X dev" command line option to enable debug checks at runtime. Changes: * Add unit tests for -X dev * test_cmd_line: replace test.support with support. * Fix _PyRuntimeState_Fini(): Use the same memory allocator than _PyRuntimeState_Init(). * Fix _PyMem_GetDefaultRawAllocator()
1 parent 05cb728 commit ccb0442

File tree

7 files changed

+110
-25
lines changed

7 files changed

+110
-25
lines changed

Doc/using/cmdline.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,17 @@ Miscellaneous options
413413
nested imports). Note that its output may be broken in multi-threaded
414414
application. Typical usage is ``python3 -X importtime -c 'import
415415
asyncio'``. See also :envvar:`PYTHONPROFILEIMPORTTIME`.
416+
* ``-X dev`` enables the "developer mode": enable debug checks at runtime.
417+
In short, ``python3 -X dev ...`` behaves as ``PYTHONMALLOC=debug python3
418+
-W default -X faulthandler ...``, except that the :envvar:`PYTHONMALLOC`
419+
environment variable is not set in practice. Developer mode:
420+
421+
* Add ``default`` warnings option. For example, display
422+
:exc:`DeprecationWarning` and :exc:`ResourceWarning` warnings.
423+
* Install debug hooks on memory allocators as if :envvar:`PYTHONMALLOC`
424+
is set to ``debug``.
425+
* Enable the :mod:`faulthandler` module to dump the Python traceback
426+
on a crash.
416427

417428
It also allows passing arbitrary values and retrieving them through the
418429
:data:`sys._xoptions` dictionary.
@@ -430,7 +441,8 @@ Miscellaneous options
430441
The ``-X showalloccount`` option.
431442

432443
.. versionadded:: 3.7
433-
The ``-X importtime`` and :envvar:`PYTHONPROFILEIMPORTTIME` options.
444+
The ``-X importtime``, ``-X dev`` and :envvar:`PYTHONPROFILEIMPORTTIME`
445+
options.
434446

435447

436448
Options you shouldn't use

Doc/whatsnew/3.7.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,19 @@ resolution on Linux and Windows.
185185
PEP written and implemented by Victor Stinner
186186

187187

188+
New Developer Mode: -X dev
189+
--------------------------
190+
191+
Add a new "developer mode": ``-X dev`` command line option to enable debug
192+
checks at runtime.
193+
194+
In short, ``python3 -X dev ...`` behaves as ``PYTHONMALLOC=debug python3 -W
195+
default -X faulthandler ...``, except that the PYTHONMALLOC environment
196+
variable is not set in practice.
197+
198+
See :option:`-X` ``dev`` for the details.
199+
200+
188201
Other Language Changes
189202
======================
190203

Lib/test/test_cmd_line.py

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
# Most tests are executed with environment variables ignored
33
# See test_cmd_line_script.py for testing of script execution
44

5-
import test.support, unittest
65
import os
7-
import sys
86
import subprocess
7+
import sys
98
import tempfile
10-
from test.support import script_helper, is_android
9+
import unittest
10+
from test import support
1111
from test.support.script_helper import (
12-
spawn_python, kill_python, assert_python_ok, assert_python_failure
12+
spawn_python, kill_python, assert_python_ok, assert_python_failure,
13+
interpreter_requires_environment
1314
)
1415

1516
# XXX (ncoghlan): Move to script_helper and make consistent with run_python
@@ -132,11 +133,11 @@ def test_run_code(self):
132133
# All good if execution is successful
133134
assert_python_ok('-c', 'pass')
134135

135-
@unittest.skipUnless(test.support.FS_NONASCII, 'need support.FS_NONASCII')
136+
@unittest.skipUnless(support.FS_NONASCII, 'need support.FS_NONASCII')
136137
def test_non_ascii(self):
137138
# Test handling of non-ascii data
138139
command = ("assert(ord(%r) == %s)"
139-
% (test.support.FS_NONASCII, ord(test.support.FS_NONASCII)))
140+
% (support.FS_NONASCII, ord(support.FS_NONASCII)))
140141
assert_python_ok('-c', command)
141142

142143
# On Windows, pass bytes to subprocess doesn't test how Python decodes the
@@ -179,7 +180,7 @@ def test_undecodable_code(self):
179180
raise AssertionError("%a doesn't start with %a" % (stdout, pattern))
180181

181182
@unittest.skipUnless((sys.platform == 'darwin' or
182-
is_android), 'test specific to Mac OS X and Android')
183+
support.is_android), 'test specific to Mac OS X and Android')
183184
def test_osx_android_utf8(self):
184185
def check_output(text):
185186
decoded = text.decode('utf-8', 'surrogateescape')
@@ -385,7 +386,7 @@ def preexec():
385386
stderr=subprocess.PIPE,
386387
preexec_fn=preexec)
387388
out, err = p.communicate()
388-
self.assertEqual(test.support.strip_python_stderr(err), b'')
389+
self.assertEqual(support.strip_python_stderr(err), b'')
389390
self.assertEqual(p.returncode, 42)
390391

391392
def test_no_stdin(self):
@@ -433,8 +434,8 @@ def test_del___main__(self):
433434
# Issue #15001: PyRun_SimpleFileExFlags() did crash because it kept a
434435
# borrowed reference to the dict of __main__ module and later modify
435436
# the dict whereas the module was destroyed
436-
filename = test.support.TESTFN
437-
self.addCleanup(test.support.unlink, filename)
437+
filename = support.TESTFN
438+
self.addCleanup(support.unlink, filename)
438439
with open(filename, "w") as script:
439440
print("import sys", file=script)
440441
print("del sys.modules['__main__']", file=script)
@@ -458,7 +459,7 @@ def test_unknown_options(self):
458459
self.assertEqual(err.splitlines().count(b'Unknown option: -a'), 1)
459460
self.assertEqual(b'', out)
460461

461-
@unittest.skipIf(script_helper.interpreter_requires_environment(),
462+
@unittest.skipIf(interpreter_requires_environment(),
462463
'Cannot run -I tests when PYTHON env vars are required.')
463464
def test_isolatedmode(self):
464465
self.verify_valid_flag('-I')
@@ -469,7 +470,7 @@ def test_isolatedmode(self):
469470
# dummyvar to prevent extraneous -E
470471
dummyvar="")
471472
self.assertEqual(out.strip(), b'1 1 1')
472-
with test.support.temp_cwd() as tmpdir:
473+
with support.temp_cwd() as tmpdir:
473474
fake = os.path.join(tmpdir, "uuid.py")
474475
main = os.path.join(tmpdir, "main.py")
475476
with open(fake, "w") as f:
@@ -506,6 +507,46 @@ def test_sys_flags_set(self):
506507
with self.subTest(envar_value=value):
507508
assert_python_ok('-c', code, **env_vars)
508509

510+
def run_xdev(self, code, check_exitcode=True):
511+
env = dict(os.environ)
512+
env.pop('PYTHONWARNINGS', None)
513+
# Force malloc() to disable the debug hooks which are enabled
514+
# by default for Python compiled in debug mode
515+
env['PYTHONMALLOC'] = 'malloc'
516+
517+
args = (sys.executable, '-X', 'dev', '-c', code)
518+
proc = subprocess.run(args,
519+
stdout=subprocess.PIPE,
520+
stderr=subprocess.STDOUT,
521+
universal_newlines=True,
522+
env=env)
523+
if check_exitcode:
524+
self.assertEqual(proc.returncode, 0, proc)
525+
return proc.stdout.rstrip()
526+
527+
def test_xdev(self):
528+
out = self.run_xdev("import sys; print(sys.warnoptions)")
529+
self.assertEqual(out, "['default']")
530+
531+
try:
532+
import _testcapi
533+
except ImportError:
534+
pass
535+
else:
536+
code = "import _testcapi; _testcapi.pymem_api_misuse()"
537+
with support.SuppressCrashReport():
538+
out = self.run_xdev(code, check_exitcode=False)
539+
self.assertIn("Debug memory block at address p=", out)
540+
541+
try:
542+
import faulthandler
543+
except ImportError:
544+
pass
545+
else:
546+
code = "import faulthandler; print(faulthandler.is_enabled())"
547+
out = self.run_xdev(code)
548+
self.assertEqual(out, "True")
549+
509550
class IgnoreEnvironmentTest(unittest.TestCase):
510551

511552
def run_ignoring_vars(self, predicate, **env_vars):
@@ -541,8 +582,8 @@ def test_sys_flags_not_set(self):
541582

542583

543584
def test_main():
544-
test.support.run_unittest(CmdLineTest, IgnoreEnvironmentTest)
545-
test.support.reap_children()
585+
support.run_unittest(CmdLineTest, IgnoreEnvironmentTest)
586+
support.reap_children()
546587

547588
if __name__ == "__main__":
548589
test_main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add a new "developer mode": new "-X dev" command line option to enable debug
2+
checks at runtime.

Modules/main.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,16 @@ pymain_parse_envvars(_PyMain *pymain)
13961396
if (pymain_init_tracemalloc(pymain) < 0) {
13971397
return -1;
13981398
}
1399+
if (pymain_get_xoption(pymain, L"dev")) {
1400+
/* "python3 -X dev ..." behaves
1401+
as "PYTHONMALLOC=debug python3 -Wd -X faulthandler ..." */
1402+
core_config->allocator = "debug";
1403+
if (pymain_optlist_append(pymain, &pymain->cmdline.warning_options,
1404+
L"default") < 0) {
1405+
return -1;
1406+
}
1407+
core_config->faulthandler = 1;
1408+
}
13991409
return 0;
14001410
}
14011411

Objects/obmalloc.c

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,14 @@ static struct {
190190
void
191191
_PyMem_GetDefaultRawAllocator(PyMemAllocatorEx *alloc_p)
192192
{
193-
PyMemAllocatorEx alloc = {NULL, PYRAW_FUNCS};
194-
*alloc_p = alloc;
193+
PyMemAllocatorEx pymem_raw = {
194+
#ifdef Py_DEBUG
195+
&_PyMem_Debug.raw, PYRAWDBG_FUNCS
196+
#else
197+
NULL, PYRAW_FUNCS
198+
#endif
199+
};
200+
*alloc_p = pymem_raw;
195201
}
196202

197203
int
@@ -274,13 +280,6 @@ _PyObject_Initialize(struct _pyobj_runtime_state *state)
274280
void
275281
_PyMem_Initialize(struct _pymem_runtime_state *state)
276282
{
277-
PyMemAllocatorEx pymem_raw = {
278-
#ifdef Py_DEBUG
279-
&_PyMem_Debug.raw, PYRAWDBG_FUNCS
280-
#else
281-
NULL, PYRAW_FUNCS
282-
#endif
283-
};
284283
PyMemAllocatorEx pymem = {
285284
#ifdef Py_DEBUG
286285
&_PyMem_Debug.mem, PYDBG_FUNCS
@@ -296,7 +295,7 @@ _PyMem_Initialize(struct _pymem_runtime_state *state)
296295
#endif
297296
};
298297

299-
state->allocators.raw = pymem_raw;
298+
_PyMem_GetDefaultRawAllocator(&state->allocators.raw);
300299
state->allocators.mem = pymem;
301300
state->allocators.obj = pyobject;
302301

Python/pystate.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,18 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
6464
void
6565
_PyRuntimeState_Fini(_PyRuntimeState *runtime)
6666
{
67+
/* Use the same memory allocator than _PyRuntimeState_Init() */
68+
PyMemAllocatorEx old_alloc, raw_alloc;
69+
PyMem_GetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
70+
_PyMem_GetDefaultRawAllocator(&raw_alloc);
71+
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &raw_alloc);
72+
6773
if (runtime->interpreters.mutex != NULL) {
6874
PyThread_free_lock(runtime->interpreters.mutex);
6975
runtime->interpreters.mutex = NULL;
7076
}
77+
78+
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
7179
}
7280

7381
#define HEAD_LOCK() PyThread_acquire_lock(_PyRuntime.interpreters.mutex, \

0 commit comments

Comments
 (0)