diff --git a/Lib/site.py b/Lib/site.py index 0cc22eba2e5..a0489fa461c 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -55,6 +55,7 @@ ImportError exception, it is silently ignored. import sys import os import builtins +import traceback # Prefixes for site-packages; add additional prefixes like /usr/local here PREFIXES = [sys.prefix, sys.exec_prefix] @@ -146,17 +147,26 @@ def addpackage(sitedir, name, known_paths): except IOError: return with f: - for line in f: + for n, line in enumerate(f): if line.startswith("#"): continue - if line.startswith(("import ", "import\t")): - exec(line) - continue - line = line.rstrip() - dir, dircase = makepath(sitedir, line) - if not dircase in known_paths and os.path.exists(dir): - sys.path.append(dir) - known_paths.add(dircase) + try: + if line.startswith(("import ", "import\t")): + exec(line) + continue + line = line.rstrip() + dir, dircase = makepath(sitedir, line) + if not dircase in known_paths and os.path.exists(dir): + sys.path.append(dir) + known_paths.add(dircase) + except Exception as err: + print("Error processing line {:d} of {}:\n".format(n+1, fullname), + file=sys.stderr) + for record in traceback.format_exception(*sys.exc_info()): + for line in record.splitlines(): + print(' '+line, file=sys.stderr) + print("\nRemainder of file ignored", file=sys.stderr) + break if reset: known_paths = None return known_paths diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index 7ed0ee24d48..343f8ef64be 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -6,6 +6,7 @@ executing have not been removed. """ import unittest from test.support import run_unittest, TESTFN, EnvironmentVarGuard +from test.support import captured_output import builtins import os import sys @@ -83,6 +84,53 @@ class HelperFunctionsTests(unittest.TestCase): finally: pth_file.cleanup() + def make_pth(self, contents, pth_dir='.', pth_name=TESTFN): + # Create a .pth file and return its (abspath, basename). + pth_dir = os.path.abspath(pth_dir) + pth_basename = pth_name + '.pth' + pth_fn = os.path.join(pth_dir, pth_basename) + pth_file = open(pth_fn, 'w', encoding='utf-8') + self.addCleanup(lambda: os.remove(pth_fn)) + pth_file.write(contents) + pth_file.close() + return pth_dir, pth_basename + + def test_addpackage_import_bad_syntax(self): + # Issue 10642 + pth_dir, pth_fn = self.make_pth("import bad)syntax\n") + with captured_output("stderr") as err_out: + site.addpackage(pth_dir, pth_fn, set()) + self.assertRegexpMatches(err_out.getvalue(), "line 1") + self.assertRegexpMatches(err_out.getvalue(), os.path.join(pth_dir, pth_fn)) + # XXX: the previous two should be independent checks so that the + # order doesn't matter. The next three could be a single check + # but my regex foo isn't good enough to write it. + self.assertRegexpMatches(err_out.getvalue(), 'Traceback') + self.assertRegexpMatches(err_out.getvalue(), r'import bad\)syntax') + self.assertRegexpMatches(err_out.getvalue(), 'SyntaxError') + + def test_addpackage_import_bad_exec(self): + # Issue 10642 + pth_dir, pth_fn = self.make_pth("randompath\nimport nosuchmodule\n") + with captured_output("stderr") as err_out: + site.addpackage(pth_dir, pth_fn, set()) + self.assertRegexpMatches(err_out.getvalue(), "line 2") + self.assertRegexpMatches(err_out.getvalue(), os.path.join(pth_dir, pth_fn)) + # XXX: ditto previous XXX comment. + self.assertRegexpMatches(err_out.getvalue(), 'Traceback') + self.assertRegexpMatches(err_out.getvalue(), 'ImportError') + + def test_addpackage_import_bad_pth_file(self): + # Issue 5258 + pth_dir, pth_fn = self.make_pth("abc\x00def\n") + with captured_output("stderr") as err_out: + site.addpackage(pth_dir, pth_fn, set()) + self.assertRegexpMatches(err_out.getvalue(), "line 1") + self.assertRegexpMatches(err_out.getvalue(), os.path.join(pth_dir, pth_fn)) + # XXX: ditto previous XXX comment. + self.assertRegexpMatches(err_out.getvalue(), 'Traceback') + self.assertRegexpMatches(err_out.getvalue(), 'TypeError') + def test_addsitedir(self): # Same tests for test_addpackage since addsitedir() essentially just # calls addpackage() for every .pth file in the directory diff --git a/Misc/NEWS b/Misc/NEWS index bd43ee770a0..d0dd3a19c60 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -24,6 +24,10 @@ Core and Builtins Library ------- +- Issue #5258/#10642: if site.py encounters a .pth file that generates an error, + it now prints the filename, line number, and traceback to stderr and skips + the rest of that individual file, instead of stopping processing entirely. + - Issue #4871: The zipfile module now gives a more useful error message if an attempt is made to use a string to specify the archive password.