From ece8186ae230d7928980f1aa3fd8726668505077 Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Tue, 14 Jul 2026 19:42:26 +0800 Subject: [PATCH] gh-153731: Handle NUL and lone surrogate in sqlite3 CLI REPL --- Lib/sqlite3/__main__.py | 11 +++++++++- Lib/test/test_sqlite3/test_cli.py | 22 +++++++++++++++++++ ...-07-14-19-41-38.gh-issue-153731.ctLI83.rst | 4 ++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-14-19-41-38.gh-issue-153731.ctLI83.rst diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index ec72c6943907175..ca04eb9ccc4469e 100644 --- a/Lib/sqlite3/__main__.py +++ b/Lib/sqlite3/__main__.py @@ -80,7 +80,16 @@ def runsource(self, source, filename="", symbol="single"): self.write(f'{t.type}Error{t.reset}: {t.message}unknown ' f'command: "{unknown}"{t.reset}\n') else: - if not sqlite3.complete_statement(source): + try: + complete = sqlite3.complete_statement(source) + # NUL -> ValueError, lone surrogate -> UnicodeEncodeError; keep + # narrow so real bugs still surface. + except (ValueError, UnicodeEncodeError) as e: + t = theme.traceback + self.write(f"{t.type}{type(e).__name__}{t.reset}: " + f"{t.message}{e}{t.reset}\n") + return False + if not complete: return True execute(self._cur, source, theme=theme) return False diff --git a/Lib/test/test_sqlite3/test_cli.py b/Lib/test/test_sqlite3/test_cli.py index 1fc0236780fa8ba..c8044e1341f2399 100644 --- a/Lib/test/test_sqlite3/test_cli.py +++ b/Lib/test/test_sqlite3/test_cli.py @@ -189,6 +189,28 @@ def test_interact_invalid_sql(self): self.assertEqual(out.count(self.PS1), 2) self.assertEqual(out.count(self.PS2), 0) + def test_interact_null_byte(self): + # NUL byte -> ValueError from complete_statement(). + out, err = self.run_cli(commands=("SELECT '\0';", "SELECT 1;")) + self.assertIn(self.MEMORY_DB_MSG, err) + self.assertNotIn("Traceback (most recent call last)", err) + self.assertIn("ValueError: ", err) + self.assertIn("(1,)\n", out) + self.assertEndsWith(out, self.PS1) + self.assertEqual(out.count(self.PS1), 3) + self.assertEqual(out.count(self.PS2), 0) + + def test_interact_lone_surrogate(self): + # Lone surrogate -> UnicodeEncodeError from complete_statement(). + out, err = self.run_cli(commands=("SELECT '\udc80';", "SELECT 1;")) + self.assertIn(self.MEMORY_DB_MSG, err) + self.assertNotIn("Traceback (most recent call last)", err) + self.assertIn("UnicodeEncodeError: ", err) + self.assertIn("(1,)\n", out) + self.assertEndsWith(out, self.PS1) + self.assertEqual(out.count(self.PS1), 3) + self.assertEqual(out.count(self.PS2), 0) + def test_interact_on_disk_file(self): self.addCleanup(unlink, TESTFN) diff --git a/Misc/NEWS.d/next/Library/2026-07-14-19-41-38.gh-issue-153731.ctLI83.rst b/Misc/NEWS.d/next/Library/2026-07-14-19-41-38.gh-issue-153731.ctLI83.rst new file mode 100644 index 000000000000000..cb7ca4eea8e99d9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-14-19-41-38.gh-issue-153731.ctLI83.rst @@ -0,0 +1,4 @@ +Fix the :mod:`sqlite3` command-line interface so that an input line +containing an embedded null character or a lone surrogate no longer crashes +the interactive shell; an error is now printed and the shell continues. +Patch by tonghuaroot.