[llvm] [lit] Don't crash in _write_message when getsourcefile() returns None (PR #206957)
David Young via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 13 06:05:57 PDT 2026
https://github.com/youngd007 updated https://github.com/llvm/llvm-project/pull/206957
>From 354908d8eb5af5f59f06c32a5bca2c8757d13eea Mon Sep 17 00:00:00 2001
From: David Young <davidayoung at meta.com>
Date: Wed, 1 Jul 2026 04:53:00 -0700
Subject: [PATCH 1/3] [lit] Don't crash in _write_message when getsourcefile()
returns None
LitConfig._write_message derives the file to report a note()/warning()/
error() against with:
file = os.path.abspath(inspect.getsourcefile(f))
inspect.getsourcefile(f) returns None when the calling frame's source is
not on disk and not in linecache -- for example when lit is packaged into
a zip/par, or when a config is exec'd from a synthetic filename. In that
case os.path.abspath(None) raises 'TypeError: expected str, bytes or
os.PathLike object, not NoneType', which turns an informational diagnostic
into a fatal config-parse crash.
Fall back to inspect.getfile(f), which returns the frame's co_filename and
is always a str, so the diagnostic is emitted (tagged with co_filename)
instead of crashing. Add a lit unit test covering the None case.
---
llvm/utils/lit/lit/LitConfig.py | 8 +++-
llvm/utils/lit/tests/unit/LitConfig.py | 63 ++++++++++++++++++++++++++
2 files changed, 70 insertions(+), 1 deletion(-)
create mode 100644 llvm/utils/lit/tests/unit/LitConfig.py
diff --git a/llvm/utils/lit/lit/LitConfig.py b/llvm/utils/lit/lit/LitConfig.py
index 331320cf8ebc8..f12a983b5f3f5 100644
--- a/llvm/utils/lit/lit/LitConfig.py
+++ b/llvm/utils/lit/lit/LitConfig.py
@@ -222,7 +222,13 @@ def _write_message(self, kind, message):
f = inspect.currentframe()
# Step out of _write_message, and then out of wrapper.
f = f.f_back.f_back
- file = os.path.abspath(inspect.getsourcefile(f))
+ # getsourcefile() returns None when the calling frame's source is not on
+ # disk and not in linecache (e.g. lit packaged into a zip/par, or code
+ # exec'd from a synthetic filename). Fall back to getfile(), which
+ # returns the frame's co_filename and is always a str, so an
+ # informational note()/warning() never crashes with a TypeError from
+ # os.path.abspath(None).
+ file = os.path.abspath(inspect.getsourcefile(f) or inspect.getfile(f))
if lit.util.pythonize_bool(self.params.get("use_normalized_slashes")):
file = file.replace("\\", "/")
line = inspect.getlineno(f)
diff --git a/llvm/utils/lit/tests/unit/LitConfig.py b/llvm/utils/lit/tests/unit/LitConfig.py
new file mode 100644
index 0000000000000..337914b8e9f9e
--- /dev/null
+++ b/llvm/utils/lit/tests/unit/LitConfig.py
@@ -0,0 +1,63 @@
+# RUN: %{python} %s
+
+"""Unit tests for lit.LitConfig."""
+
+import contextlib
+import io
+import platform
+import unittest
+
+from lit.LitConfig import LitConfig
+
+
+def make_lit_config():
+ return LitConfig(
+ progname="lit",
+ path=[],
+ diagnostic_level="note",
+ useValgrind=False,
+ valgrindLeakCheck=False,
+ valgrindArgs=[],
+ noExecute=False,
+ debug=False,
+ isWindows=(platform.system() == "Windows"),
+ order="smart",
+ params={},
+ )
+
+
+class TestWriteMessage(unittest.TestCase):
+ def test_note_survives_getsourcefile_returning_none(self):
+ """note() must not crash when the caller's frame has no source file.
+
+ inspect.getsourcefile() returns None when the calling frame's source is
+ not on disk and not in linecache (e.g. lit packaged into a zip/par).
+ _write_message() then used to do os.path.abspath(None), raising a
+ TypeError and turning an informational note into a fatal error. Simulate
+ that frame by exec'ing a note() call compiled with a filename that does
+ not exist on disk.
+ """
+ lit_config = make_lit_config()
+
+ # co_filename points at a path that is not on disk and not in
+ # linecache, so inspect.getsourcefile() on this frame returns None.
+ fake_filename = "/nonexistent/packaged/lit.cfg.py"
+ code = compile(
+ "lit_config.note('a note from a frame with no source file')",
+ fake_filename,
+ "exec",
+ )
+
+ captured = io.StringIO()
+ with contextlib.redirect_stderr(captured):
+ # Must not raise TypeError: expected str, ... not NoneType.
+ exec(code, {"lit_config": lit_config})
+
+ # The message is still emitted, tagged with the frame's co_filename.
+ output = captured.getvalue()
+ self.assertIn(fake_filename, output)
+ self.assertIn("note:", output)
+
+
+if __name__ == "__main__":
+ unittest.main()
>From 4a7d2bc44f87a5f5f57350b29eb87c74d8aee8d9 Mon Sep 17 00:00:00 2001
From: David Young <davidayoung at meta.com>
Date: Wed, 1 Jul 2026 05:39:18 -0700
Subject: [PATCH 2/3] [lit] Make LitConfig unit test path assertion portable on
Windows
The test asserted the exact forward-slash filename appeared in the
diagnostic output, but _write_message runs the path through
os.path.abspath(), which on Windows rewrites separators to backslashes and
prepends a drive letter (C:\nonexistent\...), so the original string is
not preserved verbatim and the assertion failed there.
Compare on os.path.basename() only, and also assert the message text is
present, so the test stays meaningful and platform-independent.
---
llvm/utils/lit/tests/unit/LitConfig.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/llvm/utils/lit/tests/unit/LitConfig.py b/llvm/utils/lit/tests/unit/LitConfig.py
index 337914b8e9f9e..0f9ee09ddcda0 100644
--- a/llvm/utils/lit/tests/unit/LitConfig.py
+++ b/llvm/utils/lit/tests/unit/LitConfig.py
@@ -4,6 +4,7 @@
import contextlib
import io
+import os
import platform
import unittest
@@ -54,9 +55,13 @@ def test_note_survives_getsourcefile_returning_none(self):
exec(code, {"lit_config": lit_config})
# The message is still emitted, tagged with the frame's co_filename.
+ # Compare on the basename only: _write_message runs the path through
+ # os.path.abspath(), which on Windows rewrites separators and prepends a
+ # drive, so the original string is not preserved verbatim.
output = captured.getvalue()
- self.assertIn(fake_filename, output)
+ self.assertIn(os.path.basename(fake_filename), output)
self.assertIn("note:", output)
+ self.assertIn("a note from a frame with no source file", output)
if __name__ == "__main__":
>From 5d40f7351d80ab33f0c678d9a00f319210b00501 Mon Sep 17 00:00:00 2001
From: David Young <davidayoung at meta.com>
Date: Wed, 1 Jul 2026 07:46:53 -0700
Subject: [PATCH 3/3] [lit] Re-trigger CI (flaky lldb-api TestRunLocker,
unrelated to this change)
No-op commit to re-run CI. The prior run failed only in
lldb-api :: python_api/run_locker/TestRunLocker.py, a known timing-flaky
test (lldb.target convenience var None in the async run-locker window);
this branch touches only llvm/utils/lit and no lldb files.
More information about the llvm-commits
mailing list