diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 5dc11253e0d5c8..d9e1c0bb3069de 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -5456,5 +5456,17 @@ def test_suggestion_still_works_for_non_lazy_attributes(self): self.assertNotIn(b"BAR_MODULE_LOADED", stdout) +class TestNoCrashInTracebackException(unittest.TestCase): + def test_module_not_found_error_with_bad_name(self): + exc = ModuleNotFoundError(name=NotImplemented) + try: + te = traceback.TracebackException.from_exception(exc) + except Exception as e: + self.fail(f"TracebackException raised unexpected exception: {e!r}") + else: + msg = "".join(te.format()) + self.assertEqual(msg, "ModuleNotFoundError\n") + + if __name__ == "__main__": unittest.main() diff --git a/Lib/traceback.py b/Lib/traceback.py index 1f9f151ebf5d39..dcf2d04b0d3eee 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -1907,26 +1907,29 @@ def _find_incompatible_extension_module(module_name): import importlib.machinery import importlib.resources.readers - if not module_name or not importlib.machinery.EXTENSION_SUFFIXES: + if not isinstance(module_name, str) or not importlib.machinery.EXTENSION_SUFFIXES: return # We assume the last extension is untagged (eg. .so, .pyd)! # tests.test_traceback.MiscTest.test_find_incompatible_extension_modules # tests that assumption. - untagged_suffix = importlib.machinery.EXTENSION_SUFFIXES[-1] - # On Windows the debug tag is part of the module file stem, instead of the - # extension (eg. foo_d.pyd), so let's remove it and just look for .pyd. - if os.name == 'nt': - untagged_suffix = untagged_suffix.removeprefix('_d') - - parent, _, child = module_name.rpartition('.') - if parent: - traversable = importlib.resources.files(parent) - else: - traversable = importlib.resources.readers.MultiplexedPath( - *map(pathlib.Path, filter(os.path.isdir, sys.path)) - ) + try: + untagged_suffix = importlib.machinery.EXTENSION_SUFFIXES[-1] + # On Windows the debug tag is part of the module file stem, instead of the + # extension (eg. foo_d.pyd), so let's remove it and just look for .pyd. + if os.name == 'nt': + untagged_suffix = untagged_suffix.removeprefix('_d') + + parent, _, child = module_name.rpartition('.') + if parent: + traversable = importlib.resources.files(parent) + else: + traversable = importlib.resources.readers.MultiplexedPath( + *map(pathlib.Path, filter(os.path.isdir, sys.path)) + ) - for entry in traversable.iterdir(): - if entry.name.startswith(child + '.') and entry.name.endswith(untagged_suffix): - return entry.name + for entry in traversable.iterdir(): + if entry.name.startswith(child + '.') and entry.name.endswith(untagged_suffix): + return entry.name + except Exception: + return diff --git a/Misc/NEWS.d/next/Library/2026-03-30-21-11-02.gh-issue-146632.cDa6Yp.rst b/Misc/NEWS.d/next/Library/2026-03-30-21-11-02.gh-issue-146632.cDa6Yp.rst new file mode 100644 index 00000000000000..5a959efe1695dd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-30-21-11-02.gh-issue-146632.cDa6Yp.rst @@ -0,0 +1 @@ +Add type check and use try-except to fix traceback crash