bpo-41662: Fix bugs in binding parameters in sqlite3 (GH-21998)


* When the parameters argument is a list, correctly handle the case
  of changing it during iteration.
* When the parameters argument is a custom sequence, no longer
  override an exception raised in ``__len__()``.
(cherry picked from commit 0b419b791077414bbc011a412698ebb362b63761)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
diff --git a/Lib/sqlite3/test/dbapi.py b/Lib/sqlite3/test/dbapi.py
index be11337..ad9c9f0 100644
--- a/Lib/sqlite3/test/dbapi.py
+++ b/Lib/sqlite3/test/dbapi.py
@@ -276,7 +276,7 @@
         self.assertEqual(row[0], "foo")
 
     def CheckExecuteParamSequence(self):
-        class L(object):
+        class L:
             def __len__(self):
                 return 1
             def __getitem__(self, x):
@@ -288,6 +288,18 @@
         row = self.cu.fetchone()
         self.assertEqual(row[0], "foo")
 
+    def CheckExecuteParamSequenceBadLen(self):
+        # Issue41662: Error in __len__() was overridden with ProgrammingError.
+        class L:
+            def __len__(self):
+                1/0
+            def __getitem__(slf, x):
+                raise AssertionError
+
+        self.cu.execute("insert into test(name) values ('foo')")
+        with self.assertRaises(ZeroDivisionError):
+            self.cu.execute("select name from test where name=?", L())
+
     def CheckExecuteDictMapping(self):
         self.cu.execute("insert into test(name) values ('foo')")
         self.cu.execute("select name from test where name=:name", {"name": "foo"})
diff --git a/Lib/sqlite3/test/regression.py b/Lib/sqlite3/test/regression.py
index cbd46d4..6aa86d5 100644
--- a/Lib/sqlite3/test/regression.py
+++ b/Lib/sqlite3/test/regression.py
@@ -133,6 +133,19 @@
         con.execute("insert into foo(bar) values (5)")
         con.execute(SELECT)
 
+    def CheckBindMutatingList(self):
+        # Issue41662: Crash when mutate a list of parameters during iteration.
+        class X:
+            def __conform__(self, protocol):
+                parameters.clear()
+                return "..."
+        parameters = [X(), 0]
+        con = sqlite.connect(":memory:",detect_types=sqlite.PARSE_DECLTYPES)
+        con.execute("create table foo(bar X, baz integer)")
+        # Should not crash
+        with self.assertRaises(IndexError):
+            con.execute("insert into foo(bar, baz) values (?, ?)", parameters)
+
     def CheckErrorMsgDecodeError(self):
         # When porting the module to Python 3.0, the error message about
         # decoding errors disappeared. This verifies they're back again.