diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py index ec72c694390717..ca04eb9ccc4469 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 1fc0236780fa8b..c8044e1341f239 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 00000000000000..cb7ca4eea8e99d --- /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.