diff --git a/Lib/imp.py b/Lib/imp.py index 2cd64407e86..f6fff442013 100644 --- a/Lib/imp.py +++ b/Lib/imp.py @@ -334,6 +334,12 @@ if create_dynamic: """ import importlib.machinery loader = importlib.machinery.ExtensionFileLoader(name, path) - return loader.load_module() + + # Issue #24748: Skip the sys.modules check in _load_module_shim; + # always load new extension + spec = importlib.machinery.ModuleSpec( + name=name, loader=loader, origin=path) + return _load(spec) + else: load_dynamic = None diff --git a/Lib/test/imp_dummy.py b/Lib/test/imp_dummy.py new file mode 100644 index 00000000000..2a4deb49547 --- /dev/null +++ b/Lib/test/imp_dummy.py @@ -0,0 +1,3 @@ +# Fodder for test of issue24748 in test_imp + +dummy_name = True diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py index 47bf1de92a4..ee9ee1ad8c3 100644 --- a/Lib/test/test_imp.py +++ b/Lib/test/test_imp.py @@ -3,6 +3,7 @@ try: except ImportError: _thread = None import importlib +import importlib.util import os import os.path import shutil @@ -275,6 +276,29 @@ class ImportTests(unittest.TestCase): self.skipTest("found module doesn't appear to be a C extension") imp.load_module(name, None, *found[1:]) + @requires_load_dynamic + def test_issue24748_load_module_skips_sys_modules_check(self): + name = 'test.imp_dummy' + try: + del sys.modules[name] + except KeyError: + pass + try: + module = importlib.import_module(name) + spec = importlib.util.find_spec('_testmultiphase') + module = imp.load_dynamic(name, spec.origin) + self.assertEqual(module.__name__, name) + self.assertEqual(module.__spec__.name, name) + self.assertEqual(module.__spec__.origin, spec.origin) + self.assertRaises(AttributeError, getattr, module, 'dummy_name') + self.assertEqual(module.int_const, 1969) + self.assertIs(sys.modules[name], module) + finally: + try: + del sys.modules[name] + except KeyError: + pass + @unittest.skipIf(sys.dont_write_bytecode, "test meaningful only when writing bytecode") def test_bug7732(self): diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 6334e022e00..6bcd2124fa4 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -174,6 +174,19 @@ class TimeTestCase(unittest.TestCase): def test_strftime_bounding_check(self): self._bounds_checking(lambda tup: time.strftime('', tup)) + def test_strftime_format_check(self): + # Test that strftime does not crash on invalid format strings + # that may trigger a buffer overread. When not triggered, + # strftime may succeed or raise ValueError depending on + # the platform. + for x in [ '', 'A', '%A', '%AA' ]: + for y in range(0x0, 0x10): + for z in [ '%', 'A%', 'AA%', '%A%', 'A%A%', '%#' ]: + try: + time.strftime(x * y + z) + except ValueError: + pass + def test_default_values_for_zero(self): # Make sure that using all zeros uses the proper default # values. No test for daylight savings since strftime() does diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings/__init__.py similarity index 97% rename from Lib/test/test_warnings.py rename to Lib/test/test_warnings/__init__.py index 03d9958f697..991a249f4a4 100644 --- a/Lib/test/test_warnings.py +++ b/Lib/test/test_warnings/__init__.py @@ -7,7 +7,7 @@ import unittest from test import support from test.support.script_helper import assert_python_ok, assert_python_failure -from test import warning_tests +from test.test_warnings.data import stacklevel as warning_tests import warnings as original_warnings @@ -44,7 +44,6 @@ class BaseTest: """Basic bookkeeping required for testing.""" def setUp(self): - self.old_unittest_module = unittest.case.warnings # The __warningregistry__ needs to be in a pristine state for tests # to work properly. if '__warningregistry__' in globals(): @@ -56,15 +55,10 @@ class BaseTest: # The 'warnings' module must be explicitly set so that the proper # interaction between _warnings and 'warnings' can be controlled. sys.modules['warnings'] = self.module - # Ensure that unittest.TestCase.assertWarns() uses the same warnings - # module than warnings.catch_warnings(). Otherwise, - # warnings.catch_warnings() will be unable to remove the added filter. - unittest.case.warnings = self.module super(BaseTest, self).setUp() def tearDown(self): sys.modules['warnings'] = original_warnings - unittest.case.warnings = self.old_unittest_module super(BaseTest, self).tearDown() class PublicAPITests(BaseTest): @@ -194,11 +188,11 @@ class FilterTests(BaseTest): self.module.resetwarnings() self.module.filterwarnings("once", category=UserWarning) message = UserWarning("FilterTests.test_once") - self.module.warn_explicit(message, UserWarning, "test_warnings.py", + self.module.warn_explicit(message, UserWarning, "__init__.py", 42) self.assertEqual(w[-1].message, message) del w[:] - self.module.warn_explicit(message, UserWarning, "test_warnings.py", + self.module.warn_explicit(message, UserWarning, "__init__.py", 13) self.assertEqual(len(w), 0) self.module.warn_explicit(message, UserWarning, "test_warnings2.py", @@ -304,10 +298,10 @@ class WarnTests(BaseTest): module=self.module) as w: warning_tests.inner("spam1") self.assertEqual(os.path.basename(w[-1].filename), - "warning_tests.py") + "stacklevel.py") warning_tests.outer("spam2") self.assertEqual(os.path.basename(w[-1].filename), - "warning_tests.py") + "stacklevel.py") def test_stacklevel(self): # Test stacklevel argument @@ -317,25 +311,36 @@ class WarnTests(BaseTest): module=self.module) as w: warning_tests.inner("spam3", stacklevel=1) self.assertEqual(os.path.basename(w[-1].filename), - "warning_tests.py") + "stacklevel.py") warning_tests.outer("spam4", stacklevel=1) self.assertEqual(os.path.basename(w[-1].filename), - "warning_tests.py") + "stacklevel.py") warning_tests.inner("spam5", stacklevel=2) self.assertEqual(os.path.basename(w[-1].filename), - "test_warnings.py") + "__init__.py") warning_tests.outer("spam6", stacklevel=2) self.assertEqual(os.path.basename(w[-1].filename), - "warning_tests.py") + "stacklevel.py") warning_tests.outer("spam6.5", stacklevel=3) self.assertEqual(os.path.basename(w[-1].filename), - "test_warnings.py") + "__init__.py") warning_tests.inner("spam7", stacklevel=9999) self.assertEqual(os.path.basename(w[-1].filename), "sys") + def test_stacklevel_import(self): + # Issue #24305: With stacklevel=2, module-level warnings should work. + support.unload('test.test_warnings.data.import_warning') + with warnings_state(self.module): + with original_warnings.catch_warnings(record=True, + module=self.module) as w: + self.module.simplefilter('always') + import test.test_warnings.data.import_warning + self.assertEqual(len(w), 1) + self.assertEqual(w[0].filename, __file__) + def test_missing_filename_not_main(self): # If __file__ is not specified and __main__ is not the module name, # then __file__ should be set to the module name. diff --git a/Lib/test/test_warnings/__main__.py b/Lib/test/test_warnings/__main__.py new file mode 100644 index 00000000000..44e52ec0704 --- /dev/null +++ b/Lib/test/test_warnings/__main__.py @@ -0,0 +1,3 @@ +import unittest + +unittest.main('test.test_warnings') diff --git a/Lib/test/test_warnings/data/import_warning.py b/Lib/test/test_warnings/data/import_warning.py new file mode 100644 index 00000000000..d6ea2ce1046 --- /dev/null +++ b/Lib/test/test_warnings/data/import_warning.py @@ -0,0 +1,3 @@ +import warnings + +warnings.warn('module-level warning', DeprecationWarning, stacklevel=2) \ No newline at end of file diff --git a/Lib/test/warning_tests.py b/Lib/test/test_warnings/data/stacklevel.py similarity index 100% rename from Lib/test/warning_tests.py rename to Lib/test/test_warnings/data/stacklevel.py diff --git a/Lib/warnings.py b/Lib/warnings.py index 16246b43658..1d4fb208f83 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -160,6 +160,20 @@ def _getcategory(category): return cat +def _is_internal_frame(frame): + """Signal whether the frame is an internal CPython implementation detail.""" + filename = frame.f_code.co_filename + return 'importlib' in filename and '_bootstrap' in filename + + +def _next_external_frame(frame): + """Find the next frame that doesn't involve CPython internals.""" + frame = frame.f_back + while frame is not None and _is_internal_frame(frame): + frame = frame.f_back + return frame + + # Code typically replaced by _warnings def warn(message, category=None, stacklevel=1): """Issue a warning, or maybe ignore it or raise an exception.""" @@ -174,13 +188,23 @@ def warn(message, category=None, stacklevel=1): "not '{:s}'".format(type(category).__name__)) # Get context information try: - caller = sys._getframe(stacklevel) + if stacklevel <= 1 or _is_internal_frame(sys._getframe(1)): + # If frame is too small to care or if the warning originated in + # internal code, then do not try to hide any frames. + frame = sys._getframe(stacklevel) + else: + frame = sys._getframe(1) + # Look for one frame less since the above line starts us off. + for x in range(stacklevel-1): + frame = _next_external_frame(frame) + if frame is None: + raise ValueError except ValueError: globals = sys.__dict__ lineno = 1 else: - globals = caller.f_globals - lineno = caller.f_lineno + globals = frame.f_globals + lineno = frame.f_lineno if '__name__' in globals: module = globals['__name__'] else: @@ -374,7 +398,6 @@ try: defaultaction = _defaultaction onceregistry = _onceregistry _warnings_defaults = True - except ImportError: filters = [] defaultaction = "default" diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 6deed922306..845f1d004c0 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -495,23 +495,10 @@ if os.environ.get("TERM"): # if sys.platform[:3] == "win": - class WindowsDefault(BaseBrowser): - # Windows Default opening arguments. - - cmd = "start" - newwindow = "" - newtab = "" - def open(self, url, new=0, autoraise=True): - # Format the command for optional arguments and add the url. - if new == 1: - self.cmd += " " + self.newwindow - elif new == 2: - self.cmd += " " + self.newtab - self.cmd += " " + url try: - subprocess.call(self.cmd, shell=True) + os.startfile(url) except OSError: # [Error 22] No application is associated with the specified # file for this operation: '' @@ -519,108 +506,19 @@ if sys.platform[:3] == "win": else: return True - - # Windows Sub-Classes for commonly used browsers. - - class InternetExplorer(WindowsDefault): - """Launcher class for Internet Explorer browser""" - - cmd = "start iexplore.exe" - newwindow = "" - newtab = "" - - - class WinChrome(WindowsDefault): - """Launcher class for windows specific Google Chrome browser""" - - cmd = "start chrome.exe" - newwindow = "-new-window" - newtab = "-new-tab" - - - class WinFirefox(WindowsDefault): - """Launcher class for windows specific Firefox browser""" - - cmd = "start firefox.exe" - newwindow = "-new-window" - newtab = "-new-tab" - - - class WinOpera(WindowsDefault): - """Launcher class for windows specific Opera browser""" - - cmd = "start opera" - newwindow = "" - newtab = "" - - - class WinSeaMonkey(WindowsDefault): - """Launcher class for windows specific SeaMonkey browser""" - - cmd = "start seamonkey" - newwinow = "" - newtab = "" - - _tryorder = [] _browsers = {} - # First try to use the default Windows browser. + # First try to use the default Windows browser register("windows-default", WindowsDefault) - def find_windows_browsers(): - """ Access the windows registry to determine - what browsers are on the system. - """ - - import winreg - HKLM = winreg.HKEY_LOCAL_MACHINE - subkey = r'Software\Clients\StartMenuInternet' - read32 = winreg.KEY_READ | winreg.KEY_WOW64_32KEY - read64 = winreg.KEY_READ | winreg.KEY_WOW64_64KEY - key32 = winreg.OpenKey(HKLM, subkey, access=read32) - key64 = winreg.OpenKey(HKLM, subkey, access=read64) - - # Return a list of browsers found in the registry - # Check if there are any different browsers in the - # 32 bit location instead of the 64 bit location. - browsers = [] - i = 0 - while True: - try: - browsers.append(winreg.EnumKey(key32, i)) - except EnvironmentError: - break - i += 1 - - i = 0 - while True: - try: - browsers.append(winreg.EnumKey(key64, i)) - except EnvironmentError: - break - i += 1 - - winreg.CloseKey(key32) - winreg.CloseKey(key64) - - return browsers - - # Detect some common windows browsers - for browser in find_windows_browsers(): - browser = browser.lower() - if "iexplore" in browser: - register("iexplore", None, InternetExplorer("iexplore")) - elif "chrome" in browser: - register("chrome", None, WinChrome("chrome")) - elif "firefox" in browser: - register("firefox", None, WinFirefox("firefox")) - elif "opera" in browser: - register("opera", None, WinOpera("opera")) - elif "seamonkey" in browser: - register("seamonkey", None, WinSeaMonkey("seamonkey")) - else: - register(browser, None, WindowsDefault(browser)) + # Detect some common Windows browsers, fallback to IE + iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"), + "Internet Explorer\\IEXPLORE.EXE") + for browser in ("firefox", "firebird", "seamonkey", "mozilla", + "netscape", "opera", iexplore): + if shutil.which(browser): + register(browser, None, BackgroundBrowser(browser)) # # Platform support for MacOS diff --git a/Misc/NEWS b/Misc/NEWS index 64c9af75bcb..8a09be790a9 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -1,4 +1,4 @@ -+++++++++++ ++++++++++++ Python News +++++++++++ @@ -88,6 +88,9 @@ Release date: 2015-09-06 Core and Builtins ----------------- +- Issue #24305: Prevent import subsystem stack frames from being counted + by the warnings.warn(stacklevel=) parameter. + - Issue #24912: Prevent __class__ assignment to immutable built-in objects. - Issue #24975: Fix AST compilation for PEP 448 syntax. @@ -95,9 +98,15 @@ Core and Builtins Library ------- +- Issue #24917: time_strftime() buffer over-read. - Issue #23144: Make sure that HTMLParser.feed() returns all the data, even when convert_charrefs is True. +- Issue #24748: To resolve a compatibility problem found with py2exe and + pywin32, imp.load_dynamic() once again ignores previously loaded modules + to support Python modules replacing themselves with extension modules. + Patch by Petr Viktorin. + - Issue #24635: Fixed a bug in typing.py where isinstance([], typing.Iterable) would return True once, then False on subsequent calls. @@ -386,9 +395,6 @@ Library - Issue #14373: C implementation of functools.lru_cache() now can be used with methods. -- Issue #8232: webbrowser support incomplete on Windows. Patch by Brandon - Milam - - Issue #24347: Set KeyError if PyDict_GetItemWithError returns NULL. - Issue #24348: Drop superfluous incref/decref. diff --git a/Modules/_testmultiphase.c b/Modules/_testmultiphase.c index 2919687e112..2005205d335 100644 --- a/Modules/_testmultiphase.c +++ b/Modules/_testmultiphase.c @@ -582,3 +582,13 @@ PyInit__testmultiphase_exec_unreported_exception(PyObject *spec) { return PyModuleDef_Init(&def_exec_unreported_exception); } + +/*** Helper for imp test ***/ + +static PyModuleDef imp_dummy_def = TEST_MODULE_DEF("imp_dummy", main_slots, testexport_methods); + +PyMODINIT_FUNC +PyInit_imp_dummy(PyObject *spec) +{ + return PyModuleDef_Init(&imp_dummy_def); +} diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 197d2c0b8dd..eca67d9e286 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -610,14 +610,15 @@ time_strftime(PyObject *self, PyObject *args) #if defined(MS_WINDOWS) && !defined(HAVE_WCSFTIME) /* check that the format string contains only valid directives */ - for(outbuf = strchr(fmt, '%'); + for (outbuf = strchr(fmt, '%'); outbuf != NULL; outbuf = strchr(outbuf+2, '%')) { - if (outbuf[1]=='#') + if (outbuf[1] == '#') ++outbuf; /* not documented by python, */ - if ((outbuf[1] == 'y') && buf.tm_year < 0) - { + if (outbuf[1] == '\0') + break; + if ((outbuf[1] == 'y') && buf.tm_year < 0) { PyErr_SetString(PyExc_ValueError, "format %y requires year >= 1900 on Windows"); Py_DECREF(format); @@ -625,10 +626,12 @@ time_strftime(PyObject *self, PyObject *args) } } #elif (defined(_AIX) || defined(sun)) && defined(HAVE_WCSFTIME) - for(outbuf = wcschr(fmt, '%'); + for (outbuf = wcschr(fmt, '%'); outbuf != NULL; outbuf = wcschr(outbuf+2, '%')) { + if (outbuf[1] == L'\0') + break; /* Issue #19634: On AIX, wcsftime("y", (1899, 1, 1, 0, 0, 0, 0, 0, 0)) returns "0/" instead of "99" */ if (outbuf[1] == L'y' && buf.tm_year < 0) { @@ -659,7 +662,8 @@ time_strftime(PyObject *self, PyObject *args) #if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) err = errno; #endif - if (buflen > 0 || i >= 256 * fmtlen) { + if (buflen > 0 || fmtlen == 0 || + (fmtlen > 4 && i >= 256 * fmtlen)) { /* If the buffer is 256 times as long as the format, it's probably not failing for lack of room! More likely, the format yields an empty result, diff --git a/Python/_warnings.c b/Python/_warnings.c index 22f617a9ffb..9ca83145c94 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -513,6 +513,64 @@ warn_explicit(PyObject *category, PyObject *message, return result; /* Py_None or NULL. */ } +static int +is_internal_frame(PyFrameObject *frame) +{ + static PyObject *importlib_string = NULL; + static PyObject *bootstrap_string = NULL; + PyObject *filename; + int contains; + + if (importlib_string == NULL) { + importlib_string = PyUnicode_FromString("importlib"); + if (importlib_string == NULL) { + return 0; + } + + bootstrap_string = PyUnicode_FromString("_bootstrap"); + if (bootstrap_string == NULL) { + Py_DECREF(importlib_string); + return 0; + } + Py_INCREF(importlib_string); + Py_INCREF(bootstrap_string); + } + + if (frame == NULL || frame->f_code == NULL || + frame->f_code->co_filename == NULL) { + return 0; + } + filename = frame->f_code->co_filename; + if (!PyUnicode_Check(filename)) { + return 0; + } + contains = PyUnicode_Contains(filename, importlib_string); + if (contains < 0) { + return 0; + } + else if (contains > 0) { + contains = PyUnicode_Contains(filename, bootstrap_string); + if (contains < 0) { + return 0; + } + else if (contains > 0) { + return 1; + } + } + + return 0; +} + +static PyFrameObject * +next_external_frame(PyFrameObject *frame) +{ + do { + frame = frame->f_back; + } while (frame != NULL && is_internal_frame(frame)); + + return frame; +} + /* filename, module, and registry are new refs, globals is borrowed */ /* Returns 0 on error (no new refs), 1 on success */ static int @@ -523,8 +581,18 @@ setup_context(Py_ssize_t stack_level, PyObject **filename, int *lineno, /* Setup globals and lineno. */ PyFrameObject *f = PyThreadState_GET()->frame; - while (--stack_level > 0 && f != NULL) - f = f->f_back; + // Stack level comparisons to Python code is off by one as there is no + // warnings-related stack level to avoid. + if (stack_level <= 0 || is_internal_frame(f)) { + while (--stack_level > 0 && f != NULL) { + f = f->f_back; + } + } + else { + while (--stack_level > 0 && f != NULL) { + f = next_external_frame(f); + } + } if (f == NULL) { globals = PyThreadState_Get()->interp->sysdict;